From 323e2c142c15f3bd36e75eb5d688a42e7aa026cb Mon Sep 17 00:00:00 2001 From: AI-Casanova <54461896+AI-Casanova@users.noreply.github.com> Date: Sun, 3 Dec 2023 15:06:13 -0600 Subject: [PATCH 001/143] in-mid-out LoRA Weights --- extensions-builtin/Lora/extra_networks_lora.py | 7 +++++-- extensions-builtin/Lora/network.py | 10 ++++++++-- 2 files changed, 13 insertions(+), 4 deletions(-) diff --git a/extensions-builtin/Lora/extra_networks_lora.py b/extensions-builtin/Lora/extra_networks_lora.py index eb9a40b0d..c9995013c 100644 --- a/extensions-builtin/Lora/extra_networks_lora.py +++ b/extensions-builtin/Lora/extra_networks_lora.py @@ -35,8 +35,11 @@ def activate(self, p, params_list): names.append(params.positional[0]) te_multiplier = float(params.positional[1]) if len(params.positional) > 1 else 1.0 te_multiplier = float(params.named.get("te", te_multiplier)) - unet_multiplier = float(params.positional[2]) if len(params.positional) > 2 else te_multiplier - unet_multiplier = float(params.named.get("unet", unet_multiplier)) + unet_multiplier = [float(params.positional[2]) if len(params.positional) > 2 else te_multiplier] * 3 + unet_multiplier = [float(params.named.get("unet", unet_multiplier))] * 3 + unet_multiplier[0] = float(params.named.get("in", unet_multiplier[0])) + unet_multiplier[1] = float(params.named.get("mid", unet_multiplier[1])) + unet_multiplier[2] = float(params.named.get("out", unet_multiplier[2])) dyn_dim = int(params.positional[3]) if len(params.positional) > 3 else None dyn_dim = int(params.named["dyn"]) if "dyn" in params.named else dyn_dim te_multipliers.append(te_multiplier) diff --git a/extensions-builtin/Lora/network.py b/extensions-builtin/Lora/network.py index d7c158770..8445fb361 100644 --- a/extensions-builtin/Lora/network.py +++ b/extensions-builtin/Lora/network.py @@ -83,7 +83,7 @@ def __init__(self, name, network_on_disk: NetworkOnDisk): self.name = name self.network_on_disk = network_on_disk self.te_multiplier = 1.0 - self.unet_multiplier = 1.0 + self.unet_multiplier = [1.0] * 3 self.dyn_dim = None self.modules = {} self.mtime = None @@ -112,8 +112,14 @@ def __init__(self, net: Network, weights: NetworkWeights): def multiplier(self): if 'transformer' in self.sd_key[:20]: return self.network.te_multiplier + if "input_blocks" in self.sd_key: + return self.network.unet_multiplier[0] + if "middle_block" in self.sd_key: + return self.network.unet_multiplier[1] + if "output_blocks" in self.sd_key: + return self.network.unet_multiplier[2] else: - return self.network.unet_multiplier + return self.network.unet_multiplier[0] def calc_scale(self): if self.scale is not None: From b59f79657d88267400eb08c1a2c63e73834df43b Mon Sep 17 00:00:00 2001 From: AI-Casanova <54461896+AI-Casanova@users.noreply.github.com> Date: Sun, 3 Dec 2023 17:20:44 -0600 Subject: [PATCH 002/143] fixes --- extensions-builtin/Lora/extra_networks_lora.py | 2 +- extensions-builtin/Lora/network.py | 6 +++--- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/extensions-builtin/Lora/extra_networks_lora.py b/extensions-builtin/Lora/extra_networks_lora.py index c9995013c..a5d9e1726 100644 --- a/extensions-builtin/Lora/extra_networks_lora.py +++ b/extensions-builtin/Lora/extra_networks_lora.py @@ -36,7 +36,7 @@ def activate(self, p, params_list): te_multiplier = float(params.positional[1]) if len(params.positional) > 1 else 1.0 te_multiplier = float(params.named.get("te", te_multiplier)) unet_multiplier = [float(params.positional[2]) if len(params.positional) > 2 else te_multiplier] * 3 - unet_multiplier = [float(params.named.get("unet", unet_multiplier))] * 3 + unet_multiplier = [float(params.named.get("unet", unet_multiplier[0]))] * 3 unet_multiplier[0] = float(params.named.get("in", unet_multiplier[0])) unet_multiplier[1] = float(params.named.get("mid", unet_multiplier[1])) unet_multiplier[2] = float(params.named.get("out", unet_multiplier[2])) diff --git a/extensions-builtin/Lora/network.py b/extensions-builtin/Lora/network.py index 8445fb361..e5828daf3 100644 --- a/extensions-builtin/Lora/network.py +++ b/extensions-builtin/Lora/network.py @@ -112,11 +112,11 @@ def __init__(self, net: Network, weights: NetworkWeights): def multiplier(self): if 'transformer' in self.sd_key[:20]: return self.network.te_multiplier - if "input_blocks" in self.sd_key: + if "down_blocks" in self.sd_key: return self.network.unet_multiplier[0] - if "middle_block" in self.sd_key: + if "mid_block" in self.sd_key: return self.network.unet_multiplier[1] - if "output_blocks" in self.sd_key: + if "up_blocks" in self.sd_key: return self.network.unet_multiplier[2] else: return self.network.unet_multiplier[0] From 2b49dae74fce9aea74353a993bd7e85797bae486 Mon Sep 17 00:00:00 2001 From: Vladimir Mandic Date: Mon, 4 Dec 2023 18:32:52 -0500 Subject: [PATCH 003/143] en load improvements, torchvision compatibility fix --- CHANGELOG.md | 8 ++++++++ html/locale_en.json | 4 ++-- javascript/extraNetworks.js | 29 ++++++++++++++++++++++++----- modules/extensions.py | 1 + modules/loader.py | 10 ++++++++++ modules/sd_models.py | 3 ++- modules/shared.py | 12 +++--------- modules/theme.py | 12 +++++++----- modules/ui_extensions.py | 5 +++-- modules/ui_extra_networks.py | 36 +++++++++++++++++++----------------- 10 files changed, 79 insertions(+), 41 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 6650290b6..71749b1ca 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,13 @@ # Change Log for SD.Next +## Update for 2023-12-05 + +- **General** + - **Extra networks** new settting -> extra networks -> build info on first access + indexes all networks on first access instead of server startup + - disable google fonts check on server startup + - fix torchvision/basicsr compatibility + ## Update for 2023-12-04 What's new? Native video in SD.Next via both **AnimateDiff** and **Stable-Video-Diffusion** - and including native MP4 encoding and smooth video outputs out-of-the-box, not just animated-GIFs. diff --git a/html/locale_en.json b/html/locale_en.json index 27d4beaa7..82383808f 100644 --- a/html/locale_en.json +++ b/html/locale_en.json @@ -484,8 +484,8 @@ {"id":"","label":"Dark","localized":"","hint":""}, {"id":"","label":"Light","localized":"","hint":""}, {"id":"","label":"Show grid in results","localized":"","hint":""}, - {"id":"","label":"For inpainting, include the greyscale mask in results","localized":"","hint":""}, - {"id":"","label":"For inpainting, include masked composite in results","localized":"","hint":""}, + {"id":"","label":"Inpainting include greyscale mask in results","localized":"","hint":""}, + {"id":"","label":"Inpainting include masked composite in results","localized":"","hint":""}, {"id":"","label":"Do not change selected model when reading generation parameters","localized":"","hint":""}, {"id":"","label":"Send seed when sending prompt or image to other interface","localized":"","hint":""}, {"id":"","label":"Send size when sending prompt or image to another interface","localized":"","hint":""}, diff --git a/javascript/extraNetworks.js b/javascript/extraNetworks.js index 216673ede..075b10f80 100644 --- a/javascript/extraNetworks.js +++ b/javascript/extraNetworks.js @@ -90,14 +90,23 @@ function readCardDescription(page, item) { }); } -async function filterExtraNetworksForTab(tabname, searchTerm) { +function getCardsForActivePage() { + const pagename = getENActivePage(); + if (!pagename) return []; + const allCards = Array.from(gradioApp().querySelectorAll('.extra-network-cards > .card')); + const cards = allCards.filter((el) => el.dataset.page.toLowerCase().includes(pagename.toLowerCase())); + log('getCardsForActivePage', pagename, cards.length); + return allCards; +} + +async function filterExtraNetworksForTab(searchTerm) { let found = 0; let items = 0; const t0 = performance.now(); const pagename = getENActivePage(); if (!pagename) return; const allPages = Array.from(gradioApp().querySelectorAll('.extra-network-cards')); - const pages = allPages.filter((el) => el.id.includes(pagename.toLowerCase())); + const pages = allPages.filter((el) => el.id.toLowerCase().includes(pagename.toLowerCase())); for (const pg of pages) { const cards = Array.from(pg.querySelectorAll('.card') || []); cards.forEach((elem) => { @@ -158,7 +167,7 @@ function sortExtraNetworks() { const pagename = getENActivePage(); if (!pagename) return 'sort error: unknown page'; const allPages = Array.from(gradioApp().querySelectorAll('.extra-network-cards')); - const pages = allPages.filter((el) => el.id.includes(pagename.toLowerCase())); + const pages = allPages.filter((el) => el.id.toLowerCase().includes(pagename.toLowerCase())); let num = 0; for (const pg of pages) { const cards = Array.from(pg.querySelectorAll('.card') || []); @@ -255,8 +264,17 @@ function refeshDetailsEN(args) { return args; } -// init +// refresh on en show +function refreshENpage() { + if (getCardsForActivePage().length === 0) { + log('refreshENpage'); + const tabname = getENActiveTab(); + const btnRefresh = gradioApp().getElementById(`${tabname}_extra_refresh`); + if (btnRefresh) btnRefresh.click(); + } +} +// init function setupExtraNetworksForTab(tabname) { gradioApp().querySelector(`#${tabname}_extra_tabs`).classList.add('extra-networks'); const en = gradioApp().getElementById(`${tabname}_extra_networks`); @@ -307,7 +325,7 @@ function setupExtraNetworksForTab(tabname) { txtSearchValue.addEventListener('input', (evt) => { if (searchTimer) clearTimeout(searchTimer); searchTimer = setTimeout(() => { - filterExtraNetworksForTab(tabname, txtSearchValue.value.toLowerCase()); + filterExtraNetworksForTab(txtSearchValue.value.toLowerCase()); searchTimer = null; }, 150); }); @@ -339,6 +357,7 @@ function setupExtraNetworksForTab(tabname) { el.parentElement.style.width = '-webkit-fill-available'; } if (entries[0].intersectionRatio > 0) { + refreshENpage(); if (window.opts.extra_networks_card_cover === 'cover') { en.style.transition = ''; en.style.zIndex = 100; diff --git a/modules/extensions.py b/modules/extensions.py index 4e37e4611..3ad20acec 100644 --- a/modules/extensions.py +++ b/modules/extensions.py @@ -153,3 +153,4 @@ def list_extensions(): for dirname, path, is_builtin in extension_paths: extension = Extension(name=dirname, path=path, enabled=dirname not in disabled_extensions, is_builtin=is_builtin) extensions.append(extension) + shared.log.info(f'Disabled extensions: {[e.name for e in extensions if not e.enabled]}') diff --git a/modules/loader.py b/modules/loader.py index 8b06f09ac..bb6b120a1 100644 --- a/modules/loader.py +++ b/modules/loader.py @@ -1,5 +1,6 @@ from __future__ import annotations import re +import sys import logging import warnings import urllib3 @@ -55,3 +56,12 @@ errors.log.debug(f'Detected: cores={cores} affinity={affinity} set threads={threads}') except Exception: pass + +try: # fix changed import in torchvision 0.17+, which breaks basicsr + import torchvision.transforms.functional_tensor # pylint: disable=unused-import, ungrouped-imports +except ImportError: + try: + import torchvision.transforms.functional as functional + sys.modules["torchvision.transforms.functional_tensor"] = functional + except ImportError: + pass # shrug... diff --git a/modules/sd_models.py b/modules/sd_models.py index 0956353ce..e123a64b2 100644 --- a/modules/sd_models.py +++ b/modules/sd_models.py @@ -806,7 +806,7 @@ def load_diffuser(checkpoint_info=None, already_loaded_state_dict=None, timer=No sd_model = None try: - if shared.cmd_opts.ckpt is not None and model_data.initial: # initial load + if shared.cmd_opts.ckpt is not None and os.path.isdir(shared.cmd_opts.ckpt) and model_data.initial: # initial load ckpt_basename = os.path.basename(shared.cmd_opts.ckpt) model_name = modelloader.find_diffuser(ckpt_basename) if model_name is not None: @@ -833,6 +833,7 @@ def load_diffuser(checkpoint_info=None, already_loaded_state_dict=None, timer=No if vae is not None: diffusers_load_config["vae"] = vae + shared.log.debug(f'Diffusers loading: path="{checkpoint_info.path}"') if os.path.isdir(checkpoint_info.path): err1 = None err2 = None diff --git a/modules/shared.py b/modules/shared.py index 7c2bea684..2808f7f2a 100644 --- a/modules/shared.py +++ b/modules/shared.py @@ -166,17 +166,14 @@ def temp_disable_extensions(): for ext in disable_safe: if ext not in opts.disabled_extensions: disabled.append(ext) - log.info(f'Safe mode disabling extensions: {disabled}') if backend == Backend.DIFFUSERS: for ext in disable_diffusers: if ext not in opts.disabled_extensions: disabled.append(ext) - log.info(f'Disabling uncompatible extensions: backend={backend} {disabled}') if backend == Backend.ORIGINAL: for ext in disable_original: if ext not in opts.disabled_extensions: disabled.append(ext) - log.info(f'Disabling uncompatible extensions: backend={backend} {disabled}') cmd_opts.controlnet_loglevel = 'WARNING' return disabled @@ -469,8 +466,8 @@ def default(obj): "gallery_height": OptionInfo("", "Gallery height", gr.Textbox), "compact_view": OptionInfo(False, "Compact view"), "return_grid": OptionInfo(True, "Show grid in results"), - "return_mask": OptionInfo(False, "For inpainting, include the greyscale mask in results"), - "return_mask_composite": OptionInfo(False, "For inpainting, include masked composite in results"), + "return_mask": OptionInfo(False, "Inpainting include greyscale mask in results"), + "return_mask_composite": OptionInfo(False, "Inpainting include masked composite in results"), "disable_weights_auto_swap": OptionInfo(True, "Do not change selected model when reading generation parameters"), "send_seed": OptionInfo(True, "Send seed when sending prompt or image to other interface"), "send_size": OptionInfo(True, "Send size when sending prompt or image to another interface"), @@ -601,18 +598,15 @@ def default(obj): "extra_networks_card_size": OptionInfo(160, "UI card size (px)", gr.Slider, {"minimum": 20, "maximum": 2000, "step": 1}), "extra_networks_card_square": OptionInfo(True, "UI disable variable aspect ratio"), "extra_networks_card_fit": OptionInfo("cover", "UI image contain method", gr.Radio, {"choices": ["contain", "cover", "fill"], "visible": False}), - "extra_networks_sep2": OptionInfo("

Extra networks general

", "", gr.HTML), - "extra_network_skip_indexing": OptionInfo(False, "Do not automatically build extra network pages", gr.Checkbox), + "extra_network_skip_indexing": OptionInfo(False, "Build info on first access", gr.Checkbox), "extra_networks_default_multiplier": OptionInfo(1.0, "Default multiplier for extra networks", gr.Slider, {"minimum": 0.0, "maximum": 1.0, "step": 0.01}), - "extra_networks_sep3": OptionInfo("

Extra networks settings

", "", gr.HTML), "extra_networks_styles": OptionInfo(True, "Show built-in styles"), "lora_preferred_name": OptionInfo("filename", "LoRA preffered name", gr.Radio, {"choices": ["filename", "alias"]}), "lora_add_hashes_to_infotext": OptionInfo(True, "LoRA add hash info"), "lora_in_memory_limit": OptionInfo(0, "LoRA memory cache", gr.Slider, {"minimum": 0, "maximum": 24, "step": 1}), "lora_functional": OptionInfo(False, "Use Kohya method for handling multiple LoRA", gr.Checkbox, { "visible": False }), - "sd_hypernetwork": OptionInfo("None", "Add hypernetwork to prompt", gr.Dropdown, { "choices": ["None"], "visible": False }), })) diff --git a/modules/theme.py b/modules/theme.py index 83a14c902..35f98fe55 100644 --- a/modules/theme.py +++ b/modules/theme.py @@ -1,6 +1,5 @@ import os import json -import urllib.request import gradio as gr import modules.shared # from modules.shared import log, opts, req, writefile @@ -51,18 +50,21 @@ def reload_gradio_theme(theme_name=None): if not theme_name: theme_name = modules.shared.opts.gradio_theme default_font_params = {} + """ res = 0 try: + import urllib.request request = urllib.request.Request("https://fonts.googleapis.com/css2?family=IBM+Plex+Mono", method="HEAD") res = urllib.request.urlopen(request, timeout=3.0).status # pylint: disable=consider-using-with except Exception: res = 0 if res != 200: modules.shared.log.info('No internet access detected, using default fonts') - default_font_params = { - 'font':['Helvetica', 'ui-sans-serif', 'system-ui', 'sans-serif'], - 'font_mono':['IBM Plex Mono', 'ui-monospace', 'Consolas', 'monospace'] - } + """ + default_font_params = { + 'font':['Helvetica', 'ui-sans-serif', 'system-ui', 'sans-serif'], + 'font_mono':['IBM Plex Mono', 'ui-monospace', 'Consolas', 'monospace'] + } is_builtin = theme_name in list_builtin_themes() modules.shared.log.info(f'Load UI theme: name="{theme_name}" style={modules.shared.opts.theme_style} base={"sdnext.css" if is_builtin else "base.css"}') if is_builtin: diff --git a/modules/ui_extensions.py b/modules/ui_extensions.py index 6a3e1e6b1..8cf67bc67 100644 --- a/modules/ui_extensions.py +++ b/modules/ui_extensions.py @@ -250,13 +250,14 @@ def refresh_extensions_list(search_text, sort_column): global extensions_list # pylint: disable=global-statement import urllib.request try: - with urllib.request.urlopen(extensions_index) as response: + shared.log.debug(f'Updating extensions list: url={extensions_index}') + with urllib.request.urlopen(extensions_index, timeout=3.0) as response: text = response.read() extensions_list = json.loads(text) with open(os.path.join(paths.script_path, "html", "extensions.json"), "w", encoding="utf-8") as outfile: json_object = json.dumps(extensions_list, indent=2) outfile.write(json_object) - shared.log.debug(f'Updated extensions list: {len(extensions_list)} {extensions_index}') + shared.log.info(f'Updated extensions list: items={len(extensions_list)} url={extensions_index}') except Exception as e: shared.log.warning(f'Updated extensions list failed: {extensions_index} {e}') list_extensions() diff --git a/modules/ui_extra_networks.py b/modules/ui_extra_networks.py index f30dba2cc..f424402e2 100644 --- a/modules/ui_extra_networks.py +++ b/modules/ui_extra_networks.py @@ -343,7 +343,6 @@ def handle_endtag(self, tag): self.text += '\n' fn = os.path.splitext(path)[0] + '.txt' - # if os.path.exists(fn): if fn in listdir(os.path.dirname(path)): try: with open(fn, "r", encoding="utf-8", errors="replace") as f: @@ -364,7 +363,6 @@ def handle_endtag(self, tag): def find_info(self, path): t0 = time.time() fn = os.path.splitext(path)[0] + '.json' - # if os.path.exists(fn): data = {} if fn in listdir(os.path.dirname(path)): data = shared.readfile(fn, silent=True) @@ -382,12 +380,15 @@ def initialize(): def register_page(page: ExtraNetworksPage): # registers extra networks page for the UI; recommend doing it in on_before_ui() callback for extensions debug(f'EN register-page: {page}') + if page in shared.extra_networks: + debug(f'EN register-page: {page} already registered') + return shared.extra_networks.append(page) - allowed_dirs.clear() - for pg in shared.extra_networks: - for folder in pg.allowed_directories_for_previews(): - if folder not in allowed_dirs: - allowed_dirs.append(os.path.abspath(folder)) + # allowed_dirs.clear() + # for pg in shared.extra_networks: + for folder in page.allowed_directories_for_previews(): + if folder not in allowed_dirs: + allowed_dirs.append(os.path.abspath(folder)) def register_pages(): @@ -396,6 +397,7 @@ def register_pages(): from modules.ui_extra_networks_checkpoints import ExtraNetworksPageCheckpoints from modules.ui_extra_networks_styles import ExtraNetworksPageStyles from modules.ui_extra_networks_vae import ExtraNetworksPageVAEs + debug('EN register-pages') register_page(ExtraNetworksPageCheckpoints()) register_page(ExtraNetworksPageStyles()) register_page(ExtraNetworksPageTextualInversion()) @@ -556,15 +558,16 @@ def ui_tab_change(page): if ui.tabname == 'txt2img': # refresh only once global refresh_time # pylint: disable=global-statement refresh_time = time.time() - threads = [] - for page in get_pages(): - if os.environ.get('SD_EN_DEBUG', None) is not None: - threads.append(threading.Thread(target=page.create_items, args=[ui.tabname])) - threads[-1].start() - else: - page.create_items(ui.tabname) - for thread in threads: - thread.join() + if not skip_indexing: + threads = [] + for page in get_pages(): + if os.environ.get('SD_EN_DEBUG', None) is not None: + threads.append(threading.Thread(target=page.create_items, args=[ui.tabname])) + threads[-1].start() + else: + page.create_items(ui.tabname) + for thread in threads: + thread.join() for page in get_pages(): page.create_page(ui.tabname, skip_indexing) with gr.Tab(page.title, id=page.title.lower().replace(" ", "_"), elem_classes="extra-networks-tab") as tab: @@ -574,7 +577,6 @@ def ui_tab_change(page): if shared.cmd_opts.profile: errors.profile(pr, 'ExtraNetworks') pr.disable() - # ui.tabs.change(fn=ui_tab_change, inputs=[], outputs=[ui.button_scan, ui.button_save]) def fn_save_img(image): From 167b903ed825182db577f95c63c79f263fbe50a2 Mon Sep 17 00:00:00 2001 From: Vladimir Mandic Date: Mon, 4 Dec 2023 19:16:12 -0500 Subject: [PATCH 004/143] add additional ip-adapters --- CHANGELOG.md | 8 +++++++- modules/processing.py | 5 ++++- scripts/ipadapter.py | 13 +++++++++---- 3 files changed, 20 insertions(+), 6 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 71749b1ca..dbc1784db 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,8 +2,14 @@ ## Update for 2023-12-05 +*Note*: based on `diffusers==0.25.0.dev0` + +- **Diffusers** + - **IP Adapter** add support for `ip-adapter-plus_sd15` and `ip-adapter-plus-face_sd15` - **General** - - **Extra networks** new settting -> extra networks -> build info on first access + - **LoRA** add support for block weights, thanks @AI-Casanova + example `` + - **Extra networks** new *settting -> extra networks -> build info on first access* indexes all networks on first access instead of server startup - disable google fonts check on server startup - fix torchvision/basicsr compatibility diff --git a/modules/processing.py b/modules/processing.py index b04388d8f..3f115377f 100644 --- a/modules/processing.py +++ b/modules/processing.py @@ -705,13 +705,15 @@ def process_images(p: StableDiffusionProcessing) -> Processed: modules.sd_vae.reload_vae_weights() shared.prompt_styles.apply_styles_to_extra(p) - + print('HERE1') if not shared.opts.cuda_compile: modules.sd_models.apply_token_merging(p.sd_model, p.get_token_merging_ratio()) modules.sd_hijack_freeu.apply_freeu(p, shared.backend == shared.Backend.ORIGINAL) + print('HERE2') modules.script_callbacks.before_process_callback(p) + print('HERE3') if shared.cmd_opts.profile: import cProfile profile_python = cProfile.Profile() @@ -732,6 +734,7 @@ def process_images(p: StableDiffusionProcessing) -> Processed: else: with context_hypertile_vae(p), context_hypertile_unet(p): res = process_images_inner(p) + print('HERE4') finally: if not shared.opts.cuda_compile: diff --git a/scripts/ipadapter.py b/scripts/ipadapter.py index 797ff2785..cadd24c6e 100644 --- a/scripts/ipadapter.py +++ b/scripts/ipadapter.py @@ -8,6 +8,7 @@ - SD/SDXL autodetect """ +import time import gradio as gr from modules import scripts, processing, shared, devices @@ -18,11 +19,11 @@ 'none', 'models/ip-adapter_sd15', 'models/ip-adapter_sd15_light', - # 'models/ip-adapter_sd15_vit-G', # RuntimeError: mat1 and mat2 shapes cannot be multiplied (2x1024 and 1280x3072) - # 'models/ip-adapter-plus_sd15', # KeyError: 'proj.weight' - # 'models/ip-adapter-plus-face_sd15', # KeyError: 'proj.weight' + 'models/ip-adapter-plus_sd15', + 'models/ip-adapter-plus-face_sd15', # 'models/ip-adapter-full-face_sd15', # KeyError: 'proj.weight' 'sdxl_models/ip-adapter_sdxl', + # 'models/ip-adapter_sd15_vit-G', # RuntimeError: mat1 and mat2 shapes cannot be multiplied (2x1024 and 1280x3072) # 'sdxl_models/ip-adapter_sdxl_vit-h', # 'sdxl_models/ip-adapter-plus_sdxl_vit-h', # 'sdxl_models/ip-adapter-plus-face_sdxl_vit-h', @@ -89,13 +90,17 @@ def process(self, p: processing.StableDiffusionProcessing, adapter, scale, image # main code subfolder, model = adapter.split('/') if model != loaded or getattr(shared.sd_model.unet.config, 'encoder_hid_dim_type', None) is None: + t0 = time.time() if loaded is not None: shared.log.debug('IP adapter: reset attention processor') shared.sd_model.unet.set_default_attn_processor() loaded = None - shared.log.info(f'IP adapter load: adapter="{model}" scale={scale} image={image}') + else: + shared.log.debug('IP adapter: load attention processor') shared.sd_model.image_encoder = image_encoder shared.sd_model.load_ip_adapter("h94/IP-Adapter", subfolder=subfolder, weight_name=f'{model}.safetensors') + t1 = time.time() + shared.log.info(f'IP adapter load: adapter="{model}" scale={scale} image={image} time={t1-t0:.2f}') loaded = model else: shared.log.debug(f'IP adapter cache: adapter="{model}" scale={scale} image={image}') From 6cd4abba97d88340ad60fd3108b4b09ff3161621 Mon Sep 17 00:00:00 2001 From: Vladimir Mandic Date: Mon, 4 Dec 2023 19:16:35 -0500 Subject: [PATCH 005/143] cleanup --- modules/processing.py | 4 ---- 1 file changed, 4 deletions(-) diff --git a/modules/processing.py b/modules/processing.py index 3f115377f..d0cbb7a26 100644 --- a/modules/processing.py +++ b/modules/processing.py @@ -705,15 +705,12 @@ def process_images(p: StableDiffusionProcessing) -> Processed: modules.sd_vae.reload_vae_weights() shared.prompt_styles.apply_styles_to_extra(p) - print('HERE1') if not shared.opts.cuda_compile: modules.sd_models.apply_token_merging(p.sd_model, p.get_token_merging_ratio()) modules.sd_hijack_freeu.apply_freeu(p, shared.backend == shared.Backend.ORIGINAL) - print('HERE2') modules.script_callbacks.before_process_callback(p) - print('HERE3') if shared.cmd_opts.profile: import cProfile profile_python = cProfile.Profile() @@ -734,7 +731,6 @@ def process_images(p: StableDiffusionProcessing) -> Processed: else: with context_hypertile_vae(p), context_hypertile_unet(p): res = process_images_inner(p) - print('HERE4') finally: if not shared.opts.cuda_compile: From bf5ea244a8a02536cb17ef3f064b0ce8e41f2fcb Mon Sep 17 00:00:00 2001 From: Vladimir Mandic Date: Tue, 5 Dec 2023 09:26:55 -0500 Subject: [PATCH 006/143] add hdr settings to metadata --- CHANGELOG.md | 1 + modules/processing_correction.py | 3 +++ 2 files changed, 4 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index dbc1784db..7e57de16e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -13,6 +13,7 @@ indexes all networks on first access instead of server startup - disable google fonts check on server startup - fix torchvision/basicsr compatibility + - add hdr settings to metadata ## Update for 2023-12-04 diff --git a/modules/processing_correction.py b/modules/processing_correction.py index 704805db4..3e8d6994e 100644 --- a/modules/processing_correction.py +++ b/modules/processing_correction.py @@ -54,10 +54,13 @@ def maximize_tensor(input_tensor, boundary=1.0, channels=[0, 1, 2]): # pylint: d def correction_callback(p, timestep, kwags): if timestep > 950 and p.hdr_clamp: + p.extra_generation_params["HDR clamp"] = f'{p.hdr_threshold}/{p.hdr_boundary}' kwags["latents"] = soft_clamp_tensor(kwags["latents"], threshold=p.hdr_threshold, boundary=p.hdr_boundary) if timestep > 700 and p.hdr_center: + p.extra_generation_params["HDR center"] = f'{p.hdr_channel_shift}/{p.hdr_full_shift}' kwags["latents"] = center_tensor(kwags["latents"], channel_shift=p.hdr_channel_shift, full_shift=p.hdr_full_shift) if timestep > 1 and timestep < 100 and p.hdr_maximize: + p.extra_generation_params["HDR max"] = f'{p.hdr_max_center}/p.hdr_max_boundry' kwags["latents"] = center_tensor(kwags["latents"], channel_shift=p.hdr_max_center, full_shift=1.0) kwags["latents"] = maximize_tensor(kwags["latents"], boundary=p.hdr_max_boundry) return kwags From 635efb0203edda696518407e4b2b7cd20fcabcbd Mon Sep 17 00:00:00 2001 From: Vladimir Mandic Date: Tue, 5 Dec 2023 10:04:32 -0500 Subject: [PATCH 007/143] fix save metadata --- modules/images.py | 16 ++++++++-------- modules/ui_common.py | 7 ++++--- 2 files changed, 12 insertions(+), 11 deletions(-) diff --git a/modules/images.py b/modules/images.py index 76c4a3cc6..0dbe64e81 100644 --- a/modules/images.py +++ b/modules/images.py @@ -508,6 +508,14 @@ def atomically_save_image(): if shared.opts.image_watermark_enabled: image = set_watermark(image, shared.opts.image_watermark) shared.log.debug(f'Saving: image="{fn}" type={image_format} size={image.width}x{image.height}') + # additional metadata saved in files + if shared.opts.save_txt and len(exifinfo) > 0: + try: + with open(filename_txt, "w", encoding="utf8") as file: + file.write(f"{exifinfo}\n") + shared.log.debug(f'Saving: text="{filename_txt}" len={len(exifinfo)}') + except Exception as e: + shared.log.warning(f'Image description save failed: {filename_txt} {e}') # actual save exifinfo = (exifinfo or "") if shared.opts.image_metadata else "" if image_format == 'PNG': @@ -543,14 +551,6 @@ def atomically_save_image(): image.save(fn, format=image_format, quality=shared.opts.jpeg_quality) except Exception as e: shared.log.warning(f'Image save failed: {fn} {e}') - # additional metadata saved in files - if shared.opts.save_txt and len(exifinfo) > 0: - try: - with open(filename_txt, "w", encoding="utf8") as file: - file.write(f"{exifinfo}\n") - shared.log.debug(f'Saving: text="{filename_txt}"') - except Exception as e: - shared.log.warning(f'Image description save failed: {filename_txt} {e}') with open(os.path.join(paths.data_path, "params.txt"), "w", encoding="utf8") as file: file.write(exifinfo) if shared.opts.save_log_fn != '' and len(exifinfo) > 0: diff --git a/modules/ui_common.py b/modules/ui_common.py index d63c1ce7a..bd34e475e 100644 --- a/modules/ui_common.py +++ b/modules/ui_common.py @@ -157,15 +157,16 @@ def __init__(self, d=None): fullfns.append(fullfn) if txt_fullfn: filenames.append(os.path.basename(txt_fullfn)) - fullfns.append(txt_fullfn) + # fullfns.append(txt_fullfn) modules.script_callbacks.image_save_btn_callback(filename) if shared.opts.samples_save_zip and len(fullfns) > 1: zip_filepath = os.path.join(shared.opts.outdir_save, "images.zip") from zipfile import ZipFile with ZipFile(zip_filepath, "w") as zip_file: for i in range(len(fullfns)): - with open(fullfns[i], mode="rb") as f: - zip_file.writestr(filenames[i], f.read()) + if os.path.isfile(fullfns[i]): + with open(fullfns[i], mode="rb") as f: + zip_file.writestr(filenames[i], f.read()) fullfns.insert(0, zip_filepath) return gr.File.update(value=fullfns, visible=True), plaintext_to_html(f"Saved: {filenames[0] if len(filenames) > 0 else 'none'}") From 3a36c07c6c734c6e31964ddd40d3320ed527242f Mon Sep 17 00:00:00 2001 From: Disty0 Date: Tue, 5 Dec 2023 19:22:40 +0300 Subject: [PATCH 008/143] Check SD_LORA_DIFFUSERS flag once in networks.py --- extensions-builtin/Lora/networks.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/extensions-builtin/Lora/networks.py b/extensions-builtin/Lora/networks.py index 40f19bde1..05e36e8db 100644 --- a/extensions-builtin/Lora/networks.py +++ b/extensions-builtin/Lora/networks.py @@ -39,6 +39,7 @@ network_norm.ModuleTypeNorm(), ] convert_diffusers_name_to_compvis = lora_convert.convert_diffusers_name_to_compvis # supermerger compatibility item +force_lora_diffusers = os.environ.get('SD_LORA_DIFFUSERS', None) is not None # OpenVINO only works with Diffusers LoRa loading def assign_network_names_to_compvis_modules(sd_model): @@ -170,7 +171,7 @@ def load_networks(names, te_multipliers=None, unet_multipliers=None, dyn_dims=No try: if recompile_model: shared.compiled_model_state.lora_model.append(f"{name}:{te_multipliers[i] if te_multipliers else 1.0}") - if shared.backend == shared.Backend.DIFFUSERS and (os.environ.get('SD_LORA_DIFFUSERS', None) is not None): # OpenVINO only works with Diffusers LoRa loading. + if shared.backend == shared.Backend.DIFFUSERS and force_lora_diffusers: # OpenVINO only works with Diffusers LoRa loading. # or getattr(network_on_disk, 'shorthash', '').lower() == 'aaebf6360f7d' # sd15-lcm # or getattr(network_on_disk, 'shorthash', '').lower() == '3d18b05e4f56' # sdxl-lcm # or getattr(network_on_disk, 'shorthash', '').lower() == '813ea5fb1c67' # turbo sdxl-turbo From 629a46aaa5efd59edd49b78539f104f3f40958eb Mon Sep 17 00:00:00 2001 From: Disty0 Date: Tue, 5 Dec 2023 19:27:14 +0300 Subject: [PATCH 009/143] Disable IPEX attention if the GPU supports 64 bit --- modules/intel/ipex/__init__.py | 5 +++-- modules/intel/ipex/gradscaler.py | 6 +++++- 2 files changed, 8 insertions(+), 3 deletions(-) diff --git a/modules/intel/ipex/__init__.py b/modules/intel/ipex/__init__.py index cbadd14fc..851bc79c4 100644 --- a/modules/intel/ipex/__init__.py +++ b/modules/intel/ipex/__init__.py @@ -166,8 +166,9 @@ def ipex_init(): # pylint: disable=too-many-statements torch.cuda.get_device_id_list_per_card = torch.xpu.get_device_id_list_per_card ipex_hijacks() - attention_init() - ipex_diffusers() + if not torch.xpu.has_fp64_dtype(): + attention_init() + ipex_diffusers() except Exception as e: return False, e return True, None diff --git a/modules/intel/ipex/gradscaler.py b/modules/intel/ipex/gradscaler.py index 530212101..6eb56bc2b 100644 --- a/modules/intel/ipex/gradscaler.py +++ b/modules/intel/ipex/gradscaler.py @@ -5,6 +5,7 @@ # pylint: disable=protected-access, missing-function-docstring, line-too-long +device_supports_fp64 = torch.xpu.has_fp64_dtype() OptState = ipex.cpu.autocast._grad_scaler.OptState _MultiDeviceReplicator = ipex.cpu.autocast._grad_scaler._MultiDeviceReplicator _refresh_per_optimizer_state = ipex.cpu.autocast._grad_scaler._refresh_per_optimizer_state @@ -96,7 +97,10 @@ def unscale_(self, optimizer): # FP32 division can be imprecise for certain compile options, so we carry out the reciprocal in FP64. assert self._scale is not None - inv_scale = self._scale.to("cpu").double().reciprocal().float().to(self._scale.device) + if device_supports_fp64: + inv_scale = self._scale.double().reciprocal().float() + else: + inv_scale = self._scale.to("cpu").double().reciprocal().float().to(self._scale.device) found_inf = torch.full( (1,), 0.0, dtype=torch.float32, device=self._scale.device ) From 7c16ea6b1da29ce06f5af1c5cc74d233ba3c66df Mon Sep 17 00:00:00 2001 From: Vladimir Mandic Date: Tue, 5 Dec 2023 11:27:45 -0500 Subject: [PATCH 010/143] add modelscope --- CHANGELOG.md | 2 ++ html/reference.json | 5 +++++ .../damo-vilab--text-to-video-ms-1.7b.jpg | Bin 0 -> 18687 bytes modules/processing_diffusers.py | 10 ++++++++-- scripts/animatediff.py | 2 ++ scripts/stablevideodiffusion.py | 12 ++++++++---- 6 files changed, 25 insertions(+), 6 deletions(-) create mode 100644 models/Reference/damo-vilab--text-to-video-ms-1.7b.jpg diff --git a/CHANGELOG.md b/CHANGELOG.md index 7e57de16e..481507267 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,6 +6,8 @@ - **Diffusers** - **IP Adapter** add support for `ip-adapter-plus_sd15` and `ip-adapter-plus-face_sd15` + - added support for basic [ModelScope T2V](https://huggingface.co/damo-vilab/text-to-video-ms-1.7b) model + - simply select from *networks -> reference* and use from *txt2img* tab - **General** - **LoRA** add support for block weights, thanks @AI-Casanova example `` diff --git a/html/reference.json b/html/reference.json index a3ff809ed..44a4b15e2 100644 --- a/html/reference.json +++ b/html/reference.json @@ -88,5 +88,10 @@ "path": "thu-ml/unidiffuser-v1", "desc": "UniDiffuser is a unified diffusion framework to fit all distributions relevant to a set of multi-modal data in one transformer. UniDiffuser is able to perform image, text, text-to-image, image-to-text, and image-text pair generation by setting proper timesteps without additional overhead.\nSpecifically, UniDiffuser employs a variation of transformer, called U-ViT, which parameterizes the joint noise prediction network. Other components perform as encoders and decoders of different modalities, including a pretrained image autoencoder from Stable Diffusion, a pretrained image ViT-B/32 CLIP encoder, a pretrained text ViT-L CLIP encoder, and a GPT-2 text decoder finetuned by ourselves.", "preview": "thu-ml--unidiffuser-v1.jpg" + }, + "ModelScope T2V": { + "path": "damo-vilab/text-to-video-ms-1.7b", + "desc": "The text-to-video generation diffusion model consists of three sub-networks: text feature extraction model, text feature-to-video latent space diffusion model, and video latent space to video visual space model. The overall model parameters are about 1.7 billion. Currently, it only supports English input. The diffusion model adopts a UNet3D structure, and implements video generation through the iterative denoising process from the pure Gaussian noise video.", + "preview": "damo-vilab--text-to-video-ms-1.7b.jpg" } } \ No newline at end of file diff --git a/models/Reference/damo-vilab--text-to-video-ms-1.7b.jpg b/models/Reference/damo-vilab--text-to-video-ms-1.7b.jpg new file mode 100644 index 0000000000000000000000000000000000000000..39ad83eea52da9122edccbafef77ba0b6d47f6fb GIT binary patch literal 18687 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 literal 0 HcmV?d00001 diff --git a/modules/processing_diffusers.py b/modules/processing_diffusers.py index e90cd0126..95b2c1c41 100644 --- a/modules/processing_diffusers.py +++ b/modules/processing_diffusers.py @@ -164,7 +164,12 @@ def vae_decode(latents, model, output_type='np', full_quality=True): decoded = taesd_vae_decode(latents=latents) # TODO validate decoded sample diffusers # decoded = validate_sample(decoded) - imgs = model.image_processor.postprocess(decoded, output_type=output_type) + if hasattr(model, 'image_processor'): + imgs = model.image_processor.postprocess(decoded, output_type=output_type) + else: + import diffusers + image_processor = diffusers.image_processor.VaeImageProcessor() + imgs = image_processor.postprocess(decoded, output_type=output_type) shared.state.job = prev_job if shared.cmd_opts.profile: t1 = time.time() @@ -345,7 +350,8 @@ def set_pipeline_args(model, prompts: list, negative_prompts: list, prompts_2: t args[arg] = task_kwargs[arg] task_args = getattr(p, 'task_args', {}) for k, v in task_args.items(): - args[k] = v + if k in possible: + args[k] = v hypertile_set(p, hr=len(getattr(p, 'init_images', []))) clean = args.copy() diff --git a/scripts/animatediff.py b/scripts/animatediff.py index 4b0ef5d9c..531335761 100644 --- a/scripts/animatediff.py +++ b/scripts/animatediff.py @@ -152,6 +152,8 @@ def process(self, p: processing.StableDiffusionProcessing, adapter_index, frames p.extra_generation_params['AnimateDiff Lora'] = f'{lora}:{strength}' p.extra_generation_params['AnimateDiff'] = loaded_adapter p.do_not_save_grid = True + if 'animatediff' not in p.ops: + p.ops.append('animatediff') p.task_args['num_frames'] = frames p.task_args['num_inference_steps'] = p.steps if not latent_mode: diff --git a/scripts/stablevideodiffusion.py b/scripts/stablevideodiffusion.py index 16077cc2c..116bf9636 100644 --- a/scripts/stablevideodiffusion.py +++ b/scripts/stablevideodiffusion.py @@ -44,7 +44,8 @@ def video_type_change(video_type): return [num_frames, override_resolution, min_guidance_scale, max_guidance_scale, decode_chunk_size, motion_bucket_id, noise_aug_strength, video_type, duration, gif_loop, mp4_pad, mp4_interpolate] def run(self, p: processing.StableDiffusionProcessing, num_frames, override_resolution, min_guidance_scale, max_guidance_scale, decode_chunk_size, motion_bucket_id, noise_aug_strength, video_type, duration, gif_loop, mp4_pad, mp4_interpolate): # pylint: disable=arguments-differ, unused-argument - if shared.sd_model is None or shared.sd_model.__class__.__name__ != 'StableVideoDiffusionPipeline': + c = shared.sd_model.__class__.__name__ if shared.sd_model is not None else '' + if c != 'StableVideoDiffusionPipeline' and c != 'TextToVideoSDPipeline': return None if hasattr(p, 'init_images') and len(p.init_images) > 0: if override_resolution: @@ -53,9 +54,13 @@ def run(self, p: processing.StableDiffusionProcessing, num_frames, override_reso p.task_args['image'] = images.resize_image(resize_mode=2, im=p.init_images[0], width=p.width, height=p.height, upscaler_name=None, output_type='pil') else: p.task_args['image'] = p.init_images[0] - p.ops.append('svd') + p.ops.append('stablevideo') p.do_not_save_grid = True - p.sampler_name = 'Default' # svd does not support non-default sampler + if c == 'StableVideoDiffusionPipeline': + p.sampler_name = 'Default' # svd does not support non-default sampler + p.task_args['output_type'] = 'np' + else: + p.task_args['output_type'] = 'pil' p.task_args['generator'] = torch.manual_seed(p.seed) # svd does not support gpu based generator p.task_args['width'] = p.width p.task_args['height'] = p.height @@ -66,7 +71,6 @@ def run(self, p: processing.StableDiffusionProcessing, num_frames, override_reso p.task_args['num_inference_steps'] = p.steps p.task_args['min_guidance_scale'] = min_guidance_scale p.task_args['max_guidance_scale'] = max_guidance_scale - p.task_args['output_type'] = 'np' shared.log.debug(f'StableVideo: args={p.task_args}') shared.sd_model = sd_models.set_diffuser_pipe(shared.sd_model, sd_models.DiffusersTaskType.IMAGE_2_IMAGE) processed = processing.process_images(p) From c8970ab3615d42fda62daa78358a62177acb7476 Mon Sep 17 00:00:00 2001 From: Disty0 Date: Tue, 5 Dec 2023 19:48:15 +0300 Subject: [PATCH 011/143] Cleanup IPEX libs --- modules/intel/ipex/__init__.py | 28 +++++++++++++++++----------- modules/intel/ipex/diffusers.py | 2 +- modules/intel/ipex/hijacks.py | 23 +++++++++++------------ 3 files changed, 29 insertions(+), 24 deletions(-) diff --git a/modules/intel/ipex/__init__.py b/modules/intel/ipex/__init__.py index 851bc79c4..662572c87 100644 --- a/modules/intel/ipex/__init__.py +++ b/modules/intel/ipex/__init__.py @@ -4,14 +4,12 @@ import torch import intel_extension_for_pytorch as ipex # pylint: disable=import-error, unused-import from .hijacks import ipex_hijacks -from .attention import attention_init -from .diffusers import ipex_diffusers # pylint: disable=protected-access, missing-function-docstring, line-too-long def ipex_init(): # pylint: disable=too-many-statements try: - #Replace cuda with xpu: + # Replace cuda with xpu: torch.cuda.current_device = torch.xpu.current_device torch.cuda.current_stream = torch.xpu.current_stream torch.cuda.device = torch.xpu.device @@ -92,9 +90,9 @@ def ipex_init(): # pylint: disable=too-many-statements torch.cuda.CharStorage = torch.xpu.CharStorage torch.cuda.__file__ = torch.xpu.__file__ torch.cuda._is_in_bad_fork = torch.xpu.lazy_init._is_in_bad_fork - #torch.cuda.is_current_stream_capturing = torch.xpu.is_current_stream_capturing + # torch.cuda.is_current_stream_capturing = torch.xpu.is_current_stream_capturing - #Memory: + # Memory: torch.cuda.memory = torch.xpu.memory if 'linux' in sys.platform and "WSL2" in os.popen("uname -a").read(): torch.xpu.empty_cache = lambda: None @@ -114,7 +112,7 @@ def ipex_init(): # pylint: disable=too-many-statements torch.cuda.memory_stats_as_nested_dict = torch.xpu.memory_stats_as_nested_dict torch.cuda.reset_accumulated_memory_stats = torch.xpu.reset_accumulated_memory_stats - #RNG: + # RNG: torch.cuda.get_rng_state = torch.xpu.get_rng_state torch.cuda.get_rng_state_all = torch.xpu.get_rng_state_all torch.cuda.set_rng_state = torch.xpu.set_rng_state @@ -125,7 +123,7 @@ def ipex_init(): # pylint: disable=too-many-statements torch.cuda.seed_all = torch.xpu.seed_all torch.cuda.initial_seed = torch.xpu.initial_seed - #AMP: + # AMP: torch.cuda.amp = torch.xpu.amp if not hasattr(torch.cuda.amp, "common"): torch.cuda.amp.common = contextlib.nullcontext() @@ -140,12 +138,12 @@ def ipex_init(): # pylint: disable=too-many-statements except Exception: # pylint: disable=broad-exception-caught torch.cuda.amp.GradScaler = ipex.cpu.autocast._grad_scaler.GradScaler - #C + # C torch._C._cuda_getCurrentRawStream = ipex._C._getCurrentStream ipex._C._DeviceProperties.major = 2023 ipex._C._DeviceProperties.minor = 2 - #Fix functions with ipex: + # Fix functions with ipex: torch.cuda.mem_get_info = lambda device=None: [(torch.xpu.get_device_properties(device).total_memory - torch.xpu.memory_reserved(device)), torch.xpu.get_device_properties(device).total_memory] torch._utils._get_available_device_type = lambda: "xpu" torch.has_cuda = True @@ -167,8 +165,16 @@ def ipex_init(): # pylint: disable=too-many-statements ipex_hijacks() if not torch.xpu.has_fp64_dtype(): - attention_init() - ipex_diffusers() + try: + from .attention import attention_init + attention_init() + except Exception: # pylint: disable=broad-exception-caught + pass + try: + from .diffusers import ipex_diffusers + ipex_diffusers() + except Exception: # pylint: disable=broad-exception-caught + pass except Exception as e: return False, e return True, None diff --git a/modules/intel/ipex/diffusers.py b/modules/intel/ipex/diffusers.py index 005ee49f0..c32af507b 100644 --- a/modules/intel/ipex/diffusers.py +++ b/modules/intel/ipex/diffusers.py @@ -1,6 +1,6 @@ import torch import intel_extension_for_pytorch as ipex # pylint: disable=import-error, unused-import -import diffusers #0.21.1 # pylint: disable=import-error +import diffusers #0.24.0 # pylint: disable=import-error from diffusers.models.attention_processor import Attention # pylint: disable=protected-access, missing-function-docstring, line-too-long diff --git a/modules/intel/ipex/hijacks.py b/modules/intel/ipex/hijacks.py index cdd83cea8..ddc38e3fc 100644 --- a/modules/intel/ipex/hijacks.py +++ b/modules/intel/ipex/hijacks.py @@ -65,7 +65,7 @@ def ipex_autocast(*args, **kwargs): else: return original_autocast(*args, **kwargs) -#Embedding BF16 +# Embedding BF16 original_torch_cat = torch.cat def torch_cat(tensor, *args, **kwargs): if len(tensor) == 3 and (tensor[0].dtype != tensor[1].dtype or tensor[2].dtype != tensor[1].dtype): @@ -73,7 +73,7 @@ def torch_cat(tensor, *args, **kwargs): else: return original_torch_cat(tensor, *args, **kwargs) -#Latent antialias: +# Latent antialias: original_interpolate = torch.nn.functional.interpolate def interpolate(tensor, size=None, scale_factor=None, mode='nearest', align_corners=None, recompute_scale_factor=None, antialias=False): # pylint: disable=too-many-arguments if antialias or align_corners is not None: @@ -136,7 +136,7 @@ def ipex_hijacks(): lambda orig_func, device=None: torch.xpu.Generator(return_xpu(device)), lambda orig_func, device=None: device is not None and device != torch.device("cpu") and device != "cpu") - #TiledVAE and ControlNet: + # TiledVAE and ControlNet: CondFunc('torch.batch_norm', lambda orig_func, input, weight, bias, *args, **kwargs: orig_func(input, weight if weight is not None else torch.ones(input.size()[1], device=input.device), @@ -148,42 +148,41 @@ def ipex_hijacks(): bias if bias is not None else torch.zeros(input.size()[1], device=input.device), *args, **kwargs), lambda orig_func, input, *args, **kwargs: input.device != torch.device("cpu")) - #Functions with dtype errors: - #Original backend: + # Functions with dtype errors: CondFunc('torch.nn.modules.GroupNorm.forward', lambda orig_func, self, input: orig_func(self, input.to(self.weight.data.dtype)), lambda orig_func, self, input: input.dtype != self.weight.data.dtype) - #Hypernetwork training: + # Training: CondFunc('torch.nn.modules.linear.Linear.forward', lambda orig_func, self, input: orig_func(self, input.to(self.weight.data.dtype)), lambda orig_func, self, input: input.dtype != self.weight.data.dtype) CondFunc('torch.nn.modules.conv.Conv2d.forward', lambda orig_func, self, input: orig_func(self, input.to(self.weight.data.dtype)), lambda orig_func, self, input: input.dtype != self.weight.data.dtype) - #BF16: + # BF16: CondFunc('torch.nn.functional.layer_norm', lambda orig_func, input, normalized_shape=None, weight=None, *args, **kwargs: orig_func(input.to(weight.data.dtype), normalized_shape, weight, *args, **kwargs), lambda orig_func, input, normalized_shape=None, weight=None, *args, **kwargs: weight is not None and input.dtype != weight.data.dtype) - #SwinIR BF16: + # SwinIR BF16: CondFunc('torch.nn.functional.pad', lambda orig_func, input, pad, mode='constant', value=None: orig_func(input.to(torch.float32), pad, mode=mode, value=value).to(dtype=torch.bfloat16), lambda orig_func, input, pad, mode='constant', value=None: mode == 'reflect' and input.dtype == torch.bfloat16) - #Diffusers Float64 (ARC GPUs doesn't support double or Float64): + # Diffusers Float64 (Alchemist GPUs doesn't support 64 bit): if not torch.xpu.has_fp64_dtype(): CondFunc('torch.from_numpy', lambda orig_func, ndarray: orig_func(ndarray.astype('float32')), lambda orig_func, ndarray: ndarray.dtype == float) - #Broken functions when torch.cuda.is_available is True: - #Pin Memory: + # Broken functions when torch.cuda.is_available is True: + # Pin Memory: CondFunc('torch.utils.data.dataloader._BaseDataLoaderIter.__init__', lambda orig_func, *args, **kwargs: ipex_no_cuda(orig_func, *args, **kwargs), lambda orig_func, *args, **kwargs: True) - #Functions that make compile mad with CondFunc: + # Functions that make compile mad with CondFunc: torch.utils.data.dataloader._MultiProcessingDataLoaderIter._shutdown_workers = _shutdown_workers torch.nn.DataParallel = DummyDataParallel torch.autocast = ipex_autocast From 6cec284a6ff8d88cd53c74d7eadc8167481fa262 Mon Sep 17 00:00:00 2001 From: Vladimir Mandic Date: Tue, 5 Dec 2023 11:49:59 -0500 Subject: [PATCH 012/143] cleanup compile --- modules/sd_models_compile.py | 18 ++++++++++-------- 1 file changed, 10 insertions(+), 8 deletions(-) diff --git a/modules/sd_models_compile.py b/modules/sd_models_compile.py index 67fc32740..ece694085 100644 --- a/modules/sd_models_compile.py +++ b/modules/sd_models_compile.py @@ -77,6 +77,7 @@ def compile_stablefast(sd_model): config.enable_cuda_graph = shared.opts.cuda_compile_fullgraph config.enable_jit_freeze = shared.opts.diffusers_eval config.memory_format = torch.channels_last if shared.opts.opt_channelslast else torch.contiguous_format + # config.trace_scheduler = False # config.enable_cnn_optimization # config.prefer_lowp_gemm try: @@ -108,12 +109,17 @@ def compile_torch(sd_model): torch._dynamo.config.suppress_errors = shared.opts.cuda_compile_errors # pylint: disable=protected-access t0 = time.time() if shared.opts.cuda_compile: - sd_model.unet = torch.compile(sd_model.unet, mode=shared.opts.cuda_compile_mode, backend=shared.opts.cuda_compile_backend, fullgraph=shared.opts.cuda_compile_fullgraph) + if shared.opts.cuda_compile and (not hasattr(sd_model, 'unet') or not hasattr(sd_model.unet, 'config')): + shared.log.warning('Model compile enabled but model has no Unet') + else: + sd_model.unet = torch.compile(sd_model.unet, mode=shared.opts.cuda_compile_mode, backend=shared.opts.cuda_compile_backend, fullgraph=shared.opts.cuda_compile_fullgraph) if shared.opts.cuda_compile_vae: - if hasattr(sd_model, 'vae'): + if hasattr(sd_model, 'vae') and hasattr(sd_model.vae, 'decode'): sd_model.vae.decode = torch.compile(sd_model.vae.decode, mode=shared.opts.cuda_compile_mode, backend=shared.opts.cuda_compile_backend, fullgraph=shared.opts.cuda_compile_fullgraph) - if hasattr(sd_model, 'movq'): + elif hasattr(sd_model, 'movq') and hasattr(sd_model.movq, 'decode'): sd_model.movq.decode = torch.compile(sd_model.movq.decode, mode=shared.opts.cuda_compile_mode, backend=shared.opts.cuda_compile_backend, fullgraph=shared.opts.cuda_compile_fullgraph) + else: + shared.log.warning('Model compile enabled but model has no VAE') setup_logging() # compile messes with logging so reset is needed if shared.opts.cuda_compile_precompile: sd_model("dummy prompt") @@ -127,14 +133,10 @@ def compile_torch(sd_model): def compile_diffusers(sd_model): if not (shared.opts.cuda_compile or shared.opts.cuda_compile_vae or shared.opts.cuda_compile_upscaler): return sd_model - if not hasattr(sd_model, 'unet') or not hasattr(sd_model.unet, 'config'): - shared.log.warning('Model compile enabled but model has no Unet') - return sd_model if shared.opts.cuda_compile_backend == 'none': shared.log.warning('Model compile enabled but no backend specified') return sd_model - size = 8*getattr(sd_model.unet.config, 'sample_size', 0) - shared.log.info(f"Model compile: pipeline={sd_model.__class__.__name__} shape={size} mode={shared.opts.cuda_compile_mode} backend={shared.opts.cuda_compile_backend} fullgraph={shared.opts.cuda_compile_fullgraph} unet={shared.opts.cuda_compile} vae={shared.opts.cuda_compile_vae} upscaler={shared.opts.cuda_compile_upscaler}") + shared.log.info(f"Model compile: pipeline={sd_model.__class__.__name__} mode={shared.opts.cuda_compile_mode} backend={shared.opts.cuda_compile_backend} fullgraph={shared.opts.cuda_compile_fullgraph} unet={shared.opts.cuda_compile} vae={shared.opts.cuda_compile_vae} upscaler={shared.opts.cuda_compile_upscaler}") if shared.opts.cuda_compile_backend == 'stable-fast': sd_model = compile_stablefast(sd_model) else: From 0febcc2aa84131d2ec737056eabb0ad1ad35fe29 Mon Sep 17 00:00:00 2001 From: Vladimir Mandic Date: Tue, 5 Dec 2023 14:07:52 -0500 Subject: [PATCH 013/143] animatediff full latent mode --- CHANGELOG.md | 2 + README.md | 3 +- modules/processing_correction.py | 76 ++++++++++++++++++++------------ modules/processing_diffusers.py | 24 +++++++--- scripts/animatediff.py | 2 +- 5 files changed, 70 insertions(+), 37 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 481507267..b1c40ef95 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,6 +6,8 @@ - **Diffusers** - **IP Adapter** add support for `ip-adapter-plus_sd15` and `ip-adapter-plus-face_sd15` + - **AnimateDiff** can now be used with *second pass* and *hdr controls* - enhance, upscale and hires your videos! + - **HDE controls** are now batch-aware for enhancement of multiple images - added support for basic [ModelScope T2V](https://huggingface.co/damo-vilab/text-to-video-ms-1.7b) model - simply select from *networks -> reference* and use from *txt2img* tab - **General** diff --git a/README.md b/README.md index 2f22500ca..f2841e787 100644 --- a/README.md +++ b/README.md @@ -60,7 +60,7 @@ Additional models will be added as they become available and there is public int - [RunwayML Stable Diffusion](https://github.com/Stability-AI/stablediffusion/) 1.x and 2.x *(all variants)* - [StabilityAI Stable Diffusion XL](https://github.com/Stability-AI/generative-models) -- [StabilityAI Stable Video Diffusion Base and XT](https://huggingface.co/stabilityai/stable-video-diffusion-img2vid) +- [StabilityAI Stable Video Diffusion](https://huggingface.co/stabilityai/stable-video-diffusion-img2vid) Base and XT - [Segmind SSD-1B](https://huggingface.co/segmind/SSD-1B) - [LCM: Latent Consistency Models](https://github.com/openai/consistency_models) - [Kandinsky](https://github.com/ai-forever/Kandinsky-2) *2.1 and 2.2 and latest 3.0* @@ -68,6 +68,7 @@ Additional models will be added as they become available and there is public int - [Warp Wuerstchen](https://huggingface.co/blog/wuertschen) - [Tsinghua UniDiffusion](https://github.com/thu-ml/unidiffuser) - [DeepFloyd IF](https://github.com/deep-floyd/IF) *Medium and Large* +- [ModelScope T2V](https://huggingface.co/damo-vilab/text-to-video-ms-1.7b) - [Segmind SD Distilled](https://huggingface.co/blog/sd_distillation) *(all variants)* Also supported are modifiers such as: diff --git a/modules/processing_correction.py b/modules/processing_correction.py index 3e8d6994e..d0f189dcc 100644 --- a/modules/processing_correction.py +++ b/modules/processing_correction.py @@ -11,56 +11,74 @@ debug = shared.log.info if os.environ.get('SD_HDR_DEBUG', None) is not None else lambda *args, **kwargs: None -def soft_clamp_tensor(input_tensor, threshold=0.8, boundary=4): +def soft_clamp_tensor(tensor, threshold=0.8, boundary=4): # shrinking towards the mean; will also remove outliers - if max(abs(input_tensor.max()), abs(input_tensor.min())) < boundary or threshold == 0: - return input_tensor + if max(abs(tensor.max()), abs(tensor.min())) < boundary or threshold == 0: + return tensor channel_dim = 1 threshold *= boundary - max_vals = input_tensor.max(channel_dim, keepdim=True)[0] - max_replace = ((input_tensor - threshold) / (max_vals - threshold)) * (boundary - threshold) + threshold - over_mask = input_tensor > threshold - min_vals = input_tensor.min(channel_dim, keepdim=True)[0] - min_replace = ((input_tensor + threshold) / (min_vals + threshold)) * (-boundary + threshold) - threshold - under_mask = input_tensor < -threshold + max_vals = tensor.max(channel_dim, keepdim=True)[0] + max_replace = ((tensor - threshold) / (max_vals - threshold)) * (boundary - threshold) + threshold + over_mask = tensor > threshold + min_vals = tensor.min(channel_dim, keepdim=True)[0] + min_replace = ((tensor + threshold) / (min_vals + threshold)) * (-boundary + threshold) - threshold + under_mask = tensor < -threshold debug(f'HDE soft clamp: threshold={threshold} boundary={boundary}') - input_tensor = torch.where(over_mask, max_replace, torch.where(under_mask, min_replace, input_tensor)) - return input_tensor + tensor = torch.where(over_mask, max_replace, torch.where(under_mask, min_replace, tensor)) + return tensor -def center_tensor(input_tensor, channel_shift=1.0, full_shift=1.0, channels=[0, 1, 2, 3]): # pylint: disable=dangerous-default-value # noqa: B006 +def center_tensor(tensor, channel_shift=1.0, full_shift=1.0, channels=[0, 1, 2, 3]): # pylint: disable=dangerous-default-value # noqa: B006 if channel_shift == 0 and full_shift == 0: - return input_tensor + return tensor means = [] for channel in channels: - means.append(input_tensor[0, channel].mean()) - input_tensor[0, channel] -= means[-1] * channel_shift + means.append(tensor[0, channel].mean()) + tensor[0, channel] -= means[-1] * channel_shift debug(f'HDR center: channel-shift{channel_shift} full-shift={full_shift} means={torch.stack(means)}') - input_tensor = input_tensor - input_tensor.mean() * full_shift - return input_tensor + tensor = tensor - tensor.mean() * full_shift + return tensor -def maximize_tensor(input_tensor, boundary=1.0, channels=[0, 1, 2]): # pylint: disable=dangerous-default-value # noqa: B006 +def maximize_tensor(tensor, boundary=1.0, channels=[0, 1, 2]): # pylint: disable=dangerous-default-value # noqa: B006 if boundary == 1.0: - return input_tensor + return tensor boundary *= 4 - min_val = input_tensor.min() - max_val = input_tensor.max() + min_val = tensor.min() + max_val = tensor.max() normalization_factor = boundary / max(abs(min_val), abs(max_val)) - input_tensor[0, channels] *= normalization_factor + tensor[0, channels] *= normalization_factor debug(f'HDR maximize: boundary={boundary} min={min_val} max={max_val} factor={normalization_factor}') - return input_tensor + return tensor -def correction_callback(p, timestep, kwags): +def correction(p, timestep, latent): if timestep > 950 and p.hdr_clamp: p.extra_generation_params["HDR clamp"] = f'{p.hdr_threshold}/{p.hdr_boundary}' - kwags["latents"] = soft_clamp_tensor(kwags["latents"], threshold=p.hdr_threshold, boundary=p.hdr_boundary) + latent = soft_clamp_tensor(latent, threshold=p.hdr_threshold, boundary=p.hdr_boundary) if timestep > 700 and p.hdr_center: p.extra_generation_params["HDR center"] = f'{p.hdr_channel_shift}/{p.hdr_full_shift}' - kwags["latents"] = center_tensor(kwags["latents"], channel_shift=p.hdr_channel_shift, full_shift=p.hdr_full_shift) + latent = center_tensor(latent, channel_shift=p.hdr_channel_shift, full_shift=p.hdr_full_shift) if timestep > 1 and timestep < 100 and p.hdr_maximize: p.extra_generation_params["HDR max"] = f'{p.hdr_max_center}/p.hdr_max_boundry' - kwags["latents"] = center_tensor(kwags["latents"], channel_shift=p.hdr_max_center, full_shift=1.0) - kwags["latents"] = maximize_tensor(kwags["latents"], boundary=p.hdr_max_boundry) - return kwags + latent = center_tensor(latent, channel_shift=p.hdr_max_center, full_shift=1.0) + latent = maximize_tensor(latent, boundary=p.hdr_max_boundry) + return latent + + +def correction_callback(p, timestep, kwargs): + if not p.hdr_clamp and not p.hdr_center and not p.hdr_maximize: + return kwargs + latents = kwargs["latents"] + if len(latents.shape) == 4: # standard batched latent + for i in range(latents.shape[0]): + latents[i] = correction(p, timestep, latents[i]) + elif len(latents.shape) == 5 and latents.shape[0] == 1: # probably animatediff + latents = latents.squeeze(0).permute(1, 0, 2, 3) + for i in range(latents.shape[0]): + latents[i] = correction(p, timestep, latents[i]) + latents = latents.permute(1, 0, 2, 3).unsqueeze(0) + else: + shared.log.debug(f'HDR correction: unknown latent shape {latents.shape}') + kwargs["latents"] = latents + return kwargs diff --git a/modules/processing_diffusers.py b/modules/processing_diffusers.py index 95b2c1c41..512b67314 100644 --- a/modules/processing_diffusers.py +++ b/modules/processing_diffusers.py @@ -37,6 +37,10 @@ def process_diffusers(p: StableDiffusionProcessing, seeds, prompts, negative_pro p.mask_for_overlay = images.resize_image(1, p.mask_for_overlay, tgt_width, tgt_height, upscaler_name=None) def hires_resize(latents): # input=latents output=pil + if not torch.is_tensor(latents): + shared.log.warning('Hires: input is not tensor') + first_pass_images = vae_decode(latents=latents, model=shared.sd_model, full_quality=p.full_quality, output_type='pil') + return first_pass_images latent_upscaler = shared.latent_upscale_modes.get(p.hr_upscaler, None) shared.log.info(f'Hires: upscaler={p.hr_upscaler} width={p.hr_upscale_to_x} height={p.hr_upscale_to_y} images={latents.shape[0]}') if latent_upscaler is not None: @@ -59,9 +63,10 @@ def save_intermediate(latents, suffix): for j in range(len(decoded)): images.save_image(decoded[j], path=p.outpath_samples, basename="", seed=seeds[i], prompt=prompts[i], extension=shared.opts.samples_format, info=info, p=p, suffix=suffix) - def diffusers_callback_legacy(step: int, _timestep: int, latents: torch.FloatTensor): + def diffusers_callback_legacy(step: int, timestep: int, latents: torch.FloatTensor): shared.state.sampling_step = step shared.state.current_latent = latents + latents = correction_callback(p, timestep, {'latents': latents}) if shared.state.interrupted or shared.state.skipped: raise AssertionError('Interrupted...') if shared.state.paused: @@ -386,9 +391,9 @@ def set_pipeline_args(model, prompts: list, negative_prompts: list, prompts_2: t shared.log.debug(f'Diffuser pipeline: {model.__class__.__name__} task={sd_models.get_diffusers_task(model)} set={clean}') if p.hdr_clamp or p.hdr_center or p.hdr_maximize: txt = 'HDR:' - txt += f' Clamp threshold={p.hdr_threshold} boundary={p.hdr_boundary}' if p.hdr_clamp else 'Clamp off' - txt += f' Center channel-shift={p.hdr_channel_shift} full-shift={p.hdr_full_shift}' if p.hdr_center else 'Center off' - txt += f' Maximize boundary={p.hdr_max_boundry} center={p.hdr_max_center}' if p.hdr_maximize else 'Maximize off' + txt += f' Clamp threshold={p.hdr_threshold} boundary={p.hdr_boundary}' if p.hdr_clamp else ' Clamp off' + txt += f' Center channel-shift={p.hdr_channel_shift} full-shift={p.hdr_full_shift}' if p.hdr_center else ' Center off' + txt += f' Maximize boundary={p.hdr_max_boundry} center={p.hdr_max_center}' if p.hdr_maximize else ' Maximize off' shared.log.debug(txt) # components = [{ k: getattr(v, 'device', None) } for k, v in model.components.items()] # shared.log.debug(f'Diffuser pipeline components: {components}') @@ -659,8 +664,15 @@ def calculate_refiner_steps(): # final decode since there is no refiner if not is_refiner_enabled: - if output is not None and output.images is not None and len(output.images) > 0: - results = vae_decode(latents=output.images, model=shared.sd_model, full_quality=p.full_quality) + if output is not None: + if not hasattr(output, 'images') and hasattr(output, 'frames'): + shared.log.debug(f'Generated: frames={len(output.frames[0])}') + output.images = output.frames[0] + if output.images is not None and len(output.images) > 0: + results = vae_decode(latents=output.images, model=shared.sd_model, full_quality=p.full_quality) + else: + shared.log.warning('Processing returned no results') + results = [] else: shared.log.warning('Processing returned no results') results = [] diff --git a/scripts/animatediff.py b/scripts/animatediff.py index 531335761..00100b3b8 100644 --- a/scripts/animatediff.py +++ b/scripts/animatediff.py @@ -128,7 +128,7 @@ def video_type_change(video_type): lora_index = gr.Dropdown(label='Lora', choices=list(LORAS), value='None') strength = gr.Slider(label='Strength', minimum=0.0, maximum=2.0, step=0.05, value=1.0) with gr.Row(): - latent_mode = gr.Checkbox(label='Latent mode', value=False) + latent_mode = gr.Checkbox(label='Latent mode', value=True, visible=False) with gr.Row(): video_type = gr.Dropdown(label='Video file', choices=['None', 'GIF', 'PNG', 'MP4'], value='None') duration = gr.Slider(label='Duration', minimum=0.25, maximum=10, step=0.25, value=2, visible=False) From 6031b7199e7cd4ba561c6f7dc8d766176ed27e34 Mon Sep 17 00:00:00 2001 From: Disty0 Date: Tue, 5 Dec 2023 22:20:52 +0300 Subject: [PATCH 014/143] IPEX fix torch.UntypedStorage.is_cuda --- modules/intel/ipex/hijacks.py | 1 + 1 file changed, 1 insertion(+) diff --git a/modules/intel/ipex/hijacks.py b/modules/intel/ipex/hijacks.py index ddc38e3fc..207ed69b2 100644 --- a/modules/intel/ipex/hijacks.py +++ b/modules/intel/ipex/hijacks.py @@ -93,6 +93,7 @@ def linalg_solve(A, B, *args, **kwargs): # pylint: disable=invalid-name else: return original_linalg_solve(A, B, *args, **kwargs) +@property def is_cuda(self): return self.device.type == 'xpu' From 327d15a999a413d2de5ae516d38097fc5aeb9c8e Mon Sep 17 00:00:00 2001 From: Vladimir Mandic Date: Tue, 5 Dec 2023 14:42:04 -0500 Subject: [PATCH 015/143] update changelog --- CHANGELOG.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index b1c40ef95..d188ada24 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,8 +6,8 @@ - **Diffusers** - **IP Adapter** add support for `ip-adapter-plus_sd15` and `ip-adapter-plus-face_sd15` - - **AnimateDiff** can now be used with *second pass* and *hdr controls* - enhance, upscale and hires your videos! - - **HDE controls** are now batch-aware for enhancement of multiple images + - **AnimateDiff** can now be used with *second pass* - enhance, upscale and hires your videos! + - **HDE controls** are now batch-aware for enhancement of multiple images or video frames - added support for basic [ModelScope T2V](https://huggingface.co/damo-vilab/text-to-video-ms-1.7b) model - simply select from *networks -> reference* and use from *txt2img* tab - **General** From f6bf51516ee24421a0c05cd40496e4bc16c42ca3 Mon Sep 17 00:00:00 2001 From: Disty0 Date: Wed, 6 Dec 2023 11:32:20 +0300 Subject: [PATCH 016/143] Use devices.dtype with IP Adapter --- scripts/ipadapter.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/scripts/ipadapter.py b/scripts/ipadapter.py index cadd24c6e..f581b4868 100644 --- a/scripts/ipadapter.py +++ b/scripts/ipadapter.py @@ -82,7 +82,7 @@ def process(self, p: processing.StableDiffusionProcessing, adapter, scale, image return if image_encoder is None: try: - image_encoder = CLIPVisionModelWithProjection.from_pretrained("h94/IP-Adapter", subfolder=subfolder, torch_dtype=torch.float16, cache_dir=shared.opts.diffusers_dir, use_safetensors=True).to(devices.device) + image_encoder = CLIPVisionModelWithProjection.from_pretrained("h94/IP-Adapter", subfolder=subfolder, torch_dtype=devices.dtype, cache_dir=shared.opts.diffusers_dir, use_safetensors=True).to(devices.device) except Exception as e: shared.log.error(f'IP adapter: failed to load image encoder: {e}') return From 71743dfda3cbfde2c5bb16dc82c7efc9a546f6c0 Mon Sep 17 00:00:00 2001 From: Vladimir Mandic Date: Wed, 6 Dec 2023 10:48:37 -0500 Subject: [PATCH 017/143] add playground models --- CHANGELOG.md | 11 ++++++---- README.md | 15 +++++++------ html/reference.json | 20 ++++++++++++++++++ .../Reference/playgroundai--playground-v1.jpg | Bin 0 -> 50781 bytes ...oundai--playground-v2-1024px-aesthetic.jpg | Bin 0 -> 90463 bytes ...playgroundai--playground-v2-256px-base.jpg | Bin 0 -> 102070 bytes ...playgroundai--playground-v2-512px-base.jpg | Bin 0 -> 102824 bytes ...yai--stable-video-diffusion-img2vid-xt.jpg | Bin 349667 -> 56620 bytes ...lityai--stable-video-diffusion-img2vid.jpg | Bin 349667 -> 57029 bytes modules/shared.py | 2 +- wiki | 2 +- 11 files changed, 37 insertions(+), 13 deletions(-) create mode 100644 models/Reference/playgroundai--playground-v1.jpg create mode 100644 models/Reference/playgroundai--playground-v2-1024px-aesthetic.jpg create mode 100644 models/Reference/playgroundai--playground-v2-256px-base.jpg create mode 100644 models/Reference/playgroundai--playground-v2-512px-base.jpg diff --git a/CHANGELOG.md b/CHANGELOG.md index d188ada24..8db1f12af 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,20 +1,23 @@ # Change Log for SD.Next -## Update for 2023-12-05 +## Update for 2023-12-06 *Note*: based on `diffusers==0.25.0.dev0` - **Diffusers** - - **IP Adapter** add support for `ip-adapter-plus_sd15` and `ip-adapter-plus-face_sd15` - **AnimateDiff** can now be used with *second pass* - enhance, upscale and hires your videos! - - **HDE controls** are now batch-aware for enhancement of multiple images or video frames - - added support for basic [ModelScope T2V](https://huggingface.co/damo-vilab/text-to-video-ms-1.7b) model + - **IP Adapter** add support for `ip-adapter-plus_sd15` and `ip-adapter-plus-face_sd15` + - **HDR controls** are now batch-aware for enhancement of multiple images or video frames + - [Playground v1](https://huggingface.co/playgroundai/playground-v1), [Playground v2 256](https://huggingface.co/playgroundai/playground-v2-256px-base), [Playground v2 512](https://huggingface.co/playgroundai/playground-v2-512px-base), [Playground v2 1024](https://huggingface.co/playgroundai/playground-v2-1024px-aesthetic) model support + - simply select from *networks -> reference* and use as usual + - [ModelScope T2V](https://huggingface.co/damo-vilab/text-to-video-ms-1.7b) model support - simply select from *networks -> reference* and use from *txt2img* tab - **General** - **LoRA** add support for block weights, thanks @AI-Casanova example `` - **Extra networks** new *settting -> extra networks -> build info on first access* indexes all networks on first access instead of server startup + - **Ipex** improvements, thanks @disty0 - disable google fonts check on server startup - fix torchvision/basicsr compatibility - add hdr settings to metadata diff --git a/README.md b/README.md index f2841e787..4c9e62ce3 100644 --- a/README.md +++ b/README.md @@ -58,14 +58,15 @@ All individual features are not listed here, instead check [ChangeLog](CHANGELOG Additional models will be added as they become available and there is public interest in them -- [RunwayML Stable Diffusion](https://github.com/Stability-AI/stablediffusion/) 1.x and 2.x *(all variants)* -- [StabilityAI Stable Diffusion XL](https://github.com/Stability-AI/generative-models) -- [StabilityAI Stable Video Diffusion](https://huggingface.co/stabilityai/stable-video-diffusion-img2vid) Base and XT -- [Segmind SSD-1B](https://huggingface.co/segmind/SSD-1B) -- [LCM: Latent Consistency Models](https://github.com/openai/consistency_models) -- [Kandinsky](https://github.com/ai-forever/Kandinsky-2) *2.1 and 2.2 and latest 3.0* -- [PixArt-α XL 2](https://github.com/PixArt-alpha/PixArt-alpha) *Medium and Large* +- [RunwayML Stable Diffusion](https://github.com/Stability-AI/stablediffusion/) 1.x and 2.x *(all variants)* +- [StabilityAI Stable Diffusion XL](https://github.com/Stability-AI/generative-models) +- [StabilityAI Stable Video Diffusion](https://huggingface.co/stabilityai/stable-video-diffusion-img2vid) Base and XT +- [Segmind SSD-1B](https://huggingface.co/segmind/SSD-1B) +- [LCM: Latent Consistency Models](https://github.com/openai/consistency_models) +- [Kandinsky](https://github.com/ai-forever/Kandinsky-2) *2.1 and 2.2 and latest 3.0* +- [PixArt-α XL 2](https://github.com/PixArt-alpha/PixArt-alpha) *Medium and Large* - [Warp Wuerstchen](https://huggingface.co/blog/wuertschen) +- [Playground](https://huggingface.co/playgroundai/playground-v2-256px-base) *v1, v2 256, v2 512, v2 1024* - [Tsinghua UniDiffusion](https://github.com/thu-ml/unidiffuser) - [DeepFloyd IF](https://github.com/deep-floyd/IF) *Medium and Large* - [ModelScope T2V](https://huggingface.co/damo-vilab/text-to-video-ms-1.7b) diff --git a/html/reference.json b/html/reference.json index 44a4b15e2..e8da810dd 100644 --- a/html/reference.json +++ b/html/reference.json @@ -79,6 +79,26 @@ "desc": "Kandinsky 3.0 is an open-source text-to-image diffusion model built upon the Kandinsky2-x model family. In comparison to its predecessors, Kandinsky 3.0 incorporates more data and specifically related to Russian culture, which allows to generate pictures related to Russin culture. Furthermore, enhancements have been made to the text understanding and visual quality of the model, achieved by increasing the size of the text encoder and Diffusion U-Net models, respectively.", "preview": "kandinsky-community--kandinsky-3.jpg" }, + "Playground v1": { + "path": "playgroundai/playground-v1", + "desc": "Playground v1 is a latent diffusion model that improves the overall HDR quality to get more stunning images.", + "preview": "playgroundai--playground-v1.jpg" + }, + "Playground v2 256": { + "path": "playgroundai/playground-v2-256px-base", + "desc": "Playground v2 is a diffusion-based text-to-image generative model. The model was trained from scratch by the research team at Playground. Images generated by Playground v2 are favored 2.5 times more than those produced by Stable Diffusion XL, according to Playground’s user study.", + "preview": "playgroundai--playground-v2-256px-base.jpg" + }, + "Playground v2 512": { + "path": "playgroundai/playground-v2-512px-base", + "desc": "Playground v2 is a diffusion-based text-to-image generative model. The model was trained from scratch by the research team at Playground. Images generated by Playground v2 are favored 2.5 times more than those produced by Stable Diffusion XL, according to Playground’s user study.", + "preview": "playgroundai--playground-v2-512px-base.jpg" + }, + "Playground v2 1024": { + "path": "playgroundai/playground-v2-1024px-aesthetic", + "desc": "Playground v2 is a diffusion-based text-to-image generative model. The model was trained from scratch by the research team at Playground. Images generated by Playground v2 are favored 2.5 times more than those produced by Stable Diffusion XL, according to Playground’s user study.", + "preview": "playgroundai--playground-v2-1024px-aesthetic.jpg" + }, "DeepFloyd IF Medium": { "path": "DeepFloyd/IF-I-M-v1.0", "desc": "DeepFloyd-IF is a pixel-based text-to-image triple-cascaded diffusion model, that can generate pictures with new state-of-the-art for photorealism and language understanding. The result is a highly efficient model that outperforms current state-of-the-art models, achieving a zero-shot FID-30K score of 6.66 on the COCO dataset. It is modular and composed of frozen text mode and three pixel cascaded diffusion modules, each designed to generate images of increasing resolution: 64x64, 256x256, and 1024x1024.", diff --git a/models/Reference/playgroundai--playground-v1.jpg b/models/Reference/playgroundai--playground-v1.jpg new file mode 100644 index 0000000000000000000000000000000000000000..5a2cf4b5bf8d4aff86d73224077f3951b21ad178 GIT binary patch literal 50781 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 z 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 Zx50{~)5Ez)^g?>6^`{qphW^E$|Jk}JdZz#Y literal 0 HcmV?d00001 diff --git a/models/Reference/playgroundai--playground-v2-256px-base.jpg b/models/Reference/playgroundai--playground-v2-256px-base.jpg new file mode 100644 index 0000000000000000000000000000000000000000..42a332b91800d6d367fbc72245b2907c61b7903e GIT binary patch literal 102070 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 literal 0 HcmV?d00001 diff --git a/models/Reference/playgroundai--playground-v2-512px-base.jpg b/models/Reference/playgroundai--playground-v2-512px-base.jpg new file mode 100644 index 0000000000000000000000000000000000000000..759945df410b995be2202e7bd0c1b5af5a780df9 GIT binary patch literal 102824 zcmb@tWmp|e(=Ix&aCf)h9^9Sa!QF$)!rci;&>+EsCrEI2hhV`Sf(CbY=d9$B_ubz) zKla(z9?~=SR998kT|Kj=yJzzJ^LYipke8N|2B4szfH#mo;CU4=m-4c)1OP=vfDr%y z1ONt#2!MqkC`b{3BK{XkL(v1EKXPbDB|8Aw189(<1S!9_H<0q%=Fic|#Kysm)xpAz zf}52McwR#vlarJ*R#8)ymU}G)X&wO3J4~FM?4U6Kz}~^lMNLM6LPuAR0)87n1h4^I z00&?;F>`eiS5bKl@d^BM{Eh#!UCsPy5twB8?dw1C|1E@RZsBSM30VcAZEEgnW)HzN z005J2=H%iA0C2zMRGw~5zcHi$I4+O?Avo+ew)zXF|Hh_&;fg;x8fuadoo+}XQA|u+ ztRN{GgUBg7%&Z_j&`uzj&B5Hp0m7dGf<^4@9nB&54FuCebetggw|_p<|MFw?U)aRN z^k1JQCRYFAzqmjwAq~H>adomY@&0r2e{*MV=MKr&pN$r>N3nL1Rf8OHAbI-g%G$5D&g2OCarPTk{`QqZJ_IF#*b(SuY;(yD3n7BaDUu`YB zgEfE4fBQePwvhYVKM2joT0~X`>c8WG!~hcVv6lQT|D)sL zuJ${AYKUCT!a+s?f+1W$9+ocBzq$X`kFa-zpg-I}U#xB9e%nIw0IG7c){uo@NPa-w zW+s2)hj0OnSvZLQofD9Ug{#i**dY4Qb>Up3Nz3E%=)0Tuwu-;#eUwExsNK=$f@4{!)L zKy+OHwUhW$YXNvbS60aSk{I381mj5IDkES?;@9V#9-T!Hk0{tI*I4QVSaI$dHa11a+Fd{I5 zFs~tdF-YNn;e+A-hsS^PVYgt%Vb@@%VOL=1jBVbz{Er+d0xJ+kf922ulHdQz1q?03 zCpU})j4;GI#4`*Y3=zNr!v$$a97Yt3c^ z_=sYN^#9WmjS-FeAHIKb{ok4VD^}Bg^Zic*|L^wyQ)2^|L*i8Uw>SQd02BtQ2K9kD zLH(c>P(45aY6mrdhCr>q@!#!B|LI+;e_K=g6Au{DFE;<`{KNO39{+uPxUvDTL8wV!<{C>Xt)&YQX)j!V-D7>Nn z$lm_~0O9uM=jZ1C$mH7rppqAI`#1haM$ZQTn8*O|qtVRW#p5r3FuxD5kY5RO$el(A zkO5QxJ%kSjzyk;ZVt^DN4=4i~fG%JJxn8Xy*SQPe3HSj)KnU;=_yi;ZsX!Kx0~7$I zKow9Av;dtzFE9v<0n@+&CD3)yozR2O)6gr>`_Pvl0E7&} z1HAyTfcQX?AZ3sq$P(lN3IIibl0ey@GEftwpC>^ppaak?3>*wL34uqvS%o=)d4ffSC4psv6@Zn4)q%Bw^@I(9O@jRfTMOF*xtBI! zui)U|@Zjj+cp>+qE}Si#FI*&C23$E@2i!Q^I@~2ZJUk&h6TC3IGQ0`A8+-_S3VaEC zJNyLvCj1QoG6FdQ2ZA($9)bhHJA@>JB7`=C350EgdqfOG8bkp^WkhpCZ^RhHT*M~C zQN&Hedn8OGIwWBv4J2EnAf(SoWk@|pi%93l$jFq){K%@vHpp+0KOMX9VX2 z7YmmQR}0q*Hx0KHcLfiGM~f$iXNMPqSA{o&cZW}kFOF}4AC6y!KY@QuKujP;U_lT; zP);ySa7Rc%C`AY+j3ulmTq1%YVkA-}@+8V6>LEHH#vv9YHY1K8t|Fc%fhJ)jQ6ups z$srjcxg;ecl_7N^O(E?fJs`s)6C<-FOCW0_+a<>)7a_MHPatn6-=n~#5T~%C_)O7F zaq@!Xh3pIW7hhkDytt>Nqtv8)M_Ep}M1@4fM`cNsK=qyKgqoaMk=l>Cn0lTDk%o`P znkJd1kLH?|j#isCl(wFBhmL?wj?Ra!m~N3Cm0pzInLe9-iUE#+j{(e(!7#=M&B)DY z&6vhG$^^~C!(_vh!8Fbc%goR0z?{uI%Ywop&f>vR%(BXg$NHM}9cw-7(M#%=dM{&N z_Pu;!<7TsC%Vk?&$6}Xff5+a$e#XJbVaAchF~y0>Da{$k*}!?q#l&U7mC5y!8=G5^ zJCyr7_ahH4j|)#3&n_=D?;GB9-dR2zJ{7)5z90Os{1W_u{4M;q0z3k40#yRXf-Hhy z!6LyOAvz%oppQyiSz-Fk?t(=w;Y%L}X-V)M|`wY+_vf z2KJ5io1!-lCdww+CYPpirs<|9W|C&fX8Y!1<_YGz7Qz;B7TcD>mT{IlR>D^CR=d`s z)=Aa}Hj*}}HmA0-wpq5+vZspVPih3VzsHRR3c9qzsDBkhyt z3*&3%``zz_Uy$FbzqtR`0H^@dfbW4+fgb`l-^#o#2to=12Mxbtdl&ca;=T6!rVpea z-hNmQmI*EnK?`vSnGF>T%?g7Fvk4ms=L}B?e~K`V82HHcG3nz&q*>%Z6noU?sOMJ?bg@r{VMKQ(D#qPzsB?cv9rLRgG%2>+2mgALw ztN<$9EA}eiRL)c>RDG}Jt1hddsY$QJu8pXJs`IWpskg3QY0zyLZL)&w^Py1zuQ^!H4Rp)p57wB&s;2WqPV zbFz8ra{7E0c}{qqf5CO}{Zi#}`O5z4{yO4@@TTCF@3!ww`)>Q*^8xOQ7a-$O5Hw^U0S$s|Fpz(+2(Yj) zFtAAQ@NftyNGK@CNXW>jXjmAiXqafo$QXDSnAkYDxVR|j_yl-31Xwt@IKQ1hfgm<8 zu!yj*h&ZUos5t+>x91K369F24dICXV0nnIGAWW#|Zh#z;QCP@u39|je7YZ5@BOE*e zA`&u0p%w#xh5~`0VL-p5hG_dk>H!!`SgaT9;&9liCh(NbI2=K-UlFJzDm!u2#!sj@ zOQO8%UZnw^uI_btDmu&AoKrnauWp|RL*t`ED zX8#iV53hLu1q21@JP;-z3fv&8v^!#g1!Q$682XYyvg0KR*p4e3?x3>0WR+;HQ@kvT zlKDO}++rC=cbX!X@le5Bs`~}y^%`dbhWmO!`?Apby-b{&pyIt5BBRFAh1#Cg=;h_d z_3-YXeh?OJKh+q9lkLkJZ4q2U@!ZMFB?Y~cevQw1^WL#33F&rFVIVMu++lP`!&Kr0 z_so1IK^uPR%P`c{TUN>t^Qowhi$kks(pYw^!*k+RR>?COsDNcTp`Vt)^lp?1{^Cpg@28M>yel0b!C8G-QB{rcqlpvUw^yOLXBLcna z871rk#T%uxyqLY%YK`3pS^R7fK_`v*`4+Oj8X9{0MQO+a&EO9>UfJQAGcoGS_9zcmLF|Dtjrz&<2_1kllZ~$| zO{x;hxDd(1JW$}frd`y!(jlqLhX<;X9p|5W6@4#1jZ{fjHlhrnpTu74ILY|jd#;Ff zr72_hv4)DDV(1fKtsmBLj#eOtEkff{Z@eiNpL%NJ+8^GIc{>c?z3LlE6PM75R&oKG^=6^6a2ccT3)mlpqAoo+u$HWL%npNk0d zOe}J&EImEh!3MdND@oG(hv_S`<4>G7An>{)^1MjAH%-(|q~G55pmR{Ck&GB0iMB$p zR~}z+&Pdd2p@k0@^w#Y}e&}ma@~DDx)6YPS;}NMEpGk<=NZ%xd^RZ4AoFCI_I{;Q= zt^5{fwZHs^O$j%X9}WrY!^LQRDOeJhv24O7z)IMUTfp|T`5{KFc^qE!rO*CWC5nJ) zE7-<$s7h$&Tw*CJsk!i?qWd(}b|)0aTWUM>?xA)^+edagqE?Vlz*8vtTzvmb`>T<4 zS5{JuI)_^fW%JmN(Ltji@9ZFmBr?)MF;l_&Yi5AnaXFV!JBxL0N@QVm|Ivl|=%C@| zR_yF%DSvD-O$>9>SSPthb8{&bahqOq6+b#t^Vn(nOWWhJnt*l|ZB9<7%1vH9_6_^8 zr+sNiROKnymfo7Zp8}f=AA3WHZ)|VFDmTw};WL@q8peDcY7M&2s)y5LMG}AUgpM*r z@W1Oz+e<3`W|PdTRuM%NmF9fehwHeG<6`rKn@O*!KV%@E8~H)_#9K7jRlBUP*xkyA z9Bp6z5p!E_dqGHimC0UGX)10}fc2u6g3)P$PYev5MA|3 zvXd(qrQ+#~D#(eJu{~%@W>Y#&BvW;@xldsH=^&=UFLK#fYEzU+K6$Jfbe@;QgX9gX zPaYzkN4Ts+`w)}-c}s724usiBIBb||Q)&_|Jg2L{PL}Qj);Kb~VCo>D*QBydUoRLf z6HKjzomlv87A-ts^`e!&c}Ib>B$;Tq&6|aSw!AKL=B_T8iX`QG|844gWc(+-xtv@b zPq*T$y3L>TxO@D>(!xmZreZs2Tc3fI?M}{hU5q9R*X5SGpL-hIX@U%^{@?n7b$8AU zWpSun4~=~m0|sWA4Bw`hstoY+l{4xd!sqe|58O_zET6KU7anv_;O; zDEOnAzmo#;aR;hxM~wDFm2Bs zI@A)g8*>jr73Fgi7>eu138ToBz(3uMQ5XDPj$LfTO^m8em*vr3d9Uf_M8ZA;s$}8> zq(5C`JNQU$T!!fz8&nNu79X+EJNZK^qN#{!q!cWu7jE+^*VDj1OE1U~y!vdP0js(w zzr~n};ac*Y5~L@cyvI_hKrB|#muI|w@F}AP%&Y1XT7hnHLY|K6M#J_e3et_AyWKOC zmhL_z^>X%A!*`O)=cldAE<6y9G@ys+S9LuD8{^LaXD?6cwGPDn5?$db>M8lb&tCMW z$ik<&&Op(XzNaszc)zrtf&QE)t6+QK#`2tt_V)pJ6FQ=EN8Gx}8F!KI2jOQ2#o)IT z&6v*-+KAg@_e@&2N5P`YXPw>y&p_g6mYtihjAm4BRYg;a)cvp{G9S4bRC!@`ZM;kk zv>XBB23l-y!$A?-7k^mq$NSa*(a3tYmh%Joa0)1 z1~hLnK}PN60V5(*)f13k!i#2bt3*fCfJKd0O6WaHRMZXTMY(j6MR3U>;p1I^Rj(PJ zu|UVK1(W5B(wC1k)jyb8r$mr$MrBWelOtX}2>Y6RU2atHZ7ztEzgejtj%4-f?^S+1 z>r)7KklJ{5#2L{n1FK)YN@|G5W5{v#Di$T2+G7I8f>ejJB_<%z)9^<#+g_K%4<~VQ zRm}l~6l0r;%(Mw z9Mc?_x@*`h8^YK}DHAFKq*Z=cmpXVO>LE~ zJzz=LQm~|R|GJzxUui~&^LraX!^!fmn(qU^o|J#kslw`V3S8Kk3=sozrby!HFueoCaDi5aMn~XGnX=~MfO=|72H&xT*KC2nd-+Lz9azI1e zGFXP}@U6_BIAs<61tKCUZ!Qfai&@P~XNHdhsOI$s>y^_ERlEIGXfd7msXqO@IG)I! z&@x0`Lm+c%N$Op#W&|V?;i=&WM8mdmPs^9`zh1Ck=eiNigz{vR)223Uh`wn`uxpCs za$>R~o(p_W>hD!{-;3w+3|QzIDCGa_%3T~~k{%Kn@%)@oF?74)A_gM4k66#=SCF=$ zj$yh{$Xl}gh5aCCVNs~R>sh^rOrjS&c^E-Qz4|kA8o!HJpW(qI?-{VsSM>ZwDAp80 z&^0hlM!=Yf{N_X$Tvt*`D)-KF?D|(UIogAJcE4lp-d*_v^;Z=IWXUE;r-o-hN5m}s zP2?SvW&pRJ=riyEUN_`dmy=h~(n$fl`Al3C z(KZ%5}Z>qfJkP>iIP>jq2%GU*e!h;e;EuTIyFz+W1ekLk~(X zx>MI?6{F=?tU@@b#o!EIy^mkm6sg@%FWA*ks~u^YdMqK)ue^*oL@u$0?|fniw+Ozp z$<{|Lae0&^&6!_cM=3|w*_XM?OREeZ$o@|cBFqD-W#B`A5*}+GryIbb2Gm@ zsZs5}a1zHD$s_o#eHnMB$tzN%!6(@$KkCqW#-mhesC0FS^}c()r!KExUQf6N%`#}6 zq|6%>JA%G#i2k94>55rCwd9%|6hvq?x#&@Lz(SNrov+4woAVJ<3-e8JWG3zus-31#LZBIn8@US;prrtAx= zNyobkcUuGWc3JK2?x(TVobXeXjgy+vim66PtvQ^9_Ri&dotR2mCaqFPbb=CXqRd;B39H$5{l#I_Y9zVDCXW) zRy_>HUMIg}IJa@}cCQxEdHhm(oW#Ethpfx|1zPqNmDPEYRJ(C0ldp)JWYMOn^e`!> zV6;TJHtJLK8b8dNNYnv1f7emmm8s%GMq%TjJ0hIcble!`6UE-}99xfTGFp5NuAzEN zeQIjSO*=lbprn9?!%?AG9J_MvQKsud=7LU%tW^~_ev%cpj?#G5slv`--Bxt7)6tJ^ zj>+vJtPc1j1<<=ElFsW3dfqsbZM+Ft=k{eU`nhpV% zzWkX+CaFeOUdN7^h;KOqwL-JK^AGdiHj}u|enxAF7Q*@e)e>(_--YVkEwU>#i%fk?VQ?Q2BZl4X!X52w!g?wV=7Lzh-4F#2)V<| zyx3$l47*@7yfM1!9UAo{tgDNEyXj&)2mW$uS?O9l;84!05e#{zIW!n2Y-eAPJ;k@uHmbUVPjmQn@fc zbt=*ud+Z_XCTL~wk!($>Au4E;zr4ELraaW=72`xdHIHo{@lW%tHv;hoOnXj*-QMbj zUpKV}!Io#6T|EQJmA98UUgWB$(qCq-oar@g^BNKkqj4|I9^P(T;T~g3+I*Y;2quW+ z2~z<2n75lNkNPflnCdaRIc}UzZ`;pA(il$aIBM7Gf`CFcmw({MA z*>Tm)1|egj5G*bt%_+CXui~k~^>RXeuhbrUlX|ZYE{IsV2>t5>eQ&@!zve`*P}^@d z$x_zZF_%kYf-JEdqvzFPby^VWzl#! zDo$TUsm*lBRyItr9_1@|*&nW3StZ5{dwu5`vH!)gG%Ku*KZeZGiR@m zPZ)Ld`*`w;YX>kUMoA)ILL27Azh7Nmm*?pg(;%>hVz1e4Db|N4FMH#MJOk#>K=2b` zv`QOcsD>5Y_B3f30{o4%SKq2pV^gU#lev@T@Hx@1?rz0rU}5xO1Wz<55PfO^|0E#R z#s2zp+qfMKVmomKG1ZOCreUXTjq{@`*0E33?C@tjE6BJPd8d+#hm3f_Zp}en(hTce z;Bo2#EB{q7N1{*F&9`4e(F%gzEJ;hNk`4{XXk!>q3y!ZUyG`Sa?0nl_q#7oMG;(n~h3bm0sw+b0f4`C0wlR zR2-R`F-fVaHBBbGhW_(@RjD$6u_OR#9QNTuLt9G?zM8{IBE0X*nSx#?1L;#l#t zu}un}WaX{m{i|o7Q}l_!X5Y2MhR8F-orRq=gR31$H_ao2ePnxy=xBX9Dxd^!R5jKp zSZJVD_+W(bvw)^RO`xz5{SA0625!E~wRDGf-mtcllCbP6eVo~S?j_kH)0O^J#bexq z=)Hn9&!i$vPiO?!CWA?V*gC9Se}tw3l2!WS>Vh!SOit?eoLWt@(c-rW+~1A@C=Ppk zmHQm$`c!U+iw$gMt87zCKJoNTQI3DBpFDr+J~JD>D_UP}wbl%lIucCh_^^6P)nn^X zN0siZcUH}6t_@k)>0!dl4Xc|I43 z#~qO!HeAQ`aBb{wri!RNiCQmkChVJNmk1zv8mgP$N+WI&^$u({;-TXCKIV}SqktaN zqj{a6{PyeQ_T-k~4~@d|+9%eh0rYI$K~LKhO~TzC*NI`ahEQwS3nRQ%?&I#$(O`0d z?UPSYgd^j}6D_kk;~NvK8)2KeN(jZJ!FgfYaojmt8doA8PG=7ZS!?rZ5MOHc>D}hP zq_XGd9#m%iDz!vazo1$AWfar6t=OBo#I<27bKaL@qwt}4>?$^*is6l|#uCX;Qobj2 z{V&BA4SKld1KxhCW8Ej2Ep6ndYkQ7Sl7o%$BBmBrT8oHp;3d$!N{msL7k2MH#4j#9 ze&u149ZV#S0pf(2Hut3XbPhgNH|{(Gge|HzrGZxSyY+F)`W+<)p6E1&A@b4y3CiVz zFnF|s+AOM;6-qHUD|r86KYmro5rk`Q;Q`-7Y%D z{(%bOFzzjKw9C$~dr_R?+xf{g54jZ^m^JT^cw|rne27MI3FUdD6LRB4!fy`OOC|Nr zOE=?sr+PeFWZro8H`{>xHd4r~*9ie1%NA|DsWmN|l~g&D{D58MWB%>TUok!#`HH2( z>9JAV9Z82>8E1Hdf+X>i4@}$+3QL@xbi&~3PX<4Z9`Grj0b40TnBuxcXu%VK8OIRn zb8dw7qrTY%?tUZQkAn1r8UdLBedqc%o~iDQwNrLPYw)7XX@^!6X9EI-e8Brf?Tn|v zK%$m3!6-KSs`iM<0e3=51GerQVKhGO^~r$mISEnwJ=4LlhsKz0qNuxVn5R4i9{t4z zgAB&p*SvJWWVM4{eQHk=C(9K-CQ2~%<+!c|oti5=dQ(V8I06~B5)$I87Uxsr;##83 zR)#!1$tc3zu6j09TTa;w7r!jM6MP>Tz^%Wc93EKVXJO`Iq4S9<=f^-dpb_nH2H$Sm zi|D#{;Gx}K-86MKx$4lIzlUwe7F2V;G}lmL_Nj_2&%fHBI|_Pl+&1YNAzAHBwZ1I1 zw6PM?*jFPR@1@*PqFREP=;)zq9W{jUyeLia5nUO*j)_b6n64+^A7Yl@*Gv6zguA`_oqxGHF7JKvZjGv>7_wY-zi$_pf6{$h<5*wvCUj>S04QDwM4V zoX-@bQNec;`G0Pu9N>|ac$c|VdIDCk!yD`dKz?wuLR z?wnTw`A{o>LwV+m>%6WxVwDXuIKXqed<4Y!z@(5;oa+!oB|AK{Q z0RQJ);t!GiVdwfxq3PU{sJl9izQT5kKEOAXqc`< z>~D%ugWEC!@uDo89i5$-qnth6nh7aKpy{coggMhw^G-7#FCM`v_liA_H~w?rcm0Hm zeCBFUc9;EtcB~SI+Spai=5!{1wwJ@Y?sQGFKjj%Q`2H;ryM<}zhdqehCtsSoJL9v| zji>GDq$5dhw>X4|AVvpcmi`IW$H8b+m*hxj;K!vRU9a?WI7x9+bMI_?Vk!*uf|>H zHFni57S(%tGS+k|y{yu3R1)B@)us@a;+cQkih83n8{wz!VSz zwYZSCk4W5~&Nd8-+Y}hgE%d7NiW3k<$=#h#d@~sSsU_hRuc4^6``U>a+%ZScX*RWS zKKZ~<)rX=TG^bEt@JU%Rdizd2hFNnhEOD;1&@p;{^}L`HZ}+X}evYm@eijXzss|`K2vNR{Frc%Kx&Acxe7}1gmn!H-^cSR~KBt1s@7-OQORsZHyZseC)H@{;ja?6R zx3bg&Mv)PEk@MQs8&6p){fmcE9p017dcN-9(y}#A5|bIgK#7mZmg%U|7j z+2X!NN~egi+?%5S4%#x3&^Tvw*U*BAd^{4S*VtXXn$BX%JhfYEP4_&=+VW_xdxc!-+CYz0_ZWV4eao5!TTj^@umFkqBPy>;I-Vv z&3toa8`2gV66=DnubLVF4%%C^uKCZyv-8z1eEc%UJ+E}swqP+2=1O*7=Gu6?$|vl! zbIbWWu5Qs!tJAA@u8p1EtH=1n(| zllltz97B{STOjdJPcCJX<*hPwcDzbmH4G^EY45UFo4EO>TbzQ!VJzg1w!WVdTQBPoict|?uXVp4H5I>v$Iat!a(Nxj zYGHQLyw3YRu+mS{XE)HSM0b%r^_SYDs@C!<`YDB`ig54vsO|mWRqtc8)hXxbnsR6p zT;xkuV*krX{<@4`EwK-4v~&#=Xfr~drBROZ z$x2hKrB*7{;#5+U#c)tTorEML49K78SQq2gdl z_2uYFOc>XGIobk_*Lj6!`KU3}gXgRtF_Hw4SP}dCo&a#vk5gQ$VLbmUa$*T~*+-n4 z@zUL~8;AWN>!;N|?%QWz%`w{NSB%HcaAFSUh;%YVt;pM=flhsV#J(h??Q}n>IHq!p zWiFvY*GPZxj&Vp})dOVZ;NEb@(gyA*@gkAafE7=uDshw}6RuQd*lbG~btiBhp91&T zSoNms$|bGClgSw=i%#g&$lh)Ocl|yoAo$ZNo7>*YQmP-stD`BFD9#RcKH9UQNKeEI zwy6d#@@e5@iRbQ|j5V9boEMQ=bo4oBI^vwGit691=0;Rk%|+JQ_HKesXuo@}xrfU@ z9A}dAe3;4aGG$UpC@08De1Z)@EYIhqazlzRfJ&kg3cfCUfXbKi^cE-tJ~g;>YPh(@ z&$lv8$mwl&Oe!uKlj`+Z6<|BoADp4&(04evD4*qf-ILqa!x^)`eY1ql-=``?s(FMi z(f;AWo15N|F2XWG|4p#Rc6$61Zt=;LWptlrBuOY{Tx&G4+3mQR(hk_PERVEH*2qfB zgOBg($kx!-bXHt>Ni|jein%8^be`wB=;TMh+_24<&7S8ofVTE-zbDn4_qE!xoChaV z90vl4zq958GGWuHbQV?;OgpT=J1B}-Tnn%2qO@v}loQvsR^E+okqq+kg=MqX2^2~=y9~n1oS5(F3avbf%dQ#Z`KC*On8+l28JEbIp`&H zW!eO^$?rwIa_-UBNTZfv(o7QUh0i`{45TU@?4=(QQ|*KO)#e4m@s?jwVz`nxz7-pl zJT@?XphkD*tWLCKY4!lK8uj8g9?m>bWVm5{bI17F?R@*0dV9>hTp(T#_1kw1K68U+ z5}dv-*TZyb@S~E|`%SDhV?m9Td*WLygL0>!e!uZhb&gFN>TKTVGwZ>2{@n)_-bUR;uKH zW91`@>|{={)p>a8elp_^YteY`fCr!0ye1DT7YL|3bh_*h;{@+#KSuX5=!#u9HFy@s z?h%oPf0>ot&yPslhHDH!w=#WQ$FI@SqslN@2)@hQT-(2# za3e0x?u53#4Zp@+zw3)3*STT2%8wQ5iIXMVU|xLR3qRRdnjh9G;`O=rqxz;*h4)7e z2^UVrBh~$0a4*+r#JD1O<&C8(bDTG)`H29le<0FsG$o77(13Y<<)f+PT((m4HSrzODVywikD8=owbYl-f zUQFhsXojoF{WuUV-X_k%H_*RfD2ePsb~FFT?2-?nBR!u;*ktHYXXMhb-16b=dhjgwuWUwr1{mcBmHP$V#`G%}C6nm3-c4zq|3(H5AnexmufZz+pFuMiTKdxwt5H z$m0{WY7lUWZ~6EP;I)W5uf^aS8yXsM)$;`|H`0jh1`~Ab`4=Pi5GABu#JIZbf|23k z2Y;Y4p+R+!k5p&p;T|8^Fc~ii9dS#GDCuTYm~OwFyEv9($CfBc!D3kdMOSbfYgG8d z=eWz5P*-oqOunt!l$zP$C7gw2Cm$l=3fpe4z>K-*Nu*61+T>PULj!pK1ILoIB^FDg z1DhAgGr&Ci(a7M;>4Wj2$AlaWgJ^gD`Y6^;{lbEHU6Nq##{uzP`1^stFrSS~aNN|P z*4hYvPQA5BII^^+lRTEoP}kQ{rR`mH=?@gwft#zdGFx9Ot(fvTx;X z#Vkw?=bLU0E{2U)`yyJ3JwlF^BIeB-Mvl1%<=5$A`&N5=Z1Y)Udx{Nu*|$DqjB;Tn zMe|U@E_7VpO?XGvVPc^VHK<67ca{wrtjBwjQpY$WAc!w?XzpvQ#>sBJfzs{-P~Jyh zG};$YrBu(fz*6o)<_sbzT6a*!fjdW6Ul&q-$nb0K?J-k8-9P{q+xEukNz|%hp?&WV1Cx!e8pfJ>&?7tn1#d z#IQ%@_aCIzmjo5Z9as&g;|K;nq0@~8(1A|NkQzeeToufg2A%jK-Mv;%PF>=6{3_R* z1s$PFFPxyRx)3?oo?xhg$&a10?9eW|AJnGHFJFpm_Fgo4cyFktW4XXr9Nr+^hMUqw z7aohcyNMRKq`wPAUB%fXvps{nev1=oS8a(qTir~V+c1mkp4R^|Bw|#%E6rg4wM#2E z_cpIsFy`$Oa^+3T>HF_(WK(f__5xR@T6;`<)T!EGE(6ehT@-KXrQoSgjlYneDE)ft zkqJ6^{Uq4XrvIpJ6l@lWtQBs*0xzhUA<0fxXu#0tvt4M!^vcf&oW_XhX5;60B2sxW z8gdZ`x(OJS9%rVQs7a&mR?%B1!^H8p6jkr%4llHwFff)akkhm;%E`ywkZ-cPnGVh{ zUe*Zoo*w1LI95{D8?H;2w8!x1biuHCxGYv{96q>(ywMYGnKY2q9GNm|*w?sh^_lEy za+}3&%Fd^wBjol;%HHG3!Is`M>_A$edZHac^*dfWo_{Yp;XwQG(Ai*7^4fkaFo9gl z;XQfyRjDR}!q-_6*nqxv+Le7C)=%LNVb{p<4GawAie87^ zpl7=B{S`%+wP3CALu$ZL-yGt-wxOKQgps(=SpPG?x=)WvK6W7*<)`4eUfLMV zD<}&ZIzr$tO%xEujPIPPyngko*3QZ1C0u&Fmb%<0zwbN+moVmAZS-}g_Z;B}9Ipb- z%9%skSM{Y6$@;R?k#))4yd1BJA{11b!cx7*-V9sO^71Mxto(5n>A>FcD0BUEtqpzR^79cdU$SfAc4lwZ^lo*k zYP#~2Ba)xiVwgrUA9dzXYg_H@>FQ{NPiF8&4tZ@CxzwkJ6(5Hqk=hRx z>`A?TlQMe*ZHiwLr-LrIPhWOr&EqMM`Yh|#5?W4tQ{58hbclnjnEGq~+5t!FAW9k9 z>XnY?9O1_1stky$PgJ=HTQ2BShV0FC$xwz6E6GrCZw;Wa4wEAOQoG7A!OHvVpSnhHkXiL90s=6x+zD zhf%NAEgH`ohJ`Fg!;6GXaHCiYXDB$jcaQR{Xd_Nx`r;?11UBX_>^qH*PK^I0F%*o517)fiKk9Q;}=uF_qN zv2PZ-MOaP^Q+OrB2?=`@Jy!Sv^7xH%_`us5Yo&0$<&bA|RFcQ1&(w+9HXU1SoQ8VW z;th@|vFF1+IwcW_h8|?(eDQkCSzn&$ZyNJQOc^*Zv-EC|jKlEHpqjM!F1fcSe^9|U z?kI3H34uFL28F8hW!xRDS;eBI6HlPeobLh|Hx3TacdO+sY!X7cD*)cM!BqRZFd zLl?SuiTo5@N&8c=8LX$?wyXo0?V+XOUzUVNS}k0eoeaY`epxoa#wHgM2z!l^O{eqk z^fb1aE`58PHd9_5vfM4FGDo$qrA~y5GjOmt&>xVRnn{U>d)ucdGw0z`a^w!p$PBzG zGP+3N%k9=@8jG~mZ0|UACVh`wjOe51XO31nd9w%&a{QKa%EYd>N|*EpW)*Gs!X~5x zY-C<|y2xyR^W>;&n<$siI2PBof6ipA>qxaRv2U5QfkQ~@h0Mmx&P#6_lfp7GM&5T~ zfA*6zqoBsMi^~7x+@p08I9#$&aqD4G9V2dViI*hs=CJTMm9=8rID8@5EZOdIRByAy zor;)|6Arn<#DFz%*n8wxk6vF&t`Qp}=bQ_$)kuzE3tRIs+KW-Z-{zhpM_4Is)qgj> zyY8l$=K~WaW8^xhLAW^mM!fLeMloA$>8Q)(#-TCK`m)q=iPELF;SlK6FQ z*x^OFpujTqBJt)ZNaT?zYtCAk{{YJ?<;`Tbv-||t$Me(gYYqDTruxNl-xfAHQM_&i zu)IKvvpB1TlZaF;?|TqYCmM;X_(#dZ%K2L%gSyXCbxI>`%b)WOhWTUiT9DVoY4hIf zvM!Cd=-eTlwj2oKBP>b}kVpGBA;il83|iw5N~8RXM^7ZHv@ya(YD` zy=;aoF1}K4?H}(;xN;0P;^LkCNMTk(8NAfz0PgY=hkX1bz$F;1VMaYO;aYS|MK8@Y z9XsXq(J0QI6<=jU<4k-0V(2m1MTU`^=t^it`jGWUB- z`6Td6i&n!ZTb7@WwBk*9)}$r!hm=>Cinvr0az{RPi!g&fma`KBF7SCS&Ns&0sqacY z?acP#n-D2bql69l&|7x&cRm!?;TXSi8OYYJ&LiW`R7R>xx3mnWwGnDL9SukvsfWCq z!;wTrv}Y*=x!-F(um|##P9wHv50UDDwW$RbdXtIlr&~_z)?tb%nXsb;{h25ofxSJY zN6D^J?77Q9=Vzc=>z8)nLrILI!fou#@Ts!uy-=qE5BJb0b8#jj74Crk4ZbEgOs;jz zO3Y=DtkF*H8R#oABJ;S>@wE*cdfz=i#VdfDp!!Z%-BobKHS?|drE>5xP3EshEMlh4 zk)QV51T+q9!^0LRLOD!xN&6DNuk_;SLJ)7z5%)-aqcviogO!jjP2rC6|V* z+sbmX{254vRNoL*rkh+f`I}IcIJ91OO3nut67Pj=__`a}Ku%VlQ%$vS(b0D+yqJRrkQe&ot&IKaBv+%ioEjSHMe>)uG3mGfifHD*Sod9nS~k$-he_iWKdd}Sy2z}8DfukPe=b7;1o zI=1Sp@)K#}j`(43Amio{Z*_=+vTPt2ExT90yEa-h z(IPa?qvC*AxEZPQG1LC|;ECDGR59|()%aCad2*wZGD={RjKNj^WeNRwu)s+I=_MXz zO$MQ%ZR~~6%yj^vX_#z}$1g0AzWk>v`@9$iOuiApEU8P;U|+r#O=f}X%+~1i_cVRm z>Z&LO{GSbs~*#|XCHiIaes_AW1l+0y(YsslkF1|oXs#YRxWsL z(GZ*Kx)gKUqxZe;nC_bNl2a4cmc6}D$Z273egh-BG^4d=fMPeSiw(B%ZmX$1NI++% zjoS;a|8QoH-#yLnr2ks?RZz5l1yh-E^Lr6veBIvky}@j&{h5!sbAJ3M9x1C!ESp}3 z_A@WI&hS5-4px&XV2|#w0l3QD+7f~(hQDsAbg}|!&!-vdMh6O1u7;-zi%smEM5dz7 zap5ys!6JK`rDkR*VVcP)P#t8DpDJ;6*urW0bKipTnHCFh?h;MUWQmGC%7NK;6nBx! z^Q_)-d?smT`z_`Ij)!&XyO=;_al_N7ks(^OVkTkC@YBil?z^dK{e_`I6NjFy*z*vo zs4qTl+w*PeH+gF@JaAPZua5$kNiKVQ609$U8&-Bq%#Lz0W`%TlDygXL%#*Rd`d_s~RP|iA{_5C562zY0@m}E`fu)6-F%6BPG{A{SXiNx^}ML83y3) zK9G<^I53Z`D85Op9jLb=l{-7qYrJ`1s1WO*h0p{W<$QNpaB(k+VB`5I2IFQ@Ihw|9 z5H1F%e$s4=gLNp&ReRXN>)Vse%etJaJ!&gS$6~KS4I6%k2HtH4 zp60R;MX>eyz^ndrs`W^0v#7aSa_GhE_Pq_8#@LY2`%aaGF;ZPY*5!_OK z3{dr$KZXS`=@r#9(KNl-`@Ty|c7uCiSbiAfzivo#(DMHPwm?b0FE)v&T$mWmez)oS zg#(#9qC@aH0xKV_V&m~f$4ne2D=9_)0NcmSm;V5=2aU9qR=R6@c45)?kJlCSOrNyn zuBESNcAwhXW}~A)X7SwWPpLw#*BAj{{VPSa!5WB&9~zuH0zF9_V(+)bz3YB`v}oA`prxyqHvw=dXoa)~6M3Q{aCYd>iq%!?0%8yfvw5kpqvkJS4~e0DZ3&`afg)MtD2KdKyD@ zGujEF3njC}B~VDg1acT=x}%y_j*@}o%MW*EeqLF4g7Z(fSyEEK3=mIIUtauI{hWLc z=>8&0 z;3S3iu;p1>n(>Vz#8)~+@+!L_ErS~jeulbZBhRUZ(?joHfnT(Cq2We@NxQpdwcaqY z0Lza-{oML=uam93R+Gjhg}k{GgN*jhFluK`6*osGvwnx{$AkP^XX4E?T-r&z5D8Q7 z-o8iulKwI*I((Xbp*QbPhmDJML%|i};xLXHz)_n{(jxx=f{1B)-SYTnb4dOkGD)>Z zmRIc<9eCaj-(E4&y}$OF(ro-q@WyGSA!81biIz2d9^HCpIOCeeo-^ukcSogx%Hx{i zV@I0TKP;|1KGFXG0mst4cU{onj|u?JYo?B9>U2JrFyQ|Hs6&~`=ZxFvH*YX7M(o++ z0>0e%1MpVET(^$qc$LZL<`sq`K86w5?tF*#N~xjphM%fy{vV77iex}K;MeHSjQ;>< z{Tf{kP`1&7u>SxEu1Z{08j|I4IVy1NKZzfn_P#FEwdocom4NS)T^EczGZ%t8^$*r3Y7F$CKo$H$UpRMCLtbX3|9FjDB z+g$XYhQACf(W7>ZXX#LQFXFAOyGFZ*9`*GYIo6u@kLQji!&0MJsyiODt9T;B>2fcU zpatuS&eXhgbqz6=-8XJtwd$(8kKT4aJ$PwU_)+o~#!VJ2H(8WM02{ki?~As#ml~5x zEKH!EO4;Hm%~>vSe4{gpH~@9o7DT921AL*Lz)jO1`V>bpL)t^$y&yd zcGl;o=pHT_JkzYfV)I7O=mEla1bg+*YV*5|Q|(Hn7ib)3IpVmbg^fjNqpCHzR)o>ZCHX8~QD8Lv8r{YgvTI6HA9$P6q9=`RoD5xZoCRH4r*=lQ;W>|z#c|}(a zNy&EVPHLPjxq%+F(PW6Z*!T^P_M9-nx+9lf&xs1lofs?`JoMjn1CQ`h0WZw^Oe4S`y_=@4 z#r4w-#ZVSwj-=5VZRuhqD5ZAKM7Y-_KO%xpr$bz5)o#}1t?y%Kr$*ZG&UTePhdn6e z<=p~GO6cn>TJKoCNaRLKH6iXMc~&)!Jo3fQ1KZxZzk+u0N3TAmG9;~P0fkEkRh0h# zA3SAOI6aBYbWnt3?<1DH+*PBw+E~fpT?RRqNbr`u;@G8a!L=*3v9cUv0Ja^+zAL5i zb;gsZ*}dyrSnr!_4YQ&?3FLvC{*~x^I$swj;CbBh*~rL;;%|y{*u*{@@XvyLBOgU* zpnJ)e{sdHW_}4u>wU3!1#jjdH{l%HfeyS_3Ki!Fpn(EIl{iQx2c>Bbwp!jc5@k8rR zI?1X@1U6{|Q7ArKcm$twlj~k>@rzQl@i)XRN@Tr@?6$GRWgnXpkCrvTILAUsHR(b; zu{b3tN$Ppz*P4Pc2JwMYjCnC1{gSN74WGytQn!8Xs6Iyk1N=mh_eXPDUPArXk)0;1 zelweefA_7SKl>$WFNY$yXoBkcVe?4?ZNY3{_ZZ{xquv#%c^v$jr2hcN4V1&t?Bo7` zR_*77V7K`i;^4{C%#PeVS@t00kHU+{IcauBJ7%`m_bUmuOIe@p$!nG88RbbJ^gMA^ zq0#QNO=26F$a^^BdD7)U2;eIy9Q19(4X2u@$==Myttwot^)R)Mi5kCzMBd z(UaRu69MGO^(DNbY?->zk6`NA<=kWlx&UocJuAc-jDOjh-Ob(C+Vu;)GG(4g1^_2+0v^B-k5YXr&2=qe(^&Bi zy9JX-`hVE23@+|jzFm#G>PqvDwT&D%Dvaf(r(A1Yw7VW-rRp+U=~G+6itRID!Caoi zW7DCor{OQdOomBfv;Z?Qk^!XH?Kp6ojR2?k3*1; zOK9(-hVRU@cH4&tPTZcq&!>8qUcR0^UTAKn4dhBe2uT?P`gZHmw2w5RWhEHO%=$0k z)vdOV<4+IU#k%HYyG8SrausvQW7j+mdR7O*-y6rTc=N*6mNwfXR)sDuBy2JHixJ0C zPhsg^A0< z8)%nEk%oF5lEzQzn&igKs97`TGh4$9xwMR(nfI2bBo`JE>N<%F0xsuY@39;xt|#I) zu>SyOv9yi9awTUx@JPV@E5cgpe7+Y6Dw5Ii#T;Lb{w2%v!Du73MHu-&0x{|CYrwo= ztX<#hS2pOalQSZxnhs5PA~NIWlh zc;E5w5*S&J_q?*-`!DNVRMxs1-Z1x1nEX+6W|fIN^2Vt6isNy4is5B&tH&88z3O@% zO>cRXejj{3@ejnm5k8xu#SO*(0Fd*KwOk|-qAyZe$Q}AqVNnTIQbCQ?w`7c9idAY! z$J};d94A!uF}^lRXEp8*YidTww&gkZiin%-SJR>JRDp5dannbRok6AVo!Z0$cct_6Ar?9KZ>>K_%ZEOaTa zTTRk*_?BDo<}wMcr#r-FZzpO6&U$9Pe-nzOOPV*bdv!kdD~)JTRVu%{%lP51YZ_j? zsY&9f?RA5p+o-Z%vk2h0c^e6k56pn%kPkQlyRX{Q_HNXE8rWW?mB;pmm8o0&u`9a^ zdzgQCxDCh-p@$vCa^R}alTh2gLFSbz)S~KA)p;Kt>s~tX2D>x8?z4F6c=Do!8Dr!K;#~_HgV! zX*+Iu&XF~Sw$}Qdk|nl;%p$zg=9A1e(h<2zz6R*q2LymY1Rq*|?BfNLzAV)3^-V5a zTH0Fz(b-3x(#(z-0KpjJe>Izvpw{g(uAIH3T9%eRgZLw_CB5W|8*R<9iI+dbxx)|3 z`B#|uKg6pyinrGhS)%x*O}ygqr@EvXgPBS<33fh)Tm4p*6{0u9aV{$tq}k{nv$u}ZMbl!2 z@OgM0>Rn^u{{Z$)fp~sG2k_U0S^nOvGVi;DM;Uh%iZ-7eQfm-p&PUt0Jt;&+HO z3mb_o&BN>j_pVwTKM2d2v#ME-GPF^L@sGueuXu{$SdRscFo}x#0aCi<#uUq(c@U(b>ILTb++O?GkM^<0<-Cu*5oG*m#rT*1~J1|_= z(FgWxy4ohSkLMilE0$QQ6xF$r4plKa^0s)~)c?53}A! zAVnArgI^_!RjbPMr*wN5Z)Z(=@v%S0ZwA;$r^w2x7!35UDfqGRk+sO)1$_2A*G>lu ziHrA{RjX2a9%*TP9-AbHFnPstR~mtmMr`#p?bN}luJS#szdo&jk1ZqTA2YT&t@w0N zFd=-jAPY2X}H+lv`3WN6tljx&nUN-tqqqO#n4*&Gwv zp0JR|a%&H(TwfJM>EqCGn0ES;wv-nx1+F9Jj zuO_yxWYz?Dz?5zqQ~~@dKNDEf%W2{*?H#`H)W%AVu~++#F!-C{e~Dik{A`yiqU%={ zK>0R}cLNBm<0r23^vONzr1;nI1L7Bi^_zdU_@4Js(={t`dtp4X6!T$kRUBj!gN`|` zpr@3qJb8J)1Hq}BN~`z|OW^+if$sGG03BHB+B7nFzeBT~t?#ZaHpU{81zZfCc9EZP ziuAA9OW_}gemZN+KGZaC55)VfZeoZoQy0Xk{uk8p^f%ppX zzp`n=;GDk$*IAus@x^{8=a!%QJNVbcmxkX&@cqWO89y!Eo_vBmOE3Ue=%&5#-$c=e zmTxViMBpiq06zg+Q@~q!TvE)tMIQlpi}p+Sk>MMADP_=p*)`mZf*T*+n6RabNZl1c z9GdhG+7tH8)pc!CU(vij;tM?|Ot;hKh}>dNGG^P#s0-DzfGf5b8h7`OI+*&cd#94w z{1w)94>h!35oz8gh9yyY+9lVJkul`JS<0CYsUIl(Yvx$>8)=nF>JJm`1o?%d=kc#b zo*s=qg&s9bO=_A=v+Nl>Xa4{QEvlxY@PZvl<6J%E-O0FjBe}t^j~Z*X{{WADR#x=c za^K@u^?FE zCxtv?p=qBBJVSkZb#W6*cdOi@BUysmRbs|)kb2ffc^6ec5;an4}+xHIo zG?I_LwLKqOywdI+=F{~HX%vz|tWv_A@-R73eZLS_5+%Om+y@)+)HClbm zJ&bxCjh?q{_T)`Y_UWwDAN3REmA{1H{#<9TdgE@j191LhZQR{?4P-2lv_V-BdmoE|_VfsE_W{Pa}PY>z(j=$jh8;EVDo5_eo z(@%rn)R+_GlHMO11pOW{Gwcr9YkYE*JMR5M+YXF6^J9um5Dn?IQKQ_I!}dSxUe?S zT*rH>$`r+=>G8CaIV=X&al-Y@Wh_#Qvo($#wR4m3rimmo-8NX|dd;=#)Q0JQC&y1nayzA-%(r$rn(i@VVHx4~E5 zE7NYZT{lCxy0MQ?j74@LVv6ddkQn|wx#qt9Zw&Y&;J$}ipd@osj&i$5%d|g?3e{sVnt7j4l#b znO*4>yEB`?_wlSO;T5ECfdR_`Yw3@M{{RZ^bxl&zTgxYy1`>Ug3=o;+n4W_blxJRA z$ypwLWtwEUdiH93^Iapa-_a8O%zhic@V|;y^=<~9vT64g(MCtlJF;O%x6Om@K(C^$ zz76X7FO00UTN`E6Y#}Rhmlo_8JLC|gcE_)ND3mEqlWqIQ=Q)=W;<3_=IKnZcoz#6h zvpVALEA19Rb9A=T+T4-4Zi>X5t9o(>s=pOJ0rpe~9@ zB<&;ldkXQ@B=x!M(8b}aB{e=*ZBfRb1lwL~PpMwt*epbsa{~q^@XiH8efvM_lA_Pz zInB<&^G9wL+T8s{OEq;xMJqOXGOtci0)*1KrNH_*CpgjM6KK706>&@b8G zu_5g(8zy1@04<+5Xa4|qJP%=>^$)}gt5EYpZhXshQ4D1N02m*|_?~OmrkTTtX(K8X z^A;TQg(fr z;tv4qE7Pw&9_V+@Ue8jz(n;sYyMyg0K8~L@KBBz{;;Fvq`C6GSDlg!_q2&@o&mz4s zeihoW{qKrw)PK_}8QA_yIxdZ z{{W*}1L|W>oyV~vpVpxCiflizaZ6do%gvm3fqW{rGDodyFc;EblMzPTeYRoB#-6GU z3m$+Bcdwy8XWxTb-|Ye7tBcEvlcs1k5&12s2mpo?)nT`YamS$gl1*`9aa@bvcJk_a zSUj?YKi*b%x7>XV`!CwRg!~((Upp+_hiXIfx^gj{m+6<9RKZoVGH&&Yu?`)-R zBoULr!QqhbMmie%{4Ot^3GTlW=JEM7;ZgE0>&W@@ZaM>+?dSMmsymj_B)8Wh9OBu+ zOg)+>AcOR;MOK6AcoM&i&_!`#m#r}0fZ9W#Wu(frH)fnz%A-9HO-I(N) z>T05#rOfmRB^dIf=|71dv(B-ncy13J==M>xp!wRapJq3GmV9DYj1IB3az||DzKi%P z@meiINQOsH$#jFwh>?)sV3GZ6IZCBBqPjREQKxAbKLB+|buR^JvP3|RO+3I?t_rXi zuW9h!kKs!z$u2Ckt9Z*Xc7h@zKy#8xbQwJHSX8d9cC|5;K4+sSX!>mWYfj*gCODc` z!OCp|IW>v#TgSHZXxDlkg>NJ;e0JPTX&Gh;4>B)Yn~g%T@YFU#_<;D?cqLyUlrv|3l%_sLYY$k{CXQDv^N#I|1N5zZyk5-o>E-n)ty4M3SVuas=nWSU zm2XPcH4N%e=+CQvXI*aA;o*dp&Ooo9{u=nwe+ugm${d6YGhCS7dX()A&TKvo5vk24 zezN#yMz)hrM%bq-UM29~_KMSF(c)W)fMC7PwSHTR!{WL93%%;J5QU# zcT*V9FhX;WYVn`i1NMfrztU#3w3IPmM+Ulb{4OinC#pu1Q03L!_`Al4A=a*AL-GbV ztj${d-(JZnJhKyDrPFVfT<~R0T+eOYy4KE{r>)d~tq!t_Hm8Q>)gCRT-%F=Uy`oag z2v>2Pr17`|!K|3?0sG8bj1GENgy7|*jH5ejdK{h^pTw3{acR=UIAsO{x2`J>MV>+M|L&ZJ%7&V?ww(I%s%$)(+SGnk9u5Ye2E&Z}KqJ5E`o zW4nR8?z6P#m``czP9+ zSh3#5IK1Ty$D)k&HDgb@lGb&WCMzQJJ3$B7j`f_IDMwT8Z6Cuv48PEB^)DNE?)Erf zhY;!Z_a$UISCwoG0fEQXz9;dAiZ#7@!c0ugnEmWl@sOcF;9ztE(yEo7mNBh(+VU!R z@@v4+b3gOMrLq76V|>%3yy-K?C9l>W7@S3+e70HqQw6I!dBLR zoPlj=0%P&mPwQSP-!CVD>S{_ys7d=!d{nrZrI*H6vq;A!rAU~c%`404>MJME^%ec6 zJ|nh1(W}~DFLk@qZR7|402~$N)m+lzr4MZRb7el}Rq7 zOAkt24|LYNFC~PN7$rgFsD04L85s!5x{=jL9G(hB$@4*%0nr^jsqTfY(WhsKPbY2@z58PzydY+|XcVI1rn$7a#u2_%Cx!CJn zx)>d-)_xXQHd|_%wbGtbb+l-I5t_)8!uq`47JXXUBla!CJ1O~!j=1)$x#H!^b#qlt zPCWd&8Vzrt_qo4O{^B3xYlpSCHnJ-ZyZlR@qrGb4%y|4x8a`g!Qi%_;Al*r(S9cY~EWnv<%=cZb#$!SJOYRZ^d=B z_?6-h0xq1XYRoL{B#lT&gi(2f$&XTB2j0A@)hcN<9U1D=j3FLI?$PBQ8~vVqcjE14 z7+~<$qa1ADJogcKQKILPOIPX~_Apt-vdd%kV(rA%vKCzW^U}FqCZ(;hs*O9IFYv4O zXV88X#|_vBFT92ORph6Kexxs==lNHp>zWe%}a!yiuGpd|v$8FD>{C%e0+v)cgwxUf>QF+~GH0S?Ri*EsAtVw58r?rA!bTO;HZ{ghY48kMd0iKd!8I?5p>&7&Q;b~y;` z*pZUJe|bfHC*o~V9b)Ak?q4HLgzs1s<)v?bQ=Ic#dwD@}!bJNuQKvYcIe63H#5dN% zRgK$3@cxEP#kHJZB3xW3!tNbex({JqrSTJ9OYaTe#5Tva)uDzEI8b_=b~x|WighY; zC`Imi`E03C#?h-&MHwc1$F6A8>sEhfwTR1cy-Fzjx%3z{>)#K&BDc)VVp6u%dw!gpsrp^BiHadT?uck%U#?wLdb% zXBhl!eVs~=m$K1)$n3O#hjUzAHJm;ihFu0m1>}zB4=y=YX%Am@#d;0*#1W_2$m?vv zCFgvY1N0o%l{`}Gxz9@rh?DmsZRq-Kwv%TSn!z2M5U$inq)$mcV_zEUKNz(eC|qiKq}QlA zi+NQ5`kYp`a#|y{96MSL{Qm%P>3~Ks;F)Nml!RQ&iJ$-A<#iyOJ?m!#?>_4S@6zRq*RCP|P_+U|z)asW3 z<~yCv5+vmR0QH4>M}qt_9n=!ZCAfm=qGf$0O%_ z+j^X4mcvhxVHY!oM3D0Z z%OcyxT!kH2mi~Za_^X2ha z+%%o4)xVO|{NvO9D{HM4N-|Js#_`CiIzo%**EWgxj z^_gvS`7UrF80n zyP9h*rM!|Zw-EA2+*+N?KePse;_rs~hlO?h0cZ0XYpY#ZKvs%#^9BbQ>M`${>YM%v z^W!TI5=RW|vFPeSc~JSQw2qPfJYdrLmK{EL%HE9QmVD|*_mR6kakqqDM<2)jnD*piE)x8b6NeFJW>_(gwtdEk!V(QeB=4UnHl?$TG@*VQHCvy^8f$Djx zUNE{cUmHH0XWJO!wryI-<%)8A&tm7oIPI?Pr?9*%vE4$l?q9s6Pu^c~oYys>Ur(ak zY4>sbw-)E4a2WoKFE6@A~ z@!kyv+RWW6+-Xn3$fq(}w{#=EJ5bkS&y-}|$J21fd1r5HboV=p?vHZ+01SipPq7s* zfqY40;@E9(wENg@AeWrD43ZB*J%1uAlBU*Zn8we{=;P8aPLh|O+CECmgbj)oT#?2P zf9Z&tS*}gZEUA;ch3%3aH>6P%9OEHC1dmqCMzw%!8H`mlRQP? zRg=shXX{>%@tD}?{w%qM-e_eKD#<61M!_K;K0S%a9>%6K#X@#JK*7|?G1ygAMI+}& z@J-&Qstapqi?bYXI#<;n0DLiFs_Lt8DkHXd&3bao>S5t|ElAMU`w{3Yp>kWQwkC*zDK8NJivHh{&`-ilg$qO$X#%tXF z0BfBV#@kliY^WQ)1$|Bj7O`=>;&9ZW={rTAFlxHh#rtMS8NV7&?AazK3z1&0H8`77 z!`G<2)pH$~`I)T?jVf6BsTFaYUd)=9YSjKHv|7@y%n^~CR({5hf-yIxj@9-{+mV;=RLZ#Nu_pL&M`%@mJd_;&Fk26}^Nxh%WDM5N!@r4;E~x~Uw(-dx zB;Gi{$mbq|7#QzfZ0h3ZUG9#!!j(hHJVD_(yg#c;WY-Yfw13A5;Eu-~Mt$qhyk#_6 zwy8XJ(O9A}^U^yM3IJ}zo=L#`4SEzY6O>b)=5nN>^gQO*O1RuMt`Bz3E7ZOiYFbx< zJZ+|G{v_~xJ~Xg^mO3rO%>vGPD`%r%@)YoK&2;-kEiPu_;;&=NukWrjJAfgN?RPRj zOl@pSm2B{EI@jKwH2tJMXMc&7CAA-iz9H~uh;6MJ3kR^bSduvaB>`x_Qa2IF?OVna z+Pt04X;iDt?rC%3?;QLG*8Cgr{{TSoqiPy{siNx2qFJnWZL^r<7^Ng*a3cq{c&+^g z^HT99lAaT~zqxx!t{ASA_W~PITxH}ecmyy9CcPNS6%tm|^CwPmla=)RPZs{od%2Q0 z?k!m0oy|B^{7J8|d>#J)1kcrcQDY^=$HY$tS;*m98)$FM(dV-=HUS?>zDp3Ke5a%6 zG1%Tg{iSDVdmY591beQJmZbMSUpe#@^zVv4;GaJdJ{8_sUTIco;~3Uqa+Bx~eYPcK z1O=4~D+A6(Gf?3!hLMu$c%Fy-m*Jb&*5mNLiE%cQ9G9sD#-TeT>l>Kc5s`-HJQH0e zvGAMX7Kf(Y>DT@r)U0B-M21-|v>TvV7E>uwR1k8aou)y^gCOF?obxKz08BYd)B*j}iE1Ue>O!UQ26xKeVI~82pR12(dQc2KdSE(DWDr zoK*UWxV?`w)O0yx)bAp)ww_yPr6`19h!_L{d!B2#(tZQ{LHL2HY8M_J@TR?}LL|?d zZ(%4#91Mcnaw?R2qFd~9UKaRY;=dYyXbl@eyT1DbK7Gc;$CjDKdF%LBvUoo0$G@`n zp=P&wRGM#yQg*s+KUs`{By2Fd*Kzqu=Zw_GbmLQ&Y1kzSvZu{WtNEWjNvGXH?2m0S zGLo#V8*oQa(z~ySQ0pEd@dW-Nj#Y$f6FtO>Zdxc3L(42{&jaaKqxfzswed7?*5RU) z1TvpS1Xi%o^;xkL*VeDjK%VVJ58?(YTo`jM+MqGD{4}ldTMol z4)w`bP>VskjgrQAyrhaj*p5y;@@vw5D)?o!3+*T329UaJJ{Ff#{>an(m&MK3m4Md; zdK6~JY8hxeC=?(ztUS={?BQ98Mde|CtoH^#TK7{Z-lLeavK#@eMJ75o>uQStrC~CT$ z_|m)~r@pJI+&ae`{#kWSgXK}w&q+yW6D%P^(S@*d{>EhXHt_|_@Ot3wY#Hjsa#1Vt>&k!U^+~U8)D~(fG9?K z^If!XKUbwGH+%Lx+Sva9ZB{(0+`Z4TKVi=R_^(9qr;Yqur&?)pcxPDFjfKRQ7X(~e zq#O6_j~{rkKr(t3ud4KaiXRMoD`N}`;?E74l~ze2v%Q5?r7AZ$0P;!d-D`riY1L7S zR!FLGQEjbT)b$ISg|+a-(_9~v#?Dla%Df-PpR}fdai-1oIbTw>Q}h1-XN}3UddS#N zJ?olPtqw@5+}@rZQktCAk?lSfxBlADQdu`gG)H_bpP5HoXVSiQ@Q?fy7vTn*V=Nk; zv3aN6DwHt&jiyjX96Io8rw>Y8p8Q$OT1ijcXVE?**$(N`=Son&0re{PE{{UyoU5R5-X-%O7h_q(MZ8kPE==-GN3W~0p7gLEG>LN@h8Fei)7Yu+-i`b`GfBqlk%R! zsrSu#*kw+Y>Q8iV(wy;FnsjfZosXwJ7tAcP`iO{Cb|I{-gSR98*kksk)5C)L7L~SiS~}`L&N^dsZL6o)gH6sTeSYq z*vOJACh~7Ya&2M}5WXfB~nUDKJILGHx7b~BjV0r5$JJ}x?d|$nm&P#|A zXJC)C^T7Ikb$jBriY~QFl`Vle^CND=;{;c~hLWQnHnVRfr!GD#F@;a*6 zV+xmkOnzg+tbSM9>5=&K$Rtyol1Du`Mx4F(W76dC4yg~3b76IBG7KL!L*@QB^u=}; z-vTAF(#^(}w^uPT=lvdMDx7~3zBUi7aw#j0RpTwBeHZ&O=~{i~g!MQWThA@}JW>@= zwf5mk`;JdvTJgVuzBQA;aLJ?3avE_nPiZ2y0g!>ZJp1F1Q}v>x9F@`Y*@YR@l9XBF zf3%;#-x6ql6SYUO(h*rZ}&=2gc6{YLHvcuUUyCQJC&lE#Fbc zAcNE%hLmc~D$Nd-HxUT(&JDjK;(vxev3{xX0j+Lky}q-wkd4;&77cGVzbtSU-oE;= z_`RUsT7#r-DBUsweuU8KRC^p%^DInug^2zf{2%c5!aXfk&ej*cjvx$*ql^Fm2dO

# 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 literal 0 HcmV?d00001 diff --git a/models/Reference/stabilityai--stable-video-diffusion-img2vid-xt.jpg b/models/Reference/stabilityai--stable-video-diffusion-img2vid-xt.jpg index 81a8096958e1e58120a8f97da8370efb556f85cc..822d1cd9cccf8fa1ad667c1bbe11dc295fafcc77 100644 GIT binary patch delta 56354 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 z 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

@T)b#F+OIKA>Fa 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%0DRIuHg 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 diff --git a/models/Reference/stabilityai--stable-video-diffusion-img2vid.jpg b/models/Reference/stabilityai--stable-video-diffusion-img2vid.jpg index 81a8096958e1e58120a8f97da8370efb556f85cc..1c3513ae431234b6bc27759c09928dc19e2c09d2 100644 GIT binary patch delta 56766 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%0AtzR{h?#kIcJDk-|+Rj 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 DX?EGn|H-&f6 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 literal 349667 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

@T)b#F+OIKA>Fa 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%0DRIuHg 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 diff --git a/modules/shared.py b/modules/shared.py index 2808f7f2a..204ae7714 100644 --- a/modules/shared.py +++ b/modules/shared.py @@ -306,7 +306,7 @@ def default(obj): "cuda_compile_backend": OptionInfo("openvino_fx" if cmd_opts.use_openvino else "none", "Model compile backend", gr.Radio, {"choices": ['none', 'inductor', 'cudagraphs', 'aot_ts_nvfuser', 'hidet', 'ipex', 'openvino_fx', 'stable-fast']}), "cuda_compile_mode": OptionInfo("default", "Model compile mode", gr.Radio, {"choices": ['default', 'reduce-overhead', 'max-autotune', 'max-autotune-no-cudagraphs']}), "cuda_compile_fullgraph": OptionInfo(False, "Model compile fullgraph"), - "cuda_compile_precompile": OptionInfo(False if cmd_opts.use_openvino else True, "Model compile precompile"), + "cuda_compile_precompile": OptionInfo(False, "Model compile precompile"), "cuda_compile_verbose": OptionInfo(False, "Model compile verbose mode"), "cuda_compile_errors": OptionInfo(True, "Model compile suppress errors"), diff --git a/wiki b/wiki index 931082304..06a89abd7 160000 --- a/wiki +++ b/wiki @@ -1 +1 @@ -Subproject commit 931082304da70e7683d993e28a1765c8cb6844c1 +Subproject commit 06a89abd7c2db4608f52b11c9fbf2186a2cfac2b From 21eb4292e39f07df1118fd98f26bfd19d9907136 Mon Sep 17 00:00:00 2001 From: Disty0 Date: Wed, 6 Dec 2023 19:53:52 +0300 Subject: [PATCH 018/143] Fix IPEX Optimize --- modules/sd_models_compile.py | 18 +++++++++++------- 1 file changed, 11 insertions(+), 7 deletions(-) diff --git a/modules/sd_models_compile.py b/modules/sd_models_compile.py index ece694085..b68f29b6b 100644 --- a/modules/sd_models_compile.py +++ b/modules/sd_models_compile.py @@ -20,12 +20,15 @@ def __init__(self): self.partitioned_modules = {} -def optimize_ipex(sd_model): +def ipex_optimize(sd_model): try: t0 = time.time() import intel_extension_for_pytorch as ipex # pylint: disable=import-error, unused-import - sd_model.unet.training = False - sd_model.unet = ipex.optimize(sd_model.unet, dtype=devices.dtype_unet, inplace=True, weights_prepack=False) # pylint: disable=attribute-defined-outside-init + if hasattr(sd_model, 'unet'): + sd_model.unet.training = False + sd_model.unet = ipex.optimize(sd_model.unet, dtype=devices.dtype_unet, inplace=True, weights_prepack=False) # pylint: disable=attribute-defined-outside-init + else: + shared.log.warning('IPEX Optimize enabled but model has no Unet') if hasattr(sd_model, 'vae'): sd_model.vae.training = False sd_model.vae = ipex.optimize(sd_model.vae, dtype=devices.dtype_vae, inplace=True, weights_prepack=False) # pylint: disable=attribute-defined-outside-init @@ -33,9 +36,10 @@ def optimize_ipex(sd_model): sd_model.movq.training = False sd_model.movq = ipex.optimize(sd_model.movq, dtype=devices.dtype_vae, inplace=True, weights_prepack=False) # pylint: disable=attribute-defined-outside-init t1 = time.time() - shared.log.info(f"Model compile: mode=IPEX-optimize time={t1-t0:.2f}") + shared.log.info(f"IPEX Optimize: time={t1-t0:.2f}") + return sd_model except Exception as e: - shared.log.warning(f"Model compile: task=IPEX-optimize error: {e}") + shared.log.warning(f"IPEX Optimize: error: {e}") def optimize_openvino(): @@ -98,8 +102,6 @@ def compile_torch(sd_model): import torch._dynamo # pylint: disable=unused-import,redefined-outer-name torch._dynamo.reset() # pylint: disable=protected-access shared.log.debug(f"Model compile available backends: {torch._dynamo.list_backends()}") # pylint: disable=protected-access - if shared.opts.ipex_optimize: - optimize_ipex(sd_model) if shared.opts.cuda_compile_backend == "openvino_fx": optimize_openvino() log_level = logging.WARNING if shared.opts.cuda_compile_verbose else logging.CRITICAL # pylint: disable=protected-access @@ -131,6 +133,8 @@ def compile_torch(sd_model): def compile_diffusers(sd_model): + if shared.opts.ipex_optimize: + sd_model = ipex_optimize(sd_model) if not (shared.opts.cuda_compile or shared.opts.cuda_compile_vae or shared.opts.cuda_compile_upscaler): return sd_model if shared.opts.cuda_compile_backend == 'none': From bd64bac2a39da712eefa7b3cd73165d09001628a Mon Sep 17 00:00:00 2001 From: Vladimir Mandic Date: Thu, 7 Dec 2023 12:38:59 -0500 Subject: [PATCH 019/143] update ip-adapter, schedulers and xyz-grid --- CHANGELOG.md | 8 +++++-- README.md | 3 +++ extensions-builtin/sd-webui-controlnet | 2 +- modules/k-diffusion | 2 +- modules/sd_samplers_diffusers.py | 10 +++++--- modules/shared.py | 2 ++ modules/ui.py | 4 ++-- scripts/ipadapter.py | 33 +++++++++++++++----------- scripts/xyz_grid.py | 2 ++ 9 files changed, 43 insertions(+), 23 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 8db1f12af..72d8cc5b3 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,17 +1,21 @@ # Change Log for SD.Next -## Update for 2023-12-06 +## Update for 2023-12-07 *Note*: based on `diffusers==0.25.0.dev0` - **Diffusers** - **AnimateDiff** can now be used with *second pass* - enhance, upscale and hires your videos! - - **IP Adapter** add support for `ip-adapter-plus_sd15` and `ip-adapter-plus-face_sd15` + - **IP Adapter** add support for `ip-adapter-plus_sd15`, `ip-adapter-plus-face_sd15` and `ip-adapter-full-face_sd15` + additionally, ip-adapter can now be used in xyz-grid - **HDR controls** are now batch-aware for enhancement of multiple images or video frames - [Playground v1](https://huggingface.co/playgroundai/playground-v1), [Playground v2 256](https://huggingface.co/playgroundai/playground-v2-256px-base), [Playground v2 512](https://huggingface.co/playgroundai/playground-v2-512px-base), [Playground v2 1024](https://huggingface.co/playgroundai/playground-v2-1024px-aesthetic) model support - simply select from *networks -> reference* and use as usual - [ModelScope T2V](https://huggingface.co/damo-vilab/text-to-video-ms-1.7b) model support - simply select from *networks -> reference* and use from *txt2img* tab + - **Schedulers** + - add timesteps range, changing it will make scheduler to be over-complete or under-complete + - add rescale betas with zero SNR option (applicable to Euler and DDIM, allows for higher dynamic range) - **General** - **LoRA** add support for block weights, thanks @AI-Casanova example `` diff --git a/README.md b/README.md index 4c9e62ce3..79a647d11 100644 --- a/README.md +++ b/README.md @@ -211,6 +211,9 @@ General goals: ### **Docs** +If you're unsure how to use a feature, best place to start is [Wiki](https://github.com/vladmandic/automatic/wiki) and if its not there, +check [ChangeLog](CHANGELOG.md) for when feature was first introduced as it will always have a short note on how to use it + - [Wiki](https://github.com/vladmandic/automatic/wiki) - [ReadMe](README.md) - [ToDo](TODO.md) diff --git a/extensions-builtin/sd-webui-controlnet b/extensions-builtin/sd-webui-controlnet index 10bd9b25f..96dbc601a 160000 --- a/extensions-builtin/sd-webui-controlnet +++ b/extensions-builtin/sd-webui-controlnet @@ -1 +1 @@ -Subproject commit 10bd9b25f62deab9acb256301bbf3363c42645e7 +Subproject commit 96dbc601a6c880571d3a2a1314052d0922114604 diff --git a/modules/k-diffusion b/modules/k-diffusion index 045515774..cc49cf618 160000 --- a/modules/k-diffusion +++ b/modules/k-diffusion @@ -1 +1 @@ -Subproject commit 045515774882014cc14c1ba2668ab5bad9cbf7c0 +Subproject commit cc49cf6182284e577e896943f8e29c7c9d1a7f2c diff --git a/modules/sd_samplers_diffusers.py b/modules/sd_samplers_diffusers.py index 9cd3ee806..dc0ccc03c 100644 --- a/modules/sd_samplers_diffusers.py +++ b/modules/sd_samplers_diffusers.py @@ -26,7 +26,7 @@ config = { # beta_start, beta_end are typically per-scheduler, but we don't want them as they should be taken from the model itself as those are values model was trained on # prediction_type is ideally set in model as well, but it maybe needed that we do auto-detect of model type in the future - 'All': { 'num_train_timesteps': 1000, 'beta_start': 0.0001, 'beta_end': 0.02, 'beta_schedule': 'linear', 'prediction_type': 'epsilon' }, + 'All': { 'num_train_timesteps': 500, 'beta_start': 0.0001, 'beta_end': 0.02, 'beta_schedule': 'linear', 'prediction_type': 'epsilon' }, 'DDIM': { 'clip_sample': True, 'set_alpha_to_one': True, 'steps_offset': 0, 'clip_sample_range': 1.0, 'sample_max_value': 1.0, 'timestep_spacing': 'linspace', 'rescale_betas_zero_snr': False }, 'DDPM': { 'variance_type': "fixed_small", 'clip_sample': True, 'thresholding': False, 'clip_sample_range': 1.0, 'sample_max_value': 1.0, 'timestep_spacing': 'linspace'}, 'DEIS': { 'solver_order': 2, 'thresholding': False, 'sample_max_value': 1.0, 'algorithm_type': "deis", 'solver_type': "logrho", 'lower_order_final': True }, @@ -34,14 +34,14 @@ 'DPM++ 2M': { 'thresholding': False, 'sample_max_value': 1.0, 'algorithm_type': "dpmsolver++", 'solver_type': "midpoint", 'lower_order_final': True, 'use_karras_sigmas': False }, 'DPM SDE': { 'use_karras_sigmas': False }, 'Euler a': { }, - 'Euler': { 'interpolation_type': "linear", 'use_karras_sigmas': False }, + 'Euler': { 'interpolation_type': "linear", 'use_karras_sigmas': False, 'rescale_betas_zero_snr': False }, 'Heun': { 'use_karras_sigmas': False }, 'KDPM2': { 'steps_offset': 0 }, 'KDPM2 a': { 'steps_offset': 0 }, 'LMSD': { 'use_karras_sigmas': False, 'timestep_spacing': 'linspace', 'steps_offset': 0 }, 'PNDM': { 'skip_prk_steps': False, 'set_alpha_to_one': False, 'steps_offset': 0 }, 'UniPC': { 'solver_order': 2, 'thresholding': False, 'sample_max_value': 1.0, 'predict_x0': 'bh2', 'lower_order_final': True }, - 'LCM': { 'num_train_timesteps': 1000, 'beta_start': 0.00085, 'beta_end': 0.012, 'beta_schedule': "scaled_linear", 'set_alpha_to_one': True, 'rescale_betas_zero_snr': False }, + 'LCM': { 'beta_start': 0.00085, 'beta_end': 0.012, 'beta_schedule': "scaled_linear", 'set_alpha_to_one': True, 'rescale_betas_zero_snr': False }, } samplers_data_diffusers = [ @@ -108,6 +108,10 @@ def __init__(self, name, constructor, model, **kwargs): self.config['beta_start'] = shared.opts.schedulers_beta_start if 'beta_end' in self.config and shared.opts.schedulers_beta_end > 0: self.config['beta_end'] = shared.opts.schedulers_beta_end + if 'rescale_betas_zero_snr' in self.config: + self.config['rescale_betas_zero_snr'] = shared.opts.schedulers_rescale_betas + if 'num_train_timesteps' in self.config: + self.config['num_train_timesteps'] = shared.opts.schedulers_timesteps_range if name == 'DPM++ 2M': self.config['algorithm_type'] = shared.opts.schedulers_dpm_solver if name == 'DEIS': diff --git a/modules/shared.py b/modules/shared.py index 204ae7714..6569ab443 100644 --- a/modules/shared.py +++ b/modules/shared.py @@ -513,6 +513,8 @@ def default(obj): "schedulers_beta_schedule": OptionInfo("default", "Beta schedule", gr.Radio, {"choices": ['default', 'linear', 'scaled_linear', 'squaredcos_cap_v2']}), 'schedulers_beta_start': OptionInfo(0, "Beta start", gr.Slider, {"minimum": 0, "maximum": 1, "step": 0.00001}), 'schedulers_beta_end': OptionInfo(0, "Beta end", gr.Slider, {"minimum": 0, "maximum": 1, "step": 0.00001}), + 'schedulers_timesteps_range': OptionInfo(1000, "Timesteps range", gr.Slider, {"minimum": 250, "maximum": 4000, "step": 1}), + "schedulers_rescale_betas": OptionInfo(False, "Rescale betas with zero terminal SNR", gr.Checkbox), # managed from ui.py for backend original k-diffusion "schedulers_sep_kdiffusers": OptionInfo("

K-Diffusion specific config

", "", gr.HTML), diff --git a/modules/ui.py b/modules/ui.py index 6b076469b..39aaaf9b1 100644 --- a/modules/ui.py +++ b/modules/ui.py @@ -448,7 +448,7 @@ def create_ui(startup_timer = None): enable_hr = gr.Checkbox(label='Enable second pass', value=False, elem_id="txt2img_enable_hr") with FormRow(elem_id="sampler_selection_txt2img_alt_row1"): latent_index = gr.Dropdown(label='Secondary sampler', elem_id="txt2img_sampling_alt", choices=[x.name for x in modules.sd_samplers.samplers], value='Default', type="index") - denoising_strength = gr.Slider(minimum=0.0, maximum=1.0, step=0.05, label='Denoising strength', value=0.5, elem_id="txt2img_denoising_strength") + denoising_strength = gr.Slider(minimum=0.0, maximum=0.99, step=0.01, label='Denoising strength', value=0.5, elem_id="txt2img_denoising_strength") with FormRow(elem_id="txt2img_hires_finalres", variant="compact"): hr_final_resolution = FormHTML(value="", elem_id="txtimg_hr_finalres", label="Upscaled resolution", interactive=False) with FormRow(elem_id="txt2img_hires_fix_row1", variant="compact"): @@ -721,7 +721,7 @@ def update_orig(image, state): with gr.Accordion(open=False, label="Denoise", elem_classes=["small-accordion"], elem_id="img2img_denoise_group"): with FormRow(): - denoising_strength = gr.Slider(minimum=0.0, maximum=1.0, step=0.05, label='Denoising strength', value=0.75, elem_id="img2img_denoising_strength") + denoising_strength = gr.Slider(minimum=0.0, maximum=0.99, step=0.01, label='Denoising strength', value=0.75, elem_id="img2img_denoising_strength") refiner_start = gr.Slider(minimum=0.0, maximum=1.0, step=0.05, label='Denoise start', value=0.0, elem_id="img2img_refiner_start") with gr.Accordion(open=False, label="Advanced", elem_classes=["small-accordion"], elem_id="img2img_advanced_group"): diff --git a/scripts/ipadapter.py b/scripts/ipadapter.py index f581b4868..c822942ad 100644 --- a/scripts/ipadapter.py +++ b/scripts/ipadapter.py @@ -17,13 +17,13 @@ loaded = None ADAPTERS = [ 'none', - 'models/ip-adapter_sd15', - 'models/ip-adapter_sd15_light', - 'models/ip-adapter-plus_sd15', - 'models/ip-adapter-plus-face_sd15', - # 'models/ip-adapter-full-face_sd15', # KeyError: 'proj.weight' - 'sdxl_models/ip-adapter_sdxl', + 'ip-adapter_sd15', + 'ip-adapter_sd15_light', + 'ip-adapter-plus_sd15', + 'ip-adapter-plus-face_sd15', + 'ip-adapter-full-face_sd15', # 'models/ip-adapter_sd15_vit-G', # RuntimeError: mat1 and mat2 shapes cannot be multiplied (2x1024 and 1280x3072) + 'ip-adapter_sdxl', # 'sdxl_models/ip-adapter_sdxl_vit-h', # 'sdxl_models/ip-adapter-plus_sdxl_vit-h', # 'sdxl_models/ip-adapter-plus-face_sdxl_vit-h', @@ -47,9 +47,14 @@ def ui(self, _is_img2img): return [adapter, scale, image] def process(self, p: processing.StableDiffusionProcessing, adapter, scale, image): # pylint: disable=arguments-differ - import torch from transformers import CLIPVisionModelWithProjection - + # overrides + if hasattr(p, 'ip_adapter_name'): + adapter = p.ip_adapter_name + if hasattr(p, 'ip_adapter_scale'): + scale = p.ip_adapter_scale + if hasattr(p, 'ip_adapter_image'): + image = p.ip_adapter_image # init code global loaded, image_encoder # pylint: disable=global-statement if shared.sd_model is None: @@ -88,8 +93,8 @@ def process(self, p: processing.StableDiffusionProcessing, adapter, scale, image return # main code - subfolder, model = adapter.split('/') - if model != loaded or getattr(shared.sd_model.unet.config, 'encoder_hid_dim_type', None) is None: + subfolder = 'models' if 'sd15' in adapter else 'sdxl_models' + if adapter != loaded or getattr(shared.sd_model.unet.config, 'encoder_hid_dim_type', None) is None: t0 = time.time() if loaded is not None: shared.log.debug('IP adapter: reset attention processor') @@ -98,12 +103,12 @@ def process(self, p: processing.StableDiffusionProcessing, adapter, scale, image else: shared.log.debug('IP adapter: load attention processor') shared.sd_model.image_encoder = image_encoder - shared.sd_model.load_ip_adapter("h94/IP-Adapter", subfolder=subfolder, weight_name=f'{model}.safetensors') + shared.sd_model.load_ip_adapter("h94/IP-Adapter", subfolder=subfolder, weight_name=f'{adapter}.safetensors') t1 = time.time() - shared.log.info(f'IP adapter load: adapter="{model}" scale={scale} image={image} time={t1-t0:.2f}') - loaded = model + shared.log.info(f'IP adapter load: adapter="{adapter}" scale={scale} image={image} time={t1-t0:.2f}') + loaded = adapter else: - shared.log.debug(f'IP adapter cache: adapter="{model}" scale={scale} image={image}') + shared.log.debug(f'IP adapter cache: adapter="{adapter}" scale={scale} image={image}') shared.sd_model.set_ip_adapter_scale(scale) p.task_args['ip_adapter_image'] = p.batch_size * [image] p.extra_generation_params["IP Adapter"] = f'{adapter}:{scale}' diff --git a/scripts/xyz_grid.py b/scripts/xyz_grid.py index 15d11e555..f6fc5d553 100644 --- a/scripts/xyz_grid.py +++ b/scripts/xyz_grid.py @@ -269,6 +269,8 @@ def __init__(self, *args, **kwargs): AxisOption("[FreeU] 2nd stage backbone factor", float, apply_setting('freeu_b2')), AxisOption("[FreeU] 1st stage skip factor", float, apply_setting('freeu_s1')), AxisOption("[FreeU] 2nd stage skip factor", float, apply_setting('freeu_s2')), + AxisOption("[IP adapter] Name", str, apply_field('ip_adapter_name'), cost=1.0), + AxisOption("[IP adapter] Scale", float, apply_field('ip_adapter_scale')), ] From 7c3e0f6beb5ed86cbf185767001a541354000264 Mon Sep 17 00:00:00 2001 From: Midcoastal Date: Thu, 7 Dec 2023 18:29:10 -0500 Subject: [PATCH 020/143] Sticky Scrolling with New Data Indicator. --- javascript/logMonitor.js | 16 ++++++++++++++-- 1 file changed, 14 insertions(+), 2 deletions(-) diff --git a/javascript/logMonitor.js b/javascript/logMonitor.js index 7677cfced..af32dbeb5 100644 --- a/javascript/logMonitor.js +++ b/javascript/logMonitor.js @@ -9,8 +9,16 @@ async function logMonitor() { try { res = await fetch('/sdapi/v1/log?clear=True'); } catch {} if (res?.ok) { logMonitorStatus = true; - if (!logMonitorEl) logMonitorEl = document.getElementById('logMonitorData'); + if (!logMonitorEl) { + logMonitorEl = document.getElementById('logMonitorData'); + logMonitorEl.onscrollend = function() { + const at_bottom = logMonitorEl.scrollHeight <= (logMonitorEl.scrollTop + logMonitorEl.clientHeight) + if (at_bottom) + logMonitorEl.parentElement.style = ''; + } + } if (!logMonitorEl) return; + const at_bottom = logMonitorEl.scrollHeight <= (logMonitorEl.scrollTop + logMonitorEl.clientHeight) const lines = await res.json(); if (logMonitorEl && lines?.length > 0) logMonitorEl.parentElement.parentElement.style.display = opts.logmonitor_show ? 'block' : 'none'; for (const line of lines) { @@ -23,7 +31,11 @@ async function logMonitor() { } catch {} } while (logMonitorEl.childElementCount > 100) logMonitorEl.removeChild(logMonitorEl.firstChild); - logMonitorEl.scrollTop = logMonitorEl.scrollHeight; + if (at_bottom) { + logMonitorEl.scrollTop = logMonitorEl.scrollHeight; + } else if (lines?.length > 0) { + logMonitorEl.parentElement.style = 'border-bottom: 2px solid yellow'; + } } } From 931fa8f107166a9a201501421480db3b640c291a Mon Sep 17 00:00:00 2001 From: Midcoastal Date: Thu, 7 Dec 2023 19:01:55 -0500 Subject: [PATCH 021/143] Add ENV var to dissable Gradio Frontend Check This is nessesary for large Model collections, as the initial gradio frontend health check can timeout (3 seconds) and prevent the WebUI from loading. This is optional, and does not impact the actual load/launch or other functionality. --- webui.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/webui.py b/webui.py index d352bed72..1e53b7d47 100644 --- a/webui.py +++ b/webui.py @@ -266,6 +266,8 @@ def start_ui(): favicon_path='html/logo.ico', allowed_paths=[os.path.dirname(__file__), cmd_opts.data_dir], app_kwargs=fastapi_args, + # Workaround for issues with Gradio Network check timeouts (edge-case, but no other workound) + _frontend=not os.environ.get('SD_DISABLE_GRADIO_FRONTEND_CHECK', None), ) if cmd_opts.data_dir is not None: ui_tempdir.register_tmp_file(shared.demo, os.path.join(cmd_opts.data_dir, 'x')) From 7e937198e085fe667794b2f4820994c3fee44f9a Mon Sep 17 00:00:00 2001 From: Vladimir Mandic Date: Fri, 8 Dec 2023 07:14:34 -0500 Subject: [PATCH 022/143] enable ipadapter sd/sdxl switch --- scripts/ipadapter.py | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/scripts/ipadapter.py b/scripts/ipadapter.py index c822942ad..31ebd6d1f 100644 --- a/scripts/ipadapter.py +++ b/scripts/ipadapter.py @@ -14,6 +14,7 @@ image_encoder = None +image_encoder_type = None loaded = None ADAPTERS = [ 'none', @@ -56,7 +57,7 @@ def process(self, p: processing.StableDiffusionProcessing, adapter, scale, image if hasattr(p, 'ip_adapter_image'): image = p.ip_adapter_image # init code - global loaded, image_encoder # pylint: disable=global-statement + global loaded, image_encoder, image_encoder_type # pylint: disable=global-statement if shared.sd_model is None: return if shared.backend != shared.Backend.DIFFUSERS: @@ -85,9 +86,10 @@ def process(self, p: processing.StableDiffusionProcessing, adapter, scale, image else: shared.log.error(f'IP adapter: unsupported model type: {shared.sd_model_type}') return - if image_encoder is None: + if image_encoder is None or image_encoder_type != shared.sd_model_type: try: image_encoder = CLIPVisionModelWithProjection.from_pretrained("h94/IP-Adapter", subfolder=subfolder, torch_dtype=devices.dtype, cache_dir=shared.opts.diffusers_dir, use_safetensors=True).to(devices.device) + image_encoder_type = shared.sd_model_type except Exception as e: shared.log.error(f'IP adapter: failed to load image encoder: {e}') return From 72a167c43a863d8c4dd38981a7b3b26c57b511e0 Mon Sep 17 00:00:00 2001 From: Vladimir Mandic Date: Fri, 8 Dec 2023 10:24:01 -0500 Subject: [PATCH 023/143] add video processing --- CHANGELOG.md | 4 +++ javascript/logMonitor.js | 18 +++++------- modules/images.py | 36 ++++++++++++++--------- modules/postprocessing.py | 20 +++++++++---- modules/rife/__init__.py | 6 ++-- modules/rife/model_rife.py | 2 +- modules/scripts_postprocessing.py | 16 +++++++++-- modules/ui_postprocessing.py | 7 +++-- requirements.txt | 1 + scripts/postprocessing_video.py | 47 +++++++++++++++++++++++++++++++ 10 files changed, 119 insertions(+), 38 deletions(-) create mode 100644 scripts/postprocessing_video.py diff --git a/CHANGELOG.md b/CHANGELOG.md index 72d8cc5b3..b0871fec3 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -16,6 +16,9 @@ - **Schedulers** - add timesteps range, changing it will make scheduler to be over-complete or under-complete - add rescale betas with zero SNR option (applicable to Euler and DDIM, allows for higher dynamic range) +- **Process** + - create videos from batch or folder processing + supports GIF, PNG and MP4 with full interpolation, scene change detection, etc. - **General** - **LoRA** add support for block weights, thanks @AI-Casanova example `` @@ -25,6 +28,7 @@ - disable google fonts check on server startup - fix torchvision/basicsr compatibility - add hdr settings to metadata + - update built-in log monitor in ui, thanks @midcoastal ## Update for 2023-12-04 diff --git a/javascript/logMonitor.js b/javascript/logMonitor.js index af32dbeb5..af744aee1 100644 --- a/javascript/logMonitor.js +++ b/javascript/logMonitor.js @@ -11,14 +11,13 @@ async function logMonitor() { logMonitorStatus = true; if (!logMonitorEl) { logMonitorEl = document.getElementById('logMonitorData'); - logMonitorEl.onscrollend = function() { - const at_bottom = logMonitorEl.scrollHeight <= (logMonitorEl.scrollTop + logMonitorEl.clientHeight) - if (at_bottom) - logMonitorEl.parentElement.style = ''; - } + logMonitorEl.onscrollend = () => { + const at_bottom = logMonitorEl.scrollHeight <= (logMonitorEl.scrollTop + logMonitorEl.clientHeight); + if (at_bottom) logMonitorEl.parentElement.style = ''; + }; } if (!logMonitorEl) return; - const at_bottom = logMonitorEl.scrollHeight <= (logMonitorEl.scrollTop + logMonitorEl.clientHeight) + const at_bottom = logMonitorEl.scrollHeight <= (logMonitorEl.scrollTop + logMonitorEl.clientHeight); const lines = await res.json(); if (logMonitorEl && lines?.length > 0) logMonitorEl.parentElement.parentElement.style.display = opts.logmonitor_show ? 'block' : 'none'; for (const line of lines) { @@ -31,11 +30,8 @@ async function logMonitor() { } catch {} } while (logMonitorEl.childElementCount > 100) logMonitorEl.removeChild(logMonitorEl.firstChild); - if (at_bottom) { - logMonitorEl.scrollTop = logMonitorEl.scrollHeight; - } else if (lines?.length > 0) { - logMonitorEl.parentElement.style = 'border-bottom: 2px solid yellow'; - } + if (at_bottom) logMonitorEl.scrollTop = logMonitorEl.scrollHeight; + else if (lines?.length > 0) logMonitorEl.parentElement.style = 'border-bottom: 2px solid var(--highlight-color);'; } } diff --git a/modules/images.py b/modules/images.py index 0dbe64e81..3e297663e 100644 --- a/modules/images.py +++ b/modules/images.py @@ -322,6 +322,8 @@ class FilenameGenerator: default_time_format = '%Y%m%d%H%M%S' def __init__(self, p, seed, prompt, image, grid=False): + if getattr(self, 'p', None) is None: + return self.p = p self.seed = seed self.prompt = prompt @@ -335,7 +337,7 @@ def __init__(self, p, seed, prompt, image, grid=False): def hasprompt(self, *args): lower = self.prompt.lower() - if self.p is None or self.prompt is None: + if getattr(self, 'p', None) is None or getattr(self, 'prompt', None) is None: return None outres = "" for arg in args: @@ -350,7 +352,7 @@ def hasprompt(self, *args): return outres def image_hash(self): - if self.image is None: + if getattr(self, 'image', None) is None: return None import base64 from io import BytesIO @@ -364,7 +366,7 @@ def prompt_full(self): return self.prompt_sanitize(self.prompt) def prompt_words(self): - if self.prompt is None: + if getattr(self, 'prompt', None) is None: return '' no_attention = re_attention.sub(r'\1', self.prompt) no_network = re_network.sub(r'\1', no_attention) @@ -374,7 +376,7 @@ def prompt_words(self): return self.prompt_sanitize(prompt) def prompt_no_style(self): - if self.p is None or self.prompt is None: + if getattr(self, 'p', None) is None or getattr(self, 'prompt', None) is None: return None prompt_no_style = self.prompt for style in shared.prompt_styles.get_style_prompts(self.p.styles): @@ -455,7 +457,7 @@ def apply(self, x): break pattern, arg = m.groups() pattern_args.insert(0, arg) - fun = self.replacements.get(pattern.lower()) + fun = self.replacements.get(pattern.lower(), None) if fun is not None: try: replacement = fun(self, *pattern_args) @@ -525,7 +527,7 @@ def atomically_save_image(): try: image.save(fn, format=image_format, compress_level=6, pnginfo=pnginfo_data if shared.opts.image_metadata else None) except Exception as e: - shared.log.warning(f'Image save failed: {fn} {e}') + shared.log.error(f'Image save failed: file="{fn}" {e}') elif image_format == 'JPEG': if image.mode == 'RGBA': shared.log.warning('Saving RGBA image as JPEG: Alpha channel will be lost') @@ -536,7 +538,7 @@ def atomically_save_image(): try: image.save(fn, format=image_format, optimize=True, quality=shared.opts.jpeg_quality, exif=exif_bytes) except Exception as e: - shared.log.warning(f'Image save failed: {fn} {e}') + shared.log.error(f'Image save failed: file="{fn}" {e}') elif image_format == 'WEBP': if image.mode == 'I;16': image = image.point(lambda p: p * 0.0038910505836576).convert("RGB") @@ -544,13 +546,13 @@ def atomically_save_image(): try: image.save(fn, format=image_format, quality=shared.opts.jpeg_quality, lossless=shared.opts.webp_lossless, exif=exif_bytes) except Exception as e: - shared.log.warning(f'Image save failed: {fn} {e}') + shared.log.error(f'Image save failed: file="{fn}" {e}') else: # shared.log.warning(f'Unrecognized image format: {extension} attempting save as {image_format}') try: image.save(fn, format=image_format, quality=shared.opts.jpeg_quality) except Exception as e: - shared.log.warning(f'Image save failed: {fn} {e}') + shared.log.error(f'Image save failed: file="{fn}" {e}') with open(os.path.join(paths.data_path, "params.txt"), "w", encoding="utf8") as file: file.write(exifinfo) if shared.opts.save_log_fn != '' and len(exifinfo) > 0: @@ -668,12 +670,20 @@ def save_video(p, images, filename = None, video_type: str = 'none', duration: f if images is None or len(images) < 2 or video_type is None or video_type.lower() == 'none': return image = images[0] - namegen = FilenameGenerator(p, seed=p.all_seeds[0], prompt=p.all_prompts[0], image=image) - if filename is None: + if p is not None: + namegen = FilenameGenerator(p, seed=p.all_seeds[0], prompt=p.all_prompts[0], image=image) + else: + namegen = FilenameGenerator(None, seed=0, prompt='', image=image) + if filename is None and p is not None: filename = namegen.apply(shared.opts.samples_filename_pattern if shared.opts.samples_filename_pattern and len(shared.opts.samples_filename_pattern) > 0 else "[seq]-[prompt_words]") - filename = namegen.sanitize(os.path.join(shared.opts.outdir_video, filename)) + filename = os.path.join(shared.opts.outdir_video, filename) filename = namegen.sequence(filename, shared.opts.outdir_video, '') - filename = f'{filename}.{video_type.lower()}' + else: + if not os.pathsep in filename: + filename = os.path.join(shared.opts.outdir_video, filename) + if not filename.lower().endswith(video_type.lower()): + filename += f'.{video_type.lower()}' + filename = namegen.sanitize(filename) threading.Thread(target=save_video_atomic, args=(images, filename, video_type, duration, loop, interpolate, scale, pad, change)).start() diff --git a/modules/postprocessing.py b/modules/postprocessing.py index 82de57578..150c641dc 100644 --- a/modules/postprocessing.py +++ b/modules/postprocessing.py @@ -13,24 +13,29 @@ def run_postprocessing(extras_mode, image, image_folder: List[tempfile.NamedTemp shared.state.begin('extras') image_data = [] image_names = [] + image_fullnames = [] image_ext = [] outputs = [] params = {} if extras_mode == 1: - shared.log.debug(f'process: mode=batch folder={image_folder}') for img in image_folder: if isinstance(img, Image.Image): image = img fn = '' ext = None else: - image = Image.open(os.path.abspath(img.name)) + try: + image = Image.open(os.path.abspath(img.name)) + except Exception as e: + shared.log.error(f'Failed to open image: file="{img.name}" {e}') + continue fn, ext = os.path.splitext(img.orig_name) + image_fullnames.append(img.name) image_data.append(image) image_names.append(fn) image_ext.append(ext) + shared.log.debug(f'Process: mode=batch inputs={len(image_folder)} images={len(image_data)}') elif extras_mode == 2: - shared.log.debug(f'process: mode=folder folder={input_dir}') assert not shared.cmd_opts.hide_ui_dir_config, '--hide-ui-dir-config option must be disabled' assert input_dir, 'input directory not selected' image_list = shared.listfiles(input_dir) @@ -38,11 +43,13 @@ def run_postprocessing(extras_mode, image, image_folder: List[tempfile.NamedTemp try: image = Image.open(filename) except Exception as e: - shared.log.error(f'Failed to open image: {filename} {e}') + shared.log.error(f'Failed to open image: file="{filename}" {e}') continue + image_fullnames.append(filename) image_data.append(image) image_names.append(filename) image_ext.append(None) + shared.log.debug(f'Process: mode=folder inputs={input_dir} files={len(image_list)} images={len(image_data)}') else: image_data.append(image) image_names.append(None) @@ -51,8 +58,9 @@ def run_postprocessing(extras_mode, image, image_folder: List[tempfile.NamedTemp outpath = output_dir else: outpath = opts.outdir_samples or opts.outdir_extras_samples + processed_images = [] for image, name, ext in zip(image_data, image_names, image_ext): # pylint: disable=redefined-argument-from-local - shared.log.debug(f'process: image={image} {args}') + shared.log.debug(f'Process: image={image} {args}') infotext = '' if shared.state.interrupted: shared.log.debug('Postprocess interrupted') @@ -74,11 +82,13 @@ def run_postprocessing(extras_mode, image, image_folder: List[tempfile.NamedTemp infotext = items['parameters'] + ', ' infotext = infotext + ", ".join([k if k == v else f'{k}: {generation_parameters_copypaste.quote(v)}' for k, v in pp.info.items() if v is not None]) pp.image.info["postprocessing"] = infotext + processed_images.append(pp.image) if save_output: images.save_image(pp.image, path=outpath, basename=basename, seed=None, prompt=None, extension=ext or opts.samples_format, info=infotext, short_filename=True, no_prompt=True, grid=False, pnginfo_section_name="extras", existing_info=pp.image.info, forced_filename=None) if extras_mode != 2 or show_extras_results: outputs.append(pp.image) image.close() + scripts.scripts_postproc.postprocess(processed_images, args) devices.torch_gc() return outputs, infotext, params diff --git a/modules/rife/__init__.py b/modules/rife/__init__.py index a7db7d9a2..7e40735e7 100644 --- a/modules/rife/__init__.py +++ b/modules/rife/__init__.py @@ -11,12 +11,12 @@ from torch.nn import functional as F from tqdm.rich import tqdm from modules.rife.ssim import ssim_matlab -from modules.rife.model_rife import Model +from modules.rife.model_rife import RifeModel from modules import devices, shared model_url = 'https://github.com/vladmandic/rife/raw/main/model/flownet-v46.pkl' -model = None +model: RifeModel = None def load(model_path: str = 'rife/flownet-v46.pkl'): @@ -26,7 +26,7 @@ def load(model_path: str = 'rife/flownet-v46.pkl'): model_dir = os.path.join(shared.models_path, 'RIFE') model_path = modelloader.load_file_from_url(url=model_url, model_dir=model_dir, file_name='flownet-v46.pkl') shared.log.debug(f'RIFE load model: file="{model_path}"') - model = Model() + model = RifeModel() model.load_model(model_path, -1) model.eval() model.device() diff --git a/modules/rife/model_rife.py b/modules/rife/model_rife.py index ce8c363cd..52d359923 100644 --- a/modules/rife/model_rife.py +++ b/modules/rife/model_rife.py @@ -6,7 +6,7 @@ from modules import devices -class Model: +class RifeModel: def __init__(self, local_rank=-1): self.flownet = IFNet() self.device() diff --git a/modules/scripts_postprocessing.py b/modules/scripts_postprocessing.py index 47de61b51..f4825da17 100644 --- a/modules/scripts_postprocessing.py +++ b/modules/scripts_postprocessing.py @@ -101,7 +101,7 @@ def run(self, pp: PostprocessedImage, args): process_args = {} for (name, _component), value in zip(script.controls.items(), script_args): process_args[name] = value - shared.log.debug(f'postprocess: script={script.name} args={process_args}') + shared.log.debug(f'Process: script={script.name} args={process_args}') script.process(pp, **process_args) def create_args_for_run(self, scripts_args): @@ -110,15 +110,25 @@ def create_args_for_run(self, scripts_args): self.setup_ui() scripts = self.scripts_in_preferred_order() args = [None] * max([x.args_to for x in scripts]) - for script in scripts: script_args_dict = scripts_args.get(script.name, None) if script_args_dict is not None: for i, name in enumerate(script.controls): args[script.args_from + i] = script_args_dict.get(name, None) - return args def image_changed(self): for script in self.scripts_in_preferred_order(): script.image_changed() + + def postprocess(self, filenames, args): + for script in self.scripts_in_preferred_order(): + if not hasattr(script, 'postprocess'): + continue + shared.state.job = script.name + script_args = args[script.args_from:script.args_to] + process_args = {} + for (name, _component), value in zip(script.controls.items(), script_args): + process_args[name] = value + shared.log.debug(f'Postprocess: script={script.name} args={process_args}') + script.postprocess(filenames, **process_args) diff --git a/modules/ui_postprocessing.py b/modules/ui_postprocessing.py index 334afcc9a..de0b71d3f 100644 --- a/modules/ui_postprocessing.py +++ b/modules/ui_postprocessing.py @@ -12,8 +12,8 @@ def wrap_pnginfo(image): return infotext_to_html(geninfo), info, geninfo -def submit_click(tab_index, extras_image, image_batch, extras_batch_input_dir, extras_batch_output_dir, show_extras_results, *script_inputs): - result_images, geninfo, js_info = postprocessing.run_postprocessing(tab_index, extras_image, image_batch, extras_batch_input_dir, extras_batch_output_dir, show_extras_results, *script_inputs) +def submit_click(tab_index, extras_image, image_batch, extras_batch_input_dir, extras_batch_output_dir, show_extras_results, save_output, *script_inputs): + result_images, geninfo, js_info = postprocessing.run_postprocessing(tab_index, extras_image, image_batch, extras_batch_input_dir, extras_batch_output_dir, show_extras_results, *script_inputs, save_output=save_output) return result_images, geninfo, json.dumps(js_info), '' @@ -32,6 +32,8 @@ def create_ui(): show_extras_results = gr.Checkbox(label='Show result images', value=True, elem_id="extras_show_extras_results") with gr.Row(): buttons = parameters_copypaste.create_buttons(["txt2img", "img2img", "inpaint"]) + with gr.Row(): + save_output = gr.Checkbox(label='Save output', value=True, elem_id="extras_save_output") script_inputs = scripts.scripts_postproc.setup_ui() with gr.Column(): id_part = 'extras' @@ -66,6 +68,7 @@ def create_ui(): extras_batch_input_dir, extras_batch_output_dir, show_extras_results, + save_output, *script_inputs, ], outputs=[ diff --git a/requirements.txt b/requirements.txt index bf54222d3..eb7d0c930 100644 --- a/requirements.txt +++ b/requirements.txt @@ -42,6 +42,7 @@ scikit-image basicsr fasteners dctorch +pymatting httpx==0.24.1 compel==2.0.2 torchsde==0.2.6 diff --git a/scripts/postprocessing_video.py b/scripts/postprocessing_video.py new file mode 100644 index 000000000..f34036961 --- /dev/null +++ b/scripts/postprocessing_video.py @@ -0,0 +1,47 @@ +import gradio as gr +import modules.images +from modules import scripts_postprocessing + + +class ScriptPostprocessingUpscale(scripts_postprocessing.ScriptPostprocessing): + name = "Video" + + def ui(self): + def video_type_change(video_type): + return [ + gr.update(visible=video_type != 'None'), + gr.update(visible=video_type == 'GIF' or video_type == 'PNG'), + gr.update(visible=video_type == 'MP4'), + gr.update(visible=video_type == 'MP4'), + gr.update(visible=video_type == 'MP4'), + gr.update(visible=video_type == 'MP4'), + ] + + with gr.Row(): + video_type = gr.Dropdown(label='Video file', choices=['None', 'GIF', 'PNG', 'MP4'], value='None') + duration = gr.Slider(label='Duration', minimum=0.25, maximum=10, step=0.25, value=2, visible=False) + with gr.Row(): + loop = gr.Checkbox(label='Loop', value=True, visible=False) + pad = gr.Slider(label='Pad frames', minimum=0, maximum=24, step=1, value=1, visible=False) + interpolate = gr.Slider(label='Interpolate frames', minimum=0, maximum=24, step=1, value=0, visible=False) + scale = gr.Slider(label='Rescale', minimum=0.5, maximum=2, step=0.05, value=1, visible=False) + change = gr.Slider(label='Frame change sensitivity', minimum=0, maximum=1, step=0.05, value=0.3, visible=False) + with gr.Row(): + filename = gr.Textbox(label='Filename', placeholder='enter filename', lines=1) + video_type.change(fn=video_type_change, inputs=[video_type], outputs=[duration, loop, pad, interpolate, scale, change]) + return { + "filename": filename, + "video_type": video_type, + "duration": duration, + "loop": loop, + "pad": pad, + "interpolate": interpolate, + "scale": scale, + "change": change, + } + + def postprocess(self, images, filename, video_type, duration, loop, pad, interpolate, scale, change): # pylint: disable=arguments-differ + filename = filename.strip() + if video_type == 'None' or len(filename) == 0 or images is None or len(images) < 2: + return + modules.images.save_video(p=None, filename=filename, images=images, video_type=video_type, duration=duration, loop=loop, pad=pad, interpolate=interpolate, scale=scale, change=change) From 66f7a9af82868fd7fcc40bce4e1e71c3ad751cd7 Mon Sep 17 00:00:00 2001 From: Vladimir Mandic Date: Fri, 8 Dec 2023 10:43:50 -0500 Subject: [PATCH 024/143] add lora_force_diffusers and lora_fuse_diffusers --- CHANGELOG.md | 9 +++++++-- extensions-builtin/Lora/extra_networks_lora.py | 2 ++ extensions-builtin/Lora/lora_patches.py | 7 ++----- extensions-builtin/Lora/networks.py | 7 +++---- modules/patches.py | 1 + modules/shared.py | 2 ++ 6 files changed, 17 insertions(+), 11 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index b0871fec3..5000743af 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -19,9 +19,14 @@ - **Process** - create videos from batch or folder processing supports GIF, PNG and MP4 with full interpolation, scene change detection, etc. + - **LoRA** + - add support for block weights, thanks @AI-Casanova + example `` + - reintroduce alternative loading method in settings: `lora_force_diffusers` + - add support for `lora_fuse_diffusers` if using alternative method + use if you have multiple complex loras that may be causing performance degradation + as it fuses lora with model during load instead of interpreting lora on-the-fly - **General** - - **LoRA** add support for block weights, thanks @AI-Casanova - example `` - **Extra networks** new *settting -> extra networks -> build info on first access* indexes all networks on first access instead of server startup - **Ipex** improvements, thanks @disty0 diff --git a/extensions-builtin/Lora/extra_networks_lora.py b/extensions-builtin/Lora/extra_networks_lora.py index a5d9e1726..5ef27e0fc 100644 --- a/extensions-builtin/Lora/extra_networks_lora.py +++ b/extensions-builtin/Lora/extra_networks_lora.py @@ -69,6 +69,8 @@ def activate(self, p, params_list): def deactivate(self, p): if shared.backend == shared.Backend.DIFFUSERS and hasattr(shared.sd_model, "unload_lora_weights") and hasattr(shared.sd_model, "text_encoder"): if 'CLIP' in shared.sd_model.text_encoder.__class__.__name__ and not (shared.opts.cuda_compile and shared.opts.cuda_compile_backend == "openvino_fx"): + if shared.opts.lora_fuse_diffusers: + shared.sd_model.unfuse_lora() shared.sd_model.unload_lora_weights() if not self.active and getattr(networks, "originals", None ) is not None: networks.originals.undo() # remove patches diff --git a/extensions-builtin/Lora/lora_patches.py b/extensions-builtin/Lora/lora_patches.py index 983d80240..9ea6a10c3 100644 --- a/extensions-builtin/Lora/lora_patches.py +++ b/extensions-builtin/Lora/lora_patches.py @@ -1,10 +1,7 @@ -import os import torch import networks from modules import patches, shared -# OpenVINO only works with Diffusers LoRa loading -force_lora_diffusers = os.environ.get('SD_LORA_DIFFUSERS', None) is not None class LoraPatches: def __init__(self): @@ -21,7 +18,7 @@ def __init__(self): self.MultiheadAttention_load_state_dict = None def apply(self): - if self.active or force_lora_diffusers: + if self.active or shared.opts.lora_force_diffusers: return self.Linear_forward = patches.patch(__name__, torch.nn.Linear, 'forward', networks.network_Linear_forward) self.Linear_load_state_dict = patches.patch(__name__, torch.nn.Linear, '_load_from_state_dict', networks.network_Linear_load_state_dict) @@ -39,7 +36,7 @@ def apply(self): self.active = True def undo(self): - if not self.active or force_lora_diffusers: + if not self.active or shared.opts.lora_force_diffusers: return self.Linear_forward = patches.undo(__name__, torch.nn.Linear, 'forward') # pylint: disable=E1128 self.Linear_load_state_dict = patches.undo(__name__, torch.nn.Linear, '_load_from_state_dict') # pylint: disable=E1128 diff --git a/extensions-builtin/Lora/networks.py b/extensions-builtin/Lora/networks.py index 05e36e8db..8c2d11b5b 100644 --- a/extensions-builtin/Lora/networks.py +++ b/extensions-builtin/Lora/networks.py @@ -39,7 +39,6 @@ network_norm.ModuleTypeNorm(), ] convert_diffusers_name_to_compvis = lora_convert.convert_diffusers_name_to_compvis # supermerger compatibility item -force_lora_diffusers = os.environ.get('SD_LORA_DIFFUSERS', None) is not None # OpenVINO only works with Diffusers LoRa loading def assign_network_names_to_compvis_modules(sd_model): @@ -79,13 +78,13 @@ def load_diffusers(name, network_on_disk, lora_scale=1.0): t0 = time.time() cached = lora_cache.get(name, None) # if debug: - shared.log.debug(f'LoRA load: name={name} file={network_on_disk.filename} type=diffusers {"cached" if cached else ""}') + shared.log.debug(f'LoRA load: name={name} file={network_on_disk.filename} type=diffusers {"cached" if cached else ""} fuse={shared.opts.lora_fuse_diffusers}') if cached is not None: return cached if shared.backend != shared.Backend.DIFFUSERS: return None shared.sd_model.load_lora_weights(network_on_disk.filename) - if shared.opts.cuda_compile and shared.opts.cuda_compile_backend == "openvino_fx": + if (shared.opts.cuda_compile and shared.opts.cuda_compile_backend == "openvino_fx") or shared.opts.lora_fuse_diffusers: shared.sd_model.fuse_lora(lora_scale=lora_scale) net = network.Network(name, network_on_disk) net.mtime = os.path.getmtime(network_on_disk.filename) @@ -171,7 +170,7 @@ def load_networks(names, te_multipliers=None, unet_multipliers=None, dyn_dims=No try: if recompile_model: shared.compiled_model_state.lora_model.append(f"{name}:{te_multipliers[i] if te_multipliers else 1.0}") - if shared.backend == shared.Backend.DIFFUSERS and force_lora_diffusers: # OpenVINO only works with Diffusers LoRa loading. + if shared.backend == shared.Backend.DIFFUSERS and shared.opts.lora_force_diffusers: # OpenVINO only works with Diffusers LoRa loading. # or getattr(network_on_disk, 'shorthash', '').lower() == 'aaebf6360f7d' # sd15-lcm # or getattr(network_on_disk, 'shorthash', '').lower() == '3d18b05e4f56' # sdxl-lcm # or getattr(network_on_disk, 'shorthash', '').lower() == '813ea5fb1c67' # turbo sdxl-turbo diff --git a/modules/patches.py b/modules/patches.py index a6c3a25bb..cff6bfd64 100644 --- a/modules/patches.py +++ b/modules/patches.py @@ -50,6 +50,7 @@ def undo(key, obj, field): original_func = originals[key].pop(patch_key) setattr(obj, field, original_func) + return None def original(key, obj, field): diff --git a/modules/shared.py b/modules/shared.py index 6569ab443..e75a66c17 100644 --- a/modules/shared.py +++ b/modules/shared.py @@ -607,6 +607,8 @@ def default(obj): "extra_networks_styles": OptionInfo(True, "Show built-in styles"), "lora_preferred_name": OptionInfo("filename", "LoRA preffered name", gr.Radio, {"choices": ["filename", "alias"]}), "lora_add_hashes_to_infotext": OptionInfo(True, "LoRA add hash info"), + "lora_force_diffusers": OptionInfo(False if not cmd_opts.use_openvino else True, "LoRA use alternative loading method"), + "lora_fuse_diffusers": OptionInfo(False, "LoRA use merge when using alternative method"), "lora_in_memory_limit": OptionInfo(0, "LoRA memory cache", gr.Slider, {"minimum": 0, "maximum": 24, "step": 1}), "lora_functional": OptionInfo(False, "Use Kohya method for handling multiple LoRA", gr.Checkbox, { "visible": False }), "sd_hypernetwork": OptionInfo("None", "Add hypernetwork to prompt", gr.Dropdown, { "choices": ["None"], "visible": False }), From dc56ea140a223cfcb2f4daf1c07b6f1707151c39 Mon Sep 17 00:00:00 2001 From: Disty0 Date: Fri, 8 Dec 2023 19:10:08 +0300 Subject: [PATCH 025/143] Update shared opts --- extensions-builtin/Lora/networks.py | 2 +- installer.py | 1 - modules/shared.py | 24 ++++++++++++------------ 3 files changed, 13 insertions(+), 14 deletions(-) diff --git a/extensions-builtin/Lora/networks.py b/extensions-builtin/Lora/networks.py index 8c2d11b5b..e6e69d36b 100644 --- a/extensions-builtin/Lora/networks.py +++ b/extensions-builtin/Lora/networks.py @@ -84,7 +84,7 @@ def load_diffusers(name, network_on_disk, lora_scale=1.0): if shared.backend != shared.Backend.DIFFUSERS: return None shared.sd_model.load_lora_weights(network_on_disk.filename) - if (shared.opts.cuda_compile and shared.opts.cuda_compile_backend == "openvino_fx") or shared.opts.lora_fuse_diffusers: + if shared.opts.lora_fuse_diffusers: shared.sd_model.fuse_lora(lora_scale=lora_scale) net = network.Network(name, network_on_disk) net.mtime = os.path.getmtime(network_on_disk.filename) diff --git a/installer.py b/installer.py index 413463fbb..e9d8e20c7 100644 --- a/installer.py +++ b/installer.py @@ -507,7 +507,6 @@ def check_torch(): install('openvino==2023.2.0', 'openvino') install('onnxruntime-openvino', 'onnxruntime-openvino', ignore=True) # TODO openvino: numpy version conflicts with tensorflow and doesn't support Python 3.11 os.environ.setdefault('PYTORCH_TRACING_MODE', 'TORCHFX') - os.environ.setdefault('SD_LORA_DIFFUSERS', '1') os.environ.setdefault('NEOReadDebugKeys', '1') os.environ.setdefault('ClDeviceGlobalMemSizeAvailablePercent', '100') if args.profile: diff --git a/modules/shared.py b/modules/shared.py index e75a66c17..1d6ee1586 100644 --- a/modules/shared.py +++ b/modules/shared.py @@ -258,7 +258,7 @@ def default(obj): options_templates.update(options_section(('sd', "Execution & Models"), { - "sd_backend": OptionInfo("diffusers" if cmd_opts.use_openvino else "original", "Execution backend", gr.Radio, {"choices": ["original", "diffusers"] }), + "sd_backend": OptionInfo("original" if not cmd_opts.use_openvino else "diffusers", "Execution backend", gr.Radio, {"choices": ["original", "diffusers"] }), "sd_checkpoint_autoload": OptionInfo(True, "Model autoload on server start"), "sd_model_checkpoint": OptionInfo(default_checkpoint, "Base model", gr.Dropdown, lambda: {"choices": list_checkpoint_tiles()}, refresh=refresh_checkpoints), "sd_model_refiner": OptionInfo('None', "Refiner model", gr.Dropdown, lambda: {"choices": ['None'] + list_checkpoint_tiles()}, refresh=refresh_checkpoints), @@ -278,9 +278,9 @@ def default(obj): "math_sep": OptionInfo("

Execution precision

", "", gr.HTML), "precision": OptionInfo("Autocast", "Precision type", gr.Radio, {"choices": ["Autocast", "Full"]}), "cuda_dtype": OptionInfo("FP32" if sys.platform == "darwin" or cmd_opts.use_openvino else "BF16" if devices.backend == "ipex" else "FP16", "Device precision type", gr.Radio, {"choices": ["FP32", "FP16", "BF16"]}), - "no_half": OptionInfo(True if cmd_opts.use_openvino else False, "Use full precision for model (--no-half)", None, None, None), - "no_half_vae": OptionInfo(True if cmd_opts.use_openvino else False, "Use full precision for VAE (--no-half-vae)"), - "upcast_sampling": OptionInfo(True if sys.platform == "darwin" else False, "Enable upcast sampling"), + "no_half": OptionInfo(False if not cmd_opts.use_openvino else True, "Use full precision for model (--no-half)", None, None, None), + "no_half_vae": OptionInfo(False if not cmd_opts.use_openvino else True, "Use full precision for VAE (--no-half-vae)"), + "upcast_sampling": OptionInfo(False if not sys.platform == "darwin" else True, "Enable upcast sampling"), "upcast_attn": OptionInfo(False, "Enable upcast cross attention layer"), "cuda_cast_unet": OptionInfo(False, "Use fixed UNet precision"), "disable_nan_check": OptionInfo(True, "Disable NaN check in produced images/latent spaces", gr.Checkbox, {"visible": False}), @@ -300,10 +300,10 @@ def default(obj): "torch_gc_threshold": OptionInfo(80 if devices.backend == "ipex" else 90, "VRAM usage threshold before running Torch GC to clear up VRAM", gr.Slider, {"minimum": 0, "maximum": 100, "step": 1}), "cuda_compile_sep": OptionInfo("

Model Compile

", "", gr.HTML), - "cuda_compile": OptionInfo(True if cmd_opts.use_openvino else False, "Compile UNet"), - "cuda_compile_vae": OptionInfo(True if cmd_opts.use_openvino else False, "Compile VAE"), - "cuda_compile_upscaler": OptionInfo(True if cmd_opts.use_openvino else False, "Compile upscaler"), - "cuda_compile_backend": OptionInfo("openvino_fx" if cmd_opts.use_openvino else "none", "Model compile backend", gr.Radio, {"choices": ['none', 'inductor', 'cudagraphs', 'aot_ts_nvfuser', 'hidet', 'ipex', 'openvino_fx', 'stable-fast']}), + "cuda_compile": OptionInfo(False if not cmd_opts.use_openvino else True, "Compile UNet"), + "cuda_compile_vae": OptionInfo(False if not cmd_opts.use_openvino else True, "Compile VAE"), + "cuda_compile_upscaler": OptionInfo(False if not cmd_opts.use_openvino else True, "Compile upscaler"), + "cuda_compile_backend": OptionInfo("none" if not cmd_opts.use_openvino else "openvino_fx", "Model compile backend", gr.Radio, {"choices": ['none', 'inductor', 'cudagraphs', 'aot_ts_nvfuser', 'hidet', 'ipex', 'openvino_fx', 'stable-fast']}), "cuda_compile_mode": OptionInfo("default", "Model compile mode", gr.Radio, {"choices": ['default', 'reduce-overhead', 'max-autotune', 'max-autotune-no-cudagraphs']}), "cuda_compile_fullgraph": OptionInfo(False, "Model compile fullgraph"), "cuda_compile_precompile": OptionInfo(False, "Model compile precompile"), @@ -311,8 +311,8 @@ def default(obj): "cuda_compile_errors": OptionInfo(True, "Model compile suppress errors"), "ipex_sep": OptionInfo("

IPEX, DirectML and OpenVINO

", "", gr.HTML), - "ipex_optimize": OptionInfo(True if devices.backend == "ipex" else False, "Enable IPEX Optimize for Intel GPUs"), - "ipex_optimize_upscaler": OptionInfo(True if devices.backend == "ipex" else False, "Enable IPEX Optimize for Intel GPUs with Upscalers"), + "ipex_optimize": OptionInfo(False if not devices.backend == "ipex" else True, "Enable IPEX Optimize for Intel GPUs"), + "ipex_optimize_upscaler": OptionInfo(False if not devices.backend == "ipex" else True, "Enable IPEX Optimize for Intel GPUs with Upscalers"), "directml_memory_provider": OptionInfo(default_memory_provider, 'DirectML memory stats provider', gr.Radio, {"choices": memory_providers}), "directml_catch_nan": OptionInfo(False, "DirectML retry specific operation when NaN is produced if possible. (makes generation slower)"), "openvino_disable_model_caching": OptionInfo(False, "OpenVINO disable model caching"), @@ -357,7 +357,7 @@ def default(obj): "diffusers_seq_cpu_offload": OptionInfo(False, "Enable sequential CPU offload (--lowvram)"), "diffusers_vae_upcast": OptionInfo("default", "VAE upcasting", gr.Radio, {"choices": ['default', 'true', 'false']}), "diffusers_vae_slicing": OptionInfo(True, "Enable VAE slicing"), - "diffusers_vae_tiling": OptionInfo(False if cmd_opts.use_openvino else True, "Enable VAE tiling"), + "diffusers_vae_tiling": OptionInfo(True if not cmd_opts.use_openvino else False, "Enable VAE tiling"), "diffusers_attention_slicing": OptionInfo(False, "Enable attention slicing"), "diffusers_model_load_variant": OptionInfo("default", "Diffusers model loading variant", gr.Radio, {"choices": ['default', 'fp32', 'fp16']}), "diffusers_vae_load_variant": OptionInfo("default", "Diffusers VAE loading variant", gr.Radio, {"choices": ['default', 'fp32', 'fp16']}), @@ -608,7 +608,7 @@ def default(obj): "lora_preferred_name": OptionInfo("filename", "LoRA preffered name", gr.Radio, {"choices": ["filename", "alias"]}), "lora_add_hashes_to_infotext": OptionInfo(True, "LoRA add hash info"), "lora_force_diffusers": OptionInfo(False if not cmd_opts.use_openvino else True, "LoRA use alternative loading method"), - "lora_fuse_diffusers": OptionInfo(False, "LoRA use merge when using alternative method"), + "lora_fuse_diffusers": OptionInfo(False if not cmd_opts.use_openvino else True, "LoRA use merge when using alternative method"), "lora_in_memory_limit": OptionInfo(0, "LoRA memory cache", gr.Slider, {"minimum": 0, "maximum": 24, "step": 1}), "lora_functional": OptionInfo(False, "Use Kohya method for handling multiple LoRA", gr.Checkbox, { "visible": False }), "sd_hypernetwork": OptionInfo("None", "Add hypernetwork to prompt", gr.Dropdown, { "choices": ["None"], "visible": False }), From 87eeedd2769f81fa6c2a9b4ce6887e2ab2219825 Mon Sep 17 00:00:00 2001 From: Vladimir Mandic Date: Fri, 8 Dec 2023 12:03:17 -0500 Subject: [PATCH 026/143] long filename and batch processing --- CHANGELOG.md | 1 + modules/images.py | 10 +++++++--- modules/img2img.py | 18 +++++++++--------- 3 files changed, 17 insertions(+), 12 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 5000743af..afb2062be 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -33,6 +33,7 @@ - disable google fonts check on server startup - fix torchvision/basicsr compatibility - add hdr settings to metadata + - improve handling of long filenames and filenames during batch processing - update built-in log monitor in ui, thanks @midcoastal ## Update for 2023-12-04 diff --git a/modules/images.py b/modules/images.py index 3e297663e..f3b7dbd09 100644 --- a/modules/images.py +++ b/modules/images.py @@ -423,9 +423,11 @@ def sanitize(self, filename): if part in invalid_files: # reserved names [part := part.replace(word, '_') for word in invalid_files] # pylint: disable=expression-not-assigned newparts.append(part) - fn = Path(*newparts) - max_length = max(230, os.statvfs(__file__).f_namemax - 32 if hasattr(os, 'statvfs') else 230) - fn = str(fn)[:max_length-max(4, len(ext))].rstrip(invalid_suffix) + ext + fn = str(Path(*newparts)) + max_length = max(256 - len(ext), os.statvfs(__file__).f_namemax - 32 if hasattr(os, 'statvfs') else 256 - len(ext)) + while len(os.path.abspath(fn)) > max_length: + fn = fn[:-1] + fn += ext debug(f'Filename sanitize: input="{filename}" parts={parts} output="{fn}" ext={ext} max={max_length} len={len(fn)}') return fn @@ -577,6 +579,7 @@ def atomically_save_image(): def save_image(image, path, basename='', seed=None, prompt=None, extension=shared.opts.samples_format, info=None, short_filename=False, no_prompt=False, grid=False, pnginfo_section_name='parameters', p=None, existing_info=None, forced_filename=None, suffix='', save_to_dirs=None): # pylint: disable=unused-argument + import sys if image is None: shared.log.warning('Image is none') return None, None @@ -610,6 +613,7 @@ def save_image(image, path, basename='', seed=None, prompt=None, extension=share if dirname is not None and len(dirname) > 0: os.makedirs(dirname, exist_ok=True) params.filename = namegen.sequence(params.filename, dirname, basename) + params.filename = namegen.sanitize(filename) # callbacks script_callbacks.before_image_saved_callback(params) exifinfo = params.pnginfo.get('UserComment', '') diff --git a/modules/img2img.py b/modules/img2img.py index e0fd159b0..247cdc200 100644 --- a/modules/img2img.py +++ b/modules/img2img.py @@ -79,16 +79,16 @@ def process_batch(p, input_files, input_dir, output_dir, inpaint_mask_dir, args) if proc is None: proc = processing.process_images(p) for n, (image, image_file) in enumerate(itertools.zip_longest(proc.images,batch_image_files)): - basename, ext = os.path.splitext(os.path.basename(image_file)) - ext = ext[1:] + basename = '' + if shared.opts.use_original_name_batch: + forced_filename, ext = os.path.splitext(os.path.basename(image_file)) + else: + forced_filename = None + ext = shared.opts.samples_format if len(proc.images) > 1: - if shared.opts.batch_frame_mode: # SBM Frames are numbered globally. - basename = f'{basename}-{n + i}' - else: # Images are numbered per rept. - basename = f'{basename}-{n}' - if not shared.opts.use_original_name_batch: + basename = f'{n + i}' if shared.opts.batch_frame_mode else f'{n}' + else: basename = '' - ext = shared.opts.samples_format if output_dir == '': output_dir = shared.opts.outdir_img2img_samples if not save_normally: @@ -96,7 +96,7 @@ def process_batch(p, input_files, input_dir, output_dir, inpaint_mask_dir, args) geninfo, items = images.read_info_from_image(image) for k, v in items.items(): image.info[k] = v - images.save_image(image, path=output_dir, basename=basename, seed=None, prompt=None, extension=ext, info=geninfo, short_filename=True, no_prompt=True, grid=False, pnginfo_section_name="extras", existing_info=image.info, forced_filename=None) + images.save_image(image, path=output_dir, basename=basename, seed=None, prompt=None, extension=ext, info=geninfo, short_filename=True, no_prompt=True, grid=False, pnginfo_section_name="extras", existing_info=image.info, forced_filename=forced_filename) shared.log.debug(f'Processed: images={len(batch_image_files)} memory={memory_stats()} batch') From aa4b9b4119c47377d3215b93c41b0cb2c056cec0 Mon Sep 17 00:00:00 2001 From: Vladimir Mandic Date: Fri, 8 Dec 2023 12:21:25 -0500 Subject: [PATCH 027/143] enable civit token --- CHANGELOG.md | 1 + modules/modelloader.py | 10 ++++++---- modules/ui_models.py | 8 +++++--- 3 files changed, 12 insertions(+), 7 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index afb2062be..8b75fd984 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -27,6 +27,7 @@ use if you have multiple complex loras that may be causing performance degradation as it fuses lora with model during load instead of interpreting lora on-the-fly - **General** + - **CivitAI downloader** allow usage of access tokens for gated or private models - **Extra networks** new *settting -> extra networks -> build info on first access* indexes all networks on first access instead of server startup - **Ipex** improvements, thanks @disty0 diff --git a/modules/modelloader.py b/modules/modelloader.py index 0e8254024..c1dc6bfa1 100644 --- a/modules/modelloader.py +++ b/modules/modelloader.py @@ -111,7 +111,7 @@ def download_civit_preview(model_path: str, preview_url: str): download_pbar = None -def download_civit_model_thread(model_name, model_url, model_path, model_type, preview): +def download_civit_model_thread(model_name, model_url, model_path, model_type, preview, token): import hashlib sha256 = hashlib.sha256() sha256.update(model_name.encode('utf-8')) @@ -135,7 +135,9 @@ def download_civit_model_thread(model_name, model_url, model_path, model_type, p if os.path.isfile(temp_file): starting_pos = os.path.getsize(temp_file) res += f' resume={round(starting_pos/1024/1024)}Mb' - headers = {'Range': f'bytes={starting_pos}-'} + headers['Range'] = f'bytes={starting_pos}-' + if token is not None and len(token) > 0: + headers['Authorization'] = f'Bearer {token}' r = shared.req(model_url, headers=headers, stream=True) total_size = int(r.headers.get('content-length', 0)) @@ -175,9 +177,9 @@ def download_civit_model_thread(model_name, model_url, model_path, model_type, p return res -def download_civit_model(model_url: str, model_name: str, model_path: str, model_type: str, preview): +def download_civit_model(model_url: str, model_name: str, model_path: str, model_type: str, preview, token: str = None): import threading - thread = threading.Thread(target=download_civit_model_thread, args=(model_name, model_url, model_path, model_type, preview)) + thread = threading.Thread(target=download_civit_model_thread, args=(model_name, model_url, model_path, model_type, preview, token)) thread.start() return f'CivitAI download: name={model_name} url={model_url} path={model_path}' diff --git a/modules/ui_models.py b/modules/ui_models.py index ae4baab15..89af09737 100644 --- a/modules/ui_models.py +++ b/modules/ui_models.py @@ -488,12 +488,12 @@ def civit_select3(evt: gr.SelectData, in_data): log.debug(f'CivitAI select: variant={in_data[evt.index[0]]}') return in_data[evt.index[0]][3], in_data[evt.index[0]][0], gr.update(interactive=True) - def civit_download_model(model_url: str, model_name: str, model_path: str, model_type: str, image_url: str): + def civit_download_model(model_url: str, model_name: str, model_path: str, model_type: str, image_url: str, token: str = None): if model_url is None or len(model_url) == 0: return 'No model selected' try: from modules.modelloader import download_civit_model - res = download_civit_model(model_url, model_name, model_path, model_type, image_url) + res = download_civit_model(model_url, model_name, model_path, model_type, image_url, token) except Exception as e: res = f"CivitAI model downloaded error: model={model_url} {e}" log.error(res) @@ -577,6 +577,8 @@ def civit_search_metadata(civit_previews_rehash, title): with gr.Row(): civit_download_model_btn = gr.Button(value="Download", variant='primary') gr.HTML('Select a model, model version and and model variant from the search results to download or enter model URL manually
') + with gr.Row(): + civit_token = gr.Textbox('', label='CivitAI token', placeholder='optional access token for private or gated models') with gr.Row(): civit_name = gr.Textbox('', label='Model name', placeholder='select model from search results', visible=True) civit_selected = gr.Textbox('', label='Model URL', placeholder='select model from search results', visible=True) @@ -619,7 +621,7 @@ def is_visible(component): civit_results1.change(fn=is_visible, inputs=[civit_results1], outputs=[civit_results1]) civit_results2.change(fn=is_visible, inputs=[civit_results2], outputs=[civit_results2]) civit_results3.change(fn=is_visible, inputs=[civit_results3], outputs=[civit_results3]) - civit_download_model_btn.click(fn=civit_download_model, inputs=[civit_selected, civit_name, civit_path, civit_model_type, models_image], outputs=[models_outcome]) + civit_download_model_btn.click(fn=civit_download_model, inputs=[civit_selected, civit_name, civit_path, civit_model_type, models_image, civit_token], outputs=[models_outcome]) civit_previews_btn.click(fn=civit_search_metadata, inputs=[civit_previews_rehash, civit_previews_rehash], outputs=[models_outcome]) with gr.Tab(label="Update"): From f49e66577a3edca6be384d4e115accbede51c95a Mon Sep 17 00:00:00 2001 From: Vladimir Mandic Date: Fri, 8 Dec 2023 12:27:00 -0500 Subject: [PATCH 028/143] lint fixes --- CHANGELOG.md | 13 ++++++------- modules/images.py | 3 +-- modules/shared.py | 2 +- 3 files changed, 8 insertions(+), 10 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 8b75fd984..8d92b4930 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,6 @@ # Change Log for SD.Next -## Update for 2023-12-07 +## Update for 2023-12-08 *Note*: based on `diffusers==0.25.0.dev0` @@ -16,17 +16,16 @@ - **Schedulers** - add timesteps range, changing it will make scheduler to be over-complete or under-complete - add rescale betas with zero SNR option (applicable to Euler and DDIM, allows for higher dynamic range) -- **Process** - - create videos from batch or folder processing - supports GIF, PNG and MP4 with full interpolation, scene change detection, etc. - - **LoRA** +- **General** + - **Process** create videos from batch or folder processing + supports GIF, PNG and MP4 with full interpolation, scene change detection, etc. + - **LoRA** - add support for block weights, thanks @AI-Casanova example `` - - reintroduce alternative loading method in settings: `lora_force_diffusers` + - reintroduce alternative loading method in settings: `lora_force_diffusers` - add support for `lora_fuse_diffusers` if using alternative method use if you have multiple complex loras that may be causing performance degradation as it fuses lora with model during load instead of interpreting lora on-the-fly -- **General** - **CivitAI downloader** allow usage of access tokens for gated or private models - **Extra networks** new *settting -> extra networks -> build info on first access* indexes all networks on first access instead of server startup diff --git a/modules/images.py b/modules/images.py index f3b7dbd09..84b861626 100644 --- a/modules/images.py +++ b/modules/images.py @@ -579,7 +579,6 @@ def atomically_save_image(): def save_image(image, path, basename='', seed=None, prompt=None, extension=shared.opts.samples_format, info=None, short_filename=False, no_prompt=False, grid=False, pnginfo_section_name='parameters', p=None, existing_info=None, forced_filename=None, suffix='', save_to_dirs=None): # pylint: disable=unused-argument - import sys if image is None: shared.log.warning('Image is none') return None, None @@ -683,7 +682,7 @@ def save_video(p, images, filename = None, video_type: str = 'none', duration: f filename = os.path.join(shared.opts.outdir_video, filename) filename = namegen.sequence(filename, shared.opts.outdir_video, '') else: - if not os.pathsep in filename: + if os.pathsep not in filename: filename = os.path.join(shared.opts.outdir_video, filename) if not filename.lower().endswith(video_type.lower()): filename += f'.{video_type.lower()}' diff --git a/modules/shared.py b/modules/shared.py index 1d6ee1586..4fc1bb856 100644 --- a/modules/shared.py +++ b/modules/shared.py @@ -280,7 +280,7 @@ def default(obj): "cuda_dtype": OptionInfo("FP32" if sys.platform == "darwin" or cmd_opts.use_openvino else "BF16" if devices.backend == "ipex" else "FP16", "Device precision type", gr.Radio, {"choices": ["FP32", "FP16", "BF16"]}), "no_half": OptionInfo(False if not cmd_opts.use_openvino else True, "Use full precision for model (--no-half)", None, None, None), "no_half_vae": OptionInfo(False if not cmd_opts.use_openvino else True, "Use full precision for VAE (--no-half-vae)"), - "upcast_sampling": OptionInfo(False if not sys.platform == "darwin" else True, "Enable upcast sampling"), + "upcast_sampling": OptionInfo(False if sys.platform != "darwin" else True, "Enable upcast sampling"), "upcast_attn": OptionInfo(False, "Enable upcast cross attention layer"), "cuda_cast_unet": OptionInfo(False, "Use fixed UNet precision"), "disable_nan_check": OptionInfo(True, "Disable NaN check in produced images/latent spaces", gr.Checkbox, {"visible": False}), From b1e3a8fa1575f4931feafa77dffb96886722eef9 Mon Sep 17 00:00:00 2001 From: Vladimir Mandic Date: Fri, 8 Dec 2023 13:27:56 -0500 Subject: [PATCH 029/143] fix filenamegen --- modules/images.py | 14 ++++++++++---- 1 file changed, 10 insertions(+), 4 deletions(-) diff --git a/modules/images.py b/modules/images.py index 84b861626..44cd731a8 100644 --- a/modules/images.py +++ b/modules/images.py @@ -2,6 +2,7 @@ import io import re import os +import sys import math import json import uuid @@ -314,7 +315,7 @@ class FilenameGenerator: 'prompt_hash': lambda self: hashlib.sha256(self.prompt.encode()).hexdigest()[0:8], 'sampler': lambda self: self.p and self.p.sampler_name, - 'seed': lambda self: str(self.seed) if self.seed is not None else '', + 'seed': lambda self: self.seed and str(self.seed) or '', 'steps': lambda self: self.p and self.p.steps, 'styles': lambda self: self.p and ", ".join([style for style in self.p.styles if not style == "None"]) or "None", 'uuid': lambda self: str(uuid.uuid4()), @@ -322,10 +323,13 @@ class FilenameGenerator: default_time_format = '%Y%m%d%H%M%S' def __init__(self, p, seed, prompt, image, grid=False): - if getattr(self, 'p', None) is None: + if p is None: + debug('Filename generator init skip') return + else: + debug(f'Filename generator init: {seed} {prompt}') self.p = p - self.seed = seed + self.seed = seed if seed is not None and seed > 0 else p.all_seeds[0] self.prompt = prompt self.image = image if not grid: @@ -462,6 +466,7 @@ def apply(self, x): fun = self.replacements.get(pattern.lower(), None) if fun is not None: try: + debug(f'Filename apply: pattern={pattern.lower()} args={pattern_args}') replacement = fun(self, *pattern_args) except Exception as e: replacement = None @@ -579,6 +584,7 @@ def atomically_save_image(): def save_image(image, path, basename='', seed=None, prompt=None, extension=shared.opts.samples_format, info=None, short_filename=False, no_prompt=False, grid=False, pnginfo_section_name='parameters', p=None, existing_info=None, forced_filename=None, suffix='', save_to_dirs=None): # pylint: disable=unused-argument + debug(f'Save from function={sys._getframe(1).f_code.co_name}') # pylint: disable=protected-access if image is None: shared.log.warning('Image is none') return None, None @@ -612,7 +618,7 @@ def save_image(image, path, basename='', seed=None, prompt=None, extension=share if dirname is not None and len(dirname) > 0: os.makedirs(dirname, exist_ok=True) params.filename = namegen.sequence(params.filename, dirname, basename) - params.filename = namegen.sanitize(filename) + params.filename = namegen.sanitize(params.filename) # callbacks script_callbacks.before_image_saved_callback(params) exifinfo = params.pnginfo.get('UserComment', '') From b96131fad27ee8243e240887ec05e837021f2ced Mon Sep 17 00:00:00 2001 From: Vladimir Mandic Date: Sat, 9 Dec 2023 09:36:33 -0500 Subject: [PATCH 030/143] fix en sort --- CHANGELOG.md | 1 + modules/ui_extra_networks_styles.py | 4 ++-- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 8d92b4930..e92c46516 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -32,6 +32,7 @@ - **Ipex** improvements, thanks @disty0 - disable google fonts check on server startup - fix torchvision/basicsr compatibility + - fix extra networks sort - add hdr settings to metadata - improve handling of long filenames and filenames during batch processing - update built-in log monitor in ui, thanks @midcoastal diff --git a/modules/ui_extra_networks_styles.py b/modules/ui_extra_networks_styles.py index 8dcccf96e..9f46850e9 100644 --- a/modules/ui_extra_networks_styles.py +++ b/modules/ui_extra_networks_styles.py @@ -53,7 +53,7 @@ def create_style(self, params): "name": name, "title": name, "filename": fn, - "search_term": f'{self.search_terms_from_path(name)}', + "search_term": f'{self.search_terms_from_path(fn)} {params.get("Prompt", "")}', "preview": self.find_preview(name), "description": '', "prompt": params.get('Prompt', ''), @@ -79,7 +79,7 @@ def create_item(self, k): "name": name, "title": k, "filename": style.filename, - "search_term": f'{txt} {self.search_terms_from_path(name)}', + "search_term": f'{self.search_terms_from_path(name)} {txt}', "preview": style.preview if getattr(style, 'preview', None) is not None and style.preview.startswith('data:') else self.find_preview(fn), "description": style.description if getattr(style, 'description', None) is not None and len(style.description) > 0 else txt, "prompt": getattr(style, 'prompt', ''), From 6cd178541b527021371759a33d8632f319a4c762 Mon Sep 17 00:00:00 2001 From: Vladimir Mandic Date: Sat, 9 Dec 2023 10:00:29 -0500 Subject: [PATCH 031/143] fix hdr --- modules/processing_correction.py | 17 ++++++++++------- 1 file changed, 10 insertions(+), 7 deletions(-) diff --git a/modules/processing_correction.py b/modules/processing_correction.py index d0f189dcc..a421da052 100644 --- a/modules/processing_correction.py +++ b/modules/processing_correction.py @@ -15,7 +15,7 @@ def soft_clamp_tensor(tensor, threshold=0.8, boundary=4): # shrinking towards the mean; will also remove outliers if max(abs(tensor.max()), abs(tensor.min())) < boundary or threshold == 0: return tensor - channel_dim = 1 + channel_dim = 0 threshold *= boundary max_vals = tensor.max(channel_dim, keepdim=True)[0] max_replace = ((tensor - threshold) / (max_vals - threshold)) * (boundary - threshold) + threshold @@ -23,8 +23,8 @@ def soft_clamp_tensor(tensor, threshold=0.8, boundary=4): min_vals = tensor.min(channel_dim, keepdim=True)[0] min_replace = ((tensor + threshold) / (min_vals + threshold)) * (-boundary + threshold) - threshold under_mask = tensor < -threshold - debug(f'HDE soft clamp: threshold={threshold} boundary={boundary}') tensor = torch.where(over_mask, max_replace, torch.where(under_mask, min_replace, tensor)) + debug(f'HDR soft clamp: threshold={threshold} boundary={boundary} shape={tensor.shape}') return tensor @@ -34,21 +34,23 @@ def center_tensor(tensor, channel_shift=1.0, full_shift=1.0, channels=[0, 1, 2, means = [] for channel in channels: means.append(tensor[0, channel].mean()) - tensor[0, channel] -= means[-1] * channel_shift - debug(f'HDR center: channel-shift{channel_shift} full-shift={full_shift} means={torch.stack(means)}') + # tensor[0, channel] -= means[-1] * channel_shift + tensor[channel] -= means[-1] * channel_shift tensor = tensor - tensor.mean() * full_shift + debug(f'HDR center: channel-shift={channel_shift} full-shift={full_shift} means={torch.stack(means)} shape={tensor.shape}') return tensor -def maximize_tensor(tensor, boundary=1.0, channels=[0, 1, 2]): # pylint: disable=dangerous-default-value # noqa: B006 +def maximize_tensor(tensor, boundary=1.0, _channels=[0, 1, 2]): # pylint: disable=dangerous-default-value # noqa: B006 if boundary == 1.0: return tensor boundary *= 4 min_val = tensor.min() max_val = tensor.max() normalization_factor = boundary / max(abs(min_val), abs(max_val)) - tensor[0, channels] *= normalization_factor - debug(f'HDR maximize: boundary={boundary} min={min_val} max={max_val} factor={normalization_factor}') + # tensor[0, channels] *= normalization_factor + tensor *= normalization_factor + debug(f'HDR maximize: boundary={boundary} min={min_val} max={max_val} factor={normalization_factor} shape={tensor.shape}') return tensor @@ -70,6 +72,7 @@ def correction_callback(p, timestep, kwargs): if not p.hdr_clamp and not p.hdr_center and not p.hdr_maximize: return kwargs latents = kwargs["latents"] + # debug(f'HDR correction: latents={latents.shape}') if len(latents.shape) == 4: # standard batched latent for i in range(latents.shape[0]): latents[i] = correction(p, timestep, latents[i]) From f91735deac7669fefdbfad8cf1c29e66d8e10fd4 Mon Sep 17 00:00:00 2001 From: Vladimir Mandic Date: Sat, 9 Dec 2023 10:14:35 -0500 Subject: [PATCH 032/143] fix prompt parser with blank lines --- modules/prompt_parser_diffusers.py | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/modules/prompt_parser_diffusers.py b/modules/prompt_parser_diffusers.py index abd06b778..938be2447 100644 --- a/modules/prompt_parser_diffusers.py +++ b/modules/prompt_parser_diffusers.py @@ -163,8 +163,10 @@ def get_weighted_text_embeddings(pipe, prompt: str = "", neg_prompt: str = "", c provider_embed = [] while 'BREAK' in text: pos = text.index('BREAK') - embed, ptokens = embedding_providers[i].get_embeddings_for_weighted_prompt_fragments(text_batch=[text[:pos]], fragment_weights_batch=[weights[:pos]], device=device, should_return_tokens=True) - provider_embed.append(embed) + debug(f'Prompt: section="{text[:pos]}" len={len(text[:pos])} weights={weights[:pos]}') + if len(text[:pos]) > 0: + embed, ptokens = embedding_providers[i].get_embeddings_for_weighted_prompt_fragments(text_batch=[text[:pos]], fragment_weights_batch=[weights[:pos]], device=device, should_return_tokens=True) + provider_embed.append(embed) text = text[pos+1:] weights = weights[pos+1:] prompt_embeds.append(torch.cat(provider_embed, dim=1)) @@ -192,6 +194,7 @@ def get_weighted_text_embeddings(pipe, prompt: str = "", neg_prompt: str = "", c prompt_embeds = torch.cat(prompt_embeds, dim=-1) if len(prompt_embeds) > 1 else prompt_embeds[0] negative_prompt_embeds = torch.cat(negative_prompt_embeds, dim=-1) if len(negative_prompt_embeds) > 1 else negative_prompt_embeds[0] + debug(f'Prompt: shape={prompt_embeds.shape} negative={negative_prompt_embeds.shape}') if prompt_embeds.shape[1] != negative_prompt_embeds.shape[1]: [prompt_embeds, negative_prompt_embeds] = pad_to_same_length(pipe, [prompt_embeds, negative_prompt_embeds]) return prompt_embeds, pooled_prompt_embeds, negative_prompt_embeds, negative_pooled_prompt_embeds From 1b1ae6784c584ebb326d531cb6f883de45730e12 Mon Sep 17 00:00:00 2001 From: Vladimir Mandic Date: Sat, 9 Dec 2023 14:15:48 -0500 Subject: [PATCH 033/143] add demofusion --- CHANGELOG.md | 11 +- extensions-builtin/Lora/networks.py | 4 +- modules/processing.py | 2 + modules/prompt_parser_diffusers.py | 2 +- scripts/animatediff.py | 3 +- scripts/demofusion.py | 1283 +++++++++++++++++++++++++++ 6 files changed, 1298 insertions(+), 7 deletions(-) create mode 100644 scripts/demofusion.py diff --git a/CHANGELOG.md b/CHANGELOG.md index e92c46516..9029dcfeb 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,6 @@ # Change Log for SD.Next -## Update for 2023-12-08 +## Update for 2023-12-09 *Note*: based on `diffusers==0.25.0.dev0` @@ -11,6 +11,11 @@ - **HDR controls** are now batch-aware for enhancement of multiple images or video frames - [Playground v1](https://huggingface.co/playgroundai/playground-v1), [Playground v2 256](https://huggingface.co/playgroundai/playground-v2-256px-base), [Playground v2 512](https://huggingface.co/playgroundai/playground-v2-512px-base), [Playground v2 1024](https://huggingface.co/playgroundai/playground-v2-1024px-aesthetic) model support - simply select from *networks -> reference* and use as usual + - [DemoFusion](https://github.com/PRIS-CV/DemoFusion) run your SDXL generations at any resolution! + - in **Text** tab select *script* -> *demofusion* + - *note*: GPU VRAM limits do not automatically go away so be careful when using it with large resolutions + in the future, expect more optimizations, especially related to offloading/slicing/tiling, + but at the moment this is pretty much experimental-only - [ModelScope T2V](https://huggingface.co/damo-vilab/text-to-video-ms-1.7b) model support - simply select from *networks -> reference* and use from *txt2img* tab - **Schedulers** @@ -18,7 +23,7 @@ - add rescale betas with zero SNR option (applicable to Euler and DDIM, allows for higher dynamic range) - **General** - **Process** create videos from batch or folder processing - supports GIF, PNG and MP4 with full interpolation, scene change detection, etc. + supports *GIF*, *PNG* and *MP4* with full interpolation, scene change detection, etc. - **LoRA** - add support for block weights, thanks @AI-Casanova example `` @@ -26,7 +31,7 @@ - add support for `lora_fuse_diffusers` if using alternative method use if you have multiple complex loras that may be causing performance degradation as it fuses lora with model during load instead of interpreting lora on-the-fly - - **CivitAI downloader** allow usage of access tokens for gated or private models + - **CivitAI downloader** allow usage of access tokens for download of gated or private models - **Extra networks** new *settting -> extra networks -> build info on first access* indexes all networks on first access instead of server startup - **Ipex** improvements, thanks @disty0 diff --git a/extensions-builtin/Lora/networks.py b/extensions-builtin/Lora/networks.py index e6e69d36b..cc0c34bc3 100644 --- a/extensions-builtin/Lora/networks.py +++ b/extensions-builtin/Lora/networks.py @@ -78,7 +78,7 @@ def load_diffusers(name, network_on_disk, lora_scale=1.0): t0 = time.time() cached = lora_cache.get(name, None) # if debug: - shared.log.debug(f'LoRA load: name={name} file={network_on_disk.filename} type=diffusers {"cached" if cached else ""} fuse={shared.opts.lora_fuse_diffusers}') + shared.log.debug(f'LoRA load: name="{name}" file="{network_on_disk.filename}" type=diffusers {"cached" if cached else ""} fuse={shared.opts.lora_fuse_diffusers}') if cached is not None: return cached if shared.backend != shared.Backend.DIFFUSERS: @@ -98,7 +98,7 @@ def load_network(name, network_on_disk): t0 = time.time() cached = lora_cache.get(name, None) if debug: - shared.log.debug(f'LoRA load: name={name} file={network_on_disk.filename} {"cached" if cached else ""}') + shared.log.debug(f'LoRA load: name="{name}" file="{network_on_disk.filename}" {"cached" if cached else ""}') if cached is not None: return cached net = network.Network(name, network_on_disk) diff --git a/modules/processing.py b/modules/processing.py index d0cbb7a26..cf3673482 100644 --- a/modules/processing.py +++ b/modules/processing.py @@ -757,6 +757,8 @@ def validate_sample(tensor): sample = 255.0 * np.moveaxis(tensor.cpu().numpy(), 0, 2) else: sample = 255.0 * tensor + if isinstance(tensor, torch.Tensor): + sample = sample.detach().cpu().numpy() with warnings.catch_warnings(record=True) as w: cast = sample.astype(np.uint8) if len(w) > 0: diff --git a/modules/prompt_parser_diffusers.py b/modules/prompt_parser_diffusers.py index 938be2447..f69b7e71e 100644 --- a/modules/prompt_parser_diffusers.py +++ b/modules/prompt_parser_diffusers.py @@ -60,7 +60,7 @@ def expand_textual_inversion_token_ids_if_necessary(self, token_ids: typing.List def encode_prompts(pipe, prompts: list, negative_prompts: list, clip_skip: typing.Optional[int] = None): - if 'StableDiffusion' not in pipe.__class__.__name__: + if 'StableDiffusion' not in pipe.__class__.__name__ and 'DemoFusion': shared.log.warning(f"Prompt parser not supported: {pipe.__class__.__name__}") return None, None, None, None else: diff --git a/scripts/animatediff.py b/scripts/animatediff.py index 00100b3b8..650f43d6b 100644 --- a/scripts/animatediff.py +++ b/scripts/animatediff.py @@ -94,7 +94,8 @@ def set_adapter(adapter_name: str = 'None'): new_pipe.is_sd2 = False new_pipe.is_sd1 = True shared.sd_model = new_pipe - shared.sd_model.to(shared.device) + if not ((shared.opts.diffusers_model_cpu_offload or shared.cmd_opts.medvram) or (shared.opts.diffusers_seq_cpu_offload or shared.cmd_opts.lowvram)): + shared.sd_model.to(shared.device) sd_models.set_diffuser_options(shared.sd_model, vae=None, op='model') shared.log.debug(f'AnimateDiff create pipeline: adapter="{loaded_adapter}"') except Exception as e: diff --git a/scripts/demofusion.py b/scripts/demofusion.py new file mode 100644 index 000000000..0d655b413 --- /dev/null +++ b/scripts/demofusion.py @@ -0,0 +1,1283 @@ +import random +import inspect +from typing import Any, Callable, Dict, List, Optional, Tuple, Union +import torch +import torch.nn.functional as F +import gradio as gr +from transformers import CLIPTextModel, CLIPTextModelWithProjection, CLIPTokenizer +from diffusers.image_processor import VaeImageProcessor +from diffusers.loaders import FromSingleFileMixin, LoraLoaderMixin, TextualInversionLoaderMixin +from diffusers.models import AutoencoderKL, UNet2DConditionModel +from diffusers.models.attention_processor import AttnProcessor2_0, LoRAAttnProcessor2_0, LoRAXFormersAttnProcessor, XFormersAttnProcessor +from diffusers.models.lora import adjust_lora_scale_text_encoder +from diffusers.schedulers import KarrasDiffusionSchedulers +from diffusers.utils import is_accelerate_available, is_accelerate_version +from diffusers.utils.torch_utils import randn_tensor +from diffusers.pipelines.pipeline_utils import DiffusionPipeline, ImagePipelineOutput +from modules import scripts, processing, shared, sd_models + + +### Class definition +""" +Credits: https://github.com/PRIS-CV/DemoFusion +Source: https://github.com/PRIS-CV/DemoFusion/blob/main/pipeline_demofusion_sdxl.py +""" + + +def gaussian_kernel(kernel_size=3, sigma=1.0, channels=3): + x_coord = torch.arange(kernel_size) + gaussian_1d = torch.exp(-(x_coord - (kernel_size - 1) / 2) ** 2 / (2 * sigma ** 2)) + gaussian_1d = gaussian_1d / gaussian_1d.sum() + gaussian_2d = gaussian_1d[:, None] * gaussian_1d[None, :] + kernel = gaussian_2d[None, None, :, :].repeat(channels, 1, 1, 1) + return kernel + + +def gaussian_filter(latents, kernel_size=3, sigma=1.0): + channels = latents.shape[1] + kernel = gaussian_kernel(kernel_size, sigma, channels).to(latents.device, latents.dtype) + blurred_latents = F.conv2d(latents, kernel, padding=kernel_size//2, groups=channels) + return blurred_latents + + +# Copied from diffusers.pipelines.stable_diffusion.pipeline_stable_diffusion.rescale_noise_cfg +def rescale_noise_cfg(noise_cfg, noise_pred_text, guidance_rescale=0.0): + """ + Rescale `noise_cfg` according to `guidance_rescale`. Based on findings of [Common Diffusion Noise Schedules and + Sample Steps are Flawed](https://arxiv.org/pdf/2305.08891.pdf). See Section 3.4 + """ + std_text = noise_pred_text.std(dim=list(range(1, noise_pred_text.ndim)), keepdim=True) + std_cfg = noise_cfg.std(dim=list(range(1, noise_cfg.ndim)), keepdim=True) + # rescale the results from guidance (fixes overexposure) + noise_pred_rescaled = noise_cfg * (std_text / std_cfg) + # mix with the original results from guidance by factor guidance_rescale to avoid "plain looking" images + noise_cfg = guidance_rescale * noise_pred_rescaled + (1 - guidance_rescale) * noise_cfg + return noise_cfg + + +class DemoFusionSDXLPipeline(DiffusionPipeline, FromSingleFileMixin, LoraLoaderMixin, TextualInversionLoaderMixin): + model_cpu_offload_seq = "text_encoder->text_encoder_2->unet->vae" + + def __init__( + self, + vae: AutoencoderKL, + text_encoder: CLIPTextModel, + text_encoder_2: CLIPTextModelWithProjection, + tokenizer: CLIPTokenizer, + tokenizer_2: CLIPTokenizer, + unet: UNet2DConditionModel, + scheduler: KarrasDiffusionSchedulers, + force_zeros_for_empty_prompt: bool = True, + ): + super().__init__() + + self.register_modules( + vae=vae, + text_encoder=text_encoder, + text_encoder_2=text_encoder_2, + tokenizer=tokenizer, + tokenizer_2=tokenizer_2, + unet=unet, + scheduler=scheduler, + ) + self.register_to_config(force_zeros_for_empty_prompt=force_zeros_for_empty_prompt) + self.vae_scale_factor = 2 ** (len(self.vae.config.block_out_channels) - 1) + self.image_processor = VaeImageProcessor(vae_scale_factor=self.vae_scale_factor) + self.default_sample_size = self.unet.config.sample_size + self.watermark = None + + # Copied from diffusers.pipelines.stable_diffusion.pipeline_stable_diffusion.StableDiffusionPipeline.enable_vae_slicing + def enable_vae_slicing(self): + self.vae.enable_slicing() + + # Copied from diffusers.pipelines.stable_diffusion.pipeline_stable_diffusion.StableDiffusionPipeline.disable_vae_slicing + def disable_vae_slicing(self): + self.vae.disable_slicing() + + # Copied from diffusers.pipelines.stable_diffusion.pipeline_stable_diffusion.StableDiffusionPipeline.enable_vae_tiling + def enable_vae_tiling(self): + self.vae.enable_tiling() + + # Copied from diffusers.pipelines.stable_diffusion.pipeline_stable_diffusion.StableDiffusionPipeline.disable_vae_tiling + def disable_vae_tiling(self): + self.vae.disable_tiling() + + def encode_prompt( + self, + prompt: str, + prompt_2: Optional[str] = None, + device: Optional[torch.device] = None, + num_images_per_prompt: int = 1, + do_classifier_free_guidance: bool = True, + negative_prompt: Optional[str] = None, + negative_prompt_2: Optional[str] = None, + prompt_embeds: Optional[torch.FloatTensor] = None, + negative_prompt_embeds: Optional[torch.FloatTensor] = None, + pooled_prompt_embeds: Optional[torch.FloatTensor] = None, + negative_pooled_prompt_embeds: Optional[torch.FloatTensor] = None, + lora_scale: Optional[float] = None, + ): + device = device or self._execution_device + + # set lora scale so that monkey patched LoRA + # function of text encoder can correctly access it + if lora_scale is not None and isinstance(self, LoraLoaderMixin): + self._lora_scale = lora_scale # pylint: disable=attribute-defined-outside-init + # dynamically adjust the LoRA scale + adjust_lora_scale_text_encoder(self.text_encoder, lora_scale) + adjust_lora_scale_text_encoder(self.text_encoder_2, lora_scale) + + if prompt is not None and isinstance(prompt, str): + batch_size = 1 + elif prompt is not None and isinstance(prompt, list): + batch_size = len(prompt) + else: + batch_size = prompt_embeds.shape[0] + + # Define tokenizers and text encoders + tokenizers = [self.tokenizer, self.tokenizer_2] if self.tokenizer is not None else [self.tokenizer_2] + text_encoders = ( + [self.text_encoder, self.text_encoder_2] if self.text_encoder is not None else [self.text_encoder_2] + ) + + if prompt_embeds is None: + prompt_2 = prompt_2 or prompt + # textual inversion: procecss multi-vector tokens if necessary + prompt_embeds_list = [] + prompts = [prompt, prompt_2] + for prompt, tokenizer, text_encoder in zip(prompts, tokenizers, text_encoders): + if isinstance(self, TextualInversionLoaderMixin): + prompt = self.maybe_convert_prompt(prompt, tokenizer) + + text_inputs = tokenizer( + prompt, + padding="max_length", + max_length=tokenizer.model_max_length, + truncation=True, + return_tensors="pt", + ) + + text_input_ids = text_inputs.input_ids + untruncated_ids = tokenizer(prompt, padding="longest", return_tensors="pt").input_ids + + if untruncated_ids.shape[-1] >= text_input_ids.shape[-1] and not torch.equal( + text_input_ids, untruncated_ids + ): + removed_text = tokenizer.batch_decode(untruncated_ids[:, tokenizer.model_max_length - 1 : -1]) + shared.log.warning(f"The following part of your input was truncated because CLIP can only handle sequences up to {tokenizer.model_max_length} tokens: {removed_text}") + + prompt_embeds = text_encoder( + text_input_ids.to(device), + output_hidden_states=True, + ) + + # We are only ALWAYS interested in the pooled output of the final text encoder + pooled_prompt_embeds = prompt_embeds[0] + prompt_embeds = prompt_embeds.hidden_states[-2] + + prompt_embeds_list.append(prompt_embeds) + + prompt_embeds = torch.concat(prompt_embeds_list, dim=-1) + + # get unconditional embeddings for classifier free guidance + zero_out_negative_prompt = negative_prompt is None and self.config.force_zeros_for_empty_prompt # pylint: disable=no-member + if do_classifier_free_guidance and negative_prompt_embeds is None and zero_out_negative_prompt: + negative_prompt_embeds = torch.zeros_like(prompt_embeds) + negative_pooled_prompt_embeds = torch.zeros_like(pooled_prompt_embeds) + elif do_classifier_free_guidance and negative_prompt_embeds is None: + negative_prompt = negative_prompt or "" + negative_prompt_2 = negative_prompt_2 or negative_prompt + + uncond_tokens: List[str] + if prompt is not None and type(prompt) is not type(negative_prompt): + raise TypeError( + f"`negative_prompt` should be the same type to `prompt`, but got {type(negative_prompt)} !=" + f" {type(prompt)}." + ) + elif isinstance(negative_prompt, str): + uncond_tokens = [negative_prompt, negative_prompt_2] + elif batch_size != len(negative_prompt): + raise ValueError( + f"`negative_prompt`: {negative_prompt} has batch size {len(negative_prompt)}, but `prompt`:" + f" {prompt} has batch size {batch_size}. Please make sure that passed `negative_prompt` matches" + " the batch size of `prompt`." + ) + else: + uncond_tokens = [negative_prompt, negative_prompt_2] + + negative_prompt_embeds_list = [] + for negative_prompt, tokenizer, text_encoder in zip(uncond_tokens, tokenizers, text_encoders): + if isinstance(self, TextualInversionLoaderMixin): + negative_prompt = self.maybe_convert_prompt(negative_prompt, tokenizer) + + max_length = prompt_embeds.shape[1] + uncond_input = tokenizer( + negative_prompt, + padding="max_length", + max_length=max_length, + truncation=True, + return_tensors="pt", + ) + + negative_prompt_embeds = text_encoder( + uncond_input.input_ids.to(device), + output_hidden_states=True, + ) + # We are only ALWAYS interested in the pooled output of the final text encoder + negative_pooled_prompt_embeds = negative_prompt_embeds[0] + negative_prompt_embeds = negative_prompt_embeds.hidden_states[-2] + negative_prompt_embeds_list.append(negative_prompt_embeds) + negative_prompt_embeds = torch.concat(negative_prompt_embeds_list, dim=-1) + + prompt_embeds = prompt_embeds.to(dtype=self.text_encoder_2.dtype, device=device) + bs_embed, seq_len, _ = prompt_embeds.shape + # duplicate text embeddings for each generation per prompt, using mps friendly method + prompt_embeds = prompt_embeds.repeat(1, num_images_per_prompt, 1) + prompt_embeds = prompt_embeds.view(bs_embed * num_images_per_prompt, seq_len, -1) + + if do_classifier_free_guidance: + # duplicate unconditional embeddings for each generation per prompt, using mps friendly method + seq_len = negative_prompt_embeds.shape[1] + negative_prompt_embeds = negative_prompt_embeds.to(dtype=self.text_encoder_2.dtype, device=device) + negative_prompt_embeds = negative_prompt_embeds.repeat(1, num_images_per_prompt, 1) + negative_prompt_embeds = negative_prompt_embeds.view(batch_size * num_images_per_prompt, seq_len, -1) + + pooled_prompt_embeds = pooled_prompt_embeds.repeat(1, num_images_per_prompt).view( + bs_embed * num_images_per_prompt, -1 + ) + if do_classifier_free_guidance: + negative_pooled_prompt_embeds = negative_pooled_prompt_embeds.repeat(1, num_images_per_prompt).view( + bs_embed * num_images_per_prompt, -1 + ) + + return prompt_embeds, negative_prompt_embeds, pooled_prompt_embeds, negative_pooled_prompt_embeds + + # Copied from diffusers.pipelines.stable_diffusion.pipeline_stable_diffusion.StableDiffusionPipeline.prepare_extra_step_kwargs + def prepare_extra_step_kwargs(self, generator, eta): + # prepare extra kwargs for the scheduler step, since not all schedulers have the same signature + # eta (η) is only used with the DDIMScheduler, it will be ignored for other schedulers. + # eta corresponds to η in DDIM paper: https://arxiv.org/abs/2010.02502 + # and should be between [0, 1] + + accepts_eta = "eta" in set(inspect.signature(self.scheduler.step).parameters.keys()) + extra_step_kwargs = {} + if accepts_eta: + extra_step_kwargs["eta"] = eta + + # check if the scheduler accepts generator + accepts_generator = "generator" in set(inspect.signature(self.scheduler.step).parameters.keys()) + if accepts_generator: + extra_step_kwargs["generator"] = generator + return extra_step_kwargs + + def check_inputs( + self, + prompt, + prompt_2, + height, + width, + callback_steps, + negative_prompt=None, + negative_prompt_2=None, + prompt_embeds=None, + negative_prompt_embeds=None, + pooled_prompt_embeds=None, + negative_pooled_prompt_embeds=None, + num_images_per_prompt=None, + ): + if height % 8 != 0 or width % 8 != 0: + raise ValueError(f"`height` and `width` have to be divisible by 8 but are {height} and {width}.") + + if (callback_steps is None) or ( + callback_steps is not None and (not isinstance(callback_steps, int) or callback_steps <= 0) + ): + raise ValueError( + f"`callback_steps` has to be a positive integer but is {callback_steps} of type" + f" {type(callback_steps)}." + ) + + if prompt is not None and prompt_embeds is not None: + raise ValueError( + f"Cannot forward both `prompt`: {prompt} and `prompt_embeds`: {prompt_embeds}. Please make sure to" + " only forward one of the two." + ) + elif prompt_2 is not None and prompt_embeds is not None: + raise ValueError( + f"Cannot forward both `prompt_2`: {prompt_2} and `prompt_embeds`: {prompt_embeds}. Please make sure to" + " only forward one of the two." + ) + elif prompt is None and prompt_embeds is None: + raise ValueError( + "Provide either `prompt` or `prompt_embeds`. Cannot leave both `prompt` and `prompt_embeds` undefined." + ) + elif prompt is not None and (not isinstance(prompt, str) and not isinstance(prompt, list)): + raise ValueError(f"`prompt` has to be of type `str` or `list` but is {type(prompt)}") + elif prompt_2 is not None and (not isinstance(prompt_2, str) and not isinstance(prompt_2, list)): + raise ValueError(f"`prompt_2` has to be of type `str` or `list` but is {type(prompt_2)}") + + if negative_prompt is not None and negative_prompt_embeds is not None: + raise ValueError( + f"Cannot forward both `negative_prompt`: {negative_prompt} and `negative_prompt_embeds`:" + f" {negative_prompt_embeds}. Please make sure to only forward one of the two." + ) + elif negative_prompt_2 is not None and negative_prompt_embeds is not None: + raise ValueError( + f"Cannot forward both `negative_prompt_2`: {negative_prompt_2} and `negative_prompt_embeds`:" + f" {negative_prompt_embeds}. Please make sure to only forward one of the two." + ) + + if prompt_embeds is not None and negative_prompt_embeds is not None: + if prompt_embeds.shape != negative_prompt_embeds.shape: + raise ValueError( + "`prompt_embeds` and `negative_prompt_embeds` must have the same shape when passed directly, but" + f" got: `prompt_embeds` {prompt_embeds.shape} != `negative_prompt_embeds`" + f" {negative_prompt_embeds.shape}." + ) + + if prompt_embeds is not None and pooled_prompt_embeds is None: + raise ValueError( + "If `prompt_embeds` are provided, `pooled_prompt_embeds` also have to be passed. Make sure to generate `pooled_prompt_embeds` from the same text encoder that was used to generate `prompt_embeds`." + ) + + if negative_prompt_embeds is not None and negative_pooled_prompt_embeds is None: + raise ValueError( + "If `negative_prompt_embeds` are provided, `negative_pooled_prompt_embeds` also have to be passed. Make sure to generate `negative_pooled_prompt_embeds` from the same text encoder that was used to generate `negative_prompt_embeds`." + ) + + # DemoFusion specific checks + if max(height, width) % 1024 != 0: + shared.log.error('DemoFusion: resolution={width}x{height} long side must be divisible by 1024') + return None + + if num_images_per_prompt != 1: + shared.log.warning('DemoFusion: number of images per prompt is not support and will be ignored') + num_images_per_prompt = 1 + + # Copied from diffusers.pipelines.stable_diffusion.pipeline_stable_diffusion.StableDiffusionPipeline.prepare_latents + def prepare_latents(self, batch_size, num_channels_latents, height, width, dtype, device, generator, latents=None): + shape = (batch_size, num_channels_latents, height // self.vae_scale_factor, width // self.vae_scale_factor) + if isinstance(generator, list) and len(generator) != batch_size: + raise ValueError( + f"You have passed a list of generators of length {len(generator)}, but requested an effective batch" + f" size of {batch_size}. Make sure the batch size matches the length of the generators." + ) + + if latents is None: + latents = randn_tensor(shape, generator=generator, device=device, dtype=dtype) + else: + latents = latents.to(device) + + # scale the initial noise by the standard deviation required by the scheduler + latents = latents * self.scheduler.init_noise_sigma + return latents + + def _get_add_time_ids(self, original_size, crops_coords_top_left, target_size, dtype): + add_time_ids = list(original_size + crops_coords_top_left + target_size) + + passed_add_embed_dim = ( + self.unet.config.addition_time_embed_dim * len(add_time_ids) + self.text_encoder_2.config.projection_dim + ) + expected_add_embed_dim = self.unet.add_embedding.linear_1.in_features + + if expected_add_embed_dim != passed_add_embed_dim: + raise ValueError( + f"Model expects an added time embedding vector of length {expected_add_embed_dim}, but a vector of {passed_add_embed_dim} was created. The model has an incorrect config. Please check `unet.config.time_embedding_type` and `text_encoder_2.config.projection_dim`." + ) + + add_time_ids = torch.tensor([add_time_ids], dtype=dtype) + return add_time_ids + + def get_views(self, height, width, window_size=128, stride=64, random_jitter=False): + # Here, we define the mappings F_i (see Eq. 7 in the MultiDiffusion paper https://arxiv.org/abs/2302.08113) + # if panorama's height/width < window_size, num_blocks of height/width should return 1 + height //= self.vae_scale_factor + width //= self.vae_scale_factor + num_blocks_height = int((height - window_size) / stride - 1e-6) + 2 if height > window_size else 1 + num_blocks_width = int((width - window_size) / stride - 1e-6) + 2 if width > window_size else 1 + total_num_blocks = int(num_blocks_height * num_blocks_width) + views = [] + for i in range(total_num_blocks): + h_start = int((i // num_blocks_width) * stride) + h_end = h_start + window_size + w_start = int((i % num_blocks_width) * stride) + w_end = w_start + window_size + + if h_end > height: + h_start = int(h_start + height - h_end) + h_end = int(height) + if w_end > width: + w_start = int(w_start + width - w_end) + w_end = int(width) + if h_start < 0: + h_end = int(h_end - h_start) + h_start = 0 + if w_start < 0: + w_end = int(w_end - w_start) + w_start = 0 + + if random_jitter: + jitter_range = (window_size - stride) // 4 + w_jitter = 0 + h_jitter = 0 + if (w_start != 0) and (w_end != width): + w_jitter = random.randint(-jitter_range, jitter_range) + elif (w_start == 0) and (w_end != width): + w_jitter = random.randint(-jitter_range, 0) + elif (w_start != 0) and (w_end == width): + w_jitter = random.randint(0, jitter_range) + if (h_start != 0) and (h_end != height): + h_jitter = random.randint(-jitter_range, jitter_range) + elif (h_start == 0) and (h_end != height): + h_jitter = random.randint(-jitter_range, 0) + elif (h_start != 0) and (h_end == height): + h_jitter = random.randint(0, jitter_range) + h_start += (h_jitter + jitter_range) + h_end += (h_jitter + jitter_range) + w_start += (w_jitter + jitter_range) + w_end += (w_jitter + jitter_range) + + views.append((h_start, h_end, w_start, w_end)) + return views + + def tiled_decode(self, latents, current_height, current_width): + core_size = self.unet.config.sample_size // 4 + core_stride = core_size + pad_size = self.unet.config.sample_size // 4 * 3 + decoder_view_batch_size = 1 + + if self.lowvram: + core_stride = core_size // 2 + pad_size = core_size + + views = self.get_views(current_height, current_width, stride=core_stride, window_size=core_size) + views_batch = [views[i : i + decoder_view_batch_size] for i in range(0, len(views), decoder_view_batch_size)] + latents_ = F.pad(latents, (pad_size, pad_size, pad_size, pad_size), 'constant', 0) + image = torch.zeros(latents.size(0), 3, current_height, current_width).to(latents.device) + count = torch.zeros_like(image).to(latents.device) + # get the latents corresponding to the current view coordinates + with self.progress_bar(total=len(views_batch)) as progress_bar: + for j, batch_view in enumerate(views_batch): + len(batch_view) + latents_for_view = torch.cat( + [ + latents_[:, :, h_start:h_end+pad_size*2, w_start:w_end+pad_size*2] + for h_start, h_end, w_start, w_end in batch_view + ] + ).to(self.vae.device) + image_patch = self.vae.decode(latents_for_view / self.vae.config.scaling_factor, return_dict=False)[0] + h_start, h_end, w_start, w_end = views[j] + h_start, h_end, w_start, w_end = h_start * self.vae_scale_factor, h_end * self.vae_scale_factor, w_start * self.vae_scale_factor, w_end * self.vae_scale_factor + p_h_start, p_h_end, p_w_start, p_w_end = pad_size * self.vae_scale_factor, image_patch.size(2) - pad_size * self.vae_scale_factor, pad_size * self.vae_scale_factor, image_patch.size(3) - pad_size * self.vae_scale_factor + image[:, :, h_start:h_end, w_start:w_end] += image_patch[:, :, p_h_start:p_h_end, p_w_start:p_w_end].to(latents.device) + count[:, :, h_start:h_end, w_start:w_end] += 1 + progress_bar.update() + image = image / count + + return image + + # Copied from diffusers.pipelines.stable_diffusion.pipeline_stable_diffusion_upscale.StableDiffusionUpscalePipeline.upcast_vae + def upcast_vae(self): + dtype = self.vae.dtype + self.vae.to(dtype=torch.float32) + use_torch_2_0_or_xformers = isinstance( + self.vae.decoder.mid_block.attentions[0].processor, + ( + AttnProcessor2_0, + XFormersAttnProcessor, + LoRAXFormersAttnProcessor, + LoRAAttnProcessor2_0, + ), + ) + # if xformers or torch_2_0 is used attention block does not need + # to be in float32 which can save lots of memory + if use_torch_2_0_or_xformers: + self.vae.post_quant_conv.to(dtype) + self.vae.decoder.conv_in.to(dtype) + self.vae.decoder.mid_block.to(dtype) + + @torch.no_grad() + def __call__( + self, + prompt: Union[str, List[str]] = None, + prompt_2: Optional[Union[str, List[str]]] = None, + height: Optional[int] = None, + width: Optional[int] = None, + num_inference_steps: int = 50, + denoising_end: Optional[float] = None, + guidance_scale: float = 5.0, + negative_prompt: Optional[Union[str, List[str]]] = None, + negative_prompt_2: Optional[Union[str, List[str]]] = None, + num_images_per_prompt: Optional[int] = 1, + eta: float = 0.0, + generator: Optional[Union[torch.Generator, List[torch.Generator]]] = None, + latents: Optional[torch.FloatTensor] = None, + prompt_embeds: Optional[torch.FloatTensor] = None, + negative_prompt_embeds: Optional[torch.FloatTensor] = None, + pooled_prompt_embeds: Optional[torch.FloatTensor] = None, + negative_pooled_prompt_embeds: Optional[torch.FloatTensor] = None, + output_type: Optional[str] = "pil", + return_dict: bool = False, + callback: Optional[Callable[[int, int, torch.FloatTensor], None]] = None, + callback_steps: int = 1, + cross_attention_kwargs: Optional[Dict[str, Any]] = None, + guidance_rescale: float = 0.0, + original_size: Optional[Tuple[int, int]] = None, + crops_coords_top_left: Tuple[int, int] = (0, 0), + target_size: Optional[Tuple[int, int]] = None, + negative_original_size: Optional[Tuple[int, int]] = None, + negative_crops_coords_top_left: Tuple[int, int] = (0, 0), + negative_target_size: Optional[Tuple[int, int]] = None, + ################### DemoFusion specific parameters #################### + view_batch_size: int = 16, + multi_decoder: bool = True, + stride: Optional[int] = 64, + cosine_scale_1: Optional[float] = 3., + cosine_scale_2: Optional[float] = 1., + cosine_scale_3: Optional[float] = 1., + sigma: Optional[float] = 1.0, + lowvram: bool = False, + ): + r""" + Function invoked when calling the pipeline for generation. + + Args: + prompt (`str` or `List[str]`, *optional*): + The prompt or prompts to guide the image generation. If not defined, one has to pass `prompt_embeds`. + instead. + prompt_2 (`str` or `List[str]`, *optional*): + The prompt or prompts to be sent to the `tokenizer_2` and `text_encoder_2`. If not defined, `prompt` is + used in both text-encoders + height (`int`, *optional*, defaults to self.unet.config.sample_size * self.vae_scale_factor): + The height in pixels of the generated image. This is set to 1024 by default for the best results. + Anything below 512 pixels won't work well for + [stabilityai/stable-diffusion-xl-base-1.0](https://huggingface.co/stabilityai/stable-diffusion-xl-base-1.0) + and checkpoints that are not specifically fine-tuned on low resolutions. + width (`int`, *optional*, defaults to self.unet.config.sample_size * self.vae_scale_factor): + The width in pixels of the generated image. This is set to 1024 by default for the best results. + Anything below 512 pixels won't work well for + [stabilityai/stable-diffusion-xl-base-1.0](https://huggingface.co/stabilityai/stable-diffusion-xl-base-1.0) + and checkpoints that are not specifically fine-tuned on low resolutions. + num_inference_steps (`int`, *optional*, defaults to 50): + The number of denoising steps. More denoising steps usually lead to a higher quality image at the + expense of slower inference. + denoising_end (`float`, *optional*): + When specified, determines the fraction (between 0.0 and 1.0) of the total denoising process to be + completed before it is intentionally prematurely terminated. As a result, the returned sample will + still retain a substantial amount of noise as determined by the discrete timesteps selected by the + scheduler. The denoising_end parameter should ideally be utilized when this pipeline forms a part of a + "Mixture of Denoisers" multi-pipeline setup, as elaborated in [**Refining the Image + Output**](https://huggingface.co/docs/diffusers/api/pipelines/stable_diffusion/stable_diffusion_xl#refining-the-image-output) + guidance_scale (`float`, *optional*, defaults to 5.0): + Guidance scale as defined in [Classifier-Free Diffusion Guidance](https://arxiv.org/abs/2207.12598). + `guidance_scale` is defined as `w` of equation 2. of [Imagen + Paper](https://arxiv.org/pdf/2205.11487.pdf). Guidance scale is enabled by setting `guidance_scale > + 1`. Higher guidance scale encourages to generate images that are closely linked to the text `prompt`, + usually at the expense of lower image quality. + negative_prompt (`str` or `List[str]`, *optional*): + The prompt or prompts not to guide the image generation. If not defined, one has to pass + `negative_prompt_embeds` instead. Ignored when not using guidance (i.e., ignored if `guidance_scale` is + less than `1`). + negative_prompt_2 (`str` or `List[str]`, *optional*): + The prompt or prompts not to guide the image generation to be sent to `tokenizer_2` and + `text_encoder_2`. If not defined, `negative_prompt` is used in both text-encoders + num_images_per_prompt (`int`, *optional*, defaults to 1): + The number of images to generate per prompt. + eta (`float`, *optional*, defaults to 0.0): + Corresponds to parameter eta (η) in the DDIM paper: https://arxiv.org/abs/2010.02502. Only applies to + [`schedulers.DDIMScheduler`], will be ignored for others. + generator (`torch.Generator` or `List[torch.Generator]`, *optional*): + One or a list of [torch generator(s)](https://pytorch.org/docs/stable/generated/torch.Generator.html) + to make generation deterministic. + latents (`torch.FloatTensor`, *optional*): + Pre-generated noisy latents, sampled from a Gaussian distribution, to be used as inputs for image + generation. Can be used to tweak the same generation with different prompts. If not provided, a latents + tensor will ge generated by sampling using the supplied random `generator`. + prompt_embeds (`torch.FloatTensor`, *optional*): + Pre-generated text embeddings. Can be used to easily tweak text inputs, *e.g.* prompt weighting. If not + provided, text embeddings will be generated from `prompt` input argument. + negative_prompt_embeds (`torch.FloatTensor`, *optional*): + Pre-generated negative text embeddings. Can be used to easily tweak text inputs, *e.g.* prompt + weighting. If not provided, negative_prompt_embeds will be generated from `negative_prompt` input + argument. + pooled_prompt_embeds (`torch.FloatTensor`, *optional*): + Pre-generated pooled text embeddings. Can be used to easily tweak text inputs, *e.g.* prompt weighting. + If not provided, pooled text embeddings will be generated from `prompt` input argument. + negative_pooled_prompt_embeds (`torch.FloatTensor`, *optional*): + Pre-generated negative pooled text embeddings. Can be used to easily tweak text inputs, *e.g.* prompt + weighting. If not provided, pooled negative_prompt_embeds will be generated from `negative_prompt` + input argument. + output_type (`str`, *optional*, defaults to `"pil"`): + The output format of the generate image. Choose between + [PIL](https://pillow.readthedocs.io/en/stable/): `PIL.Image.Image` or `np.array`. + return_dict (`bool`, *optional*, defaults to `True`): + Whether or not to return a [`~pipelines.stable_diffusion_xl.StableDiffusionXLPipelineOutput`] instead + of a plain tuple. + callback (`Callable`, *optional*): + A function that will be called every `callback_steps` steps during inference. The function will be + called with the following arguments: `callback(step: int, timestep: int, latents: torch.FloatTensor)`. + callback_steps (`int`, *optional*, defaults to 1): + The frequency at which the `callback` function will be called. If not specified, the callback will be + called at every step. + cross_attention_kwargs (`dict`, *optional*): + A kwargs dictionary that if specified is passed along to the `AttentionProcessor` as defined under + `self.processor` in + [diffusers.models.attention_processor](https://github.com/huggingface/diffusers/blob/main/src/diffusers/models/attention_processor.py). + guidance_rescale (`float`, *optional*, defaults to 0.7): + Guidance rescale factor proposed by [Common Diffusion Noise Schedules and Sample Steps are + Flawed](https://arxiv.org/pdf/2305.08891.pdf) `guidance_scale` is defined as `φ` in equation 16. of + [Common Diffusion Noise Schedules and Sample Steps are Flawed](https://arxiv.org/pdf/2305.08891.pdf). + Guidance rescale factor should fix overexposure when using zero terminal SNR. + original_size (`Tuple[int]`, *optional*, defaults to (1024, 1024)): + If `original_size` is not the same as `target_size` the image will appear to be down- or upsampled. + `original_size` defaults to `(width, height)` if not specified. Part of SDXL's micro-conditioning as + explained in section 2.2 of + [https://huggingface.co/papers/2307.01952](https://huggingface.co/papers/2307.01952). + crops_coords_top_left (`Tuple[int]`, *optional*, defaults to (0, 0)): + `crops_coords_top_left` can be used to generate an image that appears to be "cropped" from the position + `crops_coords_top_left` downwards. Favorable, well-centered images are usually achieved by setting + `crops_coords_top_left` to (0, 0). Part of SDXL's micro-conditioning as explained in section 2.2 of + [https://huggingface.co/papers/2307.01952](https://huggingface.co/papers/2307.01952). + target_size (`Tuple[int]`, *optional*, defaults to (1024, 1024)): + For most cases, `target_size` should be set to the desired height and width of the generated image. If + not specified it will default to `(width, height)`. Part of SDXL's micro-conditioning as explained in + section 2.2 of [https://huggingface.co/papers/2307.01952](https://huggingface.co/papers/2307.01952). + negative_original_size (`Tuple[int]`, *optional*, defaults to (1024, 1024)): + To negatively condition the generation process based on a specific image resolution. Part of SDXL's + micro-conditioning as explained in section 2.2 of + [https://huggingface.co/papers/2307.01952](https://huggingface.co/papers/2307.01952). For more + information, refer to this issue thread: https://github.com/huggingface/diffusers/issues/4208. + negative_crops_coords_top_left (`Tuple[int]`, *optional*, defaults to (0, 0)): + To negatively condition the generation process based on a specific crop coordinates. Part of SDXL's + micro-conditioning as explained in section 2.2 of + [https://huggingface.co/papers/2307.01952](https://huggingface.co/papers/2307.01952). For more + information, refer to this issue thread: https://github.com/huggingface/diffusers/issues/4208. + negative_target_size (`Tuple[int]`, *optional*, defaults to (1024, 1024)): + To negatively condition the generation process based on a target image resolution. It should be as same + as the `target_size` for most cases. Part of SDXL's micro-conditioning as explained in section 2.2 of + [https://huggingface.co/papers/2307.01952](https://huggingface.co/papers/2307.01952). For more + information, refer to this issue thread: https://github.com/huggingface/diffusers/issues/4208. + ################### DemoFusion specific parameters #################### + view_batch_size (`int`, defaults to 16): + The batch size for multiple denoising paths. Typically, a larger batch size can result in higher + efficiency but comes with increased GPU memory requirements. + multi_decoder (`bool`, defaults to True): + Determine whether to use a tiled decoder. Generally, when the resolution exceeds 3072x3072, + a tiled decoder becomes necessary. + stride (`int`, defaults to 64): + The stride of moving local patches. A smaller stride is better for alleviating seam issues, + but it also introduces additional computational overhead and inference time. + cosine_scale_1 (`float`, defaults to 3): + Control the strength of skip-residual. For specific impacts, please refer to Appendix C + in the DemoFusion paper. + cosine_scale_2 (`float`, defaults to 1): + Control the strength of dilated sampling. For specific impacts, please refer to Appendix C + in the DemoFusion paper. + cosine_scale_3 (`float`, defaults to 1): + Control the strength of the gaussion filter. For specific impacts, please refer to Appendix C + in the DemoFusion paper. + sigma (`float`, defaults to 1): + The standard value of the gaussian filter. + show_image (`bool`, defaults to False): + Determine whether to show intermediate results during generation. + lowvram (`bool`, defaults to False): + Try to fit in 8 Gb of VRAM, with xformers installed. + + Examples: + + Returns: + a `list` with the generated images at each phase. + """ + + # 0. Default height and width to unet + height = height or self.default_sample_size * self.vae_scale_factor + width = width or self.default_sample_size * self.vae_scale_factor + + x1_size = self.default_sample_size * self.vae_scale_factor + + height_scale = height / x1_size + width_scale = width / x1_size + scale_num = int(max(height_scale, width_scale)) + aspect_ratio = min(height_scale, width_scale) / max(height_scale, width_scale) + + original_size = original_size or (height, width) + target_size = target_size or (height, width) + + # 1. Check inputs. Raise error if not correct + self.check_inputs( + prompt, + prompt_2, + height, + width, + callback_steps, + negative_prompt, + negative_prompt_2, + prompt_embeds, + negative_prompt_embeds, + pooled_prompt_embeds, + negative_pooled_prompt_embeds, + num_images_per_prompt, + ) + + # 2. Define call parameters + if prompt is not None and isinstance(prompt, str): + batch_size = 1 + elif prompt is not None and isinstance(prompt, list): + batch_size = len(prompt) + else: + batch_size = prompt_embeds.shape[0] + + device = self._execution_device + self.lowvram = lowvram # pylint: disable=attribute-defined-outside-init + if self.lowvram: + self.vae.cpu() + self.unet.cpu() + self.text_encoder.to(device) + self.text_encoder_2.to(device) + + # here `guidance_scale` is defined analog to the guidance weight `w` of equation (2) + # of the Imagen paper: https://arxiv.org/pdf/2205.11487.pdf . `guidance_scale = 1` + # corresponds to doing no classifier free guidance. + do_classifier_free_guidance = guidance_scale > 1.0 + + # 3. Encode input prompt + text_encoder_lora_scale = ( + cross_attention_kwargs.get("scale", None) if cross_attention_kwargs is not None else None + ) + ( + prompt_embeds, + negative_prompt_embeds, + pooled_prompt_embeds, + negative_pooled_prompt_embeds, + ) = self.encode_prompt( + prompt=prompt, + prompt_2=prompt_2, + device=device, + num_images_per_prompt=num_images_per_prompt, + do_classifier_free_guidance=do_classifier_free_guidance, + negative_prompt=negative_prompt, + negative_prompt_2=negative_prompt_2, + prompt_embeds=prompt_embeds, + negative_prompt_embeds=negative_prompt_embeds, + pooled_prompt_embeds=pooled_prompt_embeds, + negative_pooled_prompt_embeds=negative_pooled_prompt_embeds, + lora_scale=text_encoder_lora_scale, + ) + + # 4. Prepare timesteps + self.scheduler.set_timesteps(num_inference_steps, device=device) + + timesteps = self.scheduler.timesteps + + # 5. Prepare latent variables + num_channels_latents = self.unet.config.in_channels + latents = self.prepare_latents( + batch_size * num_images_per_prompt, + num_channels_latents, + height // scale_num, + width // scale_num, + prompt_embeds.dtype, + device, + generator, + latents, + ) + + # 6. Prepare extra step kwargs. TODO: Logic should ideally just be moved out of the pipeline + extra_step_kwargs = self.prepare_extra_step_kwargs(generator, eta) + + # 7. Prepare added time ids & embeddings + add_text_embeds = pooled_prompt_embeds + add_time_ids = self._get_add_time_ids( + original_size, crops_coords_top_left, target_size, dtype=prompt_embeds.dtype + ) + if negative_original_size is not None and negative_target_size is not None: + negative_add_time_ids = self._get_add_time_ids( + negative_original_size, + negative_crops_coords_top_left, + negative_target_size, + dtype=prompt_embeds.dtype, + ) + else: + negative_add_time_ids = add_time_ids + + if do_classifier_free_guidance: + prompt_embeds = torch.cat([negative_prompt_embeds, prompt_embeds], dim=0) + add_text_embeds = torch.cat([negative_pooled_prompt_embeds, add_text_embeds], dim=0) + add_time_ids = torch.cat([negative_add_time_ids, add_time_ids], dim=0) + del negative_prompt_embeds, negative_pooled_prompt_embeds, negative_add_time_ids + + prompt_embeds = prompt_embeds.to(device) + add_text_embeds = add_text_embeds.to(device) + add_time_ids = add_time_ids.to(device).repeat(batch_size * num_images_per_prompt, 1) + + # 8. Denoising loop + num_warmup_steps = max(len(timesteps) - num_inference_steps * self.scheduler.order, 0) + + # 7.1 Apply denoising_end + if denoising_end is not None and isinstance(denoising_end, float) and denoising_end > 0 and denoising_end < 1: + discrete_timestep_cutoff = int( + round( + self.scheduler.config.num_train_timesteps + - (denoising_end * self.scheduler.config.num_train_timesteps) + ) + ) + num_inference_steps = len(list(filter(lambda ts: ts >= discrete_timestep_cutoff, timesteps))) + timesteps = timesteps[:num_inference_steps] + + output_images = [] + + ############################################################### Phase 1 ################################################################# + + if self.lowvram: + self.text_encoder.cpu() + self.text_encoder_2.cpu() + + shared.log.debug('DemoFusion: phase=1 denoising') + with self.progress_bar(total=num_inference_steps) as progress_bar: + for i, t in enumerate(timesteps): + + if self.lowvram: + self.vae.cpu() + self.unet.to(device) + + latents_for_view = latents + + # expand the latents if we are doing classifier free guidance + latent_model_input = ( + latents.repeat_interleave(2, dim=0) + if do_classifier_free_guidance + else latents + ) + latent_model_input = self.scheduler.scale_model_input(latent_model_input, t) + + # predict the noise residual + added_cond_kwargs = {"text_embeds": add_text_embeds, "time_ids": add_time_ids} + noise_pred = self.unet( + latent_model_input, + t, + encoder_hidden_states=prompt_embeds, + cross_attention_kwargs=cross_attention_kwargs, + added_cond_kwargs=added_cond_kwargs, + return_dict=False, + )[0] + + # perform guidance + if do_classifier_free_guidance: + noise_pred_uncond, noise_pred_text = noise_pred[::2], noise_pred[1::2] + noise_pred = noise_pred_uncond + guidance_scale * (noise_pred_text - noise_pred_uncond) + + if do_classifier_free_guidance and guidance_rescale > 0.0: + # Based on 3.4. in https://arxiv.org/pdf/2305.08891.pdf + noise_pred = rescale_noise_cfg(noise_pred, noise_pred_text, guidance_rescale=guidance_rescale) + + # compute the previous noisy sample x_t -> x_t-1 + latents = self.scheduler.step(noise_pred, t, latents, **extra_step_kwargs, return_dict=False)[0] + + # call the callback, if provided + if i == len(timesteps) - 1 or ((i + 1) > num_warmup_steps and (i + 1) % self.scheduler.order == 0): + progress_bar.update() + if callback is not None and i % callback_steps == 0: + step_idx = i // getattr(self.scheduler, "order", 1) + callback(step_idx, t, latents) + + anchor_mean = latents.mean() + anchor_std = latents.std() + del latents_for_view, latent_model_input, noise_pred, noise_pred_text, noise_pred_uncond + if self.lowvram: + latents = latents.cpu() + torch.cuda.empty_cache() + if output_type != "latent": + # make sure the VAE is in float32 mode, as it overflows in float16 + needs_upcasting = self.vae.dtype == torch.float16 and self.vae.config.force_upcast + + if self.lowvram: + needs_upcasting = False # use madebyollin/sdxl-vae-fp16-fix in lowvram mode! + self.unet.cpu() + self.vae.to(device) + + if needs_upcasting: + self.upcast_vae() + latents = latents.to(next(iter(self.vae.post_quant_conv.parameters())).dtype) + shared.log.debug('DemoFusion: phase=1 decoding') + if self.lowvram and multi_decoder: + current_width_height = self.unet.config.sample_size * self.vae_scale_factor + image = self.tiled_decode(latents, current_width_height, current_width_height) + else: + image = self.vae.decode(latents / self.vae.config.scaling_factor, return_dict=False)[0] + # cast back to fp16 if needed + if needs_upcasting: + self.vae.to(dtype=torch.float16) + image = self.image_processor.postprocess(image, output_type=output_type) + output_images.append(image[0]) + else: + output_images.append(latents) + + ####################################################### Phase 2+ ##################################################### + for current_scale_num in range(2, scale_num + 1): + if self.lowvram: + latents = latents.to(device) + self.unet.to(device) + torch.cuda.empty_cache() + shared.log.debug(f'DemoFusion: phase={current_scale_num} denoising') + current_height = self.unet.config.sample_size * self.vae_scale_factor * current_scale_num + current_width = self.unet.config.sample_size * self.vae_scale_factor * current_scale_num + if height > width: + current_width = int(current_width * aspect_ratio) + else: + current_height = int(current_height * aspect_ratio) + + latents = F.interpolate(latents.to(device), size=(int(current_height / self.vae_scale_factor), int(current_width / self.vae_scale_factor)), mode='bicubic') + + noise_latents = [] + noise = torch.randn_like(latents) + for timestep in timesteps: + noise_latent = self.scheduler.add_noise(latents, noise, timestep.unsqueeze(0)) + noise_latents.append(noise_latent) + latents = noise_latents[0] + + with self.progress_bar(total=num_inference_steps) as progress_bar: + for i, t in enumerate(timesteps): + count = torch.zeros_like(latents) + value = torch.zeros_like(latents) + cosine_factor = 0.5 * (1 + torch.cos(torch.pi * (self.scheduler.config.num_train_timesteps - t) / self.scheduler.config.num_train_timesteps)).cpu() + + c1 = cosine_factor ** cosine_scale_1 + latents = latents * (1 - c1) + noise_latents[i] * c1 + + ############################################# MultiDiffusion ############################################# + + views = self.get_views(current_height, current_width, stride=stride, window_size=self.unet.config.sample_size, random_jitter=True) + views_batch = [views[i : i + view_batch_size] for i in range(0, len(views), view_batch_size)] + + jitter_range = (self.unet.config.sample_size - stride) // 4 + latents_ = F.pad(latents, (jitter_range, jitter_range, jitter_range, jitter_range), 'constant', 0) + + count_local = torch.zeros_like(latents_) + value_local = torch.zeros_like(latents_) + + for _j, batch_view in enumerate(views_batch): + vb_size = len(batch_view) + + # get the latents corresponding to the current view coordinates + latents_for_view = torch.cat( + [ + latents_[:, :, h_start:h_end, w_start:w_end] + for h_start, h_end, w_start, w_end in batch_view + ] + ) + + # expand the latents if we are doing classifier free guidance + latent_model_input = latents_for_view + latent_model_input = ( + latent_model_input.repeat_interleave(2, dim=0) + if do_classifier_free_guidance + else latent_model_input + ) + latent_model_input = self.scheduler.scale_model_input(latent_model_input, t) + + prompt_embeds_input = torch.cat([prompt_embeds] * vb_size) + add_text_embeds_input = torch.cat([add_text_embeds] * vb_size) + add_time_ids_input = [] + for h_start, _h_end, w_start, _w_end in batch_view: + add_time_ids_ = add_time_ids.clone() + add_time_ids_[:, 2] = h_start * self.vae_scale_factor + add_time_ids_[:, 3] = w_start * self.vae_scale_factor + add_time_ids_input.append(add_time_ids_) + add_time_ids_input = torch.cat(add_time_ids_input) + + # predict the noise residual + added_cond_kwargs = {"text_embeds": add_text_embeds_input, "time_ids": add_time_ids_input} + noise_pred = self.unet( + latent_model_input, + t, + encoder_hidden_states=prompt_embeds_input, + cross_attention_kwargs=cross_attention_kwargs, + added_cond_kwargs=added_cond_kwargs, + return_dict=False, + )[0] + + if do_classifier_free_guidance: + noise_pred_uncond, noise_pred_text = noise_pred[::2], noise_pred[1::2] + noise_pred = noise_pred_uncond + guidance_scale * (noise_pred_text - noise_pred_uncond) + + if do_classifier_free_guidance and guidance_rescale > 0.0: + # Based on 3.4. in https://arxiv.org/pdf/2305.08891.pdf + noise_pred = rescale_noise_cfg(noise_pred, noise_pred_text, guidance_rescale=guidance_rescale) + + # compute the previous noisy sample x_t -> x_t-1 + self.scheduler._init_step_index(t) + latents_denoised_batch = self.scheduler.step( + noise_pred, t, latents_for_view, **extra_step_kwargs, return_dict=False)[0] + + # extract value from batch + for latents_view_denoised, (h_start, h_end, w_start, w_end) in zip( + latents_denoised_batch.chunk(vb_size), batch_view + ): + value_local[:, :, h_start:h_end, w_start:w_end] += latents_view_denoised + count_local[:, :, h_start:h_end, w_start:w_end] += 1 + + value_local = value_local[: ,:, jitter_range: jitter_range + current_height // self.vae_scale_factor, jitter_range: jitter_range + current_width // self.vae_scale_factor] + count_local = count_local[: ,:, jitter_range: jitter_range + current_height // self.vae_scale_factor, jitter_range: jitter_range + current_width // self.vae_scale_factor] + + c2 = cosine_factor ** cosine_scale_2 + + value += value_local / count_local * (1 - c2) + count += torch.ones_like(value_local) * (1 - c2) + + ############################################# Dilated Sampling ############################################# + + views = [[h, w] for h in range(current_scale_num) for w in range(current_scale_num)] + views_batch = [views[i : i + view_batch_size] for i in range(0, len(views), view_batch_size)] + + h_pad = (current_scale_num - (latents.size(2) % current_scale_num)) % current_scale_num + w_pad = (current_scale_num - (latents.size(3) % current_scale_num)) % current_scale_num + latents_ = F.pad(latents, (w_pad, 0, h_pad, 0), 'constant', 0) + + count_global = torch.zeros_like(latents_) + value_global = torch.zeros_like(latents_) + + c3 = 0.99 * cosine_factor ** cosine_scale_3 + 1e-2 + std_, mean_ = latents_.std(), latents_.mean() + latents_gaussian = gaussian_filter(latents_, kernel_size=(2*current_scale_num-1), sigma=sigma*c3) + latents_gaussian = (latents_gaussian - latents_gaussian.mean()) / latents_gaussian.std() * std_ + mean_ + + for _j, batch_view in enumerate(views_batch): + latents_for_view = torch.cat( + [ + latents_[:, :, h::current_scale_num, w::current_scale_num] + for h, w in batch_view + ] + ) + latents_for_view_gaussian = torch.cat( + [ + latents_gaussian[:, :, h::current_scale_num, w::current_scale_num] + for h, w in batch_view + ] + ) + + vb_size = latents_for_view.size(0) + + # expand the latents if we are doing classifier free guidance + latent_model_input = latents_for_view_gaussian + latent_model_input = ( + latent_model_input.repeat_interleave(2, dim=0) + if do_classifier_free_guidance + else latent_model_input + ) + latent_model_input = self.scheduler.scale_model_input(latent_model_input, t) + + prompt_embeds_input = torch.cat([prompt_embeds] * vb_size) + add_text_embeds_input = torch.cat([add_text_embeds] * vb_size) + add_time_ids_input = torch.cat([add_time_ids] * vb_size) + + # predict the noise residual + added_cond_kwargs = {"text_embeds": add_text_embeds_input, "time_ids": add_time_ids_input} + noise_pred = self.unet( + latent_model_input, + t, + encoder_hidden_states=prompt_embeds_input, + cross_attention_kwargs=cross_attention_kwargs, + added_cond_kwargs=added_cond_kwargs, + return_dict=False, + )[0] + + if do_classifier_free_guidance: + noise_pred_uncond, noise_pred_text = noise_pred[::2], noise_pred[1::2] + noise_pred = noise_pred_uncond + guidance_scale * (noise_pred_text - noise_pred_uncond) + + if do_classifier_free_guidance and guidance_rescale > 0.0: + # Based on 3.4. in https://arxiv.org/pdf/2305.08891.pdf + noise_pred = rescale_noise_cfg(noise_pred, noise_pred_text, guidance_rescale=guidance_rescale) + + # compute the previous noisy sample x_t -> x_t-1 + self.scheduler._init_step_index(t) + latents_denoised_batch = self.scheduler.step( + noise_pred, t, latents_for_view, **extra_step_kwargs, return_dict=False)[0] + + # extract value from batch + for latents_view_denoised, (h, w) in zip( + latents_denoised_batch.chunk(vb_size), batch_view + ): + value_global[:, :, h::current_scale_num, w::current_scale_num] += latents_view_denoised + count_global[:, :, h::current_scale_num, w::current_scale_num] += 1 + + c2 = cosine_factor ** cosine_scale_2 + + value_global = value_global[: ,:, h_pad:, w_pad:] + + value += value_global * c2 + count += torch.ones_like(value_global) * c2 + + ########################################################### + + latents = torch.where(count > 0, value / count, value) + + # call the callback, if provided + if i == len(timesteps) - 1 or ((i + 1) > num_warmup_steps and (i + 1) % self.scheduler.order == 0): + progress_bar.update() + if callback is not None and i % callback_steps == 0: + step_idx = i // getattr(self.scheduler, "order", 1) + callback(step_idx, t, latents) + + ######################################################################################################################################### + + latents = (latents - latents.mean()) / latents.std() * anchor_std + anchor_mean + if self.lowvram: + latents = latents.cpu() + torch.cuda.empty_cache() + if output_type != "latent": + # make sure the VAE is in float32 mode, as it overflows in float16 + needs_upcasting = self.vae.dtype == torch.float16 and self.vae.config.force_upcast + + if self.lowvram: + needs_upcasting = False # use madebyollin/sdxl-vae-fp16-fix in lowvram mode! + self.unet.cpu() + self.vae.to(device) + + if needs_upcasting: + self.upcast_vae() + latents = latents.to(next(iter(self.vae.post_quant_conv.parameters())).dtype) + + shared.log.debug(f'DemoFusion: phase={current_scale_num} decoding') + if multi_decoder: + image = self.tiled_decode(latents, current_height, current_width) + else: + image = self.vae.decode(latents / self.vae.config.scaling_factor, return_dict=False)[0] + + # cast back to fp16 if needed + if needs_upcasting: + self.vae.to(dtype=torch.float16) + image = self.image_processor.postprocess(image, output_type=output_type) + output_images.append(image[0]) + else: + image = latents + output_images.append(image) + + # Offload all models + self.maybe_free_model_hooks() + output = ImagePipelineOutput(images=output_images) + return output + + # Overrride to properly handle the loading and unloading of the additional text encoder. + def load_lora_weights(self, pretrained_model_name_or_path_or_dict: Union[str, Dict[str, torch.Tensor]], **kwargs): # pylint: disable=arguments-differ + # We could have accessed the unet config from `lora_state_dict()` too. We pass + # it here explicitly to be able to tell that it's coming from an SDXL + # pipeline. + + # Remove any existing hooks. + if is_accelerate_available() and is_accelerate_version(">=", "0.17.0.dev0"): + from accelerate.hooks import AlignDevicesHook, CpuOffload, remove_hook_from_module + else: + raise ImportError("Offloading requires `accelerate v0.17.0` or higher.") + + is_model_cpu_offload = False + is_sequential_cpu_offload = False + recursive = False + for _, component in self.components.items(): + if isinstance(component, torch.nn.Module): + if hasattr(component, "_hf_hook"): + is_model_cpu_offload = isinstance(component._hf_hook, CpuOffload) # pylint: disable=protected-access + is_sequential_cpu_offload = isinstance(component._hf_hook, AlignDevicesHook) # pylint: disable=protected-access + shared.log.info("Accelerate hooks detected. Since you have called `load_lora_weights()`, the previous hooks will be first removed. Then the LoRA parameters will be loaded and the hooks will be applied again.") + recursive = is_sequential_cpu_offload + remove_hook_from_module(component, recurse=recursive) + state_dict, network_alphas = self.lora_state_dict( + pretrained_model_name_or_path_or_dict, + unet_config=self.unet.config, + **kwargs, + ) + self.load_lora_into_unet(state_dict, network_alphas=network_alphas, unet=self.unet) + + text_encoder_state_dict = {k: v for k, v in state_dict.items() if "text_encoder." in k} + if len(text_encoder_state_dict) > 0: + self.load_lora_into_text_encoder( + text_encoder_state_dict, + network_alphas=network_alphas, + text_encoder=self.text_encoder, + prefix="text_encoder", + lora_scale=self.lora_scale, + ) + + text_encoder_2_state_dict = {k: v for k, v in state_dict.items() if "text_encoder_2." in k} + if len(text_encoder_2_state_dict) > 0: + self.load_lora_into_text_encoder( + text_encoder_2_state_dict, + network_alphas=network_alphas, + text_encoder=self.text_encoder_2, + prefix="text_encoder_2", + lora_scale=self.lora_scale, + ) + + # Offload back. + if is_model_cpu_offload: + self.enable_model_cpu_offload() + elif is_sequential_cpu_offload: + self.enable_sequential_cpu_offload() + + def _remove_text_encoder_monkey_patch(self): + self._remove_text_encoder_monkey_patch_classmethod(self.text_encoder) + self._remove_text_encoder_monkey_patch_classmethod(self.text_encoder_2) + + +### Script definition + +class Script(scripts.Script): + def title(self): + return 'DemoFusion' + + def show(self, is_img2img): + return not is_img2img if shared.backend == shared.Backend.DIFFUSERS else False + + # return signature is array of gradio components + def ui(self, _is_img2img): + with gr.Row(): + cosine_scale_1 = gr.Slider(minimum=0, maximum=5, step=0.1, value=3, label="Cosine scale 1") + cosine_scale_2 = gr.Slider(minimum=0, maximum=5, step=0.1, value=1, label="Cosine scale 2") + cosine_scale_3 = gr.Slider(minimum=0, maximum=5, step=0.1, value=1, label="Cosine scale 3") + with gr.Row(): + view_batch_size = gr.Slider(minimum=4, maximum=32, step=4, value=8, label="Denoising batch size") + sigma = gr.Slider(minimum=0.1, maximum=1, step=0.1, value=0.8, label="Sigma") + stride = gr.Slider(minimum=8, maximum=96, step=8, value=64, label="Stride") + with gr.Row(): + multi_decoder = gr.Checkbox(label="Multi decoder", value=True) + return [cosine_scale_1, cosine_scale_2, cosine_scale_3, sigma, view_batch_size, stride, multi_decoder] + + def run(self, p: processing.StableDiffusionProcessing, cosine_scale_1, cosine_scale_2, cosine_scale_3, sigma, view_batch_size, stride, multi_decoder): # pylint: disable=arguments-differ + c = shared.sd_model.__class__.__name__ if shared.sd_model is not None else '' + if c != 'StableDiffusionXLPipeline': + shared.log.warning(f'DemoFusion: pipeline={c} required=StableDiffusionXLPipeline') + return None + p.task_args['cosine_scale_1'] = cosine_scale_1 + p.task_args['cosine_scale_2'] = cosine_scale_2 + p.task_args['cosine_scale_3'] = cosine_scale_3 + p.task_args['sigma'] = sigma + p.task_args['view_batch_size'] = view_batch_size + p.task_args['stride'] = stride + p.task_args['multi_decoder'] = multi_decoder + p.task_args['output_type'] = 'np' + p.task_args['low_vram'] = True + shared.log.debug(f'DemoFusion: {p.task_args}') + old_pipe = shared.sd_model + new_pipe = DemoFusionSDXLPipeline( + vae = shared.sd_model.vae, + text_encoder=shared.sd_model.text_encoder, + text_encoder_2=shared.sd_model.text_encoder_2, + tokenizer=shared.sd_model.tokenizer, + tokenizer_2=shared.sd_model.tokenizer_2, + unet=shared.sd_model.unet, + scheduler=shared.sd_model.scheduler, + force_zeros_for_empty_prompt=shared.opts.diffusers_force_zeros, + ) + new_pipe.sd_checkpoint_info = shared.sd_model.sd_checkpoint_info # pylint: disable=attribute-defined-outside-init + new_pipe.sd_model_hash = shared.sd_model.sd_model_hash # pylint: disable=attribute-defined-outside-init + new_pipe.sd_model_checkpoint = shared.sd_model.sd_checkpoint_info.filename # pylint: disable=attribute-defined-outside-init + new_pipe.is_sdxl = True # pylint: disable=attribute-defined-outside-init + new_pipe.is_sd2 = False # pylint: disable=attribute-defined-outside-init + new_pipe.is_sd1 = False # pylint: disable=attribute-defined-outside-init + shared.sd_model = new_pipe + if not ((shared.opts.diffusers_model_cpu_offload or shared.cmd_opts.medvram) or (shared.opts.diffusers_seq_cpu_offload or shared.cmd_opts.lowvram)): + shared.sd_model.to(shared.device) + sd_models.set_diffuser_options(shared.sd_model, vae=None, op='model') + shared.log.debug(f'DemoFusion create: pipeline={shared.sd_model.__class__.__name__}') + processed = processing.process_images(p) + shared.sd_model = old_pipe + return processed From 4823595dc9d5b45855eae55f48bb2c79d959c392 Mon Sep 17 00:00:00 2001 From: Vladimir Mandic Date: Sun, 10 Dec 2023 10:27:44 -0500 Subject: [PATCH 034/143] minor fixes --- modules/images.py | 8 ++++++-- modules/shared.py | 11 ++++++++--- 2 files changed, 14 insertions(+), 5 deletions(-) diff --git a/modules/images.py b/modules/images.py index 44cd731a8..1d438b6c9 100644 --- a/modules/images.py +++ b/modules/images.py @@ -325,11 +325,15 @@ class FilenameGenerator: def __init__(self, p, seed, prompt, image, grid=False): if p is None: debug('Filename generator init skip') - return else: debug(f'Filename generator init: {seed} {prompt}') self.p = p - self.seed = seed if seed is not None and seed > 0 else p.all_seeds[0] + if seed is not None and seed > 0: + self.seed = seed + elif hasattr(p, 'all_seeds'): + self.seed = p.all_seeds[0] + else: + self.seed = 0 self.prompt = prompt self.image = image if not grid: diff --git a/modules/shared.py b/modules/shared.py index 4fc1bb856..ec14cf18f 100644 --- a/modules/shared.py +++ b/modules/shared.py @@ -185,6 +185,7 @@ def readfile(filename, silent=False, lock=False): try: if not os.path.exists(filename): return {} + t0 = time.time() if lock: lock_file = fasteners.InterProcessReaderWriterLock(f"{filename}.lock", logger=log) locked = lock_file.acquire_read_lock(blocking=True, timeout=3) @@ -192,8 +193,9 @@ def readfile(filename, silent=False, lock=False): data = json.load(file) if type(data) is str: data = json.loads(data) + t1 = time.time() if not silent: - log.debug(f'Read: file="{filename}" json={len(data)} bytes={os.path.getsize(filename)}') + log.debug(f'Read: file="{filename}" json={len(data)} bytes={os.path.getsize(filename)} time={t1-t0:.3f}') except Exception as e: if not silent: log.error(f'Reading failed: {filename} {e}') @@ -215,6 +217,7 @@ def default(obj): return str(obj) try: + t0 = time.time() # skipkeys=True, ensure_ascii=True, check_circular=True, allow_nan=True if type(data) == dict: output = json.dumps(data, indent=2, default=default) @@ -232,8 +235,9 @@ def default(obj): locked = lock.acquire_write_lock(blocking=True, timeout=3) with open(filename, mode, encoding="utf8") as file: file.write(output) + t1 = time.time() if not silent: - log.debug(f'Save: file="{filename}" json={len(data)} bytes={len(output)}') + log.debug(f'Save: file="{filename}" json={len(data)} bytes={len(output)} time={t1-t0:.3f}') except Exception as e: log.error(f'Saving failed: {filename} {e}') finally: @@ -921,8 +925,9 @@ def req(url_addr, headers = None, **kwargs): class Shared(sys.modules[__name__].__class__): # this class is here to provide sd_model field as a property, so that it can be created and loaded on demand rather than at program startup. @property def sd_model(self): - # log.debug(f'Access shared.sd_model: {sys._getframe().f_back.f_code.co_name}') # pylint: disable=protected-access import modules.sd_models # pylint: disable=W0621 + if modules.sd_models.model_data.sd_model is None: + log.debug(f'Model requested: fn={sys._getframe().f_back.f_code.co_name}') # pylint: disable=protected-access return modules.sd_models.model_data.get_sd_model() @sd_model.setter From d71287e068f527979052d687d3c2d44cdf0ad99e Mon Sep 17 00:00:00 2001 From: Vladimir Mandic Date: Sun, 10 Dec 2023 18:06:07 -0500 Subject: [PATCH 035/143] fix tensor to np --- extensions-builtin/sd-webui-controlnet | 2 +- extensions-builtin/stable-diffusion-webui-images-browser | 2 +- modules/processing.py | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/extensions-builtin/sd-webui-controlnet b/extensions-builtin/sd-webui-controlnet index 96dbc601a..0d0841671 160000 --- a/extensions-builtin/sd-webui-controlnet +++ b/extensions-builtin/sd-webui-controlnet @@ -1 +1 @@ -Subproject commit 96dbc601a6c880571d3a2a1314052d0922114604 +Subproject commit 0d0841671ef165d6d408f7b9cd6bfb1dda3d78cf diff --git a/extensions-builtin/stable-diffusion-webui-images-browser b/extensions-builtin/stable-diffusion-webui-images-browser index 08fc2647f..323ce7efe 160000 --- a/extensions-builtin/stable-diffusion-webui-images-browser +++ b/extensions-builtin/stable-diffusion-webui-images-browser @@ -1 +1 @@ -Subproject commit 08fc2647f1fe413699612df923b5f495d26853ef +Subproject commit 323ce7efef3e3f67b11c09c9e7fa3567d293b048 diff --git a/modules/processing.py b/modules/processing.py index cf3673482..7da5c5512 100644 --- a/modules/processing.py +++ b/modules/processing.py @@ -757,7 +757,7 @@ def validate_sample(tensor): sample = 255.0 * np.moveaxis(tensor.cpu().numpy(), 0, 2) else: sample = 255.0 * tensor - if isinstance(tensor, torch.Tensor): + if isinstance(tensor, torch.Tensor) and hasattr(tensor, 'detach'): sample = sample.detach().cpu().numpy() with warnings.catch_warnings(record=True) as w: cast = sample.astype(np.uint8) From 91a8746a27ac7c6cc01cc76fcb6e3de48c46d8ae Mon Sep 17 00:00:00 2001 From: Vladimir Mandic Date: Mon, 11 Dec 2023 09:08:02 -0500 Subject: [PATCH 036/143] update cli scripts --- cli/simple-img2img.py | 56 +++++++++++-------- cli/simple-txt2img.py | 50 +++++++++-------- .../stable-diffusion-webui-images-browser | 2 +- modules/processing.py | 10 +--- 4 files changed, 65 insertions(+), 53 deletions(-) diff --git a/cli/simple-img2img.py b/cli/simple-img2img.py index 8590fc62a..8501f16b5 100755 --- a/cli/simple-img2img.py +++ b/cli/simple-img2img.py @@ -1,9 +1,10 @@ #!/usr/bin/env python import os import io -import sys +import time import base64 import logging +import argparse import requests import urllib3 from PIL import Image @@ -14,22 +15,13 @@ logging.basicConfig(level = logging.INFO, format = '%(asctime)s %(levelname)s: %(message)s') log = logging.getLogger(__name__) +urllib3.disable_warnings(urllib3.exceptions.InsecureRequestWarning) + +filename='/tmp/simple-img2img.jpg' options = { - "init_images": [], - "prompt": "city at night", - "negative_prompt": "foggy, blurry", - "steps": 20, - "batch_size": 1, - "n_iter": 1, - "seed": -1, - "sampler_name": "Euler a", - "cfg_scale": 6, - "width": 512, - "height": 512, "save_images": False, "send_images": True, } -urllib3.disable_warnings(urllib3.exceptions.InsecureRequestWarning) def auth(): @@ -51,26 +43,44 @@ def encode(f): image = image.convert('RGB') with io.BytesIO() as stream: image.save(stream, 'JPEG') + image.close() values = stream.getvalue() encoded = base64.b64encode(values).decode() return encoded -def generate(num: int = 0): - log.info(f'sending generate request: {num+1} {options}') - options['init_images'] = [encode('html/logo-dark.png')] - options['batch_size'] = len(options['init_images']) +def generate(args): # pylint: disable=redefined-outer-name + t0 = time.time() + if args.model is not None: + post('/sdapi/v1/options', { 'sd_model_checkpoint': args.model }) + post('/sdapi/v1/reload-checkpoint') # needed if running in api-only to trigger new model load + options['prompt'] = args.prompt + options['negative_prompt'] = args.negative + options['steps'] = int(args.steps) + options['seed'] = int(args.seed) + options['sampler_name'] = args.sampler data = post('/sdapi/v1/img2img', options) + t1 = time.time() if 'images' in data: for i in range(len(data['images'])): b64 = data['images'][i].split(',',1)[0] + info = data['info'] image = Image.open(io.BytesIO(base64.b64decode(b64))) - log.info(f'received image: {image.size}') + image.save(filename) + log.info(f'received image: size={image.size} file={filename} time={t1-t0:.2f} info="{info}"') else: log.warning(f'no images received: {data}') + if __name__ == "__main__": - sys.argv.pop(0) - repeats = int(''.join(sys.argv) or '1') - log.info(f'repeats: {repeats}') - for n in range(repeats): - generate(n) + parser = argparse.ArgumentParser(description = 'simple-img2img') + parser.add_argument('--init', required=True, help='init image') + parser.add_argument('--mask', required=False, help='mask image') + parser.add_argument('--prompt', required=False, default='', help='prompt text') + parser.add_argument('--negative', required=False, default='', help='negative prompt text') + parser.add_argument('--steps', required=False, default=20, help='number of steps') + parser.add_argument('--seed', required=False, default=-1, help='initial seed') + parser.add_argument('--sampler', required=False, default='Euler a', help='sampler name') + parser.add_argument('--model', required=False, help='model name') + args = parser.parse_args() + log.info(f'img2img: {args}') + generate(args) diff --git a/cli/simple-txt2img.py b/cli/simple-txt2img.py index 70e60a916..c2a5ee001 100755 --- a/cli/simple-txt2img.py +++ b/cli/simple-txt2img.py @@ -1,9 +1,10 @@ #!/usr/bin/env python import io import os -import sys +import time import base64 import logging +import argparse import requests import urllib3 from PIL import Image @@ -17,18 +18,7 @@ urllib3.disable_warnings(urllib3.exceptions.InsecureRequestWarning) filename='/tmp/simple-txt2img.jpg' -model = None # desired model name, will be set if not none options = { - "prompt": "city at night", - "negative_prompt": "foggy, blurry", - "steps": 20, - "batch_size": 1, - "n_iter": 1, - "seed": -1, - "sampler_name": "UniPC", - "cfg_scale": 6, - "width": 512, - "height": 512, "save_images": False, "send_images": True, } @@ -48,25 +38,41 @@ def post(endpoint: str, dct: dict = None): return req.json() -def generate(num: int = 0): - log.info(f'sending generate request: {num+1} {options}') - if model is not None: - post('/sdapi/v1/options', { 'sd_model_checkpoint': model }) +def generate(args): # pylint: disable=redefined-outer-name + t0 = time.time() + if args.model is not None: + post('/sdapi/v1/options', { 'sd_model_checkpoint': args.model }) post('/sdapi/v1/reload-checkpoint') # needed if running in api-only to trigger new model load + options['prompt'] = args.prompt + options['negative_prompt'] = args.negative + options['steps'] = int(args.steps) + options['seed'] = int(args.seed) + options['sampler_name'] = args.sampler + options['width'] = int(args.width) + options['height'] = int(args.height) data = post('/sdapi/v1/txt2img', options) + t1 = time.time() if 'images' in data: for i in range(len(data['images'])): b64 = data['images'][i].split(',',1)[0] image = Image.open(io.BytesIO(base64.b64decode(b64))) + info = data['info'] image.save(filename) - log.info(f'received image: size={image.size} file={filename}') + log.info(f'received image: size={image.size} file={filename} time={t1-t0:.2f} info="{info}"') else: log.warning(f'no images received: {data}') if __name__ == "__main__": - sys.argv.pop(0) - repeats = int(''.join(sys.argv) or '1') - log.info(f'repeats: {repeats}') - for n in range(repeats): - generate(n) + parser = argparse.ArgumentParser(description = 'simple-txt2img') + parser.add_argument('--prompt', required=False, default='', help='prompt text') + parser.add_argument('--negative', required=False, default='', help='negative prompt text') + parser.add_argument('--width', required=False, default=512, help='image width') + parser.add_argument('--height', required=False, default=512, help='image height') + parser.add_argument('--steps', required=False, default=20, help='number of steps') + parser.add_argument('--seed', required=False, default=-1, help='initial seed') + parser.add_argument('--sampler', required=False, default='Euler a', help='sampler name') + parser.add_argument('--model', required=False, help='model name') + args = parser.parse_args() + log.info(f'txt2img: {args}') + generate(args) diff --git a/extensions-builtin/stable-diffusion-webui-images-browser b/extensions-builtin/stable-diffusion-webui-images-browser index 323ce7efe..730187753 160000 --- a/extensions-builtin/stable-diffusion-webui-images-browser +++ b/extensions-builtin/stable-diffusion-webui-images-browser @@ -1 +1 @@ -Subproject commit 323ce7efef3e3f67b11c09c9e7fa3567d293b048 +Subproject commit 7301877532180b621207f3580a212cf008d621ca diff --git a/modules/processing.py b/modules/processing.py index 7da5c5512..020e187de 100644 --- a/modules/processing.py +++ b/modules/processing.py @@ -423,7 +423,7 @@ def js(self): "styles": self.styles, "job_timestamp": self.job_timestamp, "clip_skip": self.clip_skip, - "is_using_inpainting_conditioning": self.is_using_inpainting_conditioning, + # "is_using_inpainting_conditioning": self.is_using_inpainting_conditioning, } return json.dumps(obj) @@ -753,12 +753,8 @@ def validate_sample(tensor): return tensor if tensor.dtype == torch.bfloat16: # numpy does not support bf16 tensor = tensor.to(torch.float16) - if shared.backend == shared.Backend.ORIGINAL: - sample = 255.0 * np.moveaxis(tensor.cpu().numpy(), 0, 2) - else: - sample = 255.0 * tensor - if isinstance(tensor, torch.Tensor) and hasattr(tensor, 'detach'): - sample = sample.detach().cpu().numpy() + sample = tensor.detach().cpu().numpy() if isinstance(tensor, torch.Tensor) and hasattr(tensor, 'detach') else tensor.cpu().numpy() + sample = 255.0 * np.moveaxis(sample, 0, 2) if shared.backend == shared.Backend.ORIGINAL else 255.0 * sample with warnings.catch_warnings(record=True) as w: cast = sample.astype(np.uint8) if len(w) > 0: From b7cbd3410612923fea9e67559cb2b89d9753b5d3 Mon Sep 17 00:00:00 2001 From: Vladimir Mandic Date: Mon, 11 Dec 2023 10:00:42 -0500 Subject: [PATCH 037/143] update api and img2img --- CHANGELOG.md | 2 ++ cli/simple-img2img.py | 7 +++++++ modules/api/api.py | 8 ++++---- modules/images.py | 6 ++---- modules/img2img.py | 5 ++++- modules/processing.py | 24 ++++++++++++++++-------- modules/shared_state.py | 13 ++++++++----- modules/txt2img.py | 6 +++++- 8 files changed, 48 insertions(+), 23 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 9029dcfeb..36f49e282 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -40,6 +40,8 @@ - fix extra networks sort - add hdr settings to metadata - improve handling of long filenames and filenames during batch processing + - do not set preview samples when using via api + - avoid unnecessary resizes in img2img and inpaint - update built-in log monitor in ui, thanks @midcoastal ## Update for 2023-12-04 diff --git a/cli/simple-img2img.py b/cli/simple-img2img.py index 8501f16b5..41043fc93 100755 --- a/cli/simple-img2img.py +++ b/cli/simple-img2img.py @@ -58,6 +58,13 @@ def generate(args): # pylint: disable=redefined-outer-name options['steps'] = int(args.steps) options['seed'] = int(args.seed) options['sampler_name'] = args.sampler + options['init_images'] = [encode(args.init)] + image = Image.open(args.init) + options['width'] = image.width + options['height'] = image.height + image.close() + if args.mask is not None: + options['mask'] = encode(args.mask) data = post('/sdapi/v1/img2img', options) t1 = time.time() if 'images' in data: diff --git a/modules/api/api.py b/modules/api/api.py index ef0825926..66bce7959 100644 --- a/modules/api/api.py +++ b/modules/api/api.py @@ -287,14 +287,14 @@ def text2imgapi(self, txt2imgreq: models.StableDiffusionTxt2ImgProcessingAPI): p.scripts = script_runner p.outpath_grids = shared.opts.outdir_grids or shared.opts.outdir_txt2img_grids p.outpath_samples = shared.opts.outdir_samples or shared.opts.outdir_txt2img_samples - shared.state.begin('api-txt2img') + shared.state.begin('api-txt2img', api=True) script_args = self.init_script_args(p, txt2imgreq, self.default_script_arg_txt2img, selectable_scripts, selectable_script_idx, script_runner) if selectable_scripts is not None: processed = scripts.scripts_txt2img.run(p, *script_args) # Need to pass args as list here else: p.script_args = tuple(script_args) # Need to pass args as tuple here processed = process_images(p) - shared.state.end() + shared.state.end(api=False) b64images = list(map(encode_pil_to_base64, processed.images)) if send_images else [] return models.TextToImageResponse(images=b64images, parameters=vars(txt2imgreq), info=processed.js()) @@ -335,14 +335,14 @@ def img2imgapi(self, img2imgreq: models.StableDiffusionImg2ImgProcessingAPI): p.scripts = script_runner p.outpath_grids = shared.opts.outdir_img2img_grids p.outpath_samples = shared.opts.outdir_img2img_samples - shared.state.begin('api-img2img') + shared.state.begin('api-img2img', api=True) script_args = self.init_script_args(p, img2imgreq, self.default_script_arg_img2img, selectable_scripts, selectable_script_idx, script_runner) if selectable_scripts is not None: processed = scripts.scripts_img2img.run(p, *script_args) # Need to pass args as list here else: p.script_args = tuple(script_args) # Need to pass args as tuple here processed = process_images(p) - shared.state.end() + shared.state.end(api=False) b64images = list(map(encode_pil_to_base64, processed.images)) if send_images else [] if not img2imgreq.include_init_images: diff --git a/modules/images.py b/modules/images.py index 1d438b6c9..23708ccd7 100644 --- a/modules/images.py +++ b/modules/images.py @@ -211,7 +211,7 @@ def draw_prompt_matrix(im, width, height, all_prompts, margin=0): def resize_image(resize_mode, im, width, height, upscaler_name=None, output_type='image'): - # shared.log.debug(f'Image resize: mode={resize_mode} resolution={width}x{height} upscaler={upscaler_name}') + shared.log.debug(f'Image resize: mode={resize_mode} resolution={width}x{height} upscaler={upscaler_name} function={sys._getframe(1).f_code.co_name}') # pylint: disable=protected-access """ Resizes an image with the specified resize_mode, width, and height. Args: @@ -243,9 +243,7 @@ def resize(im, w, h): im = im.resize((w, h), resample=Image.Resampling.LANCZOS) return im - if resize_mode == 0: - res = im.copy() - if width == 0 or height == 0: + if resize_mode == 0 or (im.width == width and im.height == height): res = im.copy() elif resize_mode == 1: res = resize(im, width, height) diff --git a/modules/img2img.py b/modules/img2img.py index 247cdc200..db726d331 100644 --- a/modules/img2img.py +++ b/modules/img2img.py @@ -9,6 +9,9 @@ from modules.memstats import memory_stats +debug = shared.log.debug if os.environ.get('SD_PROCESS_DEBUG', None) is not None else lambda *args, **kwargs: None + + def process_batch(p, input_files, input_dir, output_dir, inpaint_mask_dir, args): shared.log.debug(f'batch: {input_files}|{input_dir}|{output_dir}|{inpaint_mask_dir}') processing.fix_seed(p) @@ -106,7 +109,7 @@ def img2img(id_task: str, mode: int, prompt: str, negative_prompt: str, prompt_s shared.log.warning('Model not loaded') return [], '', '', 'Error: model not loaded' - shared.log.debug(f'img2img: id_task={id_task}|mode={mode}|prompt={prompt}|negative_prompt={negative_prompt}|prompt_styles={prompt_styles}|init_img={init_img}|sketch={sketch}|init_img_with_mask={init_img_with_mask}|inpaint_color_sketch={inpaint_color_sketch}|inpaint_color_sketch_orig={inpaint_color_sketch_orig}|init_img_inpaint={init_img_inpaint}|init_mask_inpaint={init_mask_inpaint}|steps={steps}|sampler_index={sampler_index}|latent_index={latent_index}|mask_blur={mask_blur}|mask_alpha={mask_alpha}|inpainting_fill={inpainting_fill}|full_quality={full_quality}|restore_faces={restore_faces}|tiling={tiling}|n_iter={n_iter}|batch_size={batch_size}|cfg_scale={cfg_scale}|image_cfg_scale={image_cfg_scale}|clip_skip={clip_skip}|denoising_strength={denoising_strength}|seed={seed}|subseed{subseed}|subseed_strength={subseed_strength}|seed_resize_from_h={seed_resize_from_h}|seed_resize_from_w={seed_resize_from_w}|selected_scale_tab={selected_scale_tab}|height={height}|width={width}|scale_by={scale_by}|resize_mode={resize_mode}|inpaint_full_res={inpaint_full_res}|inpaint_full_res_padding={inpaint_full_res_padding}|inpainting_mask_invert={inpainting_mask_invert}|img2img_batch_files={img2img_batch_files}|img2img_batch_input_dir={img2img_batch_input_dir}|img2img_batch_output_dir={img2img_batch_output_dir}|img2img_batch_inpaint_mask_dir={img2img_batch_inpaint_mask_dir}|override_settings_texts={override_settings_texts}') + debug(f'img2img: id_task={id_task}|mode={mode}|prompt={prompt}|negative_prompt={negative_prompt}|prompt_styles={prompt_styles}|init_img={init_img}|sketch={sketch}|init_img_with_mask={init_img_with_mask}|inpaint_color_sketch={inpaint_color_sketch}|inpaint_color_sketch_orig={inpaint_color_sketch_orig}|init_img_inpaint={init_img_inpaint}|init_mask_inpaint={init_mask_inpaint}|steps={steps}|sampler_index={sampler_index}|latent_index={latent_index}|mask_blur={mask_blur}|mask_alpha={mask_alpha}|inpainting_fill={inpainting_fill}|full_quality={full_quality}|restore_faces={restore_faces}|tiling={tiling}|n_iter={n_iter}|batch_size={batch_size}|cfg_scale={cfg_scale}|image_cfg_scale={image_cfg_scale}|clip_skip={clip_skip}|denoising_strength={denoising_strength}|seed={seed}|subseed{subseed}|subseed_strength={subseed_strength}|seed_resize_from_h={seed_resize_from_h}|seed_resize_from_w={seed_resize_from_w}|selected_scale_tab={selected_scale_tab}|height={height}|width={width}|scale_by={scale_by}|resize_mode={resize_mode}|inpaint_full_res={inpaint_full_res}|inpaint_full_res_padding={inpaint_full_res_padding}|inpainting_mask_invert={inpainting_mask_invert}|img2img_batch_files={img2img_batch_files}|img2img_batch_input_dir={img2img_batch_input_dir}|img2img_batch_output_dir={img2img_batch_output_dir}|img2img_batch_inpaint_mask_dir={img2img_batch_inpaint_mask_dir}|override_settings_texts={override_settings_texts}') if mode == 5: if img2img_batch_files is None or len(img2img_batch_files) == 0: diff --git a/modules/processing.py b/modules/processing.py index 020e187de..4b1965386 100644 --- a/modules/processing.py +++ b/modules/processing.py @@ -44,6 +44,8 @@ opt_C = 4 opt_f = 8 +debug = shared.log.debug if os.environ.get('SD_PROCESS_DEBUG', None) is not None else lambda *args, **kwargs: None + def setup_color_correction(image): shared.log.debug("Calibrating color correction.") @@ -64,10 +66,11 @@ def apply_overlay(image: Image, paste_loc, index, overlays): overlay = overlays[index] if paste_loc is not None: x, y, w, h = paste_loc - base_image = Image.new('RGBA', (overlay.width, overlay.height)) - image = images.resize_image(2, image, w, h) - base_image.paste(image, (x, y)) - image = base_image + if image.width != w or image.height != h or x != 0 or y != 0: + base_image = Image.new('RGBA', (overlay.width, overlay.height)) + image = images.resize_image(2, image, w, h) + base_image.paste(image, (x, y)) + image = base_image image = image.convert('RGBA') image.alpha_composite(overlay) image = image.convert('RGB') @@ -667,6 +670,7 @@ def create_infotext(p: StableDiffusionProcessing, all_prompts=None, all_seeds=No def process_images(p: StableDiffusionProcessing) -> Processed: + debug(f'Process images: {vars(p)}') if not hasattr(p.sd_model, 'sd_checkpoint_info'): return None if p.scripts is not None and isinstance(p.scripts, modules.scripts.ScriptRunner): @@ -1241,10 +1245,12 @@ def init(self, all_prompts, all_seeds, all_subseeds): crop_region = modules.masking.expand_crop_region(crop_region, self.width, self.height, mask.width, mask.height) x1, y1, x2, y2 = crop_region mask = mask.crop(crop_region) - image_mask = images.resize_image(3, mask, self.width, self.height) + if mask.width != self.width or mask.height != self.height: + image_mask = images.resize_image(3, mask, self.width, self.height) self.paste_to = (x1, y1, x2-x1, y2-y1) else: - image_mask = images.resize_image(self.resize_mode, image_mask, self.width, self.height) + if image_mask.width != self.width or image_mask.height != self.height: + image_mask = images.resize_image(self.resize_mode, image_mask, self.width, self.height) np_mask = np.array(image_mask) np_mask = np.clip((np_mask.astype(np.float32)) * 2, 0, 255).astype(np.uint8) self.mask_for_overlay = Image.fromarray(np_mask) @@ -1264,7 +1270,8 @@ def init(self, all_prompts, all_seeds, all_subseeds): images.save_image(img, path=shared.opts.outdir_init_images, basename=None, forced_filename=self.init_img_hash, suffix="-init-image") image = images.flatten(img, shared.opts.img2img_background_color) if crop_region is None and self.resize_mode != 4: - image = images.resize_image(self.resize_mode, image, self.width, self.height) + if image.width != self.width or image.height != self.height: + image = images.resize_image(self.resize_mode, image, self.width, self.height) self.width = image.width self.height = image.height if image_mask is not None: @@ -1280,7 +1287,8 @@ def init(self, all_prompts, all_seeds, all_subseeds): # crop_region is not None if we are doing inpaint full res if crop_region is not None: image = image.crop(crop_region) - image = images.resize_image(3, image, self.width, self.height) + if image.width != self.width or image.height != self.height: + image = images.resize_image(3, image, self.width, self.height) if image_mask is not None and self.inpainting_fill != 1: image = modules.masking.fill(image, latent_mask) if add_color_corrections: diff --git a/modules/shared_state.py b/modules/shared_state.py index 81ebbf120..c597417f1 100644 --- a/modules/shared_state.py +++ b/modules/shared_state.py @@ -21,6 +21,7 @@ class State: current_image_sampling_step = 0 id_live_preview = 0 textinfo = None + api = False time_start = None need_restart = False server_start = time.time() @@ -58,7 +59,7 @@ def dict(self): } return obj - def begin(self, title=""): + def begin(self, title="", api=None): import modules.devices self.total_jobs += 1 self.current_image = None @@ -74,12 +75,13 @@ def begin(self, title=""): self.sampling_step = 0 self.skipped = False self.textinfo = None + self.api = api if api is not None else self.api self.time_start = time.time() if self.debug_output: log.debug(f'State begin: {self.job}') modules.devices.torch_gc() - def end(self): + def end(self, api=None): import modules.devices if self.time_start is None: # someone called end before being log.debug(f'Access state.end: {sys._getframe().f_back.f_code.co_name}') # pylint: disable=protected-access @@ -92,20 +94,21 @@ def end(self): self.paused = False self.interrupted = False self.skipped = False + self.api = api if api is not None else self.api modules.devices.torch_gc() def set_current_image(self): from modules.shared import opts, cmd_opts """sets self.current_image from self.current_latent if enough sampling steps have been made after the last call to this""" - if cmd_opts.lowvram: + if cmd_opts.lowvram or self.api: return if abs(self.sampling_step - self.current_image_sampling_step) >= opts.show_progress_every_n_steps and opts.live_previews_enable and opts.show_progress_every_n_steps > 0: self.do_set_current_image() def do_set_current_image(self): - from modules.shared import opts - if self.current_latent is None: + if self.current_latent is None or self.api: return + from modules.shared import opts import modules.sd_samplers # pylint: disable=W0621 try: image = modules.sd_samplers.samples_to_image_grid(self.current_latent) if opts.show_progress_grid else modules.sd_samplers.sample_to_image(self.current_latent) diff --git a/modules/txt2img.py b/modules/txt2img.py index 55751bb2d..4ef523d83 100644 --- a/modules/txt2img.py +++ b/modules/txt2img.py @@ -1,12 +1,16 @@ +import os import modules.scripts from modules import sd_samplers, shared, processing from modules.generation_parameters_copypaste import create_override_settings_dict from modules.ui import plaintext_to_html +debug = shared.log.debug if os.environ.get('SD_PROCESS_DEBUG', None) is not None else lambda *args, **kwargs: None + + def txt2img(id_task: str, prompt: str, negative_prompt: str, prompt_styles, steps: int, sampler_index: int, latent_index: int, full_quality: bool, restore_faces: bool, tiling: bool, n_iter: int, batch_size: int, cfg_scale: float, image_cfg_scale: float, diffusers_guidance_rescale: float, clip_skip: int, seed: int, subseed: int, subseed_strength: float, seed_resize_from_h: int, seed_resize_from_w: int, height: int, width: int, enable_hr: bool, denoising_strength: float, hr_scale: float, hr_upscaler: str, hr_force: bool, hr_second_pass_steps: int, hr_resize_x: int, hr_resize_y: int, refiner_steps: int, refiner_start: int, refiner_prompt: str, refiner_negative: str, hdr_clamp, hdr_boundary, hdr_threshold, hdr_center, hdr_channel_shift, hdr_full_shift, hdr_maximize, hdr_max_center, hdr_max_boundry, override_settings_texts, *args): # pylint: disable=unused-argument - shared.log.debug(f'txt2img: id_task={id_task}|prompt={prompt}|negative_prompt={negative_prompt}|prompt_styles={prompt_styles}|steps={steps}|sampler_index={sampler_index}|latent_index={latent_index}|full_quality={full_quality}|restore_faces={restore_faces}|tiling={tiling}|n_iter={n_iter}|batch_size={batch_size}|cfg_scale={cfg_scale}|clip_skip={clip_skip}|seed={seed}|subseed={subseed}|subseed_strength={subseed_strength}|seed_resize_from_h={seed_resize_from_h}|seed_resize_from_w={seed_resize_from_w}||height={height}|width={width}|enable_hr={enable_hr}|denoising_strength={denoising_strength}|hr_scale={hr_scale}|hr_upscaler={hr_upscaler}|hr_force={hr_force}|hr_second_pass_steps={hr_second_pass_steps}|hr_resize_x={hr_resize_x}|hr_resize_y={hr_resize_y}|image_cfg_scale={image_cfg_scale}|diffusers_guidance_rescale={diffusers_guidance_rescale}|refiner_steps={refiner_steps}|refiner_start={refiner_start}|refiner_prompt={refiner_prompt}|refiner_negative={refiner_negative}|override_settings_texts={override_settings_texts}') + debug(f'txt2img: id_task={id_task}|prompt={prompt}|negative_prompt={negative_prompt}|prompt_styles={prompt_styles}|steps={steps}|sampler_index={sampler_index}|latent_index={latent_index}|full_quality={full_quality}|restore_faces={restore_faces}|tiling={tiling}|n_iter={n_iter}|batch_size={batch_size}|cfg_scale={cfg_scale}|clip_skip={clip_skip}|seed={seed}|subseed={subseed}|subseed_strength={subseed_strength}|seed_resize_from_h={seed_resize_from_h}|seed_resize_from_w={seed_resize_from_w}||height={height}|width={width}|enable_hr={enable_hr}|denoising_strength={denoising_strength}|hr_scale={hr_scale}|hr_upscaler={hr_upscaler}|hr_force={hr_force}|hr_second_pass_steps={hr_second_pass_steps}|hr_resize_x={hr_resize_x}|hr_resize_y={hr_resize_y}|image_cfg_scale={image_cfg_scale}|diffusers_guidance_rescale={diffusers_guidance_rescale}|refiner_steps={refiner_steps}|refiner_start={refiner_start}|refiner_prompt={refiner_prompt}|refiner_negative={refiner_negative}|override_settings_texts={override_settings_texts}') if shared.sd_model is None: shared.log.warning('Model not loaded') From 0d805e3ed7607231eed3140fff02dce8eb24a63a Mon Sep 17 00:00:00 2001 From: Vladimir Mandic Date: Mon, 11 Dec 2023 10:22:53 -0500 Subject: [PATCH 038/143] fix tensor conversion --- CHANGELOG.md | 1 + modules/processing.py | 8 +++++++- 2 files changed, 8 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 36f49e282..d0487a719 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -42,6 +42,7 @@ - improve handling of long filenames and filenames during batch processing - do not set preview samples when using via api - avoid unnecessary resizes in img2img and inpaint + - updated `cli/simple-txt2img.py` and `cli/simple-img2img.py` scripts - update built-in log monitor in ui, thanks @midcoastal ## Update for 2023-12-04 diff --git a/modules/processing.py b/modules/processing.py index 4b1965386..65982615c 100644 --- a/modules/processing.py +++ b/modules/processing.py @@ -757,7 +757,13 @@ def validate_sample(tensor): return tensor if tensor.dtype == torch.bfloat16: # numpy does not support bf16 tensor = tensor.to(torch.float16) - sample = tensor.detach().cpu().numpy() if isinstance(tensor, torch.Tensor) and hasattr(tensor, 'detach') else tensor.cpu().numpy() + print('HERE', isinstance(tensor, np.ndarray), isinstance(tensor, torch.Tensor)) + if isinstance(tensor, torch.Tensor) and hasattr(tensor, 'detach'): + sample = tensor.detach().cpu().numpy() + elif isinstance(tensor, np.ndarray): + sample = tensor + else: + shared.log.warning(f'Unknown sample type: {type(tensor)}') sample = 255.0 * np.moveaxis(sample, 0, 2) if shared.backend == shared.Backend.ORIGINAL else 255.0 * sample with warnings.catch_warnings(record=True) as w: cast = sample.astype(np.uint8) From 0fe27642a5711a670c243b7749ae5b8a0e325995 Mon Sep 17 00:00:00 2001 From: Disty0 Date: Mon, 11 Dec 2023 18:45:02 +0300 Subject: [PATCH 039/143] IPEX fix dtype errors when GPU supports 64 bit --- modules/intel/ipex/__init__.py | 5 ----- modules/intel/ipex/attention.py | 29 +++++++------------------ modules/intel/ipex/hijacks.py | 38 +++++++++++++++++++++++++++++---- 3 files changed, 42 insertions(+), 30 deletions(-) diff --git a/modules/intel/ipex/__init__.py b/modules/intel/ipex/__init__.py index 662572c87..0eecb8ff3 100644 --- a/modules/intel/ipex/__init__.py +++ b/modules/intel/ipex/__init__.py @@ -165,11 +165,6 @@ def ipex_init(): # pylint: disable=too-many-statements ipex_hijacks() if not torch.xpu.has_fp64_dtype(): - try: - from .attention import attention_init - attention_init() - except Exception: # pylint: disable=broad-exception-caught - pass try: from .diffusers import ipex_diffusers ipex_diffusers() diff --git a/modules/intel/ipex/attention.py b/modules/intel/ipex/attention.py index 094ea5104..083cc6c37 100644 --- a/modules/intel/ipex/attention.py +++ b/modules/intel/ipex/attention.py @@ -4,11 +4,8 @@ # pylint: disable=protected-access, missing-function-docstring, line-too-long original_torch_bmm = torch.bmm -def torch_bmm(input, mat2, *, out=None): - if input.dtype != mat2.dtype: - mat2 = mat2.to(input.dtype) - - #ARC GPUs can't allocate more than 4GB to a single block, Slice it: +def torch_bmm_32_bit(input, mat2, *, out=None): + # ARC GPUs can't allocate more than 4GB to a single block, Slice it: batch_size_attention, input_tokens, mat2_shape = input.shape[0], input.shape[1], mat2.shape[2] block_multiply = input.element_size() slice_block_size = input_tokens * mat2_shape / 1024 / 1024 * block_multiply @@ -17,7 +14,7 @@ def torch_bmm(input, mat2, *, out=None): split_slice_size = batch_size_attention if block_size > 4: do_split = True - #Find something divisible with the input_tokens + # Find something divisible with the input_tokens while (split_slice_size * slice_block_size) > 4: split_slice_size = split_slice_size // 2 if split_slice_size <= 1: @@ -30,7 +27,7 @@ def torch_bmm(input, mat2, *, out=None): if split_slice_size * slice_block_size > 4: slice_block_size2 = split_slice_size * mat2_shape / 1024 / 1024 * block_multiply do_split_2 = True - #Find something divisible with the input_tokens + # Find something divisible with the input_tokens while (split_2_slice_size * slice_block_size2) > 4: split_2_slice_size = split_2_slice_size // 2 if split_2_slice_size <= 1: @@ -64,8 +61,8 @@ def torch_bmm(input, mat2, *, out=None): return hidden_states original_scaled_dot_product_attention = torch.nn.functional.scaled_dot_product_attention -def scaled_dot_product_attention(query, key, value, attn_mask=None, dropout_p=0.0, is_causal=False): - #ARC GPUs can't allocate more than 4GB to a single block, Slice it: +def scaled_dot_product_attention_32_bit(query, key, value, attn_mask=None, dropout_p=0.0, is_causal=False): + # ARC GPUs can't allocate more than 4GB to a single block, Slice it: if len(query.shape) == 3: batch_size_attention, query_tokens, shape_four = query.shape shape_one = 1 @@ -74,11 +71,6 @@ def scaled_dot_product_attention(query, key, value, attn_mask=None, dropout_p=0. shape_one, batch_size_attention, query_tokens, shape_four = query.shape no_shape_one = False - if query.dtype != key.dtype: - key = key.to(dtype=query.dtype) - if query.dtype != value.dtype: - value = value.to(dtype=query.dtype) - block_multiply = query.element_size() slice_block_size = shape_one * query_tokens * shape_four / 1024 / 1024 * block_multiply block_size = batch_size_attention * slice_block_size @@ -86,7 +78,7 @@ def scaled_dot_product_attention(query, key, value, attn_mask=None, dropout_p=0. split_slice_size = batch_size_attention if block_size > 6: do_split = True - #Find something divisible with the shape_one + # Find something divisible with the shape_one while (split_slice_size * slice_block_size) > 4: split_slice_size = split_slice_size // 2 if split_slice_size <= 1: @@ -99,7 +91,7 @@ def scaled_dot_product_attention(query, key, value, attn_mask=None, dropout_p=0. if split_slice_size * slice_block_size > 6: slice_block_size2 = shape_one * split_slice_size * shape_four / 1024 / 1024 * block_multiply do_split_2 = True - #Find something divisible with the batch_size_attention + # Find something divisible with the batch_size_attention while (split_2_slice_size * slice_block_size2) > 4: split_2_slice_size = split_2_slice_size // 2 if split_2_slice_size <= 1: @@ -155,8 +147,3 @@ def scaled_dot_product_attention(query, key, value, attn_mask=None, dropout_p=0. query, key, value, attn_mask=attn_mask, dropout_p=dropout_p, is_causal=is_causal ) return hidden_states - -def attention_init(): - #ARC GPUs can't allocate more than 4GB to a single block: - torch.bmm = torch_bmm - torch.nn.functional.scaled_dot_product_attention = scaled_dot_product_attention diff --git a/modules/intel/ipex/hijacks.py b/modules/intel/ipex/hijacks.py index 207ed69b2..a03cdf626 100644 --- a/modules/intel/ipex/hijacks.py +++ b/modules/intel/ipex/hijacks.py @@ -93,6 +93,31 @@ def linalg_solve(A, B, *args, **kwargs): # pylint: disable=invalid-name else: return original_linalg_solve(A, B, *args, **kwargs) +if torch.xpu.has_fp64_dtype(): + original_torch_bmm = torch.bmm + original_scaled_dot_product_attention = torch.nn.functional.scaled_dot_product_attention +else: + # 64 bit attention workarounds for Alchemist: + try: + from .attention import torch_bmm_32_bit as original_torch_bmm + from .attention import scaled_dot_product_attention_32_bit as original_scaled_dot_product_attention + except Exception: # pylint: disable=broad-exception-caught + original_torch_bmm = torch.bmm + original_scaled_dot_product_attention = torch.nn.functional.scaled_dot_product_attention + +# dtype errors: +def torch_bmm(input, mat2, *, out=None): + if input.dtype != mat2.dtype: + mat2 = mat2.to(input.dtype) + return original_torch_bmm(input, mat2, out=out) + +def scaled_dot_product_attention(query, key, value, attn_mask=None, dropout_p=0.0, is_causal=False): + if query.dtype != key.dtype: + key = key.to(dtype=query.dtype) + if query.dtype != value.dtype: + value = value.to(dtype=query.dtype) + return original_scaled_dot_product_attention(query, key, value, attn_mask=attn_mask, dropout_p=dropout_p, is_causal=is_causal) + @property def is_cuda(self): return self.device.type == 'xpu' @@ -184,11 +209,16 @@ def ipex_hijacks(): lambda orig_func, *args, **kwargs: True) # Functions that make compile mad with CondFunc: - torch.utils.data.dataloader._MultiProcessingDataLoaderIter._shutdown_workers = _shutdown_workers torch.nn.DataParallel = DummyDataParallel + torch.utils.data.dataloader._MultiProcessingDataLoaderIter._shutdown_workers = _shutdown_workers + torch.autocast = ipex_autocast - torch.cat = torch_cat - torch.linalg.solve = linalg_solve + torch.backends.cuda.sdp_kernel = return_null_context torch.UntypedStorage.is_cuda = is_cuda + torch.nn.functional.interpolate = interpolate - torch.backends.cuda.sdp_kernel = return_null_context + torch.linalg.solve = linalg_solve + + torch.bmm = torch_bmm + torch.cat = torch_cat + torch.nn.functional.scaled_dot_product_attention = scaled_dot_product_attention From 839e12e06281e06235c4d929fb2d90cb656bef83 Mon Sep 17 00:00:00 2001 From: Vladimir Mandic Date: Mon, 11 Dec 2023 10:56:41 -0500 Subject: [PATCH 040/143] cleanup --- modules/processing.py | 1 - 1 file changed, 1 deletion(-) diff --git a/modules/processing.py b/modules/processing.py index 65982615c..7c62c446d 100644 --- a/modules/processing.py +++ b/modules/processing.py @@ -757,7 +757,6 @@ def validate_sample(tensor): return tensor if tensor.dtype == torch.bfloat16: # numpy does not support bf16 tensor = tensor.to(torch.float16) - print('HERE', isinstance(tensor, np.ndarray), isinstance(tensor, torch.Tensor)) if isinstance(tensor, torch.Tensor) and hasattr(tensor, 'detach'): sample = tensor.detach().cpu().numpy() elif isinstance(tensor, np.ndarray): From ac85b3a4231f150882c64b38e0f3b81b5a25f577 Mon Sep 17 00:00:00 2001 From: Disty0 Date: Mon, 11 Dec 2023 19:18:30 +0300 Subject: [PATCH 041/143] Add OpenVINO cache path option to System Paths --- modules/intel/openvino/__init__.py | 12 +++--------- modules/shared.py | 1 + 2 files changed, 4 insertions(+), 9 deletions(-) diff --git a/modules/intel/openvino/__init__.py b/modules/intel/openvino/__init__.py index 500bfd73b..6bd090819 100644 --- a/modules/intel/openvino/__init__.py +++ b/modules/intel/openvino/__init__.py @@ -102,12 +102,6 @@ def get_openvino_device(): except Exception: return f"OpenVINO {get_device()}" -def cache_root_path(): - cache_root = "./cache/" - if os.getenv("OPENVINO_TORCH_CACHE_DIR") is not None: - cache_root = os.getenv("OPENVINO_TORCH_CACHE_DIR") - return cache_root - def cached_model_name(model_hash_str, device, args, cache_root, reversed = False): if model_hash_str is None: return None @@ -181,7 +175,7 @@ def openvino_compile(gm: GraphModule, *args, model_hash_str: str = None, file_na core = Core() device = get_device() - cache_root = cache_root_path() + cache_root = shared.opts.openvino_cache_path if file_name is not None and os.path.isfile(file_name + ".xml") and os.path.isfile(file_name + ".bin"): om = core.read_model(file_name + ".xml") @@ -256,7 +250,7 @@ def openvino_compile_cached_model(cached_model_path, *example_inputs): om.inputs[idx].get_node().set_partial_shape(PartialShape(list(input_data.shape))) om.validate_nodes_and_infer_types() - core.set_property({'CACHE_DIR': cache_root_path() + '/blob'}) + core.set_property({'CACHE_DIR': shared.opts.openvino_cache_path + '/blob'}) compiled_model = core.compile_model(om, get_device()) @@ -351,7 +345,7 @@ def openvino_fx(subgraph, example_inputs): # Check if the model was fully supported and already cached example_inputs.reverse() inputs_reversed = True - maybe_fs_cached_name = cached_model_name(model_hash_str + "_fs", get_device(), example_inputs, cache_root_path()) + maybe_fs_cached_name = cached_model_name(model_hash_str + "_fs", get_device(), example_inputs, shared.opts.openvino_cache_path) if os.path.isfile(maybe_fs_cached_name + ".xml") and os.path.isfile(maybe_fs_cached_name + ".bin"): if (shared.compiled_model_state.cn_model != [] and str(shared.compiled_model_state.cn_model) in maybe_fs_cached_name): diff --git a/modules/shared.py b/modules/shared.py index ec14cf18f..dac16eee5 100644 --- a/modules/shared.py +++ b/modules/shared.py @@ -396,6 +396,7 @@ def default(obj): "clip_models_path": OptionInfo(os.path.join(paths.models_path, 'CLIP'), "Folder with CLIP models", folder=True), "other_paths_sep_options": OptionInfo("

Other paths

", "", gr.HTML), + "openvino_cache_path": OptionInfo('cache', "Directory for OpenVINO cache", folder=True), "temp_dir": OptionInfo("", "Directory for temporary images; leave empty for default", folder=True), "clean_temp_dir_at_start": OptionInfo(True, "Cleanup non-default temporary directory when starting webui"), })) From 86894b8249cac7a0c0fd8eef2cb611f9be40319e Mon Sep 17 00:00:00 2001 From: Disty0 Date: Mon, 11 Dec 2023 19:29:55 +0300 Subject: [PATCH 042/143] Remove Intel ARC 1024x1024 workaround from OpenVINO --- modules/processing.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/modules/processing.py b/modules/processing.py index 7c62c446d..2eca014ce 100644 --- a/modules/processing.py +++ b/modules/processing.py @@ -145,7 +145,7 @@ def __init__(self, sd_model=None, outpath_samples=None, outpath_grids=None, prom self.cfg_scale: float = cfg_scale self.image_cfg_scale = image_cfg_scale self.diffusers_guidance_rescale = diffusers_guidance_rescale - if (devices.backend == "ipex" or shared.cmd_opts.use_openvino) and width == 1024 and height == 1024: + if devices.backend == "ipex" and width == 1024 and height == 1024: width = 1080 height = 1080 self.width: int = width @@ -762,7 +762,7 @@ def validate_sample(tensor): elif isinstance(tensor, np.ndarray): sample = tensor else: - shared.log.warning(f'Unknown sample type: {type(tensor)}') + shared.log.warning(f'Unknown sample type: {type(tensor)}') sample = 255.0 * np.moveaxis(sample, 0, 2) if shared.backend == shared.Backend.ORIGINAL else 255.0 * sample with warnings.catch_warnings(record=True) as w: cast = sample.astype(np.uint8) @@ -1014,7 +1014,7 @@ class StableDiffusionProcessingTxt2Img(StableDiffusionProcessing): def __init__(self, enable_hr: bool = False, denoising_strength: float = 0.75, firstphase_width: int = 0, firstphase_height: int = 0, hr_scale: float = 2.0, hr_force: bool = False, hr_upscaler: str = None, hr_second_pass_steps: int = 0, hr_resize_x: int = 0, hr_resize_y: int = 0, refiner_steps: int = 5, refiner_start: float = 0, refiner_prompt: str = '', refiner_negative: str = '', **kwargs): super().__init__(**kwargs) - if devices.backend == "ipex" or shared.cmd_opts.use_openvino: + if devices.backend == "ipex": width_curse = bool(hr_resize_x == 1024 and self.height * (hr_resize_x / self.width) == 1024) height_curse = bool(hr_resize_y == 1024 and self.width * (hr_resize_y / self.height) == 1024) if (width_curse != height_curse) or (height_curse and width_curse): From 291ef78138e4b4c1ed75be7a7657d2db1f5d3630 Mon Sep 17 00:00:00 2001 From: Disty0 Date: Mon, 11 Dec 2023 19:44:33 +0300 Subject: [PATCH 043/143] Update changelog --- CHANGELOG.md | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index d0487a719..df4f7b889 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -34,7 +34,13 @@ - **CivitAI downloader** allow usage of access tokens for download of gated or private models - **Extra networks** new *settting -> extra networks -> build info on first access* indexes all networks on first access instead of server startup - - **Ipex** improvements, thanks @disty0 + - **IPEX** + - Fix IPEX Optimize not applying with Diffusers backend, thanks @disty0 + - Disable 32 bit workarounds if the GPU supports 64 bit, thanks @disty0 + - More compatibility improvements, thanks @disty0 + - **OpenVINO** + - Add *Directory for OpenVINO cache* option to *System Paths*, thanks @disty0 + - Remove Intel ARC specific 1024x1024 workaround, thanks @disty0 - disable google fonts check on server startup - fix torchvision/basicsr compatibility - fix extra networks sort From e2213a49e01cd9da23c396109caa6fdd2d04ea26 Mon Sep 17 00:00:00 2001 From: Vladimir Mandic Date: Mon, 11 Dec 2023 19:43:55 -0500 Subject: [PATCH 044/143] add pipeline script example --- modules/sd_models.py | 5 +- scripts/demofusion.py | 6 -- scripts/example.py | 132 ++++++++++++++++++++++++++++++++++++++++++ wiki | 2 +- 4 files changed, 137 insertions(+), 8 deletions(-) create mode 100644 scripts/example.py diff --git a/modules/sd_models.py b/modules/sd_models.py index e123a64b2..433d44558 100644 --- a/modules/sd_models.py +++ b/modules/sd_models.py @@ -676,7 +676,7 @@ def detect_pipeline(f: str, op: str = 'model', warning=True): return pipeline, guess -def set_diffuser_options(sd_model, vae, op: str): +def set_diffuser_options(sd_model, vae = None, op: str = 'model'): if sd_model is None: shared.log.warning(f'{op} is not loaded') return @@ -1049,6 +1049,9 @@ def set_diffuser_pipe(pipe, new_pipe_type): new_pipe.sd_model_hash = sd_model_hash new_pipe.has_accelerate = has_accelerate new_pipe.embedding_db = embedding_db + new_pipe.is_sdxl = True # pylint: disable=attribute-defined-outside-init # a1111 compatibility item + new_pipe.is_sd2 = False # pylint: disable=attribute-defined-outside-init + new_pipe.is_sd1 = False # pylint: disable=attribute-defined-outside-init shared.log.debug(f"Pipeline class change: original={pipe.__class__.__name__} target={new_pipe.__class__.__name__}") pipe = new_pipe return pipe diff --git a/scripts/demofusion.py b/scripts/demofusion.py index 0d655b413..d37552996 100644 --- a/scripts/demofusion.py +++ b/scripts/demofusion.py @@ -1267,12 +1267,6 @@ def run(self, p: processing.StableDiffusionProcessing, cosine_scale_1, cosine_sc scheduler=shared.sd_model.scheduler, force_zeros_for_empty_prompt=shared.opts.diffusers_force_zeros, ) - new_pipe.sd_checkpoint_info = shared.sd_model.sd_checkpoint_info # pylint: disable=attribute-defined-outside-init - new_pipe.sd_model_hash = shared.sd_model.sd_model_hash # pylint: disable=attribute-defined-outside-init - new_pipe.sd_model_checkpoint = shared.sd_model.sd_checkpoint_info.filename # pylint: disable=attribute-defined-outside-init - new_pipe.is_sdxl = True # pylint: disable=attribute-defined-outside-init - new_pipe.is_sd2 = False # pylint: disable=attribute-defined-outside-init - new_pipe.is_sd1 = False # pylint: disable=attribute-defined-outside-init shared.sd_model = new_pipe if not ((shared.opts.diffusers_model_cpu_offload or shared.cmd_opts.medvram) or (shared.opts.diffusers_seq_cpu_offload or shared.cmd_opts.lowvram)): shared.sd_model.to(shared.device) diff --git a/scripts/example.py b/scripts/example.py new file mode 100644 index 000000000..a8209492f --- /dev/null +++ b/scripts/example.py @@ -0,0 +1,132 @@ +import gradio as gr +from diffusers.pipelines import StableDiffusionPipeline, StableDiffusionXLPipeline # pylint: disable=unused-import +from modules import shared, scripts, processing, sd_models + +""" +Author:: +- Your details + +Credits: +- Link to original implementation and author + +Contributions: +- Submit a PR on SD.Next GitHub repo to be included in /scripts +- Before submitting a PR, make sure to test your script thoroughly and that it passes code quality checks + Lint rules are part of SD.Next CI/CD pipeline + > pip install ruff pylint + > ruff scripts/example.py + > pylint scriptts/example.py +""" + +## Config + +# script title +title = 'Example' + +# is script available in txt2img tab +txt2img = False + +# is script available in img2img tab +img2img = False + +# is pipeline ok to run in pure latent mode without implicit conversions +# recommended so entire ecosystem can be used as-is, but requires that latent is in format that sdnext can understand +# some pipelines may not support this, in which case set to false and pipeline will implicitly do things like vae encode/decode on its own +latent = True + +# class from which this pipeline is derived, most commonly 'StableDiffusionPipeline' or 'StableDiffusionXLPipeline' +pipeline_base = 'StableDiffusionPipeline' + +# class definition for this pipeline +# for built-in diffuser pipelines, simply import it from diffusers.pipelines above +# for example only, its set to same as base pipeline +# for community pipelines, copy class definition from community source code +# in which case only class definition code and required imports needs to be copied, not the entire source code +pipeline_class = StableDiffusionPipeline + +# pipeline args values are defined in ui method below, here we need to define their exact names +# they also have to be in the exact order as they are defined in ui +# note: variable names should be exactly as defined in pipeline_class.__call__ method +# if pipeline requires a param and its not provided, it will result in runtime error +# if you provide param that is not defined by pipeline, sdnext will strip it +params = ['test1', 'test2', 'test3', 'test4'] + + +### Script definition + +class Script(scripts.Script): + def title(self): + return title + + def show(self, is_img2img): + if shared.backend == shared.Backend.DIFFUSERS: + return img2img if is_img2img else txt2img + return False + + # Define UI for pipeline + def ui(self, _is_img2img): + ui_controls = [] + with gr.Row(): + ui_controls.append(gr.Slider(minimum=0, maximum=1, step=0.1, value=0.5, label="Test1")) + ui_controls.append(gr.Slider(minimum=0, maximum=10, step=1, value=5, label="Test2")) + with gr.Row(): + ui_controls.append(gr.Checkbox(label="Test3", value=True)) + with gr.Row(): + ui_controls.append(gr.Textbox(label="Test4", value="", placeholder="enter text here")) + with gr.Row(): + gr.HTML('
TypeError: StableDiffusionPipeline.__init__() missing 2 required positional arguments: 'safety_checker' and 'feature_extractor' + vae = shared.sd_model.vae, + text_encoder=shared.sd_model.text_encoder, + tokenizer=shared.sd_model.tokenizer, + unet=shared.sd_model.unet, + scheduler=shared.sd_model.scheduler, + safety_checker=shared.sd_model.safety_checker, + feature_extractor=shared.sd_model.feature_extractor, + ) + if not ((shared.opts.diffusers_model_cpu_offload or shared.cmd_opts.medvram) or (shared.opts.diffusers_seq_cpu_offload or shared.cmd_opts.lowvram)): + shared.sd_model.to(shared.device) # move pipeline if needed, but don't touch if its under automatic managment + sd_models.set_diffuser_options(shared.sd_model) # set all model options such as fp16, offload, etc. + + # if pipeline also needs a specific type, you can set it here, but not commonly needed + # shared.sd_model = sd_models.set_diffuser_pipe(shared.sd_model, sd_models.DiffusersTaskType.IMAGE_2_IMAGE) + + # prepare params + # all pipeline params go into p.task_args and are automatically handled by sdnext from there + for i in range(len(args)): + p.task_args[params[i]] = args[i] + + # you can also re-use existing params from `p` object if pipeline wants them, but under a different name + # for example, if pipeline expects 'image' param, but you want to use 'init_images' instead which is what img2img tab uses + # p.task_args['image'] = p.init_images[0] + + if not latent: + p.task_args['output_type'] = 'np' + shared.log.debug(f'{c}: args={p.task_args}') + + # if you need to run any preprocessing, this is the place to do it + + # run processing + processed: processing.Processed = processing.process_images(p) + + # if you need to run any postprocessing, this is the place to do it + # you dont need to handle saving, metadata, etc - sdnext will do it for you + + # restore original pipeline + shared.sd_model = orig_pipepeline + return processed diff --git a/wiki b/wiki index 06a89abd7..8bdd1dfb1 160000 --- a/wiki +++ b/wiki @@ -1 +1 @@ -Subproject commit 06a89abd7c2db4608f52b11c9fbf2186a2cfac2b +Subproject commit 8bdd1dfb154a1dd4f1cbafd85a60a5135b478bb9 From a6cb38acf1c48af5fb77847024ddc96dc4ea9871 Mon Sep 17 00:00:00 2001 From: Vladimir Mandic Date: Mon, 11 Dec 2023 19:46:47 -0500 Subject: [PATCH 045/143] update changelog --- CHANGELOG.md | 2 ++ scripts/example.py | 2 +- 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index df4f7b889..cebe52296 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -21,6 +21,8 @@ - **Schedulers** - add timesteps range, changing it will make scheduler to be over-complete or under-complete - add rescale betas with zero SNR option (applicable to Euler and DDIM, allows for higher dynamic range) + - **Custom Pipelines** contribute by adding your own custom pipelines! + - for details, see fully documented example: - **General** - **Process** create videos from batch or folder processing supports *GIF*, *PNG* and *MP4* with full interpolation, scene change detection, etc. diff --git a/scripts/example.py b/scripts/example.py index a8209492f..7fedd6a4d 100644 --- a/scripts/example.py +++ b/scripts/example.py @@ -34,7 +34,7 @@ # some pipelines may not support this, in which case set to false and pipeline will implicitly do things like vae encode/decode on its own latent = True -# class from which this pipeline is derived, most commonly 'StableDiffusionPipeline' or 'StableDiffusionXLPipeline' +# base pipeline class from which this pipeline is derived, most commonly 'StableDiffusionPipeline' or 'StableDiffusionXLPipeline' pipeline_base = 'StableDiffusionPipeline' # class definition for this pipeline From be1d90c3edd1ce64d5648716b65d434d01aa5bd7 Mon Sep 17 00:00:00 2001 From: Vladimir Mandic Date: Mon, 11 Dec 2023 23:45:01 -0500 Subject: [PATCH 046/143] update examples --- scripts/example.py | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/scripts/example.py b/scripts/example.py index 7fedd6a4d..b507f6386 100644 --- a/scripts/example.py +++ b/scripts/example.py @@ -3,6 +3,14 @@ from modules import shared, scripts, processing, sd_models """ +This is a simpler template for script for SD.Next that implements a custom pipeline +Items that can be added: +- Any pipeline already in diffusers + List of pipelines that can be directly used: +- Any pipeline for which diffusers definiotion exists and can be copied + List of pipelines with community definitions: +- Any custom pipeline that you create + Author:: - Your details From 033569cfb1f149ee219d2e6c647989c3ff15c1ce Mon Sep 17 00:00:00 2001 From: Vladimir Mandic Date: Tue, 12 Dec 2023 14:36:00 -0500 Subject: [PATCH 047/143] minor updates --- cli/image-grid.py | 2 +- javascript/black-teal.css | 4 +- javascript/light-teal.css | 2 +- javascript/sdnext.css | 86 +++++++++++++-------------------------- modules/shared.py | 1 + modules/ui.py | 9 +++- modules/ui_symbols.py | 3 ++ wiki | 2 +- 8 files changed, 44 insertions(+), 65 deletions(-) diff --git a/cli/image-grid.py b/cli/image-grid.py index a1fd12d17..743a12bdf 100755 --- a/cli/image-grid.py +++ b/cli/image-grid.py @@ -56,7 +56,7 @@ def grid(images, labels = None, width = 0, height = 0, border = 0, square = Fals for i, img in enumerate(images): # pylint: disable=redefined-outer-name x = (i % cols * w) + (i % cols * border) y = (i // cols * h) + (i // cols * border) - img.thumbnail((w, h), Image.HAMMING) + img.thumbnail((w, h), Image.Resampling.HAMMING) image.paste(img, box=(x, y)) if labels is not None and len(images) == len(labels): ctx = ImageDraw.Draw(image) diff --git a/javascript/black-teal.css b/javascript/black-teal.css index b2cd0b984..d5454360c 100644 --- a/javascript/black-teal.css +++ b/javascript/black-teal.css @@ -51,7 +51,7 @@ input[type=range]::-moz-range-thumb { box-shadow: 2px 2px 3px #111111 !important ::-webkit-scrollbar { width: 12px; } ::-webkit-scrollbar-track { background: #333333; } ::-webkit-scrollbar-thumb { background-color: var(--highlight-color); border-radius: var(--radius-lg); border-width: 0; box-shadow: 2px 2px 3px #111111; } -div.form { border-width: 0; box-shadow: none; background: transparent; overflow: visible; margin-bottom: 6px; } +div.form { border-width: 0; box-shadow: none; background: transparent; overflow: visible; } /* gradio style classes */ fieldset .gr-block.gr-box, label.block span { padding: 0; margin-top: -4px; } @@ -78,7 +78,7 @@ svg.feather.feather-image, .feather .feather-image { display: none } .px-4 { padding-lefT: 1rem; padding-right: 1rem; } .py-6 { padding-bottom: 0; } .tabs { background-color: var(--background-color); } -.block.token-counter span { background-color: var(--input-background-fill) !important; box-shadow: 2px 2px 2px #111; border: none !important; font-size: 0.8rem; } +.block.token-counter span { background-color: var(--input-background-fill) !important; box-shadow: 2px 2px 2px #111; border: none !important; font-size: 0.7rem; } .tab-nav { zoom: 120%; margin-top: 10px; margin-bottom: 10px; border-bottom: 2px solid var(--highlight-color) !important; padding-bottom: 2px; } .label-wrap { margin: 8px 0px 4px 0px; } .gradio-button.tool { border: none; background: none; box-shadow: none; filter: hue-rotate(340deg) saturate(0.5); } diff --git a/javascript/light-teal.css b/javascript/light-teal.css index ae582231f..5b1aa3108 100644 --- a/javascript/light-teal.css +++ b/javascript/light-teal.css @@ -79,7 +79,7 @@ svg.feather.feather-image, .feather .feather-image { display: none } .px-4 { padding-lefT: 1rem; padding-right: 1rem; } .py-6 { padding-bottom: 0; } .tabs { background-color: var(--background-color); } -.block.token-counter span { background-color: var(--input-background-fill) !important; box-shadow: 2px 2px 2px #111; border: none !important; font-size: 0.8rem; } +.block.token-counter span { background-color: var(--input-background-fill) !important; box-shadow: 2px 2px 2px #111; border: none !important; font-size: 0.7rem; } .tab-nav { zoom: 120%; margin-top: 10px; margin-bottom: 10px; border-bottom: 2px solid var(--highlight-color) !important; padding-bottom: 2px; } .label-wrap { margin: 16px 0px 8px 0px; } .gradio-button.tool { border: none; background: none; box-shadow: none; filter: hue-rotate(340deg) saturate(0.5); } diff --git a/javascript/sdnext.css b/javascript/sdnext.css index 769d9da16..e02a387fd 100644 --- a/javascript/sdnext.css +++ b/javascript/sdnext.css @@ -62,7 +62,7 @@ button.custom-button{ border-radius: var(--button-large-radius); padding: var(-- .theme-preview { display: none; position: fixed; border: var(--spacing-sm) solid var(--neutral-600); box-shadow: 2px 2px 2px 2px var(--neutral-700); top: 0; bottom: 0; left: 0; right: 0; margin: auto; max-width: 75vw; z-index: 999; } /* txt2img/img2img specific */ -.block.token-counter{ position: absolute; display: inline-block; right: 0; min-width: 0 !important; width: auto; z-index: 100; top: -0.75em; } +.block.token-counter{ position: absolute; display: inline-block; right: 1em; min-width: 0 !important; width: auto; z-index: 100; top: -0.5em; } .block.token-counter span{ background: var(--input-background-fill) !important; box-shadow: 0 0 0.0 0.3em rgba(192,192,192,0.15), inset 0 0 0.6em rgba(192,192,192,0.075); border: 2px solid rgba(192,192,192,0.4) !important; } .block.token-counter.error span{ box-shadow: 0 0 0.0 0.3em rgba(255,0,0,0.15), inset 0 0 0.6em rgba(255,0,0,0.075); border: 2px solid rgba(255,0,0,0.4) !important; } .block.token-counter div{ display: inline; } @@ -74,7 +74,7 @@ button.custom-button{ border-radius: var(--button-large-radius); padding: var(-- #txt2img_footer, #img2img_footer, #extras_footer { height: fit-content; } #txt2img_footer, #img2img_footer { height: fit-content; display: none; } #txt2img_generate_box, #img2img_generate_box { gap: 0.5em; flex-wrap: wrap-reverse; height: fit-content; } -#txt2img_actions_column, #img2img_actions_column { gap: 0.5em; height: fit-content; } +#txt2img_actions_column, #img2img_actions_column { gap: 0.3em; height: fit-content; } #txt2img_generate_box > button, #img2img_generate_box > button, #txt2img_enqueue, #img2img_enqueue { min-height: 42px; max-height: 42px; line-height: 1em; } #txt2img_generate_line2, #img2img_generate_line2, #txt2img_tools, #img2img_tools { display: flex; } #txt2img_generate_line2 > button, #img2img_generate_line2 > button, #extras_generate_box > button, #txt2img_tools > button, #img2img_tools > button { height: 2em; line-height: 0; font-size: var(--input-text-size); @@ -251,6 +251,12 @@ table.settings-value-table td { padding: 0.4em; border: 1px solid #ccc; max-widt .nvml { position: fixed; bottom: 10px; right: 10px; background: var(--background-fill-primary); border: 1px solid var(--button-primary-border-color); padding: 6px; color: var(--button-primary-text-color); font-size: 0.7em; z-index: 50; font-family: monospace; display: none; } +/* control */ +.control-button { min-height: 42px; max-height: 42px; line-height: 1em; } +.control-tabs > .tab-nav { margin-bottom: 0; margin-top: 0; } +.processor-settings { padding: 0 !important; max-width: 300px; } +.processor-group > div { flex-flow: wrap;gap: 1em; } + /* loader */ .splash { position: fixed; top: 0; left: 0; width: 100vw; height: 100vh; z-index: 1000; display: block; text-align: center; } .motd { margin-top: 2em; color: var(--body-text-color-subdued); font-family: monospace; font-variant: all-petite-caps; } @@ -280,76 +286,40 @@ table.settings-value-table td { padding: 0.4em; border: 1px solid #ccc; max-widt --spacing-xxl: 6px; } -/* Apply different styles for devices with coarse pointers dependant on screen resolution */ -@media (hover: none) and (pointer: coarse) { - - /* Do not affect displays larger than 1024px wide. */ - @media (max-width: 1024px) { - - /* Screens smaller than 400px wide */ - @media (max-width: 399px) { +@media (hover: none) and (pointer: coarse) { /* Apply different styles for devices with coarse pointers dependant on screen resolution */ + @media (max-width: 1024px) { /* Do not affect displays larger than 1024px wide. */ + @media (max-width: 399px) { /* Screens smaller than 400px wide */ :root, .light, .dark { --left-column: 100%; } - - /* maintain single column for from image operations on larger mobile devices */ - #txt2img_results, #img2img_results, #extras_results { min-width: calc(min(320px, 100%)) !important;} + #txt2img_results, #img2img_results, #extras_results { min-width: calc(min(320px, 100%)) !important;} /* maintain single column for from image operations on larger mobile devices */ #txt2img_footer p { text-wrap: wrap; } - - } - - /* Screens larger than 400px wide */ - @media (min-width: 400px) { + } + @media (min-width: 400px) { /* Screens larger than 400px wide */ :root, .light, .dark {--left-column: 50%;} - - /* maintain side by side split on larger mobile displays for from text */ - #txt2img_results, #extras_results, #txt2img_footer p {text-wrap: wrap; max-width: 100% !important; } + #txt2img_results, #extras_results, #txt2im g_footer p {text-wrap: wrap; max-width: 100% !important; } /* maintain side by side split on larger mobile displays for from text */ } - #scripts_alwayson_txt2img div, #scripts_alwayson_img2img div { max-width: 100%; } #txt2img_prompt_container, #img2img_prompt_container { resize:vertical !important; } - - /* make generate and enqueue buttons take up the entire width of their rows. */ - #txt2img_generate_box, #txt2img_enqueue_wrapper { min-width: 100% !important;} - - /*make interrogate buttons take up appropriate space. */ - #img2img_toprow > div.gradio-column {flex-grow: 1 !important;} + #txt2img_generate_box, #txt2img_enqueue_wrapper { min-width: 100% !important;} /* make generate and enqueue buttons take up the entire width of their rows. */ + #img2img_toprow > div.gradio-column {flex-grow: 1 !important;} /*make interrogate buttons take up appropriate space. */ #img2img_actions_column {display: flex; min-width: fit-content !important; flex-direction: row;justify-content: space-evenly; align-items: center;} #txt2img_generate_box, #img2img_generate_box, #txt2img_enqueue_wrapper,#img2img_enqueue_wrapper {display: flex;flex-direction: column;height: 4em !important;align-items: stretch;justify-content: space-evenly;} - - /* maintain single column for from image operations on larger mobile devices */ - #img2img_interface, #img2img_results, #img2img_footer p {text-wrap: wrap; min-width: 100% !important; max-width: 100% !important;} - /* fix inpaint image display being too large for mobile displays */ - #img2img_sketch, #img2maskimg, #inpaint_sketch {display: flex; alignment-baseline:after-edge !important; overflow: auto !important; resize: none !important; } + #img2img_interface, #img2img_results, #img2img_footer p {text-wrap: wrap; min-width: 100% !important; max-width: 100% !important;} /* maintain single column for from image operations on larger mobile devices */ + #img2img_sketch, #img2maskimg, #inpaint_sketch {display: flex; alignment-baseline:after-edge !important; overflow: auto !important; resize: none !important; } /* fix inpaint image display being too large for mobile displays */ #img2maskimg canvas { width: auto !important; max-height: 100% !important; height: auto !important; } - - /* fix from text/image UI elements to prevent them from moving around within the UI */ - #txt2img_sampler, #txt2img_batch, #txt2img_seed_group, #txt2img_advanced, #txt2img_second_pass, #img2img_sampling_group, #img2img_resize_group, #img2img_batch_group, #img2img_seed_group, #img2img_denoise_group, #img2img_advanced_group { width: 100% !important; } + #txt2img_sampler, #txt2img_batch, #txt2img_seed_group, #txt2img_advanced, #txt2img_second_pass, #img2img_sampling_group, #img2img_resize_group, #img2img_batch_group, #img2img_seed_group, #img2img_denoise_group, #img2img_advanced_group { width: 100% !important; } /* fix from text/image UI elements to prevent them from moving around within the UI */ #img2img_resize_group .gradio-radio > div { display: flex; flex-direction: column; width: unset !important; } #inpaint_controls div {display:flex;flex-direction: row;} #inpaint_controls .gradio-radio > div { display: flex; flex-direction: column !important; } - - /* move image preview/output on models page to bottom of page */ - #models_tab { flex-direction: column-reverse !important; } - /* fix settings for agent scheduler */ - #enqueue_keyboard_shortcut_modifiers, #enqueue_keyboard_shortcut_key div { max-width: 40% !important;} - - /* adjust width of certain settings item to allow aligning as row, but not have it go off the screen */ - #settings { display: flex; flex-direction: row; flex-wrap: wrap; max-width: 100% !important; } + #models_tab { flex-direction: column-reverse !important; } /* move image preview/output on models page to bottom of page */ + #enqueue_keyboard_shortcut_modifiers, #enqueue_keyboard_shortcut_key div { max-width: 40% !important;} /* fix settings for agent scheduler */ + #settings { display: flex; flex-direction: row; flex-wrap: wrap; max-width: 100% !important; } /* adjust width of certain settings item to allow aligning as row, but not have it go off the screen */ #settings div.tab-content > div > div > div { max-width: 80% !important;} #settings div .gradio-radio { width: unset !important; } - - /* enable scrolling on extensions tab */ - #tab_extensions table { border-collapse: collapse; display: block; overflow-x:auto !important;} - - /* increase scrollbar size to make it finger friendly */ - ::-webkit-scrollbar { width: 25px !important; height:25px; } - - /* adjust dropdown size to make them easier to select individual items on mobile. */ - .gradio-dropdown ul.options {max-height: 41vh !important; } + #tab_extensions table { border-collapse: collapse; display: block; overflow-x:auto !important;} /* enable scrolling on extensions tab */ + ::-webkit-scrollbar { width: 25px !important; height:25px; } /* increase scrollbar size to make it finger friendly */ + .gradio-dropdown ul.options {max-height: 41vh !important; } /* adjust dropdown size to make them easier to select individual items on mobile. */ .gradio-dropdown ul.options li.item {height: 40px !important; display: flex; align-items: center;} - - /* adjust slider input fields as they were too large for mobile devices. */ - .gradio-slider input[type="number"] { width: 4em; font-size: 0.8rem; height: 16px; text-align: center; } + .gradio-slider input[type="number"] { width: 4em; font-size: 0.8rem; height: 16px; text-align: center; } /* adjust slider input fields as they were too large for mobile devices. */ #txt2img_settings .block .padded:not(.gradio-accordion) {padding: 0 !important;margin-right: 0; min-width: 100% !important; width:100% !important;} - } - + } } diff --git a/modules/shared.py b/modules/shared.py index dac16eee5..3b9e4fbd5 100644 --- a/modules/shared.py +++ b/modules/shared.py @@ -385,6 +385,7 @@ def default(obj): "styles_dir": OptionInfo(os.path.join(paths.data_path, 'styles.csv'), "File or Folder with user-defined styles", folder=True), "embeddings_dir": OptionInfo(os.path.join(paths.models_path, 'embeddings'), "Folder with textual inversion embeddings", folder=True), "hypernetwork_dir": OptionInfo(os.path.join(paths.models_path, 'hypernetworks'), "Folder with Hypernetwork models", folder=True), + "control_dir": OptionInfo(os.path.join(paths.models_path, 'control'), "Folder with Control models", folder=True), "codeformer_models_path": OptionInfo(os.path.join(paths.models_path, 'Codeformer'), "Folder with codeformer models", folder=True), "gfpgan_models_path": OptionInfo(os.path.join(paths.models_path, 'GFPGAN'), "Folder with GFPGAN models", folder=True), "esrgan_models_path": OptionInfo(os.path.join(paths.models_path, 'ESRGAN'), "Folder with ESRGAN models", folder=True), diff --git a/modules/ui.py b/modules/ui.py index 39aaaf9b1..e2ad98144 100644 --- a/modules/ui.py +++ b/modules/ui.py @@ -44,6 +44,8 @@ extra_networks_symbol = symbols.networks apply_style_symbol = symbols.apply save_style_symbol = symbols.save +txt2img_paste_fields = [] +img2img_paste_fields = [] if not cmd_opts.share and not cmd_opts.listen: @@ -234,8 +236,9 @@ def update_token_counter(text, steps): return f"{token_count}/{max_length}" -def create_toprow(is_img2img): - id_part = "img2img" if is_img2img else "txt2img" +def create_toprow(is_img2img: bool = False, id_part: str = None): + if id_part is None: + id_part = "img2img" if is_img2img else "txt2img" with gr.Row(elem_id=f"{id_part}_toprow", variant="compact"): with gr.Column(elem_id=f"{id_part}_prompt_container", scale=6): with gr.Row(): @@ -525,6 +528,7 @@ def create_ui(startup_timer = None): batch_switch_btn.click(lambda w, h: (h, w), inputs=[batch_count, batch_size], outputs=[batch_count, batch_size], show_progress=False) txt_prompt_img.change(fn=modules.images.image_data, inputs=[txt_prompt_img], outputs=[txt2img_prompt, txt_prompt_img]) + global txt2img_paste_fields # pylint: disable=global-statement txt2img_paste_fields = [ # prompt (txt2img_prompt, "Prompt"), @@ -857,6 +861,7 @@ def select_img2img_tab(tab): negative_token_button.click(fn=wrap_queued_call(update_token_counter), inputs=[img2img_negative_prompt, steps], outputs=[negative_token_counter]) ui_extra_networks.setup_ui(extra_networks_ui_img2img, img2img_gallery) + global img2img_paste_fields # pylint: disable=global-statement img2img_paste_fields = [ # prompt (img2img_prompt, "Prompt"), diff --git a/modules/ui_symbols.py b/modules/ui_symbols.py index 0e80508ec..509df747b 100644 --- a/modules/ui_symbols.py +++ b/modules/ui_symbols.py @@ -18,6 +18,9 @@ random = '🎲️' reuse = '♻️' info = 'ℹ' # noqa +reset = '🔄' +upload = '⬆️' +preview = '🔍' """ refresh = '🔄' close = '🛗' diff --git a/wiki b/wiki index 8bdd1dfb1..240a7339c 160000 --- a/wiki +++ b/wiki @@ -1 +1 @@ -Subproject commit 8bdd1dfb154a1dd4f1cbafd85a60a5135b478bb9 +Subproject commit 240a7339ca1199c7735e279e7ea4cdbac0163ec6 From c5494e0b957aa2d4862bc077c984295cf5036657 Mon Sep 17 00:00:00 2001 From: Vladimir Mandic Date: Tue, 12 Dec 2023 15:55:41 -0500 Subject: [PATCH 048/143] add cff --- CITATION.cff | 28 ++++++++++++++++++++++++++++ 1 file changed, 28 insertions(+) create mode 100644 CITATION.cff diff --git a/CITATION.cff b/CITATION.cff new file mode 100644 index 000000000..f7fd4bba3 --- /dev/null +++ b/CITATION.cff @@ -0,0 +1,28 @@ +cff-version: 1.2.0 +title: SD.Next +url: 'https://github.com/vladmandic/automatic' +message: >- + If you use this software, please cite it using the + metadata from this file +type: software +authors: + - given-names: Vladimir + name-particle: Vlado + family-names: Mandic + orcid: 'https://orcid.org/0009-0003-4592-5074' +identifiers: + - type: url + value: 'https://github.com/vladmandic' + description: GitHub + - type: url + value: 'https://www.linkedin.com/in/cyan051/' + description: LinkedIn +repository-code: 'https://github.com/vladmandic/automatic' +abstract: >- + SD.Next: Advanced Implementation of Stable Diffusion and + other diffusion models for text, image and video + generation +keywords: + - stablediffusion diffusers sdnext +license: AGPL-3.0 +date-released: 2022-12-24 From 9e9d31f1b7214da7ddb3d473783f929486401fa5 Mon Sep 17 00:00:00 2001 From: Vladimir Mandic Date: Wed, 13 Dec 2023 08:34:42 -0500 Subject: [PATCH 049/143] fix save params.txt --- CHANGELOG.md | 1 + modules/images.py | 12 +++++------- modules/ui_extra_networks.py | 20 ++++++++++++-------- 3 files changed, 18 insertions(+), 15 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index cebe52296..e6e6f255f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -51,6 +51,7 @@ - do not set preview samples when using via api - avoid unnecessary resizes in img2img and inpaint - updated `cli/simple-txt2img.py` and `cli/simple-img2img.py` scripts + - save `params.txt` regardless of image save status - update built-in log monitor in ui, thanks @midcoastal ## Update for 2023-12-04 diff --git a/modules/images.py b/modules/images.py index 23708ccd7..2e467105c 100644 --- a/modules/images.py +++ b/modules/images.py @@ -1,4 +1,3 @@ -import datetime import io import re import os @@ -6,13 +5,13 @@ import math import json import uuid +import queue import string import hashlib -import queue +import datetime import threading from pathlib import Path from collections import namedtuple -import pytz import numpy as np import piexif import piexif.helper @@ -393,6 +392,7 @@ def prompt_no_style(self): return self.prompt_sanitize(prompt_no_style) def datetime(self, *args): + import pytz time_datetime = datetime.datetime.now() time_format = args[0] if len(args) > 0 and args[0] != "" else self.default_time_format try: @@ -507,6 +507,8 @@ def atomically_save_image(): Image.MAX_IMAGE_PIXELS = None # disable check in Pillow and rely on check below to allow large custom image sizes while True: image, filename, extension, params, exifinfo, filename_txt = save_queue.get() + with open(os.path.join(paths.data_path, "params.txt"), "w", encoding="utf8") as file: + file.write(exifinfo) fn = filename + extension filename = filename.strip() if extension[0] != '.': # add dot if missing @@ -562,8 +564,6 @@ def atomically_save_image(): image.save(fn, format=image_format, quality=shared.opts.jpeg_quality) except Exception as e: shared.log.error(f'Image save failed: file="{fn}" {e}') - with open(os.path.join(paths.data_path, "params.txt"), "w", encoding="utf8") as file: - file.write(exifinfo) if shared.opts.save_log_fn != '' and len(exifinfo) > 0: fn = os.path.join(paths.data_path, shared.opts.save_log_fn) if not fn.endswith('.json'): @@ -575,8 +575,6 @@ def atomically_save_image(): entry = { 'id': idx, 'filename': filename, 'time': datetime.datetime.now().isoformat(), 'info': exifinfo } entries.append(entry) shared.writefile(entries, fn, mode='w') - with open(os.path.join(paths.data_path, "params.txt"), "w", encoding="utf8") as file: - file.write(exifinfo) save_queue.task_done() diff --git a/modules/ui_extra_networks.py b/modules/ui_extra_networks.py index f424402e2..f4a0945a0 100644 --- a/modules/ui_extra_networks.py +++ b/modules/ui_extra_networks.py @@ -797,21 +797,25 @@ def ui_quicksave_click(name): prompt = '' params = generation_parameters_copypaste.parse_generation_parameters(prompt) fn = os.path.join(shared.opts.styles_dir, os.path.splitext(name)[0] + '.json') + prompt = params.get('Prompt', '') item = { - "type": 'Style', "name": name, - "title": name, - "filename": fn, - "search_term": None, - "preview": None, "description": '', - "prompt": params.get('Prompt', ''), + "prompt": prompt, "negative": params.get('Negative prompt', ''), "extra": '', - "local_preview": None, + # "type": 'Style', + # "title": name, + # "filename": fn, + # "search_term": None, + # "preview": None, + # "local_preview": None, } shared.writefile(item, fn, silent=True) - shared.log.debug(f"Extra network quick save style: item={item['name']} filename='{fn}'") + if len(prompt) > 0: + shared.log.debug(f"Extra network quick save style: item={name} filename='{fn}'") + else: + shared.log.warning(f"Extra network quick save model: item={name} filename='{fn}' prompt is empty") def ui_sort_cards(msg): shared.log.debug(f'Extra networks: {msg}') From 68206fbc0583c283b5448ce6b13f92a10b6db3a3 Mon Sep 17 00:00:00 2001 From: Disty0 Date: Wed, 13 Dec 2023 18:19:08 +0300 Subject: [PATCH 050/143] IPEX update to PyTorch 2.1 wheels for Linux --- CHANGELOG.md | 1 + installer.py | 2 +- modules/intel/ipex/__init__.py | 6 ------ modules/intel/ipex/hijacks.py | 8 ++++---- 4 files changed, 6 insertions(+), 11 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index e6e6f255f..c8ca24915 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -37,6 +37,7 @@ - **Extra networks** new *settting -> extra networks -> build info on first access* indexes all networks on first access instead of server startup - **IPEX** + - Update to **Torch 2.1 - This Update Requires Intel OneApi 2024.0**, thanks @disty0 - Fix IPEX Optimize not applying with Diffusers backend, thanks @disty0 - Disable 32 bit workarounds if the GPU supports 64 bit, thanks @disty0 - More compatibility improvements, thanks @disty0 diff --git a/installer.py b/installer.py index e9d8e20c7..bb957f9b4 100644 --- a/installer.py +++ b/installer.py @@ -424,7 +424,7 @@ def check_torch(): os.environ.setdefault('NEOReadDebugKeys', '1') os.environ.setdefault('ClDeviceGlobalMemSizeAvailablePercent', '100') if "linux" in sys.platform: - torch_command = os.environ.get('TORCH_COMMAND', 'torch==2.0.1a0 torchvision==0.15.2a0 intel_extension_for_pytorch==2.0.110+xpu --extra-index-url https://pytorch-extension.intel.com/release-whl/stable/xpu/us/') + torch_command = os.environ.get('TORCH_COMMAND', 'torch==2.1.0a0 torchvision==0.16.0a0 intel-extension-for-pytorch==2.1.10+xpu --extra-index-url https://pytorch-extension.intel.com/release-whl/stable/xpu/us/') os.environ.setdefault('TENSORFLOW_PACKAGE', 'tensorflow==2.13.0 intel-extension-for-tensorflow[gpu]') else: pytorch_pip = 'https://github.com/Nuullll/intel-extension-for-pytorch/releases/download/v2.0.110%2Bxpu-master%2Bdll-bundle/torch-2.0.0a0+gite9ebda2-cp310-cp310-win_amd64.whl' diff --git a/modules/intel/ipex/__init__.py b/modules/intel/ipex/__init__.py index 0eecb8ff3..c78547915 100644 --- a/modules/intel/ipex/__init__.py +++ b/modules/intel/ipex/__init__.py @@ -156,12 +156,6 @@ def ipex_init(): # pylint: disable=too-many-statements torch.cuda.get_device_properties.minor = 7 torch.cuda.ipc_collect = lambda *args, **kwargs: None torch.cuda.utilization = lambda *args, **kwargs: 0 - if hasattr(torch.xpu, 'getDeviceIdListForCard'): - torch.cuda.getDeviceIdListForCard = torch.xpu.getDeviceIdListForCard - torch.cuda.get_device_id_list_per_card = torch.xpu.getDeviceIdListForCard - else: - torch.cuda.getDeviceIdListForCard = torch.xpu.get_device_id_list_per_card - torch.cuda.get_device_id_list_per_card = torch.xpu.get_device_id_list_per_card ipex_hijacks() if not torch.xpu.has_fp64_dtype(): diff --git a/modules/intel/ipex/hijacks.py b/modules/intel/ipex/hijacks.py index a03cdf626..554fe320b 100644 --- a/modules/intel/ipex/hijacks.py +++ b/modules/intel/ipex/hijacks.py @@ -157,10 +157,10 @@ def ipex_hijacks(): lambda orig_func, f, map_location=None, pickle_module=None, *, weights_only=False, mmap=None, **kwargs: orig_func(orig_func, f, map_location=return_xpu(map_location), pickle_module=pickle_module, weights_only=weights_only, mmap=mmap, **kwargs), lambda orig_func, f, map_location=None, pickle_module=None, *, weights_only=False, mmap=None, **kwargs: check_device(map_location)) - - CondFunc('torch.Generator', - lambda orig_func, device=None: torch.xpu.Generator(return_xpu(device)), - lambda orig_func, device=None: device is not None and device != torch.device("cpu") and device != "cpu") + if hasattr(torch.xpu, "Generator"): + CondFunc('torch.Generator', + lambda orig_func, device=None: torch.xpu.Generator(return_xpu(device)), + lambda orig_func, device=None: device is not None and device != torch.device("cpu") and device != "cpu") # TiledVAE and ControlNet: CondFunc('torch.batch_norm', From 72839b7d65a8f5255a755357413e2b86265a6580 Mon Sep 17 00:00:00 2001 From: Vladimir Mandic Date: Wed, 13 Dec 2023 15:48:07 -0500 Subject: [PATCH 051/143] refactoring to support control --- extensions-builtin/sd-webui-controlnet | 2 +- .../stable-diffusion-webui-images-browser | 2 +- javascript/amethyst-nightfall.css | 3 - javascript/black-orange.css | 3 - javascript/black-teal.css | 4 - javascript/control.js | 7 + javascript/extraNetworks.js | 17 ++- javascript/invoked.css | 3 - javascript/light-teal.css | 3 - javascript/midnight-barbie.css | 4 - javascript/promptChecker.js | 2 + javascript/sdnext.css | 79 ++++++------ javascript/ui.js | 2 + modules/processing.py | 11 ++ modules/processing_diffusers.py | 12 +- modules/sd_models.py | 12 ++ modules/ui.py | 121 +++++++++--------- requirements.txt | 2 +- scripts/example.py | 7 +- 19 files changed, 168 insertions(+), 128 deletions(-) create mode 100644 javascript/control.js diff --git a/extensions-builtin/sd-webui-controlnet b/extensions-builtin/sd-webui-controlnet index 0d0841671..a13bd2feb 160000 --- a/extensions-builtin/sd-webui-controlnet +++ b/extensions-builtin/sd-webui-controlnet @@ -1 +1 @@ -Subproject commit 0d0841671ef165d6d408f7b9cd6bfb1dda3d78cf +Subproject commit a13bd2febe4fae1184b548504957d19a65425a89 diff --git a/extensions-builtin/stable-diffusion-webui-images-browser b/extensions-builtin/stable-diffusion-webui-images-browser index 730187753..27fe4a713 160000 --- a/extensions-builtin/stable-diffusion-webui-images-browser +++ b/extensions-builtin/stable-diffusion-webui-images-browser @@ -1 +1 @@ -Subproject commit 7301877532180b621207f3580a212cf008d621ca +Subproject commit 27fe4a713d883436049ed1ed9e1642f8eb4fd924 diff --git a/javascript/amethyst-nightfall.css b/javascript/amethyst-nightfall.css index b36ae8160..ea544dcc4 100644 --- a/javascript/amethyst-nightfall.css +++ b/javascript/amethyst-nightfall.css @@ -101,9 +101,6 @@ svg.feather.feather-image, .feather .feather-image { display: none } #txt2img_checkboxes, #img2img_checkboxes { background-color: transparent; } #txt2img_checkboxes, #img2img_checkboxes { margin-bottom: 0.2em; } #txt2img_gallery, #img2img_gallery, #extras_gallery { padding: 0; margin: 0; object-fit: contain; box-shadow: none; min-height: 0; } -#txt2img_actions_column, #img2img_actions_column { flex-flow: wrap; justify-content: space-between; } -#txt2img_enqueue_wrapper, #img2img_enqueue_wrapper { min-width: unset; width: 48%; } -#txt2img_generate_box, #img2img_generate_box { min-width: unset; width: 48%; } #extras_upscale { margin-top: 10px } #txt2img_progress_row > div { min-width: var(--left-column); max-width: var(--left-column); } diff --git a/javascript/black-orange.css b/javascript/black-orange.css index b35267daa..90e1cd8bd 100644 --- a/javascript/black-orange.css +++ b/javascript/black-orange.css @@ -117,9 +117,6 @@ svg.feather.feather-image, .feather .feather-image { display: none } #txt2img_cfg_scale { min-width: 200px; } #txt2img_checkboxes, #img2img_checkboxes { background-color: transparent; } #txt2img_checkboxes, #img2img_checkboxes { margin-bottom: 0.2em; } -#txt2img_actions_column, #img2img_actions_column { flex-flow: wrap; justify-content: space-between; } -#txt2img_enqueue_wrapper, #img2img_enqueue_wrapper { min-width: unset; width: 48%; } -#txt2img_generate_box, #img2img_generate_box { min-width: unset; width: 48%; } #extras_upscale { margin-top: 10px } #txt2img_progress_row > div { min-width: var(--left-column); max-width: var(--left-column); } diff --git a/javascript/black-teal.css b/javascript/black-teal.css index d5454360c..07624a76e 100644 --- a/javascript/black-teal.css +++ b/javascript/black-teal.css @@ -118,11 +118,7 @@ svg.feather.feather-image, .feather .feather-image { display: none } #txt2img_cfg_scale { min-width: 200px; } #txt2img_checkboxes, #img2img_checkboxes { background-color: transparent; } #txt2img_checkboxes, #img2img_checkboxes { margin-bottom: 0.2em; } -#txt2img_actions_column, #img2img_actions_column { flex-flow: wrap; justify-content: space-between; } -#txt2img_enqueue_wrapper, #img2img_enqueue_wrapper { min-width: unset; width: 48%; } -#txt2img_generate_box, #img2img_generate_box { min-width: unset; width: 48%; } textarea[rows="1"] { height: 33px !important; width: 99% !important; padding: 8px !important; } - #extras_upscale { margin-top: 10px } #txt2img_progress_row > div { min-width: var(--left-column); max-width: var(--left-column); } #txt2img_settings { min-width: var(--left-column); max-width: var(--left-column); background-color: #111111; padding-top: 16px; } diff --git a/javascript/control.js b/javascript/control.js new file mode 100644 index 000000000..4d3afcc9e --- /dev/null +++ b/javascript/control.js @@ -0,0 +1,7 @@ +const getControlActiveTab = (...args) => { + const selectedTab = gradioApp().querySelector('#control-tabs > .tab-nav > .selected'); + let activeTab = ''; + if (selectedTab) activeTab = selectedTab.innerText.toLowerCase(); + args.shift(); + return [activeTab, ...args]; +}; diff --git a/javascript/extraNetworks.js b/javascript/extraNetworks.js index 075b10f80..ba5793f9c 100644 --- a/javascript/extraNetworks.js +++ b/javascript/extraNetworks.js @@ -16,7 +16,12 @@ const requestGet = (url, data, handler) => { xhr.send(JSON.stringify(data)); }; -const getENActiveTab = () => gradioApp().getElementById('tab_txt2img').style.display === 'block' ? 'txt2img' : 'img2img'; +const getENActiveTab = () => { + if (gradioApp().getElementById('tab_txt2img').style.display === 'block') return 'txt2img'; + if (gradioApp().getElementById('tab_img2img').style.display === 'block') return 'img2img'; + if (gradioApp().getElementById('tab_control').style.display === 'block') return 'control'; + return ''; +}; const getENActivePage = () => { const tabname = getENActiveTab(); @@ -276,9 +281,11 @@ function refreshENpage() { // init function setupExtraNetworksForTab(tabname) { - gradioApp().querySelector(`#${tabname}_extra_tabs`).classList.add('extra-networks'); + let tabs = gradioApp().querySelector(`#${tabname}_extra_tabs`); + if (tabs) tabs.classList.add('extra-networks'); const en = gradioApp().getElementById(`${tabname}_extra_networks`); - const tabs = gradioApp().querySelector(`#${tabname}_extra_tabs > div`); + tabs = gradioApp().querySelector(`#${tabname}_extra_tabs > div`); + if (!tabs) return; // buttons const btnRefresh = gradioApp().getElementById(`${tabname}_extra_refresh`); @@ -394,9 +401,11 @@ function setupExtraNetworksForTab(tabname) { function setupExtraNetworks() { setupExtraNetworksForTab('txt2img'); setupExtraNetworksForTab('img2img'); + setupExtraNetworksForTab('control'); function registerPrompt(tabname, id) { const textarea = gradioApp().querySelector(`#${id} > label > textarea`); + if (!textarea) return; if (!activePromptTextarea[tabname]) activePromptTextarea[tabname] = textarea; textarea.addEventListener('focus', () => { activePromptTextarea[tabname] = textarea; }); } @@ -405,6 +414,8 @@ function setupExtraNetworks() { registerPrompt('txt2img', 'txt2img_neg_prompt'); registerPrompt('img2img', 'img2img_prompt'); registerPrompt('img2img', 'img2img_neg_prompt'); + registerPrompt('control', 'control_prompt'); + registerPrompt('control', 'control_neg_prompt'); log('initExtraNetworks'); } diff --git a/javascript/invoked.css b/javascript/invoked.css index ab5b48c06..d93f04ccf 100644 --- a/javascript/invoked.css +++ b/javascript/invoked.css @@ -113,9 +113,6 @@ button.selected {background: var(--button-primary-background-fill);} #txt2img_cfg_scale { min-width: 200px; } #txt2img_checkboxes, #img2img_checkboxes { background-color: transparent; } #txt2img_checkboxes, #img2img_checkboxes { margin-bottom: 0.2em; } -#txt2img_actions_column, #img2img_actions_column { flex-flow: wrap; justify-content: space-between; } -#txt2img_enqueue_wrapper, #img2img_enqueue_wrapper { min-width: unset; width: 48%; } -#txt2img_generate_box, #img2img_generate_box { min-width: unset; width: 48%; } #extras_upscale { margin-top: 10px } #txt2img_progress_row > div { min-width: var(--left-column); max-width: var(--left-column); } diff --git a/javascript/light-teal.css b/javascript/light-teal.css index 5b1aa3108..4829791f9 100644 --- a/javascript/light-teal.css +++ b/javascript/light-teal.css @@ -115,9 +115,6 @@ svg.feather.feather-image, .feather .feather-image { display: none } #txt2img_cfg_scale { min-width: 200px; } #txt2img_checkboxes, #img2img_checkboxes { background-color: transparent; } #txt2img_checkboxes, #img2img_checkboxes { margin-bottom: 0.2em; } -#txt2img_actions_column, #img2img_actions_column { flex-flow: wrap; justify-content: space-between; } -#txt2img_enqueue_wrapper, #img2img_enqueue_wrapper { min-width: unset; width: 48%; } -#txt2img_generate_box, #img2img_generate_box { min-width: unset; width: 48%; } #extras_upscale { margin-top: 10px } #txt2img_progress_row > div { min-width: var(--left-column); max-width: var(--left-column); } diff --git a/javascript/midnight-barbie.css b/javascript/midnight-barbie.css index 8f47fe262..d988a1275 100644 --- a/javascript/midnight-barbie.css +++ b/javascript/midnight-barbie.css @@ -106,10 +106,6 @@ svg.feather.feather-image, .feather .feather-image { display: none } #txt2img_cfg_scale { min-width: 200px; } #txt2img_checkboxes, #img2img_checkboxes { background-color: transparent; } #txt2img_checkboxes, #img2img_checkboxes { margin-bottom: 0.2em; } -#txt2img_actions_column, #img2img_actions_column { flex-flow: wrap; justify-content: space-between; } -#txt2img_enqueue_wrapper, #img2img_enqueue_wrapper { min-width: unset; width: 48%; } -#txt2img_generate_box, #img2img_generate_box { min-width: unset; width: 48%; } - #extras_upscale { margin-top: 10px } #txt2img_progress_row > div { min-width: var(--left-column); max-width: var(--left-column); } #txt2img_settings { min-width: var(--left-column); max-width: var(--left-column); background-color: #111111; padding-top: 16px; } diff --git a/javascript/promptChecker.js b/javascript/promptChecker.js index d18a030ec..284482205 100644 --- a/javascript/promptChecker.js +++ b/javascript/promptChecker.js @@ -36,4 +36,6 @@ onAfterUiUpdate(() => { setupBracketChecking('txt2img_neg_prompt', 'txt2img_negative_token_counter'); setupBracketChecking('img2img_prompt', 'img2img_token_counter'); setupBracketChecking('img2img_neg_prompt', 'img2img_negative_token_counter'); + setupBracketChecking('control_prompt', 'control_token_counter'); + setupBracketChecking('control_neg_prompt', 'control_negative_token_counter'); }); diff --git a/javascript/sdnext.css b/javascript/sdnext.css index e02a387fd..43a4f159c 100644 --- a/javascript/sdnext.css +++ b/javascript/sdnext.css @@ -48,7 +48,7 @@ textarea { overflow-y: auto !important; } /* custom gradio elements */ .accordion-compact { padding: 8px 0px 4px 0px !important; } -.settings-accordion > div { flex-flow: wrap; } +.settings-accordion>div { flex-flow: wrap; } .small-accordion .form { min-width: var(--left-column) !important; max-width: max-content; } .small-accordion .label-wrap .icon { margin-right: 1.6em; margin-left: 0.6em; color: var(--button-primary-border-color); } .small-accordion .label-wrap { padding: 16px 0px 8px 0px; margin: 0; border-top: 2px solid var(--button-secondary-border-color); } @@ -70,18 +70,21 @@ button.custom-button{ border-radius: var(--button-large-radius); padding: var(-- .performance { font-size: 0.85em; color: #444; } .performance p { display: inline-block; color: var(--body-text-color-subdued) !important } .performance .time { margin-right: 0; } -#txt2img_prompt_container, #img2img_prompt_container { margin-right: var(--layout-gap) } +#txt2img_prompt_container, #img2img_prompt_container, #control_prompt_container { margin-right: var(--layout-gap) } #txt2img_footer, #img2img_footer, #extras_footer { height: fit-content; } #txt2img_footer, #img2img_footer { height: fit-content; display: none; } -#txt2img_generate_box, #img2img_generate_box { gap: 0.5em; flex-wrap: wrap-reverse; height: fit-content; } -#txt2img_actions_column, #img2img_actions_column { gap: 0.3em; height: fit-content; } -#txt2img_generate_box > button, #img2img_generate_box > button, #txt2img_enqueue, #img2img_enqueue { min-height: 42px; max-height: 42px; line-height: 1em; } -#txt2img_generate_line2, #img2img_generate_line2, #txt2img_tools, #img2img_tools { display: flex; } -#txt2img_generate_line2 > button, #img2img_generate_line2 > button, #extras_generate_box > button, #txt2img_tools > button, #img2img_tools > button { height: 2em; line-height: 0; font-size: var(--input-text-size); +#txt2img_generate_box, #img2img_generate_box, #control_general_box { gap: 0.5em; flex-wrap: wrap-reverse; height: fit-content; } +#txt2img_actions_column, #img2img_actions_column, #control_actions_column { gap: 0.3em; height: fit-content; } +#txt2img_generate_box>button, #img2img_generate_box>button, #control_generate_box>button, #txt2img_enqueue, #img2img_enqueue { min-height: 42px; max-height: 42px; line-height: 1em; } +#txt2img_generate_line2, #img2img_generate_line2, #txt2img_tools, #img2img_tools, #control_generate_line2, #control_tools { display: flex; } +#txt2img_generate_line2>button, #img2img_generate_line2>button, #extras_generate_box>button, #control_generate_line2>button, #txt2img_tools>button, #img2img_tools>button, #control_tools>button { height: 2em; line-height: 0; font-size: var(--input-text-size); min-width: unset; display: block !important; } -#txt2img_prompt, #txt2img_neg_prompt, #img2img_prompt, #img2img_neg_prompt { display: contents; } +#txt2img_prompt, #txt2img_neg_prompt, #img2img_prompt, #img2img_neg_prompt, #control_prompt, #control_neg_prompt { display: contents; } +#txt2img_generate_box, #img2img_generate_box, #control_generate_box { min-width: unset; width: 48%; } +#txt2img_actions_column, #img2img_actions_column, #control_actions { flex-flow: wrap; justify-content: space-between; } +#txt2img_enqueue_wrapper, #img2img_enqueue_wrapper, #control_enqueue_wrapper { min-width: unset !important; width: 48%; } .interrogate-col{ min-width: 0 !important; max-width: fit-content; margin-right: var(--spacing-xxl); } -.interrogate-col > button{ flex: 1; } +.interrogate-col>button{ flex: 1; } #sampler_selection_img2img { margin-top: 1em; } #txtimg_hr_finalres{ min-height: 0 !important; } #img2img_scale_resolution_preview.block{ display: flex; align-items: end; } @@ -91,11 +94,11 @@ div#extras_scale_to_tab div.form{ flex-direction: row; } .inactive{ opacity: 0.5; } div.dimensions-tools { min-width: 0 !important; max-width: fit-content; flex-direction: row; align-content: center; } div#extras_scale_to_tab div.form{ flex-direction: row; } -#mode_img2img .gradio-image > div.fixed-height, #mode_img2img .gradio-image > div.fixed-height img{ height: 480px !important; max-height: 480px !important; min-height: 480px !important; } +#mode_img2img .gradio-image>div.fixed-height, #mode_img2img .gradio-image>div.fixed-height img{ height: 480px !important; max-height: 480px !important; min-height: 480px !important; } #img2img_sketch, #img2maskimg, #inpaint_sketch { overflow: overlay !important; resize: auto; background: var(--panel-background-fill); z-index: 5; } .image-buttons button{ min-width: auto; } .infotext { overflow-wrap: break-word; line-height: 1.5em; } -.infotext > p { padding-left: 1em; text-indent: -1em; white-space: pre-wrap; } +.infotext>p { padding-left: 1em; text-indent: -1em; white-space: pre-wrap; } .tooltip { display: block; position: fixed; top: 1em; right: 1em; padding: 0.5em; background: var(--input-background-fill); color: var(--body-text-color); border: 1pt solid var(--button-primary-border-color); width: 22em; min-height: 1.3em; font-size: 0.8em; transition: opacity 0.2s ease-in; pointer-events: none; opacity: 0; z-index: 999; } .tooltip-show { opacity: 0.9; } @@ -104,15 +107,15 @@ div#extras_scale_to_tab div.form{ flex-direction: row; } /* settings */ #si-sparkline-memo, #si-sparkline-load { background-color: #111; } #quicksettings { width: fit-content; } -#quicksettings > button { padding: 0 1em 0 0; align-self: end; margin-bottom: var(--text-sm); } +#quicksettings>button { padding: 0 1em 0 0; align-self: end; margin-bottom: var(--text-sm); } #settings { display: flex; gap: var(--layout-gap); } #settings div { border: none; gap: 0; margin: 0 0 var(--layout-gap) 0px; padding: 0; } -#settings > div.tab-content { flex: 10 0 75%; display: grid; } -#settings > div.tab-content > div { border: none; padding: 0; } -#settings > div.tab-content > div > div > div > div > div { flex-direction: unset; } -#settings > div.tab-nav { display: grid; grid-template-columns: repeat(auto-fill, .5em minmax(10em, 1fr)); flex: 1 0 auto; width: 12em; align-self: flex-start; gap: var(--spacing-xxl); } -#settings > div.tab-nav button { display: block; border: none; text-align: left; white-space: initial; padding: 0; } -#settings > div.tab-nav > #settings_show_all_pages { padding: var(--size-2) var(--size-4); } +#settings>div.tab-content { flex: 10 0 75%; display: grid; } +#settings>div.tab-content>div { border: none; padding: 0; } +#settings>div.tab-content>div>div>div>div>div { flex-direction: unset; } +#settings>div.tab-nav { display: grid; grid-template-columns: repeat(auto-fill, .5em minmax(10em, 1fr)); flex: 1 0 auto; width: 12em; align-self: flex-start; gap: var(--spacing-xxl); } +#settings>div.tab-nav button { display: block; border: none; text-align: left; white-space: initial; padding: 0; } +#settings>div.tab-nav>#settings_show_all_pages { padding: var(--size-2) var(--size-4); } #settings .block.gradio-checkbox { margin: 0; width: auto; } #settings .dirtyable { gap: .5em; } #settings .dirtyable.hidden { display: none; } @@ -146,8 +149,8 @@ div#extras_scale_to_tab div.form{ flex-direction: row; } .modalControls span:hover, .modalControls span:focus { color: var(--highlight-color); filter: none; } .lightboxModalPreviewZone { display: flex; width: 100%; height: 100%; } .lightboxModalPreviewZone:focus-visible { outline: none; } -.lightboxModalPreviewZone > img { display: block; margin: auto; width: auto; } -.lightboxModalPreviewZone > img.modalImageFullscreen{ object-fit: contain; height: 100%; width: 100%; min-height: 0; background: transparent; } +.lightboxModalPreviewZone>img { display: block; margin: auto; width: auto; } +.lightboxModalPreviewZone>img.modalImageFullscreen{ object-fit: contain; height: 100%; width: 100%; min-height: 0; background: transparent; } table.settings-value-table { background: white; border-collapse: collapse; margin: 1em; border: var(--spacing-sm) solid white; } table.settings-value-table td { padding: 0.4em; border: 1px solid #ccc; max-width: 36em; } .modalPrev, .modalNext { cursor: pointer; position: relative; z-index: 1; top: 0; width: auto; height: 100vh; line-height: 100vh; text-align: center; padding: 16px; @@ -178,13 +181,13 @@ table.settings-value-table td { padding: 0.4em; border: 1px solid #ccc; max-widt #extensions .date{ opacity: 0.85; font-size: 90%; } /* extra networks */ -.extra-networks > div { margin: 0; border-bottom: none !important; gap: 0.3em 0; } +.extra-networks>div { margin: 0; border-bottom: none !important; gap: 0.3em 0; } .extra-networks .second-line { display: flex; width: -moz-available; width: -webkit-fill-available; gap: 0.3em; box-shadow: var(--input-shadow); } .extra-networks .search { flex: 1; } .extra-networks .description { flex: 3; } -.extra-networks .tab-nav > button { margin-right: 0; height: 24px; padding: 2px 4px 2px 4px; } +.extra-networks .tab-nav>button { margin-right: 0; height: 24px; padding: 2px 4px 2px 4px; } .extra-networks .buttons { position: absolute; right: 0; margin: -4px; background: var(--background-color); } -.extra-networks .buttons > button { margin-left: -0.2em; height: 1.4em; color: var(--primary-300) !important; } +.extra-networks .buttons>button { margin-left: -0.2em; height: 1.4em; color: var(--primary-300) !important; } .extra-networks .custom-button { width: 120px; width: 100%; background: none; justify-content: left; text-align: left; padding: 3px 3px 3px 12px; text-indent: -6px; box-shadow: none; line-break: auto; } .extra-networks .custom-button:hover { background: var(--button-primary-background-fill) } .extra-networks-tab { padding: 0 !important; } @@ -200,18 +203,18 @@ table.settings-value-table td { padding: 0.4em; border: 1px solid #ccc; max-widt .extra-network-cards .card:hover .overlay { background: rgba(0, 0, 0, 0.40); } .extra-network-cards .card .overlay .tags { display: none; overflow-wrap: break-word; } .extra-network-cards .card .overlay .tag { padding: 2px; margin: 2px; background: rgba(70, 70, 70, 0.60); font-size: var(--text-md); cursor: pointer; display: inline-block; } -.extra-network-cards .card .actions > span { padding: 4px; } -.extra-network-cards .card .actions > span:hover { color: var(--highlight-color); } +.extra-network-cards .card .actions>span { padding: 4px; } +.extra-network-cards .card .actions>span:hover { color: var(--highlight-color); } .extra-network-cards .card:hover .actions { display: block; } .extra-network-cards .card:hover .overlay .tags { display: block; } .extra-network-cards .card .actions { font-size: 3em; display: none; text-align-last: right; cursor: pointer; font-variant: unicase; position: absolute; z-index: 100; right: 0; height: 0.7em; width: 100%; background: rgba(0, 0, 0, 0.40); } .extra-network-cards .card-list { display: flex; margin: 0.3em; padding: 0.3em; background: var(--input-background-fill); cursor: pointer; border-radius: var(--button-large-radius); } .extra-network-cards .card-list .tag { color: var(--primary-500); margin-left: 0.8em; } .extra-details-close { position: fixed; top: 0.2em; right: 0.2em; z-index: 99; background: var(--button-secondary-background-fill) !important; } -#txt2img_description, #img2img_description { max-height: 63px; overflow-y: auto !important; } -#txt2img_description > label > textarea, #img2img_description > label > textarea { font-size: 0.9em } +#txt2img_description, #img2img_description, #control_description { max-height: 63px; overflow-y: auto !important; } +#txt2img_description>label>textarea, #img2img_description>label>textarea, #control_description>label>textarea { font-size: 0.9em } -#txt2img_extra_details > div, #img2img_extra_details > div { overflow-y: auto; min-height: 40vh; max-height: 80vh; align-self: flex-start; } +#txt2img_extra_details>div, #img2img_extra_details>div { overflow-y: auto; min-height: 40vh; max-height: 80vh; align-self: flex-start; } #txt2img_extra_details, #img2img_extra_details { position: fixed; bottom: 50%; left: 50%; transform: translate(-50%, 50%); padding: 0.8em; border: var(--block-border-width) solid var(--highlight-color) !important; z-index: 100; box-shadow: var(--button-shadow); } #txt2img_extra_details td:first-child, #img2img_extra_details td:first-child { font-weight: bold; vertical-align: top; } @@ -221,14 +224,14 @@ table.settings-value-table td { padding: 0.4em; border: 1px solid #ccc; max-widt /* specific elements */ #modelmerger_interp_description { margin-top: 1em; margin-bottom: 1em; } #scripts_alwayson_txt2img, #scripts_alwayson_img2img { padding: 0 } -#scripts_alwayson_txt2img > .label-wrap, #scripts_alwayson_img2img > .label-wrap { background: var(--input-background-fill); padding: 0; margin: 0; border-radius: var(--radius-lg); } -#scripts_alwayson_txt2img > .label-wrap > span, #scripts_alwayson_img2img > .label-wrap > span { padding: var(--spacing-xxl); } +#scripts_alwayson_txt2img>.label-wrap, #scripts_alwayson_img2img>.label-wrap { background: var(--input-background-fill); padding: 0; margin: 0; border-radius: var(--radius-lg); } +#scripts_alwayson_txt2img>.label-wrap>span, #scripts_alwayson_img2img>.label-wrap>span { padding: var(--spacing-xxl); } #scripts_alwayson_txt2img div { max-width: var(--left-column); } #script_txt2img_agent_scheduler { display: none; } #refresh_tac_refreshTempFiles { display: none; } #train_tab { flex-flow: row-reverse; } #models_tab { flex-flow: row-reverse; } -#swap_axes > button { min-width: 100px; font-size: 1em; } +#swap_axes>button { min-width: 100px; font-size: 1em; } #ui_defaults_review { margin: 1em; } /* extras */ @@ -253,9 +256,9 @@ table.settings-value-table td { padding: 0.4em; border: 1px solid #ccc; max-widt /* control */ .control-button { min-height: 42px; max-height: 42px; line-height: 1em; } -.control-tabs > .tab-nav { margin-bottom: 0; margin-top: 0; } +.control-tabs>.tab-nav { margin-bottom: 0; margin-top: 0; } .processor-settings { padding: 0 !important; max-width: 300px; } -.processor-group > div { flex-flow: wrap;gap: 1em; } +.processor-group>div { flex-flow: wrap;gap: 1em; } /* loader */ .splash { position: fixed; top: 0; left: 0; width: 100vw; height: 100vh; z-index: 1000; display: block; text-align: center; } @@ -298,22 +301,22 @@ table.settings-value-table td { padding: 0.4em; border: 1px solid #ccc; max-widt #txt2img_results, #extras_results, #txt2im g_footer p {text-wrap: wrap; max-width: 100% !important; } /* maintain side by side split on larger mobile displays for from text */ } #scripts_alwayson_txt2img div, #scripts_alwayson_img2img div { max-width: 100%; } - #txt2img_prompt_container, #img2img_prompt_container { resize:vertical !important; } + #txt2img_prompt_container, #img2img_prompt_container, #control_prompt_container { resize:vertical !important; } #txt2img_generate_box, #txt2img_enqueue_wrapper { min-width: 100% !important;} /* make generate and enqueue buttons take up the entire width of their rows. */ - #img2img_toprow > div.gradio-column {flex-grow: 1 !important;} /*make interrogate buttons take up appropriate space. */ + #img2img_toprow>div.gradio-column {flex-grow: 1 !important;} /*make interrogate buttons take up appropriate space. */ #img2img_actions_column {display: flex; min-width: fit-content !important; flex-direction: row;justify-content: space-evenly; align-items: center;} #txt2img_generate_box, #img2img_generate_box, #txt2img_enqueue_wrapper,#img2img_enqueue_wrapper {display: flex;flex-direction: column;height: 4em !important;align-items: stretch;justify-content: space-evenly;} #img2img_interface, #img2img_results, #img2img_footer p {text-wrap: wrap; min-width: 100% !important; max-width: 100% !important;} /* maintain single column for from image operations on larger mobile devices */ #img2img_sketch, #img2maskimg, #inpaint_sketch {display: flex; alignment-baseline:after-edge !important; overflow: auto !important; resize: none !important; } /* fix inpaint image display being too large for mobile displays */ #img2maskimg canvas { width: auto !important; max-height: 100% !important; height: auto !important; } #txt2img_sampler, #txt2img_batch, #txt2img_seed_group, #txt2img_advanced, #txt2img_second_pass, #img2img_sampling_group, #img2img_resize_group, #img2img_batch_group, #img2img_seed_group, #img2img_denoise_group, #img2img_advanced_group { width: 100% !important; } /* fix from text/image UI elements to prevent them from moving around within the UI */ - #img2img_resize_group .gradio-radio > div { display: flex; flex-direction: column; width: unset !important; } + #img2img_resize_group .gradio-radio>div { display: flex; flex-direction: column; width: unset !important; } #inpaint_controls div {display:flex;flex-direction: row;} - #inpaint_controls .gradio-radio > div { display: flex; flex-direction: column !important; } + #inpaint_controls .gradio-radio>div { display: flex; flex-direction: column !important; } #models_tab { flex-direction: column-reverse !important; } /* move image preview/output on models page to bottom of page */ #enqueue_keyboard_shortcut_modifiers, #enqueue_keyboard_shortcut_key div { max-width: 40% !important;} /* fix settings for agent scheduler */ #settings { display: flex; flex-direction: row; flex-wrap: wrap; max-width: 100% !important; } /* adjust width of certain settings item to allow aligning as row, but not have it go off the screen */ - #settings div.tab-content > div > div > div { max-width: 80% !important;} + #settings div.tab-content>div>div>div { max-width: 80% !important;} #settings div .gradio-radio { width: unset !important; } #tab_extensions table { border-collapse: collapse; display: block; overflow-x:auto !important;} /* enable scrolling on extensions tab */ ::-webkit-scrollbar { width: 25px !important; height:25px; } /* increase scrollbar size to make it finger friendly */ diff --git a/javascript/ui.js b/javascript/ui.js index daffbe352..e192c5935 100644 --- a/javascript/ui.js +++ b/javascript/ui.js @@ -279,6 +279,8 @@ onAfterUiUpdate(async () => { registerTextarea('txt2img_neg_prompt', 'txt2img_negative_token_counter', 'txt2img_negative_token_button'); registerTextarea('img2img_prompt', 'img2img_token_counter', 'img2img_token_button'); registerTextarea('img2img_neg_prompt', 'img2img_negative_token_counter', 'img2img_negative_token_button'); + registerTextarea('control_prompt', 'control_token_counter', 'control_token_button'); + registerTextarea('control_neg_prompt', 'control_negative_token_counter', 'control_negative_token_button'); }); function update_txt2img_tokens(...args) { diff --git a/modules/processing.py b/modules/processing.py index 2eca014ce..0bd8788e7 100644 --- a/modules/processing.py +++ b/modules/processing.py @@ -181,8 +181,19 @@ def __init__(self, sd_model=None, outpath_samples=None, outpath_grids=None, prom self.is_refiner_pass = False self.hr_force = False self.enable_hr = None + self.hr_scale = None + self.hr_upscaler = None + self.hr_resize_x = 0 + self.hr_resize_y = 0 + self.hr_upscale_to_x = 0 + self.hr_upscale_to_y = 0 + self.truncate_x = 0 + self.truncate_y = 0 + self.applied_old_hires_behavior_to = None self.refiner_steps = 5 self.refiner_start = 0 + self.refiner_prompt = '' + self.refiner_negative = '' self.ops = [] self.resize_mode: int = 0 self.ddim_discretize = shared.opts.ddim_discretize diff --git a/modules/processing_diffusers.py b/modules/processing_diffusers.py index 512b67314..4c40957d5 100644 --- a/modules/processing_diffusers.py +++ b/modules/processing_diffusers.py @@ -458,8 +458,10 @@ def calculate_base_steps(): if is_img2img: if use_denoise_start and shared.sd_model_type == 'sdxl': steps = p.steps // (1 - p.refiner_start) - else: + elif p.denoising_strength > 0: steps = (p.steps // p.denoising_strength) + 1 + else: + steps = p.steps elif use_refiner_start and shared.sd_model_type == 'sdxl': steps = (p.steps // p.refiner_start) + 1 else: @@ -472,8 +474,10 @@ def calculate_base_steps(): def calculate_hires_steps(): if p.hr_second_pass_steps > 0: steps = (p.hr_second_pass_steps // p.denoising_strength) + 1 - else: + elif p.denoising_strength > 0: steps = (p.steps // p.denoising_strength) + 1 + else: + steps = 0 if os.environ.get('SD_STEPS_DEBUG', None) is not None: shared.log.debug(f'Steps: type=hires input={p.hr_second_pass_steps} output={steps} denoise={p.denoising_strength}') @@ -484,8 +488,10 @@ def calculate_refiner_steps(): if p.refiner_start > 0 and p.refiner_start < 1: #steps = p.refiner_steps // (1 - p.refiner_start) # SDXL with denoise strenght steps = (p.refiner_steps // (1 - p.refiner_start) // 2) + 1 - else: + elif p.denoising_strength > 0: steps = (p.refiner_steps // p.denoising_strength) + 1 + else: + steps = 0 else: #steps = p.refiner_steps # SD 1.5 with denoise strenght steps = (p.refiner_steps * 1.25) + 1 diff --git a/modules/sd_models.py b/modules/sd_models.py index 433d44558..06d6632b0 100644 --- a/modules/sd_models.py +++ b/modules/sd_models.py @@ -676,6 +676,18 @@ def detect_pipeline(f: str, op: str = 'model', warning=True): return pipeline, guess +def copy_diffuser_options(new_pipe, orig_pipe): + new_pipe.sd_checkpoint_info = orig_pipe.sd_checkpoint_info + new_pipe.sd_model_checkpoint = orig_pipe.sd_model_checkpoint + new_pipe.sd_model_hash = orig_pipe.sd_model_hash + new_pipe.has_accelerate = orig_pipe.has_accelerate + new_pipe.embedding_db = orig_pipe.embedding_db + new_pipe.is_sdxl = True # pylint: disable=attribute-defined-outside-init # a1111 compatibility item + new_pipe.is_sd2 = False # pylint: disable=attribute-defined-outside-init + new_pipe.is_sd1 = False # pylint: disable=attribute-defined-outside-init + + + def set_diffuser_options(sd_model, vae = None, op: str = 'model'): if sd_model is None: shared.log.warning(f'{op} is not loaded') diff --git a/modules/ui.py b/modules/ui.py index e2ad98144..fd359a680 100644 --- a/modules/ui.py +++ b/modules/ui.py @@ -46,6 +46,8 @@ save_style_symbol = symbols.save txt2img_paste_fields = [] img2img_paste_fields = [] +txt2img_args = [] +img2img_args = [] if not cmd_opts.share and not cmd_opts.listen: @@ -491,28 +493,30 @@ def create_ui(startup_timer = None): connect_reuse_seed(seed, reuse_seed, generation_info, dummy_component, is_subseed=False) connect_reuse_seed(subseed, reuse_subseed, generation_info, dummy_component, is_subseed=True) - txt2img_args = dict( + global txt2img_args # pylint: disable=global-statement + txt2img_args = [ + dummy_component, + txt2img_prompt, txt2img_negative_prompt, + txt2img_prompt_styles, + steps, + sampler_index, latent_index, + full_quality, restore_faces, tiling, + batch_count, batch_size, + cfg_scale, image_cfg_scale, + diffusers_guidance_rescale, + clip_skip, + seed, subseed, subseed_strength, seed_resize_from_h, seed_resize_from_w, + height, width, + enable_hr, denoising_strength, + hr_scale, hr_upscaler, hr_force, hr_second_pass_steps, hr_resize_x, hr_resize_y, + refiner_steps, refiner_start, refiner_prompt, refiner_negative, + hdr_clamp, hdr_boundary, hdr_threshold, hdr_center, hdr_channel_shift, hdr_full_shift, hdr_maximize, hdr_max_center, hdr_max_boundry, + override_settings, + ] + txt2img_dict = dict( fn=wrap_gradio_gpu_call(modules.txt2img.txt2img, extra_outputs=[None, '', '']), _js="submit_txt2img", - inputs=[ - dummy_component, - txt2img_prompt, txt2img_negative_prompt, - txt2img_prompt_styles, - steps, - sampler_index, latent_index, - full_quality, restore_faces, tiling, - batch_count, batch_size, - cfg_scale, image_cfg_scale, - diffusers_guidance_rescale, - clip_skip, - seed, subseed, subseed_strength, seed_resize_from_h, seed_resize_from_w, - height, width, - enable_hr, denoising_strength, - hr_scale, hr_upscaler, hr_force, hr_second_pass_steps, hr_resize_x, hr_resize_y, - refiner_steps, refiner_start, refiner_prompt, refiner_negative, - hdr_clamp, hdr_boundary, hdr_threshold, hdr_center, hdr_channel_shift, hdr_full_shift, hdr_maximize, hdr_max_center, hdr_max_boundry, - override_settings, - ] + custom_inputs, + inputs=txt2img_args + custom_inputs, outputs=[ txt2img_gallery, generation_info, @@ -521,8 +525,8 @@ def create_ui(startup_timer = None): ], show_progress=False, ) - txt2img_prompt.submit(**txt2img_args) - submit.click(**txt2img_args) + txt2img_prompt.submit(**txt2img_dict) + submit.click(**txt2img_dict) res_switch_btn.click(lambda w, h: (h, w), inputs=[width, height], outputs=[width, height], show_progress=False) batch_switch_btn.click(lambda w, h: (h, w), inputs=[batch_count, batch_size], outputs=[batch_count, batch_size], show_progress=False) @@ -782,42 +786,43 @@ def select_img2img_tab(tab): img2img_prompt_img ] ) - - img2img_args = dict( + global img2img_args # pylint: disable=global-statement + img2img_args = [ + dummy_component, dummy_component, + img2img_prompt, img2img_negative_prompt, + img2img_prompt_styles, + init_img, + sketch, + init_img_with_mask, + inpaint_color_sketch, + inpaint_color_sketch_orig, + init_img_inpaint, + init_mask_inpaint, + steps, + sampler_index, latent_index, + mask_blur, mask_alpha, + inpainting_fill, + full_quality, restore_faces, tiling, + batch_count, batch_size, + cfg_scale, image_cfg_scale, + diffusers_guidance_rescale, + refiner_steps, + refiner_start, + clip_skip, + denoising_strength, + seed, subseed, subseed_strength, seed_resize_from_h, seed_resize_from_w, + selected_scale_tab, + height, width, + scale_by, + resize_mode, + inpaint_full_res, inpaint_full_res_padding, inpainting_mask_invert, + img2img_batch_files, img2img_batch_input_dir, img2img_batch_output_dir, img2img_batch_inpaint_mask_dir, + override_settings, + ] + img2img_dict = dict( fn=wrap_gradio_gpu_call(modules.img2img.img2img, extra_outputs=[None, '', '']), _js="submit_img2img", - inputs=[ - dummy_component, dummy_component, - img2img_prompt, img2img_negative_prompt, - img2img_prompt_styles, - init_img, - sketch, - init_img_with_mask, - inpaint_color_sketch, - inpaint_color_sketch_orig, - init_img_inpaint, - init_mask_inpaint, - steps, - sampler_index, latent_index, - mask_blur, mask_alpha, - inpainting_fill, - full_quality, restore_faces, tiling, - batch_count, batch_size, - cfg_scale, image_cfg_scale, - diffusers_guidance_rescale, - refiner_steps, - refiner_start, - clip_skip, - denoising_strength, - seed, subseed, subseed_strength, seed_resize_from_h, seed_resize_from_w, - selected_scale_tab, - height, width, - scale_by, - resize_mode, - inpaint_full_res, inpaint_full_res_padding, inpainting_mask_invert, - img2img_batch_files, img2img_batch_input_dir, img2img_batch_output_dir, img2img_batch_inpaint_mask_dir, - override_settings, - ] + custom_inputs, + inputs= img2img_args + custom_inputs, outputs=[ img2img_gallery, generation_info, @@ -826,8 +831,8 @@ def select_img2img_tab(tab): ], show_progress=False, ) - img2img_prompt.submit(**img2img_args) - submit.click(**img2img_args) + img2img_prompt.submit(**img2img_dict) + submit.click(**img2img_dict) interrogate_args = dict( _js="get_img2img_tab_index", diff --git a/requirements.txt b/requirements.txt index eb7d0c930..1aee9cccd 100644 --- a/requirements.txt +++ b/requirements.txt @@ -63,7 +63,7 @@ pandas==1.5.3 protobuf==3.20.3 pytorch_lightning==1.9.4 tokenizers==0.15.0 -transformers==4.35.2 +transformers==4.36.0 tomesd==0.1.3 urllib3==1.26.15 Pillow==10.1.0 diff --git a/scripts/example.py b/scripts/example.py index b507f6386..01eaef88b 100644 --- a/scripts/example.py +++ b/scripts/example.py @@ -92,7 +92,7 @@ def run(self, p: processing.StableDiffusionProcessing, *args): # pylint: disable if c != pipeline_base: shared.log.warning(f'{title}: pipeline={c} required={pipeline_base}') return None - orig_pipepeline = shared.sd_model # backup current pipeline definition + orig_pipeline = shared.sd_model # backup current pipeline definition shared.sd_model = pipeline_class( # create new pipeline using currently loaded model which is always in `shared.sd_model` # different pipelines may need different init params, so you may need to change this # to see init params, see pipeline_class.__init__ method @@ -107,9 +107,10 @@ def run(self, p: processing.StableDiffusionProcessing, *args): # pylint: disable safety_checker=shared.sd_model.safety_checker, feature_extractor=shared.sd_model.feature_extractor, ) + sd_models.copy_diffuser_options(shared.sd_model, orig_pipeline) # copy options from original pipeline + sd_models.set_diffuser_options(shared.sd_model) # set all model options such as fp16, offload, etc. if not ((shared.opts.diffusers_model_cpu_offload or shared.cmd_opts.medvram) or (shared.opts.diffusers_seq_cpu_offload or shared.cmd_opts.lowvram)): shared.sd_model.to(shared.device) # move pipeline if needed, but don't touch if its under automatic managment - sd_models.set_diffuser_options(shared.sd_model) # set all model options such as fp16, offload, etc. # if pipeline also needs a specific type, you can set it here, but not commonly needed # shared.sd_model = sd_models.set_diffuser_pipe(shared.sd_model, sd_models.DiffusersTaskType.IMAGE_2_IMAGE) @@ -136,5 +137,5 @@ def run(self, p: processing.StableDiffusionProcessing, *args): # pylint: disable # you dont need to handle saving, metadata, etc - sdnext will do it for you # restore original pipeline - shared.sd_model = orig_pipepeline + shared.sd_model = orig_pipeline return processed From 92baeae55dd58e96234eac02de506ca8171e6a7d Mon Sep 17 00:00:00 2001 From: Disty0 Date: Thu, 14 Dec 2023 01:01:45 +0300 Subject: [PATCH 052/143] IPEX bundle in MKL and DPCPP --- CHANGELOG.md | 4 +++- installer.py | 13 ++++--------- webui.sh | 12 +++--------- 3 files changed, 10 insertions(+), 19 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index c8ca24915..4198185dd 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -37,7 +37,9 @@ - **Extra networks** new *settting -> extra networks -> build info on first access* indexes all networks on first access instead of server startup - **IPEX** - - Update to **Torch 2.1 - This Update Requires Intel OneApi 2024.0**, thanks @disty0 + - Update to **Torch 2.1**, thanks @disty0 + - Built-in *MKL* and *DPCPP* for IPEX, thanks @disty0 + No need to install OneAPI anymore - Fix IPEX Optimize not applying with Diffusers backend, thanks @disty0 - Disable 32 bit workarounds if the GPU supports 64 bit, thanks @disty0 - More compatibility improvements, thanks @disty0 diff --git a/installer.py b/installer.py index bb957f9b4..c6725287b 100644 --- a/installer.py +++ b/installer.py @@ -423,14 +423,10 @@ def check_torch(): log.info('Intel OneAPI Toolkit detected') os.environ.setdefault('NEOReadDebugKeys', '1') os.environ.setdefault('ClDeviceGlobalMemSizeAvailablePercent', '100') - if "linux" in sys.platform: - torch_command = os.environ.get('TORCH_COMMAND', 'torch==2.1.0a0 torchvision==0.16.0a0 intel-extension-for-pytorch==2.1.10+xpu --extra-index-url https://pytorch-extension.intel.com/release-whl/stable/xpu/us/') - os.environ.setdefault('TENSORFLOW_PACKAGE', 'tensorflow==2.13.0 intel-extension-for-tensorflow[gpu]') - else: - pytorch_pip = 'https://github.com/Nuullll/intel-extension-for-pytorch/releases/download/v2.0.110%2Bxpu-master%2Bdll-bundle/torch-2.0.0a0+gite9ebda2-cp310-cp310-win_amd64.whl' - torchvision_pip = 'https://github.com/Nuullll/intel-extension-for-pytorch/releases/download/v2.0.110%2Bxpu-master%2Bdll-bundle/torchvision-0.15.2a0+fa99a53-cp310-cp310-win_amd64.whl' - ipex_pip = 'https://github.com/Nuullll/intel-extension-for-pytorch/releases/download/v2.0.110%2Bxpu-master%2Bdll-bundle/intel_extension_for_pytorch-2.0.110+gitc6ea20b-cp310-cp310-win_amd64.whl' - torch_command = os.environ.get('TORCH_COMMAND', f'{pytorch_pip} {torchvision_pip} {ipex_pip}') + torch_command = os.environ.get('TORCH_COMMAND', 'torch==2.1.0a0 torchvision==0.16.0a0 intel-extension-for-pytorch==2.1.10+xpu --extra-index-url https://pytorch-extension.intel.com/release-whl/stable/xpu/us/') + os.environ.setdefault('TENSORFLOW_PACKAGE', 'tensorflow==2.13.0 intel-extension-for-tensorflow[gpu]') + install('mkl==2024.0.0', 'mkl') + install('mkl-dpcpp==2024.0.0', 'mkl-dpcpp') install('openvino', 'openvino', ignore=True) install('onnxruntime-openvino', 'onnxruntime-openvino', ignore=True) elif allow_openvino and args.use_openvino: @@ -503,7 +499,6 @@ def check_torch(): if opts.get('cuda_compile_backend', '') == 'hidet': install('hidet', 'hidet') if args.use_openvino or opts.get('cuda_compile_backend', '') == 'openvino_fx': - uninstall('openvino-nightly') # TODO openvino: remove after people had enough time upgrading install('openvino==2023.2.0', 'openvino') install('onnxruntime-openvino', 'onnxruntime-openvino', ignore=True) # TODO openvino: numpy version conflicts with tensorflow and doesn't support Python 3.11 os.environ.setdefault('PYTORCH_TRACING_MODE', 'TORCHFX') diff --git a/webui.sh b/webui.sh index 692cc36c1..cec7fb960 100755 --- a/webui.sh +++ b/webui.sh @@ -80,22 +80,16 @@ else exit 1 fi -#Set OneAPI environmet if it's not set by the user -if ([[ "$@" == *"--use-ipex"* ]] || [[ -d "/opt/intel/oneapi" ]] || [[ ! -z "$ONEAPI_ROOT" ]]) && [ ! -x "$(command -v sycl-ls)" ] +if [ -d "$(realpath "$venv_dir")/lib/" ] then - echo "Setting OneAPI environment" - if [[ -z "$ONEAPI_ROOT" ]] - then - ONEAPI_ROOT=/opt/intel/oneapi - fi - source $ONEAPI_ROOT/setvars.sh + export LD_LIBRARY_PATH=$(realpath "$venv_dir")/lib/:$LD_LIBRARY_PATH fi if [[ ! -z "${ACCELERATE}" ]] && [ ${ACCELERATE}="True" ] && [ -x "$(command -v accelerate)" ] then echo "Launching accelerate launch.py..." exec accelerate launch --num_cpu_threads_per_process=6 launch.py "$@" -elif [[ "$@" == *"--use-ipex"* ]] && [[ -z "${first_launch}" ]] && [ -x "$(command -v ipexrun)" ] && [ -x "$(command -v sycl-ls)" ] +elif [[ "$@" == *"--use-ipex"* ]] && [[ -z "${first_launch}" ]] && [ -x "$(command -v ipexrun)" ] then echo "Launching ipexrun launch.py..." exec ipexrun --multi-task-manager 'taskset' --memory-allocator 'jemalloc' launch.py "$@" From f5cab1185d45e08f9996b279eb0edd2ff06a8619 Mon Sep 17 00:00:00 2001 From: Disty0 Date: Thu, 14 Dec 2023 12:13:28 +0300 Subject: [PATCH 053/143] IPEX update Tensorflow --- installer.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/installer.py b/installer.py index c6725287b..fb0f462ed 100644 --- a/installer.py +++ b/installer.py @@ -424,7 +424,8 @@ def check_torch(): os.environ.setdefault('NEOReadDebugKeys', '1') os.environ.setdefault('ClDeviceGlobalMemSizeAvailablePercent', '100') torch_command = os.environ.get('TORCH_COMMAND', 'torch==2.1.0a0 torchvision==0.16.0a0 intel-extension-for-pytorch==2.1.10+xpu --extra-index-url https://pytorch-extension.intel.com/release-whl/stable/xpu/us/') - os.environ.setdefault('TENSORFLOW_PACKAGE', 'tensorflow==2.13.0 intel-extension-for-tensorflow[gpu]') + if "linux" in sys.platform: + os.environ.setdefault('TENSORFLOW_PACKAGE', 'tensorflow==2.14.0 intel-extension-for-tensorflow[xpu]==2.14.0.1') install('mkl==2024.0.0', 'mkl') install('mkl-dpcpp==2024.0.0', 'mkl-dpcpp') install('openvino', 'openvino', ignore=True) From 934152dcfcfc4cfa57ad2f520fd4a5bdbd6284fe Mon Sep 17 00:00:00 2001 From: Vladimir Mandic Date: Thu, 14 Dec 2023 09:22:02 -0500 Subject: [PATCH 054/143] fix hypertile --- modules/sd_hijack_hypertile.py | 30 ++++++++++++++++-------------- 1 file changed, 16 insertions(+), 14 deletions(-) diff --git a/modules/sd_hijack_hypertile.py b/modules/sd_hijack_hypertile.py index 6c6213a48..217ffad48 100644 --- a/modules/sd_hijack_hypertile.py +++ b/modules/sd_hijack_hypertile.py @@ -48,6 +48,7 @@ def split_attention(layer: nn.Module, tile_size: int=256, min_tile_size: int=256 reset_needed = True nhs = possible_tile_sizes(height, tile_size, min_tile_size, swap_size) # possible sub-grids that fit into the image nws = possible_tile_sizes(width, tile_size, min_tile_size, swap_size) + def reset_nhs(): nonlocal nhs, ar ar = height / width # Aspect ratio @@ -135,18 +136,17 @@ def wrapper(*args, **kwargs): def context_hypertile_vae(p): - global height, width, max_h, max_w, error_reported # pylint: disable=global-statement - error_reported = False - height=p.height - width=p.width - max_h = 0 - max_w = 0 from modules import shared if p.sd_model is None or not shared.opts.hypertile_vae_enabled: return nullcontext() if shared.opts.cross_attention_optimization == 'Sub-quadratic': shared.log.warning('Hypertile UNet is not compatible with Sub-quadratic cross-attention optimization') return nullcontext() + global height, width, max_h, max_w, error_reported # pylint: disable=global-statement + error_reported = False + error_reported = False + height, width = p.height, p.width + max_h, max_w = 0, 0 vae = getattr(p.sd_model, "vae", None) if shared.backend == shared.Backend.DIFFUSERS else getattr(p.sd_model, "first_stage_model", None) if height % 8 != 0 or width % 8 != 0: log.warning(f'Hypertile VAE disabled: width={width} height={height} are not divisible by 8') @@ -161,18 +161,16 @@ def context_hypertile_vae(p): def context_hypertile_unet(p): - global height, width, max_h, max_w, error_reported # pylint: disable=global-statement - error_reported = False - height=p.height - width=p.width - max_h = 0 - max_w = 0 from modules import shared if p.sd_model is None or not shared.opts.hypertile_unet_enabled: return nullcontext() if shared.opts.cross_attention_optimization == 'Sub-quadratic' and not shared.cmd_opts.experimental: shared.log.warning('Hypertile UNet is not compatible with Sub-quadratic cross-attention optimization') return nullcontext() + global height, width, max_h, max_w, error_reported # pylint: disable=global-statement + error_reported = False + height, width = p.height, p.width + max_h, max_w = 0, 0 unet = getattr(p.sd_model, "unet", None) if shared.backend == shared.Backend.DIFFUSERS else getattr(p.sd_model.model, "diffusion_model", None) if height % 8 != 0 or width % 8 != 0: log.warning(f'Hypertile UNet disabled: width={width} height={height} are not divisible by 8') @@ -192,6 +190,10 @@ def hypertile_set(p, hr=False): if not shared.opts.hypertile_unet_enabled: return error_reported = False - height=p.height if not hr else getattr(p, 'hr_upscale_to_y', p.height) - width=p.width if not hr else getattr(p, 'hr_upscale_to_x', p.width) + if not hr: + height=p.height if not hr else getattr(p, 'hr_upscale_to_y', p.height) + width=p.width if not hr else getattr(p, 'hr_upscale_to_x', p.width) + else: + height=max(p.height, getattr(p, 'hr_upscale_to_y', p.height)) + width=max(p.width, getattr(p, 'hr_upscale_to_x', p.width)) reset_needed = True From b96fbd8f95f1fd9ac51e0250bc684a018943a31f Mon Sep 17 00:00:00 2001 From: Vladimir Mandic Date: Thu, 14 Dec 2023 10:09:55 -0500 Subject: [PATCH 055/143] add img2img_apply_overlay --- CHANGELOG.md | 18 ++++++++++-------- extensions-builtin/sd-extension-system-info | 2 +- modules/processing.py | 2 ++ modules/shared.py | 6 ++++-- 4 files changed, 17 insertions(+), 11 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 4198185dd..0acc17d68 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,6 @@ # Change Log for SD.Next -## Update for 2023-12-09 +## Update for 2023-12-14 *Note*: based on `diffusers==0.25.0.dev0` @@ -36,16 +36,18 @@ - **CivitAI downloader** allow usage of access tokens for download of gated or private models - **Extra networks** new *settting -> extra networks -> build info on first access* indexes all networks on first access instead of server startup - - **IPEX** - - Update to **Torch 2.1**, thanks @disty0 - - Built-in *MKL* and *DPCPP* for IPEX, thanks @disty0 - No need to install OneAPI anymore - - Fix IPEX Optimize not applying with Diffusers backend, thanks @disty0 - - Disable 32 bit workarounds if the GPU supports 64 bit, thanks @disty0 - - More compatibility improvements, thanks @disty0 + - **IPEX**, thanks @disty0 + - update to **Torch 2.1** + - built-in *MKL* and *DPCPP* for IPEX + no need to install OneAPI anymore + - fix IPEX Optimize not applying with Diffusers backend + - disable 32 bit workarounds if the GPU supports 64 bit + - compatibility improvements - **OpenVINO** - Add *Directory for OpenVINO cache* option to *System Paths*, thanks @disty0 - Remove Intel ARC specific 1024x1024 workaround, thanks @disty0 + - **Inpaint** add option `apply_overlay` to control if inpaint result should be applied as overlay or as-is + can remove artifacts and hard edges of inpaint area but also remove some details from original - disable google fonts check on server startup - fix torchvision/basicsr compatibility - fix extra networks sort diff --git a/extensions-builtin/sd-extension-system-info b/extensions-builtin/sd-extension-system-info index 1841cf762..ce1b2a4a1 160000 --- a/extensions-builtin/sd-extension-system-info +++ b/extensions-builtin/sd-extension-system-info @@ -1 +1 @@ -Subproject commit 1841cf7627d3991d7ba3dfe8b86a14b18f78f933 +Subproject commit ce1b2a4a10eb25d33d716132a209599cb3733164 diff --git a/modules/processing.py b/modules/processing.py index 0bd8788e7..31170d8d5 100644 --- a/modules/processing.py +++ b/modules/processing.py @@ -61,6 +61,8 @@ def apply_color_correction(correction, original_image): def apply_overlay(image: Image, paste_loc, index, overlays): + if not shared.opts.img2img_apply_overlay: + return image if overlays is None or index >= len(overlays): return image overlay = overlays[index] diff --git a/modules/shared.py b/modules/shared.py index 3b9e4fbd5..6ef5d382c 100644 --- a/modules/shared.py +++ b/modules/shared.py @@ -545,9 +545,11 @@ def default(obj): options_templates.update(options_section(('postprocessing', "Postprocessing"), { 'postprocessing_enable_in_main_ui': OptionInfo([], "Enable additional postprocessing operations", ui_components.DropdownMulti, lambda: {"choices": [x.name for x in shared_items.postprocessing_scripts()]}), 'postprocessing_operation_order': OptionInfo([], "Postprocessing operation order", ui_components.DropdownMulti, lambda: {"choices": [x.name for x in shared_items.postprocessing_scripts()]}), + "postprocessing_sep_img2img": OptionInfo("

Img2Img & Inpainting

", "", gr.HTML), - "img2img_color_correction": OptionInfo(False, "Apply color correction to match original colors"), - "img2img_fix_steps": OptionInfo(False, "For image processing do exact number of steps as specified"), + "img2img_color_correction": OptionInfo(False, "Apply color correction"), + "img2img_apply_overlay": OptionInfo(False, "Apply result as overlay"), + "img2img_fix_steps": OptionInfo(False, "For image processing do exact number of steps as specified", gr.Checkbox, { "visible": False }), "img2img_background_color": OptionInfo("#ffffff", "Image transparent color fill", ui_components.FormColorPicker, {}), "inpainting_mask_weight": OptionInfo(1.0, "Inpainting conditioning mask strength", gr.Slider, {"minimum": 0.0, "maximum": 1.0, "step": 0.01}), "initial_noise_multiplier": OptionInfo(1.0, "Noise multiplier for image processing", gr.Slider, {"minimum": 0.1, "maximum": 1.5, "step": 0.01}), From 135a8aa2f1d535c65c96cfb545e607b2385fe3cf Mon Sep 17 00:00:00 2001 From: Disty0 Date: Thu, 14 Dec 2023 19:53:21 +0300 Subject: [PATCH 056/143] Add DISABLE_IPEXRUN environment variable --- webui.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/webui.sh b/webui.sh index cec7fb960..d88c8ec53 100755 --- a/webui.sh +++ b/webui.sh @@ -89,7 +89,7 @@ if [[ ! -z "${ACCELERATE}" ]] && [ ${ACCELERATE}="True" ] && [ -x "$(command -v then echo "Launching accelerate launch.py..." exec accelerate launch --num_cpu_threads_per_process=6 launch.py "$@" -elif [[ "$@" == *"--use-ipex"* ]] && [[ -z "${first_launch}" ]] && [ -x "$(command -v ipexrun)" ] +elif [[ "$@" == *"--use-ipex"* ]] && [[ -z "${first_launch}" ]] && [ -x "$(command -v ipexrun)" ] && [[ -z "${DISABLE_IPEXRUN}" ]] then echo "Launching ipexrun launch.py..." exec ipexrun --multi-task-manager 'taskset' --memory-allocator 'jemalloc' launch.py "$@" From e6d95fd0a72271ee650cfc4f72d56006db3020da Mon Sep 17 00:00:00 2001 From: Disty0 Date: Fri, 15 Dec 2023 11:32:01 +0300 Subject: [PATCH 057/143] Add env variables for MKL, DPCPP and OpenVINO installer --- CHANGELOG.md | 13 +++++++------ installer.py | 8 ++++---- 2 files changed, 11 insertions(+), 10 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 0acc17d68..3ee3aa807 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -37,15 +37,16 @@ - **Extra networks** new *settting -> extra networks -> build info on first access* indexes all networks on first access instead of server startup - **IPEX**, thanks @disty0 - - update to **Torch 2.1** - - built-in *MKL* and *DPCPP* for IPEX - no need to install OneAPI anymore + - update to **Torch 2.1** + if you get file not found errors, set DISABLE_IPEXRUN=1 and run the webui with --reinstall + - built-in *MKL* and *DPCPP* for IPEX, no need to install OneAPI anymore - fix IPEX Optimize not applying with Diffusers backend - disable 32 bit workarounds if the GPU supports 64 bit + - add DISABLE_IPEXRUN environment variable - compatibility improvements - - **OpenVINO** - - Add *Directory for OpenVINO cache* option to *System Paths*, thanks @disty0 - - Remove Intel ARC specific 1024x1024 workaround, thanks @disty0 + - **OpenVINO**, thanks @disty0 + - Add *Directory for OpenVINO cache* option to *System Paths* + - Remove Intel ARC specific 1024x1024 workaround - **Inpaint** add option `apply_overlay` to control if inpaint result should be applied as overlay or as-is can remove artifacts and hard edges of inpaint area but also remove some details from original - disable google fonts check on server startup diff --git a/installer.py b/installer.py index fb0f462ed..206e4b107 100644 --- a/installer.py +++ b/installer.py @@ -426,9 +426,9 @@ def check_torch(): torch_command = os.environ.get('TORCH_COMMAND', 'torch==2.1.0a0 torchvision==0.16.0a0 intel-extension-for-pytorch==2.1.10+xpu --extra-index-url https://pytorch-extension.intel.com/release-whl/stable/xpu/us/') if "linux" in sys.platform: os.environ.setdefault('TENSORFLOW_PACKAGE', 'tensorflow==2.14.0 intel-extension-for-tensorflow[xpu]==2.14.0.1') - install('mkl==2024.0.0', 'mkl') - install('mkl-dpcpp==2024.0.0', 'mkl-dpcpp') - install('openvino', 'openvino', ignore=True) + install(os.environ.get('MKL_PACKAGE', 'mkl==2024.0.0'), 'mkl') + install(os.environ.get('DPCPP_PACKAGE', 'mkl-dpcpp==2024.0.0'), 'mkl-dpcpp') + install(os.environ.get('OPENVINO_PACKAGE', 'openvino==2023.2.0'), 'openvino', ignore=True) install('onnxruntime-openvino', 'onnxruntime-openvino', ignore=True) elif allow_openvino and args.use_openvino: log.info('Using OpenVINO') @@ -500,7 +500,7 @@ def check_torch(): if opts.get('cuda_compile_backend', '') == 'hidet': install('hidet', 'hidet') if args.use_openvino or opts.get('cuda_compile_backend', '') == 'openvino_fx': - install('openvino==2023.2.0', 'openvino') + install(os.environ.get('OPENVINO_PACKAGE', 'openvino==2023.2.0'), 'openvino') install('onnxruntime-openvino', 'onnxruntime-openvino', ignore=True) # TODO openvino: numpy version conflicts with tensorflow and doesn't support Python 3.11 os.environ.setdefault('PYTORCH_TRACING_MODE', 'TORCHFX') os.environ.setdefault('NEOReadDebugKeys', '1') From 936cf9786c64475481bdedb68b7a104df624b2c8 Mon Sep 17 00:00:00 2001 From: Vladimir Mandic Date: Fri, 15 Dec 2023 18:53:03 -0500 Subject: [PATCH 058/143] create_ui and process refactor --- CHANGELOG.md | 45 ++- extensions-builtin/sd-extension-chainner | 2 +- extensions-builtin/sd-extension-system-info | 2 +- html/locale_en.json | 8 +- javascript/sdnext.css | 2 +- modules/images.py | 2 +- modules/img2img.py | 39 +- modules/processing.py | 31 +- modules/processing_diffusers.py | 4 +- modules/sd_hijack_hypertile.py | 14 +- modules/shared.py | 3 +- modules/txt2img.py | 18 +- modules/ui.py | 414 ++++++++++---------- webui.py | 3 +- 14 files changed, 319 insertions(+), 268 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 3ee3aa807..0fd5bd560 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -8,7 +8,6 @@ - **AnimateDiff** can now be used with *second pass* - enhance, upscale and hires your videos! - **IP Adapter** add support for `ip-adapter-plus_sd15`, `ip-adapter-plus-face_sd15` and `ip-adapter-full-face_sd15` additionally, ip-adapter can now be used in xyz-grid - - **HDR controls** are now batch-aware for enhancement of multiple images or video frames - [Playground v1](https://huggingface.co/playgroundai/playground-v1), [Playground v2 256](https://huggingface.co/playgroundai/playground-v2-256px-base), [Playground v2 512](https://huggingface.co/playgroundai/playground-v2-512px-base), [Playground v2 1024](https://huggingface.co/playgroundai/playground-v2-1024px-aesthetic) model support - simply select from *networks -> reference* and use as usual - [DemoFusion](https://github.com/PRIS-CV/DemoFusion) run your SDXL generations at any resolution! @@ -37,28 +36,38 @@ - **Extra networks** new *settting -> extra networks -> build info on first access* indexes all networks on first access instead of server startup - **IPEX**, thanks @disty0 - - update to **Torch 2.1** - if you get file not found errors, set DISABLE_IPEXRUN=1 and run the webui with --reinstall + - update to **Torch 2.1** + if you get file not found errors, set DISABLE_IPEXRUN=1 and run the webui with --reinstall - built-in *MKL* and *DPCPP* for IPEX, no need to install OneAPI anymore - fix IPEX Optimize not applying with Diffusers backend - disable 32 bit workarounds if the GPU supports 64 bit - add DISABLE_IPEXRUN environment variable - compatibility improvements - - **OpenVINO**, thanks @disty0 - - Add *Directory for OpenVINO cache* option to *System Paths* - - Remove Intel ARC specific 1024x1024 workaround - - **Inpaint** add option `apply_overlay` to control if inpaint result should be applied as overlay or as-is - can remove artifacts and hard edges of inpaint area but also remove some details from original - - disable google fonts check on server startup - - fix torchvision/basicsr compatibility - - fix extra networks sort - - add hdr settings to metadata - - improve handling of long filenames and filenames during batch processing - - do not set preview samples when using via api - - avoid unnecessary resizes in img2img and inpaint - - updated `cli/simple-txt2img.py` and `cli/simple-img2img.py` scripts - - save `params.txt` regardless of image save status - - update built-in log monitor in ui, thanks @midcoastal + - **OpenVINO**, thanks @disty0 + - add *Directory for OpenVINO cache* option to *System Paths* + - remove Intel ARC specific 1024x1024 workaround + - **UI** + - more dynamic controls depending on the backend (original or diffusers) + controls that are not applicable in current mode are now hidden + - allow setting of resize method directly in image tab + (previously via settings -> upscaler_for_img2img) + - **HDR controls** + - batch-aware for enhancement of multiple images or video frames + - available in image tab + - **Other** + - **Inpaint** add option `apply_overlay` to control if inpaint result should be applied as overlay or as-is + can remove artifacts and hard edges of inpaint area but also remove some details from original + - **chaiNNer** fix NaN issues due to autocast + - **Extra Networks** fix sort + - disable google fonts check on server startup + - fix torchvision/basicsr compatibility + - add hdr settings to metadata + - improve handling of long filenames and filenames during batch processing + - do not set preview samples when using via api + - avoid unnecessary resizes in img2img and inpaint + - updated `cli/simple-txt2img.py` and `cli/simple-img2img.py` scripts + - save `params.txt` regardless of image save status + - update built-in log monitor in ui, thanks @midcoastal ## Update for 2023-12-04 diff --git a/extensions-builtin/sd-extension-chainner b/extensions-builtin/sd-extension-chainner index 1cdc8578a..e48020d03 160000 --- a/extensions-builtin/sd-extension-chainner +++ b/extensions-builtin/sd-extension-chainner @@ -1 +1 @@ -Subproject commit 1cdc8578a3c0538177c3ac13721b9764f8782c8f +Subproject commit e48020d035b0d4daffec37a2bbce61c3ea04798c diff --git a/extensions-builtin/sd-extension-system-info b/extensions-builtin/sd-extension-system-info index ce1b2a4a1..f93480869 160000 --- a/extensions-builtin/sd-extension-system-info +++ b/extensions-builtin/sd-extension-system-info @@ -1 +1 @@ -Subproject commit ce1b2a4a10eb25d33d716132a209599cb3733164 +Subproject commit f934808698523465e7abb2d1fb0d290065def776 diff --git a/html/locale_en.json b/html/locale_en.json index 82383808f..14ea99c9b 100644 --- a/html/locale_en.json +++ b/html/locale_en.json @@ -226,10 +226,10 @@ {"id":"","label":"Inpaint batch input directory","localized":"","hint":""}, {"id":"","label":"Inpaint batch output directory","localized":"","hint":""}, {"id":"","label":"Inpaint batch mask directory","localized":"","hint":""}, - {"id":"","label":"Resize fixed","localized":"","hint":"Resize image to target resolution. Unless height and width match, you will get incorrect aspect ratio"}, - {"id":"","label":"Crop and resize","localized":"","hint":"Resize the image so that entirety of target resolution is filled with the image. Crop parts that stick out"}, - {"id":"","label":"Resize and fill","localized":"","hint":"Resize the image so that entirety of image is inside target resolution. Fill empty space with image's colors"}, - {"id":"","label":"Latent upscale","localized":"","hint":""}, + {"id":"","label":"Fixed","localized":"","hint":"Resize image to target resolution. Unless height and width match, you will get incorrect aspect ratio"}, + {"id":"","label":"Crop","localized":"","hint":"Resize the image so that entirety of target resolution is filled with the image. Crop parts that stick out"}, + {"id":"","label":"Fill","localized":"","hint":"Resize the image so that entirety of image is inside target resolution. Fill empty space with image's colors"}, + {"id":"","label":"Latent","localized":"","hint":""}, {"id":"","label":"Mask blur","localized":"","hint":"How much to blur the mask before processing, in pixels"}, {"id":"","label":"Mask transparency","localized":"","hint":""}, {"id":"","label":"Inpaint masked","localized":"","hint":""}, diff --git a/javascript/sdnext.css b/javascript/sdnext.css index 43a4f159c..afc3f9095 100644 --- a/javascript/sdnext.css +++ b/javascript/sdnext.css @@ -52,7 +52,7 @@ textarea { overflow-y: auto !important; } .small-accordion .form { min-width: var(--left-column) !important; max-width: max-content; } .small-accordion .label-wrap .icon { margin-right: 1.6em; margin-left: 0.6em; color: var(--button-primary-border-color); } .small-accordion .label-wrap { padding: 16px 0px 8px 0px; margin: 0; border-top: 2px solid var(--button-secondary-border-color); } -.small-accordion { width: fit-content !important; padding-left: 0 !important; } +.small-accordion { width: fit-content !important; min-width: fit-content !important; padding-left: 0 !important; } .extension-script { max-width: 48vw; } button.custom-button{ border-radius: var(--button-large-radius); padding: var(--button-large-padding); font-weight: var(--button-large-text-weight); border: var(--button-border-width) solid var(--button-secondary-border-color); background: var(--button-secondary-background-fill); color: var(--button-secondary-text-color); font-size: var(--button-large-text-size); diff --git a/modules/images.py b/modules/images.py index 2e467105c..db4d16e82 100644 --- a/modules/images.py +++ b/modules/images.py @@ -215,7 +215,7 @@ def resize_image(resize_mode, im, width, height, upscaler_name=None, output_type Resizes an image with the specified resize_mode, width, and height. Args: resize_mode: The mode to use when resizing the image. - 0: No resie + 0: No resize 1: Resize the image to the specified width and height. 2: Resize the image to fill the specified width and height, maintaining the aspect ratio, and then center the image within the dimensions, cropping the excess. 3: Resize the image to fit within the specified width and height, maintaining the aspect ratio, and then center the image within the dimensions, filling empty with data from image. diff --git a/modules/img2img.py b/modules/img2img.py index db726d331..20ef6fe6d 100644 --- a/modules/img2img.py +++ b/modules/img2img.py @@ -103,13 +103,43 @@ def process_batch(p, input_files, input_dir, output_dir, inpaint_mask_dir, args) shared.log.debug(f'Processed: images={len(batch_image_files)} memory={memory_stats()} batch') -def img2img(id_task: str, mode: int, prompt: str, negative_prompt: str, prompt_styles, init_img, sketch, init_img_with_mask, inpaint_color_sketch, inpaint_color_sketch_orig, init_img_inpaint, init_mask_inpaint, steps: int, sampler_index: int, latent_index: int, mask_blur: int, mask_alpha: float, inpainting_fill: int, full_quality: bool, restore_faces: bool, tiling: bool, n_iter: int, batch_size: int, cfg_scale: float, image_cfg_scale: float, diffusers_guidance_rescale: float, refiner_steps: int, refiner_start: float, clip_skip: int, denoising_strength: float, seed: int, subseed: int, subseed_strength: float, seed_resize_from_h: int, seed_resize_from_w: int, selected_scale_tab: int, height: int, width: int, scale_by: float, resize_mode: int, inpaint_full_res: bool, inpaint_full_res_padding: int, inpainting_mask_invert: int, img2img_batch_files: list, img2img_batch_input_dir: str, img2img_batch_output_dir: str, img2img_batch_inpaint_mask_dir: str, override_settings_texts, *args): # pylint: disable=unused-argument +def img2img(id_task: str, mode: int, + prompt, negative_prompt, prompt_styles, + init_img, + sketch, + init_img_with_mask, + inpaint_color_sketch, + inpaint_color_sketch_orig, + init_img_inpaint, + init_mask_inpaint, + steps, + sampler_index, latent_index, + mask_blur, mask_alpha, + inpainting_fill, + full_quality, restore_faces, tiling, + n_iter, batch_size, + cfg_scale, image_cfg_scale, + diffusers_guidance_rescale, + refiner_steps, + refiner_start, + clip_skip, + denoising_strength, + seed, subseed, subseed_strength, seed_resize_from_h, seed_resize_from_w, + selected_scale_tab, + height, width, + scale_by, + resize_mode, resize_name, + inpaint_full_res, inpaint_full_res_padding, inpainting_mask_invert, + img2img_batch_files, img2img_batch_input_dir, img2img_batch_output_dir, img2img_batch_inpaint_mask_dir, + hdr_clamp, hdr_boundary, hdr_threshold, hdr_center, hdr_channel_shift, hdr_full_shift, hdr_maximize, hdr_max_center, hdr_max_boundry, + override_settings_texts, + *args): # pylint: disable=unused-argument if shared.sd_model is None: shared.log.warning('Model not loaded') return [], '', '', 'Error: model not loaded' - debug(f'img2img: id_task={id_task}|mode={mode}|prompt={prompt}|negative_prompt={negative_prompt}|prompt_styles={prompt_styles}|init_img={init_img}|sketch={sketch}|init_img_with_mask={init_img_with_mask}|inpaint_color_sketch={inpaint_color_sketch}|inpaint_color_sketch_orig={inpaint_color_sketch_orig}|init_img_inpaint={init_img_inpaint}|init_mask_inpaint={init_mask_inpaint}|steps={steps}|sampler_index={sampler_index}|latent_index={latent_index}|mask_blur={mask_blur}|mask_alpha={mask_alpha}|inpainting_fill={inpainting_fill}|full_quality={full_quality}|restore_faces={restore_faces}|tiling={tiling}|n_iter={n_iter}|batch_size={batch_size}|cfg_scale={cfg_scale}|image_cfg_scale={image_cfg_scale}|clip_skip={clip_skip}|denoising_strength={denoising_strength}|seed={seed}|subseed{subseed}|subseed_strength={subseed_strength}|seed_resize_from_h={seed_resize_from_h}|seed_resize_from_w={seed_resize_from_w}|selected_scale_tab={selected_scale_tab}|height={height}|width={width}|scale_by={scale_by}|resize_mode={resize_mode}|inpaint_full_res={inpaint_full_res}|inpaint_full_res_padding={inpaint_full_res_padding}|inpainting_mask_invert={inpainting_mask_invert}|img2img_batch_files={img2img_batch_files}|img2img_batch_input_dir={img2img_batch_input_dir}|img2img_batch_output_dir={img2img_batch_output_dir}|img2img_batch_inpaint_mask_dir={img2img_batch_inpaint_mask_dir}|override_settings_texts={override_settings_texts}') + debug(f'img2img: id_task={id_task}|mode={mode}|prompt={prompt}|negative_prompt={negative_prompt}|prompt_styles={prompt_styles}|init_img={init_img}|sketch={sketch}|init_img_with_mask={init_img_with_mask}|inpaint_color_sketch={inpaint_color_sketch}|inpaint_color_sketch_orig={inpaint_color_sketch_orig}|init_img_inpaint={init_img_inpaint}|init_mask_inpaint={init_mask_inpaint}|steps={steps}|sampler_index={sampler_index}|latent_index={latent_index}|mask_blur={mask_blur}|mask_alpha={mask_alpha}|inpainting_fill={inpainting_fill}|full_quality={full_quality}|restore_faces={restore_faces}|tiling={tiling}|n_iter={n_iter}|batch_size={batch_size}|cfg_scale={cfg_scale}|image_cfg_scale={image_cfg_scale}|clip_skip={clip_skip}|denoising_strength={denoising_strength}|seed={seed}|subseed{subseed}|subseed_strength={subseed_strength}|seed_resize_from_h={seed_resize_from_h}|seed_resize_from_w={seed_resize_from_w}|selected_scale_tab={selected_scale_tab}|height={height}|width={width}|scale_by={scale_by}|resize_mode={resize_mode}|resize_name={resize_name}|inpaint_full_res={inpaint_full_res}|inpaint_full_res_padding={inpaint_full_res_padding}|inpainting_mask_invert={inpainting_mask_invert}|img2img_batch_files={img2img_batch_files}|img2img_batch_input_dir={img2img_batch_input_dir}|img2img_batch_output_dir={img2img_batch_output_dir}|img2img_batch_inpaint_mask_dir={img2img_batch_inpaint_mask_dir}|override_settings_texts={override_settings_texts}') if mode == 5: if img2img_batch_files is None or len(img2img_batch_files) == 0: @@ -159,6 +189,7 @@ def img2img(id_task: str, mode: int, prompt: str, negative_prompt: str, prompt_s image = init_img_inpaint mask = init_mask_inpaint else: + shared.log.error(f'Image processing unknown mode: {mode}') image = None mask = None if image is not None: @@ -197,6 +228,7 @@ def img2img(id_task: str, mode: int, prompt: str, negative_prompt: str, prompt_s mask_blur=mask_blur, inpainting_fill=inpainting_fill, resize_mode=resize_mode, + resize_name=resize_name, denoising_strength=denoising_strength, image_cfg_scale=image_cfg_scale, diffusers_guidance_rescale=diffusers_guidance_rescale, @@ -205,6 +237,9 @@ def img2img(id_task: str, mode: int, prompt: str, negative_prompt: str, prompt_s inpaint_full_res=inpaint_full_res, inpaint_full_res_padding=inpaint_full_res_padding, inpainting_mask_invert=inpainting_mask_invert, + hdr_clamp=hdr_clamp, hdr_boundary=hdr_boundary, hdr_threshold=hdr_threshold, + hdr_center=hdr_center, hdr_channel_shift=hdr_channel_shift, hdr_full_shift=hdr_full_shift, + hdr_maximize=hdr_maximize, hdr_max_center=hdr_max_center, hdr_max_boundry=hdr_max_boundry, override_settings=override_settings, ) if selected_scale_tab == 1 and resize_mode != 0: diff --git a/modules/processing.py b/modules/processing.py index 31170d8d5..bb9753e72 100644 --- a/modules/processing.py +++ b/modules/processing.py @@ -126,7 +126,7 @@ class StableDiffusionProcessing: """ The first set of paramaters: sd_models -> do_not_reload_embeddings represent the minimum required to create a StableDiffusionProcessing """ - def __init__(self, sd_model=None, outpath_samples=None, outpath_grids=None, prompt: str = "", styles: List[str] = None, seed: int = -1, subseed: int = -1, subseed_strength: float = 0, seed_resize_from_h: int = -1, seed_resize_from_w: int = -1, seed_enable_extras: bool = True, sampler_name: str = None, latent_sampler: str = None, batch_size: int = 1, n_iter: int = 1, steps: int = 50, cfg_scale: float = 7.0, image_cfg_scale: float = None, clip_skip: int = 1, width: int = 512, height: int = 512, full_quality: bool = True, restore_faces: bool = False, tiling: bool = False, do_not_save_samples: bool = False, do_not_save_grid: bool = False, extra_generation_params: Dict[Any, Any] = None, overlay_images: Any = None, negative_prompt: str = None, eta: float = None, do_not_reload_embeddings: bool = False, denoising_strength: float = 0, diffusers_guidance_rescale: float = 0.7, hdr_clamp: bool = False, hdr_boundary: float = 4.0, hdr_threshold: float = 3.5, hdr_center: bool = False, hdr_channel_shift: float = 0.8, hdr_full_shift: float = 0.8, hdr_maximize: bool = False, hdr_max_center: float = 0.6, hdr_max_boundry: float = 1.0, override_settings: Dict[str, Any] = None, override_settings_restore_afterwards: bool = True, sampler_index: int = None, script_args: list = None): # pylint: disable=unused-argument + def __init__(self, sd_model=None, outpath_samples=None, outpath_grids=None, prompt: str = "", styles: List[str] = None, seed: int = -1, subseed: int = -1, subseed_strength: float = 0, seed_resize_from_h: int = -1, seed_resize_from_w: int = -1, seed_enable_extras: bool = True, sampler_name: str = None, latent_sampler: str = None, batch_size: int = 1, n_iter: int = 1, steps: int = 50, cfg_scale: float = 7.0, image_cfg_scale: float = None, clip_skip: int = 1, width: int = 512, height: int = 512, full_quality: bool = True, restore_faces: bool = False, tiling: bool = False, do_not_save_samples: bool = False, do_not_save_grid: bool = False, extra_generation_params: Dict[Any, Any] = None, overlay_images: Any = None, negative_prompt: str = None, eta: float = None, do_not_reload_embeddings: bool = False, denoising_strength: float = 0, diffusers_guidance_rescale: float = 0.7, resize_mode: int = 0, resize_name: str = 'None', scale_by: float = 0, selected_scale_tab: int = 0, hdr_clamp: bool = False, hdr_boundary: float = 4.0, hdr_threshold: float = 3.5, hdr_center: bool = False, hdr_channel_shift: float = 0.8, hdr_full_shift: float = 0.8, hdr_maximize: bool = False, hdr_max_center: float = 0.6, hdr_max_boundry: float = 1.0, override_settings: Dict[str, Any] = None, override_settings_restore_afterwards: bool = True, sampler_index: int = None, script_args: list = None): # pylint: disable=unused-argument self.outpath_samples: str = outpath_samples self.outpath_grids: str = outpath_grids self.prompt: str = prompt @@ -145,6 +145,7 @@ def __init__(self, sd_model=None, outpath_samples=None, outpath_grids=None, prom self.steps: int = steps self.hr_second_pass_steps = 0 self.cfg_scale: float = cfg_scale + self.scale_by: float = scale_by self.image_cfg_scale = image_cfg_scale self.diffusers_guidance_rescale = diffusers_guidance_rescale if devices.backend == "ipex" and width == 1024 and height == 1024: @@ -197,7 +198,8 @@ def __init__(self, sd_model=None, outpath_samples=None, outpath_grids=None, prom self.refiner_prompt = '' self.refiner_negative = '' self.ops = [] - self.resize_mode: int = 0 + self.resize_mode: int = resize_mode + self.resize_name: str = resize_name self.ddim_discretize = shared.opts.ddim_discretize self.s_min_uncond = shared.opts.s_min_uncond self.s_churn = shared.opts.s_churn @@ -636,9 +638,7 @@ def create_infotext(p: StableDiffusionProcessing, all_prompts=None, all_seeds=No args["Denoising strength"] = getattr(p, 'denoising_strength', None) # lookup by index if getattr(p, 'resize_mode', None) is not None: - RESIZE_MODES = ["None", "Resize fixed", "Crop and resize", "Resize and fill", "Latent upscale"] - args['Resize mode'] = RESIZE_MODES[p.resize_mode] - # TODO missing-by-index: inpainting_fill, inpaint_full_res, inpainting_mask_invert + args['Resize mode'] = shared.resize_modes[p.resize_mode] if 'face' in p.ops: args["Face restoration"] = shared.opts.face_restoration_model if 'color' in p.ops: @@ -846,6 +846,7 @@ def infotext(_inxex=0): # dummy function overriden if there are iterations with devices.autocast(): p.init(p.all_prompts, p.all_seeds, p.all_subseeds) extra_network_data = None + debug(f'Processing inner: args={vars(p)}') for n in range(p.n_iter): p.iteration = n if shared.state.skipped: @@ -1199,10 +1200,11 @@ def sample(self, conditioning, unconditional_conditioning, seeds, subseeds, subs class StableDiffusionProcessingImg2Img(StableDiffusionProcessing): - def __init__(self, init_images: list = None, resize_mode: int = 0, denoising_strength: float = 0.3, image_cfg_scale: float = None, mask: Any = None, mask_blur: int = 4, inpainting_fill: int = 0, inpaint_full_res: bool = True, inpaint_full_res_padding: int = 0, inpainting_mask_invert: int = 0, initial_noise_multiplier: float = None, refiner_steps: int = 5, refiner_start: float = 0, refiner_prompt: str = '', refiner_negative: str = '', **kwargs): + def __init__(self, init_images: list = None, resize_mode: int = 0, resize_name: str = 'None', denoising_strength: float = 0.3, image_cfg_scale: float = None, mask: Any = None, mask_blur: int = 4, inpainting_fill: int = 0, inpaint_full_res: bool = True, inpaint_full_res_padding: int = 0, inpainting_mask_invert: int = 0, initial_noise_multiplier: float = None, refiner_steps: int = 5, refiner_start: float = 0, refiner_prompt: str = '', refiner_negative: str = '', **kwargs): super().__init__(**kwargs) self.init_images = init_images self.resize_mode: int = resize_mode + self.resize_name: str = resize_name self.denoising_strength: float = denoising_strength self.image_cfg_scale: float = image_cfg_scale self.init_latent = None @@ -1264,11 +1266,11 @@ def init(self, all_prompts, all_seeds, all_subseeds): x1, y1, x2, y2 = crop_region mask = mask.crop(crop_region) if mask.width != self.width or mask.height != self.height: - image_mask = images.resize_image(3, mask, self.width, self.height) + image_mask = images.resize_image(3, mask, self.width, self.height, self.resize_name) self.paste_to = (x1, y1, x2-x1, y2-y1) else: if image_mask.width != self.width or image_mask.height != self.height: - image_mask = images.resize_image(self.resize_mode, image_mask, self.width, self.height) + image_mask = images.resize_image(self.resize_mode, image_mask, self.width, self.height, self.resize_name) np_mask = np.array(image_mask) np_mask = np.clip((np_mask.astype(np.float32)) * 2, 0, 255).astype(np.uint8) self.mask_for_overlay = Image.fromarray(np_mask) @@ -1280,7 +1282,13 @@ def init(self, all_prompts, all_seeds, all_subseeds): self.color_corrections = [] imgs = [] unprocessed = [] + if getattr(self, 'init_images', None) is None: + return + # raise RuntimeError("No images provided") for img in self.init_images: + if img is None: + shared.log.warning(f"Skipping empty image: images={self.init_images}") + continue self.init_img_hash = hashlib.sha256(img.tobytes()).hexdigest()[0:8] # pylint: disable=attribute-defined-outside-init self.init_img_width = img.width # pylint: disable=attribute-defined-outside-init self.init_img_height = img.height # pylint: disable=attribute-defined-outside-init @@ -1289,7 +1297,7 @@ def init(self, all_prompts, all_seeds, all_subseeds): image = images.flatten(img, shared.opts.img2img_background_color) if crop_region is None and self.resize_mode != 4: if image.width != self.width or image.height != self.height: - image = images.resize_image(self.resize_mode, image, self.width, self.height) + image = images.resize_image(self.resize_mode, image, self.width, self.height, self.resize_name) self.width = image.width self.height = image.height if image_mask is not None: @@ -1297,6 +1305,7 @@ def init(self, all_prompts, all_seeds, all_subseeds): image_masked = Image.new('RGBa', (image.width, image.height)) image_to_paste = image.convert("RGBA").convert("RGBa") image_to_mask = ImageOps.invert(self.mask_for_overlay.convert('L')) if self.mask_for_overlay is not None else None + image_to_mask = image_to_mask.resize((image.width, image.height), Image.Resampling.BILINEAR) if image_to_mask is not None else None image_masked.paste(image_to_paste, mask=image_to_mask) self.overlay_images.append(image_masked.convert('RGBA')) except Exception as e: @@ -1306,7 +1315,7 @@ def init(self, all_prompts, all_seeds, all_subseeds): if crop_region is not None: image = image.crop(crop_region) if image.width != self.width or image.height != self.height: - image = images.resize_image(3, image, self.width, self.height) + image = images.resize_image(3, image, self.width, self.height, self.resize_name) if image_mask is not None and self.inpainting_fill != 1: image = modules.masking.fill(image, latent_mask) if add_color_corrections: @@ -1327,7 +1336,7 @@ def init(self, all_prompts, all_seeds, all_subseeds): self.batch_size = len(imgs) batch_images = np.array(imgs) else: - raise RuntimeError(f"bad number of images passed: {len(imgs)}; expecting {self.batch_size} or less") + raise RuntimeError(f"Incorrect number of of images={len(imgs)} expected={self.batch_size} or less") if shared.backend == shared.Backend.DIFFUSERS: return # we've already set self.init_images and self.mask and we dont need any more processing diff --git a/modules/processing_diffusers.py b/modules/processing_diffusers.py index 4c40957d5..1fe11b817 100644 --- a/modules/processing_diffusers.py +++ b/modules/processing_diffusers.py @@ -23,7 +23,7 @@ def process_diffusers(p: StableDiffusionProcessing, seeds, prompts, negative_pro results = [] is_refiner_enabled = p.enable_hr and p.refiner_steps > 0 and p.refiner_start > 0 and p.refiner_start < 1 and shared.sd_refiner is not None - if hasattr(p, 'init_images') and len(p.init_images) > 0: + if getattr(p, 'init_images', None) is not None and len(p.init_images) > 0: tgt_width, tgt_height = 8 * math.ceil(p.init_images[0].width / 8), 8 * math.ceil(p.init_images[0].height / 8) if p.init_images[0].width != tgt_width or p.init_images[0].height != tgt_height: shared.log.debug(f'Resizing init images: original={p.init_images[0].width}x{p.init_images[0].height} target={tgt_width}x{tgt_height}') @@ -358,7 +358,7 @@ def set_pipeline_args(model, prompts: list, negative_prompts: list, prompts_2: t if k in possible: args[k] = v - hypertile_set(p, hr=len(getattr(p, 'init_images', []))) + hypertile_set(p, hr=len(getattr(p, 'init_images', [])) > 0) clean = args.copy() clean.pop('callback', None) clean.pop('callback_steps', None) diff --git a/modules/sd_hijack_hypertile.py b/modules/sd_hijack_hypertile.py index 217ffad48..dfd54dc64 100644 --- a/modules/sd_hijack_hypertile.py +++ b/modules/sd_hijack_hypertile.py @@ -74,7 +74,7 @@ def wrapper(*args, **kwargs): out = forward(x, *args[1:], **kwargs) return out if x.ndim == 4: # VAE - # TODO hyperlink vae breaks for diffusers when using non-standard sizes + # TODO hypertile vae breaks for diffusers when using non-standard sizes if nh * nw > 1: x = rearrange(x, "b c (nh h) (nw w) -> (b nh nw) c h w", nh=nh, nw=nw) out = forward(x, *args[1:], **kwargs) @@ -190,10 +190,12 @@ def hypertile_set(p, hr=False): if not shared.opts.hypertile_unet_enabled: return error_reported = False - if not hr: - height=p.height if not hr else getattr(p, 'hr_upscale_to_y', p.height) - width=p.width if not hr else getattr(p, 'hr_upscale_to_x', p.width) + if hr: + x = getattr(p, 'hr_upscale_to_x', 0) + y = getattr(p, 'hr_upscale_to_y', 0) + width = y if y > 0 else p.width + height = x if x > 0 else p.height else: - height=max(p.height, getattr(p, 'hr_upscale_to_y', p.height)) - width=max(p.width, getattr(p, 'hr_upscale_to_x', p.width)) + width=p.width + height=p.height reset_needed = True diff --git a/modules/shared.py b/modules/shared.py index 6ef5d382c..1a8e664b2 100644 --- a/modules/shared.py +++ b/modules/shared.py @@ -63,6 +63,7 @@ "outdir_save", "outdir_init_images" } +resize_modes = ["None", "Fixed", "Crop", "Fill", "Latent"] compatibility_opts = ['clip_skip', 'uni_pc_lower_order_final', 'uni_pc_order'] console = Console(log_time=True, log_time_format='%H:%M:%S-%f') @@ -563,7 +564,7 @@ def default(obj): "postprocessing_sep_upscalers": OptionInfo("

Upscaling

", "", gr.HTML), "upscaler_unload": OptionInfo(False, "Unload upscaler after processing"), # 'upscaling_max_images_in_cache': OptionInfo(5, "Maximum number of images in upscaling cache", gr.Slider, {"minimum": 0, "maximum": 10, "step": 1, "visible": False}), - "upscaler_for_img2img": OptionInfo("None", "Default upscaler for image resize operations", gr.Dropdown, lambda: {"choices": [x.name for x in sd_upscalers]}), + "upscaler_for_img2img": OptionInfo("None", "Default upscaler for image resize operations", gr.Dropdown, lambda: {"choices": [x.name for x in sd_upscalers], "visible": False}), "upscaler_tile_size": OptionInfo(192, "Upscaler tile size", gr.Slider, {"minimum": 0, "maximum": 512, "step": 16}), "upscaler_tile_overlap": OptionInfo(8, "Upscaler tile overlap", gr.Slider, {"minimum": 0, "maximum": 64, "step": 1}), })) diff --git a/modules/txt2img.py b/modules/txt2img.py index 4ef523d83..0b7a1aba6 100644 --- a/modules/txt2img.py +++ b/modules/txt2img.py @@ -8,9 +8,23 @@ debug = shared.log.debug if os.environ.get('SD_PROCESS_DEBUG', None) is not None else lambda *args, **kwargs: None -def txt2img(id_task: str, prompt: str, negative_prompt: str, prompt_styles, steps: int, sampler_index: int, latent_index: int, full_quality: bool, restore_faces: bool, tiling: bool, n_iter: int, batch_size: int, cfg_scale: float, image_cfg_scale: float, diffusers_guidance_rescale: float, clip_skip: int, seed: int, subseed: int, subseed_strength: float, seed_resize_from_h: int, seed_resize_from_w: int, height: int, width: int, enable_hr: bool, denoising_strength: float, hr_scale: float, hr_upscaler: str, hr_force: bool, hr_second_pass_steps: int, hr_resize_x: int, hr_resize_y: int, refiner_steps: int, refiner_start: int, refiner_prompt: str, refiner_negative: str, hdr_clamp, hdr_boundary, hdr_threshold, hdr_center, hdr_channel_shift, hdr_full_shift, hdr_maximize, hdr_max_center, hdr_max_boundry, override_settings_texts, *args): # pylint: disable=unused-argument +def txt2img(id_task, + prompt, negative_prompt, prompt_styles, + steps, sampler_index, latent_index, + full_quality, restore_faces, tiling, + n_iter, batch_size, + cfg_scale, image_cfg_scale, diffusers_guidance_rescale, + clip_skip, + seed, subseed, subseed_strength, seed_resize_from_h, seed_resize_from_w, + height, width, + enable_hr, denoising_strength, + hr_scale, hr_upscaler, hr_force, hr_second_pass_steps, hr_resize_x, hr_resize_y, + refiner_steps, refiner_start, refiner_prompt, refiner_negative, + hdr_clamp, hdr_boundary, hdr_threshold, hdr_center, hdr_channel_shift, hdr_full_shift, hdr_maximize, hdr_max_center, hdr_max_boundry, + override_settings_texts, + *args): - debug(f'txt2img: id_task={id_task}|prompt={prompt}|negative_prompt={negative_prompt}|prompt_styles={prompt_styles}|steps={steps}|sampler_index={sampler_index}|latent_index={latent_index}|full_quality={full_quality}|restore_faces={restore_faces}|tiling={tiling}|n_iter={n_iter}|batch_size={batch_size}|cfg_scale={cfg_scale}|clip_skip={clip_skip}|seed={seed}|subseed={subseed}|subseed_strength={subseed_strength}|seed_resize_from_h={seed_resize_from_h}|seed_resize_from_w={seed_resize_from_w}||height={height}|width={width}|enable_hr={enable_hr}|denoising_strength={denoising_strength}|hr_scale={hr_scale}|hr_upscaler={hr_upscaler}|hr_force={hr_force}|hr_second_pass_steps={hr_second_pass_steps}|hr_resize_x={hr_resize_x}|hr_resize_y={hr_resize_y}|image_cfg_scale={image_cfg_scale}|diffusers_guidance_rescale={diffusers_guidance_rescale}|refiner_steps={refiner_steps}|refiner_start={refiner_start}|refiner_prompt={refiner_prompt}|refiner_negative={refiner_negative}|override_settings_texts={override_settings_texts}') + debug(f'txt2img: id_task={id_task}|prompt={prompt}|negative={negative_prompt}|styles={prompt_styles}|steps={steps}|sampler_index={sampler_index}|latent_index={latent_index}|full_quality={full_quality}|restore_faces={restore_faces}|tiling={tiling}|batch_count={n_iter}|batch_size={batch_size}|cfg_scale={cfg_scale}|clip_skip={clip_skip}|seed={seed}|subseed={subseed}|subseed_strength={subseed_strength}|seed_resize_from_h={seed_resize_from_h}|seed_resize_from_w={seed_resize_from_w}|height={height}|width={width}|enable_hr={enable_hr}|denoising_strength={denoising_strength}|hr_scale={hr_scale}|hr_upscaler={hr_upscaler}|hr_force={hr_force}|hr_second_pass_steps={hr_second_pass_steps}|hr_resize_x={hr_resize_x}|hr_resize_y={hr_resize_y}|image_cfg_scale={image_cfg_scale}|diffusers_guidance_rescale={diffusers_guidance_rescale}|refiner_steps={refiner_steps}|refiner_start={refiner_start}|refiner_prompt={refiner_prompt}|refiner_negative={refiner_negative}|override_settings={override_settings_texts}') if shared.sd_model is None: shared.log.warning('Model not loaded') diff --git a/modules/ui.py b/modules/ui.py index fd359a680..4100e290b 100644 --- a/modules/ui.py +++ b/modules/ui.py @@ -48,6 +48,7 @@ img2img_paste_fields = [] txt2img_args = [] img2img_args = [] +paste_function = None if not cmd_opts.share and not cmd_opts.listen: @@ -60,18 +61,15 @@ def gr_show(visible=True): return {"visible": visible, "__type__": "update"} -sample_img2img = "assets/stable-samples/img2img/sketch-mountains-input.jpg" -sample_img2img = sample_img2img if os.path.exists(sample_img2img) else None -paste_function = None - - def create_output_panel(tabname, outdir): # pylint: disable=unused-argument # outdir is used by extensions a, b, c, _d, e = ui_common.create_output_panel(tabname) return a, b, c, e + def plaintext_to_html(text): # may be referenced by extensions return ui_common.plaintext_to_html(text) + def infotext_to_html(text): # may be referenced by extensions return ui_common.infotext_to_html(text) @@ -158,6 +156,16 @@ def interrogate_deepbooru(image): return gr.update() if prompt is None else prompt +def create_batch_inputs(tab): + with gr.Accordion(open=False, label="Batch", elem_id=f"{tab}_batch", elem_classes=["small-accordion"]): + with FormRow(elem_id=f"{tab}_row_batch"): + batch_count = gr.Slider(minimum=1, step=1, label='Batch count', value=1, elem_id=f"{tab}_batch_count") + batch_size = gr.Slider(minimum=1, maximum=32, step=1, label='Batch size', value=1, elem_id=f"{tab}_batch_size") + batch_switch_btn = ToolButton(value=symbols.switch, elem_id=f"{tab}_batch_switch_btn", label="Switch dims") + batch_switch_btn.click(lambda w, h: (h, w), inputs=[batch_count, batch_size], outputs=[batch_count, batch_size], show_progress=False) + return batch_count, batch_size + + def create_seed_inputs(tab): with gr.Accordion(open=False, label="Seed", elem_id=f"{tab}_seed_group", elem_classes=["small-accordion"]): with FormRow(elem_id=f"{tab}_seed_row", variant="compact"): @@ -174,14 +182,89 @@ def create_seed_inputs(tab): seed_resize_from_h = gr.Slider(minimum=0, maximum=4096, step=8, label="Resize seed from height", value=0, elem_id=f"{tab}_seed_resize_from_h") random_seed.click(fn=lambda: [-1, -1], show_progress=False, inputs=[], outputs=[seed, subseed]) random_subseed.click(fn=lambda: -1, show_progress=False, inputs=[], outputs=[subseed]) - return seed, reuse_seed, subseed, reuse_subseed, subseed_strength, seed_resize_from_h, seed_resize_from_w + return seed, reuse_seed, subseed, reuse_subseed, subseed_strength, seed_resize_from_h, seed_resize_from_w + + +def create_advanced_inputs(tab): + with gr.Accordion(open=False, label="Advanced", elem_id=f"{tab}_advanced", elem_classes=["small-accordion"]): + with gr.Group(): + with FormRow(): + cfg_scale = gr.Slider(minimum=0.0, maximum=30.0, step=0.1, label='CFG scale', value=6.0, elem_id=f"{tab}_cfg_scale") + clip_skip = gr.Slider(label='CLIP skip', value=1, minimum=1, maximum=14, step=1, elem_id=f"{tab}_clip_skip", interactive=True) + with FormRow(): + image_cfg_scale = gr.Slider(minimum=0.0, maximum=30.0, step=0.1, label='Secondary CFG scale', value=6.0, elem_id=f"{tab}_image_cfg_scale") + diffusers_guidance_rescale = gr.Slider(minimum=0.0, maximum=1.0, step=0.05, label='Guidance rescale', value=0.7, elem_id=f"{tab}_image_cfg_rescale", visible=modules.shared.backend == modules.shared.Backend.DIFFUSERS) + with gr.Group(): + with FormRow(): + full_quality = gr.Checkbox(label='Full quality', value=True, elem_id=f"{tab}_full_quality") + restore_faces = gr.Checkbox(label='Face restore', value=False, visible=len(modules.shared.face_restorers) > 1, elem_id=f"{tab}_restore_faces") + tiling = gr.Checkbox(label='Tiling', value=False, elem_id=f"{tab}_tiling", visible=modules.shared.backend == modules.shared.Backend.ORIGINAL) + with gr.Group(visible=modules.shared.backend == modules.shared.Backend.DIFFUSERS): + with FormRow(): + hdr_clamp = gr.Checkbox(label='HDR clamp', value=False, elem_id=f"{tab}_hdr_clamp") + hdr_boundary = gr.Slider(minimum=0.0, maximum=10.0, step=0.1, value=4.0, label='Range', elem_id=f"{tab}_hdr_boundary") + hdr_threshold = gr.Slider(minimum=0.0, maximum=1.0, step=0.01, value=0.95, label='Threshold', elem_id=f"{tab}_hdr_threshold") + with FormRow(): + hdr_center = gr.Checkbox(label='HDR center', value=False, elem_id=f"{tab}_hdr_center") + hdr_channel_shift = gr.Slider(minimum=0.0, maximum=2.0, step=0.1, value=1.0, label='Channel shift', elem_id=f"{tab}_hdr_channel_shift") + hdr_full_shift = gr.Slider(minimum=0.0, maximum=2.0, step=0.1, value=1, label='Full shift', elem_id=f"{tab}_hdr_full_shift") + with FormRow(): + hdr_maximize = gr.Checkbox(label='HDR maximize', value=False, elem_id=f"{tab}_hdr_maximize") + hdr_max_center = gr.Slider(minimum=0.0, maximum=2.0, step=0.1, value=0.6, label='Center', elem_id=f"{tab}_hdr_max_center") + hdr_max_boundry = gr.Slider(minimum=0.5, maximum=2.0, step=0.1, value=1.0, label='Range', elem_id=f"{tab}_hdr_max_boundry") + return cfg_scale, clip_skip, image_cfg_scale, diffusers_guidance_rescale, full_quality, restore_faces, tiling, hdr_clamp, hdr_boundary, hdr_threshold, hdr_center, hdr_channel_shift, hdr_full_shift, hdr_maximize, hdr_max_center, hdr_max_boundry + + +def create_resize_inputs(tab, images): + dummy_component = gr.Number(visible=False, value=0) + with gr.Accordion(open=False, label="Resize", elem_classes=["small-accordion"], elem_id=f"{tab}_resize_group"): + with gr.Row(): + resize_mode = gr.Radio(label="Resize mode", elem_id=f"{tab}_resize_mode", choices=modules.shared.resize_modes, type="index", value="None") + with gr.Row(): + resize_name = gr.Dropdown(label="Resize method", elem_id=f"{tab}_resize_name", choices=[x.name for x in modules.shared.sd_upscalers], value=opts.upscaler_for_img2img) + + with FormRow(visible=True) as _resize_group: + with gr.Column(elem_id=f"{tab}_column_size", scale=4): + selected_scale_tab = gr.State(value=0) # pylint: disable=abstract-class-instantiated + with gr.Tabs(): + with gr.Tab(label="Resize to") as tab_scale_to: + with FormRow(): + with gr.Column(elem_id=f"{tab}_column_size", scale=4): + with FormRow(): + width = gr.Slider(minimum=64, maximum=4096, step=8, label="Width", value=512, elem_id=f"{tab}_width") + height = gr.Slider(minimum=64, maximum=4096, step=8, label="Height", value=512, elem_id=f"{tab}_height") + with gr.Column(elem_id=f"{tab}_column_dim", scale=1, elem_classes="dimensions-tools"): + with FormRow(): + res_switch_btn = ToolButton(value=symbols.switch, elem_id=f"{tab}_res_switch_btn") + res_switch_btn.click(lambda w, h: (h, w), inputs=[width, height], outputs=[width, height], show_progress=False) + detect_image_size_btn = ToolButton(value=symbols.detect, elem_id=f"{tab}_detect_image_size_btn") + detect_image_size_btn.click(fn=lambda w, h, _: (w or gr.update(), h or gr.update()), _js="currentImg2imgSourceResolution", inputs=[dummy_component, dummy_component, dummy_component], outputs=[width, height], show_progress=False) + + with gr.Tab(label="Resize by") as tab_scale_by: + scale_by = gr.Slider(minimum=0.05, maximum=4.0, step=0.05, label="Scale", value=1.0, elem_id=f"{tab}_scale") + with FormRow(): + scale_by_html = FormHTML(resize_from_to_html(0, 0, 0.0), elem_id=f"{tab}_scale_resolution_preview") + gr.Slider(label="Unused", elem_id=f"{tab}_unused_scale_by_slider") + button_update_resize_to = gr.Button(visible=False, elem_id=f"{tab}_update_resize_to") + + on_change_args = dict(fn=resize_from_to_html, _js="currentImg2imgSourceResolution", inputs=[dummy_component, dummy_component, scale_by], outputs=scale_by_html, show_progress=False) + scale_by.release(**on_change_args) + button_update_resize_to.click(**on_change_args) + + for component in images: + component.change(fn=lambda: None, _js="updateImg2imgResizeToTextAfterChangingImage", inputs=[], outputs=[], show_progress=False) + + tab_scale_to.select(fn=lambda: 0, inputs=[], outputs=[selected_scale_tab]) + tab_scale_by.select(fn=lambda: 1, inputs=[], outputs=[selected_scale_tab]) + # resize_mode.change(fn=lambda x: gr.update(visible=x != 0), inputs=[resize_mode], outputs=[_resize_group]) + return resize_mode, resize_name, width, height, scale_by, selected_scale_tab def connect_clear_prompt(button): # pylint: disable=unused-argument pass -def connect_reuse_seed(seed: gr.Number, reuse_seed: gr.Button, generation_info: gr.Textbox, dummy_component, is_subseed): +def connect_reuse_seed(seed: gr.Number, reuse_seed: gr.Button, generation_info: gr.Textbox, is_subseed): """ Connects a 'reuse (sub)seed' button's click event so that it copies last used (sub)seed value from generation info the to the seed field. If copying subseed and subseed strength was 0, i.e. no variation seed was used, it copies the normal seed value instead.""" @@ -202,6 +285,7 @@ def copy_seed(gen_info_string: str, index): log.error(f"Error parsing JSON generation info: {gen_info_string}") return [res, gr_show(False)] + dummy_component = gr.Number(visible=False, value=0) reuse_seed.click(fn=copy_seed, _js="(x, y) => [x, selected_gallery_index()]", show_progress=False, inputs=[generation_info, dummy_component], outputs=[seed, dummy_component]) @@ -255,8 +339,8 @@ def create_toprow(is_img2img: bool = False, id_part: str = None): button_deepbooru = None if is_img2img: with gr.Column(scale=1, elem_classes="interrogate-col"): - button_interrogate = gr.Button('Interrogate\nCLIP', elem_id="interrogate") - button_deepbooru = gr.Button('Interrogate\nDeepBooru', elem_id="deepbooru") + button_interrogate = gr.Button('Interrogate\nCLIP', elem_id=f"{id_part}_interrogate") + button_deepbooru = gr.Button('Interrogate\nDeepBooru', elem_id=f"{id_part}_deepbooru") with gr.Column(scale=1, elem_id=f"{id_part}_actions_column"): with gr.Row(elem_id=f"{id_part}_generate_box"): submit = gr.Button('Generate', elem_id=f"{id_part}_generate", variant='primary') @@ -278,14 +362,14 @@ def create_toprow(is_img2img: bool = False, id_part: str = None): negative_token_counter = gr.HTML(value="0/75", elem_id=f"{id_part}_negative_token_counter", elem_classes=["token-counter"]) negative_token_button = gr.Button(visible=False, elem_id=f"{id_part}_negative_token_button") with gr.Row(elem_id=f"{id_part}_styles_row"): - prompt_styles = gr.Dropdown(label="Styles", elem_id=f"{id_part}_styles", choices=[style.name for style in modules.shared.prompt_styles.styles.values()], value=[], multiselect=True) - prompt_styles_btn_refresh = ToolButton(symbols.refresh, elem_id=f"{id_part}_styles_refresh", visible=True) - prompt_styles_btn_refresh.click(fn=lambda: gr.update(choices=[style.name for style in modules.shared.prompt_styles.styles.values()]), inputs=[], outputs=[prompt_styles]) - prompt_styles_btn_select = gr.Button('Select', elem_id=f"{id_part}_styles_select", visible=False) - prompt_styles_btn_select.click(_js="applyStyles", fn=parse_style, inputs=[prompt_styles], outputs=[prompt_styles]) - prompt_styles_btn_apply = ToolButton(symbols.apply, elem_id=f"{id_part}_extra_apply", visible=False) - prompt_styles_btn_apply.click(fn=apply_styles, inputs=[prompt, negative_prompt, prompt_styles], outputs=[prompt, negative_prompt, prompt_styles]) - return prompt, prompt_styles, negative_prompt, submit, button_interrogate, button_deepbooru, button_paste, button_extra, token_counter, token_button, negative_token_counter, negative_token_button + styles = gr.Dropdown(label="Styles", elem_id=f"{id_part}_styles", choices=[style.name for style in modules.shared.prompt_styles.styles.values()], value=[], multiselect=True) + styles_btn_refresh = ToolButton(symbols.refresh, elem_id=f"{id_part}_styles_refresh", visible=True) + styles_btn_refresh.click(fn=lambda: gr.update(choices=[style.name for style in modules.shared.prompt_styles.styles.values()]), inputs=[], outputs=[styles]) + styles_btn_select = gr.Button('Select', elem_id=f"{id_part}_styles_select", visible=False) + styles_btn_select.click(_js="applyStyles", fn=parse_style, inputs=[styles], outputs=[styles]) + styles_btn_apply = ToolButton(symbols.apply, elem_id=f"{id_part}_extra_apply", visible=False) + styles_btn_apply.click(fn=apply_styles, inputs=[prompt, negative_prompt, styles], outputs=[prompt, negative_prompt, styles]) + return prompt, styles, negative_prompt, submit, button_interrogate, button_deepbooru, button_paste, button_extra, token_counter, token_button, negative_token_counter, negative_token_button def setup_progressbar(*args, **kwargs): # pylint: disable=unused-argument @@ -362,6 +446,44 @@ def set_sampler_diffuser_options(sampler_options): return steps, sampler_index +def create_sampler_inputs(tab): + with gr.Accordion(open=False, label="Sampler", elem_id=f"{tab}_sampler", elem_classes=["small-accordion"]): + with FormRow(elem_id=f"{tab}_row_sampler"): + modules.sd_samplers.set_samplers() + steps, sampler_index = create_sampler_and_steps_selection(modules.sd_samplers.samplers, tab) + return steps, sampler_index + + +def create_hires_inputs(tab): + with gr.Accordion(open=False, label="Second pass", elem_id=f"{tab}_second_pass", elem_classes=["small-accordion"]): + with FormGroup(): + with FormRow(elem_id=f"{tab}_hires_row1"): + enable_hr = gr.Checkbox(label='Enable second pass', value=False, elem_id=f"{tab}_enable_hr") + with FormRow(elem_id=f"{tab}_hires_row2"): + latent_index = gr.Dropdown(label='Secondary sampler', elem_id=f"{tab}_sampling_alt", choices=[x.name for x in modules.sd_samplers.samplers], value='Default', type="index") + denoising_strength = gr.Slider(minimum=0.0, maximum=0.99, step=0.01, label='Denoising strength', value=0.5, elem_id=f"{tab}_denoising_strength") + with FormRow(elem_id=f"{tab}_hires_finalres", variant="compact"): + hr_final_resolution = FormHTML(value="", elem_id=f"{tab}_hr_finalres", label="Upscaled resolution", interactive=False) + with FormRow(elem_id=f"{tab}_hires_fix_row1", variant="compact"): + hr_upscaler = gr.Dropdown(label="Upscaler", elem_id=f"{tab}_hr_upscaler", choices=[*modules.shared.latent_upscale_modes, *[x.name for x in modules.shared.sd_upscalers]], value=modules.shared.latent_upscale_default_mode) + hr_force = gr.Checkbox(label='Force Hires', value=False, elem_id=f"{tab}_hr_force") + with FormRow(elem_id=f"{tab}_hires_fix_row2", variant="compact"): + hr_second_pass_steps = gr.Slider(minimum=0, maximum=99, step=1, label='Hires steps', elem_id=f"{tab}_steps_alt", value=20) + hr_scale = gr.Slider(minimum=1.0, maximum=4.0, step=0.05, label="Upscale by", value=2.0, elem_id=f"{tab}_hr_scale") + with FormRow(elem_id=f"{tab}_hires_fix_row3", variant="compact"): + hr_resize_x = gr.Slider(minimum=0, maximum=4096, step=8, label="Resize width to", value=0, elem_id=f"{tab}_hr_resize_x") + hr_resize_y = gr.Slider(minimum=0, maximum=4096, step=8, label="Resize height to", value=0, elem_id=f"{tab}_hr_resize_y") + with FormGroup(visible=modules.shared.backend == modules.shared.Backend.DIFFUSERS): + with FormRow(elem_id=f"{tab}_refiner_row1", variant="compact"): + refiner_start = gr.Slider(minimum=0.0, maximum=1.0, step=0.05, label='Refiner start', value=0.8, elem_id=f"{tab}_refiner_start") + refiner_steps = gr.Slider(minimum=0, maximum=99, step=1, label="Refiner steps", elem_id=f"{tab}_refiner_steps", value=5) + with FormRow(elem_id=f"{tab}_refiner_row3", variant="compact"): + refiner_prompt = gr.Textbox(value='', label='Secondary Prompt', elem_id=f"{tab}_refiner_prompt") + with FormRow(elem_id="txt2img_refiner_row4", variant="compact"): + refiner_negative = gr.Textbox(value='', label='Secondary negative prompt', elem_id=f"{tab}_refiner_neg_prompt") + return enable_hr, latent_index, denoising_strength, hr_final_resolution, hr_upscaler, hr_force, hr_second_pass_steps, hr_scale, hr_resize_x, hr_resize_y, refiner_steps, refiner_start, refiner_prompt, refiner_negative + + def get_value_for_setting(key): value = getattr(opts, key) info = opts.data_labels[key] @@ -371,13 +493,14 @@ def get_value_for_setting(key): def ordered_ui_categories(): - return ['dimensions', 'sampler', 'seed', 'denoising', 'cfg', 'checkboxes', 'accordions', 'override_settings', 'scripts'] # TODO: a1111 compatibility item, not implemented + return ['dimensions', 'sampler', 'seed', 'denoising', 'cfg', 'checkboxes', 'accordions', 'override_settings', 'scripts'] # a1111 compatibility item, not implemented -def create_override_settings_dropdown(tabname, row): # pylint: disable=unused-argument - dropdown = gr.Dropdown([], label="Override settings", visible=False, elem_id=f"{tabname}_override_settings", multiselect=True) - dropdown.change(fn=lambda x: gr.Dropdown.update(visible=len(x) > 0), inputs=[dropdown], outputs=[dropdown]) - return dropdown +def create_override_inputs(tab): # pylint: disable=unused-argument + with FormRow(elem_id=f"{tab}_override_settings_row"): + override_settings = gr.Dropdown([], value=None, label="Override settings", visible=False, elem_id=f"{tab}_override_settings", multiselect=True) + override_settings.change(fn=lambda x: gr.Dropdown.update(visible=len(x) > 0), inputs=[override_settings], outputs=[override_settings]) + return override_settings def create_ui(startup_timer = None): @@ -390,12 +513,14 @@ def create_ui(startup_timer = None): modules.scripts.scripts_current = modules.scripts.scripts_txt2img modules.scripts.scripts_txt2img.initialize_scripts(is_img2img=False) with gr.Blocks(analytics_enabled=False) as txt2img_interface: - txt2img_prompt, txt2img_prompt_styles, txt2img_negative_prompt, submit, _interrogate, _deepbooru, txt2img_paste, extra_networks_button, token_counter, token_button, negative_token_counter, negative_token_button = create_toprow(is_img2img=False) - dummy_component = gr.Label(visible=False) + txt2img_prompt, txt2img_prompt_styles, txt2img_negative_prompt, txt2img_submit, _interrogate, _deepbooru, txt2img_paste, txt2img_extra_networks_button, txt2img_token_counter, txt2img_token_button, txt2img_negative_token_counter, txt2img_negative_token_button = create_toprow(is_img2img=False, id_part="txt2img") + txt_prompt_img = gr.File(label="", elem_id="txt2img_prompt_image", file_count="single", type="binary", visible=False) + txt_prompt_img.change(fn=modules.images.image_data, inputs=[txt_prompt_img], outputs=[txt2img_prompt, txt_prompt_img]) + with FormRow(variant='compact', elem_id="txt2img_extra_networks", visible=False) as extra_networks_ui: from modules import ui_extra_networks - extra_networks_ui = ui_extra_networks.create_ui(extra_networks_ui, extra_networks_button, 'txt2img', skip_indexing=opts.extra_network_skip_indexing) + extra_networks_ui = ui_extra_networks.create_ui(extra_networks_ui, txt2img_extra_networks_button, 'txt2img', skip_indexing=opts.extra_network_skip_indexing) timer.startup.record('ui-extra-networks') with gr.Row(elem_id="txt2img_interface", equal_height=False): @@ -405,79 +530,18 @@ def create_ui(startup_timer = None): width = gr.Slider(minimum=64, maximum=4096, step=8, label="Width", value=512, elem_id="txt2img_width") height = gr.Slider(minimum=64, maximum=4096, step=8, label="Height", value=512, elem_id="txt2img_height") res_switch_btn = ToolButton(value=symbols.switch, elem_id="txt2img_res_switch_btn", label="Switch dims") + res_switch_btn.click(lambda w, h: (h, w), inputs=[width, height], outputs=[width, height], show_progress=False) with FormGroup(elem_classes="settings-accordion"): - with gr.Accordion(open=False, label="Sampler", elem_id="txt2img_sampler", elem_classes=["small-accordion"]): - with FormRow(elem_id="txt2img_row_sampler"): - modules.sd_samplers.set_samplers() - steps, sampler_index = create_sampler_and_steps_selection(modules.sd_samplers.samplers, "txt2img") - - with gr.Accordion(open=False, label="Batch", elem_id="txt2img_batch", elem_classes=["small-accordion"]): - with FormRow(elem_id="txt2img_row_batch"): - batch_count = gr.Slider(minimum=1, step=1, label='Batch count', value=1, elem_id="txt2img_batch_count") - batch_size = gr.Slider(minimum=1, maximum=32, step=1, label='Batch size', value=1, elem_id="txt2img_batch_size") - batch_switch_btn = ToolButton(value=symbols.switch, elem_id="txt2img_batch_switch_btn", label="Switch dims") + steps, sampler_index = create_sampler_inputs('txt2img') + batch_count, batch_size = create_batch_inputs('txt2img') seed, reuse_seed, subseed, reuse_subseed, subseed_strength, seed_resize_from_h, seed_resize_from_w = create_seed_inputs('txt2img') + cfg_scale, clip_skip, image_cfg_scale, diffusers_guidance_rescale, full_quality, restore_faces, tiling, hdr_clamp, hdr_boundary, hdr_threshold, hdr_center, hdr_channel_shift, hdr_full_shift, hdr_maximize, hdr_max_center, hdr_max_boundry = create_advanced_inputs('txt2img') + enable_hr, latent_index, denoising_strength, hr_final_resolution, hr_upscaler, hr_force, hr_second_pass_steps, hr_scale, hr_resize_x, hr_resize_y, refiner_steps, refiner_start, refiner_prompt, refiner_negative = create_hires_inputs('txt2img') + override_settings = create_override_inputs('txt2img') - with gr.Accordion(open=False, label="Advanced", elem_id="txt2img_advanced", elem_classes=["small-accordion"]): - with gr.Group(): - with FormRow(): - cfg_scale = gr.Slider(minimum=0.0, maximum=30.0, step=0.1, label='CFG scale', value=6.0, elem_id="txt2img_cfg_scale") - clip_skip = gr.Slider(label='CLIP skip', value=1, minimum=1, maximum=14, step=1, elem_id='txt2img_clip_skip', interactive=True) - with FormRow(): - image_cfg_scale = gr.Slider(minimum=0.0, maximum=30.0, step=0.1, label='Secondary CFG scale', value=6.0, elem_id="txt2img_image_cfg_scale") - diffusers_guidance_rescale = gr.Slider(minimum=0.0, maximum=1.0, step=0.05, label='Guidance rescale', value=0.7, elem_id="txt2img_image_cfg_rescale") - with gr.Group(): - with FormRow(): - full_quality = gr.Checkbox(label='Full quality', value=True, elem_id="txt2img_full_quality") - restore_faces = gr.Checkbox(label='Face restore', value=False, visible=len(modules.shared.face_restorers) > 1, elem_id="txt2img_restore_faces") - tiling = gr.Checkbox(label='Tiling', value=False, elem_id="txt2img_tiling") - with gr.Group(): - with FormRow(): - hdr_clamp = gr.Checkbox(label='HDR clamp', value=False, elem_id="txt2img_hdr_clamp") - hdr_boundary = gr.Slider(minimum=0.0, maximum=10.0, step=0.1, value=4.0, label='Range', elem_id="txt2img_hdr_boundary") - hdr_threshold = gr.Slider(minimum=0.0, maximum=1.0, step=0.01, value=0.95, label='Threshold', elem_id="txt2img_hdr_threshold") - with FormRow(): - hdr_center = gr.Checkbox(label='HDR center', value=False, elem_id="txt2img_hdr_center") - hdr_channel_shift = gr.Slider(minimum=0.0, maximum=2.0, step=0.1, value=1.0, label='Channel shift', elem_id="txt2img_hdr_channel_shift") - hdr_full_shift = gr.Slider(minimum=0.0, maximum=2.0, step=0.1, value=1, label='Full shift', elem_id="txt2img_hdr_full_shift") - with FormRow(): - hdr_maximize = gr.Checkbox(label='HDR maximize', value=False, elem_id="txt2img_hdr_maximize") - hdr_max_center = gr.Slider(minimum=0.0, maximum=2.0, step=0.1, value=0.6, label='Center', elem_id="txt2img_hdr_max_center") - hdr_max_boundry = gr.Slider(minimum=0.5, maximum=2.0, step=0.1, value=1.0, label='Range', elem_id="txt2img_hdr_max_boundry") - - with gr.Accordion(open=False, label="Second pass", elem_id="txt2img_second_pass", elem_classes=["small-accordion"]): - with FormGroup(): - with FormRow(elem_id="sampler_selection_txt2img_alt_row1"): - enable_hr = gr.Checkbox(label='Enable second pass', value=False, elem_id="txt2img_enable_hr") - with FormRow(elem_id="sampler_selection_txt2img_alt_row1"): - latent_index = gr.Dropdown(label='Secondary sampler', elem_id="txt2img_sampling_alt", choices=[x.name for x in modules.sd_samplers.samplers], value='Default', type="index") - denoising_strength = gr.Slider(minimum=0.0, maximum=0.99, step=0.01, label='Denoising strength', value=0.5, elem_id="txt2img_denoising_strength") - with FormRow(elem_id="txt2img_hires_finalres", variant="compact"): - hr_final_resolution = FormHTML(value="", elem_id="txtimg_hr_finalres", label="Upscaled resolution", interactive=False) - with FormRow(elem_id="txt2img_hires_fix_row1", variant="compact"): - hr_upscaler = gr.Dropdown(label="Upscaler", elem_id="txt2img_hr_upscaler", choices=[*modules.shared.latent_upscale_modes, *[x.name for x in modules.shared.sd_upscalers]], value=modules.shared.latent_upscale_default_mode) - hr_force = gr.Checkbox(label='Force Hires', value=False, elem_id="txt2img_hr_force") - with FormRow(elem_id="txt2img_hires_fix_row2", variant="compact"): - hr_second_pass_steps = gr.Slider(minimum=0, maximum=99, step=1, label='Hires steps', elem_id="txt2img_steps_alt", value=20) - hr_scale = gr.Slider(minimum=1.0, maximum=4.0, step=0.05, label="Upscale by", value=2.0, elem_id="txt2img_hr_scale") - with FormRow(elem_id="txt2img_hires_fix_row3", variant="compact"): - hr_resize_x = gr.Slider(minimum=0, maximum=4096, step=8, label="Resize width to", value=0, elem_id="txt2img_hr_resize_x") - hr_resize_y = gr.Slider(minimum=0, maximum=4096, step=8, label="Resize height to", value=0, elem_id="txt2img_hr_resize_y") - with FormGroup(visible=modules.shared.backend == modules.shared.Backend.DIFFUSERS): - with FormRow(elem_id="txt2img_refiner_row1", variant="compact"): - refiner_start = gr.Slider(minimum=0.0, maximum=1.0, step=0.05, label='Refiner start', value=0.8, elem_id="txt2img_refiner_start") - refiner_steps = gr.Slider(minimum=0, maximum=99, step=1, label="Refiner steps", elem_id="txt2img_refiner_steps", value=5) - with FormRow(elem_id="txt2img_refiner_row3", variant="compact"): - refiner_prompt = gr.Textbox(value='', label='Secondary Prompt') - with FormRow(elem_id="txt2img_refiner_row4", variant="compact"): - refiner_negative = gr.Textbox(value='', label='Secondary negative prompt') - - with FormRow(elem_id="txt2img_override_settings_row") as row: - override_settings = create_override_settings_dropdown('txt2img', row) - - custom_inputs = modules.scripts.scripts_txt2img.setup_ui() + txt2img_script_inputs = modules.scripts.scripts_txt2img.setup_ui() hr_resolution_preview_inputs = [width, height, hr_scale, hr_resize_x, hr_resize_y, hr_upscaler] for preview_input in hr_resolution_preview_inputs: @@ -489,21 +553,19 @@ def create_ui(startup_timer = None): show_progress=False, ) - txt2img_gallery, generation_info, html_info, _html_info_formatted, html_log = ui_common.create_output_panel("txt2img") - connect_reuse_seed(seed, reuse_seed, generation_info, dummy_component, is_subseed=False) - connect_reuse_seed(subseed, reuse_subseed, generation_info, dummy_component, is_subseed=True) + txt2img_gallery, txt2img_generation_info, txt2img_html_info, _txt2img_html_info_formatted, txt2img_html_log = ui_common.create_output_panel("txt2img") + connect_reuse_seed(seed, reuse_seed, txt2img_generation_info, is_subseed=False) + connect_reuse_seed(subseed, reuse_subseed, txt2img_generation_info, is_subseed=True) global txt2img_args # pylint: disable=global-statement + dummy_component = gr.Textbox(visible=False, value='dummy') txt2img_args = [ dummy_component, - txt2img_prompt, txt2img_negative_prompt, - txt2img_prompt_styles, - steps, - sampler_index, latent_index, + txt2img_prompt, txt2img_negative_prompt, txt2img_prompt_styles, + steps, sampler_index, latent_index, full_quality, restore_faces, tiling, batch_count, batch_size, - cfg_scale, image_cfg_scale, - diffusers_guidance_rescale, + cfg_scale, image_cfg_scale, diffusers_guidance_rescale, clip_skip, seed, subseed, subseed_strength, seed_resize_from_h, seed_resize_from_w, height, width, @@ -516,21 +578,17 @@ def create_ui(startup_timer = None): txt2img_dict = dict( fn=wrap_gradio_gpu_call(modules.txt2img.txt2img, extra_outputs=[None, '', '']), _js="submit_txt2img", - inputs=txt2img_args + custom_inputs, + inputs=txt2img_args + txt2img_script_inputs, outputs=[ txt2img_gallery, - generation_info, - html_info, - html_log, + txt2img_generation_info, + txt2img_html_info, + txt2img_html_log, ], show_progress=False, ) txt2img_prompt.submit(**txt2img_dict) - submit.click(**txt2img_dict) - - res_switch_btn.click(lambda w, h: (h, w), inputs=[width, height], outputs=[width, height], show_progress=False) - batch_switch_btn.click(lambda w, h: (h, w), inputs=[batch_count, batch_size], outputs=[batch_count, batch_size], show_progress=False) - txt_prompt_img.change(fn=modules.images.image_data, inputs=[txt_prompt_img], outputs=[txt2img_prompt, txt_prompt_img]) + txt2img_submit.click(**txt2img_dict) global txt2img_paste_fields # pylint: disable=global-statement txt2img_paste_fields = [ @@ -581,8 +639,8 @@ def create_ui(startup_timer = None): parameters_copypaste.add_paste_fields("txt2img", None, txt2img_paste_fields, override_settings) parameters_copypaste.register_paste_params_button(parameters_copypaste.ParamBinding(paste_button=txt2img_paste, tabname="txt2img", source_text_component=txt2img_prompt, source_image_component=None)) - token_button.click(fn=wrap_queued_call(update_token_counter), inputs=[txt2img_prompt, steps], outputs=[token_counter]) - negative_token_button.click(fn=wrap_queued_call(update_token_counter), inputs=[txt2img_negative_prompt, steps], outputs=[negative_token_counter]) + txt2img_token_button.click(fn=wrap_queued_call(update_token_counter), inputs=[txt2img_prompt, steps], outputs=[txt2img_token_counter]) + txt2img_negative_token_button.click(fn=wrap_queued_call(update_token_counter), inputs=[txt2img_negative_prompt, steps], outputs=[txt2img_negative_token_counter]) ui_extra_networks.setup_ui(extra_networks_ui, txt2img_gallery) @@ -592,12 +650,12 @@ def create_ui(startup_timer = None): modules.scripts.scripts_current = modules.scripts.scripts_img2img modules.scripts.scripts_img2img.initialize_scripts(is_img2img=True) with gr.Blocks(analytics_enabled=False) as img2img_interface: - img2img_prompt, img2img_prompt_styles, img2img_negative_prompt, submit, img2img_interrogate, img2img_deepbooru, img2img_paste, extra_networks_button, token_counter, token_button, negative_token_counter, negative_token_button = create_toprow(is_img2img=True) + img2img_prompt, img2img_prompt_styles, img2img_negative_prompt, submit, img2img_interrogate, img2img_deepbooru, img2img_paste, img2img_extra_networks_button, img2img_token_counter, img2img_token_button, img2img_negative_token_counter, img2img_negative_token_button = create_toprow(is_img2img=True, id_part="img2img") img2img_prompt_img = gr.File(label="", elem_id="img2img_prompt_image", file_count="single", type="binary", visible=False) with FormRow(variant='compact', elem_id="img2img_extra_networks", visible=False) as extra_networks_ui: from modules import ui_extra_networks - extra_networks_ui_img2img = ui_extra_networks.create_ui(extra_networks_ui, extra_networks_button, 'img2img', skip_indexing=opts.extra_network_skip_indexing) + extra_networks_ui_img2img = ui_extra_networks.create_ui(extra_networks_ui, img2img_extra_networks_button, 'img2img', skip_indexing=opts.extra_network_skip_indexing) with FormRow(elem_id="img2img_interface", equal_height=False): with gr.Column(variant='compact', elem_id="img2img_settings"): @@ -671,78 +729,18 @@ def update_orig(image, state): button.click(fn=lambda: None, _js=f"switch_to_{name.replace(' ', '_')}", inputs=[], outputs=[]) with FormGroup(elem_classes="settings-accordion"): - with gr.Accordion(open=False, label="Sampler", elem_classes=["small-accordion"], elem_id="img2img_sampling_group"): - modules.sd_samplers.set_samplers() - steps, sampler_index = create_sampler_and_steps_selection(modules.sd_samplers.samplers_for_img2img, "img2img") - - with gr.Accordion(open=False, label="Resize", elem_classes=["small-accordion"], elem_id="img2img_resize_group"): - with gr.Row(): - resize_mode = gr.Radio(label="Resize mode", elem_id="resize_mode", choices=["None", "Resize fixed", "Crop and resize", "Resize and fill", "Latent upscale"], type="index", value="None") - - with FormRow(): - with gr.Column(elem_id="img2img_column_size", scale=4): - selected_scale_tab = gr.State(value=0) # pylint: disable=abstract-class-instantiated - - with gr.Tabs(): - with gr.Tab(label="Resize to") as tab_scale_to: - with FormRow(): - with gr.Column(elem_id="img2img_column_size", scale=4): - with FormRow(): - width = gr.Slider(minimum=64, maximum=4096, step=8, label="Width", value=512, elem_id="img2img_width") - height = gr.Slider(minimum=64, maximum=4096, step=8, label="Height", value=512, elem_id="img2img_height") - with gr.Column(elem_id="img2img_column_dim", scale=1, elem_classes="dimensions-tools"): - with FormRow(): - res_switch_btn = ToolButton(value=symbols.switch, elem_id="img2img_res_switch_btn") - detect_image_size_btn = ToolButton(value=symbols.detect, elem_id="img2img_detect_image_size_btn") - - with gr.Tab(label="Resize by") as tab_scale_by: - scale_by = gr.Slider(minimum=0.05, maximum=4.0, step=0.05, label="Scale", value=1.0, elem_id="img2img_scale") - - with FormRow(): - scale_by_html = FormHTML(resize_from_to_html(0, 0, 0.0), elem_id="img2img_scale_resolution_preview") - gr.Slider(label="Unused", elem_id="img2img_unused_scale_by_slider") - button_update_resize_to = gr.Button(visible=False, elem_id="img2img_update_resize_to") - - on_change_args = dict( - fn=resize_from_to_html, - _js="currentImg2imgSourceResolution", - inputs=[dummy_component, dummy_component, scale_by], - outputs=scale_by_html, - show_progress=False, - ) - - scale_by.release(**on_change_args) - button_update_resize_to.click(**on_change_args) - - for component in [init_img, sketch]: - component.change(fn=lambda: None, _js="updateImg2imgResizeToTextAfterChangingImage", inputs=[], outputs=[], show_progress=False) - - tab_scale_to.select(fn=lambda: 0, inputs=[], outputs=[selected_scale_tab]) - tab_scale_by.select(fn=lambda: 1, inputs=[], outputs=[selected_scale_tab]) - - with gr.Accordion(open=False, label="Batch", elem_classes=["small-accordion"], elem_id="img2img_batch_group"): - with FormRow(elem_id="img2img_column_batch"): - batch_count = gr.Slider(minimum=1, step=1, label='Batch count', value=1, elem_id="img2img_batch_count") - batch_size = gr.Slider(minimum=1, maximum=8, step=1, label='Batch size', value=1, elem_id="img2img_batch_size") + steps, sampler_index = create_sampler_inputs('img2img') + resize_mode, resize_name, width, height, scale_by, selected_scale_tab = create_resize_inputs('img2img', [init_img, sketch]) + batch_count, batch_size = create_batch_inputs('img2img') seed, reuse_seed, subseed, reuse_subseed, subseed_strength, seed_resize_from_h, seed_resize_from_w = create_seed_inputs('img2img') with gr.Accordion(open=False, label="Denoise", elem_classes=["small-accordion"], elem_id="img2img_denoise_group"): with FormRow(): - denoising_strength = gr.Slider(minimum=0.0, maximum=0.99, step=0.01, label='Denoising strength', value=0.75, elem_id="img2img_denoising_strength") + denoising_strength = gr.Slider(minimum=0.0, maximum=0.99, step=0.01, label='Denoising strength', value=0.50, elem_id="img2img_denoising_strength") refiner_start = gr.Slider(minimum=0.0, maximum=1.0, step=0.05, label='Denoise start', value=0.0, elem_id="img2img_refiner_start") - with gr.Accordion(open=False, label="Advanced", elem_classes=["small-accordion"], elem_id="img2img_advanced_group"): - with FormRow(): - cfg_scale = gr.Slider(minimum=0.0, maximum=30.0, step=0.1, label='CFG scale', value=6.0, elem_id="img2img_cfg_scale") - image_cfg_scale = gr.Slider(minimum=0.0, maximum=30.0, step=0.15, label='Image CFG scale', value=1.5, elem_id="img2img_image_cfg_scale") - with FormRow(): - clip_skip = gr.Slider(label='CLIP skip', value=1, minimum=1, maximum=4, step=1, elem_id='img2img_clip_skip', interactive=True) - diffusers_guidance_rescale = gr.Slider(minimum=0.0, maximum=1.0, step=0.05, label='Guidance rescale', value=0.7, elem_id="txt2img_image_cfg_rescale") - with FormRow(elem_classes="img2img_checkboxes_row", variant="compact"): - full_quality = gr.Checkbox(label='Full quality', value=True, elem_id="img2img_full_quality") - restore_faces = gr.Checkbox(label='Restore faces', value=False, visible=len(modules.shared.face_restorers) > 1, elem_id="img2img_restore_faces") - tiling = gr.Checkbox(label='Tiling', value=False, elem_id="img2img_tiling") + cfg_scale, clip_skip, image_cfg_scale, diffusers_guidance_rescale, full_quality, restore_faces, tiling, hdr_clamp, hdr_boundary, hdr_threshold, hdr_center, hdr_channel_shift, hdr_full_shift, hdr_maximize, hdr_max_center, hdr_max_boundry = create_advanced_inputs('img2img') with FormGroup(elem_id="inpaint_controls", visible=False) as inpaint_controls: with FormRow(): @@ -765,32 +763,23 @@ def select_img2img_tab(tab): for i, elem in enumerate(img2img_tabs): elem.select(fn=lambda tab=i: select_img2img_tab(tab), inputs=[], outputs=[inpaint_controls, mask_alpha]) # pylint: disable=cell-var-from-loop - with FormRow(elem_id="img2img_override_settings_row") as row: - override_settings = create_override_settings_dropdown('img2img', row) + override_settings = create_override_inputs('img2img') with FormGroup(elem_id="img2img_script_container"): - custom_inputs = modules.scripts.scripts_img2img.setup_ui() + img2img_script_inputs = modules.scripts.scripts_img2img.setup_ui() - img2img_gallery, generation_info, html_info, _html_info_formatted, html_log = ui_common.create_output_panel("img2img") + img2img_gallery, img2img_generation_info, img2img_html_info, _img2img_html_info_formatted, img2img_html_log = ui_common.create_output_panel("img2img") - connect_reuse_seed(seed, reuse_seed, generation_info, dummy_component, is_subseed=False) - connect_reuse_seed(subseed, reuse_subseed, generation_info, dummy_component, is_subseed=True) + connect_reuse_seed(seed, reuse_seed, img2img_generation_info, is_subseed=False) + connect_reuse_seed(subseed, reuse_subseed, img2img_generation_info, is_subseed=True) - img2img_prompt_img.change( - fn=modules.images.image_data, - inputs=[ - img2img_prompt_img - ], - outputs=[ - img2img_prompt, - img2img_prompt_img - ] - ) + img2img_prompt_img.change(fn=modules.images.image_data, inputs=[img2img_prompt_img], outputs=[img2img_prompt, img2img_prompt_img]) + dummy_component1 = gr.Textbox(visible=False, value='dummy') + dummy_component2 = gr.Number(visible=False, value=0) global img2img_args # pylint: disable=global-statement img2img_args = [ - dummy_component, dummy_component, - img2img_prompt, img2img_negative_prompt, - img2img_prompt_styles, + dummy_component1, dummy_component2, + img2img_prompt, img2img_negative_prompt, img2img_prompt_styles, init_img, sketch, init_img_with_mask, @@ -814,25 +803,27 @@ def select_img2img_tab(tab): selected_scale_tab, height, width, scale_by, - resize_mode, + resize_mode, resize_name, inpaint_full_res, inpaint_full_res_padding, inpainting_mask_invert, img2img_batch_files, img2img_batch_input_dir, img2img_batch_output_dir, img2img_batch_inpaint_mask_dir, + hdr_clamp, hdr_boundary, hdr_threshold, hdr_center, hdr_channel_shift, hdr_full_shift, hdr_maximize, hdr_max_center, hdr_max_boundry, override_settings, ] img2img_dict = dict( fn=wrap_gradio_gpu_call(modules.img2img.img2img, extra_outputs=[None, '', '']), _js="submit_img2img", - inputs= img2img_args + custom_inputs, + inputs= img2img_args + img2img_script_inputs, outputs=[ img2img_gallery, - generation_info, - html_info, - html_log, + img2img_generation_info, + img2img_html_info, + img2img_html_log, ], show_progress=False, ) img2img_prompt.submit(**img2img_dict) submit.click(**img2img_dict) + dummy_component = gr.Textbox(visible=False, value='dummy') interrogate_args = dict( _js="get_img2img_tab_index", @@ -852,18 +843,8 @@ def select_img2img_tab(tab): img2img_interrogate.click(fn=lambda *args: process_interrogate(interrogate, *args), **interrogate_args) img2img_deepbooru.click(fn=lambda *args: process_interrogate(interrogate_deepbooru, *args), **interrogate_args) - res_switch_btn.click(lambda w, h: (h, w), inputs=[width, height], outputs=[width, height], show_progress=False) - - detect_image_size_btn.click( - fn=lambda w, h, _: (w or gr.update(), h or gr.update()), - _js="currentImg2imgSourceResolution", - inputs=[dummy_component, dummy_component, dummy_component], - outputs=[width, height], - show_progress=False, - ) - - token_button.click(fn=wrap_queued_call(update_token_counter), inputs=[img2img_prompt, steps], outputs=[token_counter]) - negative_token_button.click(fn=wrap_queued_call(update_token_counter), inputs=[img2img_negative_prompt, steps], outputs=[negative_token_counter]) + img2img_token_button.click(fn=wrap_queued_call(update_token_counter), inputs=[img2img_prompt, steps], outputs=[img2img_token_counter]) + img2img_negative_token_button.click(fn=wrap_queued_call(update_token_counter), inputs=[img2img_negative_prompt, steps], outputs=[img2img_negative_token_counter]) ui_extra_networks.setup_ui(extra_networks_ui_img2img, img2img_gallery) global img2img_paste_fields # pylint: disable=global-statement @@ -999,6 +980,7 @@ def get_opt_values(): components = [] component_dict = {} modules.shared.settings_components = component_dict + dummy_component1 = gr.Label(visible=False) script_callbacks.ui_settings_callback() opts.reorder() diff --git a/webui.py b/webui.py index 1e53b7d47..350d0d1c6 100644 --- a/webui.py +++ b/webui.py @@ -266,8 +266,7 @@ def start_ui(): favicon_path='html/logo.ico', allowed_paths=[os.path.dirname(__file__), cmd_opts.data_dir], app_kwargs=fastapi_args, - # Workaround for issues with Gradio Network check timeouts (edge-case, but no other workound) - _frontend=not os.environ.get('SD_DISABLE_GRADIO_FRONTEND_CHECK', None), + _frontend=not cmd_opts.share, ) if cmd_opts.data_dir is not None: ui_tempdir.register_tmp_file(shared.demo, os.path.join(cmd_opts.data_dir, 'x')) From 86af00ce366c3fd7ee8c32112065054998ab18c7 Mon Sep 17 00:00:00 2001 From: Vladimir Mandic Date: Sat, 16 Dec 2023 13:25:25 -0500 Subject: [PATCH 059/143] control prep work --- CHANGELOG.md | 5 ++- extensions-builtin/sd-webui-controlnet | 2 +- javascript/control.js | 23 +++++++---- javascript/progressBar.js | 12 +++--- javascript/sdnext.css | 8 ++-- javascript/ui.js | 24 +++++++++++ modules/call_queue.py | 3 +- modules/generation_parameters_copypaste.py | 6 +++ modules/processing.py | 4 +- modules/ui.py | 48 +++++++++++----------- modules/ui_common.py | 6 +-- modules/ui_postprocessing.py | 2 +- wiki | 2 +- 13 files changed, 94 insertions(+), 51 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 0fd5bd560..d624a94a5 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,6 @@ # Change Log for SD.Next -## Update for 2023-12-14 +## Update for 2023-12-16 *Note*: based on `diffusers==0.25.0.dev0` @@ -57,7 +57,8 @@ - **Other** - **Inpaint** add option `apply_overlay` to control if inpaint result should be applied as overlay or as-is can remove artifacts and hard edges of inpaint area but also remove some details from original - - **chaiNNer** fix NaN issues due to autocast + - **chaiNNer** fix `NaN` issues due to autocast + - **Upscale** increase limit from 4x to 8x given the quality of some upscalers - **Extra Networks** fix sort - disable google fonts check on server startup - fix torchvision/basicsr compatibility diff --git a/extensions-builtin/sd-webui-controlnet b/extensions-builtin/sd-webui-controlnet index a13bd2feb..102449bf1 160000 --- a/extensions-builtin/sd-webui-controlnet +++ b/extensions-builtin/sd-webui-controlnet @@ -1 +1 @@ -Subproject commit a13bd2febe4fae1184b548504957d19a65425a89 +Subproject commit 102449bf16974baf9fff9a2ecec528755e1ba5e2 diff --git a/javascript/control.js b/javascript/control.js index 4d3afcc9e..090afb11a 100644 --- a/javascript/control.js +++ b/javascript/control.js @@ -1,7 +1,16 @@ -const getControlActiveTab = (...args) => { - const selectedTab = gradioApp().querySelector('#control-tabs > .tab-nav > .selected'); - let activeTab = ''; - if (selectedTab) activeTab = selectedTab.innerText.toLowerCase(); - args.shift(); - return [activeTab, ...args]; -}; +function setupControlUI() { + const tabs = ['input', 'output', 'preview']; + for (const tab of tabs) { + const btn = gradioApp().getElementById(`control-${tab}-button`); + btn.style.cursor = 'pointer'; + btn.onclick = () => { + const t = gradioApp().getElementById(`control-tab-${tab}`); + t.style.display = t.style.display === 'none' ? 'block' : 'none'; + const c = gradioApp().getElementById(`control-${tab}-column`); + c.style.flexGrow = c.style.flexGrow === '0' ? '9' : '0'; + }; + } + log('initControlUI'); +} + +onUiLoaded(setupControlUI); diff --git a/javascript/progressBar.js b/javascript/progressBar.js index 0133d3c1f..85c339f26 100644 --- a/javascript/progressBar.js +++ b/javascript/progressBar.js @@ -40,7 +40,7 @@ function checkPaused(state) { } function setProgress(res) { - const elements = ['txt2img_generate', 'img2img_generate', 'extras_generate']; + const elements = ['txt2img_generate', 'img2img_generate', 'extras_generate', 'control_generate']; const progress = (res?.progress || 0); const job = res?.job || ''; const perc = res && (progress > 0) ? `${Math.round(100.0 * progress)}%` : ''; @@ -57,10 +57,12 @@ function setProgress(res) { document.title = `SD.Next ${perc}`; for (const elId of elements) { const el = document.getElementById(elId); - el.innerText = (res ? `${job} ${perc} ${eta}` : 'Generate'); - el.style.background = res && (progress > 0) - ? `linear-gradient(to right, var(--primary-500) 0%, var(--primary-800) ${perc}, var(--neutral-700) ${perc})` - : 'var(--button-primary-background-fill)'; + if (el) { + el.innerText = (res ? `${job} ${perc} ${eta}` : 'Generate'); + el.style.background = res && (progress > 0) + ? `linear-gradient(to right, var(--primary-500) 0%, var(--primary-800) ${perc}, var(--neutral-700) ${perc})` + : 'var(--button-primary-background-fill)'; + } } } diff --git a/javascript/sdnext.css b/javascript/sdnext.css index afc3f9095..2b3954bc3 100644 --- a/javascript/sdnext.css +++ b/javascript/sdnext.css @@ -70,9 +70,10 @@ button.custom-button{ border-radius: var(--button-large-radius); padding: var(-- .performance { font-size: 0.85em; color: #444; } .performance p { display: inline-block; color: var(--body-text-color-subdued) !important } .performance .time { margin-right: 0; } +#control_gallery { height: 564px; } +#control-inputs { margin-top: 1em; } #txt2img_prompt_container, #img2img_prompt_container, #control_prompt_container { margin-right: var(--layout-gap) } -#txt2img_footer, #img2img_footer, #extras_footer { height: fit-content; } -#txt2img_footer, #img2img_footer { height: fit-content; display: none; } +#txt2img_footer, #img2img_footer, #extras_footer, #control_footer { height: fit-content; display: none; } #txt2img_generate_box, #img2img_generate_box, #control_general_box { gap: 0.5em; flex-wrap: wrap-reverse; height: fit-content; } #txt2img_actions_column, #img2img_actions_column, #control_actions_column { gap: 0.3em; height: fit-content; } #txt2img_generate_box>button, #img2img_generate_box>button, #control_generate_box>button, #txt2img_enqueue, #img2img_enqueue { min-height: 42px; max-height: 42px; line-height: 1em; } @@ -92,7 +93,6 @@ button.custom-button{ border-radius: var(--button-large-radius); padding: var(-- div#extras_scale_to_tab div.form{ flex-direction: row; } #img2img_unused_scale_by_slider { visibility: hidden; width: 0.5em; max-width: 0.5em; min-width: 0.5em; } .inactive{ opacity: 0.5; } -div.dimensions-tools { min-width: 0 !important; max-width: fit-content; flex-direction: row; align-content: center; } div#extras_scale_to_tab div.form{ flex-direction: row; } #mode_img2img .gradio-image>div.fixed-height, #mode_img2img .gradio-image>div.fixed-height img{ height: 480px !important; max-height: 480px !important; min-height: 480px !important; } #img2img_sketch, #img2maskimg, #inpaint_sketch { overflow: overlay !important; resize: auto; background: var(--panel-background-fill); z-index: 5; } @@ -307,7 +307,7 @@ table.settings-value-table td { padding: 0.4em; border: 1px solid #ccc; max-widt #img2img_actions_column {display: flex; min-width: fit-content !important; flex-direction: row;justify-content: space-evenly; align-items: center;} #txt2img_generate_box, #img2img_generate_box, #txt2img_enqueue_wrapper,#img2img_enqueue_wrapper {display: flex;flex-direction: column;height: 4em !important;align-items: stretch;justify-content: space-evenly;} #img2img_interface, #img2img_results, #img2img_footer p {text-wrap: wrap; min-width: 100% !important; max-width: 100% !important;} /* maintain single column for from image operations on larger mobile devices */ - #img2img_sketch, #img2maskimg, #inpaint_sketch {display: flex; alignment-baseline:after-edge !important; overflow: auto !important; resize: none !important; } /* fix inpaint image display being too large for mobile displays */ + #img2img_sketch, #img2maskimg, #inpaint_sketch {display: flex; overflow: auto !important; resize: none !important; } /* fix inpaint image display being too large for mobile displays */ #img2maskimg canvas { width: auto !important; max-height: 100% !important; height: auto !important; } #txt2img_sampler, #txt2img_batch, #txt2img_seed_group, #txt2img_advanced, #txt2img_second_pass, #img2img_sampling_group, #img2img_resize_group, #img2img_batch_group, #img2img_seed_group, #img2img_denoise_group, #img2img_advanced_group { width: 100% !important; } /* fix from text/image UI elements to prevent them from moving around within the UI */ #img2img_resize_group .gradio-radio>div { display: flex; flex-direction: column; width: unset !important; } diff --git a/javascript/ui.js b/javascript/ui.js index e192c5935..845402f99 100644 --- a/javascript/ui.js +++ b/javascript/ui.js @@ -101,6 +101,13 @@ function switch_to_extras(...args) { return Array.from(arguments); } +function switch_to_control(...args) { + const tabs = Array.from(gradioApp().querySelector('#tabs').querySelectorAll('button')); + const btn = tabs.find((el) => el.innerText.toLowerCase() === 'control'); + btn.click(); + return Array.from(arguments); +} + function get_tab_index(tabId) { let res = 0; gradioApp().getElementById(tabId).querySelector('div').querySelectorAll('button') @@ -164,6 +171,17 @@ function submit_img2img(...args) { return res; } +function submit_control(...args) { + log('submitControl'); + clearGallery('control'); + const id = randomId(); + requestProgress(id, null, gradioApp().getElementById('control_gallery')); + const res = create_submit_args(args); + res[0] = id; + res[1] = gradioApp().querySelector('#control-tabs > .tab-nav > .selected')?.innerText.toLowerCase() || ''; // selected tab name + return res; +} + function submit_postprocessing(...args) { log('SubmitExtras'); clearGallery('extras'); @@ -211,6 +229,12 @@ function recalculate_prompts_inpaint(...args) { return Array.from(arguments); } +function recalculate_prompts_control(...args) { + recalculatePromptTokens('control_prompt'); + recalculatePromptTokens('control_neg_prompt'); + return Array.from(arguments); +} + function registerDragDrop() { const qs = gradioApp().getElementById('quicksettings'); if (!qs) return; diff --git a/modules/call_queue.py b/modules/call_queue.py index 21f94352d..7da6dddc8 100644 --- a/modules/call_queue.py +++ b/modules/call_queue.py @@ -77,6 +77,7 @@ def f(*args, extra_outputs_array=extra_outputs, **kwargs): vram = {k: -(v//-(1024*1024)) for k, v in shared.mem_mon.read().items()} if vram.get('active_peak', 0) > 0: vram_html = f" |

GPU active {max(vram['active_peak'], vram['reserved_peak'])} MB reserved {vram['reserved']} | used {vram['used']} MB free {vram['free']} MB total {vram['total']} MB | retries {vram['retries']} oom {vram['oom']}

" - res[-1] += f"

Time: {elapsed_text}

{vram_html}
" + if isinstance(res, list): + res[-1] += f"

Time: {elapsed_text}

{vram_html}
" return tuple(res) return f diff --git a/modules/generation_parameters_copypaste.py b/modules/generation_parameters_copypaste.py index 8dd589772..d26317fc9 100644 --- a/modules/generation_parameters_copypaste.py +++ b/modules/generation_parameters_copypaste.py @@ -28,6 +28,7 @@ def __init__(self, paste_button, tabname, source_text_component=None, source_ima self.source_tabname = source_tabname self.override_settings_component = override_settings_component self.paste_field_names = paste_field_names or [] + debug(f'ParamBinding: {vars(self)}') def reset(): @@ -112,6 +113,8 @@ def create_buttons(tabs_list): name = 'Inpaint' elif name == 'extras': name = 'Process' + elif name == 'control': + name = 'Control' buttons[tab] = gr.Button(f"➠ {name}", elem_id=f"{tab}_tab") return buttons @@ -131,6 +134,9 @@ def register_paste_params_button(binding: ParamBinding): def connect_paste_params_buttons(): binding: ParamBinding for binding in registered_param_bindings: + if binding.tabname not in paste_fields: + debug(f"Not not registered: tab={binding.tabname}") + continue destination_image_component = paste_fields[binding.tabname]["init_img"] fields = paste_fields[binding.tabname]["fields"] override_settings_component = binding.override_settings_component or paste_fields[binding.tabname]["override_settings_component"] diff --git a/modules/processing.py b/modules/processing.py index bb9753e72..3ae023fbe 100644 --- a/modules/processing.py +++ b/modules/processing.py @@ -1200,7 +1200,7 @@ def sample(self, conditioning, unconditional_conditioning, seeds, subseeds, subs class StableDiffusionProcessingImg2Img(StableDiffusionProcessing): - def __init__(self, init_images: list = None, resize_mode: int = 0, resize_name: str = 'None', denoising_strength: float = 0.3, image_cfg_scale: float = None, mask: Any = None, mask_blur: int = 4, inpainting_fill: int = 0, inpaint_full_res: bool = True, inpaint_full_res_padding: int = 0, inpainting_mask_invert: int = 0, initial_noise_multiplier: float = None, refiner_steps: int = 5, refiner_start: float = 0, refiner_prompt: str = '', refiner_negative: str = '', **kwargs): + def __init__(self, init_images: list = None, resize_mode: int = 0, resize_name: str = 'None', denoising_strength: float = 0.3, image_cfg_scale: float = None, mask: Any = None, mask_blur: int = 4, inpainting_fill: int = 0, inpaint_full_res: bool = True, inpaint_full_res_padding: int = 0, inpainting_mask_invert: int = 0, initial_noise_multiplier: float = None, scale_by: float = 1, refiner_steps: int = 5, refiner_start: float = 0, refiner_prompt: str = '', refiner_negative: str = '', **kwargs): super().__init__(**kwargs) self.init_images = init_images self.resize_mode: int = resize_mode @@ -1226,7 +1226,7 @@ def __init__(self, init_images: list = None, resize_mode: int = 0, resize_name: self.refiner_negative = refiner_negative self.enable_hr = None self.is_batch = False - self.scale_by = 1.0 + self.scale_by = scale_by self.sampler = None self.scripts = None self.script_args = [] diff --git a/modules/ui.py b/modules/ui.py index 4100e290b..217e284ec 100644 --- a/modules/ui.py +++ b/modules/ui.py @@ -166,16 +166,16 @@ def create_batch_inputs(tab): return batch_count, batch_size -def create_seed_inputs(tab): +def create_seed_inputs(tab, reuse_visible=True): with gr.Accordion(open=False, label="Seed", elem_id=f"{tab}_seed_group", elem_classes=["small-accordion"]): with FormRow(elem_id=f"{tab}_seed_row", variant="compact"): seed = gr.Number(label='Initial seed', value=-1, elem_id=f"{tab}_seed", container=True) random_seed = ToolButton(symbols.random, elem_id=f"{tab}_random_seed", label='Random seed') - reuse_seed = ToolButton(symbols.reuse, elem_id=f"{tab}_reuse_seed", label='Reuse seed') + reuse_seed = ToolButton(symbols.reuse, elem_id=f"{tab}_reuse_seed", label='Reuse seed', visible=reuse_visible) with FormRow(visible=True, elem_id=f"{tab}_subseed_row", variant="compact"): subseed = gr.Number(label='Variation', value=-1, elem_id=f"{tab}_subseed", container=True) random_subseed = ToolButton(symbols.random, elem_id=f"{tab}_random_subseed") - reuse_subseed = ToolButton(symbols.reuse, elem_id=f"{tab}_reuse_subseed") + reuse_subseed = ToolButton(symbols.reuse, elem_id=f"{tab}_reuse_subseed", visible=reuse_visible) subseed_strength = gr.Slider(label='Variation strength', value=0.0, minimum=0, maximum=1, step=0.01, elem_id=f"{tab}_subseed_strength") with FormRow(visible=False): seed_resize_from_w = gr.Slider(minimum=0, maximum=4096, step=8, label="Resize seed from width", value=0, elem_id=f"{tab}_seed_resize_from_w") @@ -215,41 +215,41 @@ def create_advanced_inputs(tab): return cfg_scale, clip_skip, image_cfg_scale, diffusers_guidance_rescale, full_quality, restore_faces, tiling, hdr_clamp, hdr_boundary, hdr_threshold, hdr_center, hdr_channel_shift, hdr_full_shift, hdr_maximize, hdr_max_center, hdr_max_boundry -def create_resize_inputs(tab, images): +def create_resize_inputs(tab, images, time_selector=False, scale_visible=True): dummy_component = gr.Number(visible=False, value=0) with gr.Accordion(open=False, label="Resize", elem_classes=["small-accordion"], elem_id=f"{tab}_resize_group"): with gr.Row(): resize_mode = gr.Radio(label="Resize mode", elem_id=f"{tab}_resize_mode", choices=modules.shared.resize_modes, type="index", value="None") + resize_time = gr.Radio(label="Resize order", elem_id=f"{tab}_resize_order", choices=['Before', 'After'], value="Before", visible=time_selector) with gr.Row(): resize_name = gr.Dropdown(label="Resize method", elem_id=f"{tab}_resize_name", choices=[x.name for x in modules.shared.sd_upscalers], value=opts.upscaler_for_img2img) with FormRow(visible=True) as _resize_group: - with gr.Column(elem_id=f"{tab}_column_size", scale=4): + with gr.Column(elem_id=f"{tab}_column_size"): selected_scale_tab = gr.State(value=0) # pylint: disable=abstract-class-instantiated with gr.Tabs(): with gr.Tab(label="Resize to") as tab_scale_to: with FormRow(): - with gr.Column(elem_id=f"{tab}_column_size", scale=4): - with FormRow(): - width = gr.Slider(minimum=64, maximum=4096, step=8, label="Width", value=512, elem_id=f"{tab}_width") - height = gr.Slider(minimum=64, maximum=4096, step=8, label="Height", value=512, elem_id=f"{tab}_height") - with gr.Column(elem_id=f"{tab}_column_dim", scale=1, elem_classes="dimensions-tools"): + with gr.Column(elem_id=f"{tab}_column_size"): with FormRow(): + width = gr.Slider(minimum=64, maximum=8192, step=8, label="Width", value=512, elem_id=f"{tab}_width") + height = gr.Slider(minimum=64, maximum=8192, step=8, label="Height", value=512, elem_id=f"{tab}_height") res_switch_btn = ToolButton(value=symbols.switch, elem_id=f"{tab}_res_switch_btn") res_switch_btn.click(lambda w, h: (h, w), inputs=[width, height], outputs=[width, height], show_progress=False) detect_image_size_btn = ToolButton(value=symbols.detect, elem_id=f"{tab}_detect_image_size_btn") detect_image_size_btn.click(fn=lambda w, h, _: (w or gr.update(), h or gr.update()), _js="currentImg2imgSourceResolution", inputs=[dummy_component, dummy_component, dummy_component], outputs=[width, height], show_progress=False) with gr.Tab(label="Resize by") as tab_scale_by: - scale_by = gr.Slider(minimum=0.05, maximum=4.0, step=0.05, label="Scale", value=1.0, elem_id=f"{tab}_scale") - with FormRow(): - scale_by_html = FormHTML(resize_from_to_html(0, 0, 0.0), elem_id=f"{tab}_scale_resolution_preview") - gr.Slider(label="Unused", elem_id=f"{tab}_unused_scale_by_slider") - button_update_resize_to = gr.Button(visible=False, elem_id=f"{tab}_update_resize_to") + scale_by = gr.Slider(minimum=0.05, maximum=8.0, step=0.05, label="Scale", value=1.0, elem_id=f"{tab}_scale") + if scale_visible: + with FormRow(): + scale_by_html = FormHTML(resize_from_to_html(0, 0, 0.0), elem_id=f"{tab}_scale_resolution_preview") + gr.Slider(label="Unused", elem_id=f"{tab}_unused_scale_by_slider") + button_update_resize_to = gr.Button(visible=False, elem_id=f"{tab}_update_resize_to") - on_change_args = dict(fn=resize_from_to_html, _js="currentImg2imgSourceResolution", inputs=[dummy_component, dummy_component, scale_by], outputs=scale_by_html, show_progress=False) - scale_by.release(**on_change_args) - button_update_resize_to.click(**on_change_args) + on_change_args = dict(fn=resize_from_to_html, _js="currentImg2imgSourceResolution", inputs=[dummy_component, dummy_component, scale_by], outputs=scale_by_html, show_progress=False) + scale_by.release(**on_change_args) + button_update_resize_to.click(**on_change_args) for component in images: component.change(fn=lambda: None, _js="updateImg2imgResizeToTextAfterChangingImage", inputs=[], outputs=[], show_progress=False) @@ -257,7 +257,7 @@ def create_resize_inputs(tab, images): tab_scale_to.select(fn=lambda: 0, inputs=[], outputs=[selected_scale_tab]) tab_scale_by.select(fn=lambda: 1, inputs=[], outputs=[selected_scale_tab]) # resize_mode.change(fn=lambda x: gr.update(visible=x != 0), inputs=[resize_mode], outputs=[_resize_group]) - return resize_mode, resize_name, width, height, scale_by, selected_scale_tab + return resize_mode, resize_name, width, height, scale_by, selected_scale_tab, resize_time def connect_clear_prompt(button): # pylint: disable=unused-argument @@ -637,7 +637,8 @@ def create_ui(startup_timer = None): *modules.scripts.scripts_txt2img.infotext_fields ] parameters_copypaste.add_paste_fields("txt2img", None, txt2img_paste_fields, override_settings) - parameters_copypaste.register_paste_params_button(parameters_copypaste.ParamBinding(paste_button=txt2img_paste, tabname="txt2img", source_text_component=txt2img_prompt, source_image_component=None)) + txt2img_bindings = parameters_copypaste.ParamBinding(paste_button=txt2img_paste, tabname="txt2img", source_text_component=txt2img_prompt, source_image_component=None) + parameters_copypaste.register_paste_params_button(txt2img_bindings) txt2img_token_button.click(fn=wrap_queued_call(update_token_counter), inputs=[txt2img_prompt, steps], outputs=[txt2img_token_counter]) txt2img_negative_token_button.click(fn=wrap_queued_call(update_token_counter), inputs=[txt2img_negative_prompt, steps], outputs=[txt2img_negative_token_counter]) @@ -731,7 +732,7 @@ def update_orig(image, state): with FormGroup(elem_classes="settings-accordion"): steps, sampler_index = create_sampler_inputs('img2img') - resize_mode, resize_name, width, height, scale_by, selected_scale_tab = create_resize_inputs('img2img', [init_img, sketch]) + resize_mode, resize_name, width, height, scale_by, selected_scale_tab, _resize_time = create_resize_inputs('img2img', [init_img, sketch]) batch_count, batch_size = create_batch_inputs('img2img') seed, reuse_seed, subseed, reuse_subseed, subseed_strength, seed_resize_from_h, seed_resize_from_w = create_seed_inputs('img2img') @@ -892,9 +893,8 @@ def select_img2img_tab(tab): ] parameters_copypaste.add_paste_fields("img2img", init_img, img2img_paste_fields, override_settings) parameters_copypaste.add_paste_fields("inpaint", init_img_with_mask, img2img_paste_fields, override_settings) - parameters_copypaste.register_paste_params_button(parameters_copypaste.ParamBinding( - paste_button=img2img_paste, tabname="img2img", source_text_component=img2img_prompt, source_image_component=None, - )) + img2img_bindings = parameters_copypaste.ParamBinding(paste_button=img2img_paste, tabname="img2img", source_text_component=img2img_prompt, source_image_component=None) + parameters_copypaste.register_paste_params_button(img2img_bindings) timer.startup.record("ui-img2img") diff --git a/modules/ui_common.py b/modules/ui_common.py index bd34e475e..368f61730 100644 --- a/modules/ui_common.py +++ b/modules/ui_common.py @@ -195,13 +195,13 @@ def open_folder(result_gallery, gallery_index = 0): subprocess.Popen(["xdg-open", path]) # pylint: disable=consider-using-with -def create_output_panel(tabname): +def create_output_panel(tabname, preview=True): import modules.generation_parameters_copypaste as parameters_copypaste with gr.Column(variant='panel', elem_id=f"{tabname}_results"): with gr.Group(elem_id=f"{tabname}_gallery_container"): # columns are for <576px, <768px, <992px, <1200px, <1400px, >1400px - result_gallery = gr.Gallery(value=[], label='Output', show_label=False, show_download_button=True, allow_preview=True, elem_id=f"{tabname}_gallery", container=False, preview=True, columns=5, object_fit='scale-down', height=shared.opts.gallery_height or None) + result_gallery = gr.Gallery(value=[], label='Output', show_label=False, show_download_button=True, allow_preview=True, elem_id=f"{tabname}_gallery", container=False, preview=preview, columns=5, object_fit='scale-down', height=shared.opts.gallery_height or None) with gr.Column(elem_id=f"{tabname}_footer", elem_classes="gallery_footer"): dummy_component = gr.Label(visible=False) @@ -214,7 +214,7 @@ def create_output_panel(tabname): clip_files.click(fn=None, _js='clip_gallery_urls', inputs=[result_gallery], outputs=[]) save = gr.Button('Save', elem_id=f'save_{tabname}') delete = gr.Button('Delete', elem_id=f'delete_{tabname}') - buttons = parameters_copypaste.create_buttons(["img2img", "inpaint", "extras"]) + buttons = parameters_copypaste.create_buttons(["img2img", "inpaint", "extras", "control"]) download_files = gr.File(None, file_count="multiple", interactive=False, show_label=False, visible=False, elem_id=f'download_files_{tabname}') with gr.Group(): diff --git a/modules/ui_postprocessing.py b/modules/ui_postprocessing.py index de0b71d3f..0fb103859 100644 --- a/modules/ui_postprocessing.py +++ b/modules/ui_postprocessing.py @@ -31,7 +31,7 @@ def create_ui(): extras_batch_output_dir = gr.Textbox(label="Output directory", **shared.hide_dirs, placeholder="Leave blank to save images to the default path.", elem_id="extras_batch_output_dir") show_extras_results = gr.Checkbox(label='Show result images', value=True, elem_id="extras_show_extras_results") with gr.Row(): - buttons = parameters_copypaste.create_buttons(["txt2img", "img2img", "inpaint"]) + buttons = parameters_copypaste.create_buttons(["txt2img", "img2img", "inpaint", "control"]) with gr.Row(): save_output = gr.Checkbox(label='Save output', value=True, elem_id="extras_save_output") script_inputs = scripts.scripts_postproc.setup_ui() diff --git a/wiki b/wiki index 240a7339c..faa2defd1 160000 --- a/wiki +++ b/wiki @@ -1 +1 @@ -Subproject commit 240a7339ca1199c7735e279e7ea4cdbac0163ec6 +Subproject commit faa2defd19badfef74a1e4f54b695a68c7651224 From b9df9be5a50aeb61dafdecd68376841d8c0b1426 Mon Sep 17 00:00:00 2001 From: Vladimir Mandic Date: Sat, 16 Dec 2023 18:06:10 -0500 Subject: [PATCH 060/143] add segmind vega --- CHANGELOG.md | 3 +++ README.md | 3 ++- html/reference.json | 5 +++++ models/Reference/segmind--Segmind-Vega.jpg | Bin 0 -> 77594 bytes modules/images.py | 6 ++++-- modules/sd_models.py | 4 ++++ modules/ui_tempdir.py | 3 ++- 7 files changed, 20 insertions(+), 4 deletions(-) create mode 100644 models/Reference/segmind--Segmind-Vega.jpg diff --git a/CHANGELOG.md b/CHANGELOG.md index d624a94a5..b727e8f13 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -8,6 +8,9 @@ - **AnimateDiff** can now be used with *second pass* - enhance, upscale and hires your videos! - **IP Adapter** add support for `ip-adapter-plus_sd15`, `ip-adapter-plus-face_sd15` and `ip-adapter-full-face_sd15` additionally, ip-adapter can now be used in xyz-grid + - [Segmind Vega](https://huggingface.co/segmind/Segmind-Vega) support + - small and fast version of SDXL, only 3.1GB in size! + - select from networks -> reference - [Playground v1](https://huggingface.co/playgroundai/playground-v1), [Playground v2 256](https://huggingface.co/playgroundai/playground-v2-256px-base), [Playground v2 512](https://huggingface.co/playgroundai/playground-v2-512px-base), [Playground v2 1024](https://huggingface.co/playgroundai/playground-v2-1024px-aesthetic) model support - simply select from *networks -> reference* and use as usual - [DemoFusion](https://github.com/PRIS-CV/DemoFusion) run your SDXL generations at any resolution! diff --git a/README.md b/README.md index 132c8cbb6..12b571272 100644 --- a/README.md +++ b/README.md @@ -61,8 +61,9 @@ Additional models will be added as they become available and there is public int - [RunwayML Stable Diffusion](https://github.com/Stability-AI/stablediffusion/) 1.x and 2.x *(all variants)* - [StabilityAI Stable Diffusion XL](https://github.com/Stability-AI/generative-models) - [StabilityAI Stable Video Diffusion](https://huggingface.co/stabilityai/stable-video-diffusion-img2vid) Base and XT -- [Segmind SSD-1B](https://huggingface.co/segmind/SSD-1B) - [LCM: Latent Consistency Models](https://github.com/openai/consistency_models) +- [Segmind Vega](https://huggingface.co/segmind/Segmind-Vega) +- [Segmind SSD-1B](https://huggingface.co/segmind/SSD-1B) - [Kandinsky](https://github.com/ai-forever/Kandinsky-2) *2.1 and 2.2 and latest 3.0* - [PixArt-α XL 2](https://github.com/PixArt-alpha/PixArt-alpha) *Medium and Large* - [Warp Wuerstchen](https://huggingface.co/blog/wuertschen) diff --git a/html/reference.json b/html/reference.json index e8da810dd..09cb1d92f 100644 --- a/html/reference.json +++ b/html/reference.json @@ -34,6 +34,11 @@ "desc": "(SVD) Image-to-Video is a latent diffusion model trained to generate short video clips from an image conditioning. This model was trained to generate 25 frames at resolution 576x1024 given a context frame of the same size, finetuned from SVD Image-to-Video [14 frames]. We also finetune the widely used f8-decoder for temporal consistency.", "preview": "stabilityai--stable-video-diffusion-img2vid-xt.jpg" }, + "Segmind Vega": { + "path": "segmind/Segmind-Vega", + "desc": "The Segmind-Vega Model is a distilled version of the Stable Diffusion XL (SDXL), offering a remarkable 70% reduction in size and an impressive 100% speedup while retaining high-quality text-to-image generation capabilities. Trained on diverse datasets, including Grit and Midjourney scrape data, it excels at creating a wide range of visual content based on textual prompts. Employing a knowledge distillation strategy, Segmind-Vega leverages the teachings of several expert models, including SDXL, ZavyChromaXL, and JuggernautXL, to combine their strengths and produce compelling visual outputs.", + "preview": "segmind--Segmind-Vega.jpg" + }, "Segmind SSD-1B": { "path": "segmind/SSD-1B", "desc": "The Segmind Stable Diffusion Model (SSD-1B) offers a compact, efficient, and distilled version of the SDXL model. At 50% smaller and 60% faster than Stable Diffusion XL (SDXL), it provides quick and seamless performance without sacrificing image quality.", diff --git a/models/Reference/segmind--Segmind-Vega.jpg b/models/Reference/segmind--Segmind-Vega.jpg new file mode 100644 index 0000000000000000000000000000000000000000..8356b0ae8d77e390b3af5cd490bd9c6c1c33e25d GIT binary patch literal 77594 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 GQ~%lf?h0`L literal 0 HcmV?d00001 diff --git a/modules/images.py b/modules/images.py index db4d16e82..9b6d4cf67 100644 --- a/modules/images.py +++ b/modules/images.py @@ -657,7 +657,8 @@ def save_video_atomic(images, filename, video_type: str = 'none', duration: floa for i in range(len(video_frames)): img = cv2.cvtColor(video_frames[i], cv2.COLOR_RGB2BGR) video_writer.write(img) - shared.log.info(f'Save video: file="{filename}" frames={len(frames)} duration={duration} fourcc={fourcc}') + size = os.path.getsize(filename) + shared.log.info(f'Save video: file="{filename}" frames={len(frames)} duration={duration} fourcc={fourcc} size={size}') if video_type.lower() == 'gif' or video_type.lower() == 'png': append = images.copy() image = append.pop(0) @@ -672,7 +673,8 @@ def save_video_atomic(images, filename, video_type: str = 'none', duration: floa duration = 1000.0 * duration / frames, loop = 0 if loop else 1, ) - shared.log.info(f'Save video: file="{filename}" frames={len(append) + 1} duration={duration} loop={loop}') + size = os.path.getsize(filename) + shared.log.info(f'Save video: file="{filename}" frames={len(append) + 1} duration={duration} loop={loop} size={size}') def save_video(p, images, filename = None, video_type: str = 'none', duration: float = 2.0, loop: bool = False, interpolate: int = 0, scale: float = 1.0, pad: int = 1, change: float = 0.3): diff --git a/modules/sd_models.py b/modules/sd_models.py index 06d6632b0..6fe672b4f 100644 --- a/modules/sd_models.py +++ b/modules/sd_models.py @@ -634,6 +634,10 @@ def detect_pipeline(f: str, op: str = 'model', warning=True): if shared.backend == shared.Backend.ORIGINAL: warn(f'Model detected as SD XL instruct pix2pix model, but attempting to load using backend=original: {op}={f} size={size} MB') guess = 'Stable Diffusion XL Instruct' + elif size > 3138 and size < 3142: #3140 + if shared.backend == shared.Backend.ORIGINAL: + warn(f'Model detected as Segmind Vega model, but attempting to load using backend=original: {op}={f} size={size} MB') + guess = 'Stable Diffusion XL' else: guess = 'Stable Diffusion' # guess by name diff --git a/modules/ui_tempdir.py b/modules/ui_tempdir.py index 25f59125e..c21d321cd 100644 --- a/modules/ui_tempdir.py +++ b/modules/ui_tempdir.py @@ -68,7 +68,8 @@ def pil_to_temp_file(self, img: Image, dir: str, format="png") -> str: # pylint: with tempfile.NamedTemporaryFile(delete=False, suffix=".png", dir=dir) as tmp: name = tmp.name img.save(name, pnginfo=(metadata if use_metadata else None)) - shared.log.debug(f'Saving temp: image="{name}"') + size = os.path.getsize(name) + shared.log.debug(f'Saving temp: image="{name}" resolution={img.width}x{img.height} size={size}') params = ', '.join([f'{k}: {v}' for k, v in img.info.items()]) params = params[12:] if params.startswith('parameters: ') else params with open(os.path.join(paths.data_path, "params.txt"), "w", encoding="utf8") as file: From 0609e995bfd8a482696be884ccb818a2ecf31a4d Mon Sep 17 00:00:00 2001 From: Vladimir Mandic Date: Sat, 16 Dec 2023 18:38:27 -0500 Subject: [PATCH 061/143] add upscaler refresh --- CHANGELOG.md | 1 + modules/images.py | 3 ++- modules/modelloader.py | 1 + modules/shared.py | 7 ++++++- modules/ui.py | 4 ++-- 5 files changed, 12 insertions(+), 4 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index b727e8f13..7a46ec7f4 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -68,6 +68,7 @@ - add hdr settings to metadata - improve handling of long filenames and filenames during batch processing - do not set preview samples when using via api + - log output file sizes - avoid unnecessary resizes in img2img and inpaint - updated `cli/simple-txt2img.py` and `cli/simple-img2img.py` scripts - save `params.txt` regardless of image save status diff --git a/modules/images.py b/modules/images.py index 9b6d4cf67..2a372faaf 100644 --- a/modules/images.py +++ b/modules/images.py @@ -520,7 +520,8 @@ def atomically_save_image(): image_format = 'JPEG' if shared.opts.image_watermark_enabled: image = set_watermark(image, shared.opts.image_watermark) - shared.log.debug(f'Saving: image="{fn}" type={image_format} size={image.width}x{image.height}') + size = os.path.getsize(fn) if os.path.exists(fn) else 0 + shared.log.debug(f'Saving: image="{fn}" type={image_format} resolution={image.width}x{image.height} size={size}') # additional metadata saved in files if shared.opts.save_txt and len(exifinfo) > 0: try: diff --git a/modules/modelloader.py b/modules/modelloader.py index c1dc6bfa1..a8bf22339 100644 --- a/modules/modelloader.py +++ b/modules/modelloader.py @@ -607,3 +607,4 @@ def load_upscalers(): shared.sd_upscalers = sorted(datas, key=lambda x: x.name.lower() if not isinstance(x.scaler, (UpscalerNone, UpscalerLanczos, UpscalerNearest)) else "") # Special case for UpscalerNone keeps it at the beginning of the list. t1 = time.time() shared.log.debug(f"Load upscalers: total={len(shared.sd_upscalers)} downloaded={len([x for x in shared.sd_upscalers if x.data_path is not None and os.path.isfile(x.data_path)])} user={len([x for x in shared.sd_upscalers if x.custom])} time={t1-t0:.2f} {names}") + return [x.name for x in shared.sd_upscalers] diff --git a/modules/shared.py b/modules/shared.py index 1a8e664b2..d88d651bd 100644 --- a/modules/shared.py +++ b/modules/shared.py @@ -152,6 +152,11 @@ def refresh_vaes(): modules.sd_vae.refresh_vae_list() +def refresh_upscalers(): + import modules.modelloader + modules.modelloader.load_upscalers() + + def list_samplers(): import modules.sd_samplers # pylint: disable=W0621 modules.sd_samplers.set_samplers() @@ -564,7 +569,7 @@ def default(obj): "postprocessing_sep_upscalers": OptionInfo("

Upscaling

", "", gr.HTML), "upscaler_unload": OptionInfo(False, "Unload upscaler after processing"), # 'upscaling_max_images_in_cache': OptionInfo(5, "Maximum number of images in upscaling cache", gr.Slider, {"minimum": 0, "maximum": 10, "step": 1, "visible": False}), - "upscaler_for_img2img": OptionInfo("None", "Default upscaler for image resize operations", gr.Dropdown, lambda: {"choices": [x.name for x in sd_upscalers], "visible": False}), + "upscaler_for_img2img": OptionInfo("None", "Default upscaler for image resize operations", gr.Dropdown, lambda: {"choices": [x.name for x in sd_upscalers], "visible": False}, refresh=refresh_upscalers), "upscaler_tile_size": OptionInfo(192, "Upscaler tile size", gr.Slider, {"minimum": 0, "maximum": 512, "step": 16}), "upscaler_tile_overlap": OptionInfo(8, "Upscaler tile overlap", gr.Slider, {"minimum": 0, "maximum": 64, "step": 1}), })) diff --git a/modules/ui.py b/modules/ui.py index 217e284ec..c93c76430 100644 --- a/modules/ui.py +++ b/modules/ui.py @@ -10,7 +10,7 @@ from PIL import Image from modules.call_queue import wrap_gradio_gpu_call, wrap_queued_call, wrap_gradio_call -from modules import sd_hijack, sd_models, script_callbacks, ui_extensions, deepbooru, extra_networks, ui_common, ui_postprocessing, ui_loadsave, ui_train, ui_models, ui_interrogate +from modules import sd_hijack, sd_models, script_callbacks, ui_extensions, deepbooru, extra_networks, ui_common, ui_postprocessing, ui_loadsave, ui_train, ui_models, ui_interrogate, modelloader from modules.ui_components import FormRow, FormGroup, ToolButton, FormHTML from modules.paths import script_path, data_path from modules.shared import opts, cmd_opts @@ -223,6 +223,7 @@ def create_resize_inputs(tab, images, time_selector=False, scale_visible=True): resize_time = gr.Radio(label="Resize order", elem_id=f"{tab}_resize_order", choices=['Before', 'After'], value="Before", visible=time_selector) with gr.Row(): resize_name = gr.Dropdown(label="Resize method", elem_id=f"{tab}_resize_name", choices=[x.name for x in modules.shared.sd_upscalers], value=opts.upscaler_for_img2img) + create_refresh_button(resize_name, modelloader.load_upscalers, lambda: {"choices": modelloader.load_upscalers()}, 'refresh_upscalers') with FormRow(visible=True) as _resize_group: with gr.Column(elem_id=f"{tab}_column_size"): @@ -1206,7 +1207,6 @@ def reload_sd_weights(): ) def reference_submit(model): - from modules import modelloader loaded = modelloader.load_reference(model) if loaded: return model if loaded else opts.sd_model_checkpoint From 2020d20bcb52f01a97530488787e67a7839e20b7 Mon Sep 17 00:00:00 2001 From: AI-Casanova <54461896+AI-Casanova@users.noreply.github.com> Date: Sat, 16 Dec 2023 18:07:20 -0600 Subject: [PATCH 062/143] Prompt Scheduling --- modules/processing_diffusers.py | 33 ++++++++++-------- modules/prompt_parser_diffusers.py | 55 ++++++++++++++++++------------ 2 files changed, 51 insertions(+), 37 deletions(-) diff --git a/modules/processing_diffusers.py b/modules/processing_diffusers.py index 1fe11b817..417139bde 100644 --- a/modules/processing_diffusers.py +++ b/modules/processing_diffusers.py @@ -89,6 +89,8 @@ def diffusers_callback(_pipe, step: int, timestep: int, kwargs: dict): if kwargs.get('latents', None) is None: return kwargs kwargs = correction_callback(p, timestep, kwargs) + kwargs["prompt_embeds"] = p.prompt_embeds[step-1] + kwargs["negative_prompt_embeds"] = p.negative_embeds[step-1] shared.state.current_latent = kwargs['latents'] if shared.cmd_opts.profile and shared.profiler is not None: shared.profiler.step() @@ -293,36 +295,37 @@ def set_pipeline_args(model, prompts: list, negative_prompts: list, prompts_2: t possible = signature.parameters.keys() generator_device = devices.cpu if shared.opts.diffusers_generator_device == "cpu" else shared.device generator = [torch.Generator(generator_device).manual_seed(s) for s in seeds] - prompt_embed = None - pooled = None - negative_embed = None - negative_pooled = None + # prompt_embed = None + # pooled = None + # negative_embed = None + # negative_pooled = None prompts, negative_prompts, prompts_2, negative_prompts_2 = fix_prompts(prompts, negative_prompts, prompts_2, negative_prompts_2) parser = 'Fixed attention' if shared.opts.prompt_attention != 'Fixed attention' and 'StableDiffusion' in model.__class__.__name__: try: - prompt_embed, pooled, negative_embed, negative_pooled = prompt_parser_diffusers.encode_prompts(model, prompts, negative_prompts, kwargs.pop("clip_skip", None)) + prompt_parser_diffusers.encode_prompts(model, p, prompts, negative_prompts, kwargs.get("num_inference_steps", 1), 0, kwargs.pop("clip_skip", None)) + # prompt_embed, pooled, negative_embed, negative_pooled = , , , , parser = shared.opts.prompt_attention except Exception as e: shared.log.error(f'Prompt parser encode: {e}') if os.environ.get('SD_PROMPT_DEBUG', None) is not None: errors.display(e, 'Prompt parser encode') if 'prompt' in possible: - if hasattr(model, 'text_encoder') and 'prompt_embeds' in possible and prompt_embed is not None: - if type(pooled) == list: - pooled = pooled[0] - if type(negative_pooled) == list: - negative_pooled = negative_pooled[0] - args['prompt_embeds'] = prompt_embed + if hasattr(model, 'text_encoder') and 'prompt_embeds' in possible and p.prompt_embeds[0] is not None: + # if type(pooled) == list: + # pooled = pooled[0] + # if type(negative_pooled) == list: + # negative_pooled = p.negative_pooleds[0][0] + args['prompt_embeds'] = p.prompt_embeds[0] if 'XL' in model.__class__.__name__: - args['pooled_prompt_embeds'] = pooled + args['pooled_prompt_embeds'] = p.positive_pooleds[0][0] else: args['prompt'] = prompts if 'negative_prompt' in possible: - if hasattr(model, 'text_encoder') and 'negative_prompt_embeds' in possible and negative_embed is not None: - args['negative_prompt_embeds'] = negative_embed + if hasattr(model, 'text_encoder') and 'negative_prompt_embeds' in possible and p.negative_embeds[0] is not None: + args['negative_prompt_embeds'] = p.negative_embeds[0] if 'XL' in model.__class__.__name__: - args['negative_pooled_prompt_embeds'] = negative_pooled + args['negative_pooled_prompt_embeds'] = p.negative_pooleds[0][0] else: args['negative_prompt'] = negative_prompts if hasattr(model, 'scheduler') and hasattr(model.scheduler, 'noise_sampler_seed') and hasattr(model.scheduler, 'noise_sampler'): diff --git a/modules/prompt_parser_diffusers.py b/modules/prompt_parser_diffusers.py index f69b7e71e..ceba53977 100644 --- a/modules/prompt_parser_diffusers.py +++ b/modules/prompt_parser_diffusers.py @@ -58,32 +58,43 @@ def expand_textual_inversion_token_ids_if_necessary(self, token_ids: typing.List debug(f'Prompt: expand={prompt}') return self.pipe.tokenizer.encode(prompt, add_special_tokens=False) - -def encode_prompts(pipe, prompts: list, negative_prompts: list, clip_skip: typing.Optional[int] = None): +def get_prompt_schedule(prompt, steps, step): + temp = [] + schedule = prompt_parser.get_learned_conditioning_prompt_schedules([prompt], steps)[0] + for chunk in schedule: + for s in range(steps): + if s + 1 <= chunk[0]: + temp.append(chunk[1]) + return temp[step-1] + +def encode_prompts(pipe, p, prompts: list, negative_prompts: list, steps: int, step: int = 1, clip_skip: typing.Optional[int] = None): if 'StableDiffusion' not in pipe.__class__.__name__ and 'DemoFusion': shared.log.warning(f"Prompt parser not supported: {pipe.__class__.__name__}") return None, None, None, None else: - prompt_embeds = [] - positive_pooleds = [] - negative_embeds = [] - negative_pooleds = [] - for i in range(len(prompts)): - prompt_embed, positive_pooled, negative_embed, negative_pooled = get_weighted_text_embeddings(pipe, prompts[i], negative_prompts[i], clip_skip) - prompt_embeds.append(prompt_embed) - positive_pooleds.append(positive_pooled) - negative_embeds.append(negative_embed) - negative_pooleds.append(negative_pooled) - - if prompt_embeds is not None: - prompt_embeds = torch.cat(prompt_embeds, dim=0) - if negative_embeds is not None: - negative_embeds = torch.cat(negative_embeds, dim=0) - if positive_pooleds is not None and shared.sd_model_type == "sdxl": - positive_pooleds = torch.cat(positive_pooleds, dim=0) - if negative_pooleds is not None and shared.sd_model_type == "sdxl": - negative_pooleds = torch.cat(negative_pooleds, dim=0) - return prompt_embeds, positive_pooleds, negative_embeds, negative_pooleds + positive_schedule = get_prompt_schedule(prompts[0]) + negative_schedule = get_prompt_schedule(negative_prompts[0]) + + p.prompt_embeds = [] + p.positive_pooleds = [] + p.negative_embeds = [] + p.negative_pooleds = [] + + for i in range(len(positive_schedule)): + prompt_embed, positive_pooled, negative_embed, negative_pooled = get_weighted_text_embeddings(pipe, + positive_schedule[i], + negative_schedule[i], + clip_skip) + + if prompt_embed is not None: + p.prompt_embeds.append(torch.cat([prompt_embed]*len(prompts), dim=0)) + if negative_embed is not None: + p.negative_embeds.append(torch.cat([negative_embed]*len(negative_prompts), dim=0)) + if positive_pooled is not None and shared.sd_model_type == "sdxl": + p.positive_pooleds.append(torch.cat([positive_pooled]*len(prompts), dim=0)) + if negative_pooled is not None and shared.sd_model_type == "sdxl": + p.negative_pooleds.append(torch.cat([negative_pooled]*len(negative_prompts), dim=0)) + return def get_prompts_with_weights(prompt: str): From 9e7757b7feb2b51b4794e0cf5a51180eaed8fe75 Mon Sep 17 00:00:00 2001 From: AI-Casanova <54461896+AI-Casanova@users.noreply.github.com> Date: Sat, 16 Dec 2023 20:16:01 -0600 Subject: [PATCH 063/143] Fixes --- modules/processing.py | 4 ++++ modules/processing_diffusers.py | 23 ++++++++++------------- modules/prompt_parser_diffusers.py | 11 +++++------ 3 files changed, 19 insertions(+), 19 deletions(-) diff --git a/modules/processing.py b/modules/processing.py index 3ae023fbe..68a1912c3 100644 --- a/modules/processing.py +++ b/modules/processing.py @@ -231,6 +231,10 @@ def __init__(self, sd_model=None, outpath_samples=None, outpath_grids=None, prom self.hdr_maximize = hdr_maximize self.hdr_max_center = hdr_max_center self.hdr_max_boundry = hdr_max_boundry + self.prompt_embeds = [] + self.positive_pooleds = [] + self.negative_embeds = [] + self.negative_pooleds = [] @property diff --git a/modules/processing_diffusers.py b/modules/processing_diffusers.py index 417139bde..25f6fe646 100644 --- a/modules/processing_diffusers.py +++ b/modules/processing_diffusers.py @@ -89,8 +89,13 @@ def diffusers_callback(_pipe, step: int, timestep: int, kwargs: dict): if kwargs.get('latents', None) is None: return kwargs kwargs = correction_callback(p, timestep, kwargs) - kwargs["prompt_embeds"] = p.prompt_embeds[step-1] - kwargs["negative_prompt_embeds"] = p.negative_embeds[step-1] + try: + kwargs["prompt_embeds"] = p.prompt_embeds[step + 1].repeat(1, kwargs["prompt_embeds"].shape[0], 1).view( + kwargs["prompt_embeds"].shape[0], kwargs["prompt_embeds"].shape[1], -1) + kwargs["negative_prompt_embeds"] = p.negative_embeds[step + 1].repeat(1, kwargs["negative_prompt_embeds"].shape[0], 1).view( + kwargs["negative_prompt_embeds"].shape[0], kwargs["negative_prompt_embeds"].shape[1], -1) + except: + pass shared.state.current_latent = kwargs['latents'] if shared.cmd_opts.profile and shared.profiler is not None: shared.profiler.step() @@ -295,10 +300,6 @@ def set_pipeline_args(model, prompts: list, negative_prompts: list, prompts_2: t possible = signature.parameters.keys() generator_device = devices.cpu if shared.opts.diffusers_generator_device == "cpu" else shared.device generator = [torch.Generator(generator_device).manual_seed(s) for s in seeds] - # prompt_embed = None - # pooled = None - # negative_embed = None - # negative_pooled = None prompts, negative_prompts, prompts_2, negative_prompts_2 = fix_prompts(prompts, negative_prompts, prompts_2, negative_prompts_2) parser = 'Fixed attention' if shared.opts.prompt_attention != 'Fixed attention' and 'StableDiffusion' in model.__class__.__name__: @@ -312,20 +313,16 @@ def set_pipeline_args(model, prompts: list, negative_prompts: list, prompts_2: t errors.display(e, 'Prompt parser encode') if 'prompt' in possible: if hasattr(model, 'text_encoder') and 'prompt_embeds' in possible and p.prompt_embeds[0] is not None: - # if type(pooled) == list: - # pooled = pooled[0] - # if type(negative_pooled) == list: - # negative_pooled = p.negative_pooleds[0][0] args['prompt_embeds'] = p.prompt_embeds[0] if 'XL' in model.__class__.__name__: - args['pooled_prompt_embeds'] = p.positive_pooleds[0][0] + args['pooled_prompt_embeds'] = p.positive_pooleds[0] else: args['prompt'] = prompts if 'negative_prompt' in possible: if hasattr(model, 'text_encoder') and 'negative_prompt_embeds' in possible and p.negative_embeds[0] is not None: args['negative_prompt_embeds'] = p.negative_embeds[0] if 'XL' in model.__class__.__name__: - args['negative_pooled_prompt_embeds'] = p.negative_pooleds[0][0] + args['negative_pooled_prompt_embeds'] = p.negative_pooleds[0] else: args['negative_prompt'] = negative_prompts if hasattr(model, 'scheduler') and hasattr(model.scheduler, 'noise_sampler_seed') and hasattr(model.scheduler, 'noise_sampler'): @@ -345,7 +342,7 @@ def set_pipeline_args(model, prompts: list, negative_prompts: list, prompts_2: t args['callback'] = diffusers_callback_legacy elif 'callback_on_step_end_tensor_inputs' in possible: args['callback_on_step_end'] = diffusers_callback - args['callback_on_step_end_tensor_inputs'] = ['latents'] + args['callback_on_step_end_tensor_inputs'] = ['latents', 'prompt_embeds', 'negative_prompt_embeds'] for arg in kwargs: if arg in possible: # add kwargs args[arg] = kwargs[arg] diff --git a/modules/prompt_parser_diffusers.py b/modules/prompt_parser_diffusers.py index ceba53977..16fab82d3 100644 --- a/modules/prompt_parser_diffusers.py +++ b/modules/prompt_parser_diffusers.py @@ -58,22 +58,22 @@ def expand_textual_inversion_token_ids_if_necessary(self, token_ids: typing.List debug(f'Prompt: expand={prompt}') return self.pipe.tokenizer.encode(prompt, add_special_tokens=False) -def get_prompt_schedule(prompt, steps, step): +def get_prompt_schedule(prompt, steps): temp = [] schedule = prompt_parser.get_learned_conditioning_prompt_schedules([prompt], steps)[0] for chunk in schedule: for s in range(steps): - if s + 1 <= chunk[0]: + if len(temp) < s + 1 <= chunk[0]: temp.append(chunk[1]) - return temp[step-1] + return temp def encode_prompts(pipe, p, prompts: list, negative_prompts: list, steps: int, step: int = 1, clip_skip: typing.Optional[int] = None): if 'StableDiffusion' not in pipe.__class__.__name__ and 'DemoFusion': shared.log.warning(f"Prompt parser not supported: {pipe.__class__.__name__}") return None, None, None, None else: - positive_schedule = get_prompt_schedule(prompts[0]) - negative_schedule = get_prompt_schedule(negative_prompts[0]) + positive_schedule = get_prompt_schedule(prompts[0], steps) + negative_schedule = get_prompt_schedule(negative_prompts[0], steps) p.prompt_embeds = [] p.positive_pooleds = [] @@ -85,7 +85,6 @@ def encode_prompts(pipe, p, prompts: list, negative_prompts: list, steps: int, positive_schedule[i], negative_schedule[i], clip_skip) - if prompt_embed is not None: p.prompt_embeds.append(torch.cat([prompt_embed]*len(prompts), dim=0)) if negative_embed is not None: From 4f5478df4f048b0d343988456757076ce762737e Mon Sep 17 00:00:00 2001 From: Vladimir Mandic Date: Sun, 17 Dec 2023 07:56:23 -0500 Subject: [PATCH 064/143] fix reuse seed --- modules/ui.py | 4 +++- modules/ui_interrogate.py | 2 +- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/modules/ui.py b/modules/ui.py index c93c76430..97b7c3e4b 100644 --- a/modules/ui.py +++ b/modules/ui.py @@ -269,11 +269,13 @@ def connect_reuse_seed(seed: gr.Number, reuse_seed: gr.Button, generation_info: """ Connects a 'reuse (sub)seed' button's click event so that it copies last used (sub)seed value from generation info the to the seed field. If copying subseed and subseed strength was 0, i.e. no variation seed was used, it copies the normal seed value instead.""" - def copy_seed(gen_info_string: str, index): + def copy_seed(gen_info_string: str, index: int): res = -1 try: gen_info = json.loads(gen_info_string) + log.debug(f'Reuse: info={gen_info}') index -= gen_info.get('index_of_first_image', 0) + index = int(index) if is_subseed and gen_info.get('subseed_strength', 0) > 0: all_subseeds = gen_info.get('all_subseeds', [-1]) diff --git a/modules/ui_interrogate.py b/modules/ui_interrogate.py index b9bb074ea..98fbe9a2f 100644 --- a/modules/ui_interrogate.py +++ b/modules/ui_interrogate.py @@ -195,7 +195,7 @@ def create_ui(): analyze_btn = gr.Button("Analyze", variant='primary') unload_btn = gr.Button("Unload") with gr.Row(): - buttons = parameters_copypaste.create_buttons(["txt2img", "img2img", "extras"]) + buttons = parameters_copypaste.create_buttons(["txt2img", "img2img", "extras", "control"]) for tabname, button in buttons.items(): parameters_copypaste.register_paste_params_button(parameters_copypaste.ParamBinding(paste_button=button, tabname=tabname, source_text_component=prompt, source_image_component=image,)) with gr.Tab("Batch"): From 2b56e82f16807530ca0a396d2119baa9bcfe8e85 Mon Sep 17 00:00:00 2001 From: Vladimir Mandic Date: Sun, 17 Dec 2023 09:32:26 -0500 Subject: [PATCH 065/143] modularize en --- javascript/extraNetworks.js | 2 +- modules/ui.py | 1 + modules/ui_extra_networks.py | 32 ++++++++++++++++---------------- 3 files changed, 18 insertions(+), 17 deletions(-) diff --git a/javascript/extraNetworks.js b/javascript/extraNetworks.js index ba5793f9c..18e401fde 100644 --- a/javascript/extraNetworks.js +++ b/javascript/extraNetworks.js @@ -357,8 +357,8 @@ function setupExtraNetworksForTab(tabname) { }; // en style + if (!en) return; const intersectionObserver = new IntersectionObserver((entries) => { - if (!en) return; for (const el of Array.from(gradioApp().querySelectorAll('.extra-networks-page'))) { el.style.height = `${window.opts.extra_networks_height}vh`; el.parentElement.style.width = '-webkit-fill-available'; diff --git a/modules/ui.py b/modules/ui.py index 97b7c3e4b..64496ae52 100644 --- a/modules/ui.py +++ b/modules/ui.py @@ -1152,6 +1152,7 @@ def reload_sd_weights(): # if label in modules.shared.opts.hidden_tabs or label == '': # continue with gr.TabItem(label, id=ifid, elem_id=f"tab_{ifid}"): + # log.debug(f'UI render: id={ifid}') interface.render() for interface, _label, ifid in interfaces: if interface is None: diff --git a/modules/ui_extra_networks.py b/modules/ui_extra_networks.py index f4a0945a0..64a8e5342 100644 --- a/modules/ui_extra_networks.py +++ b/modules/ui_extra_networks.py @@ -459,11 +459,11 @@ def create_ui(container, button_parent, tabname, skip_indexing = False): ui = ExtraNetworksUi() ui.tabname = tabname ui.pages = [] - ui.state = gr.Textbox('{}', elem_id=tabname+"_extra_state", visible=False) + ui.state = gr.Textbox('{}', elem_id=f"{tabname}_extra_state", visible=False) ui.visible = gr.State(value=False) # pylint: disable=abstract-class-instantiated - ui.details = gr.Group(elem_id=tabname+"_extra_details", visible=False) - ui.tabs = gr.Tabs(elem_id=tabname+"_extra_tabs") - ui.button_details = gr.Button('Details', elem_id=tabname+"_extra_details_btn", visible=False) + ui.details = gr.Group(elem_id=f"{tabname}_extra_details", visible=False) + ui.tabs = gr.Tabs(elem_id=f"{tabname}_extra_tabs") + ui.button_details = gr.Button('Details', elem_id=f"{tabname}_extra_details_btn", visible=False) state = {} if shared.cmd_opts.profile: import cProfile @@ -504,14 +504,14 @@ def toggle_visibility(is_visible): return is_visible, gr.update(visible=is_visible), gr.update(variant=("secondary-down" if is_visible else "secondary")) with ui.details: - details_close = ToolButton(symbols.close, elem_id=tabname+"_extra_details_close", elem_classes=['extra-details-close']) + details_close = ToolButton(symbols.close, elem_id=f"{tabname}_extra_details_close", elem_classes=['extra-details-close']) details_close.click(fn=lambda: gr.update(visible=False), inputs=[], outputs=[ui.details]) with gr.Row(): with gr.Column(scale=1): text = gr.HTML('
title
') ui.details_components.append(text) with gr.Column(scale=1): - img = gr.Image(value=None, show_label=False, interactive=False, container=False, show_download_button=False, show_info=False, elem_id=tabname+"_extra_details_img", elem_classes=['extra-details-img']) + img = gr.Image(value=None, show_label=False, interactive=False, container=False, show_download_button=False, show_info=False, elem_id=f"{tabname}_extra_details_img", elem_classes=['extra-details-img']) ui.details_components.append(img) with gr.Row(): btn_save_img = gr.Button('Replace', elem_classes=['small-button']) @@ -544,16 +544,16 @@ def ui_tab_change(page): model_visible = page in ['Model'] return [gr.update(visible=scan_visible), gr.update(visible=save_visible), gr.update(visible=model_visible)] - ui.button_refresh = ToolButton(symbols.refresh, elem_id=tabname+"_extra_refresh") - ui.button_scan = ToolButton(symbols.scan, elem_id=tabname+"_extra_scan", visible=True) - ui.button_quicksave = ToolButton(symbols.book, elem_id=tabname+"_extra_quicksave", visible=False) - ui.button_save = ToolButton(symbols.book, elem_id=tabname+"_extra_save", visible=False) - ui.button_sort = ToolButton(symbols.sort, elem_id=tabname+"_extra_sort", visible=True) - ui.button_view = ToolButton(symbols.view, elem_id=tabname+"_extra_view", visible=True) - ui.button_close = ToolButton(symbols.close, elem_id=tabname+"_extra_close", visible=True) - ui.button_model = ToolButton(symbols.refine, elem_id=tabname+"_extra_model", visible=True) - ui.search = gr.Textbox('', show_label=False, elem_id=tabname+"_extra_search", placeholder="Search...", elem_classes="textbox", lines=2, container=False) - ui.description = gr.Textbox('', show_label=False, elem_id=tabname+"_description", elem_classes="textbox", lines=2, interactive=False, container=False) + ui.button_refresh = ToolButton(symbols.refresh, elem_id=f"{tabname}_extra_refresh") + ui.button_scan = ToolButton(symbols.scan, elem_id=f"{tabname}_extra_scan", visible=True) + ui.button_quicksave = ToolButton(symbols.book, elem_id=f"{tabname}_extra_quicksave", visible=False) + ui.button_save = ToolButton(symbols.book, elem_id=f"{tabname}_extra_save", visible=False) + ui.button_sort = ToolButton(symbols.sort, elem_id=f"{tabname}_extra_sort", visible=True) + ui.button_view = ToolButton(symbols.view, elem_id=f"{tabname}_extra_view", visible=True) + ui.button_close = ToolButton(symbols.close, elem_id=f"{tabname}_extra_close", visible=True) + ui.button_model = ToolButton(symbols.refine, elem_id=f"{tabname}_extra_model", visible=True) + ui.search = gr.Textbox('', show_label=False, elem_id=f"{tabname}_extra_search", placeholder="Search...", elem_classes="textbox", lines=2, container=False) + ui.description = gr.Textbox('', show_label=False, elem_id=f"{tabname}_description", elem_classes="textbox", lines=2, interactive=False, container=False) if ui.tabname == 'txt2img': # refresh only once global refresh_time # pylint: disable=global-statement From 0c4f546e7b0ea154f49c8121570ce9d39e8134ed Mon Sep 17 00:00:00 2001 From: Disty0 Date: Sun, 17 Dec 2023 21:21:29 +0300 Subject: [PATCH 066/143] IPEX use custom wheels for Windows --- installer.py | 22 +++++++++++++++++++--- 1 file changed, 19 insertions(+), 3 deletions(-) diff --git a/installer.py b/installer.py index 206e4b107..8e6fd7ba1 100644 --- a/installer.py +++ b/installer.py @@ -423,11 +423,27 @@ def check_torch(): log.info('Intel OneAPI Toolkit detected') os.environ.setdefault('NEOReadDebugKeys', '1') os.environ.setdefault('ClDeviceGlobalMemSizeAvailablePercent', '100') - torch_command = os.environ.get('TORCH_COMMAND', 'torch==2.1.0a0 torchvision==0.16.0a0 intel-extension-for-pytorch==2.1.10+xpu --extra-index-url https://pytorch-extension.intel.com/release-whl/stable/xpu/us/') if "linux" in sys.platform: + torch_command = os.environ.get('TORCH_COMMAND', 'torch==2.1.0a0 torchvision==0.16.0a0 intel-extension-for-pytorch==2.1.10+xpu --extra-index-url https://pytorch-extension.intel.com/release-whl/stable/xpu/us/') os.environ.setdefault('TENSORFLOW_PACKAGE', 'tensorflow==2.14.0 intel-extension-for-tensorflow[xpu]==2.14.0.1') - install(os.environ.get('MKL_PACKAGE', 'mkl==2024.0.0'), 'mkl') - install(os.environ.get('DPCPP_PACKAGE', 'mkl-dpcpp==2024.0.0'), 'mkl-dpcpp') + install(os.environ.get('MKL_PACKAGE', 'mkl==2024.0.0'), 'mkl') + install(os.environ.get('DPCPP_PACKAGE', 'mkl-dpcpp==2024.0.0'), 'mkl-dpcpp') + else: + if sys.version_info[1] == 11: + pytorch_pip = 'https://github.com/Nuullll/intel-extension-for-pytorch/releases/download/v2.1.10%2Bxpu/torch-2.1.0a0+cxx11.abi-cp311-cp311-win_amd64.whl' + torchvision_pip = 'https://github.com/Nuullll/intel-extension-for-pytorch/releases/download/v2.1.10%2Bxpu/torchvision-0.16.0a0+cxx11.abi-cp311-cp311-win_amd64.whl' + ipex_pip = 'https://github.com/Nuullll/intel-extension-for-pytorch/releases/download/v2.1.10%2Bxpu/intel_extension_for_pytorch-2.1.10+xpu-cp311-cp311-win_amd64.whl' + elif sys.version_info[1] == 10: + pytorch_pip = 'https://github.com/Nuullll/intel-extension-for-pytorch/releases/download/v2.1.10%2Bxpu/torch-2.1.0a0+cxx11.abi-cp310-cp310-win_amd64.whl' + torchvision_pip = 'https://github.com/Nuullll/intel-extension-for-pytorch/releases/download/v2.1.10%2Bxpu/torchvision-0.16.0a0+cxx11.abi-cp310-cp310-win_amd64.whl' + ipex_pip = 'https://github.com/Nuullll/intel-extension-for-pytorch/releases/download/v2.1.10%2Bxpu/intel_extension_for_pytorch-2.1.10+xpu-cp310-cp310-win_amd64.whl' + else: + pytorch_pip = 'torch==2.1.0a0' + torchvision_pip = 'torchvision==0.16.0a0' + ipex_pip = 'intel-extension-for-pytorch==2.1.10+xpu --extra-index-url https://pytorch-extension.intel.com/release-whl/stable/xpu/us/' + install(os.environ.get('MKL_PACKAGE', 'mkl==2024.0.0'), 'mkl') + install(os.environ.get('DPCPP_PACKAGE', 'mkl-dpcpp==2024.0.0'), 'mkl-dpcpp') + torch_command = os.environ.get('TORCH_COMMAND', f'{pytorch_pip} {torchvision_pip} {ipex_pip}') install(os.environ.get('OPENVINO_PACKAGE', 'openvino==2023.2.0'), 'openvino', ignore=True) install('onnxruntime-openvino', 'onnxruntime-openvino', ignore=True) elif allow_openvino and args.use_openvino: From a3c4b5097c2f76206251b03b320b6102057c6f4e Mon Sep 17 00:00:00 2001 From: Vladimir Mandic Date: Sun, 17 Dec 2023 15:50:52 -0500 Subject: [PATCH 067/143] fix styles refresh --- CHANGELOG.md | 3 ++- extensions-builtin/sd-webui-agent-scheduler | 2 +- modules/ui.py | 5 +++-- 3 files changed, 6 insertions(+), 4 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 7a46ec7f4..9aaa87845 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,6 @@ # Change Log for SD.Next -## Update for 2023-12-16 +## Update for 2023-12-17 *Note*: based on `diffusers==0.25.0.dev0` @@ -65,6 +65,7 @@ - **Extra Networks** fix sort - disable google fonts check on server startup - fix torchvision/basicsr compatibility + - fix styles quick save - add hdr settings to metadata - improve handling of long filenames and filenames during batch processing - do not set preview samples when using via api diff --git a/extensions-builtin/sd-webui-agent-scheduler b/extensions-builtin/sd-webui-agent-scheduler index a8d527b26..435dd9bde 160000 --- a/extensions-builtin/sd-webui-agent-scheduler +++ b/extensions-builtin/sd-webui-agent-scheduler @@ -1 +1 @@ -Subproject commit a8d527b269358595bd7f9c839f266b1db5bea468 +Subproject commit 435dd9bdec0fdb22f73f645de76b55684ed16e67 diff --git a/modules/ui.py b/modules/ui.py index 64496ae52..6f6213c2e 100644 --- a/modules/ui.py +++ b/modules/ui.py @@ -366,8 +366,9 @@ def create_toprow(is_img2img: bool = False, id_part: str = None): negative_token_button = gr.Button(visible=False, elem_id=f"{id_part}_negative_token_button") with gr.Row(elem_id=f"{id_part}_styles_row"): styles = gr.Dropdown(label="Styles", elem_id=f"{id_part}_styles", choices=[style.name for style in modules.shared.prompt_styles.styles.values()], value=[], multiselect=True) - styles_btn_refresh = ToolButton(symbols.refresh, elem_id=f"{id_part}_styles_refresh", visible=True) - styles_btn_refresh.click(fn=lambda: gr.update(choices=[style.name for style in modules.shared.prompt_styles.styles.values()]), inputs=[], outputs=[styles]) + styles_btn_refresh = create_refresh_button(styles, modules.shared.prompt_styles.reload, lambda: {"choices": list(modules.shared.prompt_styles.styles)}, f"{id_part}_styles_refresh") + # styles_btn_refresh = ToolButton(symbols.refresh, elem_id=f"{id_part}_styles_refresh", visible=True) + # styles_btn_refresh.click(fn=lambda: gr.update(choices=[style.name for style in modules.shared.prompt_styles.styles.values()]), inputs=[], outputs=[styles]) styles_btn_select = gr.Button('Select', elem_id=f"{id_part}_styles_select", visible=False) styles_btn_select.click(_js="applyStyles", fn=parse_style, inputs=[styles], outputs=[styles]) styles_btn_apply = ToolButton(symbols.apply, elem_id=f"{id_part}_extra_apply", visible=False) From d872c8e56902592945ecdc0275c5a0eb7f5ecbe6 Mon Sep 17 00:00:00 2001 From: Vladimir Mandic Date: Sun, 17 Dec 2023 17:30:34 -0500 Subject: [PATCH 068/143] add glora --- CHANGELOG.md | 3 ++- extensions-builtin/Lora/network_glora.py | 30 ++++++++++++++++++++++++ extensions-builtin/Lora/networks.py | 2 ++ html/locale_en.json | 2 +- modules/processing_diffusers.py | 2 ++ modules/shared.py | 2 +- modules/ui.py | 2 +- wiki | 2 +- 8 files changed, 40 insertions(+), 5 deletions(-) create mode 100644 extensions-builtin/Lora/network_glora.py diff --git a/CHANGELOG.md b/CHANGELOG.md index 9aaa87845..27b3eb034 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -62,7 +62,8 @@ can remove artifacts and hard edges of inpaint area but also remove some details from original - **chaiNNer** fix `NaN` issues due to autocast - **Upscale** increase limit from 4x to 8x given the quality of some upscalers - - **Extra Networks** fix sort + - **Extra Networks** fix sort + - **LoRA** add support for LyCORIS GLora networks - disable google fonts check on server startup - fix torchvision/basicsr compatibility - fix styles quick save diff --git a/extensions-builtin/Lora/network_glora.py b/extensions-builtin/Lora/network_glora.py new file mode 100644 index 000000000..3c54a14cc --- /dev/null +++ b/extensions-builtin/Lora/network_glora.py @@ -0,0 +1,30 @@ + +import network + +class ModuleTypeGLora(network.ModuleType): + def create_module(self, net: network.Network, weights: network.NetworkWeights): + if all(x in weights.w for x in ["a1.weight", "a2.weight", "alpha", "b1.weight", "b2.weight"]): + return NetworkModuleGLora(net, weights) + return None + +# adapted from https://github.com/KohakuBlueleaf/LyCORIS +class NetworkModuleGLora(network.NetworkModule): # pylint: disable=abstract-method + def __init__(self, net: network.Network, weights: network.NetworkWeights): + super().__init__(net, weights) + + if hasattr(self.sd_module, 'weight'): + self.shape = self.sd_module.weight.shape + + self.w1a = weights.w["a1.weight"] + self.w1b = weights.w["b1.weight"] + self.w2a = weights.w["a2.weight"] + self.w2b = weights.w["b2.weight"] + + def calc_updown(self, orig_weight): # pylint: disable=arguments-differ + w1a = self.w1a.to(orig_weight.device, dtype=orig_weight.dtype) + w1b = self.w1b.to(orig_weight.device, dtype=orig_weight.dtype) + w2a = self.w2a.to(orig_weight.device, dtype=orig_weight.dtype) + w2b = self.w2b.to(orig_weight.device, dtype=orig_weight.dtype) + output_shape = [w1a.size(0), w1b.size(1)] + updown = (w2b @ w1b) + ((orig_weight @ w2a) @ w1a) + return self.finalize_updown(updown, orig_weight, output_shape) diff --git a/extensions-builtin/Lora/networks.py b/extensions-builtin/Lora/networks.py index cc0c34bc3..006eaf5bb 100644 --- a/extensions-builtin/Lora/networks.py +++ b/extensions-builtin/Lora/networks.py @@ -11,6 +11,7 @@ import network_lokr import network_full import network_norm +import network_glora import lora_convert import torch import diffusers.models.lora @@ -37,6 +38,7 @@ network_lokr.ModuleTypeLokr(), network_full.ModuleTypeFull(), network_norm.ModuleTypeNorm(), + network_glora.ModuleTypeGLora(), ] convert_diffusers_name_to_compvis = lora_convert.convert_diffusers_name_to_compvis # supermerger compatibility item diff --git a/html/locale_en.json b/html/locale_en.json index 14ea99c9b..8bce40518 100644 --- a/html/locale_en.json +++ b/html/locale_en.json @@ -500,7 +500,7 @@ {"id":"","label":"Show previews of all images generated in a batch as a grid","localized":"","hint":""}, {"id":"","label":"Play a sound when images are finished generating","localized":"","hint":""}, {"id":"","label":"Path to notification sound","localized":"","hint":""}, - {"id":"","label":"Live preview display period","localized":"","hint":""}, + {"id":"","label":"Live preview display period","localized":"","hint":"Request preview image every n steps, set to 0 to disable"}, {"id":"","label":"Full VAE","localized":"","hint":""}, {"id":"","label":"Approximate","localized":"","hint":"Cheap neural network approximation. Very fast compared to VAE, but produces pictures with 4 times smaller horizontal/vertical resolution and lower quality"}, {"id":"","label":"Simple","localized":"","hint":"Very cheap approximation. Very fast compared to VAE, but produces pictures with 8 times smaller horizontal/vertical resolution and extremely low quality"}, diff --git a/modules/processing_diffusers.py b/modules/processing_diffusers.py index 1fe11b817..a6490fb58 100644 --- a/modules/processing_diffusers.py +++ b/modules/processing_diffusers.py @@ -651,6 +651,8 @@ def calculate_refiner_steps(): ) shared.state.sampling_steps = refiner_args['num_inference_steps'] try: + shared.sd_refiner.register_to_config(requires_aesthetics_score=shared.opts.diffusers_aesthetics_score) + print('HERE req', shared.sd_refiner.config.requires_aesthetics_score) refiner_output = shared.sd_refiner(**refiner_args) # pylint: disable=not-callable except AssertionError as e: shared.log.info(e) diff --git a/modules/shared.py b/modules/shared.py index d88d651bd..c6facfd21 100644 --- a/modules/shared.py +++ b/modules/shared.py @@ -496,7 +496,7 @@ def default(obj): "show_progress_grid": OptionInfo(True, "Show previews of all images generated in a batch as a grid", gr.Checkbox, {"visible": False}), "notification_audio_enable": OptionInfo(False, "Play a sound when images are finished generating"), "notification_audio_path": OptionInfo("html/notification.mp3","Path to notification sound", component_args=hide_dirs, folder=True), - "show_progress_every_n_steps": OptionInfo(1, "Live preview display period", gr.Slider, {"minimum": -1, "maximum": 32, "step": 1}), + "show_progress_every_n_steps": OptionInfo(1, "Live preview display period", gr.Slider, {"minimum": 0, "maximum": 32, "step": 1}), "show_progress_type": OptionInfo("Approximate", "Live preview method", gr.Radio, {"choices": ["Simple", "Approximate", "TAESD", "Full VAE"]}), "live_preview_content": OptionInfo("Combined", "Live preview subject", gr.Radio, {"choices": ["Combined", "Prompt", "Negative prompt"], "visible": False}), "live_preview_refresh_period": OptionInfo(500, "Progress update period", gr.Slider, {"minimum": 0, "maximum": 5000, "step": 25}), diff --git a/modules/ui.py b/modules/ui.py index 6f6213c2e..65b70f3fc 100644 --- a/modules/ui.py +++ b/modules/ui.py @@ -366,7 +366,7 @@ def create_toprow(is_img2img: bool = False, id_part: str = None): negative_token_button = gr.Button(visible=False, elem_id=f"{id_part}_negative_token_button") with gr.Row(elem_id=f"{id_part}_styles_row"): styles = gr.Dropdown(label="Styles", elem_id=f"{id_part}_styles", choices=[style.name for style in modules.shared.prompt_styles.styles.values()], value=[], multiselect=True) - styles_btn_refresh = create_refresh_button(styles, modules.shared.prompt_styles.reload, lambda: {"choices": list(modules.shared.prompt_styles.styles)}, f"{id_part}_styles_refresh") + _styles_btn_refresh = create_refresh_button(styles, modules.shared.prompt_styles.reload, lambda: {"choices": list(modules.shared.prompt_styles.styles)}, f"{id_part}_styles_refresh") # styles_btn_refresh = ToolButton(symbols.refresh, elem_id=f"{id_part}_styles_refresh", visible=True) # styles_btn_refresh.click(fn=lambda: gr.update(choices=[style.name for style in modules.shared.prompt_styles.styles.values()]), inputs=[], outputs=[styles]) styles_btn_select = gr.Button('Select', elem_id=f"{id_part}_styles_select", visible=False) diff --git a/wiki b/wiki index faa2defd1..6317f96f9 160000 --- a/wiki +++ b/wiki @@ -1 +1 @@ -Subproject commit faa2defd19badfef74a1e4f54b695a68c7651224 +Subproject commit 6317f96f9b5ade24e8ea5c8e796d5c22c412cfd6 From 327739d6044325a629348a3801db0160579535fd Mon Sep 17 00:00:00 2001 From: Vladimir Mandic Date: Sun, 17 Dec 2023 20:03:32 -0500 Subject: [PATCH 069/143] add text2video --- CHANGELOG.md | 44 +++++++------ html/reference.json | 5 -- scripts/stablevideodiffusion.py | 1 + scripts/text2video.py | 106 ++++++++++++++++++++++++++++++++ wiki | 2 +- 5 files changed, 133 insertions(+), 25 deletions(-) create mode 100644 scripts/text2video.py diff --git a/CHANGELOG.md b/CHANGELOG.md index 27b3eb034..e88862b6a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,32 +5,39 @@ *Note*: based on `diffusers==0.25.0.dev0` - **Diffusers** - - **AnimateDiff** can now be used with *second pass* - enhance, upscale and hires your videos! - - **IP Adapter** add support for `ip-adapter-plus_sd15`, `ip-adapter-plus-face_sd15` and `ip-adapter-full-face_sd15` - additionally, ip-adapter can now be used in xyz-grid + - **AnimateDiff** + - can now be used with *second pass* - enhance, upscale and hires your videos! + - **IP Adapter** + - add support for `ip-adapter-plus_sd15`, `ip-adapter-plus-face_sd15` and `ip-adapter-full-face_sd15` + - can now be used in *xyz-grid* + - **Text-to-Video** + - in text tab, select `text-to-video` script + - supported models: ModelScope v1.7b, ZeroScope v1, ZeroScope v1.1, ZeroScope v2, ZeroScope v2 Dark, Potat v1 + *if you know of any other t2v models you'd like to see supported, let me know!* + - models are auto-downloaded on first use + - *note*: current base model will be unloaded to free up resources - [Segmind Vega](https://huggingface.co/segmind/Segmind-Vega) support - - small and fast version of SDXL, only 3.1GB in size! - - select from networks -> reference + - small and fast version of **SDXL**, only 3.1GB in size! + - select from *networks -> reference* - [Playground v1](https://huggingface.co/playgroundai/playground-v1), [Playground v2 256](https://huggingface.co/playgroundai/playground-v2-256px-base), [Playground v2 512](https://huggingface.co/playgroundai/playground-v2-512px-base), [Playground v2 1024](https://huggingface.co/playgroundai/playground-v2-1024px-aesthetic) model support - simply select from *networks -> reference* and use as usual - [DemoFusion](https://github.com/PRIS-CV/DemoFusion) run your SDXL generations at any resolution! - - in **Text** tab select *script* -> *demofusion* + - in **Text** tab select *script* -> *demofusion* - *note*: GPU VRAM limits do not automatically go away so be careful when using it with large resolutions in the future, expect more optimizations, especially related to offloading/slicing/tiling, - but at the moment this is pretty much experimental-only - - [ModelScope T2V](https://huggingface.co/damo-vilab/text-to-video-ms-1.7b) model support - - simply select from *networks -> reference* and use from *txt2img* tab - - **Schedulers** + but at the moment this is pretty much experimental-only + - **Custom Pipelines** contribute by adding your own custom pipelines! + - for details, see fully documented example: + - **Schedulers** - add timesteps range, changing it will make scheduler to be over-complete or under-complete - add rescale betas with zero SNR option (applicable to Euler and DDIM, allows for higher dynamic range) - - **Custom Pipelines** contribute by adding your own custom pipelines! - - for details, see fully documented example: - **General** - **Process** create videos from batch or folder processing supports *GIF*, *PNG* and *MP4* with full interpolation, scene change detection, etc. - **LoRA** - add support for block weights, thanks @AI-Casanova example `` + - add support for LyCORIS GLora networks - reintroduce alternative loading method in settings: `lora_force_diffusers` - add support for `lora_fuse_diffusers` if using alternative method use if you have multiple complex loras that may be causing performance degradation @@ -44,26 +51,25 @@ - built-in *MKL* and *DPCPP* for IPEX, no need to install OneAPI anymore - fix IPEX Optimize not applying with Diffusers backend - disable 32 bit workarounds if the GPU supports 64 bit - - add DISABLE_IPEXRUN environment variable + - add `DISABLE_IPEXRUN` environment variable - compatibility improvements - **OpenVINO**, thanks @disty0 - add *Directory for OpenVINO cache* option to *System Paths* - remove Intel ARC specific 1024x1024 workaround - - **UI** + - **UI** - more dynamic controls depending on the backend (original or diffusers) controls that are not applicable in current mode are now hidden - allow setting of resize method directly in image tab - (previously via settings -> upscaler_for_img2img) - - **HDR controls** + (previously via settings -> upscaler_for_img2img) + - **HDR controls** - batch-aware for enhancement of multiple images or video frames - available in image tab - - **Other** + - **Other** - **Inpaint** add option `apply_overlay` to control if inpaint result should be applied as overlay or as-is can remove artifacts and hard edges of inpaint area but also remove some details from original - **chaiNNer** fix `NaN` issues due to autocast - **Upscale** increase limit from 4x to 8x given the quality of some upscalers - **Extra Networks** fix sort - - **LoRA** add support for LyCORIS GLora networks - disable google fonts check on server startup - fix torchvision/basicsr compatibility - fix styles quick save @@ -73,7 +79,7 @@ - log output file sizes - avoid unnecessary resizes in img2img and inpaint - updated `cli/simple-txt2img.py` and `cli/simple-img2img.py` scripts - - save `params.txt` regardless of image save status + - save `params.txt` regardless of image save status - update built-in log monitor in ui, thanks @midcoastal ## Update for 2023-12-04 diff --git a/html/reference.json b/html/reference.json index 09cb1d92f..4043fe2da 100644 --- a/html/reference.json +++ b/html/reference.json @@ -113,10 +113,5 @@ "path": "thu-ml/unidiffuser-v1", "desc": "UniDiffuser is a unified diffusion framework to fit all distributions relevant to a set of multi-modal data in one transformer. UniDiffuser is able to perform image, text, text-to-image, image-to-text, and image-text pair generation by setting proper timesteps without additional overhead.\nSpecifically, UniDiffuser employs a variation of transformer, called U-ViT, which parameterizes the joint noise prediction network. Other components perform as encoders and decoders of different modalities, including a pretrained image autoencoder from Stable Diffusion, a pretrained image ViT-B/32 CLIP encoder, a pretrained text ViT-L CLIP encoder, and a GPT-2 text decoder finetuned by ourselves.", "preview": "thu-ml--unidiffuser-v1.jpg" - }, - "ModelScope T2V": { - "path": "damo-vilab/text-to-video-ms-1.7b", - "desc": "The text-to-video generation diffusion model consists of three sub-networks: text feature extraction model, text feature-to-video latent space diffusion model, and video latent space to video visual space model. The overall model parameters are about 1.7 billion. Currently, it only supports English input. The diffusion model adopts a UNet3D structure, and implements video generation through the iterative denoising process from the pure Gaussian noise video.", - "preview": "damo-vilab--text-to-video-ms-1.7b.jpg" } } \ No newline at end of file diff --git a/scripts/stablevideodiffusion.py b/scripts/stablevideodiffusion.py index 116bf9636..078c0a054 100644 --- a/scripts/stablevideodiffusion.py +++ b/scripts/stablevideodiffusion.py @@ -1,6 +1,7 @@ """ Additional params for StableVideoDiffusion """ + import torch import gradio as gr from modules import scripts, processing, shared, sd_models, images diff --git a/scripts/text2video.py b/scripts/text2video.py new file mode 100644 index 000000000..34571b4ae --- /dev/null +++ b/scripts/text2video.py @@ -0,0 +1,106 @@ +""" +Additional params for Text-to-Video + + +TODO: +- Video-to-Video upscaling: , +""" + +import gradio as gr +from modules import scripts, processing, shared, images, sd_models, modelloader + + +MODELS = [ + {'name': 'None'}, + {'name': 'ModelScope v1.7b', 'path': 'damo-vilab/text-to-video-ms-1.7b', 'params': [16,320,320]}, + {'name': 'ZeroScope v1', 'path': 'cerspense/zeroscope_v1_320s', 'params': [16,320,320]}, + {'name': 'ZeroScope v1.1', 'path': 'cerspense/zeroscope_v1-1_320s', 'params': [16,320,320]}, + {'name': 'ZeroScope v2', 'path': 'cerspense/zeroscope_v2_576w', 'params': [24,576,320]}, + {'name': 'ZeroScope v2 Dark', 'path': 'cerspense/zeroscope_v2_dark_30x448x256', 'params': [24,448,256]}, + {'name': 'Potat v1', 'path': 'camenduru/potat1', 'params': [24,1024,576]}, +] + + +class Script(scripts.Script): + def title(self): + return 'Text-to-Video' + + def show(self, is_img2img): + return not is_img2img if shared.backend == shared.Backend.DIFFUSERS else False + + # return signature is array of gradio components + def ui(self, _is_img2img): + + def video_type_change(video_type): + return [ + gr.update(visible=video_type != 'None'), + gr.update(visible=video_type == 'GIF' or video_type == 'PNG'), + gr.update(visible=video_type == 'MP4'), + gr.update(visible=video_type == 'MP4'), + ] + + def model_info_change(model_name): + if model_name == 'None': + return gr.update(value='') + else: + model = next(m for m in MODELS if m['name'] == model_name) + return gr.update(value=f'   frames: {model["params"][0]} size: {model["params"][1]}x{model["params"][2]}
link') + + with gr.Row(): + model_name = gr.Dropdown(label='Model', value='None', choices=[m['name'] for m in MODELS]) + with gr.Row(): + model_info = gr.HTML() + model_name.change(fn=model_info_change, inputs=[model_name], outputs=[model_info]) + with gr.Row(): + use_default = gr.Checkbox(label='Use defaults', value=True) + num_frames = gr.Slider(label='Frames', minimum=1, maximum=50, step=1, value=0) + with gr.Row(): + video_type = gr.Dropdown(label='Video file', choices=['None', 'GIF', 'PNG', 'MP4'], value='None') + duration = gr.Slider(label='Duration', minimum=0.25, maximum=10, step=0.25, value=2, visible=False) + with gr.Row(): + gif_loop = gr.Checkbox(label='Loop', value=True, visible=False) + mp4_pad = gr.Slider(label='Pad frames', minimum=0, maximum=24, step=1, value=1, visible=False) + mp4_interpolate = gr.Slider(label='Interpolate frames', minimum=0, maximum=24, step=1, value=0, visible=False) + video_type.change(fn=video_type_change, inputs=[video_type], outputs=[duration, gif_loop, mp4_pad, mp4_interpolate]) + return [model_name, use_default, num_frames, video_type, duration, gif_loop, mp4_pad, mp4_interpolate] + + def run(self, p: processing.StableDiffusionProcessing, model_name, use_default, num_frames, video_type, duration, gif_loop, mp4_pad, mp4_interpolate): # pylint: disable=arguments-differ, unused-argument + if model_name == 'None': + return + model = [m for m in MODELS if m['name'] == model_name][0] + shared.log.debug(f'Text2Video: model={model} defaults={use_default} frames={num_frames}, video={video_type} duration={duration} loop={gif_loop} pad={mp4_pad} interpolate={mp4_interpolate}') + + if model['path'] in shared.opts.sd_model_checkpoint: + shared.log.debug(f'Text2Video cached: model={shared.opts.sd_model_checkpoint}') + else: + checkpoint = sd_models.get_closet_checkpoint_match(model['path']) + if checkpoint is None: + shared.log.debug(f'Text2Video downloading: model={model["path"]}') + checkpoint = modelloader.download_diffusers_model(hub_id=model['path']) + sd_models.list_models() + if checkpoint is None: + shared.log.error(f'Text2Video: failed to find model={model["path"]}') + return + shared.log.debug(f'Text2Video loading: model={checkpoint}') + shared.opts.sd_model_checkpoint = checkpoint + sd_models.reload_model_weights(op='model') + + p.ops.append('text2video') + p.do_not_save_grid = True + if use_default: + p.task_args['num_frames'] = model['params'][0] + p.width = model['params'][1] + p.height = model['params'][2] + elif num_frames > 0: + p.task_args['num_frames'] = num_frames + else: + shared.log.error('Text2Video: invalid number of frames') + return + + shared.sd_model = sd_models.set_diffuser_pipe(shared.sd_model, sd_models.DiffusersTaskType.TEXT_2_IMAGE) + shared.log.debug(f'Text2Video: args={p.task_args}') + processed = processing.process_images(p) + + if video_type != 'None': + images.save_video(p, filename=None, images=processed.images, video_type=video_type, duration=duration, loop=gif_loop, pad=mp4_pad, interpolate=mp4_interpolate) + return processed diff --git a/wiki b/wiki index 6317f96f9..8e60a3b8d 160000 --- a/wiki +++ b/wiki @@ -1 +1 @@ -Subproject commit 6317f96f9b5ade24e8ea5c8e796d5c22c412cfd6 +Subproject commit 8e60a3b8dcb46991947ccefd8dae2d0f9e0dc6bf From 0b3e1f475d87696207beb8c65de3743d4abe20d1 Mon Sep 17 00:00:00 2001 From: AI-Casanova <54461896+AI-Casanova@users.noreply.github.com> Date: Sun, 17 Dec 2023 19:37:05 -0600 Subject: [PATCH 070/143] Fix batch & optimize --- modules/processing.py | 1 + modules/processing_diffusers.py | 16 +++++++++------- modules/prompt_parser_diffusers.py | 22 ++++++++++++++-------- 3 files changed, 24 insertions(+), 15 deletions(-) diff --git a/modules/processing.py b/modules/processing.py index 68a1912c3..6d51d6d1d 100644 --- a/modules/processing.py +++ b/modules/processing.py @@ -231,6 +231,7 @@ def __init__(self, sd_model=None, outpath_samples=None, outpath_grids=None, prom self.hdr_maximize = hdr_maximize self.hdr_max_center = hdr_max_center self.hdr_max_boundry = hdr_max_boundry + self.scheduled_prompt: bool = False self.prompt_embeds = [] self.positive_pooleds = [] self.negative_embeds = [] diff --git a/modules/processing_diffusers.py b/modules/processing_diffusers.py index 25f6fe646..66c607fe9 100644 --- a/modules/processing_diffusers.py +++ b/modules/processing_diffusers.py @@ -89,13 +89,15 @@ def diffusers_callback(_pipe, step: int, timestep: int, kwargs: dict): if kwargs.get('latents', None) is None: return kwargs kwargs = correction_callback(p, timestep, kwargs) - try: - kwargs["prompt_embeds"] = p.prompt_embeds[step + 1].repeat(1, kwargs["prompt_embeds"].shape[0], 1).view( - kwargs["prompt_embeds"].shape[0], kwargs["prompt_embeds"].shape[1], -1) - kwargs["negative_prompt_embeds"] = p.negative_embeds[step + 1].repeat(1, kwargs["negative_prompt_embeds"].shape[0], 1).view( - kwargs["negative_prompt_embeds"].shape[0], kwargs["negative_prompt_embeds"].shape[1], -1) - except: - pass + if p.scheduled_prompt: + try: + i = (step + 1) % len(p.prompt_embeds) + kwargs["prompt_embeds"] = p.prompt_embeds[i][0:1].repeat(1, kwargs["prompt_embeds"].shape[0], 1).view( + kwargs["prompt_embeds"].shape[0], kwargs["prompt_embeds"].shape[1], -1) + kwargs["negative_prompt_embeds"] = p.negative_embeds[i][0:1].repeat(1, kwargs["negative_prompt_embeds"].shape[0], 1).view( + kwargs["negative_prompt_embeds"].shape[0], kwargs["negative_prompt_embeds"].shape[1], -1) + except Exception as e: + shared.log.debug(f"Callback: {e}") shared.state.current_latent = kwargs['latents'] if shared.cmd_opts.profile and shared.profiler is not None: shared.profiler.step() diff --git a/modules/prompt_parser_diffusers.py b/modules/prompt_parser_diffusers.py index 16fab82d3..ae5acb8f6 100644 --- a/modules/prompt_parser_diffusers.py +++ b/modules/prompt_parser_diffusers.py @@ -58,33 +58,39 @@ def expand_textual_inversion_token_ids_if_necessary(self, token_ids: typing.List debug(f'Prompt: expand={prompt}') return self.pipe.tokenizer.encode(prompt, add_special_tokens=False) -def get_prompt_schedule(prompt, steps): +def get_prompt_schedule(p, prompt, steps): temp = [] schedule = prompt_parser.get_learned_conditioning_prompt_schedules([prompt], steps)[0] for chunk in schedule: for s in range(steps): if len(temp) < s + 1 <= chunk[0]: temp.append(chunk[1]) - return temp + return temp, len(schedule) > 1 def encode_prompts(pipe, p, prompts: list, negative_prompts: list, steps: int, step: int = 1, clip_skip: typing.Optional[int] = None): if 'StableDiffusion' not in pipe.__class__.__name__ and 'DemoFusion': shared.log.warning(f"Prompt parser not supported: {pipe.__class__.__name__}") return None, None, None, None else: - positive_schedule = get_prompt_schedule(prompts[0], steps) - negative_schedule = get_prompt_schedule(negative_prompts[0], steps) + positive_schedule, scheduled = get_prompt_schedule(p, prompts[0], steps) + negative_schedule, neg_scheduled = get_prompt_schedule(p, negative_prompts[0], steps) + p.scheduled_prompt = scheduled or neg_scheduled p.prompt_embeds = [] p.positive_pooleds = [] p.negative_embeds = [] p.negative_pooleds = [] + cache = {} for i in range(len(positive_schedule)): - prompt_embed, positive_pooled, negative_embed, negative_pooled = get_weighted_text_embeddings(pipe, - positive_schedule[i], - negative_schedule[i], - clip_skip) + cached = cache.get(positive_schedule[i]+negative_schedule[i], None) + if cached is not None: + prompt_embed, positive_pooled, negative_embed, negative_pooled = cached + else: + prompt_embed, positive_pooled, negative_embed, negative_pooled = get_weighted_text_embeddings(pipe, + positive_schedule[i], + negative_schedule[i], + clip_skip) if prompt_embed is not None: p.prompt_embeds.append(torch.cat([prompt_embed]*len(prompts), dim=0)) if negative_embed is not None: From 5b61d849b8d6733158c09b02128cbb69ae63e33c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Enes=20Sad=C4=B1k=20=C3=96zbek?= Date: Mon, 18 Dec 2023 15:21:06 +0300 Subject: [PATCH 071/143] [Breaking change] Align some APIs with AUTOMATIC1111 * /refresh-vaes -> /refresh-vae * /sd_models response [name -> model_name] --- modules/api/api.py | 4 ++-- modules/api/models.py | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/modules/api/api.py b/modules/api/api.py index 66bce7959..0ad7d232c 100644 --- a/modules/api/api.py +++ b/modules/api/api.py @@ -133,7 +133,7 @@ def __init__(self, app: FastAPI, queue_lock: Lock): self.add_api_route("/sdapi/v1/embeddings", self.get_embeddings, methods=["GET"], response_model=models.EmbeddingsResponse) self.add_api_route("/sdapi/v1/refresh-checkpoints", self.refresh_checkpoints, methods=["POST"]) self.add_api_route("/sdapi/v1/sd-vae", self.get_sd_vaes, methods=["GET"], response_model=List[models.SDVaeItem]) - self.add_api_route("/sdapi/v1/refresh-vaes", self.refresh_vaes, methods=["POST"]) + self.add_api_route("/sdapi/v1/refresh-vae", self.refresh_vaes, methods=["POST"]) self.add_api_route("/sdapi/v1/create/embedding", self.create_embedding, methods=["POST"], response_model=models.CreateResponse) self.add_api_route("/sdapi/v1/create/hypernetwork", self.create_hypernetwork, methods=["POST"], response_model=models.CreateResponse) self.add_api_route("/sdapi/v1/preprocess", self.preprocess, methods=["POST"], response_model=models.PreprocessResponse) @@ -464,7 +464,7 @@ def get_upscalers(self): return [{"name": upscaler.name, "model_name": upscaler.scaler.model_name, "model_path": upscaler.data_path, "model_url": None, "scale": upscaler.scale} for upscaler in shared.sd_upscalers] def get_sd_models(self): - return [{"title": x.title, "name": x.name, "filename": x.filename, "type": x.type, "hash": x.shorthash, "sha256": x.sha256, "config": find_checkpoint_config_near_filename(x)} for x in checkpoints_list.values()] + return [{"title": x.title, "model_name": x.name, "filename": x.filename, "type": x.type, "hash": x.shorthash, "sha256": x.sha256, "config": find_checkpoint_config_near_filename(x)} for x in checkpoints_list.values()] def get_hypernetworks(self): return [{"name": name, "path": shared.hypernetworks[name]} for name in shared.hypernetworks] diff --git a/modules/api/models.py b/modules/api/models.py index 3ed73a42e..7b8ecfbd7 100644 --- a/modules/api/models.py +++ b/modules/api/models.py @@ -245,7 +245,7 @@ class UpscalerItem(BaseModel): class SDModelItem(BaseModel): title: str = Field(title="Title") - name: str = Field(title="Model Name") + model_name: str = Field(title="Model Name") filename: str = Field(title="Filename") type: str = Field(title="Model type") sha256: Optional[str] = Field(title="SHA256 hash") From 0263a898265b15de8dcc3342dd89f450a854d2e6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Enes=20Sad=C4=B1k=20=C3=96zbek?= Date: Mon, 18 Dec 2023 18:08:17 +0300 Subject: [PATCH 072/143] vscode: disable format on save --- .vscode/settings.json | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/.vscode/settings.json b/.vscode/settings.json index d5e8da26c..2b6588ae9 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -10,5 +10,6 @@ "./repositories/stable-diffusion-stability-ai", "./repositories/stable-diffusion-stability-ai/ldm" ], - "python.analysis.typeCheckingMode": "off" -} + "python.analysis.typeCheckingMode": "off", + "editor.formatOnSave": false +} \ No newline at end of file From 350cd17465d4da263610fa8779817f2414e66667 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Enes=20Sad=C4=B1k=20=C3=96zbek?= Date: Mon, 18 Dec 2023 18:09:25 +0300 Subject: [PATCH 073/143] Implement remaining API changes --- modules/api/api.py | 31 ++++++++++++++++++++++++++++--- modules/api/models.py | 15 +++++++++++---- 2 files changed, 39 insertions(+), 7 deletions(-) diff --git a/modules/api/api.py b/modules/api/api.py index 0ad7d232c..a7a0833bb 100644 --- a/modules/api/api.py +++ b/modules/api/api.py @@ -13,7 +13,7 @@ import piexif import piexif.helper import gradio as gr -from modules import errors, shared, sd_samplers, deepbooru, sd_hijack, images, scripts, ui, postprocessing +from modules import errors, shared, sd_samplers, deepbooru, sd_hijack, images, scripts, ui, postprocessing, script_callbacks, generation_parameters_copypaste from modules.sd_vae import vae_dict from modules.api import models from modules.processing import StableDiffusionProcessingTxt2Img, StableDiffusionProcessingImg2Img, process_images @@ -145,6 +145,7 @@ def __init__(self, app: FastAPI, queue_lock: Lock): self.add_api_route("/sdapi/v1/reload-checkpoint", self.reloadapi, methods=["POST"]) self.add_api_route("/sdapi/v1/scripts", self.get_scripts_list, methods=["GET"], response_model=models.ScriptsList) self.add_api_route("/sdapi/v1/script-info", self.get_script_info, methods=["GET"], response_model=List[models.ScriptInfo]) + self.add_api_route("/sdapi/v1/extensions", self.get_extensions_list, methods=["GET"], response_model=List[models.ExtensionItem]) self.add_api_route("/sdapi/v1/log", self.get_log_buffer, methods=["GET"], response_model=List) self.add_api_route("/sdapi/v1/start", self.session_start, methods=["GET"]) self.add_api_route("/sdapi/v1/motd", self.get_motd, methods=["GET"], response_model=str) @@ -368,14 +369,19 @@ def extras_batch_images_api(self, req: models.ExtrasBatchImagesRequest): def pnginfoapi(self, req: models.PNGInfoRequest): if not req.image.strip(): return models.PNGInfoResponse(info="") + image = decode_base64_to_image(req.image.strip()) if image is None: return models.PNGInfoResponse(info="") + geninfo, items = images.read_info_from_image(image) if geninfo is None: geninfo = "" - items = {**{'parameters': geninfo}, **items} - return models.PNGInfoResponse(info=geninfo, items=items) + + params = generation_parameters_copypaste.parse_generation_parameters(geninfo) + script_callbacks.infotext_pasted_callback(geninfo, params) + + return models.PNGInfoResponse(info=geninfo, items=items, parameters=params) def progressapi(self, req: models.ProgressRequest = Depends()): if shared.state.job_count == 0: @@ -640,6 +646,25 @@ def get_memory(self): cuda = { 'error': f'{err}' } return models.MemoryResponse(ram = ram, cuda = cuda) + def get_extensions_list(self): + from modules import extensions + extensions.list_extensions() + ext_list = [] + for ext in extensions.extensions: + ext: extensions.Extension + ext.read_info_from_repo() + if ext.remote is not None: + ext_list.append({ + "name": ext.name, + "remote": ext.remote, + "branch": ext.branch, + "commit_hash":ext.commit_hash, + "commit_date":ext.commit_date, + "version":ext.version, + "enabled":ext.enabled + }) + return ext_list + def launch(self): config = { "listen": shared.cmd_opts.listen, diff --git a/modules/api/models.py b/modules/api/models.py index 7b8ecfbd7..2c0d72d87 100644 --- a/modules/api/models.py +++ b/modules/api/models.py @@ -171,6 +171,7 @@ class PNGInfoRequest(BaseModel): class PNGInfoResponse(BaseModel): info: str = Field(title="Image info", description="A string with the parameters used to generate the image") items: dict = Field(title="Items", description="An object containing all the info the image had") + parameters: dict = Field(title="Parameters", description="A dictionary with parsed generation info fields") class LogRequest(BaseModel): lines: int = Field(default=100, title="Lines", description="How many lines to return") @@ -209,7 +210,7 @@ class PreprocessResponse(BaseModel): if metadata is not None: fields.update({key: (Optional[optType], Field( - default=metadata.default ,description=metadata.label))}) + default=metadata.default, description=metadata.label))}) else: fields.update({key: (Optional[optType], Field())}) @@ -286,7 +287,6 @@ class ExtraNetworkItem(BaseModel): # metadata: Optional[Any] = Field(title="Metadata") # local: Optional[str] = Field(title="Local") - class ArtistItem(BaseModel): name: str = Field(title="Name") score: float = Field(title="Score") @@ -311,7 +311,6 @@ class ScriptsList(BaseModel): txt2img: list = Field(default=None, title="Txt2img", description="Titles of scripts (txt2img)") img2img: list = Field(default=None, title="Img2img", description="Titles of scripts (img2img)") - class ScriptArg(BaseModel): label: str = Field(default=None, title="Label", description="Name of the argument in UI") value: Optional[Any] = Field(default=None, title="Value", description="Default value of the argument") @@ -320,9 +319,17 @@ class ScriptArg(BaseModel): step: Optional[Any] = Field(default=None, title="Minimum", description="Step for changing value of the argumentin UI") choices: Optional[Any] = Field(default=None, title="Choices", description="Possible values for the argument") - class ScriptInfo(BaseModel): name: str = Field(default=None, title="Name", description="Script name") is_alwayson: bool = Field(default=None, title="IsAlwayson", description="Flag specifying whether this script is an alwayson script") is_img2img: bool = Field(default=None, title="IsImg2img", description="Flag specifying whether this script is an img2img script") args: List[ScriptArg] = Field(title="Arguments", description="List of script's arguments") + +class ExtensionItem(BaseModel): + name: str = Field(title="Name", description="Extension name") + remote: str = Field(title="Remote", description="Extension Repository URL") + branch: str = Field(title="Branch", description="Extension Repository Branch") + commit_hash: str = Field(title="Commit Hash", description="Extension Repository Commit Hash") + version: str = Field(title="Version", description="Extension Version") + commit_date: str = Field(title="Commit Date", description="Extension Repository Commit Date") + enabled: bool = Field(title="Enabled", description="Flag specifying whether this extension is enabled") From 17e8fbfe92f8fb12bcc096520566ad637f660a74 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Enes=20Sad=C4=B1k=20=C3=96zbek?= Date: Mon, 18 Dec 2023 18:29:08 +0300 Subject: [PATCH 074/143] Fix png-info and extensions endpoints --- modules/api/api.py | 5 ++++- modules/api/models.py | 2 +- 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/modules/api/api.py b/modules/api/api.py index a7a0833bb..d33a81834 100644 --- a/modules/api/api.py +++ b/modules/api/api.py @@ -378,6 +378,9 @@ def pnginfoapi(self, req: models.PNGInfoRequest): if geninfo is None: geninfo = "" + if items and items['parameters']: + del items['parameters'] + params = generation_parameters_copypaste.parse_generation_parameters(geninfo) script_callbacks.infotext_pasted_callback(geninfo, params) @@ -652,7 +655,7 @@ def get_extensions_list(self): ext_list = [] for ext in extensions.extensions: ext: extensions.Extension - ext.read_info_from_repo() + ext.read_info() if ext.remote is not None: ext_list.append({ "name": ext.name, diff --git a/modules/api/models.py b/modules/api/models.py index 2c0d72d87..9d90f0ca7 100644 --- a/modules/api/models.py +++ b/modules/api/models.py @@ -170,7 +170,7 @@ class PNGInfoRequest(BaseModel): class PNGInfoResponse(BaseModel): info: str = Field(title="Image info", description="A string with the parameters used to generate the image") - items: dict = Field(title="Items", description="An object containing all the info the image had") + items: dict = Field(title="Items", description="A dictionary containing all the other fields the image had") parameters: dict = Field(title="Parameters", description="A dictionary with parsed generation info fields") class LogRequest(BaseModel): From 21adfd57b7a4a8f2ca57ee8388c9c9b22fc50db8 Mon Sep 17 00:00:00 2001 From: Vladimir Mandic Date: Mon, 18 Dec 2023 11:46:08 -0500 Subject: [PATCH 075/143] make some params optional --- extensions-builtin/sd-webui-controlnet | 2 +- javascript/sdnext.css | 1 + modules/processing.py | 13 ++++++++----- modules/processing_diffusers.py | 4 ++-- wiki | 2 +- 5 files changed, 13 insertions(+), 9 deletions(-) diff --git a/extensions-builtin/sd-webui-controlnet b/extensions-builtin/sd-webui-controlnet index 102449bf1..01e4574d8 160000 --- a/extensions-builtin/sd-webui-controlnet +++ b/extensions-builtin/sd-webui-controlnet @@ -1 +1 @@ -Subproject commit 102449bf16974baf9fff9a2ecec528755e1ba5e2 +Subproject commit 01e4574d8e01fa00628fdae0c8215283a1c36a8d diff --git a/javascript/sdnext.css b/javascript/sdnext.css index 2b3954bc3..846c4c5f7 100644 --- a/javascript/sdnext.css +++ b/javascript/sdnext.css @@ -71,6 +71,7 @@ button.custom-button{ border-radius: var(--button-large-radius); padding: var(-- .performance p { display: inline-block; color: var(--body-text-color-subdued) !important } .performance .time { margin-right: 0; } #control_gallery { height: 564px; } +#control-result { padding: 0.5em; } #control-inputs { margin-top: 1em; } #txt2img_prompt_container, #img2img_prompt_container, #control_prompt_container { margin-right: var(--layout-gap) } #txt2img_footer, #img2img_footer, #extras_footer, #control_footer { height: fit-content; display: none; } diff --git a/modules/processing.py b/modules/processing.py index 3ae023fbe..22019a123 100644 --- a/modules/processing.py +++ b/modules/processing.py @@ -374,8 +374,8 @@ def __init__(self, p: StableDiffusionProcessing, images_list, seed=-1, info="", self.subseed_strength = p.subseed_strength self.info = info self.comments = comments - self.width = p.width - self.height = p.height + self.width = p.width if hasattr(p, 'width') else self.images[0].width + self.height = p.height if hasattr(p, 'height') else self.images[0].height self.sampler_name = p.sampler_name self.cfg_scale = p.cfg_scale self.image_cfg_scale = p.image_cfg_scale @@ -584,7 +584,7 @@ def create_infotext(p: StableDiffusionProcessing, all_prompts=None, all_seeds=No "Seed": all_seeds[index], "Sampler": p.sampler_name, "CFG scale": p.cfg_scale, - "Size": f"{p.width}x{p.height}", + "Size": f"{p.width}x{p.height}" if hasattr(p, 'width') and hasattr(p, 'height') else None, "Batch": f'{p.n_iter}x{p.batch_size}' if p.n_iter > 1 or p.batch_size > 1 else None, "Index": f'{p.iteration + 1}x{index + 1}' if (p.n_iter > 1 or p.batch_size > 1) and index >= 0 else None, "Parser": shared.opts.prompt_attention, @@ -636,6 +636,8 @@ def create_infotext(p: StableDiffusionProcessing, all_prompts=None, all_seeds=No args['Resize scale'] = getattr(p, 'scale_by', None) args["Mask blur"] = p.mask_blur if getattr(p, 'mask', None) is not None and getattr(p, 'mask_blur', 0) > 0 else None args["Denoising strength"] = getattr(p, 'denoising_strength', None) + if args["Size"] is None: + args["Size"] = args["Init image size"] # lookup by index if getattr(p, 'resize_mode', None) is not None: args['Resize mode'] = shared.resize_modes[p.resize_mode] @@ -1284,7 +1286,8 @@ def init(self, all_prompts, all_seeds, all_subseeds): unprocessed = [] if getattr(self, 'init_images', None) is None: return - # raise RuntimeError("No images provided") + if not isinstance(self.init_images, list): + self.init_images = [self.init_images] for img in self.init_images: if img is None: shared.log.warning(f"Skipping empty image: images={self.init_images}") @@ -1295,7 +1298,7 @@ def init(self, all_prompts, all_seeds, all_subseeds): if shared.opts.save_init_img: images.save_image(img, path=shared.opts.outdir_init_images, basename=None, forced_filename=self.init_img_hash, suffix="-init-image") image = images.flatten(img, shared.opts.img2img_background_color) - if crop_region is None and self.resize_mode != 4: + if crop_region is None and self.resize_mode != 4 and self.resize_mode > 0: if image.width != self.width or image.height != self.height: image = images.resize_image(self.resize_mode, image, self.width, self.height, self.resize_name) self.width = image.width diff --git a/modules/processing_diffusers.py b/modules/processing_diffusers.py index a6490fb58..c713399e3 100644 --- a/modules/processing_diffusers.py +++ b/modules/processing_diffusers.py @@ -527,7 +527,8 @@ def calculate_refiner_steps(): update_sampler(shared.sd_model) shared.state.sampling_steps = base_args['num_inference_steps'] p.extra_generation_params['Pipeline'] = shared.sd_model.__class__.__name__ - p.extra_generation_params["Sampler Eta"] = shared.opts.scheduler_eta if shared.opts.scheduler_eta is not None and shared.opts.scheduler_eta > 0 and shared.opts.scheduler_eta < 1 else None + if shared.opts.scheduler_eta is not None and shared.opts.scheduler_eta > 0 and shared.opts.scheduler_eta < 1: + p.extra_generation_params["Sampler Eta"] = shared.opts.scheduler_eta try: t0 = time.time() output = shared.sd_model(**base_args) # pylint: disable=not-callable @@ -652,7 +653,6 @@ def calculate_refiner_steps(): shared.state.sampling_steps = refiner_args['num_inference_steps'] try: shared.sd_refiner.register_to_config(requires_aesthetics_score=shared.opts.diffusers_aesthetics_score) - print('HERE req', shared.sd_refiner.config.requires_aesthetics_score) refiner_output = shared.sd_refiner(**refiner_args) # pylint: disable=not-callable except AssertionError as e: shared.log.info(e) diff --git a/wiki b/wiki index 8e60a3b8d..554124a95 160000 --- a/wiki +++ b/wiki @@ -1 +1 @@ -Subproject commit 8e60a3b8dcb46991947ccefd8dae2d0f9e0dc6bf +Subproject commit 554124a957b9d895475c337dbcb844753a1120d3 From c1aa146b5a0580f2daab7893366b1527854e32d0 Mon Sep 17 00:00:00 2001 From: Vladimir Mandic Date: Mon, 18 Dec 2023 11:56:16 -0500 Subject: [PATCH 076/143] update changelog --- CHANGELOG.md | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index e88862b6a..904e112d9 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -26,7 +26,8 @@ - *note*: GPU VRAM limits do not automatically go away so be careful when using it with large resolutions in the future, expect more optimizations, especially related to offloading/slicing/tiling, but at the moment this is pretty much experimental-only - - **Custom Pipelines** contribute by adding your own custom pipelines! + - **Prompt scheduling** now implemented for Diffusers backend, thanks @AI-Casanova + - **Custom pipelines** contribute by adding your own custom pipelines! - for details, see fully documented example: - **Schedulers** - add timesteps range, changing it will make scheduler to be over-complete or under-complete @@ -65,6 +66,7 @@ - batch-aware for enhancement of multiple images or video frames - available in image tab - **Other** + - **API** several minor but breaking changes to API behavior to better align response fields, thanks @Trojaner - **Inpaint** add option `apply_overlay` to control if inpaint result should be applied as overlay or as-is can remove artifacts and hard edges of inpaint area but also remove some details from original - **chaiNNer** fix `NaN` issues due to autocast From f13f2d03f9cee3797cfa976090c7f5c9f047de5a Mon Sep 17 00:00:00 2001 From: Vladimir Mandic Date: Mon, 18 Dec 2023 13:27:36 -0500 Subject: [PATCH 077/143] fix prompt scheduling --- modules/api/api.py | 2 +- modules/processing.py | 2 ++ modules/processing_diffusers.py | 4 ++-- modules/prompt_parser_diffusers.py | 5 +++++ 4 files changed, 10 insertions(+), 3 deletions(-) diff --git a/modules/api/api.py b/modules/api/api.py index d33a81834..1f60e5c01 100644 --- a/modules/api/api.py +++ b/modules/api/api.py @@ -667,7 +667,7 @@ def get_extensions_list(self): "enabled":ext.enabled }) return ext_list - + def launch(self): config = { "listen": shared.cmd_opts.listen, diff --git a/modules/processing.py b/modules/processing.py index deaf81ac5..156a33810 100644 --- a/modules/processing.py +++ b/modules/processing.py @@ -697,6 +697,8 @@ def process_images(p: StableDiffusionProcessing) -> Processed: p.scripts.before_process(p) stored_opts = {} for k, v in p.override_settings.copy().items(): + if shared.opts.data.get(k, None) is None and shared.opts.data_labels.get(k, None) is None: + continue orig = shared.opts.data.get(k, None) or shared.opts.data_labels[k].default if orig == v or (type(orig) == str and os.path.splitext(orig)[0] == v): p.override_settings.pop(k, None) diff --git a/modules/processing_diffusers.py b/modules/processing_diffusers.py index 15e3bc3b8..73c3fcc09 100644 --- a/modules/processing_diffusers.py +++ b/modules/processing_diffusers.py @@ -314,14 +314,14 @@ def set_pipeline_args(model, prompts: list, negative_prompts: list, prompts_2: t if os.environ.get('SD_PROMPT_DEBUG', None) is not None: errors.display(e, 'Prompt parser encode') if 'prompt' in possible: - if hasattr(model, 'text_encoder') and 'prompt_embeds' in possible and p.prompt_embeds[0] is not None: + if hasattr(model, 'text_encoder') and 'prompt_embeds' in possible and len(p.prompt_embeds) > 0 and p.prompt_embeds[0] is not None: args['prompt_embeds'] = p.prompt_embeds[0] if 'XL' in model.__class__.__name__: args['pooled_prompt_embeds'] = p.positive_pooleds[0] else: args['prompt'] = prompts if 'negative_prompt' in possible: - if hasattr(model, 'text_encoder') and 'negative_prompt_embeds' in possible and p.negative_embeds[0] is not None: + if hasattr(model, 'text_encoder') and 'negative_prompt_embeds' in possible and len(p.negative_embeds) > 0 and p.negative_embeds[0] is not None: args['negative_prompt_embeds'] = p.negative_embeds[0] if 'XL' in model.__class__.__name__: args['negative_pooled_prompt_embeds'] = p.negative_pooleds[0] diff --git a/modules/prompt_parser_diffusers.py b/modules/prompt_parser_diffusers.py index ae5acb8f6..f29a19b6d 100644 --- a/modules/prompt_parser_diffusers.py +++ b/modules/prompt_parser_diffusers.py @@ -1,4 +1,5 @@ import os +import time import typing import torch from compel import ReturnedEmbeddingsType @@ -59,12 +60,16 @@ def expand_textual_inversion_token_ids_if_necessary(self, token_ids: typing.List return self.pipe.tokenizer.encode(prompt, add_special_tokens=False) def get_prompt_schedule(p, prompt, steps): + t0 = time.time() temp = [] schedule = prompt_parser.get_learned_conditioning_prompt_schedules([prompt], steps)[0] + if all(x == schedule[0] for x in schedule): + return [prompt], False for chunk in schedule: for s in range(steps): if len(temp) < s + 1 <= chunk[0]: temp.append(chunk[1]) + debug(f'Prompt: schedule={temp} time={time.time()-t0}') return temp, len(schedule) > 1 def encode_prompts(pipe, p, prompts: list, negative_prompts: list, steps: int, step: int = 1, clip_skip: typing.Optional[int] = None): From 711c086be0eb509d38313aca84887a27ffe45985 Mon Sep 17 00:00:00 2001 From: Vladimir Mandic Date: Mon, 18 Dec 2023 14:11:21 -0500 Subject: [PATCH 078/143] update wiki --- wiki | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/wiki b/wiki index 554124a95..5bc95f681 160000 --- a/wiki +++ b/wiki @@ -1 +1 @@ -Subproject commit 554124a957b9d895475c337dbcb844753a1120d3 +Subproject commit 5bc95f681de940288f433b13672b3ec1416f7fee From bcb54fed453882e6e32eae69c4da05c41d460f79 Mon Sep 17 00:00:00 2001 From: Vladimir Mandic Date: Mon, 18 Dec 2023 14:23:09 -0500 Subject: [PATCH 079/143] allow missing size --- modules/processing_diffusers.py | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/modules/processing_diffusers.py b/modules/processing_diffusers.py index 73c3fcc09..64eb8eebe 100644 --- a/modules/processing_diffusers.py +++ b/modules/processing_diffusers.py @@ -230,10 +230,11 @@ def task_specific_kwargs(model): is_img2img_model = bool('Zero123' in shared.sd_model.__class__.__name__) if sd_models.get_diffusers_task(model) == sd_models.DiffusersTaskType.TEXT_2_IMAGE and not is_img2img_model: p.ops.append('txt2img') - task_args = { - 'height': 8 * math.ceil(p.height / 8), - 'width': 8 * math.ceil(p.width / 8), - } + if hasattr(p, 'width') and hasattr(p, 'height'): + task_args = { + 'width': 8 * math.ceil(p.width / 8), + 'height': 8 * math.ceil(p.height / 8), + } elif (sd_models.get_diffusers_task(model) == sd_models.DiffusersTaskType.IMAGE_2_IMAGE or is_img2img_model) and len(getattr(p, 'init_images' ,[])) > 0: p.ops.append('img2img') task_args = { @@ -243,8 +244,8 @@ def task_specific_kwargs(model): elif sd_models.get_diffusers_task(model) == sd_models.DiffusersTaskType.INSTRUCT and len(getattr(p, 'init_images' ,[])) > 0: p.ops.append('instruct') task_args = { - 'height': 8 * math.ceil(p.height / 8), 'width': 8 * math.ceil(p.width / 8), + 'height': 8 * math.ceil(p.height / 8), 'image': p.init_images, 'strength': p.denoising_strength, } From 3ee3f300461ff80c7c48985be22adf635d73fac0 Mon Sep 17 00:00:00 2001 From: Vladimir Mandic Date: Mon, 18 Dec 2023 18:45:35 -0500 Subject: [PATCH 080/143] update control wiki --- wiki | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/wiki b/wiki index 5bc95f681..66f8845ba 160000 --- a/wiki +++ b/wiki @@ -1 +1 @@ -Subproject commit 5bc95f681de940288f433b13672b3ec1416f7fee +Subproject commit 66f8845ba70e3b054be1c96987a6140722042058 From 0482d5a4483dea4587857b6c70dbc1be87691f84 Mon Sep 17 00:00:00 2001 From: Vladimir Mandic Date: Mon, 18 Dec 2023 18:51:56 -0500 Subject: [PATCH 081/143] width/height validation --- modules/processing_diffusers.py | 21 +++++++++++---------- wiki | 2 +- 2 files changed, 12 insertions(+), 11 deletions(-) diff --git a/modules/processing_diffusers.py b/modules/processing_diffusers.py index 64eb8eebe..3395d370f 100644 --- a/modules/processing_diffusers.py +++ b/modules/processing_diffusers.py @@ -94,10 +94,11 @@ def diffusers_callback(_pipe, step: int, timestep: int, kwargs: dict): i = (step + 1) % len(p.prompt_embeds) kwargs["prompt_embeds"] = p.prompt_embeds[i][0:1].repeat(1, kwargs["prompt_embeds"].shape[0], 1).view( kwargs["prompt_embeds"].shape[0], kwargs["prompt_embeds"].shape[1], -1) - kwargs["negative_prompt_embeds"] = p.negative_embeds[i][0:1].repeat(1, kwargs["negative_prompt_embeds"].shape[0], 1).view( + j = (step + 1) % len(p.negative_embeds) + kwargs["negative_prompt_embeds"] = p.negative_embeds[j][0:1].repeat(1, kwargs["negative_prompt_embeds"].shape[0], 1).view( kwargs["negative_prompt_embeds"].shape[0], kwargs["negative_prompt_embeds"].shape[1], -1) except Exception as e: - shared.log.debug(f"Callback: {e}") + shared.log.debug(f"Callback: {e}") shared.state.current_latent = kwargs['latents'] if shared.cmd_opts.profile and shared.profiler is not None: shared.profiler.step() @@ -244,8 +245,8 @@ def task_specific_kwargs(model): elif sd_models.get_diffusers_task(model) == sd_models.DiffusersTaskType.INSTRUCT and len(getattr(p, 'init_images' ,[])) > 0: p.ops.append('instruct') task_args = { - 'width': 8 * math.ceil(p.width / 8), - 'height': 8 * math.ceil(p.height / 8), + 'width': 8 * math.ceil(p.width / 8) if hasattr(p, 'width') else None, + 'height': 8 * math.ceil(p.height / 8) if hasattr(p, 'height') else None, 'image': p.init_images, 'strength': p.denoising_strength, } @@ -289,8 +290,8 @@ def task_specific_kwargs(model): init_latent = (1 - p.denoising_strength) * init_latent + init_noise task_args = { 'latents': init_latent.to(model.dtype), - 'width': p.width, - 'height': p.height, + 'width': p.width if hasattr(p, 'width') else None, + 'height': p.height if hasattr(p, 'height') else None, } return task_args @@ -408,8 +409,8 @@ def set_pipeline_args(model, prompts: list, negative_prompts: list, prompts_2: t def recompile_model(hires=False): if shared.opts.cuda_compile and shared.opts.cuda_compile_backend != 'none': if shared.opts.cuda_compile_backend == "openvino_fx": - compile_height = p.height if not hires else p.hr_upscale_to_y - compile_width = p.width if not hires else p.hr_upscale_to_x + compile_height = p.height if not hires and hasattr(p, 'height') else p.hr_upscale_to_y + compile_width = p.width if not hires and hasattr(p, 'width') else p.hr_upscale_to_x if (shared.compiled_model_state is None or (not shared.compiled_model_state.first_pass and (shared.compiled_model_state.height != compile_height @@ -509,7 +510,7 @@ def calculate_refiner_steps(): if hasattr(shared.sd_model, 'unet') and hasattr(shared.sd_model.unet, 'config') and hasattr(shared.sd_model.unet.config, 'in_channels') and shared.sd_model.unet.config.in_channels == 9: shared.sd_model = sd_models.set_diffuser_pipe(shared.sd_model, sd_models.DiffusersTaskType.INPAINTING) # force pipeline if len(getattr(p, 'init_images' ,[])) == 0: - p.init_images = [TF.to_pil_image(torch.rand((3, p.height, p.width)))] + p.init_images = [TF.to_pil_image(torch.rand((3, getattr(p, 'height', 512), getattr(p, 'width', 512))))] base_args = set_pipeline_args( model=shared.sd_model, prompts=prompts, @@ -567,7 +568,7 @@ def calculate_refiner_steps(): if p.is_hr_pass: p.init_hr() prev_job = shared.state.job - if p.width != p.hr_upscale_to_x or p.height != p.hr_upscale_to_y: + if hasattr(p, 'height') and hasattr(p, 'width') and (p.width != p.hr_upscale_to_x or p.height != p.hr_upscale_to_y): p.ops.append('upscale') if shared.opts.save and not p.do_not_save_samples and shared.opts.save_images_before_highres_fix and hasattr(shared.sd_model, 'vae'): save_intermediate(latents=output.images, suffix="-before-hires") diff --git a/wiki b/wiki index 66f8845ba..0d8ab0f77 160000 --- a/wiki +++ b/wiki @@ -1 +1 @@ -Subproject commit 66f8845ba70e3b054be1c96987a6140722042058 +Subproject commit 0d8ab0f77d9bb1a1466111ab2816c23bb046c1fa From ff94b197c1d14a2163bdaedcc2b665f86efaa925 Mon Sep 17 00:00:00 2001 From: AI-Casanova <54461896+AI-Casanova@users.noreply.github.com> Date: Mon, 18 Dec 2023 23:42:31 -0600 Subject: [PATCH 082/143] Fix scheduling, lint --- modules/prompt_parser_diffusers.py | 62 +++++++++++++++--------------- 1 file changed, 32 insertions(+), 30 deletions(-) diff --git a/modules/prompt_parser_diffusers.py b/modules/prompt_parser_diffusers.py index f29a19b6d..7dcc04336 100644 --- a/modules/prompt_parser_diffusers.py +++ b/modules/prompt_parser_diffusers.py @@ -6,10 +6,8 @@ from compel.embeddings_provider import BaseTextualInversionManager, EmbeddingsProvider from modules import shared, prompt_parser, devices - debug = shared.log.info if os.environ.get('SD_PROMPT_DEBUG', None) is not None else lambda *args, **kwargs: None - CLIP_SKIP_MAPPING = { None: ReturnedEmbeddingsType.LAST_HIDDEN_STATES_NORMALIZED, 1: ReturnedEmbeddingsType.LAST_HIDDEN_STATES_NORMALIZED, @@ -59,6 +57,7 @@ def expand_textual_inversion_token_ids_if_necessary(self, token_ids: typing.List debug(f'Prompt: expand={prompt}') return self.pipe.tokenizer.encode(prompt, add_special_tokens=False) + def get_prompt_schedule(p, prompt, steps): t0 = time.time() temp = [] @@ -69,14 +68,16 @@ def get_prompt_schedule(p, prompt, steps): for s in range(steps): if len(temp) < s + 1 <= chunk[0]: temp.append(chunk[1]) - debug(f'Prompt: schedule={temp} time={time.time()-t0}') + debug(f'Prompt: schedule={temp} time={time.time() - t0}') return temp, len(schedule) > 1 -def encode_prompts(pipe, p, prompts: list, negative_prompts: list, steps: int, step: int = 1, clip_skip: typing.Optional[int] = None): + +def encode_prompts(pipe, p, prompts: list, negative_prompts: list, steps: int, step: int = 1, clip_skip: typing.Optional[int] = None): if 'StableDiffusion' not in pipe.__class__.__name__ and 'DemoFusion': shared.log.warning(f"Prompt parser not supported: {pipe.__class__.__name__}") return None, None, None, None else: + t0 = time.time() positive_schedule, scheduled = get_prompt_schedule(p, prompts[0], steps) negative_schedule, neg_scheduled = get_prompt_schedule(p, negative_prompts[0], steps) p.scheduled_prompt = scheduled or neg_scheduled @@ -87,23 +88,24 @@ def encode_prompts(pipe, p, prompts: list, negative_prompts: list, steps: int, p.negative_pooleds = [] cache = {} - for i in range(len(positive_schedule)): - cached = cache.get(positive_schedule[i]+negative_schedule[i], None) + for i in range(max(len(positive_schedule), len(negative_schedule))): + cached = cache.get(positive_schedule[i % len(positive_schedule)] + negative_schedule[i % len(negative_schedule)], None) if cached is not None: - prompt_embed, positive_pooled, negative_embed, negative_pooled = cached + prompt_embed, positive_pooled, negative_embed, negative_pooled = cached else: - prompt_embed, positive_pooled, negative_embed, negative_pooled = get_weighted_text_embeddings(pipe, - positive_schedule[i], - negative_schedule[i], - clip_skip) + prompt_embed, positive_pooled, negative_embed, negative_pooled = get_weighted_text_embeddings(pipe, + positive_schedule[i % len(positive_schedule)], + negative_schedule[i % len(negative_schedule)], + clip_skip) if prompt_embed is not None: - p.prompt_embeds.append(torch.cat([prompt_embed]*len(prompts), dim=0)) + p.prompt_embeds.append(torch.cat([prompt_embed] * len(prompts), dim=0)) if negative_embed is not None: - p.negative_embeds.append(torch.cat([negative_embed]*len(negative_prompts), dim=0)) + p.negative_embeds.append(torch.cat([negative_embed] * len(negative_prompts), dim=0)) if positive_pooled is not None and shared.sd_model_type == "sdxl": - p.positive_pooleds.append(torch.cat([positive_pooled]*len(prompts), dim=0)) + p.positive_pooleds.append(torch.cat([positive_pooled] * len(prompts), dim=0)) if negative_pooled is not None and shared.sd_model_type == "sdxl": - p.negative_pooleds.append(torch.cat([negative_pooled]*len(negative_prompts), dim=0)) + p.negative_pooleds.append(torch.cat([negative_pooled] * len(negative_prompts), dim=0)) + debug(f"Prompt Parser: Elapsed Time {time.time() - t0}") return @@ -138,9 +140,9 @@ def prepare_embedding_providers(pipe, clip_skip): def pad_to_same_length(pipe, embeds): device = pipe.device if str(pipe.device) != 'meta' else devices.device - try: #SDXL + try: # SDXL empty_embed = pipe.encode_prompt("") - except Exception: #SD1.5 + except Exception: # SD1.5 empty_embed = pipe.encode_prompt("", device, 1, False) empty_batched = torch.cat([empty_embed[0].to(embeds[0].device)] * embeds[0].shape[0]) max_token_count = max([embed.shape[1] for embed in embeds]) @@ -174,7 +176,7 @@ def get_weighted_text_embeddings(pipe, prompt: str = "", neg_prompt: str = "", c prompt_embeds = [] negative_prompt_embeds = [] pooled_prompt_embeds = None - negative_pooled_prompt_embeds = None + negative_pooled_prompt_embeds = None for i in range(len(embedding_providers)): # add BREAK keyword that splits the prompt into multiple fragments text = positives[i] @@ -188,8 +190,8 @@ def get_weighted_text_embeddings(pipe, prompt: str = "", neg_prompt: str = "", c if len(text[:pos]) > 0: embed, ptokens = embedding_providers[i].get_embeddings_for_weighted_prompt_fragments(text_batch=[text[:pos]], fragment_weights_batch=[weights[:pos]], device=device, should_return_tokens=True) provider_embed.append(embed) - text = text[pos+1:] - weights = weights[pos+1:] + text = text[pos + 1:] + weights = weights[pos + 1:] prompt_embeds.append(torch.cat(provider_embed, dim=1)) # negative prompt has no keywords embed, ntokens = embedding_providers[i].get_embeddings_for_weighted_prompt_fragments(text_batch=[negatives[i]], fragment_weights_batch=[negative_weights[i]], device=device, should_return_tokens=True) @@ -198,17 +200,17 @@ def get_weighted_text_embeddings(pipe, prompt: str = "", neg_prompt: str = "", c if prompt_embeds[-1].shape[-1] > 768: if shared.opts.diffusers_pooled == "weighted": pooled_prompt_embeds = prompt_embeds[-1][ - torch.arange(prompt_embeds[-1].shape[0], device=device), - (ptokens.to(dtype=torch.int, device=device) == 49407) - .int() - .argmax(dim=-1), - ] + torch.arange(prompt_embeds[-1].shape[0], device=device), + (ptokens.to(dtype=torch.int, device=device) == 49407) + .int() + .argmax(dim=-1), + ] negative_pooled_prompt_embeds = negative_prompt_embeds[-1][ - torch.arange(negative_prompt_embeds[-1].shape[0], device=device), - (ntokens.to(dtype=torch.int, device=device) == 49407) - .int() - .argmax(dim=-1), - ] + torch.arange(negative_prompt_embeds[-1].shape[0], device=device), + (ntokens.to(dtype=torch.int, device=device) == 49407) + .int() + .argmax(dim=-1), + ] else: pooled_prompt_embeds = embedding_providers[-1].get_pooled_embeddings(texts=[prompt_2], device=device) if prompt_embeds[-1].shape[-1] > 768 else None negative_pooled_prompt_embeds = embedding_providers[-1].get_pooled_embeddings(texts=[neg_prompt_2], device=device) if negative_prompt_embeds[-1].shape[-1] > 768 else None From 2986f5532e7aa7e56fb3020baeda38d3bcac54e2 Mon Sep 17 00:00:00 2001 From: Vladimir Mandic Date: Tue, 19 Dec 2023 08:26:13 -0500 Subject: [PATCH 083/143] image check and save --- modules/images.py | 8 ++++++-- modules/processing.py | 4 ++-- wiki | 2 +- 3 files changed, 9 insertions(+), 5 deletions(-) diff --git a/modules/images.py b/modules/images.py index 2a372faaf..221a184f8 100644 --- a/modules/images.py +++ b/modules/images.py @@ -678,7 +678,7 @@ def save_video_atomic(images, filename, video_type: str = 'none', duration: floa shared.log.info(f'Save video: file="{filename}" frames={len(append) + 1} duration={duration} loop={loop} size={size}') -def save_video(p, images, filename = None, video_type: str = 'none', duration: float = 2.0, loop: bool = False, interpolate: int = 0, scale: float = 1.0, pad: int = 1, change: float = 0.3): +def save_video(p, images, filename = None, video_type: str = 'none', duration: float = 2.0, loop: bool = False, interpolate: int = 0, scale: float = 1.0, pad: int = 1, change: float = 0.3, sync: bool = False): if images is None or len(images) < 2 or video_type is None or video_type.lower() == 'none': return image = images[0] @@ -696,7 +696,11 @@ def save_video(p, images, filename = None, video_type: str = 'none', duration: f if not filename.lower().endswith(video_type.lower()): filename += f'.{video_type.lower()}' filename = namegen.sanitize(filename) - threading.Thread(target=save_video_atomic, args=(images, filename, video_type, duration, loop, interpolate, scale, pad, change)).start() + if not sync: + threading.Thread(target=save_video_atomic, args=(images, filename, video_type, duration, loop, interpolate, scale, pad, change)).start() + else: + save_video_atomic(images, filename, video_type, duration, loop, interpolate, scale, pad, change) + return filename def safe_decode_string(s: bytes): diff --git a/modules/processing.py b/modules/processing.py index 156a33810..6b89e6dd7 100644 --- a/modules/processing.py +++ b/modules/processing.py @@ -379,8 +379,8 @@ def __init__(self, p: StableDiffusionProcessing, images_list, seed=-1, info="", self.subseed_strength = p.subseed_strength self.info = info self.comments = comments - self.width = p.width if hasattr(p, 'width') else self.images[0].width - self.height = p.height if hasattr(p, 'height') else self.images[0].height + self.width = p.width if hasattr(p, 'width') else (self.images[0].width if len(self.images) > 0 else 0) + self.height = p.height if hasattr(p, 'height') else (self.images[0].height if len(self.images) > 0 else 0) self.sampler_name = p.sampler_name self.cfg_scale = p.cfg_scale self.image_cfg_scale = p.image_cfg_scale diff --git a/wiki b/wiki index 0d8ab0f77..0041ca2ab 160000 --- a/wiki +++ b/wiki @@ -1 +1 @@ -Subproject commit 0d8ab0f77d9bb1a1466111ab2816c23bb046c1fa +Subproject commit 0041ca2ab7b57624e5b6448856a1ae88e295636a From 14f03c72f92aceee8e3475382a428f9c500c2e46 Mon Sep 17 00:00:00 2001 From: Disty0 Date: Tue, 19 Dec 2023 20:50:35 +0300 Subject: [PATCH 084/143] IPEX fix SDPA and SD Video --- CHANGELOG.md | 1 + modules/intel/ipex/attention.py | 118 ++++++++++++++++---------------- 2 files changed, 61 insertions(+), 58 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 904e112d9..361cf3855 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -51,6 +51,7 @@ if you get file not found errors, set DISABLE_IPEXRUN=1 and run the webui with --reinstall - built-in *MKL* and *DPCPP* for IPEX, no need to install OneAPI anymore - fix IPEX Optimize not applying with Diffusers backend + - fix garbled outputs with Stable Video Diffusion - disable 32 bit workarounds if the GPU supports 64 bit - add `DISABLE_IPEXRUN` environment variable - compatibility improvements diff --git a/modules/intel/ipex/attention.py b/modules/intel/ipex/attention.py index 083cc6c37..314078b62 100644 --- a/modules/intel/ipex/attention.py +++ b/modules/intel/ipex/attention.py @@ -20,22 +20,21 @@ def torch_bmm_32_bit(input, mat2, *, out=None): if split_slice_size <= 1: split_slice_size = 1 break + split_2_slice_size = input_tokens + if split_slice_size * slice_block_size > 4: + slice_block_size_2 = split_slice_size * mat2_shape / 1024 / 1024 * block_multiply + do_split_2 = True + # Find something divisible with the input_tokens + while (split_2_slice_size * slice_block_size_2) > 4: + split_2_slice_size = split_2_slice_size // 2 + if split_2_slice_size <= 1: + split_2_slice_size = 1 + break + else: + do_split_2 = False else: do_split = False - split_2_slice_size = input_tokens - if split_slice_size * slice_block_size > 4: - slice_block_size2 = split_slice_size * mat2_shape / 1024 / 1024 * block_multiply - do_split_2 = True - # Find something divisible with the input_tokens - while (split_2_slice_size * slice_block_size2) > 4: - split_2_slice_size = split_2_slice_size // 2 - if split_2_slice_size <= 1: - split_2_slice_size = 1 - break - else: - do_split_2 = False - if do_split: hidden_states = torch.zeros(input.shape[0], input.shape[1], mat2.shape[2], device=input.device, dtype=input.dtype) for i in range(batch_size_attention // split_slice_size): @@ -64,42 +63,51 @@ def torch_bmm_32_bit(input, mat2, *, out=None): def scaled_dot_product_attention_32_bit(query, key, value, attn_mask=None, dropout_p=0.0, is_causal=False): # ARC GPUs can't allocate more than 4GB to a single block, Slice it: if len(query.shape) == 3: - batch_size_attention, query_tokens, shape_four = query.shape - shape_one = 1 - no_shape_one = True + batch_size_attention, query_tokens, shape_three = query.shape + shape_four = 1 else: - shape_one, batch_size_attention, query_tokens, shape_four = query.shape - no_shape_one = False + batch_size_attention, query_tokens, shape_three, shape_four = query.shape block_multiply = query.element_size() - slice_block_size = shape_one * query_tokens * shape_four / 1024 / 1024 * block_multiply + slice_block_size = query_tokens * shape_three * shape_four / 1024 / 1024 * block_multiply block_size = batch_size_attention * slice_block_size split_slice_size = batch_size_attention if block_size > 6: do_split = True - # Find something divisible with the shape_one + # Find something divisible with the batch_size_attention while (split_slice_size * slice_block_size) > 4: split_slice_size = split_slice_size // 2 if split_slice_size <= 1: split_slice_size = 1 break + split_2_slice_size = query_tokens + if split_slice_size * slice_block_size > 4: + slice_block_size_2 = split_slice_size * shape_three * shape_four / 1024 / 1024 * block_multiply + do_split_2 = True + # Find something divisible with the query_tokens + while (split_2_slice_size * slice_block_size_2) > 6: + split_2_slice_size = split_2_slice_size // 2 + if split_2_slice_size <= 1: + split_2_slice_size = 1 + break + split_3_slice_size = shape_three + if split_2_slice_size * slice_block_size_2 > 6: + slice_block_size_3 = split_slice_size * split_2_slice_size * shape_four / 1024 / 1024 * block_multiply + do_split_3 = True + # Find something divisible with the shape_three + while (split_3_slice_size * slice_block_size_3) > 4: + split_3_slice_size = split_3_slice_size // 2 + if split_3_slice_size <= 1: + split_3_slice_size = 1 + break + else: + do_split_3 = False + else: + do_split_2 = False else: do_split = False - split_2_slice_size = query_tokens - if split_slice_size * slice_block_size > 6: - slice_block_size2 = shape_one * split_slice_size * shape_four / 1024 / 1024 * block_multiply - do_split_2 = True - # Find something divisible with the batch_size_attention - while (split_2_slice_size * slice_block_size2) > 4: - split_2_slice_size = split_2_slice_size // 2 - if split_2_slice_size <= 1: - split_2_slice_size = 1 - break - else: - do_split_2 = False - if do_split: hidden_states = torch.zeros(query.shape, device=query.device, dtype=query.dtype) for i in range(batch_size_attention // split_slice_size): @@ -109,7 +117,18 @@ def scaled_dot_product_attention_32_bit(query, key, value, attn_mask=None, dropo for i2 in range(query_tokens // split_2_slice_size): # pylint: disable=invalid-name start_idx_2 = i2 * split_2_slice_size end_idx_2 = (i2 + 1) * split_2_slice_size - if no_shape_one: + if do_split_3: + for i3 in range(shape_three // split_3_slice_size): # pylint: disable=invalid-name + start_idx_3 = i3 * split_3_slice_size + end_idx_3 = (i3 + 1) * split_3_slice_size + hidden_states[start_idx:end_idx, start_idx_2:end_idx_2, start_idx_3:end_idx_3] = original_scaled_dot_product_attention( + query[start_idx:end_idx, start_idx_2:end_idx_2, start_idx_3:end_idx_3], + key[start_idx:end_idx, start_idx_2:end_idx_2, start_idx_3:end_idx_3], + value[start_idx:end_idx, start_idx_2:end_idx_2, start_idx_3:end_idx_3], + attn_mask=attn_mask[start_idx:end_idx, start_idx_2:end_idx_2, start_idx_3:end_idx_3] if attn_mask is not None else attn_mask, + dropout_p=dropout_p, is_causal=is_causal + ) + else: hidden_states[start_idx:end_idx, start_idx_2:end_idx_2] = original_scaled_dot_product_attention( query[start_idx:end_idx, start_idx_2:end_idx_2], key[start_idx:end_idx, start_idx_2:end_idx_2], @@ -117,31 +136,14 @@ def scaled_dot_product_attention_32_bit(query, key, value, attn_mask=None, dropo attn_mask=attn_mask[start_idx:end_idx, start_idx_2:end_idx_2] if attn_mask is not None else attn_mask, dropout_p=dropout_p, is_causal=is_causal ) - else: - hidden_states[:, start_idx:end_idx, start_idx_2:end_idx_2] = original_scaled_dot_product_attention( - query[:, start_idx:end_idx, start_idx_2:end_idx_2], - key[:, start_idx:end_idx, start_idx_2:end_idx_2], - value[:, start_idx:end_idx, start_idx_2:end_idx_2], - attn_mask=attn_mask[:, start_idx:end_idx, start_idx_2:end_idx_2] if attn_mask is not None else attn_mask, - dropout_p=dropout_p, is_causal=is_causal - ) else: - if no_shape_one: - hidden_states[start_idx:end_idx] = original_scaled_dot_product_attention( - query[start_idx:end_idx], - key[start_idx:end_idx], - value[start_idx:end_idx], - attn_mask=attn_mask[start_idx:end_idx] if attn_mask is not None else attn_mask, - dropout_p=dropout_p, is_causal=is_causal - ) - else: - hidden_states[:, start_idx:end_idx] = original_scaled_dot_product_attention( - query[:, start_idx:end_idx], - key[:, start_idx:end_idx], - value[:, start_idx:end_idx], - attn_mask=attn_mask[:, start_idx:end_idx] if attn_mask is not None else attn_mask, - dropout_p=dropout_p, is_causal=is_causal - ) + hidden_states[start_idx:end_idx] = original_scaled_dot_product_attention( + query[start_idx:end_idx], + key[start_idx:end_idx], + value[start_idx:end_idx], + attn_mask=attn_mask[start_idx:end_idx] if attn_mask is not None else attn_mask, + dropout_p=dropout_p, is_causal=is_causal + ) else: return original_scaled_dot_product_attention( query, key, value, attn_mask=attn_mask, dropout_p=dropout_p, is_causal=is_causal From 9c8be9837bbbc936ccc98c746919e3fb9fc951a9 Mon Sep 17 00:00:00 2001 From: Vladimir Mandic Date: Tue, 19 Dec 2023 14:02:48 -0500 Subject: [PATCH 085/143] minor updates --- CHANGELOG.md | 1 + modules/processing_diffusers.py | 7 +++++-- modules/shared.py | 2 +- wiki | 2 +- 4 files changed, 8 insertions(+), 4 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 361cf3855..0742bf7ab 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -50,6 +50,7 @@ - update to **Torch 2.1** if you get file not found errors, set DISABLE_IPEXRUN=1 and run the webui with --reinstall - built-in *MKL* and *DPCPP* for IPEX, no need to install OneAPI anymore + - **StableVideoDiffusion** is now supported with IPEX - fix IPEX Optimize not applying with Diffusers backend - fix garbled outputs with Stable Video Diffusion - disable 32 bit workarounds if the GPU supports 64 bit diff --git a/modules/processing_diffusers.py b/modules/processing_diffusers.py index 3395d370f..674401e16 100644 --- a/modules/processing_diffusers.py +++ b/modules/processing_diffusers.py @@ -89,7 +89,7 @@ def diffusers_callback(_pipe, step: int, timestep: int, kwargs: dict): if kwargs.get('latents', None) is None: return kwargs kwargs = correction_callback(p, timestep, kwargs) - if p.scheduled_prompt: + if p.scheduled_prompt and hasattr(kwargs, 'prompt_embeds') and hasattr(kwargs, 'negative_prompt_embeds'): try: i = (step + 1) % len(p.prompt_embeds) kwargs["prompt_embeds"] = p.prompt_embeds[i][0:1].repeat(1, kwargs["prompt_embeds"].shape[0], 1).view( @@ -346,7 +346,10 @@ def set_pipeline_args(model, prompts: list, negative_prompts: list, prompts_2: t args['callback'] = diffusers_callback_legacy elif 'callback_on_step_end_tensor_inputs' in possible: args['callback_on_step_end'] = diffusers_callback - args['callback_on_step_end_tensor_inputs'] = ['latents', 'prompt_embeds', 'negative_prompt_embeds'] + if 'prompt_embeds' in possible and 'negative_prompt_embeds' in possible: + args['callback_on_step_end_tensor_inputs'] = ['latents', 'prompt_embeds', 'negative_prompt_embeds'] + else: + args['callback_on_step_end_tensor_inputs'] = ['latents'] for arg in kwargs: if arg in possible: # add kwargs args[arg] = kwargs[arg] diff --git a/modules/shared.py b/modules/shared.py index c6facfd21..8d887e650 100644 --- a/modules/shared.py +++ b/modules/shared.py @@ -153,7 +153,7 @@ def refresh_vaes(): def refresh_upscalers(): - import modules.modelloader + import modules.modelloader # pylint: disable=W0621 modules.modelloader.load_upscalers() diff --git a/wiki b/wiki index 0041ca2ab..d33eb1c02 160000 --- a/wiki +++ b/wiki @@ -1 +1 @@ -Subproject commit 0041ca2ab7b57624e5b6448856a1ae88e295636a +Subproject commit d33eb1c0220c3af5243ee72e32c44c6d9eba1101 From 1ec2b0e4c6a8e723f4957576bf2450c17706cef0 Mon Sep 17 00:00:00 2001 From: Vladimir Mandic Date: Tue, 19 Dec 2023 14:04:05 -0500 Subject: [PATCH 086/143] update changelog --- CHANGELOG.md | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 0742bf7ab..81445c602 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -48,12 +48,11 @@ indexes all networks on first access instead of server startup - **IPEX**, thanks @disty0 - update to **Torch 2.1** - if you get file not found errors, set DISABLE_IPEXRUN=1 and run the webui with --reinstall + if you get file not found errors, set `DISABLE_IPEXRUN=1` and run the webui with `--reinstall` - built-in *MKL* and *DPCPP* for IPEX, no need to install OneAPI anymore - **StableVideoDiffusion** is now supported with IPEX - fix IPEX Optimize not applying with Diffusers backend - - fix garbled outputs with Stable Video Diffusion - - disable 32 bit workarounds if the GPU supports 64 bit + - disable 32bit workarounds if the GPU supports 64bit - add `DISABLE_IPEXRUN` environment variable - compatibility improvements - **OpenVINO**, thanks @disty0 From 1eacfc5129b1a887a4f0d14bc0493db6eff6755c Mon Sep 17 00:00:00 2001 From: Disty0 Date: Tue, 19 Dec 2023 22:34:43 +0300 Subject: [PATCH 087/143] IPEX fixes --- CHANGELOG.md | 2 +- modules/intel/ipex/attention.py | 2 +- modules/intel/ipex/hijacks.py | 4 ++++ modules/processing.py | 4 ++-- 4 files changed, 8 insertions(+), 4 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 81445c602..c8d278532 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -53,7 +53,7 @@ - **StableVideoDiffusion** is now supported with IPEX - fix IPEX Optimize not applying with Diffusers backend - disable 32bit workarounds if the GPU supports 64bit - - add `DISABLE_IPEXRUN` environment variable + - add `DISABLE_IPEXRUN` and `DISABLE_IPEX_1024_WA` environment variables - compatibility improvements - **OpenVINO**, thanks @disty0 - add *Directory for OpenVINO cache* option to *System Paths* diff --git a/modules/intel/ipex/attention.py b/modules/intel/ipex/attention.py index 314078b62..ff7492a10 100644 --- a/modules/intel/ipex/attention.py +++ b/modules/intel/ipex/attention.py @@ -92,7 +92,7 @@ def scaled_dot_product_attention_32_bit(query, key, value, attn_mask=None, dropo split_2_slice_size = 1 break split_3_slice_size = shape_three - if split_2_slice_size * slice_block_size_2 > 6: + if split_2_slice_size * slice_block_size_2 > 4: slice_block_size_3 = split_slice_size * split_2_slice_size * shape_four / 1024 / 1024 * block_multiply do_split_3 = True # Find something divisible with the shape_three diff --git a/modules/intel/ipex/hijacks.py b/modules/intel/ipex/hijacks.py index 554fe320b..4573b7f7f 100644 --- a/modules/intel/ipex/hijacks.py +++ b/modules/intel/ipex/hijacks.py @@ -161,6 +161,10 @@ def ipex_hijacks(): CondFunc('torch.Generator', lambda orig_func, device=None: torch.xpu.Generator(return_xpu(device)), lambda orig_func, device=None: device is not None and device != torch.device("cpu") and device != "cpu") + else: + CondFunc('torch.Generator', + lambda orig_func, device=None: orig_func(return_xpu(device)), + lambda orig_func, device=None: check_device(device)) # TiledVAE and ControlNet: CondFunc('torch.batch_norm', diff --git a/modules/processing.py b/modules/processing.py index 6b89e6dd7..5df865865 100644 --- a/modules/processing.py +++ b/modules/processing.py @@ -148,7 +148,7 @@ def __init__(self, sd_model=None, outpath_samples=None, outpath_grids=None, prom self.scale_by: float = scale_by self.image_cfg_scale = image_cfg_scale self.diffusers_guidance_rescale = diffusers_guidance_rescale - if devices.backend == "ipex" and width == 1024 and height == 1024: + if devices.backend == "ipex" and width == 1024 and height == 1024 and os.environ.get('DISABLE_IPEX_1024_WA', None) is None: width = 1080 height = 1080 self.width: int = width @@ -1037,7 +1037,7 @@ class StableDiffusionProcessingTxt2Img(StableDiffusionProcessing): def __init__(self, enable_hr: bool = False, denoising_strength: float = 0.75, firstphase_width: int = 0, firstphase_height: int = 0, hr_scale: float = 2.0, hr_force: bool = False, hr_upscaler: str = None, hr_second_pass_steps: int = 0, hr_resize_x: int = 0, hr_resize_y: int = 0, refiner_steps: int = 5, refiner_start: float = 0, refiner_prompt: str = '', refiner_negative: str = '', **kwargs): super().__init__(**kwargs) - if devices.backend == "ipex": + if devices.backend == "ipex" and os.environ.get('DISABLE_IPEX_1024_WA', None) is None: width_curse = bool(hr_resize_x == 1024 and self.height * (hr_resize_x / self.width) == 1024) height_curse = bool(hr_resize_y == 1024 and self.width * (hr_resize_y / self.height) == 1024) if (width_curse != height_curse) or (height_curse and width_curse): From 9f68fd7a9a2b5c5d75dc231527e6cfcf97e24971 Mon Sep 17 00:00:00 2001 From: Vladimir Mandic Date: Tue, 19 Dec 2023 18:41:34 -0500 Subject: [PATCH 088/143] update changelog --- CHANGELOG.md | 12 ++++++++++-- wiki | 2 +- 2 files changed, 11 insertions(+), 3 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 15d942d7f..7b6f2723a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,9 +1,15 @@ # Change Log for SD.Next -## Update for 2023-12-17 +## Update for 2023-12-19 *Note*: based on `diffusers==0.25.0.dev0` +- **Control** + - native implementation of **ControlNet**, **ControlNet XS** and **T2I Adapters** + - supports all variations of **SD15** and **SD-XL** models + - supports *text*, *image*, *batch* and *video* processing + - for details see Wiki documentation: + - **Diffusers** - **AnimateDiff** - can now be used with *second pass* - enhance, upscale and hires your videos! @@ -28,7 +34,8 @@ but at the moment this is pretty much experimental-only - **Prompt scheduling** now implemented for Diffusers backend, thanks @AI-Casanova - **Custom pipelines** contribute by adding your own custom pipelines! - - for details, see fully documented example: + - for details, see fully documented example: + - **Schedulers** - add timesteps range, changing it will make scheduler to be over-complete or under-complete - add rescale betas with zero SNR option (applicable to Euler and DDIM, allows for higher dynamic range) @@ -84,6 +91,7 @@ - updated `cli/simple-txt2img.py` and `cli/simple-img2img.py` scripts - save `params.txt` regardless of image save status - update built-in log monitor in ui, thanks @midcoastal + - major CHANGELOG cleanup, thanks @JetVarimax ## Update for 2023-12-04 diff --git a/wiki b/wiki index d33eb1c02..a9dba4cfa 160000 --- a/wiki +++ b/wiki @@ -1 +1 @@ -Subproject commit d33eb1c0220c3af5243ee72e32c44c6d9eba1101 +Subproject commit a9dba4cfa90889bb9fa4c0f12b6e76aba19574c7 From 8ceeee8b8769a52f4e179518badbb3fe8503126f Mon Sep 17 00:00:00 2001 From: Vladimir Mandic Date: Wed, 20 Dec 2023 10:17:39 -0500 Subject: [PATCH 089/143] add trace logging --- CHANGELOG.md | 11 ++++++++--- installer.py | 6 ++++++ modules/errors.py | 3 ++- modules/generation_parameters_copypaste.py | 2 +- modules/images.py | 2 +- modules/img2img.py | 2 +- modules/paths.py | 2 +- modules/processing.py | 2 +- modules/processing_correction.py | 2 +- modules/prompt_parser.py | 2 +- modules/prompt_parser_diffusers.py | 6 +++--- modules/sd_samplers.py | 3 +++ modules/sd_samplers_diffusers.py | 15 ++++++++++++++- modules/txt2img.py | 2 +- modules/ui.py | 6 ++++-- modules/ui_extra_networks.py | 2 +- modules/ui_tempdir.py | 2 +- webui.py | 2 +- wiki | 2 +- 19 files changed, 52 insertions(+), 22 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 7b6f2723a..8cd934a21 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,6 @@ # Change Log for SD.Next -## Update for 2023-12-19 +## Update for 2023-12-20 *Note*: based on `diffusers==0.25.0.dev0` @@ -38,7 +38,7 @@ - **Schedulers** - add timesteps range, changing it will make scheduler to be over-complete or under-complete - - add rescale betas with zero SNR option (applicable to Euler and DDIM, allows for higher dynamic range) + - add rescale betas with zero SNR option (applicable to Euler, Euler a and DDIM, allows for higher dynamic range) - **General** - **Process** create videos from batch or folder processing supports *GIF*, *PNG* and *MP4* with full interpolation, scene change detection, etc. @@ -73,6 +73,12 @@ - **HDR controls** - batch-aware for enhancement of multiple images or video frames - available in image tab + - **Logging** + - additional *TRACE* logging enabled via specific env variables + see for details + - improved profiling + use with `--debug --profile` + - log output file sizes - **Other** - **API** several minor but breaking changes to API behavior to better align response fields, thanks @Trojaner - **Inpaint** add option `apply_overlay` to control if inpaint result should be applied as overlay or as-is @@ -86,7 +92,6 @@ - add hdr settings to metadata - improve handling of long filenames and filenames during batch processing - do not set preview samples when using via api - - log output file sizes - avoid unnecessary resizes in img2img and inpaint - updated `cli/simple-txt2img.py` and `cli/simple-img2img.py` scripts - save `params.txt` regardless of image save status diff --git a/installer.py b/installer.py index 8e6fd7ba1..5ba92cee5 100644 --- a/installer.py +++ b/installer.py @@ -67,6 +67,7 @@ def emit(self, record): def get(self): return self.buffer + from functools import partial, partialmethod from logging.handlers import RotatingFileHandler from rich.theme import Theme from rich.logging import RichHandler @@ -78,6 +79,11 @@ def get(self): global log_file # pylint: disable=global-statement log_file = args.log + logging.TRACE = 25 + logging.addLevelName(logging.TRACE, 'TRACE') + logging.Logger.trace = partialmethod(logging.Logger.log, logging.TRACE) + logging.trace = partial(logging.log, logging.TRACE) + level = logging.DEBUG if args.debug else logging.INFO log.setLevel(logging.DEBUG) # log to file is always at level debug for facility `sd` console = Console(log_time=True, log_time_format='%H:%M:%S-%f', theme=Theme({ diff --git a/modules/errors.py b/modules/errors.py index 0f770856f..122628bff 100644 --- a/modules/errors.py +++ b/modules/errors.py @@ -4,9 +4,10 @@ from rich.theme import Theme from rich.pretty import install as pretty_install from rich.traceback import install as traceback_install -from installer import log as installer_log +from installer import log as installer_log, setup_logging +setup_logging() log = installer_log console = Console(log_time=True, log_time_format='%H:%M:%S-%f', theme=Theme({ "traceback.border": "black", diff --git a/modules/generation_parameters_copypaste.py b/modules/generation_parameters_copypaste.py index d26317fc9..63c370e10 100644 --- a/modules/generation_parameters_copypaste.py +++ b/modules/generation_parameters_copypaste.py @@ -16,7 +16,7 @@ type_of_gr_update = type(gr.update()) paste_fields = {} registered_param_bindings = [] -debug = shared.log.info if os.environ.get('SD_PASTE_DEBUG', None) is not None else lambda *args, **kwargs: None +debug = shared.log.trace if os.environ.get('SD_PASTE_DEBUG', None) is not None else lambda *args, **kwargs: None class ParamBinding: diff --git a/modules/images.py b/modules/images.py index 221a184f8..22fe42148 100644 --- a/modules/images.py +++ b/modules/images.py @@ -19,7 +19,7 @@ from modules import sd_samplers, shared, script_callbacks, errors, paths -debug = errors.log.info if os.environ.get('SD_PATH_DEBUG', None) is not None else lambda *args, **kwargs: None +debug = errors.log.trace if os.environ.get('SD_PATH_DEBUG', None) is not None else lambda *args, **kwargs: None try: from pi_heif import register_heif_opener register_heif_opener() diff --git a/modules/img2img.py b/modules/img2img.py index 20ef6fe6d..04e17fc4c 100644 --- a/modules/img2img.py +++ b/modules/img2img.py @@ -9,7 +9,7 @@ from modules.memstats import memory_stats -debug = shared.log.debug if os.environ.get('SD_PROCESS_DEBUG', None) is not None else lambda *args, **kwargs: None +debug = shared.log.trace if os.environ.get('SD_PROCESS_DEBUG', None) is not None else lambda *args, **kwargs: None def process_batch(p, input_files, input_dir, output_dir, inpaint_mask_dir, args): diff --git a/modules/paths.py b/modules/paths.py index d7a9e8691..1d2869a7d 100644 --- a/modules/paths.py +++ b/modules/paths.py @@ -32,7 +32,7 @@ sd_default_config = os.path.join(sd_configs_path, "v1-inference.yaml") sd_model_file = cli.ckpt or os.path.join(script_path, 'model.ckpt') # not used default_sd_model_file = sd_model_file # not used -debug = log.info if os.environ.get('SD_PATH_DEBUG', None) is not None else lambda *args, **kwargs: None +debug = log.trace if os.environ.get('SD_PATH_DEBUG', None) is not None else lambda *args, **kwargs: None paths = {} if os.environ.get('SD_PATH_DEBUG', None) is not None: diff --git a/modules/processing.py b/modules/processing.py index 5df865865..761aa7f3b 100644 --- a/modules/processing.py +++ b/modules/processing.py @@ -44,7 +44,7 @@ opt_C = 4 opt_f = 8 -debug = shared.log.debug if os.environ.get('SD_PROCESS_DEBUG', None) is not None else lambda *args, **kwargs: None +debug = shared.log.trace if os.environ.get('SD_PROCESS_DEBUG', None) is not None else lambda *args, **kwargs: None def setup_color_correction(image): diff --git a/modules/processing_correction.py b/modules/processing_correction.py index a421da052..8f285bcff 100644 --- a/modules/processing_correction.py +++ b/modules/processing_correction.py @@ -8,7 +8,7 @@ from modules import shared -debug = shared.log.info if os.environ.get('SD_HDR_DEBUG', None) is not None else lambda *args, **kwargs: None +debug = shared.log.trace if os.environ.get('SD_HDR_DEBUG', None) is not None else lambda *args, **kwargs: None def soft_clamp_tensor(tensor, threshold=0.8, boundary=4): diff --git a/modules/prompt_parser.py b/modules/prompt_parser.py index 5a950d443..0e36707b1 100644 --- a/modules/prompt_parser.py +++ b/modules/prompt_parser.py @@ -70,7 +70,7 @@ debug_output = os.environ.get('SD_PROMPT_DEBUG', None) -debug = log.info if debug_output is not None else lambda *args, **kwargs: None +debug = log.trace if debug_output is not None else lambda *args, **kwargs: None def get_learned_conditioning_prompt_schedules(prompts, steps): diff --git a/modules/prompt_parser_diffusers.py b/modules/prompt_parser_diffusers.py index 7dcc04336..72d8ae3cc 100644 --- a/modules/prompt_parser_diffusers.py +++ b/modules/prompt_parser_diffusers.py @@ -6,7 +6,7 @@ from compel.embeddings_provider import BaseTextualInversionManager, EmbeddingsProvider from modules import shared, prompt_parser, devices -debug = shared.log.info if os.environ.get('SD_PROMPT_DEBUG', None) is not None else lambda *args, **kwargs: None +debug = shared.log.trace if os.environ.get('SD_PROMPT_DEBUG', None) is not None else lambda *args, **kwargs: None CLIP_SKIP_MAPPING = { None: ReturnedEmbeddingsType.LAST_HIDDEN_STATES_NORMALIZED, @@ -58,7 +58,7 @@ def expand_textual_inversion_token_ids_if_necessary(self, token_ids: typing.List return self.pipe.tokenizer.encode(prompt, add_special_tokens=False) -def get_prompt_schedule(p, prompt, steps): +def get_prompt_schedule(p, prompt, steps): # pylint: disable=unused-argument t0 = time.time() temp = [] schedule = prompt_parser.get_learned_conditioning_prompt_schedules([prompt], steps)[0] @@ -72,7 +72,7 @@ def get_prompt_schedule(p, prompt, steps): return temp, len(schedule) > 1 -def encode_prompts(pipe, p, prompts: list, negative_prompts: list, steps: int, step: int = 1, clip_skip: typing.Optional[int] = None): +def encode_prompts(pipe, p, prompts: list, negative_prompts: list, steps: int, step: int = 1, clip_skip: typing.Optional[int] = None): # pylint: disable=unused-argument if 'StableDiffusion' not in pipe.__class__.__name__ and 'DemoFusion': shared.log.warning(f"Prompt parser not supported: {pipe.__class__.__name__}") return None, None, None, None diff --git a/modules/sd_samplers.py b/modules/sd_samplers.py index 62f0c8a73..c4c40c30a 100644 --- a/modules/sd_samplers.py +++ b/modules/sd_samplers.py @@ -1,7 +1,9 @@ +import os from modules import sd_samplers_compvis, sd_samplers_kdiffusion, sd_samplers_diffusers, shared from modules.sd_samplers_common import samples_to_image_grid, sample_to_image # pylint: disable=unused-import +debug = shared.log.trace if os.environ.get('SD_SAMPLER_DEBUG', None) is not None else lambda *args, **kwargs: None all_samplers = [] all_samplers = [] all_samplers_map = {} @@ -9,6 +11,7 @@ samplers_for_img2img = all_samplers samplers_map = {} + def list_samplers(backend_name = shared.backend): global all_samplers # pylint: disable=global-statement global all_samplers_map # pylint: disable=global-statement diff --git a/modules/sd_samplers_diffusers.py b/modules/sd_samplers_diffusers.py index dc0ccc03c..024e88524 100644 --- a/modules/sd_samplers_diffusers.py +++ b/modules/sd_samplers_diffusers.py @@ -1,6 +1,11 @@ +import os +import inspect from modules import shared from modules import sd_samplers_common + +debug = shared.log.trace if os.environ.get('SD_SAMPLER_DEBUG', None) is not None else lambda *args, **kwargs: None + try: from diffusers import ( DDIMScheduler, @@ -33,7 +38,7 @@ 'DPM++ 1S': { 'solver_order': 2, 'thresholding': False, 'sample_max_value': 1.0, 'algorithm_type': "dpmsolver++", 'solver_type': "midpoint", 'lower_order_final': True, 'use_karras_sigmas': False }, 'DPM++ 2M': { 'thresholding': False, 'sample_max_value': 1.0, 'algorithm_type': "dpmsolver++", 'solver_type': "midpoint", 'lower_order_final': True, 'use_karras_sigmas': False }, 'DPM SDE': { 'use_karras_sigmas': False }, - 'Euler a': { }, + 'Euler a': { 'rescale_betas_zero_snr': False }, 'Euler': { 'interpolation_type': "linear", 'use_karras_sigmas': False, 'rescale_betas_zero_snr': False }, 'Heun': { 'use_karras_sigmas': False }, 'KDPM2': { 'steps_offset': 0 }, @@ -116,5 +121,13 @@ def __init__(self, name, constructor, model, **kwargs): self.config['algorithm_type'] = shared.opts.schedulers_dpm_solver if name == 'DEIS': self.config['algorithm_type'] = 'deis' + # validate all config params + signature = inspect.signature(constructor, follow_wrapped=True) + possible = signature.parameters.keys() + debug(f'Sampler: sampler="{name}" config={self.config} signature={possible}') + for key in self.config.copy().keys(): + if key not in possible: + shared.log.warning(f'Sampler: sampler="{name}" config={self.config} invalid={key}') + del self.config[key] self.sampler = constructor(**self.config) self.sampler.name = name diff --git a/modules/txt2img.py b/modules/txt2img.py index 0b7a1aba6..cbe55a4ea 100644 --- a/modules/txt2img.py +++ b/modules/txt2img.py @@ -5,7 +5,7 @@ from modules.ui import plaintext_to_html -debug = shared.log.debug if os.environ.get('SD_PROCESS_DEBUG', None) is not None else lambda *args, **kwargs: None +debug = shared.log.trace if os.environ.get('SD_PROCESS_DEBUG', None) is not None else lambda *args, **kwargs: None def txt2img(id_task, diff --git a/modules/ui.py b/modules/ui.py index 65b70f3fc..3a7f4f52e 100644 --- a/modules/ui.py +++ b/modules/ui.py @@ -421,6 +421,7 @@ def set_sampler_diffuser_options(sampler_options): opts.data['schedulers_use_karras'] = 'karras' in sampler_options opts.data['schedulers_use_thresholding'] = 'dynamic thresholding' in sampler_options opts.data['schedulers_use_loworder'] = 'low order' in sampler_options + opts.data['schedulers_rescale_betas'] = 'rescale beta' in sampler_options opts.save(modules.shared.config_filename, silent=True) with FormRow(elem_classes=['flex-break']): @@ -440,11 +441,12 @@ def set_sampler_diffuser_options(sampler_options): sampler_algo.change(fn=set_sampler_original_options, inputs=[sampler_options, sampler_algo], outputs=[]) else: with FormRow(elem_classes=['flex-break']): - choices = ['karras', 'dynamic thresholding', 'low order'] + choices = ['karras', 'dynamic threshold', 'low order', 'rescale beta'] values = [] values += ['karras'] if opts.data.get('schedulers_use_karras', True) else [] - values += ['dynamic thresholding'] if opts.data.get('schedulers_use_thresholding', False) else [] + values += ['dynamic threshold'] if opts.data.get('schedulers_use_thresholding', False) else [] values += ['low order'] if opts.data.get('schedulers_use_loworder', True) else [] + values += ['rescale beta'] if opts.data.get('schedulers_rescale_betas', False) else [] sampler_options = gr.CheckboxGroup(label='Sampler options', choices=choices, value=values, type='value') sampler_options.change(fn=set_sampler_diffuser_options, inputs=[sampler_options], outputs=[]) return steps, sampler_index diff --git a/modules/ui_extra_networks.py b/modules/ui_extra_networks.py index 64a8e5342..9a8e6d308 100644 --- a/modules/ui_extra_networks.py +++ b/modules/ui_extra_networks.py @@ -24,7 +24,7 @@ dir_cache = {} # key=path, value=(mtime, listdir(path)) refresh_time = 0 extra_pages = shared.extra_networks -debug = shared.log.info if os.environ.get('SD_EN_DEBUG', None) is not None else lambda *args, **kwargs: None +debug = shared.log.trace if os.environ.get('SD_EN_DEBUG', None) is not None else lambda *args, **kwargs: None card_full = '''
diff --git a/modules/ui_tempdir.py b/modules/ui_tempdir.py index c21d321cd..21730cf95 100644 --- a/modules/ui_tempdir.py +++ b/modules/ui_tempdir.py @@ -8,7 +8,7 @@ Savedfile = namedtuple("Savedfile", ["name"]) -debug = errors.log.info if os.environ.get('SD_PATH_DEBUG', None) is not None else lambda *args, **kwargs: None +debug = errors.log.trace if os.environ.get('SD_PATH_DEBUG', None) is not None else lambda *args, **kwargs: None def register_tmp_file(gradio, filename): diff --git a/webui.py b/webui.py index 350d0d1c6..6dcd965dd 100644 --- a/webui.py +++ b/webui.py @@ -316,7 +316,7 @@ def webui(restart=False): for k, v in modules.script_callbacks.callback_map.items(): shared.log.debug(f'Registered callbacks: {k}={len(v)} {[c.script for c in v]}') log.info(f"Startup time: {timer.startup.summary()}") - debug = log.info if os.environ.get('SD_SCRIPT_DEBUG', None) is not None else lambda *args, **kwargs: None + debug = log.trace if os.environ.get('SD_SCRIPT_DEBUG', None) is not None else lambda *args, **kwargs: None debug('Loaded scripts:') for m in modules.scripts.scripts_data: debug(f' {m}') diff --git a/wiki b/wiki index a9dba4cfa..6b1f994d4 160000 --- a/wiki +++ b/wiki @@ -1 +1 @@ -Subproject commit a9dba4cfa90889bb9fa4c0f12b6e76aba19574c7 +Subproject commit 6b1f994d4b19d08b6c39c1df2b84459573b3d04f From 567e637b9c6355d84cf768de060623fec57a79f7 Mon Sep 17 00:00:00 2001 From: Vladimir Mandic Date: Wed, 20 Dec 2023 11:33:16 -0500 Subject: [PATCH 090/143] add blip diffusion --- CHANGELOG.md | 4 ++ html/reference.json | 5 ++ .../Reference/salesforce--blipdiffusion.jpg | Bin 0 -> 47620 bytes modules/ui.py | 2 +- scripts/blipdiffusion.py | 43 ++++++++++++++++++ scripts/stablevideodiffusion.py | 1 + 6 files changed, 54 insertions(+), 1 deletion(-) create mode 100644 models/Reference/salesforce--blipdiffusion.jpg create mode 100644 scripts/blipdiffusion.py diff --git a/CHANGELOG.md b/CHANGELOG.md index 8cd934a21..c3453f8d0 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -27,6 +27,10 @@ - select from *networks -> reference* - [Playground v1](https://huggingface.co/playgroundai/playground-v1), [Playground v2 256](https://huggingface.co/playgroundai/playground-v2-256px-base), [Playground v2 512](https://huggingface.co/playgroundai/playground-v2-512px-base), [Playground v2 1024](https://huggingface.co/playgroundai/playground-v2-1024px-aesthetic) model support - simply select from *networks -> reference* and use as usual + - [BLIP-Diffusion](https://dxli94.github.io/BLIP-Diffusion-website/) + - img2img model that can replace subjects in images using prompt keywords + - download and load by selecting from *networks -> reference -> blip diffusion* + - in image tab, select `blip diffusion` script - [DemoFusion](https://github.com/PRIS-CV/DemoFusion) run your SDXL generations at any resolution! - in **Text** tab select *script* -> *demofusion* - *note*: GPU VRAM limits do not automatically go away so be careful when using it with large resolutions diff --git a/html/reference.json b/html/reference.json index 4043fe2da..5c8d26378 100644 --- a/html/reference.json +++ b/html/reference.json @@ -113,5 +113,10 @@ "path": "thu-ml/unidiffuser-v1", "desc": "UniDiffuser is a unified diffusion framework to fit all distributions relevant to a set of multi-modal data in one transformer. UniDiffuser is able to perform image, text, text-to-image, image-to-text, and image-text pair generation by setting proper timesteps without additional overhead.\nSpecifically, UniDiffuser employs a variation of transformer, called U-ViT, which parameterizes the joint noise prediction network. Other components perform as encoders and decoders of different modalities, including a pretrained image autoencoder from Stable Diffusion, a pretrained image ViT-B/32 CLIP encoder, a pretrained text ViT-L CLIP encoder, and a GPT-2 text decoder finetuned by ourselves.", "preview": "thu-ml--unidiffuser-v1.jpg" + }, + "SalesForce BLIP-Diffusion": { + "path": "salesforce/blipdiffusion", + "desc": "BLIP-Diffusion, a new subject-driven image generation model that supports multimodal control which consumes inputs of subject images and text prompts. Unlike other subject-driven generation models, BLIP-Diffusion introduces a new multimodal encoder which is pre-trained to provide subject representation.", + "preview": "salesforce--blipdiffusion.jpg" } } \ No newline at end of file diff --git a/models/Reference/salesforce--blipdiffusion.jpg b/models/Reference/salesforce--blipdiffusion.jpg new file mode 100644 index 0000000000000000000000000000000000000000..79155aba99e3d7127527832628666e82e1f93e8d GIT binary patch literal 47620 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
rw%+DUpVPf`aow+$2IgFjOJo7< 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�(%4IVELY3*$>Wo8+Q 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 0: + p.task_args['reference_image'] = p.init_images[0] + p.task_args['prompt'] = [p.prompt] + p.task_args['neg_prompt'] = p.negative_prompt + p.task_args['prompt_strength'] = prompt_strength + p.task_args['source_subject_category'] = [source_subject] + p.task_args['target_subject_category'] = [target_subject] + p.task_args['output_type'] = 'pil' + shared.log.debug(f'BLIP Diffusion: args={p.task_args}') + shared.sd_model = sd_models.set_diffuser_pipe(shared.sd_model, sd_models.DiffusersTaskType.IMAGE_2_IMAGE) + processed = processing.process_images(p) + return processed + else: + shared.log.error(f'{title}: no init_images') + return None diff --git a/scripts/stablevideodiffusion.py b/scripts/stablevideodiffusion.py index 078c0a054..3e20c4373 100644 --- a/scripts/stablevideodiffusion.py +++ b/scripts/stablevideodiffusion.py @@ -47,6 +47,7 @@ def video_type_change(video_type): def run(self, p: processing.StableDiffusionProcessing, num_frames, override_resolution, min_guidance_scale, max_guidance_scale, decode_chunk_size, motion_bucket_id, noise_aug_strength, video_type, duration, gif_loop, mp4_pad, mp4_interpolate): # pylint: disable=arguments-differ, unused-argument c = shared.sd_model.__class__.__name__ if shared.sd_model is not None else '' if c != 'StableVideoDiffusionPipeline' and c != 'TextToVideoSDPipeline': + shared.log.error(f'StableVideo: model selected={c} required=StableVideoDiffusion') return None if hasattr(p, 'init_images') and len(p.init_images) > 0: if override_resolution: From 711b744d30bd47e29eefad317aa9fe13c6945212 Mon Sep 17 00:00:00 2001 From: Disty0 Date: Wed, 20 Dec 2023 22:34:57 +0300 Subject: [PATCH 091/143] IPEX fix slicing rate --- modules/intel/ipex/attention.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/modules/intel/ipex/attention.py b/modules/intel/ipex/attention.py index ff7492a10..30a889b11 100644 --- a/modules/intel/ipex/attention.py +++ b/modules/intel/ipex/attention.py @@ -86,7 +86,7 @@ def scaled_dot_product_attention_32_bit(query, key, value, attn_mask=None, dropo slice_block_size_2 = split_slice_size * shape_three * shape_four / 1024 / 1024 * block_multiply do_split_2 = True # Find something divisible with the query_tokens - while (split_2_slice_size * slice_block_size_2) > 6: + while (split_2_slice_size * slice_block_size_2) > 4: split_2_slice_size = split_2_slice_size // 2 if split_2_slice_size <= 1: split_2_slice_size = 1 From 6e04bac7de485275393094083c63c5c0bebe87cb Mon Sep 17 00:00:00 2001 From: Vladimir Mandic Date: Wed, 20 Dec 2023 15:27:08 -0500 Subject: [PATCH 092/143] merge control --- CHANGELOG.md | 2 + modules/control/adapters.py | 142 ++ modules/control/controlnets.py | 166 ++ modules/control/controlnetsxs.py | 149 ++ modules/control/proc/__init__.py | 0 modules/control/proc/canny.py | 35 + modules/control/proc/dwpose/__init__.py | 90 + .../proc/dwpose/config/dwpose-l_384x288.py | 257 +++ ...8xb32-270e_coco-ubody-wholebody-384x288.py | 259 +++ ...8xb64-270e_coco-ubody-wholebody-256x192.py | 259 +++ ...8xb64-270e_coco-ubody-wholebody-256x192.py | 259 +++ .../dwpose/config/yolox_l_8xb8-300e_coco.py | 245 +++ modules/control/proc/dwpose/draw.py | 307 ++++ modules/control/proc/dwpose/wholebody.py | 111 ++ modules/control/proc/edge.py | 63 + modules/control/proc/hed.py | 128 ++ modules/control/proc/leres/__init__.py | 108 ++ modules/control/proc/leres/leres/LICENSE | 23 + modules/control/proc/leres/leres/Resnet.py | 199 +++ .../control/proc/leres/leres/Resnext_torch.py | 237 +++ modules/control/proc/leres/leres/__init__.py | 0 modules/control/proc/leres/leres/depthmap.py | 546 +++++++ .../leres/leres/multi_depth_model_woauxi.py | 34 + modules/control/proc/leres/leres/net_tools.py | 54 + .../control/proc/leres/leres/network_auxi.py | 419 +++++ modules/control/proc/leres/pix2pix/LICENSE | 19 + .../control/proc/leres/pix2pix/__init__.py | 0 .../proc/leres/pix2pix/models/__init__.py | 67 + .../proc/leres/pix2pix/models/base_model.py | 242 +++ .../leres/pix2pix/models/base_model_hg.py | 58 + .../proc/leres/pix2pix/models/networks.py | 628 +++++++ .../pix2pix/models/pix2pix4depth_model.py | 155 ++ .../proc/leres/pix2pix/options/__init__.py | 1 + .../leres/pix2pix/options/base_options.py | 156 ++ .../leres/pix2pix/options/test_options.py | 22 + .../proc/leres/pix2pix/util/__init__.py | 1 + .../control/proc/leres/pix2pix/util/util.py | 105 ++ modules/control/proc/lineart.py | 166 ++ modules/control/proc/lineart_anime.py | 188 +++ modules/control/proc/mediapipe_face.py | 51 + modules/control/proc/mediapipe_face_util.py | 162 ++ modules/control/proc/midas/LICENSE | 21 + modules/control/proc/midas/__init__.py | 94 ++ modules/control/proc/midas/api.py | 168 ++ modules/control/proc/midas/midas/__init__.py | 0 .../control/proc/midas/midas/base_model.py | 16 + modules/control/proc/midas/midas/blocks.py | 342 ++++ modules/control/proc/midas/midas/dpt_depth.py | 109 ++ modules/control/proc/midas/midas/midas_net.py | 76 + .../proc/midas/midas/midas_net_custom.py | 130 ++ .../control/proc/midas/midas/transforms.py | 234 +++ modules/control/proc/midas/midas/vit.py | 501 ++++++ modules/control/proc/midas/utils.py | 189 +++ modules/control/proc/mlsd/LICENSE | 201 +++ modules/control/proc/mlsd/__init__.py | 78 + modules/control/proc/mlsd/models/__init__.py | 0 .../proc/mlsd/models/mbv2_mlsd_large.py | 292 ++++ .../proc/mlsd/models/mbv2_mlsd_tiny.py | 275 ++++ modules/control/proc/mlsd/utils.py | 580 +++++++ modules/control/proc/normalbae/LICENSE | 21 + modules/control/proc/normalbae/__init__.py | 107 ++ modules/control/proc/normalbae/nets/NNET.py | 19 + .../control/proc/normalbae/nets/__init__.py | 0 .../control/proc/normalbae/nets/baseline.py | 78 + .../normalbae/nets/submodules/__init__.py | 0 .../proc/normalbae/nets/submodules/decoder.py | 202 +++ .../submodules/efficientnet_repo/BENCHMARK.md | 555 +++++++ .../nets/submodules/efficientnet_repo/LICENSE | 201 +++ .../submodules/efficientnet_repo/README.md | 323 ++++ .../submodules/efficientnet_repo/__init__.py | 0 .../efficientnet_repo/geffnet/__init__.py | 5 + .../geffnet/activations/__init__.py | 137 ++ .../geffnet/activations/activations.py | 102 ++ .../geffnet/activations/activations_jit.py | 79 + .../geffnet/activations/activations_me.py | 174 ++ .../efficientnet_repo/geffnet/config.py | 123 ++ .../geffnet/conv2d_layers.py | 304 ++++ .../geffnet/efficientnet_builder.py | 683 ++++++++ .../geffnet/gen_efficientnet.py | 1450 +++++++++++++++++ .../efficientnet_repo/geffnet/helpers.py | 71 + .../efficientnet_repo/geffnet/mobilenetv3.py | 364 +++++ .../geffnet/model_factory.py | 27 + .../efficientnet_repo/geffnet/version.py | 1 + .../submodules/efficientnet_repo/hubconf.py | 81 + .../efficientnet_repo/requirements.txt | 2 + .../submodules/efficientnet_repo/setup.py | 48 + .../submodules/efficientnet_repo/utils.py | 52 + .../submodules/efficientnet_repo/validate.py | 165 ++ .../proc/normalbae/nets/submodules/encoder.py | 28 + .../normalbae/nets/submodules/submodules.py | 139 ++ modules/control/proc/openpose/LICENSE | 108 ++ modules/control/proc/openpose/__init__.py | 233 +++ modules/control/proc/openpose/body.py | 254 +++ modules/control/proc/openpose/face.py | 360 ++++ modules/control/proc/openpose/hand.py | 89 + modules/control/proc/openpose/model.py | 215 +++ modules/control/proc/openpose/util.py | 386 +++++ modules/control/proc/pidi.py | 83 + modules/control/proc/pidi/LICENSE | 21 + modules/control/proc/pidi_model.py | 681 ++++++++ modules/control/proc/reference_sd15.py | 792 +++++++++ modules/control/proc/reference_sdxl.py | 804 +++++++++ .../control/proc/segment_anything/__init__.py | 91 ++ .../automatic_mask_generator.py | 371 +++++ .../proc/segment_anything/build_sam.py | 159 ++ .../segment_anything/modeling/__init__.py | 12 + .../proc/segment_anything/modeling/common.py | 43 + .../modeling/image_encoder.py | 395 +++++ .../segment_anything/modeling/mask_decoder.py | 176 ++ .../modeling/prompt_encoder.py | 214 +++ .../proc/segment_anything/modeling/sam.py | 178 ++ .../segment_anything/modeling/tiny_vit_sam.py | 721 ++++++++ .../segment_anything/modeling/transformer.py | 240 +++ .../proc/segment_anything/predictor.py | 267 +++ .../proc/segment_anything/utils/__init__.py | 5 + .../proc/segment_anything/utils/amg.py | 346 ++++ .../proc/segment_anything/utils/onnx.py | 143 ++ .../proc/segment_anything/utils/transforms.py | 102 ++ modules/control/proc/shuffle.py | 100 ++ modules/control/proc/zoe/LICENSE | 21 + modules/control/proc/zoe/__init__.py | 97 ++ modules/control/proc/zoe/zoedepth/__init__.py | 0 .../proc/zoe/zoedepth/models/__init__.py | 24 + .../zoedepth/models/base_models/__init__.py | 24 + .../zoe/zoedepth/models/base_models/midas.py | 378 +++++ .../models/base_models/midas_repo/LICENSE | 21 + .../models/base_models/midas_repo/README.md | 259 +++ .../models/base_models/midas_repo/__init__.py | 0 .../models/base_models/midas_repo/hubconf.py | 435 +++++ .../base_models/midas_repo/midas/__init__.py | 0 .../midas_repo/midas/backbones/__init__.py | 0 .../midas_repo/midas/backbones/beit.py | 202 +++ .../midas_repo/midas/backbones/levit.py | 109 ++ .../midas_repo/midas/backbones/next_vit.py | 37 + .../midas_repo/midas/backbones/swin.py | 13 + .../midas_repo/midas/backbones/swin2.py | 34 + .../midas_repo/midas/backbones/swin_common.py | 56 + .../midas_repo/midas/backbones/utils.py | 253 +++ .../midas_repo/midas/backbones/vit.py | 235 +++ .../midas_repo/midas/base_model.py | 16 + .../base_models/midas_repo/midas/blocks.py | 441 +++++ .../base_models/midas_repo/midas/dpt_depth.py | 166 ++ .../base_models/midas_repo/midas/midas_net.py | 76 + .../midas_repo/midas/midas_net_custom.py | 130 ++ .../midas_repo/midas/model_loader.py | 242 +++ .../midas_repo/midas/transforms.py | 234 +++ .../proc/zoe/zoedepth/models/builder.py | 51 + .../proc/zoe/zoedepth/models/depth_model.py | 150 ++ .../zoe/zoedepth/models/layers/__init__.py | 23 + .../zoe/zoedepth/models/layers/attractor.py | 208 +++ .../zoe/zoedepth/models/layers/dist_layers.py | 121 ++ .../models/layers/localbins_layers.py | 169 ++ .../models/layers/patch_transformer.py | 91 ++ .../proc/zoe/zoedepth/models/model_io.py | 91 ++ .../zoe/zoedepth/models/zoedepth/__init__.py | 31 + .../models/zoedepth/config_zoedepth.json | 58 + .../zoedepth/config_zoedepth_kitti.json | 22 + .../zoedepth/models/zoedepth/zoedepth_v1.py | 252 +++ .../zoedepth/models/zoedepth_nk/__init__.py | 31 + .../zoedepth_nk/config_zoedepth_nk.json | 67 + .../models/zoedepth_nk/zoedepth_nk_v1.py | 333 ++++ .../proc/zoe/zoedepth/utils/__init__.py | 24 + .../proc/zoe/zoedepth/utils/arg_utils.py | 33 + .../control/proc/zoe/zoedepth/utils/config.py | 437 +++++ .../zoe/zoedepth/utils/easydict/__init__.py | 158 ++ modules/control/processors.py | 214 +++ modules/control/reference.py | 57 + modules/control/run.py | 433 +++++ modules/control/test.py | 40 + modules/control/unit.py | 154 ++ modules/control/util.py | 160 ++ modules/ui.py | 7 +- modules/ui_control.py | 479 ++++++ pyproject.toml | 5 +- 174 files changed, 29949 insertions(+), 2 deletions(-) create mode 100644 modules/control/adapters.py create mode 100644 modules/control/controlnets.py create mode 100644 modules/control/controlnetsxs.py create mode 100644 modules/control/proc/__init__.py create mode 100644 modules/control/proc/canny.py create mode 100644 modules/control/proc/dwpose/__init__.py create mode 100644 modules/control/proc/dwpose/config/dwpose-l_384x288.py create mode 100644 modules/control/proc/dwpose/config/rtmpose-l_8xb32-270e_coco-ubody-wholebody-384x288.py create mode 100644 modules/control/proc/dwpose/config/rtmpose-m_8xb64-270e_coco-ubody-wholebody-256x192.py create mode 100644 modules/control/proc/dwpose/config/rtmpose-t_8xb64-270e_coco-ubody-wholebody-256x192.py create mode 100644 modules/control/proc/dwpose/config/yolox_l_8xb8-300e_coco.py create mode 100644 modules/control/proc/dwpose/draw.py create mode 100644 modules/control/proc/dwpose/wholebody.py create mode 100644 modules/control/proc/edge.py create mode 100644 modules/control/proc/hed.py create mode 100644 modules/control/proc/leres/__init__.py create mode 100644 modules/control/proc/leres/leres/LICENSE create mode 100644 modules/control/proc/leres/leres/Resnet.py create mode 100644 modules/control/proc/leres/leres/Resnext_torch.py create mode 100644 modules/control/proc/leres/leres/__init__.py create mode 100644 modules/control/proc/leres/leres/depthmap.py create mode 100644 modules/control/proc/leres/leres/multi_depth_model_woauxi.py create mode 100644 modules/control/proc/leres/leres/net_tools.py create mode 100644 modules/control/proc/leres/leres/network_auxi.py create mode 100644 modules/control/proc/leres/pix2pix/LICENSE create mode 100644 modules/control/proc/leres/pix2pix/__init__.py create mode 100644 modules/control/proc/leres/pix2pix/models/__init__.py create mode 100644 modules/control/proc/leres/pix2pix/models/base_model.py create mode 100644 modules/control/proc/leres/pix2pix/models/base_model_hg.py create mode 100644 modules/control/proc/leres/pix2pix/models/networks.py create mode 100644 modules/control/proc/leres/pix2pix/models/pix2pix4depth_model.py create mode 100644 modules/control/proc/leres/pix2pix/options/__init__.py create mode 100644 modules/control/proc/leres/pix2pix/options/base_options.py create mode 100644 modules/control/proc/leres/pix2pix/options/test_options.py create mode 100644 modules/control/proc/leres/pix2pix/util/__init__.py create mode 100644 modules/control/proc/leres/pix2pix/util/util.py create mode 100644 modules/control/proc/lineart.py create mode 100644 modules/control/proc/lineart_anime.py create mode 100644 modules/control/proc/mediapipe_face.py create mode 100644 modules/control/proc/mediapipe_face_util.py create mode 100644 modules/control/proc/midas/LICENSE create mode 100644 modules/control/proc/midas/__init__.py create mode 100644 modules/control/proc/midas/api.py create mode 100644 modules/control/proc/midas/midas/__init__.py create mode 100644 modules/control/proc/midas/midas/base_model.py create mode 100644 modules/control/proc/midas/midas/blocks.py create mode 100644 modules/control/proc/midas/midas/dpt_depth.py create mode 100644 modules/control/proc/midas/midas/midas_net.py create mode 100644 modules/control/proc/midas/midas/midas_net_custom.py create mode 100644 modules/control/proc/midas/midas/transforms.py create mode 100644 modules/control/proc/midas/midas/vit.py create mode 100644 modules/control/proc/midas/utils.py create mode 100644 modules/control/proc/mlsd/LICENSE create mode 100644 modules/control/proc/mlsd/__init__.py create mode 100644 modules/control/proc/mlsd/models/__init__.py create mode 100644 modules/control/proc/mlsd/models/mbv2_mlsd_large.py create mode 100644 modules/control/proc/mlsd/models/mbv2_mlsd_tiny.py create mode 100644 modules/control/proc/mlsd/utils.py create mode 100644 modules/control/proc/normalbae/LICENSE create mode 100644 modules/control/proc/normalbae/__init__.py create mode 100644 modules/control/proc/normalbae/nets/NNET.py create mode 100644 modules/control/proc/normalbae/nets/__init__.py create mode 100644 modules/control/proc/normalbae/nets/baseline.py create mode 100644 modules/control/proc/normalbae/nets/submodules/__init__.py create mode 100644 modules/control/proc/normalbae/nets/submodules/decoder.py create mode 100644 modules/control/proc/normalbae/nets/submodules/efficientnet_repo/BENCHMARK.md create mode 100644 modules/control/proc/normalbae/nets/submodules/efficientnet_repo/LICENSE create mode 100644 modules/control/proc/normalbae/nets/submodules/efficientnet_repo/README.md create mode 100644 modules/control/proc/normalbae/nets/submodules/efficientnet_repo/__init__.py create mode 100644 modules/control/proc/normalbae/nets/submodules/efficientnet_repo/geffnet/__init__.py create mode 100644 modules/control/proc/normalbae/nets/submodules/efficientnet_repo/geffnet/activations/__init__.py create mode 100644 modules/control/proc/normalbae/nets/submodules/efficientnet_repo/geffnet/activations/activations.py create mode 100644 modules/control/proc/normalbae/nets/submodules/efficientnet_repo/geffnet/activations/activations_jit.py create mode 100644 modules/control/proc/normalbae/nets/submodules/efficientnet_repo/geffnet/activations/activations_me.py create mode 100644 modules/control/proc/normalbae/nets/submodules/efficientnet_repo/geffnet/config.py create mode 100644 modules/control/proc/normalbae/nets/submodules/efficientnet_repo/geffnet/conv2d_layers.py create mode 100644 modules/control/proc/normalbae/nets/submodules/efficientnet_repo/geffnet/efficientnet_builder.py create mode 100644 modules/control/proc/normalbae/nets/submodules/efficientnet_repo/geffnet/gen_efficientnet.py create mode 100644 modules/control/proc/normalbae/nets/submodules/efficientnet_repo/geffnet/helpers.py create mode 100644 modules/control/proc/normalbae/nets/submodules/efficientnet_repo/geffnet/mobilenetv3.py create mode 100644 modules/control/proc/normalbae/nets/submodules/efficientnet_repo/geffnet/model_factory.py create mode 100644 modules/control/proc/normalbae/nets/submodules/efficientnet_repo/geffnet/version.py create mode 100644 modules/control/proc/normalbae/nets/submodules/efficientnet_repo/hubconf.py create mode 100644 modules/control/proc/normalbae/nets/submodules/efficientnet_repo/requirements.txt create mode 100644 modules/control/proc/normalbae/nets/submodules/efficientnet_repo/setup.py create mode 100644 modules/control/proc/normalbae/nets/submodules/efficientnet_repo/utils.py create mode 100644 modules/control/proc/normalbae/nets/submodules/efficientnet_repo/validate.py create mode 100644 modules/control/proc/normalbae/nets/submodules/encoder.py create mode 100644 modules/control/proc/normalbae/nets/submodules/submodules.py create mode 100644 modules/control/proc/openpose/LICENSE create mode 100644 modules/control/proc/openpose/__init__.py create mode 100644 modules/control/proc/openpose/body.py create mode 100644 modules/control/proc/openpose/face.py create mode 100644 modules/control/proc/openpose/hand.py create mode 100644 modules/control/proc/openpose/model.py create mode 100644 modules/control/proc/openpose/util.py create mode 100644 modules/control/proc/pidi.py create mode 100644 modules/control/proc/pidi/LICENSE create mode 100644 modules/control/proc/pidi_model.py create mode 100644 modules/control/proc/reference_sd15.py create mode 100644 modules/control/proc/reference_sdxl.py create mode 100644 modules/control/proc/segment_anything/__init__.py create mode 100644 modules/control/proc/segment_anything/automatic_mask_generator.py create mode 100644 modules/control/proc/segment_anything/build_sam.py create mode 100644 modules/control/proc/segment_anything/modeling/__init__.py create mode 100644 modules/control/proc/segment_anything/modeling/common.py create mode 100644 modules/control/proc/segment_anything/modeling/image_encoder.py create mode 100644 modules/control/proc/segment_anything/modeling/mask_decoder.py create mode 100644 modules/control/proc/segment_anything/modeling/prompt_encoder.py create mode 100644 modules/control/proc/segment_anything/modeling/sam.py create mode 100644 modules/control/proc/segment_anything/modeling/tiny_vit_sam.py create mode 100644 modules/control/proc/segment_anything/modeling/transformer.py create mode 100644 modules/control/proc/segment_anything/predictor.py create mode 100644 modules/control/proc/segment_anything/utils/__init__.py create mode 100644 modules/control/proc/segment_anything/utils/amg.py create mode 100644 modules/control/proc/segment_anything/utils/onnx.py create mode 100644 modules/control/proc/segment_anything/utils/transforms.py create mode 100644 modules/control/proc/shuffle.py create mode 100644 modules/control/proc/zoe/LICENSE create mode 100644 modules/control/proc/zoe/__init__.py create mode 100644 modules/control/proc/zoe/zoedepth/__init__.py create mode 100644 modules/control/proc/zoe/zoedepth/models/__init__.py create mode 100644 modules/control/proc/zoe/zoedepth/models/base_models/__init__.py create mode 100644 modules/control/proc/zoe/zoedepth/models/base_models/midas.py create mode 100644 modules/control/proc/zoe/zoedepth/models/base_models/midas_repo/LICENSE create mode 100644 modules/control/proc/zoe/zoedepth/models/base_models/midas_repo/README.md create mode 100644 modules/control/proc/zoe/zoedepth/models/base_models/midas_repo/__init__.py create mode 100644 modules/control/proc/zoe/zoedepth/models/base_models/midas_repo/hubconf.py create mode 100644 modules/control/proc/zoe/zoedepth/models/base_models/midas_repo/midas/__init__.py create mode 100644 modules/control/proc/zoe/zoedepth/models/base_models/midas_repo/midas/backbones/__init__.py create mode 100644 modules/control/proc/zoe/zoedepth/models/base_models/midas_repo/midas/backbones/beit.py create mode 100644 modules/control/proc/zoe/zoedepth/models/base_models/midas_repo/midas/backbones/levit.py create mode 100644 modules/control/proc/zoe/zoedepth/models/base_models/midas_repo/midas/backbones/next_vit.py create mode 100644 modules/control/proc/zoe/zoedepth/models/base_models/midas_repo/midas/backbones/swin.py create mode 100644 modules/control/proc/zoe/zoedepth/models/base_models/midas_repo/midas/backbones/swin2.py create mode 100644 modules/control/proc/zoe/zoedepth/models/base_models/midas_repo/midas/backbones/swin_common.py create mode 100644 modules/control/proc/zoe/zoedepth/models/base_models/midas_repo/midas/backbones/utils.py create mode 100644 modules/control/proc/zoe/zoedepth/models/base_models/midas_repo/midas/backbones/vit.py create mode 100644 modules/control/proc/zoe/zoedepth/models/base_models/midas_repo/midas/base_model.py create mode 100644 modules/control/proc/zoe/zoedepth/models/base_models/midas_repo/midas/blocks.py create mode 100644 modules/control/proc/zoe/zoedepth/models/base_models/midas_repo/midas/dpt_depth.py create mode 100644 modules/control/proc/zoe/zoedepth/models/base_models/midas_repo/midas/midas_net.py create mode 100644 modules/control/proc/zoe/zoedepth/models/base_models/midas_repo/midas/midas_net_custom.py create mode 100644 modules/control/proc/zoe/zoedepth/models/base_models/midas_repo/midas/model_loader.py create mode 100644 modules/control/proc/zoe/zoedepth/models/base_models/midas_repo/midas/transforms.py create mode 100644 modules/control/proc/zoe/zoedepth/models/builder.py create mode 100644 modules/control/proc/zoe/zoedepth/models/depth_model.py create mode 100644 modules/control/proc/zoe/zoedepth/models/layers/__init__.py create mode 100644 modules/control/proc/zoe/zoedepth/models/layers/attractor.py create mode 100644 modules/control/proc/zoe/zoedepth/models/layers/dist_layers.py create mode 100644 modules/control/proc/zoe/zoedepth/models/layers/localbins_layers.py create mode 100644 modules/control/proc/zoe/zoedepth/models/layers/patch_transformer.py create mode 100644 modules/control/proc/zoe/zoedepth/models/model_io.py create mode 100644 modules/control/proc/zoe/zoedepth/models/zoedepth/__init__.py create mode 100644 modules/control/proc/zoe/zoedepth/models/zoedepth/config_zoedepth.json create mode 100644 modules/control/proc/zoe/zoedepth/models/zoedepth/config_zoedepth_kitti.json create mode 100644 modules/control/proc/zoe/zoedepth/models/zoedepth/zoedepth_v1.py create mode 100644 modules/control/proc/zoe/zoedepth/models/zoedepth_nk/__init__.py create mode 100644 modules/control/proc/zoe/zoedepth/models/zoedepth_nk/config_zoedepth_nk.json create mode 100644 modules/control/proc/zoe/zoedepth/models/zoedepth_nk/zoedepth_nk_v1.py create mode 100644 modules/control/proc/zoe/zoedepth/utils/__init__.py create mode 100644 modules/control/proc/zoe/zoedepth/utils/arg_utils.py create mode 100644 modules/control/proc/zoe/zoedepth/utils/config.py create mode 100644 modules/control/proc/zoe/zoedepth/utils/easydict/__init__.py create mode 100644 modules/control/processors.py create mode 100644 modules/control/reference.py create mode 100644 modules/control/run.py create mode 100644 modules/control/test.py create mode 100644 modules/control/unit.py create mode 100644 modules/control/util.py create mode 100644 modules/ui_control.py diff --git a/CHANGELOG.md b/CHANGELOG.md index c3453f8d0..31bfd82ea 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,6 +6,7 @@ - **Control** - native implementation of **ControlNet**, **ControlNet XS** and **T2I Adapters** + - top-level control next to **text** and **image** generate - supports all variations of **SD15** and **SD-XL** models - supports *text*, *image*, *batch* and *video* processing - for details see Wiki documentation: @@ -44,6 +45,7 @@ - add timesteps range, changing it will make scheduler to be over-complete or under-complete - add rescale betas with zero SNR option (applicable to Euler, Euler a and DDIM, allows for higher dynamic range) - **General** + - support for **Torch 2.1.2** - **Process** create videos from batch or folder processing supports *GIF*, *PNG* and *MP4* with full interpolation, scene change detection, etc. - **LoRA** diff --git a/modules/control/adapters.py b/modules/control/adapters.py new file mode 100644 index 000000000..fccb093c3 --- /dev/null +++ b/modules/control/adapters.py @@ -0,0 +1,142 @@ +import os +import time +from diffusers import StableDiffusionPipeline, StableDiffusionXLPipeline, T2IAdapter, StableDiffusionAdapterPipeline, StableDiffusionXLAdapterPipeline +from modules.shared import log +from modules import errors + + +what = 'T2I-Adapter' +debug = log.debug if os.environ.get('SD_CONTROL_DEBUG', None) is not None else lambda *args, **kwargs: None +predefined_sd15 = { + 'Canny': 'TencentARC/t2iadapter_canny_sd15v2', + 'Depth': 'TencentARC/t2iadapter_depth_sd15v2', + 'Depth Zoe': 'TencentARC/t2iadapter_zoedepth_sd15v1', + 'OpenPose': 'TencentARC/t2iadapter_openpose_sd14v1', + 'Sketch': 'TencentARC/t2iadapter_sketch_sd15v2', +} +predefined_sdxl = { + 'Canny XL': 'TencentARC/t2i-adapter-canny-sdxl-1.0', + 'Depth Zoe XL': 'TencentARC/t2i-adapter-depth-zoe-sdxl-1.0', + 'Depth Midas XL': 'TencentARC/t2i-adapter-depth-midas-sdxl-1.0', + 'LineArt XL': 'TencentARC/t2i-adapter-lineart-sdxl-1.0', + 'OpenPose XL': 'TencentARC/t2i-adapter-openpose-sdxl-1.0', + 'Sketch XL': 'TencentARC/t2i-adapter-sketch-sdxl-1.0', +} +models = {} +all_models = {} +all_models.update(predefined_sd15) +all_models.update(predefined_sdxl) +cache_dir = 'models/control/adapters' + + +def list_models(refresh=False): + import modules.shared + global models # pylint: disable=global-statement + if not refresh and len(models) > 0: + return models + models = {} + if modules.shared.sd_model_type == 'none': + models = ['None'] + if modules.shared.sd_model_type == 'sdxl': + models = ['None'] + sorted(predefined_sdxl) + if modules.shared.sd_model_type == 'sd': + models = ['None'] + sorted(predefined_sd15) + else: + models = ['None'] + sorted(list(predefined_sd15) + list(predefined_sdxl)) + debug(f'Control list {what}: path={cache_dir} models={models}') + return models + + +class AdapterModel(T2IAdapter): + pass + + +class Adapter(): + def __init__(self, model_id: str = None, device = None, dtype = None, load_config = None): + self.model: AdapterModel = None + self.model_id: str = model_id + self.device = device + self.dtype = dtype + self.load_config = { 'cache_dir': cache_dir } + if load_config is not None: + self.load_config.update(load_config) + if model_id is not None: + self.load() + + def reset(self): + if self.model is not None: + log.debug(f'Control {what} model unloaded') + self.model = None + self.model_id = None + + def load(self, model_id: str = None): + try: + t0 = time.time() + model_id = model_id or self.model_id + if model_id is None or model_id == 'None': + self.reset() + return + model_path = all_models[model_id] + if model_path is None: + log.error(f'Control {what} model load failed: id="{model_id}" error=unknown model id') + return + log.debug(f'Control {what} model loading: id="{model_id}" path="{model_path}"') + self.model = T2IAdapter.from_pretrained(model_path, **self.load_config) + if self.device is not None: + self.model.to(self.device) + if self.dtype is not None: + self.model.to(self.dtype) + t1 = time.time() + self.model_id = model_id + log.debug(f'Control {what} loaded: id="{model_id}" path="{model_path}" time={t1-t0:.2f}') + except Exception as e: + log.error(f'Control {what} model load failed: id="{model_id}" error={e}') + errors.display(e, f'Control {what} load') + + +class AdapterPipeline(): + def __init__(self, adapter: T2IAdapter | list[T2IAdapter], pipeline: StableDiffusionXLPipeline | StableDiffusionPipeline, dtype = None): + t0 = time.time() + self.orig_pipeline = pipeline + self.pipeline = None + if pipeline is None: + log.error(f'Control {what} pipeline: model not loaded') + return + if isinstance(pipeline, StableDiffusionXLPipeline): + self.pipeline = StableDiffusionXLAdapterPipeline( + vae=pipeline.vae, + text_encoder=pipeline.text_encoder, + text_encoder_2=pipeline.text_encoder_2, + tokenizer=pipeline.tokenizer, + tokenizer_2=pipeline.tokenizer_2, + unet=pipeline.unet, + scheduler=pipeline.scheduler, + adapter=adapter, # can be a list + ).to(pipeline.device) + elif isinstance(pipeline, StableDiffusionPipeline): + self.pipeline = StableDiffusionAdapterPipeline( + vae=pipeline.vae, + text_encoder=pipeline.text_encoder, + tokenizer=pipeline.tokenizer, + unet=pipeline.unet, + scheduler=pipeline.scheduler, + requires_safety_checker=False, + safety_checker=None, + feature_extractor=None, + adapter=adapter, # can be a list + ).to(pipeline.device) + else: + log.error(f'Control {what} pipeline: class={pipeline.__class__.__name__} unsupported model type') + return + if dtype is not None and self.pipeline is not None: + self.pipeline.dtype = dtype + t1 = time.time() + if self.pipeline is not None: + log.debug(f'Control {what} pipeline: class={self.pipeline.__class__.__name__} time={t1-t0:.2f}') + else: + log.error(f'Control {what} pipeline: not initialized') + + + def restore(self): + self.pipeline = None + return self.orig_pipeline diff --git a/modules/control/controlnets.py b/modules/control/controlnets.py new file mode 100644 index 000000000..f884c9822 --- /dev/null +++ b/modules/control/controlnets.py @@ -0,0 +1,166 @@ +import os +import time +from diffusers import StableDiffusionPipeline +from diffusers import StableDiffusionXLPipeline +from diffusers import ControlNetModel, StableDiffusionControlNetPipeline, StableDiffusionXLControlNetPipeline +from modules.shared import log, opts +from modules import errors + + +what = 'ControlNet' +debug = log.debug if os.environ.get('SD_CONTROL_DEBUG', None) is not None else lambda *args, **kwargs: None +predefined_sd15 = { + 'OpenPose': "lllyasviel/control_v11p_sd15_openpose", + 'Canny': "lllyasviel/control_v11p_sd15_canny", + 'MLDS': "lllyasviel/control_v11p_sd15_mlsd", + 'Scribble': "lllyasviel/control_v11p_sd15_scribble", + 'SoftEdge': "lllyasviel/control_v11p_sd15_softedge", + 'Segment': "lllyasviel/control_v11p_sd15_seg", + 'Depth': "lllyasviel/control_v11f1p_sd15_depth", + 'NormalBae': "lllyasviel/control_v11p_sd15_normalbae", + 'LineArt': "lllyasviel/control_v11p_sd15_lineart", + 'LineArt Anime': "lllyasviel/control_v11p_sd15s2_lineart_anime", + 'Shuffle': "lllyasviel/control_v11e_sd15_shuffle", + 'IP2P': "lllyasviel/control_v11e_sd15_ip2p", + 'HED': "lllyasviel/sd-controlnet-hed", + 'Tile': "lllyasviel/control_v11f1e_sd15_tile", + 'TemporalNet': "CiaraRowles/TemporalNet", +} +predefined_sdxl = { + 'Canny Small XL': 'diffusers/controlnet-canny-sdxl-1.0-small', + 'Canny Mid XL': 'diffusers/controlnet-canny-sdxl-1.0-mid', + 'Canny XL': 'diffusers/controlnet-canny-sdxl-1.0', + 'Depth Zoe XL': 'diffusers/controlnet-zoe-depth-sdxl-1.0', + 'Depth Mid XL': 'diffusers/controlnet-depth-sdxl-1.0-mid', +} +models = {} +all_models = {} +all_models.update(predefined_sd15) +all_models.update(predefined_sdxl) +cache_dir = 'models/control/controlnets' + + +def find_models(): + path = os.path.join(opts.control_dir, 'controlnets') + files = os.listdir(path) + files = [f for f in files if f.endswith('.safetensors')] + downloaded_models = {} + for f in files: + basename = os.path.splitext(f)[0] + downloaded_models[basename] = os.path.join(path, f) + all_models.update(downloaded_models) + return downloaded_models + + +def list_models(refresh=False): + import modules.shared + global models # pylint: disable=global-statement + if not refresh and len(models) > 0: + return models + models = {} + if modules.shared.sd_model_type == 'none': + models = ['None'] + elif modules.shared.sd_model_type == 'sdxl': + models = ['None'] + sorted(predefined_sdxl) + sorted(find_models()) + elif modules.shared.sd_model_type == 'sd': + models = ['None'] + sorted(predefined_sd15) + sorted(find_models()) + else: + log.error('Control model list failed: unknown model type') + models = ['None'] + sorted(predefined_sd15) + sorted(predefined_sdxl) + sorted(find_models()) + debug(f'Control list {what}: path={cache_dir} models={models}') + return models + + +class ControlNet(): + def __init__(self, model_id: str = None, device = None, dtype = None, load_config = None): + self.model: ControlNetModel = None + self.model_id: str = model_id + self.device = device + self.dtype = dtype + self.load_config = { 'cache_dir': cache_dir } + if load_config is not None: + self.load_config.update(load_config) + if model_id is not None: + self.load() + + def reset(self): + if self.model is not None: + log.debug(f'Control {what} model unloaded') + self.model = None + self.model_id = None + + def load(self, model_id: str = None): + try: + t0 = time.time() + model_id = model_id or self.model_id + if model_id is None or model_id == 'None': + self.reset() + return + model_path = all_models[model_id] + if model_path == '': + return + if model_path is None: + log.error(f'Control {what} model load failed: id="{model_id}" error=unknown model id') + return + log.debug(f'Control {what} model loading: id="{model_id}" path="{model_path}"') + if model_path.endswith('.safetensors'): + self.model = ControlNetModel.from_single_file(model_path, **self.load_config) + else: + self.model = ControlNetModel.from_pretrained(model_path, **self.load_config) + if self.device is not None: + self.model.to(self.device) + if self.dtype is not None: + self.model.to(self.dtype) + t1 = time.time() + self.model_id = model_id + log.debug(f'Control {what} model loaded: id="{model_id}" path="{model_path}" time={t1-t0:.2f}') + except Exception as e: + log.error(f'Control {what} model load failed: id="{model_id}" error={e}') + errors.display(e, f'Control {what} load') + + +class ControlNetPipeline(): + def __init__(self, controlnet: ControlNetModel | list[ControlNetModel], pipeline: StableDiffusionXLPipeline | StableDiffusionPipeline, dtype = None): + t0 = time.time() + self.orig_pipeline = pipeline + self.pipeline = None + if pipeline is None: + log.error('Control model pipeline: model not loaded') + return + if isinstance(pipeline, StableDiffusionXLPipeline): + self.pipeline = StableDiffusionXLControlNetPipeline( + vae=pipeline.vae, + text_encoder=pipeline.text_encoder, + text_encoder_2=pipeline.text_encoder_2, + tokenizer=pipeline.tokenizer, + tokenizer_2=pipeline.tokenizer_2, + unet=pipeline.unet, + scheduler=pipeline.scheduler, + controlnet=controlnet, # can be a list + ).to(pipeline.device) + elif isinstance(pipeline, StableDiffusionPipeline): + self.pipeline = StableDiffusionControlNetPipeline( + vae=pipeline.vae, + text_encoder=pipeline.text_encoder, + tokenizer=pipeline.tokenizer, + unet=pipeline.unet, + scheduler=pipeline.scheduler, + requires_safety_checker=False, + safety_checker=None, + feature_extractor=None, + controlnet=controlnet, # can be a list + ).to(pipeline.device) + else: + log.error(f'Control {what} pipeline: class={pipeline.__class__.__name__} unsupported model type') + return + if dtype is not None and self.pipeline is not None: + self.pipeline = self.pipeline.to(dtype) + t1 = time.time() + if self.pipeline is not None: + log.debug(f'Control {what} pipeline: class={self.pipeline.__class__.__name__} time={t1-t0:.2f}') + else: + log.error(f'Control {what} pipeline: not initialized') + + def restore(self): + self.pipeline = None + return self.orig_pipeline diff --git a/modules/control/controlnetsxs.py b/modules/control/controlnetsxs.py new file mode 100644 index 000000000..712fdf7b9 --- /dev/null +++ b/modules/control/controlnetsxs.py @@ -0,0 +1,149 @@ +import os +import time +from diffusers import StableDiffusionPipeline +from diffusers import StableDiffusionXLPipeline +from diffusers import ControlNetXSModel, StableDiffusionControlNetXSPipeline, StableDiffusionXLControlNetXSPipeline +from modules.shared import log, opts +from modules import errors + + +what = 'ControlNet-XS' +debug = log.debug if os.environ.get('SD_CONTROL_DEBUG', None) is not None else lambda *args, **kwargs: None +predefined_sd15 = { +} +predefined_sdxl = { + 'Canny': 'UmerHA/ConrolNetXS-SDXL-canny', + 'Depth': 'UmerHA/ConrolNetXS-SDXL-depth', +} +models = {} +all_models = {} +all_models.update(predefined_sd15) +all_models.update(predefined_sdxl) +cache_dir = 'models/control/controlnetsxs' + + +def find_models(): + path = os.path.join(opts.control_dir, 'controlnetsxs') + files = os.listdir(path) + files = [f for f in files if f.endswith('.safetensors')] + downloaded_models = {} + for f in files: + basename = os.path.splitext(f)[0] + downloaded_models[basename] = os.path.join(path, f) + all_models.update(downloaded_models) + return downloaded_models + + +def list_models(refresh=False): + import modules.shared + global models # pylint: disable=global-statement + if not refresh and len(models) > 0: + return models + models = {} + if modules.shared.sd_model_type == 'none': + models = ['None'] + elif modules.shared.sd_model_type == 'sdxl': + models = ['None'] + sorted(predefined_sdxl) + sorted(find_models()) + elif modules.shared.sd_model_type == 'sd': + models = ['None'] + sorted(predefined_sd15) + sorted(find_models()) + else: + log.error(f'Control {what} model list failed: unknown model type') + models = ['None'] + sorted(predefined_sd15) + sorted(predefined_sdxl) + sorted(find_models()) + debug(f'Control list {what}: path={cache_dir} models={models}') + return models + + +class ControlNetXS(): + def __init__(self, model_id: str = None, device = None, dtype = None, load_config = None): + self.model: ControlNetXSModel = None + self.model_id: str = model_id + self.device = device + self.dtype = dtype + self.load_config = { 'cache_dir': cache_dir, 'learn_embedding': True } + if load_config is not None: + self.load_config.update(load_config) + if model_id is not None: + self.load() + + def reset(self): + if self.model is not None: + log.debug(f'Control {what} model unloaded') + self.model = None + self.model_id = None + + def load(self, model_id: str = None, time_embedding_mix: float = 0.0): + try: + t0 = time.time() + model_id = model_id or self.model_id + if model_id is None or model_id == 'None': + self.reset() + return + model_path = all_models[model_id] + if model_path == '': + return + if model_path is None: + log.error(f'Control {what} model load failed: id="{model_id}" error=unknown model id') + return + self.load_config['time_embedding_mix'] = time_embedding_mix + log.debug(f'Control {what} model loading: id="{model_id}" path="{model_path}" {self.load_config}') + if model_path.endswith('.safetensors'): + self.model = ControlNetXSModel.from_single_file(model_path, **self.load_config) + else: + self.model = ControlNetXSModel.from_pretrained(model_path, **self.load_config) + if self.device is not None: + self.model.to(self.device) + if self.dtype is not None: + self.model.to(self.dtype) + t1 = time.time() + self.model_id = model_id + log.debug(f'Control {what} model loaded: id="{model_id}" path="{model_path}" time={t1-t0:.2f}') + except Exception as e: + log.error(f'Control {what} model load failed: id="{model_id}" error={e}') + errors.display(e, f'Control {what} load') + + +class ControlNetPipeline(): + def __init__(self, controlnet: ControlNetXSModel | list[ControlNetXSModel], pipeline: StableDiffusionXLPipeline | StableDiffusionPipeline, dtype = None): + t0 = time.time() + self.orig_pipeline = pipeline + self.pipeline = None + if pipeline is None: + log.error(f'Control {what} pipeline: model not loaded') + return + if isinstance(pipeline, StableDiffusionXLPipeline): + self.pipeline = StableDiffusionXLControlNetXSPipeline( + vae=pipeline.vae, + text_encoder=pipeline.text_encoder, + text_encoder_2=pipeline.text_encoder_2, + tokenizer=pipeline.tokenizer, + tokenizer_2=pipeline.tokenizer_2, + unet=pipeline.unet, + scheduler=pipeline.scheduler, + controlnet=controlnet, # can be a list + ).to(pipeline.device) + elif isinstance(pipeline, StableDiffusionPipeline): + self.pipeline = StableDiffusionControlNetXSPipeline( + vae=pipeline.vae, + text_encoder=pipeline.text_encoder, + tokenizer=pipeline.tokenizer, + unet=pipeline.unet, + scheduler=pipeline.scheduler, + requires_safety_checker=False, + safety_checker=None, + feature_extractor=None, + controlnet=controlnet, # can be a list + ).to(pipeline.device) + else: + log.error(f'Control {what} pipeline: class={pipeline.__class__.__name__} unsupported model type') + return + if dtype is not None and self.pipeline is not None: + self.pipeline = self.pipeline.to(dtype) + t1 = time.time() + if self.pipeline is not None: + log.debug(f'Control {what} pipeline: class={self.pipeline.__class__.__name__} time={t1-t0:.2f}') + else: + log.error(f'Control {what} pipeline: not initialized') + + def restore(self): + self.pipeline = None + return self.orig_pipeline diff --git a/modules/control/proc/__init__.py b/modules/control/proc/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/modules/control/proc/canny.py b/modules/control/proc/canny.py new file mode 100644 index 000000000..e68673d88 --- /dev/null +++ b/modules/control/proc/canny.py @@ -0,0 +1,35 @@ +import warnings +import cv2 +import numpy as np +from PIL import Image +from modules.control.util import HWC3, resize_image + +class CannyDetector: + def __call__(self, input_image=None, low_threshold=100, high_threshold=200, detect_resolution=512, image_resolution=512, output_type=None, **kwargs): + if "img" in kwargs: + warnings.warn("img is deprecated, please use `input_image=...` instead.", DeprecationWarning) + input_image = kwargs.pop("img") + if input_image is None: + raise ValueError("input_image must be defined.") + + if not isinstance(input_image, np.ndarray): + input_image = np.array(input_image, dtype=np.uint8) + output_type = output_type or "pil" + else: + output_type = output_type or "np" + + input_image = HWC3(input_image) + input_image = resize_image(input_image, detect_resolution) + + detected_map = cv2.Canny(input_image, low_threshold, high_threshold) + detected_map = HWC3(detected_map) + + img = resize_image(input_image, image_resolution) + H, W, _C = img.shape + + detected_map = cv2.resize(detected_map, (W, H), interpolation=cv2.INTER_LINEAR) + + if output_type == "pil": + detected_map = Image.fromarray(detected_map) + + return detected_map diff --git a/modules/control/proc/dwpose/__init__.py b/modules/control/proc/dwpose/__init__.py new file mode 100644 index 000000000..a0c5c513b --- /dev/null +++ b/modules/control/proc/dwpose/__init__.py @@ -0,0 +1,90 @@ +# Openpose +# Original from CMU https://github.com/CMU-Perceptual-Computing-Lab/openpose +# 2nd Edited by https://github.com/Hzzone/pytorch-openpose +# 3rd Edited by ControlNet +# 4th Edited by ControlNet (added face and correct hands) + +import os +os.environ["KMP_DUPLICATE_LIB_OK"]="TRUE" + +import cv2 +import numpy as np +from PIL import Image + +from modules.control.util import HWC3, resize_image +from .draw import draw_bodypose, draw_handpose, draw_facepose + + +def draw_pose(pose, H, W): + bodies = pose['bodies'] + faces = pose['faces'] + hands = pose['hands'] + candidate = bodies['candidate'] + subset = bodies['subset'] + + canvas = np.zeros(shape=(H, W, 3), dtype=np.uint8) + canvas = draw_bodypose(canvas, candidate, subset) + canvas = draw_handpose(canvas, hands) + canvas = draw_facepose(canvas, faces) + + return canvas + +class DWposeDetector: + def __init__(self, det_config=None, det_ckpt=None, pose_config=None, pose_ckpt=None, device="cpu"): + from .wholebody import Wholebody + + self.pose_estimation = Wholebody(det_config, det_ckpt, pose_config, pose_ckpt, device) + + def to(self, device): + self.pose_estimation.to(device) + return self + + def __call__(self, input_image, detect_resolution=512, image_resolution=512, output_type="pil", min_confidence=0.3, **kwargs): + input_image = cv2.cvtColor(np.array(input_image, dtype=np.uint8), cv2.COLOR_RGB2BGR) + + input_image = HWC3(input_image) + input_image = resize_image(input_image, detect_resolution) + H, W, _C = input_image.shape + + candidate, subset = self.pose_estimation(input_image) + if candidate is None: + return Image.fromarray(input_image) + nums, _keys, locs = candidate.shape + candidate[..., 0] /= float(W) + candidate[..., 1] /= float(H) + body = candidate[:,:18].copy() + body = body.reshape(nums*18, locs) + score = subset[:,:18] + + for i in range(len(score)): + for j in range(len(score[i])): + if score[i][j] > min_confidence: + score[i][j] = int(18*i+j) + else: + score[i][j] = -1 + + un_visible = subset < min_confidence + candidate[un_visible] = -1 + + _foot = candidate[:,18:24] + + faces = candidate[:,24:92] + + hands = candidate[:,92:113] + hands = np.vstack([hands, candidate[:,113:]]) + + bodies = dict(candidate=body, subset=score) + pose = dict(bodies=bodies, hands=hands, faces=faces) + + detected_map = draw_pose(pose, H, W) + detected_map = HWC3(detected_map) + + img = resize_image(input_image, image_resolution) + H, W, _C = img.shape + + detected_map = cv2.resize(detected_map, (W, H), interpolation=cv2.INTER_LINEAR) + + if output_type == "pil": + detected_map = Image.fromarray(detected_map) + + return detected_map diff --git a/modules/control/proc/dwpose/config/dwpose-l_384x288.py b/modules/control/proc/dwpose/config/dwpose-l_384x288.py new file mode 100644 index 000000000..e054be72e --- /dev/null +++ b/modules/control/proc/dwpose/config/dwpose-l_384x288.py @@ -0,0 +1,257 @@ +# runtime +max_epochs = 270 +stage2_num_epochs = 30 +base_lr = 4e-3 + +train_cfg = dict(max_epochs=max_epochs, val_interval=10) +randomness = dict(seed=21) + +# optimizer +optim_wrapper = dict( + type='OptimWrapper', + optimizer=dict(type='AdamW', lr=base_lr, weight_decay=0.05), + paramwise_cfg=dict( + norm_decay_mult=0, bias_decay_mult=0, bypass_duplicate=True)) + +# learning rate +param_scheduler = [ + dict( + type='LinearLR', + start_factor=1.0e-5, + by_epoch=False, + begin=0, + end=1000), + dict( + # use cosine lr from 150 to 300 epoch + type='CosineAnnealingLR', + eta_min=base_lr * 0.05, + begin=max_epochs // 2, + end=max_epochs, + T_max=max_epochs // 2, + by_epoch=True, + convert_to_iter_based=True), +] + +# automatically scaling LR based on the actual training batch size +auto_scale_lr = dict(base_batch_size=512) + +# codec settings +codec = dict( + type='SimCCLabel', + input_size=(288, 384), + sigma=(6., 6.93), + simcc_split_ratio=2.0, + normalize=False, + use_dark=False) + +# model settings +model = dict( + type='TopdownPoseEstimator', + data_preprocessor=dict( + type='PoseDataPreprocessor', + mean=[123.675, 116.28, 103.53], + std=[58.395, 57.12, 57.375], + bgr_to_rgb=True), + backbone=dict( + _scope_='mmdet', + type='CSPNeXt', + arch='P5', + expand_ratio=0.5, + deepen_factor=1., + widen_factor=1., + out_indices=(4, ), + channel_attention=True, + norm_cfg=dict(type='SyncBN'), + act_cfg=dict(type='SiLU'), + init_cfg=dict( + type='Pretrained', + prefix='backbone.', + checkpoint='https://download.openmmlab.com/mmpose/v1/projects/' + 'rtmpose/cspnext-l_udp-aic-coco_210e-256x192-273b7631_20230130.pth' + )), + head=dict( + type='RTMCCHead', + in_channels=1024, + out_channels=133, + input_size=codec['input_size'], + in_featuremap_size=(9, 12), + simcc_split_ratio=codec['simcc_split_ratio'], + final_layer_kernel_size=7, + gau_cfg=dict( + hidden_dims=256, + s=128, + expansion_factor=2, + dropout_rate=0., + drop_path=0., + act_fn='SiLU', + use_rel_bias=False, + pos_enc=False), + loss=dict( + type='KLDiscretLoss', + use_target_weight=True, + beta=10., + label_softmax=True), + decoder=codec), + test_cfg=dict(flip_test=True, )) + +# base dataset settings +dataset_type = 'CocoWholeBodyDataset' +data_mode = 'topdown' +data_root = '/data/' + +backend_args = dict(backend='local') +# backend_args = dict( +# backend='petrel', +# path_mapping=dict({ +# f'{data_root}': 's3://openmmlab/datasets/detection/coco/', +# f'{data_root}': 's3://openmmlab/datasets/detection/coco/' +# })) + +# pipelines +train_pipeline = [ + dict(type='LoadImage', backend_args=backend_args), + dict(type='GetBBoxCenterScale'), + dict(type='RandomFlip', direction='horizontal'), + dict(type='RandomHalfBody'), + dict( + type='RandomBBoxTransform', scale_factor=[0.6, 1.4], rotate_factor=80), + dict(type='TopdownAffine', input_size=codec['input_size']), + dict(type='mmdet.YOLOXHSVRandomAug'), + dict( + type='Albumentation', + transforms=[ + dict(type='Blur', p=0.1), + dict(type='MedianBlur', p=0.1), + dict( + type='CoarseDropout', + max_holes=1, + max_height=0.4, + max_width=0.4, + min_holes=1, + min_height=0.2, + min_width=0.2, + p=1.0), + ]), + dict(type='GenerateTarget', encoder=codec), + dict(type='PackPoseInputs') +] +val_pipeline = [ + dict(type='LoadImage', backend_args=backend_args), + dict(type='GetBBoxCenterScale'), + dict(type='TopdownAffine', input_size=codec['input_size']), + dict(type='PackPoseInputs') +] + +train_pipeline_stage2 = [ + dict(type='LoadImage', backend_args=backend_args), + dict(type='GetBBoxCenterScale'), + dict(type='RandomFlip', direction='horizontal'), + dict(type='RandomHalfBody'), + dict( + type='RandomBBoxTransform', + shift_factor=0., + scale_factor=[0.75, 1.25], + rotate_factor=60), + dict(type='TopdownAffine', input_size=codec['input_size']), + dict(type='mmdet.YOLOXHSVRandomAug'), + dict( + type='Albumentation', + transforms=[ + dict(type='Blur', p=0.1), + dict(type='MedianBlur', p=0.1), + dict( + type='CoarseDropout', + max_holes=1, + max_height=0.4, + max_width=0.4, + min_holes=1, + min_height=0.2, + min_width=0.2, + p=0.5), + ]), + dict(type='GenerateTarget', encoder=codec), + dict(type='PackPoseInputs') +] + +datasets = [] +dataset_coco=dict( + type=dataset_type, + data_root=data_root, + data_mode=data_mode, + ann_file='coco/annotations/coco_wholebody_train_v1.0.json', + data_prefix=dict(img='coco/train2017/'), + pipeline=[], +) +datasets.append(dataset_coco) + +scene = ['Magic_show', 'Entertainment', 'ConductMusic', 'Online_class', + 'TalkShow', 'Speech', 'Fitness', 'Interview', 'Olympic', 'TVShow', + 'Singing', 'SignLanguage', 'Movie', 'LiveVlog', 'VideoConference'] + +for i in range(len(scene)): + datasets.append( + dict( + type=dataset_type, + data_root=data_root, + data_mode=data_mode, + ann_file='UBody/annotations/'+scene[i]+'/keypoint_annotation.json', + data_prefix=dict(img='UBody/images/'+scene[i]+'/'), + pipeline=[], + ) + ) + +# data loaders +train_dataloader = dict( + batch_size=32, + num_workers=10, + persistent_workers=True, + sampler=dict(type='DefaultSampler', shuffle=True), + dataset=dict( + type='CombinedDataset', + metainfo=dict(from_file='configs/_base_/datasets/coco_wholebody.py'), + datasets=datasets, + pipeline=train_pipeline, + test_mode=False, + )) +val_dataloader = dict( + batch_size=32, + num_workers=10, + persistent_workers=True, + drop_last=False, + sampler=dict(type='DefaultSampler', shuffle=False, round_up=False), + dataset=dict( + type=dataset_type, + data_root=data_root, + data_mode=data_mode, + ann_file='coco/annotations/coco_wholebody_val_v1.0.json', + bbox_file=f'{data_root}coco/person_detection_results/' + 'COCO_val2017_detections_AP_H_56_person.json', + data_prefix=dict(img='coco/val2017/'), + test_mode=True, + pipeline=val_pipeline, + )) +test_dataloader = val_dataloader + +# hooks +default_hooks = dict( + checkpoint=dict( + save_best='coco-wholebody/AP', rule='greater', max_keep_ckpts=1)) + +custom_hooks = [ + dict( + type='EMAHook', + ema_type='ExpMomentumEMA', + momentum=0.0002, + update_buffers=True, + priority=49), + dict( + type='mmdet.PipelineSwitchHook', + switch_epoch=max_epochs - stage2_num_epochs, + switch_pipeline=train_pipeline_stage2) +] + +# evaluators +val_evaluator = dict( + type='CocoWholeBodyMetric', + ann_file=data_root + 'coco/annotations/coco_wholebody_val_v1.0.json') +test_evaluator = val_evaluator diff --git a/modules/control/proc/dwpose/config/rtmpose-l_8xb32-270e_coco-ubody-wholebody-384x288.py b/modules/control/proc/dwpose/config/rtmpose-l_8xb32-270e_coco-ubody-wholebody-384x288.py new file mode 100644 index 000000000..3c89683f0 --- /dev/null +++ b/modules/control/proc/dwpose/config/rtmpose-l_8xb32-270e_coco-ubody-wholebody-384x288.py @@ -0,0 +1,259 @@ +# _base_ = ['../../../_base_/default_runtime.py'] + +# runtime +max_epochs = 270 +stage2_num_epochs = 30 +base_lr = 4e-3 + +train_cfg = dict(max_epochs=max_epochs, val_interval=10) +randomness = dict(seed=21) + +# optimizer +optim_wrapper = dict( + type='OptimWrapper', + optimizer=dict(type='AdamW', lr=base_lr, weight_decay=0.05), + paramwise_cfg=dict( + norm_decay_mult=0, bias_decay_mult=0, bypass_duplicate=True)) + +# learning rate +param_scheduler = [ + dict( + type='LinearLR', + start_factor=1.0e-5, + by_epoch=False, + begin=0, + end=1000), + dict( + # use cosine lr from 150 to 300 epoch + type='CosineAnnealingLR', + eta_min=base_lr * 0.05, + begin=max_epochs // 2, + end=max_epochs, + T_max=max_epochs // 2, + by_epoch=True, + convert_to_iter_based=True), +] + +# automatically scaling LR based on the actual training batch size +auto_scale_lr = dict(base_batch_size=512) + +# codec settings +codec = dict( + type='SimCCLabel', + input_size=(288, 384), + sigma=(6., 6.93), + simcc_split_ratio=2.0, + normalize=False, + use_dark=False) + +# model settings +model = dict( + type='TopdownPoseEstimator', + data_preprocessor=dict( + type='PoseDataPreprocessor', + mean=[123.675, 116.28, 103.53], + std=[58.395, 57.12, 57.375], + bgr_to_rgb=True), + backbone=dict( + _scope_='mmdet', + type='CSPNeXt', + arch='P5', + expand_ratio=0.5, + deepen_factor=1., + widen_factor=1., + out_indices=(4, ), + channel_attention=True, + norm_cfg=dict(type='SyncBN'), + act_cfg=dict(type='SiLU'), + init_cfg=dict( + type='Pretrained', + prefix='backbone.', + checkpoint='https://download.openmmlab.com/mmpose/v1/projects/' + 'rtmpose/cspnext-l_udp-aic-coco_210e-256x192-273b7631_20230130.pth' + )), + head=dict( + type='RTMCCHead', + in_channels=1024, + out_channels=133, + input_size=codec['input_size'], + in_featuremap_size=(9, 12), + simcc_split_ratio=codec['simcc_split_ratio'], + final_layer_kernel_size=7, + gau_cfg=dict( + hidden_dims=256, + s=128, + expansion_factor=2, + dropout_rate=0., + drop_path=0., + act_fn='SiLU', + use_rel_bias=False, + pos_enc=False), + loss=dict( + type='KLDiscretLoss', + use_target_weight=True, + beta=10., + label_softmax=True), + decoder=codec), + test_cfg=dict(flip_test=True, )) + +# base dataset settings +dataset_type = 'CocoWholeBodyDataset' +data_mode = 'topdown' +data_root = 'data/' + +backend_args = dict(backend='local') +# backend_args = dict( +# backend='petrel', +# path_mapping=dict({ +# f'{data_root}': 's3://openmmlab/datasets/detection/coco/', +# f'{data_root}': 's3://openmmlab/datasets/detection/coco/' +# })) + +# pipelines +train_pipeline = [ + dict(type='LoadImage', backend_args=backend_args), + dict(type='GetBBoxCenterScale'), + dict(type='RandomFlip', direction='horizontal'), + dict(type='RandomHalfBody'), + dict( + type='RandomBBoxTransform', scale_factor=[0.6, 1.4], rotate_factor=80), + dict(type='TopdownAffine', input_size=codec['input_size']), + dict(type='mmdet.YOLOXHSVRandomAug'), + dict( + type='Albumentation', + transforms=[ + dict(type='Blur', p=0.1), + dict(type='MedianBlur', p=0.1), + dict( + type='CoarseDropout', + max_holes=1, + max_height=0.4, + max_width=0.4, + min_holes=1, + min_height=0.2, + min_width=0.2, + p=1.0), + ]), + dict(type='GenerateTarget', encoder=codec), + dict(type='PackPoseInputs') +] +val_pipeline = [ + dict(type='LoadImage', backend_args=backend_args), + dict(type='GetBBoxCenterScale'), + dict(type='TopdownAffine', input_size=codec['input_size']), + dict(type='PackPoseInputs') +] + +train_pipeline_stage2 = [ + dict(type='LoadImage', backend_args=backend_args), + dict(type='GetBBoxCenterScale'), + dict(type='RandomFlip', direction='horizontal'), + dict(type='RandomHalfBody'), + dict( + type='RandomBBoxTransform', + shift_factor=0., + scale_factor=[0.75, 1.25], + rotate_factor=60), + dict(type='TopdownAffine', input_size=codec['input_size']), + dict(type='mmdet.YOLOXHSVRandomAug'), + dict( + type='Albumentation', + transforms=[ + dict(type='Blur', p=0.1), + dict(type='MedianBlur', p=0.1), + dict( + type='CoarseDropout', + max_holes=1, + max_height=0.4, + max_width=0.4, + min_holes=1, + min_height=0.2, + min_width=0.2, + p=0.5), + ]), + dict(type='GenerateTarget', encoder=codec), + dict(type='PackPoseInputs') +] + +datasets = [] +dataset_coco=dict( + type=dataset_type, + data_root=data_root, + data_mode=data_mode, + ann_file='coco/annotations/coco_wholebody_train_v1.0.json', + data_prefix=dict(img='coco/train2017/'), + pipeline=[], +) +datasets.append(dataset_coco) + +scene = ['Magic_show', 'Entertainment', 'ConductMusic', 'Online_class', + 'TalkShow', 'Speech', 'Fitness', 'Interview', 'Olympic', 'TVShow', + 'Singing', 'SignLanguage', 'Movie', 'LiveVlog', 'VideoConference'] + +for i in range(len(scene)): + datasets.append( + dict( + type=dataset_type, + data_root=data_root, + data_mode=data_mode, + ann_file='UBody/annotations/'+scene[i]+'/keypoint_annotation.json', + data_prefix=dict(img='UBody/images/'+scene[i]+'/'), + pipeline=[], + ) + ) + +# data loaders +train_dataloader = dict( + batch_size=32, + num_workers=10, + persistent_workers=True, + sampler=dict(type='DefaultSampler', shuffle=True), + dataset=dict( + type='CombinedDataset', + metainfo=dict(from_file='configs/_base_/datasets/coco_wholebody.py'), + datasets=datasets, + pipeline=train_pipeline, + test_mode=False, + )) +val_dataloader = dict( + batch_size=32, + num_workers=10, + persistent_workers=True, + drop_last=False, + sampler=dict(type='DefaultSampler', shuffle=False, round_up=False), + dataset=dict( + type=dataset_type, + data_root=data_root, + data_mode=data_mode, + ann_file='coco/annotations/coco_wholebody_val_v1.0.json', + bbox_file=f'{data_root}coco/person_detection_results/' + 'COCO_val2017_detections_AP_H_56_person.json', + data_prefix=dict(img='coco/val2017/'), + test_mode=True, + pipeline=val_pipeline, + )) +test_dataloader = val_dataloader + +# hooks +default_hooks = dict( + checkpoint=dict( + save_best='coco-wholebody/AP', rule='greater', max_keep_ckpts=1)) + +custom_hooks = [ + dict( + type='EMAHook', + ema_type='ExpMomentumEMA', + momentum=0.0002, + update_buffers=True, + priority=49), + dict( + type='mmdet.PipelineSwitchHook', + switch_epoch=max_epochs - stage2_num_epochs, + switch_pipeline=train_pipeline_stage2) +] + +# evaluators +val_evaluator = dict( + type='CocoWholeBodyMetric', + ann_file=data_root + 'coco/annotations/coco_wholebody_val_v1.0.json') +test_evaluator = val_evaluator diff --git a/modules/control/proc/dwpose/config/rtmpose-m_8xb64-270e_coco-ubody-wholebody-256x192.py b/modules/control/proc/dwpose/config/rtmpose-m_8xb64-270e_coco-ubody-wholebody-256x192.py new file mode 100644 index 000000000..20adeea96 --- /dev/null +++ b/modules/control/proc/dwpose/config/rtmpose-m_8xb64-270e_coco-ubody-wholebody-256x192.py @@ -0,0 +1,259 @@ +# _base_ = ['../../../_base_/default_runtime.py'] + +# runtime +max_epochs = 270 +stage2_num_epochs = 30 +base_lr = 4e-3 + +train_cfg = dict(max_epochs=max_epochs, val_interval=10) +randomness = dict(seed=21) + +# optimizer +optim_wrapper = dict( + type='OptimWrapper', + optimizer=dict(type='AdamW', lr=base_lr, weight_decay=0.05), + paramwise_cfg=dict( + norm_decay_mult=0, bias_decay_mult=0, bypass_duplicate=True)) + +# learning rate +param_scheduler = [ + dict( + type='LinearLR', + start_factor=1.0e-5, + by_epoch=False, + begin=0, + end=1000), + dict( + # use cosine lr from 150 to 300 epoch + type='CosineAnnealingLR', + eta_min=base_lr * 0.05, + begin=max_epochs // 2, + end=max_epochs, + T_max=max_epochs // 2, + by_epoch=True, + convert_to_iter_based=True), +] + +# automatically scaling LR based on the actual training batch size +auto_scale_lr = dict(base_batch_size=512) + +# codec settings +codec = dict( + type='SimCCLabel', + input_size=(192, 256), + sigma=(4.9, 5.66), + simcc_split_ratio=2.0, + normalize=False, + use_dark=False) + +# model settings +model = dict( + type='TopdownPoseEstimator', + data_preprocessor=dict( + type='PoseDataPreprocessor', + mean=[123.675, 116.28, 103.53], + std=[58.395, 57.12, 57.375], + bgr_to_rgb=True), + backbone=dict( + _scope_='mmdet', + type='CSPNeXt', + arch='P5', + expand_ratio=0.5, + deepen_factor=0.67, + widen_factor=0.75, + out_indices=(4, ), + channel_attention=True, + norm_cfg=dict(type='SyncBN'), + act_cfg=dict(type='SiLU'), + init_cfg=dict( + type='Pretrained', + prefix='backbone.', + checkpoint='https://download.openmmlab.com/mmpose/v1/projects/' + 'rtmpose/cspnext-m_udp-aic-coco_210e-256x192-f2f7d6f6_20230130.pth' + )), + head=dict( + type='RTMCCHead', + in_channels=768, + out_channels=133, + input_size=codec['input_size'], + in_featuremap_size=(6, 8), + simcc_split_ratio=codec['simcc_split_ratio'], + final_layer_kernel_size=7, + gau_cfg=dict( + hidden_dims=256, + s=128, + expansion_factor=2, + dropout_rate=0., + drop_path=0., + act_fn='SiLU', + use_rel_bias=False, + pos_enc=False), + loss=dict( + type='KLDiscretLoss', + use_target_weight=True, + beta=10., + label_softmax=True), + decoder=codec), + test_cfg=dict(flip_test=True, )) + +# base dataset settings +dataset_type = 'CocoWholeBodyDataset' +data_mode = 'topdown' +data_root = 'data/' + +backend_args = dict(backend='local') +# backend_args = dict( +# backend='petrel', +# path_mapping=dict({ +# f'{data_root}': 's3://openmmlab/datasets/detection/coco/', +# f'{data_root}': 's3://openmmlab/datasets/detection/coco/' +# })) + +# pipelines +train_pipeline = [ + dict(type='LoadImage', backend_args=backend_args), + dict(type='GetBBoxCenterScale'), + dict(type='RandomFlip', direction='horizontal'), + dict(type='RandomHalfBody'), + dict( + type='RandomBBoxTransform', scale_factor=[0.6, 1.4], rotate_factor=80), + dict(type='TopdownAffine', input_size=codec['input_size']), + dict(type='mmdet.YOLOXHSVRandomAug'), + dict( + type='Albumentation', + transforms=[ + dict(type='Blur', p=0.1), + dict(type='MedianBlur', p=0.1), + dict( + type='CoarseDropout', + max_holes=1, + max_height=0.4, + max_width=0.4, + min_holes=1, + min_height=0.2, + min_width=0.2, + p=1.0), + ]), + dict(type='GenerateTarget', encoder=codec), + dict(type='PackPoseInputs') +] +val_pipeline = [ + dict(type='LoadImage', backend_args=backend_args), + dict(type='GetBBoxCenterScale'), + dict(type='TopdownAffine', input_size=codec['input_size']), + dict(type='PackPoseInputs') +] + +train_pipeline_stage2 = [ + dict(type='LoadImage', backend_args=backend_args), + dict(type='GetBBoxCenterScale'), + dict(type='RandomFlip', direction='horizontal'), + dict(type='RandomHalfBody'), + dict( + type='RandomBBoxTransform', + shift_factor=0., + scale_factor=[0.75, 1.25], + rotate_factor=60), + dict(type='TopdownAffine', input_size=codec['input_size']), + dict(type='mmdet.YOLOXHSVRandomAug'), + dict( + type='Albumentation', + transforms=[ + dict(type='Blur', p=0.1), + dict(type='MedianBlur', p=0.1), + dict( + type='CoarseDropout', + max_holes=1, + max_height=0.4, + max_width=0.4, + min_holes=1, + min_height=0.2, + min_width=0.2, + p=0.5), + ]), + dict(type='GenerateTarget', encoder=codec), + dict(type='PackPoseInputs') +] + +datasets = [] +dataset_coco=dict( + type=dataset_type, + data_root=data_root, + data_mode=data_mode, + ann_file='coco/annotations/coco_wholebody_train_v1.0.json', + data_prefix=dict(img='coco/train2017/'), + pipeline=[], +) +datasets.append(dataset_coco) + +scene = ['Magic_show', 'Entertainment', 'ConductMusic', 'Online_class', + 'TalkShow', 'Speech', 'Fitness', 'Interview', 'Olympic', 'TVShow', + 'Singing', 'SignLanguage', 'Movie', 'LiveVlog', 'VideoConference'] + +for i in range(len(scene)): + datasets.append( + dict( + type=dataset_type, + data_root=data_root, + data_mode=data_mode, + ann_file='UBody/annotations/'+scene[i]+'/keypoint_annotation.json', + data_prefix=dict(img='UBody/images/'+scene[i]+'/'), + pipeline=[], + ) + ) + +# data loaders +train_dataloader = dict( + batch_size=64, + num_workers=10, + persistent_workers=True, + sampler=dict(type='DefaultSampler', shuffle=True), + dataset=dict( + type='CombinedDataset', + metainfo=dict(from_file='configs/_base_/datasets/coco_wholebody.py'), + datasets=datasets, + pipeline=train_pipeline, + test_mode=False, + )) +val_dataloader = dict( + batch_size=32, + num_workers=10, + persistent_workers=True, + drop_last=False, + sampler=dict(type='DefaultSampler', shuffle=False, round_up=False), + dataset=dict( + type=dataset_type, + data_root=data_root, + data_mode=data_mode, + ann_file='coco/annotations/coco_wholebody_val_v1.0.json', + bbox_file=f'{data_root}coco/person_detection_results/' + 'COCO_val2017_detections_AP_H_56_person.json', + data_prefix=dict(img='coco/val2017/'), + test_mode=True, + pipeline=val_pipeline, + )) +test_dataloader = val_dataloader + +# hooks +default_hooks = dict( + checkpoint=dict( + save_best='coco-wholebody/AP', rule='greater', max_keep_ckpts=1)) + +custom_hooks = [ + dict( + type='EMAHook', + ema_type='ExpMomentumEMA', + momentum=0.0002, + update_buffers=True, + priority=49), + dict( + type='mmdet.PipelineSwitchHook', + switch_epoch=max_epochs - stage2_num_epochs, + switch_pipeline=train_pipeline_stage2) +] + +# evaluators +val_evaluator = dict( + type='CocoWholeBodyMetric', + ann_file=data_root + 'coco/annotations/coco_wholebody_val_v1.0.json') +test_evaluator = val_evaluator diff --git a/modules/control/proc/dwpose/config/rtmpose-t_8xb64-270e_coco-ubody-wholebody-256x192.py b/modules/control/proc/dwpose/config/rtmpose-t_8xb64-270e_coco-ubody-wholebody-256x192.py new file mode 100644 index 000000000..d97c2f78b --- /dev/null +++ b/modules/control/proc/dwpose/config/rtmpose-t_8xb64-270e_coco-ubody-wholebody-256x192.py @@ -0,0 +1,259 @@ +# _base_ = ['../../../_base_/default_runtime.py'] + +# runtime +max_epochs = 270 +stage2_num_epochs = 30 +base_lr = 4e-3 + +train_cfg = dict(max_epochs=max_epochs, val_interval=10) +randomness = dict(seed=21) + +# optimizer +optim_wrapper = dict( + type='OptimWrapper', + optimizer=dict(type='AdamW', lr=base_lr, weight_decay=0.05), + paramwise_cfg=dict( + norm_decay_mult=0, bias_decay_mult=0, bypass_duplicate=True)) + +# learning rate +param_scheduler = [ + dict( + type='LinearLR', + start_factor=1.0e-5, + by_epoch=False, + begin=0, + end=1000), + dict( + # use cosine lr from 150 to 300 epoch + type='CosineAnnealingLR', + eta_min=base_lr * 0.05, + begin=max_epochs // 2, + end=max_epochs, + T_max=max_epochs // 2, + by_epoch=True, + convert_to_iter_based=True), +] + +# automatically scaling LR based on the actual training batch size +auto_scale_lr = dict(base_batch_size=512) + +# codec settings +codec = dict( + type='SimCCLabel', + input_size=(192, 256), + sigma=(4.9, 5.66), + simcc_split_ratio=2.0, + normalize=False, + use_dark=False) + +# model settings +model = dict( + type='TopdownPoseEstimator', + data_preprocessor=dict( + type='PoseDataPreprocessor', + mean=[123.675, 116.28, 103.53], + std=[58.395, 57.12, 57.375], + bgr_to_rgb=True), + backbone=dict( + _scope_='mmdet', + type='CSPNeXt', + arch='P5', + expand_ratio=0.5, + deepen_factor=0.167, + widen_factor=0.375, + out_indices=(4, ), + channel_attention=True, + norm_cfg=dict(type='SyncBN'), + act_cfg=dict(type='SiLU'), + init_cfg=dict( + type='Pretrained', + prefix='backbone.', + checkpoint='https://download.openmmlab.com/mmpose/v1/projects/' + 'rtmpose/cspnext-tiny_udp-aic-coco_210e-256x192-cbed682d_20230130.pth' + )), + head=dict( + type='RTMCCHead', + in_channels=384, + out_channels=133, + input_size=codec['input_size'], + in_featuremap_size=(6, 8), + simcc_split_ratio=codec['simcc_split_ratio'], + final_layer_kernel_size=7, + gau_cfg=dict( + hidden_dims=256, + s=128, + expansion_factor=2, + dropout_rate=0., + drop_path=0., + act_fn='SiLU', + use_rel_bias=False, + pos_enc=False), + loss=dict( + type='KLDiscretLoss', + use_target_weight=True, + beta=10., + label_softmax=True), + decoder=codec), + test_cfg=dict(flip_test=True, )) + +# base dataset settings +dataset_type = 'CocoWholeBodyDataset' +data_mode = 'topdown' +data_root = 'data/' + +backend_args = dict(backend='local') +# backend_args = dict( +# backend='petrel', +# path_mapping=dict({ +# f'{data_root}': 's3://openmmlab/datasets/detection/coco/', +# f'{data_root}': 's3://openmmlab/datasets/detection/coco/' +# })) + +# pipelines +train_pipeline = [ + dict(type='LoadImage', backend_args=backend_args), + dict(type='GetBBoxCenterScale'), + dict(type='RandomFlip', direction='horizontal'), + dict(type='RandomHalfBody'), + dict( + type='RandomBBoxTransform', scale_factor=[0.6, 1.4], rotate_factor=80), + dict(type='TopdownAffine', input_size=codec['input_size']), + dict(type='mmdet.YOLOXHSVRandomAug'), + dict( + type='Albumentation', + transforms=[ + dict(type='Blur', p=0.1), + dict(type='MedianBlur', p=0.1), + dict( + type='CoarseDropout', + max_holes=1, + max_height=0.4, + max_width=0.4, + min_holes=1, + min_height=0.2, + min_width=0.2, + p=1.0), + ]), + dict(type='GenerateTarget', encoder=codec), + dict(type='PackPoseInputs') +] +val_pipeline = [ + dict(type='LoadImage', backend_args=backend_args), + dict(type='GetBBoxCenterScale'), + dict(type='TopdownAffine', input_size=codec['input_size']), + dict(type='PackPoseInputs') +] + +train_pipeline_stage2 = [ + dict(type='LoadImage', backend_args=backend_args), + dict(type='GetBBoxCenterScale'), + dict(type='RandomFlip', direction='horizontal'), + dict(type='RandomHalfBody'), + dict( + type='RandomBBoxTransform', + shift_factor=0., + scale_factor=[0.75, 1.25], + rotate_factor=60), + dict(type='TopdownAffine', input_size=codec['input_size']), + dict(type='mmdet.YOLOXHSVRandomAug'), + dict( + type='Albumentation', + transforms=[ + dict(type='Blur', p=0.1), + dict(type='MedianBlur', p=0.1), + dict( + type='CoarseDropout', + max_holes=1, + max_height=0.4, + max_width=0.4, + min_holes=1, + min_height=0.2, + min_width=0.2, + p=0.5), + ]), + dict(type='GenerateTarget', encoder=codec), + dict(type='PackPoseInputs') +] + +datasets = [] +dataset_coco=dict( + type=dataset_type, + data_root=data_root, + data_mode=data_mode, + ann_file='coco/annotations/coco_wholebody_train_v1.0.json', + data_prefix=dict(img='coco/train2017/'), + pipeline=[], +) +datasets.append(dataset_coco) + +scene = ['Magic_show', 'Entertainment', 'ConductMusic', 'Online_class', + 'TalkShow', 'Speech', 'Fitness', 'Interview', 'Olympic', 'TVShow', + 'Singing', 'SignLanguage', 'Movie', 'LiveVlog', 'VideoConference'] + +for i in range(len(scene)): + datasets.append( + dict( + type=dataset_type, + data_root=data_root, + data_mode=data_mode, + ann_file='UBody/annotations/'+scene[i]+'/keypoint_annotation.json', + data_prefix=dict(img='UBody/images/'+scene[i]+'/'), + pipeline=[], + ) + ) + +# data loaders +train_dataloader = dict( + batch_size=64, + num_workers=10, + persistent_workers=True, + sampler=dict(type='DefaultSampler', shuffle=True), + dataset=dict( + type='CombinedDataset', + metainfo=dict(from_file='configs/_base_/datasets/coco_wholebody.py'), + datasets=datasets, + pipeline=train_pipeline, + test_mode=False, + )) +val_dataloader = dict( + batch_size=32, + num_workers=10, + persistent_workers=True, + drop_last=False, + sampler=dict(type='DefaultSampler', shuffle=False, round_up=False), + dataset=dict( + type=dataset_type, + data_root=data_root, + data_mode=data_mode, + ann_file='coco/annotations/coco_wholebody_val_v1.0.json', + bbox_file=f'{data_root}coco/person_detection_results/' + 'COCO_val2017_detections_AP_H_56_person.json', + data_prefix=dict(img='coco/val2017/'), + test_mode=True, + pipeline=val_pipeline, + )) +test_dataloader = val_dataloader + +# hooks +default_hooks = dict( + checkpoint=dict( + save_best='coco-wholebody/AP', rule='greater', max_keep_ckpts=1)) + +custom_hooks = [ + dict( + type='EMAHook', + ema_type='ExpMomentumEMA', + momentum=0.0002, + update_buffers=True, + priority=49), + dict( + type='mmdet.PipelineSwitchHook', + switch_epoch=max_epochs - stage2_num_epochs, + switch_pipeline=train_pipeline_stage2) +] + +# evaluators +val_evaluator = dict( + type='CocoWholeBodyMetric', + ann_file=data_root + 'coco/annotations/coco_wholebody_val_v1.0.json') +test_evaluator = val_evaluator diff --git a/modules/control/proc/dwpose/config/yolox_l_8xb8-300e_coco.py b/modules/control/proc/dwpose/config/yolox_l_8xb8-300e_coco.py new file mode 100644 index 000000000..7b4cb5a4b --- /dev/null +++ b/modules/control/proc/dwpose/config/yolox_l_8xb8-300e_coco.py @@ -0,0 +1,245 @@ +img_scale = (640, 640) # width, height + +# model settings +model = dict( + type='YOLOX', + data_preprocessor=dict( + type='DetDataPreprocessor', + pad_size_divisor=32, + batch_augments=[ + dict( + type='BatchSyncRandomResize', + random_size_range=(480, 800), + size_divisor=32, + interval=10) + ]), + backbone=dict( + type='CSPDarknet', + deepen_factor=1.0, + widen_factor=1.0, + out_indices=(2, 3, 4), + use_depthwise=False, + spp_kernal_sizes=(5, 9, 13), + norm_cfg=dict(type='BN', momentum=0.03, eps=0.001), + act_cfg=dict(type='Swish'), + ), + neck=dict( + type='YOLOXPAFPN', + in_channels=[256, 512, 1024], + out_channels=256, + num_csp_blocks=3, + use_depthwise=False, + upsample_cfg=dict(scale_factor=2, mode='nearest'), + norm_cfg=dict(type='BN', momentum=0.03, eps=0.001), + act_cfg=dict(type='Swish')), + bbox_head=dict( + type='YOLOXHead', + num_classes=80, + in_channels=256, + feat_channels=256, + stacked_convs=2, + strides=(8, 16, 32), + use_depthwise=False, + norm_cfg=dict(type='BN', momentum=0.03, eps=0.001), + act_cfg=dict(type='Swish'), + loss_cls=dict( + type='CrossEntropyLoss', + use_sigmoid=True, + reduction='sum', + loss_weight=1.0), + loss_bbox=dict( + type='IoULoss', + mode='square', + eps=1e-16, + reduction='sum', + loss_weight=5.0), + loss_obj=dict( + type='CrossEntropyLoss', + use_sigmoid=True, + reduction='sum', + loss_weight=1.0), + loss_l1=dict(type='L1Loss', reduction='sum', loss_weight=1.0)), + train_cfg=dict(assigner=dict(type='SimOTAAssigner', center_radius=2.5)), + # In order to align the source code, the threshold of the val phase is + # 0.01, and the threshold of the test phase is 0.001. + test_cfg=dict(score_thr=0.01, nms=dict(type='nms', iou_threshold=0.65))) + +# dataset settings +data_root = 'data/coco/' +dataset_type = 'CocoDataset' + +# Example to use different file client +# Method 1: simply set the data root and let the file I/O module +# automatically infer from prefix (not support LMDB and Memcache yet) + +# data_root = 's3://openmmlab/datasets/detection/coco/' + +# Method 2: Use `backend_args`, `file_client_args` in versions before 3.0.0rc6 +# backend_args = dict( +# backend='petrel', +# path_mapping=dict({ +# './data/': 's3://openmmlab/datasets/detection/', +# 'data/': 's3://openmmlab/datasets/detection/' +# })) +backend_args = None + +train_pipeline = [ + dict(type='Mosaic', img_scale=img_scale, pad_val=114.0), + dict( + type='RandomAffine', + scaling_ratio_range=(0.1, 2), + # img_scale is (width, height) + border=(-img_scale[0] // 2, -img_scale[1] // 2)), + dict( + type='MixUp', + img_scale=img_scale, + ratio_range=(0.8, 1.6), + pad_val=114.0), + dict(type='YOLOXHSVRandomAug'), + dict(type='RandomFlip', prob=0.5), + # According to the official implementation, multi-scale + # training is not considered here but in the + # 'mmdet/models/detectors/yolox.py'. + # Resize and Pad are for the last 15 epochs when Mosaic, + # RandomAffine, and MixUp are closed by YOLOXModeSwitchHook. + dict(type='Resize', scale=img_scale, keep_ratio=True), + dict( + type='Pad', + pad_to_square=True, + # If the image is three-channel, the pad value needs + # to be set separately for each channel. + pad_val=dict(img=(114.0, 114.0, 114.0))), + dict(type='FilterAnnotations', min_gt_bbox_wh=(1, 1), keep_empty=False), + dict(type='PackDetInputs') +] + +train_dataset = dict( + # use MultiImageMixDataset wrapper to support mosaic and mixup + type='MultiImageMixDataset', + dataset=dict( + type=dataset_type, + data_root=data_root, + ann_file='annotations/instances_train2017.json', + data_prefix=dict(img='train2017/'), + pipeline=[ + dict(type='LoadImageFromFile', backend_args=backend_args), + dict(type='LoadAnnotations', with_bbox=True) + ], + filter_cfg=dict(filter_empty_gt=False, min_size=32), + backend_args=backend_args), + pipeline=train_pipeline) + +test_pipeline = [ + dict(type='LoadImageFromFile', backend_args=backend_args), + dict(type='Resize', scale=img_scale, keep_ratio=True), + dict( + type='Pad', + pad_to_square=True, + pad_val=dict(img=(114.0, 114.0, 114.0))), + dict(type='LoadAnnotations', with_bbox=True), + dict( + type='PackDetInputs', + meta_keys=('img_id', 'img_path', 'ori_shape', 'img_shape', + 'scale_factor')) +] + +train_dataloader = dict( + batch_size=8, + num_workers=4, + persistent_workers=True, + sampler=dict(type='DefaultSampler', shuffle=True), + dataset=train_dataset) +val_dataloader = dict( + batch_size=8, + num_workers=4, + persistent_workers=True, + drop_last=False, + sampler=dict(type='DefaultSampler', shuffle=False), + dataset=dict( + type=dataset_type, + data_root=data_root, + ann_file='annotations/instances_val2017.json', + data_prefix=dict(img='val2017/'), + test_mode=True, + pipeline=test_pipeline, + backend_args=backend_args)) +test_dataloader = val_dataloader + +val_evaluator = dict( + type='CocoMetric', + ann_file=data_root + 'annotations/instances_val2017.json', + metric='bbox', + backend_args=backend_args) +test_evaluator = val_evaluator + +# training settings +max_epochs = 300 +num_last_epochs = 15 +interval = 10 + +train_cfg = dict(max_epochs=max_epochs, val_interval=interval) + +# optimizer +# default 8 gpu +base_lr = 0.01 +optim_wrapper = dict( + type='OptimWrapper', + optimizer=dict( + type='SGD', lr=base_lr, momentum=0.9, weight_decay=5e-4, + nesterov=True), + paramwise_cfg=dict(norm_decay_mult=0., bias_decay_mult=0.)) + +# learning rate +param_scheduler = [ + dict( + # use quadratic formula to warm up 5 epochs + # and lr is updated by iteration + # TODO: fix default scope in get function + type='mmdet.QuadraticWarmupLR', + by_epoch=True, + begin=0, + end=5, + convert_to_iter_based=True), + dict( + # use cosine lr from 5 to 285 epoch + type='CosineAnnealingLR', + eta_min=base_lr * 0.05, + begin=5, + T_max=max_epochs - num_last_epochs, + end=max_epochs - num_last_epochs, + by_epoch=True, + convert_to_iter_based=True), + dict( + # use fixed lr during last 15 epochs + type='ConstantLR', + by_epoch=True, + factor=1, + begin=max_epochs - num_last_epochs, + end=max_epochs, + ) +] + +default_hooks = dict( + checkpoint=dict( + interval=interval, + max_keep_ckpts=3 # only keep latest 3 checkpoints + )) + +custom_hooks = [ + dict( + type='YOLOXModeSwitchHook', + num_last_epochs=num_last_epochs, + priority=48), + dict(type='SyncNormHook', priority=48), + dict( + type='EMAHook', + ema_type='ExpMomentumEMA', + momentum=0.0001, + update_buffers=True, + priority=49) +] + +# NOTE: `auto_scale_lr` is for automatically scaling LR, +# USER SHOULD NOT CHANGE ITS VALUES. +# base_batch_size = (8 GPUs) x (8 samples per GPU) +auto_scale_lr = dict(base_batch_size=64) diff --git a/modules/control/proc/dwpose/draw.py b/modules/control/proc/dwpose/draw.py new file mode 100644 index 000000000..dbccc3af1 --- /dev/null +++ b/modules/control/proc/dwpose/draw.py @@ -0,0 +1,307 @@ +import math +import numpy as np +import cv2 + + +eps = 0.01 + + +def smart_resize(x, s): + Ht, Wt = s + if x.ndim == 2: + Ho, Wo = x.shape + Co = 1 + else: + Ho, Wo, Co = x.shape + if Co == 3 or Co == 1: + k = float(Ht + Wt) / float(Ho + Wo) + return cv2.resize(x, (int(Wt), int(Ht)), interpolation=cv2.INTER_AREA if k < 1 else cv2.INTER_LANCZOS4) + else: + return np.stack([smart_resize(x[:, :, i], s) for i in range(Co)], axis=2) + + +def smart_resize_k(x, fx, fy): + if x.ndim == 2: + Ho, Wo = x.shape + Co = 1 + else: + Ho, Wo, Co = x.shape + Ht, Wt = Ho * fy, Wo * fx + if Co == 3 or Co == 1: + k = float(Ht + Wt) / float(Ho + Wo) + return cv2.resize(x, (int(Wt), int(Ht)), interpolation=cv2.INTER_AREA if k < 1 else cv2.INTER_LANCZOS4) + else: + return np.stack([smart_resize_k(x[:, :, i], fx, fy) for i in range(Co)], axis=2) + + +def padRightDownCorner(img, stride, padValue): + h = img.shape[0] + w = img.shape[1] + + pad = 4 * [None] + pad[0] = 0 # up + pad[1] = 0 # left + pad[2] = 0 if (h % stride == 0) else stride - (h % stride) # down + pad[3] = 0 if (w % stride == 0) else stride - (w % stride) # right + + img_padded = img + pad_up = np.tile(img_padded[0:1, :, :]*0 + padValue, (pad[0], 1, 1)) + img_padded = np.concatenate((pad_up, img_padded), axis=0) + pad_left = np.tile(img_padded[:, 0:1, :]*0 + padValue, (1, pad[1], 1)) + img_padded = np.concatenate((pad_left, img_padded), axis=1) + pad_down = np.tile(img_padded[-2:-1, :, :]*0 + padValue, (pad[2], 1, 1)) + img_padded = np.concatenate((img_padded, pad_down), axis=0) + pad_right = np.tile(img_padded[:, -2:-1, :]*0 + padValue, (1, pad[3], 1)) + img_padded = np.concatenate((img_padded, pad_right), axis=1) + + return img_padded, pad + + +def transfer(model, model_weights): + transfered_model_weights = {} + for weights_name in model.state_dict().keys(): + transfered_model_weights[weights_name] = model_weights['.'.join(weights_name.split('.')[1:])] + return transfered_model_weights + + +def draw_bodypose(canvas, candidate, subset): + H, W, _C = canvas.shape + candidate = np.array(candidate) + subset = np.array(subset) + + stickwidth = 4 + + limbSeq = [[2, 3], [2, 6], [3, 4], [4, 5], [6, 7], [7, 8], [2, 9], [9, 10], \ + [10, 11], [2, 12], [12, 13], [13, 14], [2, 1], [1, 15], [15, 17], \ + [1, 16], [16, 18], [3, 17], [6, 18]] + + colors = [[255, 0, 0], [255, 85, 0], [255, 170, 0], [255, 255, 0], [170, 255, 0], [85, 255, 0], [0, 255, 0], \ + [0, 255, 85], [0, 255, 170], [0, 255, 255], [0, 170, 255], [0, 85, 255], [0, 0, 255], [85, 0, 255], \ + [170, 0, 255], [255, 0, 255], [255, 0, 170], [255, 0, 85]] + + for i in range(17): + for n in range(len(subset)): + index = subset[n][np.array(limbSeq[i]) - 1] + if -1 in index: + continue + Y = candidate[index.astype(int), 0] * float(W) + X = candidate[index.astype(int), 1] * float(H) + mX = np.mean(X) + mY = np.mean(Y) + length = ((X[0] - X[1]) ** 2 + (Y[0] - Y[1]) ** 2) ** 0.5 + angle = math.degrees(math.atan2(X[0] - X[1], Y[0] - Y[1])) + polygon = cv2.ellipse2Poly((int(mY), int(mX)), (int(length / 2), stickwidth), int(angle), 0, 360, 1) + cv2.fillConvexPoly(canvas, polygon, colors[i]) + + canvas = (canvas * 0.6).astype(np.uint8) + + for i in range(18): + for n in range(len(subset)): + index = int(subset[n][i]) + if index == -1: + continue + x, y = candidate[index][0:2] + x = int(x * W) + y = int(y * H) + cv2.circle(canvas, (int(x), int(y)), 4, colors[i], thickness=-1) + + return canvas + + +def draw_handpose(canvas, all_hand_peaks): + import matplotlib as mpl + + H, W, _C = canvas.shape + + edges = [[0, 1], [1, 2], [2, 3], [3, 4], [0, 5], [5, 6], [6, 7], [7, 8], [0, 9], [9, 10], \ + [10, 11], [11, 12], [0, 13], [13, 14], [14, 15], [15, 16], [0, 17], [17, 18], [18, 19], [19, 20]] + + # (person_number*2, 21, 2) + for i in range(len(all_hand_peaks)): + peaks = all_hand_peaks[i] + peaks = np.array(peaks) + + for ie, e in enumerate(edges): + + x1, y1 = peaks[e[0]] + x2, y2 = peaks[e[1]] + + x1 = int(x1 * W) + y1 = int(y1 * H) + x2 = int(x2 * W) + y2 = int(y2 * H) + if x1 > eps and y1 > eps and x2 > eps and y2 > eps: + cv2.line(canvas, (x1, y1), (x2, y2), mpl.colors.hsv_to_rgb([ie / float(len(edges)), 1.0, 1.0]) * 255, thickness=2) + + for _, keyponit in enumerate(peaks): + x, y = keyponit + + x = int(x * W) + y = int(y * H) + if x > eps and y > eps: + cv2.circle(canvas, (x, y), 4, (0, 0, 255), thickness=-1) + return canvas + + +def draw_facepose(canvas, all_lmks): + H, W, _C = canvas.shape + for lmks in all_lmks: + lmks = np.array(lmks) + for lmk in lmks: + x, y = lmk + x = int(x * W) + y = int(y * H) + if x > eps and y > eps: + cv2.circle(canvas, (x, y), 3, (255, 255, 255), thickness=-1) + return canvas + + +# detect hand according to body pose keypoints +# please refer to https://github.com/CMU-Perceptual-Computing-Lab/openpose/blob/master/src/openpose/hand/handDetector.cpp +def handDetect(candidate, subset, oriImg): + # right hand: wrist 4, elbow 3, shoulder 2 + # left hand: wrist 7, elbow 6, shoulder 5 + ratioWristElbow = 0.33 + detect_result = [] + image_height, image_width = oriImg.shape[0:2] + for person in subset.astype(int): + # if any of three not detected + has_left = np.sum(person[[5, 6, 7]] == -1) == 0 + has_right = np.sum(person[[2, 3, 4]] == -1) == 0 + if not (has_left or has_right): + continue + hands = [] + #left hand + if has_left: + left_shoulder_index, left_elbow_index, left_wrist_index = person[[5, 6, 7]] + x1, y1 = candidate[left_shoulder_index][:2] + x2, y2 = candidate[left_elbow_index][:2] + x3, y3 = candidate[left_wrist_index][:2] + hands.append([x1, y1, x2, y2, x3, y3, True]) + # right hand + if has_right: + right_shoulder_index, right_elbow_index, right_wrist_index = person[[2, 3, 4]] + x1, y1 = candidate[right_shoulder_index][:2] + x2, y2 = candidate[right_elbow_index][:2] + x3, y3 = candidate[right_wrist_index][:2] + hands.append([x1, y1, x2, y2, x3, y3, False]) + + for x1, y1, x2, y2, x3, y3, is_left in hands: + # pos_hand = pos_wrist + ratio * (pos_wrist - pos_elbox) = (1 + ratio) * pos_wrist - ratio * pos_elbox + # handRectangle.x = posePtr[wrist*3] + ratioWristElbow * (posePtr[wrist*3] - posePtr[elbow*3]); + # handRectangle.y = posePtr[wrist*3+1] + ratioWristElbow * (posePtr[wrist*3+1] - posePtr[elbow*3+1]); + # const auto distanceWristElbow = getDistance(poseKeypoints, person, wrist, elbow); + # const auto distanceElbowShoulder = getDistance(poseKeypoints, person, elbow, shoulder); + # handRectangle.width = 1.5f * fastMax(distanceWristElbow, 0.9f * distanceElbowShoulder); + x = x3 + ratioWristElbow * (x3 - x2) + y = y3 + ratioWristElbow * (y3 - y2) + distanceWristElbow = math.sqrt((x3 - x2) ** 2 + (y3 - y2) ** 2) + distanceElbowShoulder = math.sqrt((x2 - x1) ** 2 + (y2 - y1) ** 2) + width = 1.5 * max(distanceWristElbow, 0.9 * distanceElbowShoulder) + # x-y refers to the center --> offset to topLeft point + # handRectangle.x -= handRectangle.width / 2.f; + # handRectangle.y -= handRectangle.height / 2.f; + x -= width / 2 + y -= width / 2 # width = height + # overflow the image + if x < 0: + x = 0 + if y < 0: + y = 0 + width1 = width + width2 = width + if x + width > image_width: + width1 = image_width - x + if y + width > image_height: + width2 = image_height - y + width = min(width1, width2) + # the max hand box value is 20 pixels + if width >= 20: + detect_result.append([int(x), int(y), int(width), is_left]) + + ''' + return value: [[x, y, w, True if left hand else False]]. + width=height since the network require squared input. + x, y is the coordinate of top left + ''' + return detect_result + + +# Written by Lvmin +def faceDetect(candidate, subset, oriImg): + # left right eye ear 14 15 16 17 + detect_result = [] + image_height, image_width = oriImg.shape[0:2] + for person in subset.astype(int): + has_head = person[0] > -1 + if not has_head: + continue + + has_left_eye = person[14] > -1 + has_right_eye = person[15] > -1 + has_left_ear = person[16] > -1 + has_right_ear = person[17] > -1 + + if not (has_left_eye or has_right_eye or has_left_ear or has_right_ear): + continue + + head, left_eye, right_eye, left_ear, right_ear = person[[0, 14, 15, 16, 17]] + + width = 0.0 + x0, y0 = candidate[head][:2] + + if has_left_eye: + x1, y1 = candidate[left_eye][:2] + d = max(abs(x0 - x1), abs(y0 - y1)) + width = max(width, d * 3.0) + + if has_right_eye: + x1, y1 = candidate[right_eye][:2] + d = max(abs(x0 - x1), abs(y0 - y1)) + width = max(width, d * 3.0) + + if has_left_ear: + x1, y1 = candidate[left_ear][:2] + d = max(abs(x0 - x1), abs(y0 - y1)) + width = max(width, d * 1.5) + + if has_right_ear: + x1, y1 = candidate[right_ear][:2] + d = max(abs(x0 - x1), abs(y0 - y1)) + width = max(width, d * 1.5) + + x, y = x0, y0 + + x -= width + y -= width + + if x < 0: + x = 0 + + if y < 0: + y = 0 + + width1 = width * 2 + width2 = width * 2 + + if x + width > image_width: + width1 = image_width - x + + if y + width > image_height: + width2 = image_height - y + + width = min(width1, width2) + + if width >= 20: + detect_result.append([int(x), int(y), int(width)]) + + return detect_result + + +# get max index of 2d array +def npmax(array): + arrayindex = array.argmax(1) + arrayvalue = array.max(1) + i = arrayvalue.argmax() + j = arrayindex[i] + return i, j diff --git a/modules/control/proc/dwpose/wholebody.py b/modules/control/proc/dwpose/wholebody.py new file mode 100644 index 000000000..356044c35 --- /dev/null +++ b/modules/control/proc/dwpose/wholebody.py @@ -0,0 +1,111 @@ +# Copyright (c) OpenMMLab. All rights reserved. +import os +import numpy as np +from modules.shared import log + +mmok = True + +try: + import mmcv # pylint: disable=unused-import +except ImportError as e: + mmok = False + log.error(f"Control processor DWPose: {e}") +try: + from mmpose.apis import inference_topdown + from mmpose.apis import init_model as init_pose_estimator + from mmpose.evaluation.functional import nms + from mmpose.utils import adapt_mmdet_pipeline + from mmpose.structures import merge_data_samples +except ImportError as e: + mmok = False + log.error(f"Control processor DWPose: {e}") + +try: + from mmdet.apis import inference_detector, init_detector +except ImportError as e: + mmok = False + log.error(f"Control processor DWPose: {e}") + + def inference_detector(*args, **kwargs): + return lambda *args, **kwargs: None + +if not mmok: + log.error('Control processor DWPose: OpenMMLab is not installed') + + +class Wholebody: + def __init__(self, det_config=None, det_ckpt=None, pose_config=None, pose_ckpt=None, device="cpu"): + if not mmok: + self.detector = lambda *args, **kwargs: None + return None + prefix = os.path.dirname(__file__) + if det_config is None: + det_config = "config/yolox_l_8xb8-300e_coco.py" + if pose_config is None: + pose_config = "config/dwpose-l_384x288.py" + if not det_config.startswith('prefix'): + det_config = os.path.join(prefix, det_config) + if not pose_config.startswith('prefix'): + pose_config = os.path.join(prefix, pose_config) + if det_ckpt is None: + det_ckpt = 'https://download.openmmlab.com/mmdetection/v2.0/yolox/yolox_l_8x8_300e_coco/yolox_l_8x8_300e_coco_20211126_140236-d3bd2b23.pth' + if pose_ckpt is None: + pose_ckpt = "https://huggingface.co/wanghaofan/dw-ll_ucoco_384/resolve/main/dw-ll_ucoco_384.pth" + # build detector + self.detector = init_detector(det_config, det_ckpt, device=device) + self.detector.cfg = adapt_mmdet_pipeline(self.detector.cfg) + # build pose estimator + self.pose_estimator = init_pose_estimator( + pose_config, + pose_ckpt, + device=device) + + def to(self, device): + self.detector.to(device) + self.pose_estimator.to(device) + return self + + def __call__(self, oriImg): + if not mmok: + return None, None + # predict bbox + det_result = inference_detector(self.detector, oriImg) + pred_instance = det_result.pred_instances.cpu().numpy() + bboxes = np.concatenate((pred_instance.bboxes, pred_instance.scores[:, None]), axis=1) + bboxes = bboxes[np.logical_and(pred_instance.labels == 0, pred_instance.scores > 0.5)] + # set NMS threshold + bboxes = bboxes[nms(bboxes, 0.7), :4] + # predict keypoints + if len(bboxes) == 0: + pose_results = inference_topdown(self.pose_estimator, oriImg) + else: + pose_results = inference_topdown(self.pose_estimator, oriImg, bboxes) + preds = merge_data_samples(pose_results) + preds = preds.pred_instances + # preds = pose_results[0].pred_instances + keypoints = preds.get('transformed_keypoints', preds.keypoints) + if 'keypoint_scores' in preds: + scores = preds.keypoint_scores + else: + scores = np.ones(keypoints.shape[:-1]) + if 'keypoints_visible' in preds: + visible = preds.keypoints_visible + else: + visible = np.ones(keypoints.shape[:-1]) + keypoints_info = np.concatenate( + (keypoints, scores[..., None], visible[..., None]), + axis=-1) + # compute neck joint + neck = np.mean(keypoints_info[:, [5, 6]], axis=1) + # neck score when visualizing pred + neck[:, 2:4] = np.logical_and( + keypoints_info[:, 5, 2:4] > 0.3, + keypoints_info[:, 6, 2:4] > 0.3).astype(int) + new_keypoints_info = np.insert( + keypoints_info, 17, neck, axis=1) + mmpose_idx = [17, 6, 8, 10, 7, 9, 12, 14, 16, 13, 15, 2, 1, 4, 3] + openpose_idx = [1, 2, 3, 4, 6, 7, 8, 9, 10, 12, 13, 14, 15, 16, 17] + new_keypoints_info[:, openpose_idx] = new_keypoints_info[:, mmpose_idx] + keypoints_info = new_keypoints_info + keypoints, scores, visible = keypoints_info[..., :2], keypoints_info[..., 2], keypoints_info[..., 3] + return keypoints, scores diff --git a/modules/control/proc/edge.py b/modules/control/proc/edge.py new file mode 100644 index 000000000..d068383c1 --- /dev/null +++ b/modules/control/proc/edge.py @@ -0,0 +1,63 @@ +import warnings +import cv2 +import numpy as np +from PIL import Image +from modules.control.util import HWC3, resize_image + +ed = None +""" + PFmode: bool + EdgeDetectionOperator: int + GradientThresholdValue: int + AnchorThresholdValue: int + ScanInterval: int + MinPathLength: int + Sigma: float + SumFlag: bool + NFAValidation: bool + MinLineLength: int + MaxDistanceBetweenTwoLines: float + LineFitErrorThreshold: float + MaxErrorThreshold: float +""" + +class EdgeDetector: + def __call__(self, input_image=None, pf=True, mode='edge', detect_resolution=512, image_resolution=512, output_type=None, **kwargs): + global ed # pylint: disable=global-statement + if ed is None: + ed = cv2.ximgproc.createEdgeDrawing() + params = cv2.ximgproc.EdgeDrawing.Params() + params.PFmode = pf + ed.setParams(params) + if "img" in kwargs: + warnings.warn("img is deprecated, please use `input_image=...` instead.", DeprecationWarning) + input_image = kwargs.pop("img") + if input_image is None: + raise ValueError("input_image must be defined.") + + if not isinstance(input_image, np.ndarray): + input_image = np.array(input_image, dtype=np.uint8) + output_type = output_type or "pil" + else: + output_type = output_type or "np" + + input_image = HWC3(input_image) + input_image = resize_image(input_image, detect_resolution) + img_gray = cv2.cvtColor(input_image, cv2.COLOR_BGR2GRAY) + edges = ed.detectEdges(img_gray) + if mode == 'edge': + edge_map = ed.getEdgeImage(edges) + else: + edge_map = ed.getGradientImage(edges) + edge_map = np.expand_dims(edge_map, axis=2) + edge_map = cv2.cvtColor(edge_map, cv2.COLOR_GRAY2BGR).astype(np.uint8) + edge_map = HWC3(edge_map) + + img = resize_image(input_image, image_resolution) + H, W, _C = img.shape + edge_map = cv2.resize(edge_map, (W, H), interpolation=cv2.INTER_LINEAR) + + if output_type == "pil": + edge_map = Image.fromarray(edge_map) + + return edge_map diff --git a/modules/control/proc/hed.py b/modules/control/proc/hed.py new file mode 100644 index 000000000..9504e627e --- /dev/null +++ b/modules/control/proc/hed.py @@ -0,0 +1,128 @@ +# This is an improved version and model of HED edge detection with Apache License, Version 2.0. +# Please use this implementation in your products +# This implementation may produce slightly different results from Saining Xie's official implementations, +# but it generates smoother edges and is more suitable for ControlNet as well as other image-to-image translations. +# Different from official models and other implementations, this is an RGB-input model (rather than BGR) +# and in this way it works better for gradio's RGB protocol + +import os +import warnings + +import cv2 +import numpy as np +import torch +from einops import rearrange +from huggingface_hub import hf_hub_download +from PIL import Image + +from modules.control.util import HWC3, nms, resize_image, safe_step + + +class DoubleConvBlock(torch.nn.Module): # pylint: disable=abstract-method + def __init__(self, input_channel, output_channel, layer_number): + super().__init__() + self.convs = torch.nn.Sequential() + self.convs.append(torch.nn.Conv2d(in_channels=input_channel, out_channels=output_channel, kernel_size=(3, 3), stride=(1, 1), padding=1)) + for _i in range(1, layer_number): + self.convs.append(torch.nn.Conv2d(in_channels=output_channel, out_channels=output_channel, kernel_size=(3, 3), stride=(1, 1), padding=1)) + self.projection = torch.nn.Conv2d(in_channels=output_channel, out_channels=1, kernel_size=(1, 1), stride=(1, 1), padding=0) + + def __call__(self, x, down_sampling=False): + h = x + if down_sampling: + h = torch.nn.functional.max_pool2d(h, kernel_size=(2, 2), stride=(2, 2)) + for conv in self.convs: + h = conv(h) + h = torch.nn.functional.relu(h) + return h, self.projection(h) + + +class ControlNetHED_Apache2(torch.nn.Module): # pylint: disable=abstract-method + def __init__(self): + super().__init__() + self.norm = torch.nn.Parameter(torch.zeros(size=(1, 3, 1, 1))) + self.block1 = DoubleConvBlock(input_channel=3, output_channel=64, layer_number=2) + self.block2 = DoubleConvBlock(input_channel=64, output_channel=128, layer_number=2) + self.block3 = DoubleConvBlock(input_channel=128, output_channel=256, layer_number=3) + self.block4 = DoubleConvBlock(input_channel=256, output_channel=512, layer_number=3) + self.block5 = DoubleConvBlock(input_channel=512, output_channel=512, layer_number=3) + + def __call__(self, x): + h = x - self.norm + h, projection1 = self.block1(h) + h, projection2 = self.block2(h, down_sampling=True) + h, projection3 = self.block3(h, down_sampling=True) + h, projection4 = self.block4(h, down_sampling=True) + h, projection5 = self.block5(h, down_sampling=True) + return projection1, projection2, projection3, projection4, projection5 + +class HEDdetector: + def __init__(self, netNetwork): + self.netNetwork = netNetwork + + @classmethod + def from_pretrained(cls, pretrained_model_or_path, filename=None, cache_dir=None): + filename = filename or "ControlNetHED.pth" + + if os.path.isdir(pretrained_model_or_path): + model_path = os.path.join(pretrained_model_or_path, filename) + else: + model_path = hf_hub_download(pretrained_model_or_path, filename, cache_dir=cache_dir) + + netNetwork = ControlNetHED_Apache2() + netNetwork.load_state_dict(torch.load(model_path, map_location='cpu')) + netNetwork.float().eval() + + return cls(netNetwork) + + def to(self, device): + self.netNetwork.to(device) + return self + + def __call__(self, input_image, detect_resolution=512, image_resolution=512, safe=False, output_type="pil", scribble=False, **kwargs): + if "return_pil" in kwargs: + warnings.warn("return_pil is deprecated. Use output_type instead.", DeprecationWarning) + output_type = "pil" if kwargs["return_pil"] else "np" + if type(output_type) is bool: + warnings.warn("Passing `True` or `False` to `output_type` is deprecated and will raise an error in future versions") + if output_type: + output_type = "pil" + + device = next(iter(self.netNetwork.parameters())).device + if not isinstance(input_image, np.ndarray): + input_image = np.array(input_image, dtype=np.uint8) + + input_image = HWC3(input_image) + input_image = resize_image(input_image, detect_resolution) + + assert input_image.ndim == 3 + H, W, _C = input_image.shape + image_hed = torch.from_numpy(input_image.copy()).float().to(device) + image_hed = rearrange(image_hed, 'h w c -> 1 c h w') + edges = self.netNetwork(image_hed) + edges = [e.detach().cpu().numpy().astype(np.float32)[0, 0] for e in edges] + edges = [cv2.resize(e, (W, H), interpolation=cv2.INTER_LINEAR) for e in edges] + edges = np.stack(edges, axis=2) + edge = 1 / (1 + np.exp(-np.mean(edges, axis=2).astype(np.float64))) + if safe: + edge = safe_step(edge) + edge = (edge * 255.0).clip(0, 255).astype(np.uint8) + + detected_map = edge + detected_map = HWC3(detected_map) + + img = resize_image(input_image, image_resolution) + H, W, _C = img.shape + + detected_map = cv2.resize(detected_map, (W, H), interpolation=cv2.INTER_LINEAR) + + if scribble: + detected_map = nms(detected_map, 127, 3.0) + detected_map = cv2.GaussianBlur(detected_map, (0, 0), 3.0) + detected_map[detected_map > 4] = 255 + detected_map[detected_map < 255] = 0 + + if output_type == "pil": + detected_map = Image.fromarray(detected_map) + + return detected_map diff --git a/modules/control/proc/leres/__init__.py b/modules/control/proc/leres/__init__.py new file mode 100644 index 000000000..3a62882bd --- /dev/null +++ b/modules/control/proc/leres/__init__.py @@ -0,0 +1,108 @@ +import os + +import cv2 +import numpy as np +import torch +from huggingface_hub import hf_hub_download +from PIL import Image + +from modules.control.util import HWC3, resize_image +from .leres.depthmap import estimateboost, estimateleres +from .leres.multi_depth_model_woauxi import RelDepthModel +from .leres.net_tools import strip_prefix_if_present +from .pix2pix.models.pix2pix4depth_model import Pix2Pix4DepthModel +from .pix2pix.options.test_options import TestOptions + + +class LeresDetector: + def __init__(self, model, pix2pixmodel): + self.model = model + self.pix2pixmodel = pix2pixmodel + + @classmethod + def from_pretrained(cls, pretrained_model_or_path, filename=None, pix2pix_filename=None, cache_dir=None): + filename = filename or "res101.pth" + pix2pix_filename = pix2pix_filename or "latest_net_G.pth" + if os.path.isdir(pretrained_model_or_path): + model_path = os.path.join(pretrained_model_or_path, filename) + else: + model_path = hf_hub_download(pretrained_model_or_path, filename, cache_dir=cache_dir) + checkpoint = torch.load(model_path, map_location=torch.device('cpu')) + model = RelDepthModel(backbone='resnext101') + model.load_state_dict(strip_prefix_if_present(checkpoint['depth_model'], "module."), strict=True) + del checkpoint + if os.path.isdir(pretrained_model_or_path): + model_path = os.path.join(pretrained_model_or_path, pix2pix_filename) + else: + model_path = hf_hub_download(pretrained_model_or_path, pix2pix_filename, cache_dir=cache_dir) + opt = TestOptions().parse() + if not torch.cuda.is_available(): + opt.gpu_ids = [] # cpu mode + pix2pixmodel = Pix2Pix4DepthModel(opt) + pix2pixmodel.save_dir = os.path.dirname(model_path) + pix2pixmodel.load_networks('latest') + pix2pixmodel.eval() + return cls(model, pix2pixmodel) + + def to(self, device): + self.model.to(device) + return self + + def __call__(self, input_image, thr_a=0, thr_b=0, boost=False, detect_resolution=512, image_resolution=512, output_type="pil"): + # device = next(iter(self.model.parameters())).device + if not isinstance(input_image, np.ndarray): + input_image = np.array(input_image, dtype=np.uint8) + + input_image = HWC3(input_image) + input_image = resize_image(input_image, detect_resolution) + + assert input_image.ndim == 3 + height, width, _dim = input_image.shape + + if boost: + depth = estimateboost(input_image, self.model, 0, self.pix2pixmodel, max(width, height)) + else: + depth = estimateleres(input_image, self.model, width, height) + + numbytes=2 + depth_min = depth.min() + depth_max = depth.max() + max_val = (2**(8*numbytes))-1 + + # check output before normalizing and mapping to 16 bit + if depth_max - depth_min > np.finfo("float").eps: + out = max_val * (depth - depth_min) / (depth_max - depth_min) + else: + out = np.zeros(depth.shape) + + # single channel, 16 bit image + depth_image = out.astype("uint16") + + # convert to uint8 + depth_image = cv2.convertScaleAbs(depth_image, alpha=255.0/65535.0) + + # remove near + if thr_a != 0: + thr_a = thr_a/100*255 + depth_image = cv2.threshold(depth_image, thr_a, 255, cv2.THRESH_TOZERO)[1] + + # invert image + depth_image = cv2.bitwise_not(depth_image) + + # remove bg + if thr_b != 0: + thr_b = thr_b/100*255 + depth_image = cv2.threshold(depth_image, thr_b, 255, cv2.THRESH_TOZERO)[1] + + detected_map = depth_image + detected_map = HWC3(detected_map) + + img = resize_image(input_image, image_resolution) + H, W, _C = img.shape + + detected_map = cv2.resize(detected_map, (W, H), interpolation=cv2.INTER_LINEAR) + + if output_type == "pil": + detected_map = Image.fromarray(detected_map) + + return detected_map diff --git a/modules/control/proc/leres/leres/LICENSE b/modules/control/proc/leres/leres/LICENSE new file mode 100644 index 000000000..e0f1d07d9 --- /dev/null +++ b/modules/control/proc/leres/leres/LICENSE @@ -0,0 +1,23 @@ +https://github.com/thygate/stable-diffusion-webui-depthmap-script + +MIT License + +Copyright (c) 2023 Bob Thiry + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. \ No newline at end of file diff --git a/modules/control/proc/leres/leres/Resnet.py b/modules/control/proc/leres/leres/Resnet.py new file mode 100644 index 000000000..c9041b187 --- /dev/null +++ b/modules/control/proc/leres/leres/Resnet.py @@ -0,0 +1,199 @@ +import torch.nn as nn +import torch.nn as NN + +__all__ = ['ResNet', 'resnet18', 'resnet34', 'resnet50', 'resnet101', + 'resnet152'] + + +model_urls = { + 'resnet18': 'https://download.pytorch.org/models/resnet18-5c106cde.pth', + 'resnet34': 'https://download.pytorch.org/models/resnet34-333f7ec4.pth', + 'resnet50': 'https://download.pytorch.org/models/resnet50-19c8e357.pth', + 'resnet101': 'https://download.pytorch.org/models/resnet101-5d3b4d8f.pth', + 'resnet152': 'https://download.pytorch.org/models/resnet152-b121ed2d.pth', +} + + +def conv3x3(in_planes, out_planes, stride=1): + """3x3 convolution with padding""" + return nn.Conv2d(in_planes, out_planes, kernel_size=3, stride=stride, + padding=1, bias=False) + + +class BasicBlock(nn.Module): + expansion = 1 + + def __init__(self, inplanes, planes, stride=1, downsample=None): + super(BasicBlock, self).__init__() + self.conv1 = conv3x3(inplanes, planes, stride) + self.bn1 = NN.BatchNorm2d(planes) #NN.BatchNorm2d + self.relu = nn.ReLU(inplace=True) + self.conv2 = conv3x3(planes, planes) + self.bn2 = NN.BatchNorm2d(planes) #NN.BatchNorm2d + self.downsample = downsample + self.stride = stride + + def forward(self, x): + residual = x + + out = self.conv1(x) + out = self.bn1(out) + out = self.relu(out) + + out = self.conv2(out) + out = self.bn2(out) + + if self.downsample is not None: + residual = self.downsample(x) + + out += residual + out = self.relu(out) + + return out + + +class Bottleneck(nn.Module): + expansion = 4 + + def __init__(self, inplanes, planes, stride=1, downsample=None): + super(Bottleneck, self).__init__() + self.conv1 = nn.Conv2d(inplanes, planes, kernel_size=1, bias=False) + self.bn1 = NN.BatchNorm2d(planes) #NN.BatchNorm2d + self.conv2 = nn.Conv2d(planes, planes, kernel_size=3, stride=stride, + padding=1, bias=False) + self.bn2 = NN.BatchNorm2d(planes) #NN.BatchNorm2d + self.conv3 = nn.Conv2d(planes, planes * self.expansion, kernel_size=1, bias=False) + self.bn3 = NN.BatchNorm2d(planes * self.expansion) #NN.BatchNorm2d + self.relu = nn.ReLU(inplace=True) + self.downsample = downsample + self.stride = stride + + def forward(self, x): + residual = x + + out = self.conv1(x) + out = self.bn1(out) + out = self.relu(out) + + out = self.conv2(out) + out = self.bn2(out) + out = self.relu(out) + + out = self.conv3(out) + out = self.bn3(out) + + if self.downsample is not None: + residual = self.downsample(x) + + out += residual + out = self.relu(out) + + return out + + +class ResNet(nn.Module): + + def __init__(self, block, layers, num_classes=1000): + self.inplanes = 64 + super(ResNet, self).__init__() + self.conv1 = nn.Conv2d(3, 64, kernel_size=7, stride=2, padding=3, + bias=False) + self.bn1 = NN.BatchNorm2d(64) #NN.BatchNorm2d + self.relu = nn.ReLU(inplace=True) + self.maxpool = nn.MaxPool2d(kernel_size=3, stride=2, padding=1) + self.layer1 = self._make_layer(block, 64, layers[0]) + self.layer2 = self._make_layer(block, 128, layers[1], stride=2) + self.layer3 = self._make_layer(block, 256, layers[2], stride=2) + self.layer4 = self._make_layer(block, 512, layers[3], stride=2) + #self.avgpool = nn.AvgPool2d(7, stride=1) + #self.fc = nn.Linear(512 * block.expansion, num_classes) + + for m in self.modules(): + if isinstance(m, nn.Conv2d): + nn.init.kaiming_normal_(m.weight, mode='fan_out', nonlinearity='relu') + elif isinstance(m, nn.BatchNorm2d): + nn.init.constant_(m.weight, 1) + nn.init.constant_(m.bias, 0) + + def _make_layer(self, block, planes, blocks, stride=1): + downsample = None + if stride != 1 or self.inplanes != planes * block.expansion: + downsample = nn.Sequential( + nn.Conv2d(self.inplanes, planes * block.expansion, + kernel_size=1, stride=stride, bias=False), + NN.BatchNorm2d(planes * block.expansion), #NN.BatchNorm2d + ) + + layers = [] + layers.append(block(self.inplanes, planes, stride, downsample)) + self.inplanes = planes * block.expansion + for _i in range(1, blocks): + layers.append(block(self.inplanes, planes)) + + return nn.Sequential(*layers) + + def forward(self, x): + features = [] + + x = self.conv1(x) + x = self.bn1(x) + x = self.relu(x) + x = self.maxpool(x) + + x = self.layer1(x) + features.append(x) + x = self.layer2(x) + features.append(x) + x = self.layer3(x) + features.append(x) + x = self.layer4(x) + features.append(x) + + return features + + +def resnet18(pretrained=True, **kwargs): + """Constructs a ResNet-18 model. + Args: + pretrained (bool): If True, returns a model pre-trained on ImageNet + """ + model = ResNet(BasicBlock, [2, 2, 2, 2], **kwargs) + return model + + +def resnet34(pretrained=True, **kwargs): + """Constructs a ResNet-34 model. + Args: + pretrained (bool): If True, returns a model pre-trained on ImageNet + """ + model = ResNet(BasicBlock, [3, 4, 6, 3], **kwargs) + return model + + +def resnet50(pretrained=True, **kwargs): + """Constructs a ResNet-50 model. + Args: + pretrained (bool): If True, returns a model pre-trained on ImageNet + """ + model = ResNet(Bottleneck, [3, 4, 6, 3], **kwargs) + + return model + + +def resnet101(pretrained=True, **kwargs): + """Constructs a ResNet-101 model. + Args: + pretrained (bool): If True, returns a model pre-trained on ImageNet + """ + model = ResNet(Bottleneck, [3, 4, 23, 3], **kwargs) + + return model + + +def resnet152(pretrained=True, **kwargs): + """Constructs a ResNet-152 model. + Args: + pretrained (bool): If True, returns a model pre-trained on ImageNet + """ + model = ResNet(Bottleneck, [3, 8, 36, 3], **kwargs) + return model diff --git a/modules/control/proc/leres/leres/Resnext_torch.py b/modules/control/proc/leres/leres/Resnext_torch.py new file mode 100644 index 000000000..9af54fcc3 --- /dev/null +++ b/modules/control/proc/leres/leres/Resnext_torch.py @@ -0,0 +1,237 @@ +#!/usr/bin/env python +# coding: utf-8 +import torch.nn as nn + +try: + from urllib import urlretrieve +except ImportError: + from urllib.request import urlretrieve + +__all__ = ['resnext101_32x8d'] + + +model_urls = { + 'resnext50_32x4d': 'https://download.pytorch.org/models/resnext50_32x4d-7cdf4587.pth', + 'resnext101_32x8d': 'https://download.pytorch.org/models/resnext101_32x8d-8ba56ff5.pth', +} + + +def conv3x3(in_planes, out_planes, stride=1, groups=1, dilation=1): + """3x3 convolution with padding""" + return nn.Conv2d(in_planes, out_planes, kernel_size=3, stride=stride, + padding=dilation, groups=groups, bias=False, dilation=dilation) + + +def conv1x1(in_planes, out_planes, stride=1): + """1x1 convolution""" + return nn.Conv2d(in_planes, out_planes, kernel_size=1, stride=stride, bias=False) + + +class BasicBlock(nn.Module): + expansion = 1 + + def __init__(self, inplanes, planes, stride=1, downsample=None, groups=1, + base_width=64, dilation=1, norm_layer=None): + super(BasicBlock, self).__init__() + if norm_layer is None: + norm_layer = nn.BatchNorm2d + if groups != 1 or base_width != 64: + raise ValueError('BasicBlock only supports groups=1 and base_width=64') + if dilation > 1: + raise NotImplementedError("Dilation > 1 not supported in BasicBlock") + # Both self.conv1 and self.downsample layers downsample the input when stride != 1 + self.conv1 = conv3x3(inplanes, planes, stride) + self.bn1 = norm_layer(planes) + self.relu = nn.ReLU(inplace=True) + self.conv2 = conv3x3(planes, planes) + self.bn2 = norm_layer(planes) + self.downsample = downsample + self.stride = stride + + def forward(self, x): + identity = x + + out = self.conv1(x) + out = self.bn1(out) + out = self.relu(out) + + out = self.conv2(out) + out = self.bn2(out) + + if self.downsample is not None: + identity = self.downsample(x) + + out += identity + out = self.relu(out) + + return out + + +class Bottleneck(nn.Module): + # Bottleneck in torchvision places the stride for downsampling at 3x3 convolution(self.conv2) + # while original implementation places the stride at the first 1x1 convolution(self.conv1) + # according to "Deep residual learning for image recognition"https://arxiv.org/abs/1512.03385. + # This variant is also known as ResNet V1.5 and improves accuracy according to + # https://ngc.nvidia.com/catalog/model-scripts/nvidia:resnet_50_v1_5_for_pytorch. + + expansion = 4 + + def __init__(self, inplanes, planes, stride=1, downsample=None, groups=1, + base_width=64, dilation=1, norm_layer=None): + super(Bottleneck, self).__init__() + if norm_layer is None: + norm_layer = nn.BatchNorm2d + width = int(planes * (base_width / 64.)) * groups + # Both self.conv2 and self.downsample layers downsample the input when stride != 1 + self.conv1 = conv1x1(inplanes, width) + self.bn1 = norm_layer(width) + self.conv2 = conv3x3(width, width, stride, groups, dilation) + self.bn2 = norm_layer(width) + self.conv3 = conv1x1(width, planes * self.expansion) + self.bn3 = norm_layer(planes * self.expansion) + self.relu = nn.ReLU(inplace=True) + self.downsample = downsample + self.stride = stride + + def forward(self, x): + identity = x + + out = self.conv1(x) + out = self.bn1(out) + out = self.relu(out) + + out = self.conv2(out) + out = self.bn2(out) + out = self.relu(out) + + out = self.conv3(out) + out = self.bn3(out) + + if self.downsample is not None: + identity = self.downsample(x) + + out += identity + out = self.relu(out) + + return out + + +class ResNet(nn.Module): + + def __init__(self, block, layers, num_classes=1000, zero_init_residual=False, + groups=1, width_per_group=64, replace_stride_with_dilation=None, + norm_layer=None): + super(ResNet, self).__init__() + if norm_layer is None: + norm_layer = nn.BatchNorm2d + self._norm_layer = norm_layer + + self.inplanes = 64 + self.dilation = 1 + if replace_stride_with_dilation is None: + # each element in the tuple indicates if we should replace + # the 2x2 stride with a dilated convolution instead + replace_stride_with_dilation = [False, False, False] + if len(replace_stride_with_dilation) != 3: + raise ValueError("replace_stride_with_dilation should be None " + "or a 3-element tuple, got {}".format(replace_stride_with_dilation)) + self.groups = groups + self.base_width = width_per_group + self.conv1 = nn.Conv2d(3, self.inplanes, kernel_size=7, stride=2, padding=3, + bias=False) + self.bn1 = norm_layer(self.inplanes) + self.relu = nn.ReLU(inplace=True) + self.maxpool = nn.MaxPool2d(kernel_size=3, stride=2, padding=1) + self.layer1 = self._make_layer(block, 64, layers[0]) + self.layer2 = self._make_layer(block, 128, layers[1], stride=2, + dilate=replace_stride_with_dilation[0]) + self.layer3 = self._make_layer(block, 256, layers[2], stride=2, + dilate=replace_stride_with_dilation[1]) + self.layer4 = self._make_layer(block, 512, layers[3], stride=2, + dilate=replace_stride_with_dilation[2]) + #self.avgpool = nn.AdaptiveAvgPool2d((1, 1)) + #self.fc = nn.Linear(512 * block.expansion, num_classes) + + for m in self.modules(): + if isinstance(m, nn.Conv2d): + nn.init.kaiming_normal_(m.weight, mode='fan_out', nonlinearity='relu') + elif isinstance(m, (nn.BatchNorm2d, nn.GroupNorm)): + nn.init.constant_(m.weight, 1) + nn.init.constant_(m.bias, 0) + + # Zero-initialize the last BN in each residual branch, + # so that the residual branch starts with zeros, and each residual block behaves like an identity. + # This improves the model by 0.2~0.3% according to https://arxiv.org/abs/1706.02677 + if zero_init_residual: + for m in self.modules(): + if isinstance(m, Bottleneck): + nn.init.constant_(m.bn3.weight, 0) + elif isinstance(m, BasicBlock): + nn.init.constant_(m.bn2.weight, 0) + + def _make_layer(self, block, planes, blocks, stride=1, dilate=False): + norm_layer = self._norm_layer + downsample = None + previous_dilation = self.dilation + if dilate: + self.dilation *= stride + stride = 1 + if stride != 1 or self.inplanes != planes * block.expansion: + downsample = nn.Sequential( + conv1x1(self.inplanes, planes * block.expansion, stride), + norm_layer(planes * block.expansion), + ) + + layers = [] + layers.append(block(self.inplanes, planes, stride, downsample, self.groups, + self.base_width, previous_dilation, norm_layer)) + self.inplanes = planes * block.expansion + for _ in range(1, blocks): + layers.append(block(self.inplanes, planes, groups=self.groups, + base_width=self.base_width, dilation=self.dilation, + norm_layer=norm_layer)) + + return nn.Sequential(*layers) + + def _forward_impl(self, x): + # See note [TorchScript super()] + features = [] + x = self.conv1(x) + x = self.bn1(x) + x = self.relu(x) + x = self.maxpool(x) + + x = self.layer1(x) + features.append(x) + + x = self.layer2(x) + features.append(x) + + x = self.layer3(x) + features.append(x) + + x = self.layer4(x) + features.append(x) + + #x = self.avgpool(x) + #x = torch.flatten(x, 1) + #x = self.fc(x) + + return features + + def forward(self, x): + return self._forward_impl(x) + + + +def resnext101_32x8d(pretrained=True, **kwargs): + """Constructs a ResNet-152 model. + Args: + pretrained (bool): If True, returns a model pre-trained on ImageNet + """ + kwargs['groups'] = 32 + kwargs['width_per_group'] = 8 + + model = ResNet(Bottleneck, [3, 4, 23, 3], **kwargs) + return model + diff --git a/modules/control/proc/leres/leres/__init__.py b/modules/control/proc/leres/leres/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/modules/control/proc/leres/leres/depthmap.py b/modules/control/proc/leres/leres/depthmap.py new file mode 100644 index 000000000..d89ac7e7a --- /dev/null +++ b/modules/control/proc/leres/leres/depthmap.py @@ -0,0 +1,546 @@ +# Author: thygate +# https://github.com/thygate/stable-diffusion-webui-depthmap-script + +import gc +from operator import getitem + +import cv2 +import numpy as np +import skimage.measure +import torch +from torchvision.transforms import transforms + +from modules.control.util import torch_gc + +whole_size_threshold = 1600 # R_max from the paper +pix2pixsize = 1024 + +def scale_torch(img): + """ + Scale the image and output it in torch.tensor. + :param img: input rgb is in shape [H, W, C], input depth/disp is in shape [H, W] + :param scale: the scale factor. float + :return: img. [C, H, W] + """ + if len(img.shape) == 2: + img = img[np.newaxis, :, :] + if img.shape[2] == 3: + transform = transforms.Compose([transforms.ToTensor(), transforms.Normalize((0.485, 0.456, 0.406) , (0.229, 0.224, 0.225) )]) + img = transform(img.astype(np.float32)) + else: + img = img.astype(np.float32) + img = torch.from_numpy(img) + return img + +def estimateleres(img, model, w, h): + device = next(iter(model.parameters())).device + # leres transform input + rgb_c = img[:, :, ::-1].copy() + A_resize = cv2.resize(rgb_c, (w, h)) + img_torch = scale_torch(A_resize)[None, :, :, :] + + # compute + img_torch = img_torch.to(device) + prediction = model.depth_model(img_torch) + + prediction = prediction.squeeze().cpu().numpy() + prediction = cv2.resize(prediction, (img.shape[1], img.shape[0]), interpolation=cv2.INTER_CUBIC) + + return prediction + +def generatemask(size): + # Generates a Guassian mask + mask = np.zeros(size, dtype=np.float32) + sigma = int(size[0]/16) + k_size = int(2 * np.ceil(2 * int(size[0]/16)) + 1) + mask[int(0.15*size[0]):size[0] - int(0.15*size[0]), int(0.15*size[1]): size[1] - int(0.15*size[1])] = 1 + mask = cv2.GaussianBlur(mask, (int(k_size), int(k_size)), sigma) + mask = (mask - mask.min()) / (mask.max() - mask.min()) + mask = mask.astype(np.float32) + return mask + +def resizewithpool(img, size): + i_size = img.shape[0] + n = int(np.floor(i_size/size)) + + out = skimage.measure.block_reduce(img, (n, n), np.max) + return out + +def rgb2gray(rgb): + # Converts rgb to gray + return np.dot(rgb[..., :3], [0.2989, 0.5870, 0.1140]) + +def calculateprocessingres(img, basesize, confidence=0.1, scale_threshold=3, whole_size_threshold=3000): + # Returns the R_x resolution described in section 5 of the main paper. + + # Parameters: + # img :input rgb image + # basesize : size the dilation kernel which is equal to receptive field of the network. + # confidence: value of x in R_x; allowed percentage of pixels that are not getting any contextual cue. + # scale_threshold: maximum allowed upscaling on the input image ; it has been set to 3. + # whole_size_threshold: maximum allowed resolution. (R_max from section 6 of the main paper) + + # Returns: + # outputsize_scale*speed_scale :The computed R_x resolution + # patch_scale: K parameter from section 6 of the paper + + # speed scale parameter is to process every image in a smaller size to accelerate the R_x resolution search + speed_scale = 32 + image_dim = int(min(img.shape[0:2])) + + gray = rgb2gray(img) + grad = np.abs(cv2.Sobel(gray, cv2.CV_64F, 0, 1, ksize=3)) + np.abs(cv2.Sobel(gray, cv2.CV_64F, 1, 0, ksize=3)) + grad = cv2.resize(grad, (image_dim, image_dim), cv2.INTER_AREA) + + # thresholding the gradient map to generate the edge-map as a proxy of the contextual cues + m = grad.min() + M = grad.max() + middle = m + (0.4 * (M - m)) + grad[grad < middle] = 0 + grad[grad >= middle] = 1 + + # dilation kernel with size of the receptive field + kernel = np.ones((int(basesize/speed_scale), int(basesize/speed_scale)), float) + # dilation kernel with size of the a quarter of receptive field used to compute k + # as described in section 6 of main paper + kernel2 = np.ones((int(basesize / (4*speed_scale)), int(basesize / (4*speed_scale))), float) + + # Output resolution limit set by the whole_size_threshold and scale_threshold. + threshold = min(whole_size_threshold, scale_threshold * max(img.shape[:2])) + + outputsize_scale = basesize / speed_scale + for p_size in range(int(basesize/speed_scale), int(threshold/speed_scale), int(basesize / (2*speed_scale))): + grad_resized = resizewithpool(grad, p_size) + grad_resized = cv2.resize(grad_resized, (p_size, p_size), cv2.INTER_NEAREST) + grad_resized[grad_resized >= 0.5] = 1 + grad_resized[grad_resized < 0.5] = 0 + + dilated = cv2.dilate(grad_resized, kernel, iterations=1) + meanvalue = (1-dilated).mean() + if meanvalue > confidence: + break + else: + outputsize_scale = p_size + + grad_region = cv2.dilate(grad_resized, kernel2, iterations=1) + patch_scale = grad_region.mean() + + return int(outputsize_scale*speed_scale), patch_scale + +# Generate a double-input depth estimation +def doubleestimate(img, size1, size2, pix2pixsize, model, net_type, pix2pixmodel): + # Generate the low resolution estimation + estimate1 = singleestimate(img, size1, model, net_type) + # Resize to the inference size of merge network. + estimate1 = cv2.resize(estimate1, (pix2pixsize, pix2pixsize), interpolation=cv2.INTER_CUBIC) + + # Generate the high resolution estimation + estimate2 = singleestimate(img, size2, model, net_type) + # Resize to the inference size of merge network. + estimate2 = cv2.resize(estimate2, (pix2pixsize, pix2pixsize), interpolation=cv2.INTER_CUBIC) + + # Inference on the merge model + pix2pixmodel.set_input(estimate1, estimate2) + pix2pixmodel.test() + visuals = pix2pixmodel.get_current_visuals() + prediction_mapped = visuals['fake_B'] + prediction_mapped = (prediction_mapped+1)/2 + prediction_mapped = (prediction_mapped - torch.min(prediction_mapped)) / ( + torch.max(prediction_mapped) - torch.min(prediction_mapped)) + prediction_mapped = prediction_mapped.squeeze().cpu().numpy() + + return prediction_mapped + +# Generate a single-input depth estimation +def singleestimate(img, msize, model, net_type): + # if net_type == 0: + return estimateleres(img, model, msize, msize) + # else: + # return estimatemidasBoost(img, model, msize, msize) + +def applyGridpatch(blsize, stride, img, box): + # Extract a simple grid patch. + counter1 = 0 + patch_bound_list = {} + for k in range(blsize, img.shape[1] - blsize, stride): + for j in range(blsize, img.shape[0] - blsize, stride): + patch_bound_list[str(counter1)] = {} + patchbounds = [j - blsize, k - blsize, j - blsize + 2 * blsize, k - blsize + 2 * blsize] + patch_bound = [box[0] + patchbounds[1], box[1] + patchbounds[0], patchbounds[3] - patchbounds[1], + patchbounds[2] - patchbounds[0]] + patch_bound_list[str(counter1)]['rect'] = patch_bound + patch_bound_list[str(counter1)]['size'] = patch_bound[2] + counter1 = counter1 + 1 + return patch_bound_list + +# Generating local patches to perform the local refinement described in section 6 of the main paper. +def generatepatchs(img, base_size): + + # Compute the gradients as a proxy of the contextual cues. + img_gray = rgb2gray(img) + whole_grad = np.abs(cv2.Sobel(img_gray, cv2.CV_64F, 0, 1, ksize=3)) +\ + np.abs(cv2.Sobel(img_gray, cv2.CV_64F, 1, 0, ksize=3)) + + threshold = whole_grad[whole_grad > 0].mean() + whole_grad[whole_grad < threshold] = 0 + + # We use the integral image to speed-up the evaluation of the amount of gradients for each patch. + gf = whole_grad.sum()/len(whole_grad.reshape(-1)) + grad_integral_image = cv2.integral(whole_grad) + + # Variables are selected such that the initial patch size would be the receptive field size + # and the stride is set to 1/3 of the receptive field size. + blsize = int(round(base_size/2)) + stride = int(round(blsize*0.75)) + + # Get initial Grid + patch_bound_list = applyGridpatch(blsize, stride, img, [0, 0, 0, 0]) + + # Refine initial Grid of patches by discarding the flat (in terms of gradients of the rgb image) ones. Refine + # each patch size to ensure that there will be enough depth cues for the network to generate a consistent depth map. + patch_bound_list = adaptiveselection(grad_integral_image, patch_bound_list, gf) + + # Sort the patch list to make sure the merging operation will be done with the correct order: starting from biggest + # patch + patchset = sorted(patch_bound_list.items(), key=lambda x: getitem(x[1], 'size'), reverse=True) + return patchset + +def getGF_fromintegral(integralimage, rect): + # Computes the gradient density of a given patch from the gradient integral image. + x1 = rect[1] + x2 = rect[1]+rect[3] + y1 = rect[0] + y2 = rect[0]+rect[2] + value = integralimage[x2, y2]-integralimage[x1, y2]-integralimage[x2, y1]+integralimage[x1, y1] + return value + +# Adaptively select patches +def adaptiveselection(integral_grad, patch_bound_list, gf): + patchlist = {} + count = 0 + height, width = integral_grad.shape + + search_step = int(32/factor) + + # Go through all patches + for c in range(len(patch_bound_list)): + # Get patch + bbox = patch_bound_list[str(c)]['rect'] + + # Compute the amount of gradients present in the patch from the integral image. + cgf = getGF_fromintegral(integral_grad, bbox)/(bbox[2]*bbox[3]) + + # Check if patching is beneficial by comparing the gradient density of the patch to + # the gradient density of the whole image + if cgf >= gf: + bbox_test = bbox.copy() + patchlist[str(count)] = {} + + # Enlarge each patch until the gradient density of the patch is equal + # to the whole image gradient density + while True: + + bbox_test[0] = bbox_test[0] - int(search_step/2) + bbox_test[1] = bbox_test[1] - int(search_step/2) + + bbox_test[2] = bbox_test[2] + search_step + bbox_test[3] = bbox_test[3] + search_step + + # Check if we are still within the image + if bbox_test[0] < 0 or bbox_test[1] < 0 or bbox_test[1] + bbox_test[3] >= height \ + or bbox_test[0] + bbox_test[2] >= width: + break + + # Compare gradient density + cgf = getGF_fromintegral(integral_grad, bbox_test)/(bbox_test[2]*bbox_test[3]) + if cgf < gf: + break + bbox = bbox_test.copy() + + # Add patch to selected patches + patchlist[str(count)]['rect'] = bbox + patchlist[str(count)]['size'] = bbox[2] + count = count + 1 + + # Return selected patches + return patchlist + +def impatch(image, rect): + # Extract the given patch pixels from a given image. + w1 = rect[0] + h1 = rect[1] + w2 = w1 + rect[2] + h2 = h1 + rect[3] + image_patch = image[h1:h2, w1:w2] + return image_patch + +class ImageandPatchs: + def __init__(self, root_dir, name, patchsinfo, rgb_image, scale=1): + self.root_dir = root_dir + self.patchsinfo = patchsinfo + self.name = name + self.patchs = patchsinfo + self.scale = scale + + self.rgb_image = cv2.resize(rgb_image, (round(rgb_image.shape[1]*scale), round(rgb_image.shape[0]*scale)), + interpolation=cv2.INTER_CUBIC) + + self.do_have_estimate = False + self.estimation_updated_image = None + self.estimation_base_image = None + + def __len__(self): + return len(self.patchs) + + def set_base_estimate(self, est): + self.estimation_base_image = est + if self.estimation_updated_image is not None: + self.do_have_estimate = True + + def set_updated_estimate(self, est): + self.estimation_updated_image = est + if self.estimation_base_image is not None: + self.do_have_estimate = True + + def __getitem__(self, index): + patch_id = int(self.patchs[index][0]) + rect = np.array(self.patchs[index][1]['rect']) + msize = self.patchs[index][1]['size'] + + ## applying scale to rect: + rect = np.round(rect * self.scale) + rect = rect.astype('int') + msize = round(msize * self.scale) + + patch_rgb = impatch(self.rgb_image, rect) + if self.do_have_estimate: + patch_whole_estimate_base = impatch(self.estimation_base_image, rect) + patch_whole_estimate_updated = impatch(self.estimation_updated_image, rect) + return {'patch_rgb': patch_rgb, 'patch_whole_estimate_base': patch_whole_estimate_base, + 'patch_whole_estimate_updated': patch_whole_estimate_updated, 'rect': rect, + 'size': msize, 'id': patch_id} + else: + return {'patch_rgb': patch_rgb, 'rect': rect, 'size': msize, 'id': patch_id} + + def print_options(self, opt): + """Print and save options + + It will print both current options and default values(if different). + It will save options into a text file / [checkpoints_dir] / opt.txt + """ + message = '' + message += '----------------- Options ---------------\n' + for k, v in sorted(vars(opt).items()): + comment = '' + default = self.parser.get_default(k) + if v != default: + comment = '\t[default: %s]' % str(default) + message += '{:>25}: {:<30}{}\n'.format(str(k), str(v), comment) + message += '----------------- End -------------------' + print(message) + + # save to the disk + """ + expr_dir = os.path.join(opt.checkpoints_dir, opt.name) + util.mkdirs(expr_dir) + file_name = os.path.join(expr_dir, '{}_opt.txt'.format(opt.phase)) + with open(file_name, 'wt') as opt_file: + opt_file.write(message) + opt_file.write('\n') + """ + + def parse(self): + """Parse our options, create checkpoints directory suffix, and set up gpu device.""" + opt = self.gather_options() + opt.isTrain = self.isTrain # train or test + + # process opt.suffix + if opt.suffix: + suffix = ('_' + opt.suffix.format(**vars(opt))) if opt.suffix != '' else '' + opt.name = opt.name + suffix + + #self.print_options(opt) + + # set gpu ids + str_ids = opt.gpu_ids.split(',') + opt.gpu_ids = [] + for str_id in str_ids: + id = int(str_id) + if id >= 0: + opt.gpu_ids.append(id) + #if len(opt.gpu_ids) > 0: + # torch.cuda.set_device(opt.gpu_ids[0]) + + self.opt = opt + return self.opt + + +def estimateboost(img, model, model_type, pix2pixmodel, max_res=512, depthmap_script_boost_rmax=None): + global whole_size_threshold + + # get settings + if depthmap_script_boost_rmax: + whole_size_threshold = depthmap_script_boost_rmax + + if model_type == 0: #leres + net_receptive_field_size = 448 + patch_netsize = 2 * net_receptive_field_size + elif model_type == 1: #dpt_beit_large_512 + net_receptive_field_size = 512 + patch_netsize = 2 * net_receptive_field_size + else: #other midas + net_receptive_field_size = 384 + patch_netsize = 2 * net_receptive_field_size + + gc.collect() + torch_gc() + + # Generate mask used to smoothly blend the local pathc estimations to the base estimate. + # It is arbitrarily large to avoid artifacts during rescaling for each crop. + mask_org = generatemask((3000, 3000)) + mask = mask_org.copy() + + # Value x of R_x defined in the section 5 of the main paper. + r_threshold_value = 0.2 + #if R0: + # r_threshold_value = 0 + + input_resolution = img.shape + scale_threshold = 3 # Allows up-scaling with a scale up to 3 + + # Find the best input resolution R-x. The resolution search described in section 5-double estimation of the main paper and section B of the + # supplementary material. + whole_image_optimal_size, patch_scale = calculateprocessingres(img, net_receptive_field_size, r_threshold_value, scale_threshold, whole_size_threshold) + + # print('wholeImage being processed in :', whole_image_optimal_size) + + # Generate the base estimate using the double estimation. + whole_estimate = doubleestimate(img, net_receptive_field_size, whole_image_optimal_size, pix2pixsize, model, model_type, pix2pixmodel) + + # Compute the multiplier described in section 6 of the main paper to make sure our initial patch can select + # small high-density regions of the image. + global factor + factor = max(min(1, 4 * patch_scale * whole_image_optimal_size / whole_size_threshold), 0.2) + # print('Adjust factor is:', 1/factor) + + # Check if Local boosting is beneficial. + if max_res < whole_image_optimal_size: + # print("No Local boosting. Specified Max Res is smaller than R20, Returning doubleestimate result") + return cv2.resize(whole_estimate, (input_resolution[1], input_resolution[0]), interpolation=cv2.INTER_CUBIC) + + # Compute the default target resolution. + if img.shape[0] > img.shape[1]: + a = 2 * whole_image_optimal_size + b = round(2 * whole_image_optimal_size * img.shape[1] / img.shape[0]) + else: + a = round(2 * whole_image_optimal_size * img.shape[0] / img.shape[1]) + b = 2 * whole_image_optimal_size + b = int(round(b / factor)) + a = int(round(a / factor)) + + """ + # recompute a, b and saturate to max res. + if max(a,b) > max_res: + print('Default Res is higher than max-res: Reducing final resolution') + if img.shape[0] > img.shape[1]: + a = max_res + b = round(max_res * img.shape[1] / img.shape[0]) + else: + a = round(max_res * img.shape[0] / img.shape[1]) + b = max_res + b = int(b) + a = int(a) + """ + + img = cv2.resize(img, (b, a), interpolation=cv2.INTER_CUBIC) + + # Extract selected patches for local refinement + base_size = net_receptive_field_size * 2 + patchset = generatepatchs(img, base_size) + + # print('Target resolution: ', img.shape) + + # Computing a scale in case user prompted to generate the results as the same resolution of the input. + # Notice that our method output resolution is independent of the input resolution and this parameter will only + # enable a scaling operation during the local patch merge implementation to generate results with the same resolution + # as the input. + """ + if output_resolution == 1: + mergein_scale = input_resolution[0] / img.shape[0] + print('Dynamicly change merged-in resolution; scale:', mergein_scale) + else: + mergein_scale = 1 + """ + # always rescale to input res for now + mergein_scale = input_resolution[0] / img.shape[0] + + imageandpatchs = ImageandPatchs('', '', patchset, img, mergein_scale) + whole_estimate_resized = cv2.resize(whole_estimate, (round(img.shape[1]*mergein_scale), + round(img.shape[0]*mergein_scale)), interpolation=cv2.INTER_CUBIC) + imageandpatchs.set_base_estimate(whole_estimate_resized.copy()) + imageandpatchs.set_updated_estimate(whole_estimate_resized.copy()) + + print('Resulting depthmap resolution will be :', whole_estimate_resized.shape[:2]) + print('Patches to process: '+str(len(imageandpatchs))) + + # Enumerate through all patches, generate their estimations and refining the base estimate. + for patch_ind in range(len(imageandpatchs)): + + # Get patch information + patch = imageandpatchs[patch_ind] # patch object + patch_rgb = patch['patch_rgb'] # rgb patch + patch_whole_estimate_base = patch['patch_whole_estimate_base'] # corresponding patch from base + rect = patch['rect'] # patch size and location + patch['id'] # patch ID + org_size = patch_whole_estimate_base.shape # the original size from the unscaled input + print('\t Processing patch', patch_ind, '/', len(imageandpatchs)-1, '|', rect) + + # We apply double estimation for patches. The high resolution value is fixed to twice the receptive + # field size of the network for patches to accelerate the process. + patch_estimation = doubleestimate(patch_rgb, net_receptive_field_size, patch_netsize, pix2pixsize, model, model_type, pix2pixmodel) + patch_estimation = cv2.resize(patch_estimation, (pix2pixsize, pix2pixsize), interpolation=cv2.INTER_CUBIC) + patch_whole_estimate_base = cv2.resize(patch_whole_estimate_base, (pix2pixsize, pix2pixsize), interpolation=cv2.INTER_CUBIC) + + # Merging the patch estimation into the base estimate using our merge network: + # We feed the patch estimation and the same region from the updated base estimate to the merge network + # to generate the target estimate for the corresponding region. + pix2pixmodel.set_input(patch_whole_estimate_base, patch_estimation) + + # Run merging network + pix2pixmodel.test() + visuals = pix2pixmodel.get_current_visuals() + + prediction_mapped = visuals['fake_B'] + prediction_mapped = (prediction_mapped+1)/2 + prediction_mapped = prediction_mapped.squeeze().cpu().numpy() + + mapped = prediction_mapped + + # We use a simple linear polynomial to make sure the result of the merge network would match the values of + # base estimate + p_coef = np.polyfit(mapped.reshape(-1), patch_whole_estimate_base.reshape(-1), deg=1) + merged = np.polyval(p_coef, mapped.reshape(-1)).reshape(mapped.shape) + + merged = cv2.resize(merged, (org_size[1],org_size[0]), interpolation=cv2.INTER_CUBIC) + + # Get patch size and location + w1 = rect[0] + h1 = rect[1] + w2 = w1 + rect[2] + h2 = h1 + rect[3] + + # To speed up the implementation, we only generate the Gaussian mask once with a sufficiently large size + # and resize it to our needed size while merging the patches. + if mask.shape != org_size: + mask = cv2.resize(mask_org, (org_size[1],org_size[0]), interpolation=cv2.INTER_LINEAR) + + tobemergedto = imageandpatchs.estimation_updated_image + + # Update the whole estimation: + # We use a simple Gaussian mask to blend the merged patch region with the base estimate to ensure seamless + # blending at the boundaries of the patch region. + tobemergedto[h1:h2, w1:w2] = np.multiply(tobemergedto[h1:h2, w1:w2], 1 - mask) + np.multiply(merged, mask) + imageandpatchs.set_updated_estimate(tobemergedto) + + # output + return cv2.resize(imageandpatchs.estimation_updated_image, (input_resolution[1], input_resolution[0]), interpolation=cv2.INTER_CUBIC) diff --git a/modules/control/proc/leres/leres/multi_depth_model_woauxi.py b/modules/control/proc/leres/leres/multi_depth_model_woauxi.py new file mode 100644 index 000000000..c1266bef1 --- /dev/null +++ b/modules/control/proc/leres/leres/multi_depth_model_woauxi.py @@ -0,0 +1,34 @@ +import torch +import torch.nn as nn + +from . import network_auxi as network +from .net_tools import get_func + + +class RelDepthModel(nn.Module): + def __init__(self, backbone='resnet50'): + super(RelDepthModel, self).__init__() + if backbone == 'resnet50': + encoder = 'resnet50_stride32' + elif backbone == 'resnext101': + encoder = 'resnext101_stride32x8d' + self.depth_model = DepthModel(encoder) + + def inference(self, rgb): + input = rgb.to(self.depth_model.device) + depth = self.depth_model(input) + #pred_depth_out = depth - depth.min() + 0.01 + return depth #pred_depth_out + + +class DepthModel(nn.Module): + def __init__(self, encoder): + super(DepthModel, self).__init__() + backbone = network.__name__.split('.')[-1] + '.' + encoder + self.encoder_modules = get_func(backbone)() + self.decoder_modules = network.Decoder() + + def forward(self, x): + lateral_out = self.encoder_modules(x) + out_logit = self.decoder_modules(lateral_out) + return out_logit diff --git a/modules/control/proc/leres/leres/net_tools.py b/modules/control/proc/leres/leres/net_tools.py new file mode 100644 index 000000000..8d2340000 --- /dev/null +++ b/modules/control/proc/leres/leres/net_tools.py @@ -0,0 +1,54 @@ +import os +from collections import OrderedDict +import importlib +import torch + + +def get_func(func_name): + """Helper to return a function object by name. func_name must identify a + function in this module or the path to a function relative to the base + 'modeling' module. + """ + if func_name == '': + return None + try: + parts = func_name.split('.') + # Refers to a function in this module + if len(parts) == 1: + return globals()[parts[0]] + # Otherwise, assume we're referencing a module under modeling + module_name = 'modules.control.proc.leres.leres.' + '.'.join(parts[:-1]) + module = importlib.import_module(module_name) + return getattr(module, parts[-1]) + except Exception: + print('Failed to find function: %s', func_name) + raise + +def load_ckpt(args, depth_model, shift_model, focal_model): + """ + Load checkpoint. + """ + if os.path.isfile(args.load_ckpt): + print("loading checkpoint %s" % args.load_ckpt) + checkpoint = torch.load(args.load_ckpt) + if shift_model is not None: + shift_model.load_state_dict(strip_prefix_if_present(checkpoint['shift_model'], 'module.'), + strict=True) + if focal_model is not None: + focal_model.load_state_dict(strip_prefix_if_present(checkpoint['focal_model'], 'module.'), + strict=True) + depth_model.load_state_dict(strip_prefix_if_present(checkpoint['depth_model'], "module."), + strict=True) + del checkpoint + if torch.cuda.is_available(): + torch.cuda.empty_cache() + + +def strip_prefix_if_present(state_dict, prefix): + keys = sorted(state_dict.keys()) + if not all(key.startswith(prefix) for key in keys): + return state_dict + stripped_state_dict = OrderedDict() + for key, value in state_dict.items(): + stripped_state_dict[key.replace(prefix, "")] = value + return stripped_state_dict diff --git a/modules/control/proc/leres/leres/network_auxi.py b/modules/control/proc/leres/leres/network_auxi.py new file mode 100644 index 000000000..44d96423e --- /dev/null +++ b/modules/control/proc/leres/leres/network_auxi.py @@ -0,0 +1,419 @@ +import torch +import torch.nn as nn +import torch.nn.init as init + +from . import Resnet, Resnext_torch + + +def resnet50_stride32(): + return DepthNet(backbone='resnet', depth=50, upfactors=[2, 2, 2, 2]) + +def resnext101_stride32x8d(): + return DepthNet(backbone='resnext101_32x8d', depth=101, upfactors=[2, 2, 2, 2]) + + +class Decoder(nn.Module): + def __init__(self): + super(Decoder, self).__init__() + self.inchannels = [256, 512, 1024, 2048] + self.midchannels = [256, 256, 256, 512] + self.upfactors = [2,2,2,2] + self.outchannels = 1 + + self.conv = FTB(inchannels=self.inchannels[3], midchannels=self.midchannels[3]) + self.conv1 = nn.Conv2d(in_channels=self.midchannels[3], out_channels=self.midchannels[2], kernel_size=3, padding=1, stride=1, bias=True) + self.upsample = nn.Upsample(scale_factor=self.upfactors[3], mode='bilinear', align_corners=True) + + self.ffm2 = FFM(inchannels=self.inchannels[2], midchannels=self.midchannels[2], outchannels = self.midchannels[2], upfactor=self.upfactors[2]) + self.ffm1 = FFM(inchannels=self.inchannels[1], midchannels=self.midchannels[1], outchannels = self.midchannels[1], upfactor=self.upfactors[1]) + self.ffm0 = FFM(inchannels=self.inchannels[0], midchannels=self.midchannels[0], outchannels = self.midchannels[0], upfactor=self.upfactors[0]) + + self.outconv = AO(inchannels=self.midchannels[0], outchannels=self.outchannels, upfactor=2) + self._init_params() + + def _init_params(self): + for m in self.modules(): + if isinstance(m, nn.Conv2d): + init.normal_(m.weight, std=0.01) + if m.bias is not None: + init.constant_(m.bias, 0) + elif isinstance(m, nn.ConvTranspose2d): + init.normal_(m.weight, std=0.01) + if m.bias is not None: + init.constant_(m.bias, 0) + elif isinstance(m, nn.BatchNorm2d): #NN.BatchNorm2d + init.constant_(m.weight, 1) + init.constant_(m.bias, 0) + elif isinstance(m, nn.Linear): + init.normal_(m.weight, std=0.01) + if m.bias is not None: + init.constant_(m.bias, 0) + + def forward(self, features): + x_32x = self.conv(features[3]) # 1/32 + x_32 = self.conv1(x_32x) + x_16 = self.upsample(x_32) # 1/16 + + x_8 = self.ffm2(features[2], x_16) # 1/8 + x_4 = self.ffm1(features[1], x_8) # 1/4 + x_2 = self.ffm0(features[0], x_4) # 1/2 + #----------------------------------------- + x = self.outconv(x_2) # original size + return x + +class DepthNet(nn.Module): + __factory = { + 18: Resnet.resnet18, + 34: Resnet.resnet34, + 50: Resnet.resnet50, + 101: Resnet.resnet101, + 152: Resnet.resnet152 + } + def __init__(self, + backbone='resnet', + depth=50, + upfactors=None): + if upfactors is None: + upfactors = [2, 2, 2, 2] + super(DepthNet, self).__init__() + self.backbone = backbone + self.depth = depth + self.pretrained = False + self.inchannels = [256, 512, 1024, 2048] + self.midchannels = [256, 256, 256, 512] + self.upfactors = upfactors + self.outchannels = 1 + + # Build model + if self.backbone == 'resnet': + if self.depth not in DepthNet.__factory: + raise KeyError("Unsupported depth:", self.depth) + self.encoder = DepthNet.__factory[depth](pretrained=self.pretrained) + elif self.backbone == 'resnext101_32x8d': + self.encoder = Resnext_torch.resnext101_32x8d(pretrained=self.pretrained) + else: + self.encoder = Resnext_torch.resnext101(pretrained=self.pretrained) + + def forward(self, x): + x = self.encoder(x) # 1/32, 1/16, 1/8, 1/4 + return x + + +class FTB(nn.Module): + def __init__(self, inchannels, midchannels=512): + super(FTB, self).__init__() + self.in1 = inchannels + self.mid = midchannels + self.conv1 = nn.Conv2d(in_channels=self.in1, out_channels=self.mid, kernel_size=3, padding=1, stride=1, + bias=True) + # NN.BatchNorm2d + self.conv_branch = nn.Sequential(nn.ReLU(inplace=True), \ + nn.Conv2d(in_channels=self.mid, out_channels=self.mid, kernel_size=3, + padding=1, stride=1, bias=True), \ + nn.BatchNorm2d(num_features=self.mid), \ + nn.ReLU(inplace=True), \ + nn.Conv2d(in_channels=self.mid, out_channels=self.mid, kernel_size=3, + padding=1, stride=1, bias=True)) + self.relu = nn.ReLU(inplace=True) + + self.init_params() + + def forward(self, x): + x = self.conv1(x) + x = x + self.conv_branch(x) + x = self.relu(x) + + return x + + def init_params(self): + for m in self.modules(): + if isinstance(m, nn.Conv2d): + init.normal_(m.weight, std=0.01) + if m.bias is not None: + init.constant_(m.bias, 0) + elif isinstance(m, nn.ConvTranspose2d): + # init.kaiming_normal_(m.weight, mode='fan_out') + init.normal_(m.weight, std=0.01) + # init.xavier_normal_(m.weight) + if m.bias is not None: + init.constant_(m.bias, 0) + elif isinstance(m, nn.BatchNorm2d): # NN.BatchNorm2d + init.constant_(m.weight, 1) + init.constant_(m.bias, 0) + elif isinstance(m, nn.Linear): + init.normal_(m.weight, std=0.01) + if m.bias is not None: + init.constant_(m.bias, 0) + + +class ATA(nn.Module): + def __init__(self, inchannels, reduction=8): + super(ATA, self).__init__() + self.inchannels = inchannels + self.avg_pool = nn.AdaptiveAvgPool2d(1) + self.fc = nn.Sequential(nn.Linear(self.inchannels * 2, self.inchannels // reduction), + nn.ReLU(inplace=True), + nn.Linear(self.inchannels // reduction, self.inchannels), + nn.Sigmoid()) + self.init_params() + + def forward(self, low_x, high_x): + n, c, _, _ = low_x.size() + x = torch.cat([low_x, high_x], 1) + x = self.avg_pool(x) + x = x.view(n, -1) + x = self.fc(x).view(n, c, 1, 1) + x = low_x * x + high_x + + return x + + def init_params(self): + for m in self.modules(): + if isinstance(m, nn.Conv2d): + # init.kaiming_normal_(m.weight, mode='fan_out') + # init.normal(m.weight, std=0.01) + init.xavier_normal_(m.weight) + if m.bias is not None: + init.constant_(m.bias, 0) + elif isinstance(m, nn.ConvTranspose2d): + # init.kaiming_normal_(m.weight, mode='fan_out') + # init.normal_(m.weight, std=0.01) + init.xavier_normal_(m.weight) + if m.bias is not None: + init.constant_(m.bias, 0) + elif isinstance(m, nn.BatchNorm2d): # NN.BatchNorm2d + init.constant_(m.weight, 1) + init.constant_(m.bias, 0) + elif isinstance(m, nn.Linear): + init.normal_(m.weight, std=0.01) + if m.bias is not None: + init.constant_(m.bias, 0) + + +class FFM(nn.Module): + def __init__(self, inchannels, midchannels, outchannels, upfactor=2): + super(FFM, self).__init__() + self.inchannels = inchannels + self.midchannels = midchannels + self.outchannels = outchannels + self.upfactor = upfactor + + self.ftb1 = FTB(inchannels=self.inchannels, midchannels=self.midchannels) + # self.ata = ATA(inchannels = self.midchannels) + self.ftb2 = FTB(inchannels=self.midchannels, midchannels=self.outchannels) + + self.upsample = nn.Upsample(scale_factor=self.upfactor, mode='bilinear', align_corners=True) + + self.init_params() + + def forward(self, low_x, high_x): + x = self.ftb1(low_x) + x = x + high_x + x = self.ftb2(x) + x = self.upsample(x) + + return x + + def init_params(self): + for m in self.modules(): + if isinstance(m, nn.Conv2d): + # init.kaiming_normal_(m.weight, mode='fan_out') + init.normal_(m.weight, std=0.01) + # init.xavier_normal_(m.weight) + if m.bias is not None: + init.constant_(m.bias, 0) + elif isinstance(m, nn.ConvTranspose2d): + # init.kaiming_normal_(m.weight, mode='fan_out') + init.normal_(m.weight, std=0.01) + # init.xavier_normal_(m.weight) + if m.bias is not None: + init.constant_(m.bias, 0) + elif isinstance(m, nn.BatchNorm2d): # NN.Batchnorm2d + init.constant_(m.weight, 1) + init.constant_(m.bias, 0) + elif isinstance(m, nn.Linear): + init.normal_(m.weight, std=0.01) + if m.bias is not None: + init.constant_(m.bias, 0) + + +class AO(nn.Module): + # Adaptive output module + def __init__(self, inchannels, outchannels, upfactor=2): + super(AO, self).__init__() + self.inchannels = inchannels + self.outchannels = outchannels + self.upfactor = upfactor + + self.adapt_conv = nn.Sequential( + nn.Conv2d(in_channels=self.inchannels, out_channels=self.inchannels // 2, kernel_size=3, padding=1, + stride=1, bias=True), \ + nn.BatchNorm2d(num_features=self.inchannels // 2), \ + nn.ReLU(inplace=True), \ + nn.Conv2d(in_channels=self.inchannels // 2, out_channels=self.outchannels, kernel_size=3, padding=1, + stride=1, bias=True), \ + nn.Upsample(scale_factor=self.upfactor, mode='bilinear', align_corners=True)) + + self.init_params() + + def forward(self, x): + x = self.adapt_conv(x) + return x + + def init_params(self): + for m in self.modules(): + if isinstance(m, nn.Conv2d): + # init.kaiming_normal_(m.weight, mode='fan_out') + init.normal_(m.weight, std=0.01) + # init.xavier_normal_(m.weight) + if m.bias is not None: + init.constant_(m.bias, 0) + elif isinstance(m, nn.ConvTranspose2d): + # init.kaiming_normal_(m.weight, mode='fan_out') + init.normal_(m.weight, std=0.01) + # init.xavier_normal_(m.weight) + if m.bias is not None: + init.constant_(m.bias, 0) + elif isinstance(m, nn.BatchNorm2d): # NN.Batchnorm2d + init.constant_(m.weight, 1) + init.constant_(m.bias, 0) + elif isinstance(m, nn.Linear): + init.normal_(m.weight, std=0.01) + if m.bias is not None: + init.constant_(m.bias, 0) + + + +# ============================================================================================================== + + +class ResidualConv(nn.Module): + def __init__(self, inchannels): + super(ResidualConv, self).__init__() + # NN.BatchNorm2d + self.conv = nn.Sequential( + # nn.BatchNorm2d(num_features=inchannels), + nn.ReLU(inplace=False), + # nn.Conv2d(in_channels=inchannels, out_channels=inchannels, kernel_size=3, padding=1, stride=1, groups=inchannels,bias=True), + # nn.Conv2d(in_channels=inchannels, out_channels=inchannels, kernel_size=1, padding=0, stride=1, groups=1,bias=True) + nn.Conv2d(in_channels=inchannels, out_channels=inchannels / 2, kernel_size=3, padding=1, stride=1, + bias=False), + nn.BatchNorm2d(num_features=inchannels / 2), + nn.ReLU(inplace=False), + nn.Conv2d(in_channels=inchannels / 2, out_channels=inchannels, kernel_size=3, padding=1, stride=1, + bias=False) + ) + self.init_params() + + def forward(self, x): + x = self.conv(x) + x + return x + + def init_params(self): + for m in self.modules(): + if isinstance(m, nn.Conv2d): + # init.kaiming_normal_(m.weight, mode='fan_out') + init.normal_(m.weight, std=0.01) + # init.xavier_normal_(m.weight) + if m.bias is not None: + init.constant_(m.bias, 0) + elif isinstance(m, nn.ConvTranspose2d): + # init.kaiming_normal_(m.weight, mode='fan_out') + init.normal_(m.weight, std=0.01) + # init.xavier_normal_(m.weight) + if m.bias is not None: + init.constant_(m.bias, 0) + elif isinstance(m, nn.BatchNorm2d): # NN.BatchNorm2d + init.constant_(m.weight, 1) + init.constant_(m.bias, 0) + elif isinstance(m, nn.Linear): + init.normal_(m.weight, std=0.01) + if m.bias is not None: + init.constant_(m.bias, 0) + + +class FeatureFusion(nn.Module): + def __init__(self, inchannels, outchannels): + super(FeatureFusion, self).__init__() + self.conv = ResidualConv(inchannels=inchannels) + # NN.BatchNorm2d + self.up = nn.Sequential(ResidualConv(inchannels=inchannels), + nn.ConvTranspose2d(in_channels=inchannels, out_channels=outchannels, kernel_size=3, + stride=2, padding=1, output_padding=1), + nn.BatchNorm2d(num_features=outchannels), + nn.ReLU(inplace=True)) + + def forward(self, lowfeat, highfeat): + return self.up(highfeat + self.conv(lowfeat)) + + def init_params(self): + for m in self.modules(): + if isinstance(m, nn.Conv2d): + # init.kaiming_normal_(m.weight, mode='fan_out') + init.normal_(m.weight, std=0.01) + # init.xavier_normal_(m.weight) + if m.bias is not None: + init.constant_(m.bias, 0) + elif isinstance(m, nn.ConvTranspose2d): + # init.kaiming_normal_(m.weight, mode='fan_out') + init.normal_(m.weight, std=0.01) + # init.xavier_normal_(m.weight) + if m.bias is not None: + init.constant_(m.bias, 0) + elif isinstance(m, nn.BatchNorm2d): # NN.BatchNorm2d + init.constant_(m.weight, 1) + init.constant_(m.bias, 0) + elif isinstance(m, nn.Linear): + init.normal_(m.weight, std=0.01) + if m.bias is not None: + init.constant_(m.bias, 0) + + +class SenceUnderstand(nn.Module): + def __init__(self, channels): + super(SenceUnderstand, self).__init__() + self.channels = channels + self.conv1 = nn.Sequential(nn.Conv2d(in_channels=512, out_channels=512, kernel_size=3, padding=1), + nn.ReLU(inplace=True)) + self.pool = nn.AdaptiveAvgPool2d(8) + self.fc = nn.Sequential(nn.Linear(512 * 8 * 8, self.channels), + nn.ReLU(inplace=True)) + self.conv2 = nn.Sequential( + nn.Conv2d(in_channels=self.channels, out_channels=self.channels, kernel_size=1, padding=0), + nn.ReLU(inplace=True)) + self.initial_params() + + def forward(self, x): + n, c, h, w = x.size() + x = self.conv1(x) + x = self.pool(x) + x = x.view(n, -1) + x = self.fc(x) + x = x.view(n, self.channels, 1, 1) + x = self.conv2(x) + x = x.repeat(1, 1, h, w) + return x + + def initial_params(self, dev=0.01): + for m in self.modules(): + if isinstance(m, nn.Conv2d): + # print torch.sum(m.weight) + m.weight.data.normal_(0, dev) + if m.bias is not None: + m.bias.data.fill_(0) + elif isinstance(m, nn.ConvTranspose2d): + # print torch.sum(m.weight) + m.weight.data.normal_(0, dev) + if m.bias is not None: + m.bias.data.fill_(0) + elif isinstance(m, nn.Linear): + m.weight.data.normal_(0, dev) + + +if __name__ == '__main__': + net = DepthNet(depth=50, pretrained=True) + print(net) + inputs = torch.ones(4,3,128,128) + out = net(inputs) + print(out.size()) + diff --git a/modules/control/proc/leres/pix2pix/LICENSE b/modules/control/proc/leres/pix2pix/LICENSE new file mode 100644 index 000000000..38b1a24fd --- /dev/null +++ b/modules/control/proc/leres/pix2pix/LICENSE @@ -0,0 +1,19 @@ +https://github.com/compphoto/BoostingMonocularDepth + +Copyright 2021, Seyed Mahdi Hosseini Miangoleh, Sebastian Dille, Computational Photography Laboratory. All rights reserved. + +This software is for academic use only. A redistribution of this +software, with or without modifications, has to be for academic +use only, while giving the appropriate credit to the original +authors of the software. The methods implemented as a part of +this software may be covered under patents or patent applications. + +THIS SOFTWARE IS PROVIDED BY THE AUTHOR ''AS IS'' AND ANY EXPRESS OR IMPLIED +WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND +FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR +CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR +SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON +ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING +NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF +ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. \ No newline at end of file diff --git a/modules/control/proc/leres/pix2pix/__init__.py b/modules/control/proc/leres/pix2pix/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/modules/control/proc/leres/pix2pix/models/__init__.py b/modules/control/proc/leres/pix2pix/models/__init__.py new file mode 100644 index 000000000..936c0e7d3 --- /dev/null +++ b/modules/control/proc/leres/pix2pix/models/__init__.py @@ -0,0 +1,67 @@ +"""This package contains modules related to objective functions, optimizations, and network architectures. + +To add a custom model class called 'dummy', you need to add a file called 'dummy_model.py' and define a subclass DummyModel inherited from BaseModel. +You need to implement the following five functions: + -- <__init__>: initialize the class; first call BaseModel.__init__(self, opt). + -- : unpack data from dataset and apply preprocessing. + -- : produce intermediate results. + -- : calculate loss, gradients, and update network weights. + -- : (optionally) add model-specific options and set default options. + +In the function <__init__>, you need to define four lists: + -- self.loss_names (str list): specify the training losses that you want to plot and save. + -- self.model_names (str list): define networks used in our training. + -- self.visual_names (str list): specify the images that you want to display and save. + -- self.optimizers (optimizer list): define and initialize optimizers. You can define one optimizer for each network. If two networks are updated at the same time, you can use itertools.chain to group them. See cycle_gan_model.py for an usage. + +Now you can use the model class by specifying flag '--model dummy'. +See our template model class 'template_model.py' for more details. +""" + +import importlib +from .base_model import BaseModel + + +def find_model_using_name(model_name): + """Import the module "models/[model_name]_model.py". + + In the file, the class called DatasetNameModel() will + be instantiated. It has to be a subclass of BaseModel, + and it is case-insensitive. + """ + model_filename = "modules.control.proc.leres.pix2pix.models." + model_name + "_model" + modellib = importlib.import_module(model_filename) + model = None + target_model_name = model_name.replace('_', '') + 'model' + for name, cls in modellib.__dict__.items(): + if name.lower() == target_model_name.lower() \ + and issubclass(cls, BaseModel): + model = cls + + if model is None: + print("In %s.py, there should be a subclass of BaseModel with class name that matches %s in lowercase." % (model_filename, target_model_name)) + exit(0) + + return model + + +def get_option_setter(model_name): + """Return the static method of the model class.""" + model_class = find_model_using_name(model_name) + return model_class.modify_commandline_options + + +def create_model(opt): + """Create a model given the option. + + This function warps the class CustomDatasetDataLoader. + This is the main interface between this package and 'train.py'/'test.py' + + Example: + >>> from models import create_model + >>> model = create_model(opt) + """ + model = find_model_using_name(opt.model) + instance = model(opt) + print("model [%s] was created" % type(instance).__name__) + return instance diff --git a/modules/control/proc/leres/pix2pix/models/base_model.py b/modules/control/proc/leres/pix2pix/models/base_model.py new file mode 100644 index 000000000..64af9aff6 --- /dev/null +++ b/modules/control/proc/leres/pix2pix/models/base_model.py @@ -0,0 +1,242 @@ +import gc +import os +from abc import ABC, abstractmethod +from collections import OrderedDict + +import torch + +from modules.control.util import torch_gc +from . import networks + + +class BaseModel(ABC): + """This class is an abstract base class (ABC) for models. + To create a subclass, you need to implement the following five functions: + -- <__init__>: initialize the class; first call BaseModel.__init__(self, opt). + -- : unpack data from dataset and apply preprocessing. + -- : produce intermediate results. + -- : calculate losses, gradients, and update network weights. + -- : (optionally) add model-specific options and set default options. + """ + + def __init__(self, opt): + """Initialize the BaseModel class. + + Parameters: + opt (Option class)-- stores all the experiment flags; needs to be a subclass of BaseOptions + + When creating your custom class, you need to implement your own initialization. + In this function, you should first call + Then, you need to define four lists: + -- self.loss_names (str list): specify the training losses that you want to plot and save. + -- self.model_names (str list): define networks used in our training. + -- self.visual_names (str list): specify the images that you want to display and save. + -- self.optimizers (optimizer list): define and initialize optimizers. You can define one optimizer for each network. If two networks are updated at the same time, you can use itertools.chain to group them. See cycle_gan_model.py for an example. + """ + self.opt = opt + self.gpu_ids = opt.gpu_ids + self.isTrain = opt.isTrain + self.device = torch.device('cuda:{}'.format(self.gpu_ids[0])) if self.gpu_ids else torch.device('cpu') # get device name: CPU or GPU + self.save_dir = os.path.join(opt.checkpoints_dir, opt.name) # save all the checkpoints to save_dir + if opt.preprocess != 'scale_width': # with [scale_width], input images might have different sizes, which hurts the performance of cudnn.benchmark. + torch.backends.cudnn.benchmark = True + self.loss_names = [] + self.model_names = [] + self.visual_names = [] + self.optimizers = [] + self.image_paths = [] + self.metric = 0 # used for learning rate policy 'plateau' + + @staticmethod + def modify_commandline_options(parser, is_train): + """Add new model-specific options, and rewrite default values for existing options. + + Parameters: + parser -- original option parser + is_train (bool) -- whether training phase or test phase. You can use this flag to add training-specific or test-specific options. + + Returns: + the modified parser. + """ + return parser + + @abstractmethod + def set_input(self, input): + """Unpack input data from the dataloader and perform necessary pre-processing steps. + + Parameters: + input (dict): includes the data itself and its metadata information. + """ + pass + + @abstractmethod + def forward(self): + """Run forward pass; called by both functions and .""" + pass + + @abstractmethod + def optimize_parameters(self): + """Calculate losses, gradients, and update network weights; called in every training iteration""" + pass + + def setup(self, opt): + """Load and print networks; create schedulers + + Parameters: + opt (Option class) -- stores all the experiment flags; needs to be a subclass of BaseOptions + """ + if self.isTrain: + self.schedulers = [networks.get_scheduler(optimizer, opt) for optimizer in self.optimizers] + if not self.isTrain or opt.continue_train: + load_suffix = 'iter_%d' % opt.load_iter if opt.load_iter > 0 else opt.epoch + self.load_networks(load_suffix) + self.print_networks(opt.verbose) + + def eval(self): + """Make models eval mode during test time""" + for name in self.model_names: + if isinstance(name, str): + net = getattr(self, 'net' + name) + net.eval() + + def test(self): + """Forward function used in test time. + + It also calls to produce additional visualization results + """ + self.forward() + self.compute_visuals() + + def compute_visuals(self): # noqa + """Calculate additional output images for visdom and HTML visualization""" + pass + + def get_image_paths(self): + """ Return image paths that are used to load current data""" + return self.image_paths + + def update_learning_rate(self): + """Update learning rates for all the networks; called at the end of every epoch""" + old_lr = self.optimizers[0].param_groups[0]['lr'] + for scheduler in self.schedulers: + if self.opt.lr_policy == 'plateau': + scheduler.step(self.metric) + else: + scheduler.step() + + lr = self.optimizers[0].param_groups[0]['lr'] + print('learning rate %.7f -> %.7f' % (old_lr, lr)) + + def get_current_visuals(self): + """Return visualization images. train.py will display these images with visdom, and save the images to a HTML""" + visual_ret = OrderedDict() + for name in self.visual_names: + if isinstance(name, str): + visual_ret[name] = getattr(self, name) + return visual_ret + + def get_current_losses(self): + """Return traning losses / errors. train.py will print out these errors on console, and save them to a file""" + errors_ret = OrderedDict() + for name in self.loss_names: + if isinstance(name, str): + errors_ret[name] = float(getattr(self, 'loss_' + name)) # float(...) works for both scalar tensor and float number + return errors_ret + + def save_networks(self, epoch): + """Save all the networks to the disk. + + Parameters: + epoch (int) -- current epoch; used in the file name '%s_net_%s.pth' % (epoch, name) + """ + for name in self.model_names: + if isinstance(name, str): + save_filename = '%s_net_%s.pth' % (epoch, name) + save_path = os.path.join(self.save_dir, save_filename) + net = getattr(self, 'net' + name) + + if len(self.gpu_ids) > 0 and torch.cuda.is_available(): + torch.save(net.module.cpu().state_dict(), save_path) + net.cuda(self.gpu_ids[0]) + else: + torch.save(net.cpu().state_dict(), save_path) + + def unload_network(self, name): + """Unload network and gc. + """ + if isinstance(name, str): + net = getattr(self, 'net' + name) + del net + gc.collect() + torch_gc() + return None + + def __patch_instance_norm_state_dict(self, state_dict, module, keys, i=0): + """Fix InstanceNorm checkpoints incompatibility (prior to 0.4)""" + key = keys[i] + if i + 1 == len(keys): # at the end, pointing to a parameter/buffer + if module.__class__.__name__.startswith('InstanceNorm') and \ + (key == 'running_mean' or key == 'running_var'): + if getattr(module, key) is None: + state_dict.pop('.'.join(keys)) + if module.__class__.__name__.startswith('InstanceNorm') and \ + (key == 'num_batches_tracked'): + state_dict.pop('.'.join(keys)) + else: + self.__patch_instance_norm_state_dict(state_dict, getattr(module, key), keys, i + 1) + + def load_networks(self, epoch): + """Load all the networks from the disk. + + Parameters: + epoch (int) -- current epoch; used in the file name '%s_net_%s.pth' % (epoch, name) + """ + for name in self.model_names: + if isinstance(name, str): + load_filename = '%s_net_%s.pth' % (epoch, name) + load_path = os.path.join(self.save_dir, load_filename) + net = getattr(self, 'net' + name) + if isinstance(net, torch.nn.DataParallel): + net = net.module + # print('Loading depth boost model from %s' % load_path) + # if you are using PyTorch newer than 0.4 (e.g., built from + # GitHub source), you can remove str() on self.device + state_dict = torch.load(load_path, map_location=str(self.device)) + if hasattr(state_dict, '_metadata'): + del state_dict._metadata + + # patch InstanceNorm checkpoints prior to 0.4 + for key in list(state_dict.keys()): # need to copy keys here because we mutate in loop + self.__patch_instance_norm_state_dict(state_dict, net, key.split('.')) + net.load_state_dict(state_dict) + + def print_networks(self, verbose): + """Print the total number of parameters in the network and (if verbose) network architecture + + Parameters: + verbose (bool) -- if verbose: print the network architecture + """ + print('---------- Networks initialized -------------') + for name in self.model_names: + if isinstance(name, str): + net = getattr(self, 'net' + name) + num_params = 0 + for param in net.parameters(): + num_params += param.numel() + if verbose: + print(net) + print('[Network %s] Total number of parameters : %.3f M' % (name, num_params / 1e6)) + print('-----------------------------------------------') + + def set_requires_grad(self, nets, requires_grad=False): + """Set requies_grad=Fasle for all the networks to avoid unnecessary computations + Parameters: + nets (network list) -- a list of networks + requires_grad (bool) -- whether the networks require gradients or not + """ + if not isinstance(nets, list): + nets = [nets] + for net in nets: + if net is not None: + for param in net.parameters(): + param.requires_grad = requires_grad diff --git a/modules/control/proc/leres/pix2pix/models/base_model_hg.py b/modules/control/proc/leres/pix2pix/models/base_model_hg.py new file mode 100644 index 000000000..1709accdf --- /dev/null +++ b/modules/control/proc/leres/pix2pix/models/base_model_hg.py @@ -0,0 +1,58 @@ +import os +import torch + +class BaseModelHG(): + def name(self): + return 'BaseModel' + + def initialize(self, opt): + self.opt = opt + self.gpu_ids = opt.gpu_ids + self.isTrain = opt.isTrain + self.Tensor = torch.cuda.FloatTensor if self.gpu_ids else torch.Tensor + self.save_dir = os.path.join(opt.checkpoints_dir, opt.name) + + def set_input(self, input): + self.input = input + + def forward(self): + pass + + # used in test time, no backprop + def test(self): + pass + + def get_image_paths(self): + pass + + def optimize_parameters(self): + pass + + def get_current_visuals(self): + return self.input + + def get_current_errors(self): + return {} + + def save(self, label): + pass + + # helper saving function that can be used by subclasses + def save_network(self, network, network_label, epoch_label, gpu_ids): + save_filename = '_%s_net_%s.pth' % (epoch_label, network_label) + save_path = os.path.join(self.save_dir, save_filename) + torch.save(network.cpu().state_dict(), save_path) + if len(gpu_ids) and torch.cuda.is_available(): + network.cuda(device_id=gpu_ids[0]) + + # helper loading function that can be used by subclasses + def load_network(self, network, network_label, epoch_label): + save_filename = '%s_net_%s.pth' % (epoch_label, network_label) + save_path = os.path.join(self.save_dir, save_filename) + print(save_path) + model = torch.load(save_path) + return model + # network.load_state_dict(torch.load(save_path)) + + def update_learning_rate(): + pass diff --git a/modules/control/proc/leres/pix2pix/models/networks.py b/modules/control/proc/leres/pix2pix/models/networks.py new file mode 100644 index 000000000..1f076f89f --- /dev/null +++ b/modules/control/proc/leres/pix2pix/models/networks.py @@ -0,0 +1,628 @@ +import torch +import torch.nn as nn +from torch.nn import init +import functools +from torch.optim import lr_scheduler + + +############################################################################### +# Helper Functions +############################################################################### + + +class Identity(nn.Module): + def forward(self, x): + return x + + +def get_norm_layer(norm_type='instance'): + """Return a normalization layer + + Parameters: + norm_type (str) -- the name of the normalization layer: batch | instance | none + + For BatchNorm, we use learnable affine parameters and track running statistics (mean/stddev). + For InstanceNorm, we do not use learnable affine parameters. We do not track running statistics. + """ + if norm_type == 'batch': + norm_layer = functools.partial(nn.BatchNorm2d, affine=True, track_running_stats=True) + elif norm_type == 'instance': + norm_layer = functools.partial(nn.InstanceNorm2d, affine=False, track_running_stats=False) + elif norm_type == 'none': + def norm_layer(x): return Identity() + else: + raise NotImplementedError('normalization layer [%s] is not found' % norm_type) + return norm_layer + + +def get_scheduler(optimizer, opt): + """Return a learning rate scheduler + + Parameters: + optimizer -- the optimizer of the network + opt (option class) -- stores all the experiment flags; needs to be a subclass of BaseOptions + opt.lr_policy is the name of learning rate policy: linear | step | plateau | cosine + + For 'linear', we keep the same learning rate for the first epochs + and linearly decay the rate to zero over the next epochs. + For other schedulers (step, plateau, and cosine), we use the default PyTorch schedulers. + See https://pytorch.org/docs/stable/optim.html for more details. + """ + if opt.lr_policy == 'linear': + def lambda_rule(epoch): + lr_l = 1.0 - max(0, epoch + opt.epoch_count - opt.n_epochs) / float(opt.n_epochs_decay + 1) + return lr_l + scheduler = lr_scheduler.LambdaLR(optimizer, lr_lambda=lambda_rule) + elif opt.lr_policy == 'step': + scheduler = lr_scheduler.StepLR(optimizer, step_size=opt.lr_decay_iters, gamma=0.1) + elif opt.lr_policy == 'plateau': + scheduler = lr_scheduler.ReduceLROnPlateau(optimizer, mode='min', factor=0.2, threshold=0.01, patience=5) + elif opt.lr_policy == 'cosine': + scheduler = lr_scheduler.CosineAnnealingLR(optimizer, T_max=opt.n_epochs, eta_min=0) + else: + return NotImplementedError('learning rate policy [%s] is not implemented', opt.lr_policy) + return scheduler + + +def init_weights(net, init_type='normal', init_gain=0.02): + """Initialize network weights. + + Parameters: + net (network) -- network to be initialized + init_type (str) -- the name of an initialization method: normal | xavier | kaiming | orthogonal + init_gain (float) -- scaling factor for normal, xavier and orthogonal. + + We use 'normal' in the original pix2pix and CycleGAN paper. But xavier and kaiming might + work better for some applications. Feel free to try yourself. + """ + def init_func(m): # define the initialization function + classname = m.__class__.__name__ + if hasattr(m, 'weight') and (classname.find('Conv') != -1 or classname.find('Linear') != -1): + if init_type == 'normal': + init.normal_(m.weight.data, 0.0, init_gain) + elif init_type == 'xavier': + init.xavier_normal_(m.weight.data, gain=init_gain) + elif init_type == 'kaiming': + init.kaiming_normal_(m.weight.data, a=0, mode='fan_in') + elif init_type == 'orthogonal': + init.orthogonal_(m.weight.data, gain=init_gain) + else: + raise NotImplementedError('initialization method [%s] is not implemented' % init_type) + if hasattr(m, 'bias') and m.bias is not None: + init.constant_(m.bias.data, 0.0) + elif classname.find('BatchNorm2d') != -1: # BatchNorm Layer's weight is not a matrix; only normal distribution applies. + init.normal_(m.weight.data, 1.0, init_gain) + init.constant_(m.bias.data, 0.0) + + # print('initialize network with %s' % init_type) + net.apply(init_func) # apply the initialization function + + +def init_net(net, init_type='normal', init_gain=0.02, gpu_ids=None): + """Initialize a network: 1. register CPU/GPU device (with multi-GPU support); 2. initialize the network weights + Parameters: + net (network) -- the network to be initialized + init_type (str) -- the name of an initialization method: normal | xavier | kaiming | orthogonal + gain (float) -- scaling factor for normal, xavier and orthogonal. + gpu_ids (int list) -- which GPUs the network runs on: e.g., 0,1,2 + + Return an initialized network. + """ + if gpu_ids is None: + gpu_ids = [] + if len(gpu_ids) > 0: + assert(torch.cuda.is_available()) + net.to(gpu_ids[0]) + net = torch.nn.DataParallel(net, gpu_ids) # multi-GPUs + init_weights(net, init_type, init_gain=init_gain) + return net + + +def define_G(input_nc, output_nc, ngf, netG, norm='batch', use_dropout=False, init_type='normal', init_gain=0.02, gpu_ids=None): + """Create a generator + + Parameters: + input_nc (int) -- the number of channels in input images + output_nc (int) -- the number of channels in output images + ngf (int) -- the number of filters in the last conv layer + netG (str) -- the architecture's name: resnet_9blocks | resnet_6blocks | unet_256 | unet_128 + norm (str) -- the name of normalization layers used in the network: batch | instance | none + use_dropout (bool) -- if use dropout layers. + init_type (str) -- the name of our initialization method. + init_gain (float) -- scaling factor for normal, xavier and orthogonal. + gpu_ids (int list) -- which GPUs the network runs on: e.g., 0,1,2 + + Returns a generator + + Our current implementation provides two types of generators: + U-Net: [unet_128] (for 128x128 input images) and [unet_256] (for 256x256 input images) + The original U-Net paper: https://arxiv.org/abs/1505.04597 + + Resnet-based generator: [resnet_6blocks] (with 6 Resnet blocks) and [resnet_9blocks] (with 9 Resnet blocks) + Resnet-based generator consists of several Resnet blocks between a few downsampling/upsampling operations. + We adapt Torch code from Justin Johnson's neural style transfer project (https://github.com/jcjohnson/fast-neural-style). + + + The generator has been initialized by . It uses RELU for non-linearity. + """ + if gpu_ids is None: + gpu_ids = [] + net = None + norm_layer = get_norm_layer(norm_type=norm) + + if netG == 'resnet_9blocks': + net = ResnetGenerator(input_nc, output_nc, ngf, norm_layer=norm_layer, use_dropout=use_dropout, n_blocks=9) + elif netG == 'resnet_6blocks': + net = ResnetGenerator(input_nc, output_nc, ngf, norm_layer=norm_layer, use_dropout=use_dropout, n_blocks=6) + elif netG == 'resnet_12blocks': + net = ResnetGenerator(input_nc, output_nc, ngf, norm_layer=norm_layer, use_dropout=use_dropout, n_blocks=12) + elif netG == 'unet_128': + net = UnetGenerator(input_nc, output_nc, 7, ngf, norm_layer=norm_layer, use_dropout=use_dropout) + elif netG == 'unet_256': + net = UnetGenerator(input_nc, output_nc, 8, ngf, norm_layer=norm_layer, use_dropout=use_dropout) + elif netG == 'unet_672': + net = UnetGenerator(input_nc, output_nc, 5, ngf, norm_layer=norm_layer, use_dropout=use_dropout) + elif netG == 'unet_960': + net = UnetGenerator(input_nc, output_nc, 6, ngf, norm_layer=norm_layer, use_dropout=use_dropout) + elif netG == 'unet_1024': + net = UnetGenerator(input_nc, output_nc, 10, ngf, norm_layer=norm_layer, use_dropout=use_dropout) + else: + raise NotImplementedError('Generator model name [%s] is not recognized' % netG) + return init_net(net, init_type, init_gain, gpu_ids) + + +def define_D(input_nc, ndf, netD, n_layers_D=3, norm='batch', init_type='normal', init_gain=0.02, gpu_ids=None): + """Create a discriminator + + Parameters: + input_nc (int) -- the number of channels in input images + ndf (int) -- the number of filters in the first conv layer + netD (str) -- the architecture's name: basic | n_layers | pixel + n_layers_D (int) -- the number of conv layers in the discriminator; effective when netD=='n_layers' + norm (str) -- the type of normalization layers used in the network. + init_type (str) -- the name of the initialization method. + init_gain (float) -- scaling factor for normal, xavier and orthogonal. + gpu_ids (int list) -- which GPUs the network runs on: e.g., 0,1,2 + + Returns a discriminator + + Our current implementation provides three types of discriminators: + [basic]: 'PatchGAN' classifier described in the original pix2pix paper. + It can classify whether 70x70 overlapping patches are real or fake. + Such a patch-level discriminator architecture has fewer parameters + than a full-image discriminator and can work on arbitrarily-sized images + in a fully convolutional fashion. + + [n_layers]: With this mode, you can specify the number of conv layers in the discriminator + with the parameter (default=3 as used in [basic] (PatchGAN).) + + [pixel]: 1x1 PixelGAN discriminator can classify whether a pixel is real or not. + It encourages greater color diversity but has no effect on spatial statistics. + + The discriminator has been initialized by . It uses Leakly RELU for non-linearity. + """ + if gpu_ids is None: + gpu_ids = [] + net = None + norm_layer = get_norm_layer(norm_type=norm) + + if netD == 'basic': # default PatchGAN classifier + net = NLayerDiscriminator(input_nc, ndf, n_layers=3, norm_layer=norm_layer) + elif netD == 'n_layers': # more options + net = NLayerDiscriminator(input_nc, ndf, n_layers_D, norm_layer=norm_layer) + elif netD == 'pixel': # classify if each pixel is real or fake + net = PixelDiscriminator(input_nc, ndf, norm_layer=norm_layer) + else: + raise NotImplementedError('Discriminator model name [%s] is not recognized' % netD) + return init_net(net, init_type, init_gain, gpu_ids) + + +############################################################################## +# Classes +############################################################################## +class GANLoss(nn.Module): + """Define different GAN objectives. + + The GANLoss class abstracts away the need to create the target label tensor + that has the same size as the input. + """ + + def __init__(self, gan_mode, target_real_label=1.0, target_fake_label=0.0): + """ Initialize the GANLoss class. + + Parameters: + gan_mode (str) - - the type of GAN objective. It currently supports vanilla, lsgan, and wgangp. + target_real_label (bool) - - label for a real image + target_fake_label (bool) - - label of a fake image + + Note: Do not use sigmoid as the last layer of Discriminator. + LSGAN needs no sigmoid. vanilla GANs will handle it with BCEWithLogitsLoss. + """ + super(GANLoss, self).__init__() + self.register_buffer('real_label', torch.tensor(target_real_label)) + self.register_buffer('fake_label', torch.tensor(target_fake_label)) + self.gan_mode = gan_mode + if gan_mode == 'lsgan': + self.loss = nn.MSELoss() + elif gan_mode == 'vanilla': + self.loss = nn.BCEWithLogitsLoss() + elif gan_mode in ['wgangp']: + self.loss = None + else: + raise NotImplementedError('gan mode %s not implemented' % gan_mode) + + def get_target_tensor(self, prediction, target_is_real): + """Create label tensors with the same size as the input. + + Parameters: + prediction (tensor) - - tpyically the prediction from a discriminator + target_is_real (bool) - - if the ground truth label is for real images or fake images + + Returns: + A label tensor filled with ground truth label, and with the size of the input + """ + + if target_is_real: + target_tensor = self.real_label + else: + target_tensor = self.fake_label + return target_tensor.expand_as(prediction) + + def __call__(self, prediction, target_is_real): + """Calculate loss given Discriminator's output and grount truth labels. + + Parameters: + prediction (tensor) - - tpyically the prediction output from a discriminator + target_is_real (bool) - - if the ground truth label is for real images or fake images + + Returns: + the calculated loss. + """ + if self.gan_mode in ['lsgan', 'vanilla']: + target_tensor = self.get_target_tensor(prediction, target_is_real) + loss = self.loss(prediction, target_tensor) + elif self.gan_mode == 'wgangp': + if target_is_real: + loss = -prediction.mean() + else: + loss = prediction.mean() + return loss + + +def cal_gradient_penalty(netD, real_data, fake_data, device, type='mixed', constant=1.0, lambda_gp=10.0): + """Calculate the gradient penalty loss, used in WGAN-GP paper https://arxiv.org/abs/1704.00028 + + Arguments: + netD (network) -- discriminator network + real_data (tensor array) -- real images + fake_data (tensor array) -- generated images from the generator + device (str) -- GPU / CPU: from torch.device('cuda:{}'.format(self.gpu_ids[0])) if self.gpu_ids else torch.device('cpu') + type (str) -- if we mix real and fake data or not [real | fake | mixed]. + constant (float) -- the constant used in formula ( ||gradient||_2 - constant)^2 + lambda_gp (float) -- weight for this loss + + Returns the gradient penalty loss + """ + if lambda_gp > 0.0: + if type == 'real': # either use real images, fake images, or a linear interpolation of two. + interpolatesv = real_data + elif type == 'fake': + interpolatesv = fake_data + elif type == 'mixed': + alpha = torch.rand(real_data.shape[0], 1, device=device) + alpha = alpha.expand(real_data.shape[0], real_data.nelement() // real_data.shape[0]).contiguous().view(*real_data.shape) + interpolatesv = alpha * real_data + ((1 - alpha) * fake_data) + else: + raise NotImplementedError('{} not implemented'.format(type)) + interpolatesv.requires_grad_(True) + disc_interpolates = netD(interpolatesv) + gradients = torch.autograd.grad(outputs=disc_interpolates, inputs=interpolatesv, + grad_outputs=torch.ones(disc_interpolates.size()).to(device), + create_graph=True, retain_graph=True, only_inputs=True) + gradients = gradients[0].view(real_data.size(0), -1) # flat the data + gradient_penalty = (((gradients + 1e-16).norm(2, dim=1) - constant) ** 2).mean() * lambda_gp # added eps + return gradient_penalty, gradients + else: + return 0.0, None + + +class ResnetGenerator(nn.Module): + """Resnet-based generator that consists of Resnet blocks between a few downsampling/upsampling operations. + + We adapt Torch code and idea from Justin Johnson's neural style transfer project(https://github.com/jcjohnson/fast-neural-style) + """ + + def __init__(self, input_nc, output_nc, ngf=64, norm_layer=nn.BatchNorm2d, use_dropout=False, n_blocks=6, padding_type='reflect'): + """Construct a Resnet-based generator + + Parameters: + input_nc (int) -- the number of channels in input images + output_nc (int) -- the number of channels in output images + ngf (int) -- the number of filters in the last conv layer + norm_layer -- normalization layer + use_dropout (bool) -- if use dropout layers + n_blocks (int) -- the number of ResNet blocks + padding_type (str) -- the name of padding layer in conv layers: reflect | replicate | zero + """ + assert(n_blocks >= 0) + super(ResnetGenerator, self).__init__() + if type(norm_layer) == functools.partial: + use_bias = norm_layer.func == nn.InstanceNorm2d + else: + use_bias = norm_layer == nn.InstanceNorm2d + + model = [nn.ReflectionPad2d(3), + nn.Conv2d(input_nc, ngf, kernel_size=7, padding=0, bias=use_bias), + norm_layer(ngf), + nn.ReLU(True)] + + n_downsampling = 2 + for i in range(n_downsampling): # add downsampling layers + mult = 2 ** i + model += [nn.Conv2d(ngf * mult, ngf * mult * 2, kernel_size=3, stride=2, padding=1, bias=use_bias), + norm_layer(ngf * mult * 2), + nn.ReLU(True)] + + mult = 2 ** n_downsampling + for _i in range(n_blocks): # add ResNet blocks + model += [ResnetBlock(ngf * mult, padding_type=padding_type, norm_layer=norm_layer, use_dropout=use_dropout, use_bias=use_bias)] + + for i in range(n_downsampling): # add upsampling layers + mult = 2 ** (n_downsampling - i) + model += [nn.ConvTranspose2d(ngf * mult, int(ngf * mult / 2), + kernel_size=3, stride=2, + padding=1, output_padding=1, + bias=use_bias), + norm_layer(int(ngf * mult / 2)), + nn.ReLU(True)] + model += [nn.ReflectionPad2d(3)] + model += [nn.Conv2d(ngf, output_nc, kernel_size=7, padding=0)] + model += [nn.Tanh()] + + self.model = nn.Sequential(*model) + + def forward(self, input): + """Standard forward""" + return self.model(input) + + +class ResnetBlock(nn.Module): + """Define a Resnet block""" + + def __init__(self, dim, padding_type, norm_layer, use_dropout, use_bias): + """Initialize the Resnet block + + A resnet block is a conv block with skip connections + We construct a conv block with build_conv_block function, + and implement skip connections in function. + Original Resnet paper: https://arxiv.org/pdf/1512.03385.pdf + """ + super(ResnetBlock, self).__init__() + self.conv_block = self.build_conv_block(dim, padding_type, norm_layer, use_dropout, use_bias) + + def build_conv_block(self, dim, padding_type, norm_layer, use_dropout, use_bias): + """Construct a convolutional block. + + Parameters: + dim (int) -- the number of channels in the conv layer. + padding_type (str) -- the name of padding layer: reflect | replicate | zero + norm_layer -- normalization layer + use_dropout (bool) -- if use dropout layers. + use_bias (bool) -- if the conv layer uses bias or not + + Returns a conv block (with a conv layer, a normalization layer, and a non-linearity layer (ReLU)) + """ + conv_block = [] + p = 0 + if padding_type == 'reflect': + conv_block += [nn.ReflectionPad2d(1)] + elif padding_type == 'replicate': + conv_block += [nn.ReplicationPad2d(1)] + elif padding_type == 'zero': + p = 1 + else: + raise NotImplementedError('padding [%s] is not implemented' % padding_type) + + conv_block += [nn.Conv2d(dim, dim, kernel_size=3, padding=p, bias=use_bias), norm_layer(dim), nn.ReLU(True)] + if use_dropout: + conv_block += [nn.Dropout(0.5)] + + p = 0 + if padding_type == 'reflect': + conv_block += [nn.ReflectionPad2d(1)] + elif padding_type == 'replicate': + conv_block += [nn.ReplicationPad2d(1)] + elif padding_type == 'zero': + p = 1 + else: + raise NotImplementedError('padding [%s] is not implemented' % padding_type) + conv_block += [nn.Conv2d(dim, dim, kernel_size=3, padding=p, bias=use_bias), norm_layer(dim)] + + return nn.Sequential(*conv_block) + + def forward(self, x): + """Forward function (with skip connections)""" + out = x + self.conv_block(x) # add skip connections + return out + + +class UnetGenerator(nn.Module): + """Create a Unet-based generator""" + + def __init__(self, input_nc, output_nc, num_downs, ngf=64, norm_layer=nn.BatchNorm2d, use_dropout=False): + """Construct a Unet generator + Parameters: + input_nc (int) -- the number of channels in input images + output_nc (int) -- the number of channels in output images + num_downs (int) -- the number of downsamplings in UNet. For example, # if |num_downs| == 7, + image of size 128x128 will become of size 1x1 # at the bottleneck + ngf (int) -- the number of filters in the last conv layer + norm_layer -- normalization layer + + We construct the U-Net from the innermost layer to the outermost layer. + It is a recursive process. + """ + super(UnetGenerator, self).__init__() + # construct unet structure + unet_block = UnetSkipConnectionBlock(ngf * 8, ngf * 8, input_nc=None, submodule=None, norm_layer=norm_layer, innermost=True) # add the innermost layer + for _i in range(num_downs - 5): # add intermediate layers with ngf * 8 filters + unet_block = UnetSkipConnectionBlock(ngf * 8, ngf * 8, input_nc=None, submodule=unet_block, norm_layer=norm_layer, use_dropout=use_dropout) + # gradually reduce the number of filters from ngf * 8 to ngf + unet_block = UnetSkipConnectionBlock(ngf * 4, ngf * 8, input_nc=None, submodule=unet_block, norm_layer=norm_layer) + unet_block = UnetSkipConnectionBlock(ngf * 2, ngf * 4, input_nc=None, submodule=unet_block, norm_layer=norm_layer) + unet_block = UnetSkipConnectionBlock(ngf, ngf * 2, input_nc=None, submodule=unet_block, norm_layer=norm_layer) + self.model = UnetSkipConnectionBlock(output_nc, ngf, input_nc=input_nc, submodule=unet_block, outermost=True, norm_layer=norm_layer) # add the outermost layer + + def forward(self, input): + """Standard forward""" + return self.model(input) + + +class UnetSkipConnectionBlock(nn.Module): + """Defines the Unet submodule with skip connection. + X -------------------identity---------------------- + |-- downsampling -- |submodule| -- upsampling --| + """ + + def __init__(self, outer_nc, inner_nc, input_nc=None, + submodule=None, outermost=False, innermost=False, norm_layer=nn.BatchNorm2d, use_dropout=False): + """Construct a Unet submodule with skip connections. + + Parameters: + outer_nc (int) -- the number of filters in the outer conv layer + inner_nc (int) -- the number of filters in the inner conv layer + input_nc (int) -- the number of channels in input images/features + submodule (UnetSkipConnectionBlock) -- previously defined submodules + outermost (bool) -- if this module is the outermost module + innermost (bool) -- if this module is the innermost module + norm_layer -- normalization layer + use_dropout (bool) -- if use dropout layers. + """ + super(UnetSkipConnectionBlock, self).__init__() + self.outermost = outermost + if type(norm_layer) == functools.partial: + use_bias = norm_layer.func == nn.InstanceNorm2d + else: + use_bias = norm_layer == nn.InstanceNorm2d + if input_nc is None: + input_nc = outer_nc + downconv = nn.Conv2d(input_nc, inner_nc, kernel_size=4, + stride=2, padding=1, bias=use_bias) + downrelu = nn.LeakyReLU(0.2, True) + downnorm = norm_layer(inner_nc) + uprelu = nn.ReLU(True) + upnorm = norm_layer(outer_nc) + + if outermost: + upconv = nn.ConvTranspose2d(inner_nc * 2, outer_nc, + kernel_size=4, stride=2, + padding=1) + down = [downconv] + up = [uprelu, upconv, nn.Tanh()] + model = down + [submodule] + up + elif innermost: + upconv = nn.ConvTranspose2d(inner_nc, outer_nc, + kernel_size=4, stride=2, + padding=1, bias=use_bias) + down = [downrelu, downconv] + up = [uprelu, upconv, upnorm] + model = down + up + else: + upconv = nn.ConvTranspose2d(inner_nc * 2, outer_nc, + kernel_size=4, stride=2, + padding=1, bias=use_bias) + down = [downrelu, downconv, downnorm] + up = [uprelu, upconv, upnorm] + + if use_dropout: + model = down + [submodule] + up + [nn.Dropout(0.5)] + else: + model = down + [submodule] + up + + self.model = nn.Sequential(*model) + + def forward(self, x): + if self.outermost: + return self.model(x) + else: # add skip connections + return torch.cat([x, self.model(x)], 1) + + +class NLayerDiscriminator(nn.Module): + """Defines a PatchGAN discriminator""" + + def __init__(self, input_nc, ndf=64, n_layers=3, norm_layer=nn.BatchNorm2d): + """Construct a PatchGAN discriminator + + Parameters: + input_nc (int) -- the number of channels in input images + ndf (int) -- the number of filters in the last conv layer + n_layers (int) -- the number of conv layers in the discriminator + norm_layer -- normalization layer + """ + super(NLayerDiscriminator, self).__init__() + if type(norm_layer) == functools.partial: # no need to use bias as BatchNorm2d has affine parameters + use_bias = norm_layer.func == nn.InstanceNorm2d + else: + use_bias = norm_layer == nn.InstanceNorm2d + + kw = 4 + padw = 1 + sequence = [nn.Conv2d(input_nc, ndf, kernel_size=kw, stride=2, padding=padw), nn.LeakyReLU(0.2, True)] + nf_mult = 1 + nf_mult_prev = 1 + for n in range(1, n_layers): # gradually increase the number of filters + nf_mult_prev = nf_mult + nf_mult = min(2 ** n, 8) + sequence += [ + nn.Conv2d(ndf * nf_mult_prev, ndf * nf_mult, kernel_size=kw, stride=2, padding=padw, bias=use_bias), + norm_layer(ndf * nf_mult), + nn.LeakyReLU(0.2, True) + ] + + nf_mult_prev = nf_mult + nf_mult = min(2 ** n_layers, 8) + sequence += [ + nn.Conv2d(ndf * nf_mult_prev, ndf * nf_mult, kernel_size=kw, stride=1, padding=padw, bias=use_bias), + norm_layer(ndf * nf_mult), + nn.LeakyReLU(0.2, True) + ] + + sequence += [nn.Conv2d(ndf * nf_mult, 1, kernel_size=kw, stride=1, padding=padw)] # output 1 channel prediction map + self.model = nn.Sequential(*sequence) + + def forward(self, input): + """Standard forward.""" + return self.model(input) + + +class PixelDiscriminator(nn.Module): + """Defines a 1x1 PatchGAN discriminator (pixelGAN)""" + + def __init__(self, input_nc, ndf=64, norm_layer=nn.BatchNorm2d): + """Construct a 1x1 PatchGAN discriminator + + Parameters: + input_nc (int) -- the number of channels in input images + ndf (int) -- the number of filters in the last conv layer + norm_layer -- normalization layer + """ + super(PixelDiscriminator, self).__init__() + if type(norm_layer) == functools.partial: # no need to use bias as BatchNorm2d has affine parameters + use_bias = norm_layer.func == nn.InstanceNorm2d + else: + use_bias = norm_layer == nn.InstanceNorm2d + + self.net = [ + nn.Conv2d(input_nc, ndf, kernel_size=1, stride=1, padding=0), + nn.LeakyReLU(0.2, True), + nn.Conv2d(ndf, ndf * 2, kernel_size=1, stride=1, padding=0, bias=use_bias), + norm_layer(ndf * 2), + nn.LeakyReLU(0.2, True), + nn.Conv2d(ndf * 2, 1, kernel_size=1, stride=1, padding=0, bias=use_bias)] + + self.net = nn.Sequential(*self.net) + + def forward(self, input): + """Standard forward.""" + return self.net(input) diff --git a/modules/control/proc/leres/pix2pix/models/pix2pix4depth_model.py b/modules/control/proc/leres/pix2pix/models/pix2pix4depth_model.py new file mode 100644 index 000000000..aac9ae83a --- /dev/null +++ b/modules/control/proc/leres/pix2pix/models/pix2pix4depth_model.py @@ -0,0 +1,155 @@ +import torch +from .base_model import BaseModel +from . import networks + + +class Pix2Pix4DepthModel(BaseModel): + """ This class implements the pix2pix model, for learning a mapping from input images to output images given paired data. + + The model training requires '--dataset_mode aligned' dataset. + By default, it uses a '--netG unet256' U-Net generator, + a '--netD basic' discriminator (PatchGAN), + and a '--gan_mode' vanilla GAN loss (the cross-entropy objective used in the orignal GAN paper). + + pix2pix paper: https://arxiv.org/pdf/1611.07004.pdf + """ + @staticmethod + def modify_commandline_options(parser, is_train=True): + """Add new dataset-specific options, and rewrite default values for existing options. + + Parameters: + parser -- original option parser + is_train (bool) -- whether training phase or test phase. You can use this flag to add training-specific or test-specific options. + + Returns: + the modified parser. + + For pix2pix, we do not use image buffer + The training objective is: GAN Loss + lambda_L1 * ||G(A)-B||_1 + By default, we use vanilla GAN loss, UNet with batchnorm, and aligned datasets. + """ + # changing the default values to match the pix2pix paper (https://phillipi.github.io/pix2pix/) + parser.set_defaults(input_nc=2,output_nc=1,norm='none', netG='unet_1024', dataset_mode='depthmerge') + if is_train: + parser.set_defaults(pool_size=0, gan_mode='vanilla',) + parser.add_argument('--lambda_L1', type=float, default=1000, help='weight for L1 loss') + return parser + + def __init__(self, opt): + """Initialize the pix2pix class. + + Parameters: + opt (Option class)-- stores all the experiment flags; needs to be a subclass of BaseOptions + """ + BaseModel.__init__(self, opt) + # specify the training losses you want to print out. The training/test scripts will call + + self.loss_names = ['G_GAN', 'G_L1', 'D_real', 'D_fake'] + # self.loss_names = ['G_L1'] + + # specify the images you want to save/display. The training/test scripts will call + if self.isTrain: + self.visual_names = ['outer','inner', 'fake_B', 'real_B'] + else: + self.visual_names = ['fake_B'] + + # specify the models you want to save to the disk. The training/test scripts will call and + if self.isTrain: + self.model_names = ['G','D'] + else: # during test time, only load G + self.model_names = ['G'] + + # define networks (both generator and discriminator) + self.netG = networks.define_G(opt.input_nc, opt.output_nc, 64, 'unet_1024', 'none', + False, 'normal', 0.02, self.gpu_ids) + + if self.isTrain: # define a discriminator; conditional GANs need to take both input and output images; Therefore, #channels for D is input_nc + output_nc + self.netD = networks.define_D(opt.input_nc + opt.output_nc, opt.ndf, opt.netD, + opt.n_layers_D, opt.norm, opt.init_type, opt.init_gain, self.gpu_ids) + + if self.isTrain: + # define loss functions + self.criterionGAN = networks.GANLoss(opt.gan_mode).to(self.device) + self.criterionL1 = torch.nn.L1Loss() + # initialize optimizers; schedulers will be automatically created by function . + self.optimizer_G = torch.optim.Adam(self.netG.parameters(), lr=1e-4, betas=(opt.beta1, 0.999)) + self.optimizer_D = torch.optim.Adam(self.netD.parameters(), lr=2e-06, betas=(opt.beta1, 0.999)) + self.optimizers.append(self.optimizer_G) + self.optimizers.append(self.optimizer_D) + + def set_input_train(self, input): + self.outer = input['data_outer'].to(self.device) + self.outer = torch.nn.functional.interpolate(self.outer,(1024,1024),mode='bilinear',align_corners=False) + + self.inner = input['data_inner'].to(self.device) + self.inner = torch.nn.functional.interpolate(self.inner,(1024,1024),mode='bilinear',align_corners=False) + + self.image_paths = input['image_path'] + + if self.isTrain: + self.gtfake = input['data_gtfake'].to(self.device) + self.gtfake = torch.nn.functional.interpolate(self.gtfake, (1024, 1024), mode='bilinear', align_corners=False) + self.real_B = self.gtfake + + self.real_A = torch.cat((self.outer, self.inner), 1) + + def set_input(self, outer, inner): + inner = torch.from_numpy(inner).unsqueeze(0).unsqueeze(0) + outer = torch.from_numpy(outer).unsqueeze(0).unsqueeze(0) + + inner = (inner - torch.min(inner))/(torch.max(inner)-torch.min(inner)) + outer = (outer - torch.min(outer))/(torch.max(outer)-torch.min(outer)) + + inner = self.normalize(inner) + outer = self.normalize(outer) + + self.real_A = torch.cat((outer, inner), 1).to(self.device) + + + def normalize(self, input): + input = input * 2 + input = input - 1 + return input + + def forward(self): + """Run forward pass; called by both functions and .""" + self.fake_B = self.netG(self.real_A) # G(A) + + def backward_D(self): + """Calculate GAN loss for the discriminator""" + # Fake; stop backprop to the generator by detaching fake_B + fake_AB = torch.cat((self.real_A, self.fake_B), 1) # we use conditional GANs; we need to feed both input and output to the discriminator + pred_fake = self.netD(fake_AB.detach()) + self.loss_D_fake = self.criterionGAN(pred_fake, False) + # Real + real_AB = torch.cat((self.real_A, self.real_B), 1) + pred_real = self.netD(real_AB) + self.loss_D_real = self.criterionGAN(pred_real, True) + # combine loss and calculate gradients + self.loss_D = (self.loss_D_fake + self.loss_D_real) * 0.5 + self.loss_D.backward() + + def backward_G(self): + """Calculate GAN and L1 loss for the generator""" + # First, G(A) should fake the discriminator + fake_AB = torch.cat((self.real_A, self.fake_B), 1) + pred_fake = self.netD(fake_AB) + self.loss_G_GAN = self.criterionGAN(pred_fake, True) + # Second, G(A) = B + self.loss_G_L1 = self.criterionL1(self.fake_B, self.real_B) * self.opt.lambda_L1 + # combine loss and calculate gradients + self.loss_G = self.loss_G_L1 + self.loss_G_GAN + self.loss_G.backward() + + def optimize_parameters(self): + self.forward() # compute fake images: G(A) + # update D + self.set_requires_grad(self.netD, True) # enable backprop for D + self.optimizer_D.zero_grad() # set D's gradients to zero + self.backward_D() # calculate gradients for D + self.optimizer_D.step() # update D's weights + # update G + self.set_requires_grad(self.netD, False) # D requires no gradients when optimizing G + self.optimizer_G.zero_grad() # set G's gradients to zero + self.backward_G() # calculate graidents for G + self.optimizer_G.step() # udpate G's weights diff --git a/modules/control/proc/leres/pix2pix/options/__init__.py b/modules/control/proc/leres/pix2pix/options/__init__.py new file mode 100644 index 000000000..e7eedebe5 --- /dev/null +++ b/modules/control/proc/leres/pix2pix/options/__init__.py @@ -0,0 +1 @@ +"""This package options includes option modules: training options, test options, and basic options (used in both training and test).""" diff --git a/modules/control/proc/leres/pix2pix/options/base_options.py b/modules/control/proc/leres/pix2pix/options/base_options.py new file mode 100644 index 000000000..533a1e88a --- /dev/null +++ b/modules/control/proc/leres/pix2pix/options/base_options.py @@ -0,0 +1,156 @@ +import argparse +import os +from ...pix2pix.util import util +# import torch +from ...pix2pix import models +# import pix2pix.data +import numpy as np + +class BaseOptions(): + """This class defines options used during both training and test time. + + It also implements several helper functions such as parsing, printing, and saving the options. + It also gathers additional options defined in functions in both dataset class and model class. + """ + + def __init__(self): + """Reset the class; indicates the class hasn't been initailized""" + self.initialized = False + + def initialize(self, parser): + """Define the common options that are used in both training and test.""" + # basic parameters + parser.add_argument('--dataroot', help='path to images (should have subfolders trainA, trainB, valA, valB, etc)') + parser.add_argument('--name', type=str, default='void', help='mahdi_unet_new, scaled_unet') + parser.add_argument('--gpu_ids', type=str, default='0', help='gpu ids: e.g. 0 0,1,2, 0,2. use -1 for CPU') + parser.add_argument('--checkpoints_dir', type=str, default='./pix2pix/checkpoints', help='models are saved here') + # model parameters + parser.add_argument('--model', type=str, default='cycle_gan', help='chooses which model to use. [cycle_gan | pix2pix | test | colorization]') + parser.add_argument('--input_nc', type=int, default=2, help='# of input image channels: 3 for RGB and 1 for grayscale') + parser.add_argument('--output_nc', type=int, default=1, help='# of output image channels: 3 for RGB and 1 for grayscale') + parser.add_argument('--ngf', type=int, default=64, help='# of gen filters in the last conv layer') + parser.add_argument('--ndf', type=int, default=64, help='# of discrim filters in the first conv layer') + parser.add_argument('--netD', type=str, default='basic', help='specify discriminator architecture [basic | n_layers | pixel]. The basic model is a 70x70 PatchGAN. n_layers allows you to specify the layers in the discriminator') + parser.add_argument('--netG', type=str, default='resnet_9blocks', help='specify generator architecture [resnet_9blocks | resnet_6blocks | unet_256 | unet_128]') + parser.add_argument('--n_layers_D', type=int, default=3, help='only used if netD==n_layers') + parser.add_argument('--norm', type=str, default='instance', help='instance normalization or batch normalization [instance | batch | none]') + parser.add_argument('--init_type', type=str, default='normal', help='network initialization [normal | xavier | kaiming | orthogonal]') + parser.add_argument('--init_gain', type=float, default=0.02, help='scaling factor for normal, xavier and orthogonal.') + parser.add_argument('--no_dropout', action='store_true', help='no dropout for the generator') + # dataset parameters + parser.add_argument('--dataset_mode', type=str, default='unaligned', help='chooses how datasets are loaded. [unaligned | aligned | single | colorization]') + parser.add_argument('--direction', type=str, default='AtoB', help='AtoB or BtoA') + parser.add_argument('--serial_batches', action='store_true', help='if true, takes images in order to make batches, otherwise takes them randomly') + parser.add_argument('--num_threads', default=4, type=int, help='# threads for loading data') + parser.add_argument('--batch_size', type=int, default=1, help='input batch size') + parser.add_argument('--load_size', type=int, default=672, help='scale images to this size') + parser.add_argument('--crop_size', type=int, default=672, help='then crop to this size') + parser.add_argument('--max_dataset_size', type=int, default=10000, help='Maximum number of samples allowed per dataset. If the dataset directory contains more than max_dataset_size, only a subset is loaded.') + parser.add_argument('--preprocess', type=str, default='resize_and_crop', help='scaling and cropping of images at load time [resize_and_crop | crop | scale_width | scale_width_and_crop | none]') + parser.add_argument('--no_flip', action='store_true', help='if specified, do not flip the images for data augmentation') + parser.add_argument('--display_winsize', type=int, default=256, help='display window size for both visdom and HTML') + # additional parameters + parser.add_argument('--epoch', type=str, default='latest', help='which epoch to load? set to latest to use latest cached model') + parser.add_argument('--load_iter', type=int, default='0', help='which iteration to load? if load_iter > 0, the code will load models by iter_[load_iter]; otherwise, the code will load models by [epoch]') + parser.add_argument('--verbose', action='store_true', help='if specified, print more debugging information') + parser.add_argument('--suffix', default='', type=str, help='customized suffix: opt.name = opt.name + suffix: e.g., {model}_{netG}_size{load_size}') + + parser.add_argument('--data_dir', type=str, required=False, + help='input files directory images can be .png .jpg .tiff') + parser.add_argument('--output_dir', type=str, required=False, + help='result dir. result depth will be png. vides are JMPG as avi') + parser.add_argument('--savecrops', type=int, required=False) + parser.add_argument('--savewholeest', type=int, required=False) + parser.add_argument('--output_resolution', type=int, required=False, + help='0 for no restriction 1 for resize to input size') + parser.add_argument('--net_receptive_field_size', type=int, required=False) + parser.add_argument('--pix2pixsize', type=int, required=False) + parser.add_argument('--generatevideo', type=int, required=False) + parser.add_argument('--depthNet', type=int, required=False, help='0: midas 1:strurturedRL') + parser.add_argument('--R0', action='store_true') + parser.add_argument('--R20', action='store_true') + parser.add_argument('--Final', action='store_true') + parser.add_argument('--colorize_results', action='store_true') + parser.add_argument('--max_res', type=float, default=np.inf) + + self.initialized = True + return parser + + def gather_options(self): + """Initialize our parser with basic options(only once). + Add additional model-specific and dataset-specific options. + These options are defined in the function + in model and dataset classes. + """ + if not self.initialized: # check if it has been initialized + parser = argparse.ArgumentParser(formatter_class=argparse.ArgumentDefaultsHelpFormatter) + parser = self.initialize(parser) + + # get the basic options + opt, _ = parser.parse_known_args() + + # modify model-related parser options + model_name = opt.model + model_option_setter = models.get_option_setter(model_name) + parser = model_option_setter(parser, self.isTrain) + opt, _ = parser.parse_known_args() # parse again with new defaults + + # modify dataset-related parser options + # dataset_name = opt.dataset_mode + # dataset_option_setter = pix2pix.data.get_option_setter(dataset_name) + # parser = dataset_option_setter(parser, self.isTrain) + + # save and return the parser + self.parser = parser + #return parser.parse_args() #EVIL + return opt + + def print_options(self, opt): + """Print and save options + + It will print both current options and default values(if different). + It will save options into a text file / [checkpoints_dir] / opt.txt + """ + message = '' + message += '----------------- Options ---------------\n' + for k, v in sorted(vars(opt).items()): + comment = '' + default = self.parser.get_default(k) + if v != default: + comment = '\t[default: %s]' % str(default) + message += '{:>25}: {:<30}{}\n'.format(str(k), str(v), comment) + message += '----------------- End -------------------' + print(message) + + # save to the disk + expr_dir = os.path.join(opt.checkpoints_dir, opt.name) + util.mkdirs(expr_dir) + file_name = os.path.join(expr_dir, '{}_opt.txt'.format(opt.phase)) + with open(file_name, 'wt') as opt_file: + opt_file.write(message) + opt_file.write('\n') + + def parse(self): + """Parse our options, create checkpoints directory suffix, and set up gpu device.""" + opt = self.gather_options() + opt.isTrain = self.isTrain # train or test + + # process opt.suffix + if opt.suffix: + suffix = ('_' + opt.suffix.format(**vars(opt))) if opt.suffix != '' else '' + opt.name = opt.name + suffix + + #self.print_options(opt) + + # set gpu ids + str_ids = opt.gpu_ids.split(',') + opt.gpu_ids = [] + for str_id in str_ids: + id = int(str_id) + if id >= 0: + opt.gpu_ids.append(id) + #if len(opt.gpu_ids) > 0: + # torch.cuda.set_device(opt.gpu_ids[0]) + + self.opt = opt + return self.opt diff --git a/modules/control/proc/leres/pix2pix/options/test_options.py b/modules/control/proc/leres/pix2pix/options/test_options.py new file mode 100644 index 000000000..a3424b5e3 --- /dev/null +++ b/modules/control/proc/leres/pix2pix/options/test_options.py @@ -0,0 +1,22 @@ +from .base_options import BaseOptions + + +class TestOptions(BaseOptions): + """This class includes test options. + + It also includes shared options defined in BaseOptions. + """ + + def initialize(self, parser): + parser = BaseOptions.initialize(self, parser) # define shared options + parser.add_argument('--aspect_ratio', type=float, default=1.0, help='aspect ratio of result images') + parser.add_argument('--phase', type=str, default='test', help='train, val, test, etc') + # Dropout and Batchnorm has different behavioir during training and test. + parser.add_argument('--eval', action='store_true', help='use eval mode during test time.') + parser.add_argument('--num_test', type=int, default=50, help='how many test images to run') + # rewrite devalue values + parser.set_defaults(model='pix2pix4depth') + # To avoid cropping, the load_size should be the same as crop_size + parser.set_defaults(load_size=parser.get_default('crop_size')) + self.isTrain = False + return parser diff --git a/modules/control/proc/leres/pix2pix/util/__init__.py b/modules/control/proc/leres/pix2pix/util/__init__.py new file mode 100644 index 000000000..ae36f63d8 --- /dev/null +++ b/modules/control/proc/leres/pix2pix/util/__init__.py @@ -0,0 +1 @@ +"""This package includes a miscellaneous collection of useful helper functions.""" diff --git a/modules/control/proc/leres/pix2pix/util/util.py b/modules/control/proc/leres/pix2pix/util/util.py new file mode 100644 index 000000000..8a7aceaa0 --- /dev/null +++ b/modules/control/proc/leres/pix2pix/util/util.py @@ -0,0 +1,105 @@ +"""This module contains simple helper functions """ +from __future__ import print_function +import torch +import numpy as np +from PIL import Image +import os + + +def tensor2im(input_image, imtype=np.uint16): + """"Converts a Tensor array into a numpy image array. + + Parameters: + input_image (tensor) -- the input image tensor array + imtype (type) -- the desired type of the converted numpy array + """ + if not isinstance(input_image, np.ndarray): + if isinstance(input_image, torch.Tensor): # get the data from a variable + image_tensor = input_image.data + else: + return input_image + image_numpy = torch.squeeze(image_tensor).cpu().numpy() # convert it into a numpy array + image_numpy = (image_numpy + 1) / 2.0 * (2**16-1) # + else: # if it is a numpy array, do nothing + image_numpy = input_image + return image_numpy.astype(imtype) + + +def diagnose_network(net, name='network'): + """Calculate and print the mean of average absolute(gradients) + + Parameters: + net (torch network) -- Torch network + name (str) -- the name of the network + """ + mean = 0.0 + count = 0 + for param in net.parameters(): + if param.grad is not None: + mean += torch.mean(torch.abs(param.grad.data)) + count += 1 + if count > 0: + mean = mean / count + print(name) + print(mean) + + +def save_image(image_numpy, image_path, aspect_ratio=1.0): + """Save a numpy image to the disk + + Parameters: + image_numpy (numpy array) -- input numpy array + image_path (str) -- the path of the image + """ + image_pil = Image.fromarray(image_numpy) + + image_pil = image_pil.convert('I;16') + + # image_pil = Image.fromarray(image_numpy) + # h, w, _ = image_numpy.shape + # + # if aspect_ratio > 1.0: + # image_pil = image_pil.resize((h, int(w * aspect_ratio)), Image.BICUBIC) + # if aspect_ratio < 1.0: + # image_pil = image_pil.resize((int(h / aspect_ratio), w), Image.BICUBIC) + + image_pil.save(image_path) + + +def print_numpy(x, val=True, shp=False): + """Print the mean, min, max, median, std, and size of a numpy array + + Parameters: + val (bool) -- if print the values of the numpy array + shp (bool) -- if print the shape of the numpy array + """ + x = x.astype(np.float64) + if shp: + print('shape,', x.shape) + if val: + x = x.flatten() + print('mean = %3.3f, min = %3.3f, max = %3.3f, median = %3.3f, std=%3.3f' % ( + np.mean(x), np.min(x), np.max(x), np.median(x), np.std(x))) + + +def mkdirs(paths): + """create empty directories if they don't exist + + Parameters: + paths (str list) -- a list of directory paths + """ + if isinstance(paths, list) and not isinstance(paths, str): + for path in paths: + mkdir(path) + else: + mkdir(paths) + + +def mkdir(path): + """create a single empty directory if it didn't exist + + Parameters: + path (str) -- a single directory path + """ + if not os.path.exists(path): + os.makedirs(path) diff --git a/modules/control/proc/lineart.py b/modules/control/proc/lineart.py new file mode 100644 index 000000000..7f7aef10a --- /dev/null +++ b/modules/control/proc/lineart.py @@ -0,0 +1,166 @@ +import os +import warnings + +import cv2 +import numpy as np +import torch +import torch.nn as nn +from einops import rearrange +from huggingface_hub import hf_hub_download +from PIL import Image + +from modules.control.util import HWC3, resize_image + +norm_layer = nn.InstanceNorm2d + + +class ResidualBlock(nn.Module): + def __init__(self, in_features): + super(ResidualBlock, self).__init__() + + conv_block = [ nn.ReflectionPad2d(1), + nn.Conv2d(in_features, in_features, 3), + norm_layer(in_features), + nn.ReLU(inplace=True), + nn.ReflectionPad2d(1), + nn.Conv2d(in_features, in_features, 3), + norm_layer(in_features) + ] + + self.conv_block = nn.Sequential(*conv_block) + + def forward(self, x): + return x + self.conv_block(x) + + +class Generator(nn.Module): + def __init__(self, input_nc, output_nc, n_residual_blocks=9, sigmoid=True): + super(Generator, self).__init__() + + # Initial convolution block + model0 = [ nn.ReflectionPad2d(3), + nn.Conv2d(input_nc, 64, 7), + norm_layer(64), + nn.ReLU(inplace=True) ] + self.model0 = nn.Sequential(*model0) + + # Downsampling + model1 = [] + in_features = 64 + out_features = in_features*2 + for _ in range(2): + model1 += [ nn.Conv2d(in_features, out_features, 3, stride=2, padding=1), + norm_layer(out_features), + nn.ReLU(inplace=True) ] + in_features = out_features + out_features = in_features*2 + self.model1 = nn.Sequential(*model1) + + model2 = [] + # Residual blocks + for _ in range(n_residual_blocks): + model2 += [ResidualBlock(in_features)] + self.model2 = nn.Sequential(*model2) + + # Upsampling + model3 = [] + out_features = in_features//2 + for _ in range(2): + model3 += [ nn.ConvTranspose2d(in_features, out_features, 3, stride=2, padding=1, output_padding=1), + norm_layer(out_features), + nn.ReLU(inplace=True) ] + in_features = out_features + out_features = in_features//2 + self.model3 = nn.Sequential(*model3) + + # Output layer + model4 = [ nn.ReflectionPad2d(3), + nn.Conv2d(64, output_nc, 7)] + if sigmoid: + model4 += [nn.Sigmoid()] + + self.model4 = nn.Sequential(*model4) + + def forward(self, x, cond=None): # pylint: disable=unused-argument + out = self.model0(x) + out = self.model1(out) + out = self.model2(out) + out = self.model3(out) + out = self.model4(out) + + return out + + +class LineartDetector: + def __init__(self, model, coarse_model): + self.model = model + self.model_coarse = coarse_model + + @classmethod + def from_pretrained(cls, pretrained_model_or_path, filename=None, coarse_filename=None, cache_dir=None): + filename = filename or "sk_model.pth" + coarse_filename = coarse_filename or "sk_model2.pth" + + if os.path.isdir(pretrained_model_or_path): + model_path = os.path.join(pretrained_model_or_path, filename) + coarse_model_path = os.path.join(pretrained_model_or_path, coarse_filename) + else: + model_path = hf_hub_download(pretrained_model_or_path, filename, cache_dir=cache_dir) + coarse_model_path = hf_hub_download(pretrained_model_or_path, coarse_filename, cache_dir=cache_dir) + + model = Generator(3, 1, 3) + model.load_state_dict(torch.load(model_path, map_location=torch.device('cpu'))) + model.eval() + + coarse_model = Generator(3, 1, 3) + coarse_model.load_state_dict(torch.load(coarse_model_path, map_location=torch.device('cpu'))) + coarse_model.eval() + + return cls(model, coarse_model) + + def to(self, device): + self.model.to(device) + self.model_coarse.to(device) + return self + + def __call__(self, input_image, coarse=False, detect_resolution=512, image_resolution=512, output_type="pil", **kwargs): + if "return_pil" in kwargs: + warnings.warn("return_pil is deprecated. Use output_type instead.", DeprecationWarning) + output_type = "pil" if kwargs["return_pil"] else "np" + if type(output_type) is bool: + warnings.warn("Passing `True` or `False` to `output_type` is deprecated and will raise an error in future versions") + if output_type: + output_type = "pil" + + device = next(iter(self.model.parameters())).device + if not isinstance(input_image, np.ndarray): + input_image = np.array(input_image, dtype=np.uint8) + + input_image = HWC3(input_image) + input_image = resize_image(input_image, detect_resolution) + + model = self.model_coarse if coarse else self.model + assert input_image.ndim == 3 + image = input_image + image = torch.from_numpy(image).float().to(device) + image = image / 255.0 + image = rearrange(image, 'h w c -> 1 c h w') + line = model(image)[0][0] + + line = line.cpu().numpy() + line = (line * 255.0).clip(0, 255).astype(np.uint8) + + detected_map = line + + detected_map = HWC3(detected_map) + + img = resize_image(input_image, image_resolution) + H, W, _C = img.shape + + detected_map = cv2.resize(detected_map, (W, H), interpolation=cv2.INTER_LINEAR) + detected_map = 255 - detected_map + + if output_type == "pil": + detected_map = Image.fromarray(detected_map) + + return detected_map diff --git a/modules/control/proc/lineart_anime.py b/modules/control/proc/lineart_anime.py new file mode 100644 index 000000000..9eb4fcc09 --- /dev/null +++ b/modules/control/proc/lineart_anime.py @@ -0,0 +1,188 @@ +import functools +import os +import warnings + +import cv2 +import numpy as np +import torch +import torch.nn as nn +from einops import rearrange +from huggingface_hub import hf_hub_download +from PIL import Image + +from modules.control.util import HWC3, resize_image + + +class UnetGenerator(nn.Module): + """Create a Unet-based generator""" + + def __init__(self, input_nc, output_nc, num_downs, ngf=64, norm_layer=nn.BatchNorm2d, use_dropout=False): + """Construct a Unet generator + Parameters: + input_nc (int) -- the number of channels in input images + output_nc (int) -- the number of channels in output images + num_downs (int) -- the number of downsamplings in UNet. For example, # if |num_downs| == 7, + image of size 128x128 will become of size 1x1 # at the bottleneck + ngf (int) -- the number of filters in the last conv layer + norm_layer -- normalization layer + We construct the U-Net from the innermost layer to the outermost layer. + It is a recursive process. + """ + super(UnetGenerator, self).__init__() + # construct unet structure + unet_block = UnetSkipConnectionBlock(ngf * 8, ngf * 8, input_nc=None, submodule=None, norm_layer=norm_layer, innermost=True) # add the innermost layer + for _ in range(num_downs - 5): # add intermediate layers with ngf * 8 filters + unet_block = UnetSkipConnectionBlock(ngf * 8, ngf * 8, input_nc=None, submodule=unet_block, norm_layer=norm_layer, use_dropout=use_dropout) + # gradually reduce the number of filters from ngf * 8 to ngf + unet_block = UnetSkipConnectionBlock(ngf * 4, ngf * 8, input_nc=None, submodule=unet_block, norm_layer=norm_layer) + unet_block = UnetSkipConnectionBlock(ngf * 2, ngf * 4, input_nc=None, submodule=unet_block, norm_layer=norm_layer) + unet_block = UnetSkipConnectionBlock(ngf, ngf * 2, input_nc=None, submodule=unet_block, norm_layer=norm_layer) + self.model = UnetSkipConnectionBlock(output_nc, ngf, input_nc=input_nc, submodule=unet_block, outermost=True, norm_layer=norm_layer) # add the outermost layer + + def forward(self, input): # pylint: disable=redefined-builtin + """Standard forward""" + return self.model(input) + + +class UnetSkipConnectionBlock(nn.Module): + """Defines the Unet submodule with skip connection. + X -------------------identity---------------------- + |-- downsampling -- |submodule| -- upsampling --| + """ + + def __init__(self, outer_nc, inner_nc, input_nc=None, + submodule=None, outermost=False, innermost=False, norm_layer=nn.BatchNorm2d, use_dropout=False): + """Construct a Unet submodule with skip connections. + Parameters: + outer_nc (int) -- the number of filters in the outer conv layer + inner_nc (int) -- the number of filters in the inner conv layer + input_nc (int) -- the number of channels in input images/features + submodule (UnetSkipConnectionBlock) -- previously defined submodules + outermost (bool) -- if this module is the outermost module + innermost (bool) -- if this module is the innermost module + norm_layer -- normalization layer + use_dropout (bool) -- if use dropout layers. + """ + super(UnetSkipConnectionBlock, self).__init__() + self.outermost = outermost + if type(norm_layer) == functools.partial: + use_bias = norm_layer.func == nn.InstanceNorm2d + else: + use_bias = norm_layer == nn.InstanceNorm2d + if input_nc is None: + input_nc = outer_nc + downconv = nn.Conv2d(input_nc, inner_nc, kernel_size=4, + stride=2, padding=1, bias=use_bias) + downrelu = nn.LeakyReLU(0.2, True) + downnorm = norm_layer(inner_nc) + uprelu = nn.ReLU(True) + upnorm = norm_layer(outer_nc) + + if outermost: + upconv = nn.ConvTranspose2d(inner_nc * 2, outer_nc, + kernel_size=4, stride=2, + padding=1) + down = [downconv] + up = [uprelu, upconv, nn.Tanh()] + model = down + [submodule] + up + elif innermost: + upconv = nn.ConvTranspose2d(inner_nc, outer_nc, + kernel_size=4, stride=2, + padding=1, bias=use_bias) + down = [downrelu, downconv] + up = [uprelu, upconv, upnorm] + model = down + up + else: + upconv = nn.ConvTranspose2d(inner_nc * 2, outer_nc, + kernel_size=4, stride=2, + padding=1, bias=use_bias) + down = [downrelu, downconv, downnorm] + up = [uprelu, upconv, upnorm] + + if use_dropout: + model = down + [submodule] + up + [nn.Dropout(0.5)] + else: + model = down + [submodule] + up + + self.model = nn.Sequential(*model) + + def forward(self, x): + if self.outermost: + return self.model(x) + else: # add skip connections + return torch.cat([x, self.model(x)], 1) + + +class LineartAnimeDetector: + def __init__(self, model): + self.model = model + + @classmethod + def from_pretrained(cls, pretrained_model_or_path, filename=None, cache_dir=None): + filename = filename or "netG.pth" + + if os.path.isdir(pretrained_model_or_path): + model_path = os.path.join(pretrained_model_or_path, filename) + else: + model_path = hf_hub_download(pretrained_model_or_path, filename, cache_dir=cache_dir) + + norm_layer = functools.partial(nn.InstanceNorm2d, affine=False, track_running_stats=False) + net = UnetGenerator(3, 1, 8, 64, norm_layer=norm_layer, use_dropout=False) + ckpt = torch.load(model_path) + for key in list(ckpt.keys()): + if 'module.' in key: + ckpt[key.replace('module.', '')] = ckpt[key] + del ckpt[key] + net.load_state_dict(ckpt) + net.eval() + + return cls(net) + + def to(self, device): + self.model.to(device) + return self + + def __call__(self, input_image, detect_resolution=512, image_resolution=512, output_type="pil", **kwargs): + if "return_pil" in kwargs: + warnings.warn("return_pil is deprecated. Use output_type instead.", DeprecationWarning) + output_type = "pil" if kwargs["return_pil"] else "np" + if type(output_type) is bool: + warnings.warn("Passing `True` or `False` to `output_type` is deprecated and will raise an error in future versions") + if output_type: + output_type = "pil" + + device = next(iter(self.model.parameters())).device + if not isinstance(input_image, np.ndarray): + input_image = np.array(input_image, dtype=np.uint8) + + input_image = HWC3(input_image) + input_image = resize_image(input_image, detect_resolution) + + H, W, _C = input_image.shape + Hn = 256 * int(np.ceil(float(H) / 256.0)) + Wn = 256 * int(np.ceil(float(W) / 256.0)) + img = cv2.resize(input_image, (Wn, Hn), interpolation=cv2.INTER_CUBIC) + image_feed = torch.from_numpy(img).float().to(device) + image_feed = image_feed / 127.5 - 1.0 + image_feed = rearrange(image_feed, 'h w c -> 1 c h w') + + line = self.model(image_feed)[0, 0] * 127.5 + 127.5 + line = line.cpu().numpy() + + line = cv2.resize(line, (W, H), interpolation=cv2.INTER_CUBIC) + line = line.clip(0, 255).astype(np.uint8) + + detected_map = line + + detected_map = HWC3(detected_map) + + img = resize_image(input_image, image_resolution) + H, W, _C = img.shape + + detected_map = cv2.resize(detected_map, (W, H), interpolation=cv2.INTER_LINEAR) + detected_map = 255 - detected_map + + if output_type == "pil": + detected_map = Image.fromarray(detected_map) + + return detected_map diff --git a/modules/control/proc/mediapipe_face.py b/modules/control/proc/mediapipe_face.py new file mode 100644 index 000000000..187f14765 --- /dev/null +++ b/modules/control/proc/mediapipe_face.py @@ -0,0 +1,51 @@ +import warnings +from typing import Union +import cv2 +import numpy as np +from PIL import Image +from modules.control.util import HWC3, resize_image + + +class MediapipeFaceDetector: + def __call__(self, + input_image: Union[np.ndarray, Image.Image] = None, + max_faces: int = 1, + min_confidence: float = 0.5, + output_type: str = "pil", + detect_resolution: int = 512, + image_resolution: int = 512, + **kwargs): + + from .mediapipe_face_util import generate_annotation + if "image" in kwargs: + warnings.warn("image is deprecated, please use `input_image=...` instead.", DeprecationWarning) + input_image = kwargs.pop("image") + if input_image is None: + raise ValueError("input_image must be defined.") + + if "return_pil" in kwargs: + warnings.warn("return_pil is deprecated. Use output_type instead.", DeprecationWarning) + output_type = "pil" if kwargs["return_pil"] else "np" + if type(output_type) is bool: + warnings.warn("Passing `True` or `False` to `output_type` is deprecated and will raise an error in future versions") + if output_type: + output_type = "pil" + + if not isinstance(input_image, np.ndarray): + input_image = np.array(input_image, dtype=np.uint8) + + input_image = HWC3(input_image) + input_image = resize_image(input_image, detect_resolution) + + detected_map = generate_annotation(input_image, max_faces, min_confidence) + detected_map = HWC3(detected_map) + + img = resize_image(input_image, image_resolution) + H, W, _C = img.shape + + detected_map = cv2.resize(detected_map, (W, H), interpolation=cv2.INTER_LINEAR) + + if output_type == "pil": + detected_map = Image.fromarray(detected_map) + + return detected_map diff --git a/modules/control/proc/mediapipe_face_util.py b/modules/control/proc/mediapipe_face_util.py new file mode 100644 index 000000000..40ec0d204 --- /dev/null +++ b/modules/control/proc/mediapipe_face_util.py @@ -0,0 +1,162 @@ +from typing import Mapping +import numpy as np +from modules.shared import log + +try: + import mediapipe as mp +except ImportError: + log.error("Control processor MediaPipe: mediapipe not installed") + mp = None + +if mp: + mp_drawing = mp.solutions.drawing_utils + mp_drawing_styles = mp.solutions.drawing_styles + mp_face_detection = mp.solutions.face_detection # Only for counting faces. + mp_face_mesh = mp.solutions.face_mesh + mp_face_connections = mp.solutions.face_mesh_connections.FACEMESH_TESSELATION + mp_hand_connections = mp.solutions.hands_connections.HAND_CONNECTIONS + mp_body_connections = mp.solutions.pose_connections.POSE_CONNECTIONS + + DrawingSpec = mp.solutions.drawing_styles.DrawingSpec + PoseLandmark = mp.solutions.drawing_styles.PoseLandmark + + min_face_size_pixels: int = 64 + f_thick = 2 + f_rad = 1 + right_iris_draw = DrawingSpec(color=(10, 200, 250), thickness=f_thick, circle_radius=f_rad) + right_eye_draw = DrawingSpec(color=(10, 200, 180), thickness=f_thick, circle_radius=f_rad) + right_eyebrow_draw = DrawingSpec(color=(10, 220, 180), thickness=f_thick, circle_radius=f_rad) + left_iris_draw = DrawingSpec(color=(250, 200, 10), thickness=f_thick, circle_radius=f_rad) + left_eye_draw = DrawingSpec(color=(180, 200, 10), thickness=f_thick, circle_radius=f_rad) + left_eyebrow_draw = DrawingSpec(color=(180, 220, 10), thickness=f_thick, circle_radius=f_rad) + mouth_draw = DrawingSpec(color=(10, 180, 10), thickness=f_thick, circle_radius=f_rad) + head_draw = DrawingSpec(color=(10, 200, 10), thickness=f_thick, circle_radius=f_rad) + + # mp_face_mesh.FACEMESH_CONTOURS has all the items we care about. + face_connection_spec = {} + for edge in mp_face_mesh.FACEMESH_FACE_OVAL: + face_connection_spec[edge] = head_draw + for edge in mp_face_mesh.FACEMESH_LEFT_EYE: + face_connection_spec[edge] = left_eye_draw + for edge in mp_face_mesh.FACEMESH_LEFT_EYEBROW: + face_connection_spec[edge] = left_eyebrow_draw + # for edge in mp_face_mesh.FACEMESH_LEFT_IRIS: + # face_connection_spec[edge] = left_iris_draw + for edge in mp_face_mesh.FACEMESH_RIGHT_EYE: + face_connection_spec[edge] = right_eye_draw + for edge in mp_face_mesh.FACEMESH_RIGHT_EYEBROW: + face_connection_spec[edge] = right_eyebrow_draw + # for edge in mp_face_mesh.FACEMESH_RIGHT_IRIS: + # face_connection_spec[edge] = right_iris_draw + for edge in mp_face_mesh.FACEMESH_LIPS: + face_connection_spec[edge] = mouth_draw + iris_landmark_spec = {468: right_iris_draw, 473: left_iris_draw} + + +def draw_pupils(image, landmark_list, drawing_spec, halfwidth: int = 2): + """We have a custom function to draw the pupils because the mp.draw_landmarks method requires a parameter for all + landmarks. Until our PR is merged into mediapipe, we need this separate method.""" + if len(image.shape) != 3: + raise ValueError("Input image must be H,W,C.") + image_rows, image_cols, image_channels = image.shape + if image_channels != 3: # BGR channels + raise ValueError('Input image must contain three channel bgr data.') + for idx, landmark in enumerate(landmark_list.landmark): + if ( + (landmark.HasField('visibility') and landmark.visibility < 0.9) or + (landmark.HasField('presence') and landmark.presence < 0.5) + ): + continue + if landmark.x >= 1.0 or landmark.x < 0 or landmark.y >= 1.0 or landmark.y < 0: + continue + image_x = int(image_cols*landmark.x) + image_y = int(image_rows*landmark.y) + draw_color = None + if isinstance(drawing_spec, Mapping): + if drawing_spec.get(idx) is None: + continue + else: + draw_color = drawing_spec[idx].color + elif isinstance(drawing_spec, DrawingSpec): + draw_color = drawing_spec.color + image[image_y-halfwidth:image_y+halfwidth, image_x-halfwidth:image_x+halfwidth, :] = draw_color + + +def reverse_channels(image): + """Given a numpy array in RGB form, convert to BGR. Will also convert from BGR to RGB.""" + # im[:,:,::-1] is a neat hack to convert BGR to RGB by reversing the indexing order. + # im[:,:,::[2,1,0]] would also work but makes a copy of the data. + return image[:, :, ::-1] + + +def generate_annotation( + img_rgb, + max_faces: int, + min_confidence: float +): + """ + Find up to 'max_faces' inside the provided input image. + If min_face_size_pixels is provided and nonzero it will be used to filter faces that occupy less than this many + pixels in the image. + """ + if mp is None: + return img_rgb + with mp_face_mesh.FaceMesh( + static_image_mode=True, + max_num_faces=max_faces, + refine_landmarks=True, + min_detection_confidence=min_confidence, + ) as facemesh: + img_height, img_width, img_channels = img_rgb.shape + assert img_channels == 3 + + results = facemesh.process(img_rgb).multi_face_landmarks + + if results is None: + print("No faces detected in controlnet image for Mediapipe face annotator.") + return np.zeros_like(img_rgb) + + # Filter faces that are too small + filtered_landmarks = [] + for lm in results: + landmarks = lm.landmark + face_rect = [ + landmarks[0].x, + landmarks[0].y, + landmarks[0].x, + landmarks[0].y, + ] # Left, up, right, down. + for i in range(len(landmarks)): + face_rect[0] = min(face_rect[0], landmarks[i].x) + face_rect[1] = min(face_rect[1], landmarks[i].y) + face_rect[2] = max(face_rect[2], landmarks[i].x) + face_rect[3] = max(face_rect[3], landmarks[i].y) + if min_face_size_pixels > 0: + face_width = abs(face_rect[2] - face_rect[0]) + face_height = abs(face_rect[3] - face_rect[1]) + face_width_pixels = face_width * img_width + face_height_pixels = face_height * img_height + face_size = min(face_width_pixels, face_height_pixels) + if face_size >= min_face_size_pixels: + filtered_landmarks.append(lm) + else: + filtered_landmarks.append(lm) + + # Annotations are drawn in BGR for some reason, but we don't need to flip a zero-filled image at the start. + empty = np.zeros_like(img_rgb) + + # Draw detected faces: + for face_landmarks in filtered_landmarks: + mp_drawing.draw_landmarks( + empty, + face_landmarks, + connections=face_connection_spec.keys(), + landmark_drawing_spec=None, + connection_drawing_spec=face_connection_spec + ) + draw_pupils(empty, face_landmarks, iris_landmark_spec, 2) + + # Flip BGR back to RGB. + empty = reverse_channels(empty).copy() + + return empty diff --git a/modules/control/proc/midas/LICENSE b/modules/control/proc/midas/LICENSE new file mode 100644 index 000000000..277b5c11b --- /dev/null +++ b/modules/control/proc/midas/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2019 Intel ISL (Intel Intelligent Systems Lab) + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/modules/control/proc/midas/__init__.py b/modules/control/proc/midas/__init__.py new file mode 100644 index 000000000..13a5ad061 --- /dev/null +++ b/modules/control/proc/midas/__init__.py @@ -0,0 +1,94 @@ +import os + +import cv2 +import numpy as np +import torch +from einops import rearrange +from huggingface_hub import hf_hub_download +from PIL import Image + +from modules.control.util import HWC3, resize_image +from .api import MiDaSInference + + +class MidasDetector: + def __init__(self, model): + self.model = model + + @classmethod + def from_pretrained(cls, pretrained_model_or_path, model_type="dpt_hybrid", filename=None, cache_dir=None): + if pretrained_model_or_path == "lllyasviel/ControlNet": + filename = filename or "annotator/ckpts/dpt_hybrid-midas-501f0c75.pt" + else: + filename = filename or "dpt_hybrid-midas-501f0c75.pt" + + if os.path.isdir(pretrained_model_or_path): + model_path = os.path.join(pretrained_model_or_path, filename) + else: + model_path = hf_hub_download(pretrained_model_or_path, filename, cache_dir=cache_dir) + + model = MiDaSInference(model_type=model_type, model_path=model_path) + + return cls(model) + + + def to(self, device): + self.model.to(device) + return self + + def __call__(self, input_image, a=np.pi * 2.0, bg_th=0.1, depth_and_normal=False, detect_resolution=512, image_resolution=512, output_type=None): + device = next(iter(self.model.parameters())).device + if not isinstance(input_image, np.ndarray): + input_image = np.array(input_image, dtype=np.uint8) + output_type = output_type or "pil" + else: + output_type = output_type or "np" + + input_image = HWC3(input_image) + input_image = resize_image(input_image, detect_resolution) + + assert input_image.ndim == 3 + image_depth = input_image + image_depth = torch.from_numpy(image_depth).float() + image_depth = image_depth.to(device) + image_depth = image_depth / 127.5 - 1.0 + image_depth = rearrange(image_depth, 'h w c -> 1 c h w') + depth = self.model(image_depth)[0] + + depth_pt = depth.clone() + depth_pt -= torch.min(depth_pt) + depth_pt /= torch.max(depth_pt) + depth_pt = depth_pt.cpu().numpy() + depth_image = (depth_pt * 255.0).clip(0, 255).astype(np.uint8) + + if depth_and_normal: + depth_np = depth.cpu().numpy() + x = cv2.Sobel(depth_np, cv2.CV_32F, 1, 0, ksize=3) + y = cv2.Sobel(depth_np, cv2.CV_32F, 0, 1, ksize=3) + z = np.ones_like(x) * a + x[depth_pt < bg_th] = 0 + y[depth_pt < bg_th] = 0 + normal = np.stack([x, y, z], axis=2) + normal /= np.sum(normal ** 2.0, axis=2, keepdims=True) ** 0.5 + normal_image = (normal * 127.5 + 127.5).clip(0, 255).astype(np.uint8)[:, :, ::-1] + + depth_image = HWC3(depth_image) + if depth_and_normal: + normal_image = HWC3(normal_image) + + img = resize_image(input_image, image_resolution) + H, W, C = img.shape + + depth_image = cv2.resize(depth_image, (W, H), interpolation=cv2.INTER_LINEAR) + if depth_and_normal: + normal_image = cv2.resize(normal_image, (W, H), interpolation=cv2.INTER_LINEAR) + + if output_type == "pil": + depth_image = Image.fromarray(depth_image) + if depth_and_normal: + normal_image = Image.fromarray(normal_image) + + if depth_and_normal: + return depth_image, normal_image + else: + return depth_image diff --git a/modules/control/proc/midas/api.py b/modules/control/proc/midas/api.py new file mode 100644 index 000000000..b1540cd9e --- /dev/null +++ b/modules/control/proc/midas/api.py @@ -0,0 +1,168 @@ +# based on https://github.com/isl-org/MiDaS + +import cv2 +import os +import torch +import torch.nn as nn +from torchvision.transforms import Compose + +from .midas.dpt_depth import DPTDepthModel +from .midas.midas_net import MidasNet +from .midas.midas_net_custom import MidasNet_small +from .midas.transforms import Resize, NormalizeImage, PrepareForNet +from modules.control.util import annotator_ckpts_path + + +ISL_PATHS = { + "dpt_large": os.path.join(annotator_ckpts_path, "dpt_large-midas-2f21e586.pt"), + "dpt_hybrid": os.path.join(annotator_ckpts_path, "dpt_hybrid-midas-501f0c75.pt"), + "midas_v21": "", + "midas_v21_small": "", +} + +remote_model_path = "https://huggingface.co/lllyasviel/ControlNet/resolve/main/annotator/ckpts/dpt_hybrid-midas-501f0c75.pt" + + +def disabled_train(self, mode=True): + """Overwrite model.train with this function to make sure train/eval mode + does not change anymore.""" + return self + + +def load_midas_transform(model_type): + # https://github.com/isl-org/MiDaS/blob/master/run.py + # load transform only + if model_type == "dpt_large": # DPT-Large + net_w, net_h = 384, 384 + resize_mode = "minimal" + normalization = NormalizeImage(mean=[0.5, 0.5, 0.5], std=[0.5, 0.5, 0.5]) + + elif model_type == "dpt_hybrid": # DPT-Hybrid + net_w, net_h = 384, 384 + resize_mode = "minimal" + normalization = NormalizeImage(mean=[0.5, 0.5, 0.5], std=[0.5, 0.5, 0.5]) + + elif model_type == "midas_v21": + net_w, net_h = 384, 384 + resize_mode = "upper_bound" + normalization = NormalizeImage(mean=[0.485, 0.456, 0.406], std=[0.229, 0.224, 0.225]) + + elif model_type == "midas_v21_small": + net_w, net_h = 256, 256 + resize_mode = "upper_bound" + normalization = NormalizeImage(mean=[0.485, 0.456, 0.406], std=[0.229, 0.224, 0.225]) + + else: + raise AssertionError(f"model_type '{model_type}' not implemented, use: --model_type large") + + transform = Compose( + [ + Resize( + net_w, + net_h, + resize_target=None, + keep_aspect_ratio=True, + ensure_multiple_of=32, + resize_method=resize_mode, + image_interpolation_method=cv2.INTER_CUBIC, + ), + normalization, + PrepareForNet(), + ] + ) + + return transform + + +def load_model(model_type, model_path=None): + # https://github.com/isl-org/MiDaS/blob/master/run.py + # load network + model_path = model_path or ISL_PATHS[model_type] + if model_type == "dpt_large": # DPT-Large + model = DPTDepthModel( + path=model_path, + backbone="vitl16_384", + non_negative=True, + ) + net_w, net_h = 384, 384 + resize_mode = "minimal" + normalization = NormalizeImage(mean=[0.5, 0.5, 0.5], std=[0.5, 0.5, 0.5]) + + elif model_type == "dpt_hybrid": # DPT-Hybrid + if not os.path.exists(model_path): + from basicsr.utils.download_util import load_file_from_url + load_file_from_url(remote_model_path, model_dir=annotator_ckpts_path) + + model = DPTDepthModel( + path=model_path, + backbone="vitb_rn50_384", + non_negative=True, + ) + net_w, net_h = 384, 384 + resize_mode = "minimal" + normalization = NormalizeImage(mean=[0.5, 0.5, 0.5], std=[0.5, 0.5, 0.5]) + + elif model_type == "midas_v21": + model = MidasNet(model_path, non_negative=True) + net_w, net_h = 384, 384 + resize_mode = "upper_bound" + normalization = NormalizeImage( + mean=[0.485, 0.456, 0.406], std=[0.229, 0.224, 0.225] + ) + + elif model_type == "midas_v21_small": + model = MidasNet_small(model_path, features=64, backbone="efficientnet_lite3", exportable=True, + non_negative=True, blocks={'expand': True}) + net_w, net_h = 256, 256 + resize_mode = "upper_bound" + normalization = NormalizeImage( + mean=[0.485, 0.456, 0.406], std=[0.229, 0.224, 0.225] + ) + + else: + print(f"model_type '{model_type}' not implemented, use: --model_type large") + raise AssertionError + + transform = Compose( + [ + Resize( + net_w, + net_h, + resize_target=None, + keep_aspect_ratio=True, + ensure_multiple_of=32, + resize_method=resize_mode, + image_interpolation_method=cv2.INTER_CUBIC, + ), + normalization, + PrepareForNet(), + ] + ) + + return model.eval(), transform + + +class MiDaSInference(nn.Module): + MODEL_TYPES_TORCH_HUB = [ + "DPT_Large", + "DPT_Hybrid", + "MiDaS_small" + ] + MODEL_TYPES_ISL = [ + "dpt_large", + "dpt_hybrid", + "midas_v21", + "midas_v21_small", + ] + + def __init__(self, model_type, model_path): + super().__init__() + assert (model_type in self.MODEL_TYPES_ISL) + model, _ = load_model(model_type, model_path) + self.model = model + self.model.train = disabled_train + + def forward(self, x): + prediction = self.model(x) + return prediction + diff --git a/modules/control/proc/midas/midas/__init__.py b/modules/control/proc/midas/midas/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/modules/control/proc/midas/midas/base_model.py b/modules/control/proc/midas/midas/base_model.py new file mode 100644 index 000000000..5cf430239 --- /dev/null +++ b/modules/control/proc/midas/midas/base_model.py @@ -0,0 +1,16 @@ +import torch + + +class BaseModel(torch.nn.Module): + def load(self, path): + """Load model from file. + + Args: + path (str): file path + """ + parameters = torch.load(path, map_location=torch.device('cpu')) + + if "optimizer" in parameters: + parameters = parameters["model"] + + self.load_state_dict(parameters) diff --git a/modules/control/proc/midas/midas/blocks.py b/modules/control/proc/midas/midas/blocks.py new file mode 100644 index 000000000..cb840ded3 --- /dev/null +++ b/modules/control/proc/midas/midas/blocks.py @@ -0,0 +1,342 @@ +import torch +import torch.nn as nn + +from .vit import ( + _make_pretrained_vitb_rn50_384, + _make_pretrained_vitl16_384, + _make_pretrained_vitb16_384, + forward_vit, +) + +def _make_encoder(backbone, features, use_pretrained, groups=1, expand=False, exportable=True, hooks=None, use_vit_only=False, use_readout="ignore",): + if backbone == "vitl16_384": + pretrained = _make_pretrained_vitl16_384( + use_pretrained, hooks=hooks, use_readout=use_readout + ) + scratch = _make_scratch( + [256, 512, 1024, 1024], features, groups=groups, expand=expand + ) # ViT-L/16 - 85.0% Top1 (backbone) + elif backbone == "vitb_rn50_384": + pretrained = _make_pretrained_vitb_rn50_384( + use_pretrained, + hooks=hooks, + use_vit_only=use_vit_only, + use_readout=use_readout, + ) + scratch = _make_scratch( + [256, 512, 768, 768], features, groups=groups, expand=expand + ) # ViT-H/16 - 85.0% Top1 (backbone) + elif backbone == "vitb16_384": + pretrained = _make_pretrained_vitb16_384( + use_pretrained, hooks=hooks, use_readout=use_readout + ) + scratch = _make_scratch( + [96, 192, 384, 768], features, groups=groups, expand=expand + ) # ViT-B/16 - 84.6% Top1 (backbone) + elif backbone == "resnext101_wsl": + pretrained = _make_pretrained_resnext101_wsl(use_pretrained) + scratch = _make_scratch([256, 512, 1024, 2048], features, groups=groups, expand=expand) # efficientnet_lite3 + elif backbone == "efficientnet_lite3": + pretrained = _make_pretrained_efficientnet_lite3(use_pretrained, exportable=exportable) + scratch = _make_scratch([32, 48, 136, 384], features, groups=groups, expand=expand) # efficientnet_lite3 + else: + print(f"Backbone '{backbone}' not implemented") + raise AssertionError + + return pretrained, scratch + + +def _make_scratch(in_shape, out_shape, groups=1, expand=False): + scratch = nn.Module() + + out_shape1 = out_shape + out_shape2 = out_shape + out_shape3 = out_shape + out_shape4 = out_shape + if expand is True: + out_shape1 = out_shape + out_shape2 = out_shape*2 + out_shape3 = out_shape*4 + out_shape4 = out_shape*8 + + scratch.layer1_rn = nn.Conv2d( + in_shape[0], out_shape1, kernel_size=3, stride=1, padding=1, bias=False, groups=groups + ) + scratch.layer2_rn = nn.Conv2d( + in_shape[1], out_shape2, kernel_size=3, stride=1, padding=1, bias=False, groups=groups + ) + scratch.layer3_rn = nn.Conv2d( + in_shape[2], out_shape3, kernel_size=3, stride=1, padding=1, bias=False, groups=groups + ) + scratch.layer4_rn = nn.Conv2d( + in_shape[3], out_shape4, kernel_size=3, stride=1, padding=1, bias=False, groups=groups + ) + + return scratch + + +def _make_pretrained_efficientnet_lite3(use_pretrained, exportable=False): + efficientnet = torch.hub.load( + "rwightman/gen-efficientnet-pytorch", + "tf_efficientnet_lite3", + pretrained=use_pretrained, + exportable=exportable + ) + return _make_efficientnet_backbone(efficientnet) + + +def _make_efficientnet_backbone(effnet): + pretrained = nn.Module() + + pretrained.layer1 = nn.Sequential( + effnet.conv_stem, effnet.bn1, effnet.act1, *effnet.blocks[0:2] + ) + pretrained.layer2 = nn.Sequential(*effnet.blocks[2:3]) + pretrained.layer3 = nn.Sequential(*effnet.blocks[3:5]) + pretrained.layer4 = nn.Sequential(*effnet.blocks[5:9]) + + return pretrained + + +def _make_resnet_backbone(resnet): + pretrained = nn.Module() + pretrained.layer1 = nn.Sequential( + resnet.conv1, resnet.bn1, resnet.relu, resnet.maxpool, resnet.layer1 + ) + + pretrained.layer2 = resnet.layer2 + pretrained.layer3 = resnet.layer3 + pretrained.layer4 = resnet.layer4 + + return pretrained + + +def _make_pretrained_resnext101_wsl(use_pretrained): + resnet = torch.hub.load("facebookresearch/WSL-Images", "resnext101_32x8d_wsl") + return _make_resnet_backbone(resnet) + + + +class Interpolate(nn.Module): + """Interpolation module. + """ + + def __init__(self, scale_factor, mode, align_corners=False): + """Init. + + Args: + scale_factor (float): scaling + mode (str): interpolation mode + """ + super(Interpolate, self).__init__() + + self.interp = nn.functional.interpolate + self.scale_factor = scale_factor + self.mode = mode + self.align_corners = align_corners + + def forward(self, x): + """Forward pass. + + Args: + x (tensor): input + + Returns: + tensor: interpolated data + """ + + x = self.interp( + x, scale_factor=self.scale_factor, mode=self.mode, align_corners=self.align_corners + ) + + return x + + +class ResidualConvUnit(nn.Module): + """Residual convolution module. + """ + + def __init__(self, features): + """Init. + + Args: + features (int): number of features + """ + super().__init__() + + self.conv1 = nn.Conv2d( + features, features, kernel_size=3, stride=1, padding=1, bias=True + ) + + self.conv2 = nn.Conv2d( + features, features, kernel_size=3, stride=1, padding=1, bias=True + ) + + self.relu = nn.ReLU(inplace=True) + + def forward(self, x): + """Forward pass. + + Args: + x (tensor): input + + Returns: + tensor: output + """ + out = self.relu(x) + out = self.conv1(out) + out = self.relu(out) + out = self.conv2(out) + + return out + x + + +class FeatureFusionBlock(nn.Module): + """Feature fusion block. + """ + + def __init__(self, features): + """Init. + + Args: + features (int): number of features + """ + super(FeatureFusionBlock, self).__init__() + + self.resConfUnit1 = ResidualConvUnit(features) + self.resConfUnit2 = ResidualConvUnit(features) + + def forward(self, *xs): + """Forward pass. + + Returns: + tensor: output + """ + output = xs[0] + + if len(xs) == 2: + output += self.resConfUnit1(xs[1]) + + output = self.resConfUnit2(output) + + output = nn.functional.interpolate( + output, scale_factor=2, mode="bilinear", align_corners=True + ) + + return output + + + + +class ResidualConvUnit_custom(nn.Module): + """Residual convolution module. + """ + + def __init__(self, features, activation, bn): + """Init. + + Args: + features (int): number of features + """ + super().__init__() + + self.bn = bn + + self.groups=1 + + self.conv1 = nn.Conv2d( + features, features, kernel_size=3, stride=1, padding=1, bias=True, groups=self.groups + ) + + self.conv2 = nn.Conv2d( + features, features, kernel_size=3, stride=1, padding=1, bias=True, groups=self.groups + ) + + if self.bn is True: + self.bn1 = nn.BatchNorm2d(features) + self.bn2 = nn.BatchNorm2d(features) + + self.activation = activation + + self.skip_add = nn.quantized.FloatFunctional() + + def forward(self, x): + """Forward pass. + + Args: + x (tensor): input + + Returns: + tensor: output + """ + + out = self.activation(x) + out = self.conv1(out) + if self.bn is True: + out = self.bn1(out) + + out = self.activation(out) + out = self.conv2(out) + if self.bn is True: + out = self.bn2(out) + + if self.groups > 1: + out = self.conv_merge(out) + + return self.skip_add.add(out, x) + + # return out + x + + +class FeatureFusionBlock_custom(nn.Module): + """Feature fusion block. + """ + + def __init__(self, features, activation, deconv=False, bn=False, expand=False, align_corners=True): + """Init. + + Args: + features (int): number of features + """ + super(FeatureFusionBlock_custom, self).__init__() + + self.deconv = deconv + self.align_corners = align_corners + + self.groups=1 + + self.expand = expand + out_features = features + if self.expand is True: + out_features = features//2 + + self.out_conv = nn.Conv2d(features, out_features, kernel_size=1, stride=1, padding=0, bias=True, groups=1) + + self.resConfUnit1 = ResidualConvUnit_custom(features, activation, bn) + self.resConfUnit2 = ResidualConvUnit_custom(features, activation, bn) + + self.skip_add = nn.quantized.FloatFunctional() + + def forward(self, *xs): + """Forward pass. + + Returns: + tensor: output + """ + output = xs[0] + + if len(xs) == 2: + res = self.resConfUnit1(xs[1]) + output = self.skip_add.add(output, res) + # output += res + + output = self.resConfUnit2(output) + + output = nn.functional.interpolate( + output, scale_factor=2, mode="bilinear", align_corners=self.align_corners + ) + + output = self.out_conv(output) + + return output + diff --git a/modules/control/proc/midas/midas/dpt_depth.py b/modules/control/proc/midas/midas/dpt_depth.py new file mode 100644 index 000000000..4429b7f94 --- /dev/null +++ b/modules/control/proc/midas/midas/dpt_depth.py @@ -0,0 +1,109 @@ +import torch +import torch.nn as nn +import torch.nn.functional as F + +from .base_model import BaseModel +from .blocks import ( + FeatureFusionBlock, + FeatureFusionBlock_custom, + Interpolate, + _make_encoder, + forward_vit, +) + + +def _make_fusion_block(features, use_bn): + return FeatureFusionBlock_custom( + features, + nn.ReLU(False), + deconv=False, + bn=use_bn, + expand=False, + align_corners=True, + ) + + +class DPT(BaseModel): + def __init__( + self, + head, + features=256, + backbone="vitb_rn50_384", + readout="project", + channels_last=False, + use_bn=False, + ): + + super(DPT, self).__init__() + + self.channels_last = channels_last + + hooks = { + "vitb_rn50_384": [0, 1, 8, 11], + "vitb16_384": [2, 5, 8, 11], + "vitl16_384": [5, 11, 17, 23], + } + + # Instantiate backbone and reassemble blocks + self.pretrained, self.scratch = _make_encoder( + backbone, + features, + False, # Set to true of you want to train from scratch, uses ImageNet weights + groups=1, + expand=False, + exportable=False, + hooks=hooks[backbone], + use_readout=readout, + ) + + self.scratch.refinenet1 = _make_fusion_block(features, use_bn) + self.scratch.refinenet2 = _make_fusion_block(features, use_bn) + self.scratch.refinenet3 = _make_fusion_block(features, use_bn) + self.scratch.refinenet4 = _make_fusion_block(features, use_bn) + + self.scratch.output_conv = head + + + def forward(self, x): + if self.channels_last is True: + x.contiguous(memory_format=torch.channels_last) + + layer_1, layer_2, layer_3, layer_4 = forward_vit(self.pretrained, x) + + layer_1_rn = self.scratch.layer1_rn(layer_1) + layer_2_rn = self.scratch.layer2_rn(layer_2) + layer_3_rn = self.scratch.layer3_rn(layer_3) + layer_4_rn = self.scratch.layer4_rn(layer_4) + + path_4 = self.scratch.refinenet4(layer_4_rn) + path_3 = self.scratch.refinenet3(path_4, layer_3_rn) + path_2 = self.scratch.refinenet2(path_3, layer_2_rn) + path_1 = self.scratch.refinenet1(path_2, layer_1_rn) + + out = self.scratch.output_conv(path_1) + + return out + + +class DPTDepthModel(DPT): + def __init__(self, path=None, non_negative=True, **kwargs): + features = kwargs["features"] if "features" in kwargs else 256 + + head = nn.Sequential( + nn.Conv2d(features, features // 2, kernel_size=3, stride=1, padding=1), + Interpolate(scale_factor=2, mode="bilinear", align_corners=True), + nn.Conv2d(features // 2, 32, kernel_size=3, stride=1, padding=1), + nn.ReLU(True), + nn.Conv2d(32, 1, kernel_size=1, stride=1, padding=0), + nn.ReLU(True) if non_negative else nn.Identity(), + nn.Identity(), + ) + + super().__init__(head, **kwargs) + + if path is not None: + self.load(path) + + def forward(self, x): + return super().forward(x).squeeze(dim=1) + diff --git a/modules/control/proc/midas/midas/midas_net.py b/modules/control/proc/midas/midas/midas_net.py new file mode 100644 index 000000000..8a9549778 --- /dev/null +++ b/modules/control/proc/midas/midas/midas_net.py @@ -0,0 +1,76 @@ +"""MidashNet: Network for monocular depth estimation trained by mixing several datasets. +This file contains code that is adapted from +https://github.com/thomasjpfan/pytorch_refinenet/blob/master/pytorch_refinenet/refinenet/refinenet_4cascade.py +""" +import torch +import torch.nn as nn + +from .base_model import BaseModel +from .blocks import FeatureFusionBlock, Interpolate, _make_encoder + + +class MidasNet(BaseModel): + """Network for monocular depth estimation. + """ + + def __init__(self, path=None, features=256, non_negative=True): + """Init. + + Args: + path (str, optional): Path to saved model. Defaults to None. + features (int, optional): Number of features. Defaults to 256. + backbone (str, optional): Backbone network for encoder. Defaults to resnet50 + """ + print("Loading weights: ", path) + + super(MidasNet, self).__init__() + + use_pretrained = False if path is None else True + + self.pretrained, self.scratch = _make_encoder(backbone="resnext101_wsl", features=features, use_pretrained=use_pretrained) + + self.scratch.refinenet4 = FeatureFusionBlock(features) + self.scratch.refinenet3 = FeatureFusionBlock(features) + self.scratch.refinenet2 = FeatureFusionBlock(features) + self.scratch.refinenet1 = FeatureFusionBlock(features) + + self.scratch.output_conv = nn.Sequential( + nn.Conv2d(features, 128, kernel_size=3, stride=1, padding=1), + Interpolate(scale_factor=2, mode="bilinear"), + nn.Conv2d(128, 32, kernel_size=3, stride=1, padding=1), + nn.ReLU(True), + nn.Conv2d(32, 1, kernel_size=1, stride=1, padding=0), + nn.ReLU(True) if non_negative else nn.Identity(), + ) + + if path: + self.load(path) + + def forward(self, x): + """Forward pass. + + Args: + x (tensor): input data (image) + + Returns: + tensor: depth + """ + + layer_1 = self.pretrained.layer1(x) + layer_2 = self.pretrained.layer2(layer_1) + layer_3 = self.pretrained.layer3(layer_2) + layer_4 = self.pretrained.layer4(layer_3) + + layer_1_rn = self.scratch.layer1_rn(layer_1) + layer_2_rn = self.scratch.layer2_rn(layer_2) + layer_3_rn = self.scratch.layer3_rn(layer_3) + layer_4_rn = self.scratch.layer4_rn(layer_4) + + path_4 = self.scratch.refinenet4(layer_4_rn) + path_3 = self.scratch.refinenet3(path_4, layer_3_rn) + path_2 = self.scratch.refinenet2(path_3, layer_2_rn) + path_1 = self.scratch.refinenet1(path_2, layer_1_rn) + + out = self.scratch.output_conv(path_1) + + return torch.squeeze(out, dim=1) diff --git a/modules/control/proc/midas/midas/midas_net_custom.py b/modules/control/proc/midas/midas/midas_net_custom.py new file mode 100644 index 000000000..cba1bcfff --- /dev/null +++ b/modules/control/proc/midas/midas/midas_net_custom.py @@ -0,0 +1,130 @@ +"""MidashNet: Network for monocular depth estimation trained by mixing several datasets. +This file contains code that is adapted from +https://github.com/thomasjpfan/pytorch_refinenet/blob/master/pytorch_refinenet/refinenet/refinenet_4cascade.py +""" +import torch +import torch.nn as nn + +from .base_model import BaseModel +from .blocks import FeatureFusionBlock, FeatureFusionBlock_custom, Interpolate, _make_encoder + + +class MidasNet_small(BaseModel): + """Network for monocular depth estimation. + """ + + def __init__(self, path=None, features=64, backbone="efficientnet_lite3", non_negative=True, exportable=True, channels_last=False, align_corners=True, + blocks=None): + """Init. + + Args: + path (str, optional): Path to saved model. Defaults to None. + features (int, optional): Number of features. Defaults to 256. + backbone (str, optional): Backbone network for encoder. Defaults to resnet50 + """ + if blocks is None: + blocks = {"expand": True} + print("Loading weights: ", path) + + super(MidasNet_small, self).__init__() + + use_pretrained = False if path else True + + self.channels_last = channels_last + self.blocks = blocks + self.backbone = backbone + + self.groups = 1 + + features1=features + features2=features + features3=features + features4=features + self.expand = False + if "expand" in self.blocks and self.blocks['expand'] is True: + self.expand = True + features1=features + features2=features*2 + features3=features*4 + features4=features*8 + + self.pretrained, self.scratch = _make_encoder(self.backbone, features, use_pretrained, groups=self.groups, expand=self.expand, exportable=exportable) + + self.scratch.activation = nn.ReLU(False) + + self.scratch.refinenet4 = FeatureFusionBlock_custom(features4, self.scratch.activation, deconv=False, bn=False, expand=self.expand, align_corners=align_corners) + self.scratch.refinenet3 = FeatureFusionBlock_custom(features3, self.scratch.activation, deconv=False, bn=False, expand=self.expand, align_corners=align_corners) + self.scratch.refinenet2 = FeatureFusionBlock_custom(features2, self.scratch.activation, deconv=False, bn=False, expand=self.expand, align_corners=align_corners) + self.scratch.refinenet1 = FeatureFusionBlock_custom(features1, self.scratch.activation, deconv=False, bn=False, align_corners=align_corners) + + + self.scratch.output_conv = nn.Sequential( + nn.Conv2d(features, features//2, kernel_size=3, stride=1, padding=1, groups=self.groups), + Interpolate(scale_factor=2, mode="bilinear"), + nn.Conv2d(features//2, 32, kernel_size=3, stride=1, padding=1), + self.scratch.activation, + nn.Conv2d(32, 1, kernel_size=1, stride=1, padding=0), + nn.ReLU(True) if non_negative else nn.Identity(), + nn.Identity(), + ) + + if path: + self.load(path) + + + def forward(self, x): + """Forward pass. + + Args: + x (tensor): input data (image) + + Returns: + tensor: depth + """ + if self.channels_last is True: + print("self.channels_last = ", self.channels_last) + x.contiguous(memory_format=torch.channels_last) + + + layer_1 = self.pretrained.layer1(x) + layer_2 = self.pretrained.layer2(layer_1) + layer_3 = self.pretrained.layer3(layer_2) + layer_4 = self.pretrained.layer4(layer_3) + + layer_1_rn = self.scratch.layer1_rn(layer_1) + layer_2_rn = self.scratch.layer2_rn(layer_2) + layer_3_rn = self.scratch.layer3_rn(layer_3) + layer_4_rn = self.scratch.layer4_rn(layer_4) + + + path_4 = self.scratch.refinenet4(layer_4_rn) + path_3 = self.scratch.refinenet3(path_4, layer_3_rn) + path_2 = self.scratch.refinenet2(path_3, layer_2_rn) + path_1 = self.scratch.refinenet1(path_2, layer_1_rn) + + out = self.scratch.output_conv(path_1) + + return torch.squeeze(out, dim=1) + + + +def fuse_model(m): + prev_previous_type = nn.Identity() + prev_previous_name = '' + previous_type = nn.Identity() + previous_name = '' + for name, module in m.named_modules(): + if prev_previous_type == nn.Conv2d and previous_type == nn.BatchNorm2d and type(module) == nn.ReLU: + # print("FUSED ", prev_previous_name, previous_name, name) + torch.quantization.fuse_modules(m, [prev_previous_name, previous_name, name], inplace=True) + elif prev_previous_type == nn.Conv2d and previous_type == nn.BatchNorm2d: + # print("FUSED ", prev_previous_name, previous_name) + torch.quantization.fuse_modules(m, [prev_previous_name, previous_name], inplace=True) + # elif previous_type == nn.Conv2d and type(module) == nn.ReLU: + # print("FUSED ", previous_name, name) + # torch.quantization.fuse_modules(m, [previous_name, name], inplace=True) + + prev_previous_type = previous_type + prev_previous_name = previous_name + previous_type = type(module) + previous_name = name diff --git a/modules/control/proc/midas/midas/transforms.py b/modules/control/proc/midas/midas/transforms.py new file mode 100644 index 000000000..350cbc116 --- /dev/null +++ b/modules/control/proc/midas/midas/transforms.py @@ -0,0 +1,234 @@ +import numpy as np +import cv2 +import math + + +def apply_min_size(sample, size, image_interpolation_method=cv2.INTER_AREA): + """Rezise the sample to ensure the given size. Keeps aspect ratio. + + Args: + sample (dict): sample + size (tuple): image size + + Returns: + tuple: new size + """ + shape = list(sample["disparity"].shape) + + if shape[0] >= size[0] and shape[1] >= size[1]: + return sample + + scale = [0, 0] + scale[0] = size[0] / shape[0] + scale[1] = size[1] / shape[1] + + scale = max(scale) + + shape[0] = math.ceil(scale * shape[0]) + shape[1] = math.ceil(scale * shape[1]) + + # resize + sample["image"] = cv2.resize( + sample["image"], tuple(shape[::-1]), interpolation=image_interpolation_method + ) + + sample["disparity"] = cv2.resize( + sample["disparity"], tuple(shape[::-1]), interpolation=cv2.INTER_NEAREST + ) + sample["mask"] = cv2.resize( + sample["mask"].astype(np.float32), + tuple(shape[::-1]), + interpolation=cv2.INTER_NEAREST, + ) + sample["mask"] = sample["mask"].astype(bool) + + return tuple(shape) + + +class Resize(object): + """Resize sample to given size (width, height). + """ + + def __init__( + self, + width, + height, + resize_target=True, + keep_aspect_ratio=False, + ensure_multiple_of=1, + resize_method="lower_bound", + image_interpolation_method=cv2.INTER_AREA, + ): + """Init. + + Args: + width (int): desired output width + height (int): desired output height + resize_target (bool, optional): + True: Resize the full sample (image, mask, target). + False: Resize image only. + Defaults to True. + keep_aspect_ratio (bool, optional): + True: Keep the aspect ratio of the input sample. + Output sample might not have the given width and height, and + resize behaviour depends on the parameter 'resize_method'. + Defaults to False. + ensure_multiple_of (int, optional): + Output width and height is constrained to be multiple of this parameter. + Defaults to 1. + resize_method (str, optional): + "lower_bound": Output will be at least as large as the given size. + "upper_bound": Output will be at max as large as the given size. (Output size might be smaller than given size.) + "minimal": Scale as least as possible. (Output size might be smaller than given size.) + Defaults to "lower_bound". + """ + self.__width = width + self.__height = height + + self.__resize_target = resize_target + self.__keep_aspect_ratio = keep_aspect_ratio + self.__multiple_of = ensure_multiple_of + self.__resize_method = resize_method + self.__image_interpolation_method = image_interpolation_method + + def constrain_to_multiple_of(self, x, min_val=0, max_val=None): + y = (np.round(x / self.__multiple_of) * self.__multiple_of).astype(int) + + if max_val is not None and y > max_val: + y = (np.floor(x / self.__multiple_of) * self.__multiple_of).astype(int) + + if y < min_val: + y = (np.ceil(x / self.__multiple_of) * self.__multiple_of).astype(int) + + return y + + def get_size(self, width, height): + # determine new height and width + scale_height = self.__height / height + scale_width = self.__width / width + + if self.__keep_aspect_ratio: + if self.__resize_method == "lower_bound": + # scale such that output size is lower bound + if scale_width > scale_height: + # fit width + scale_height = scale_width + else: + # fit height + scale_width = scale_height + elif self.__resize_method == "upper_bound": + # scale such that output size is upper bound + if scale_width < scale_height: + # fit width + scale_height = scale_width + else: + # fit height + scale_width = scale_height + elif self.__resize_method == "minimal": + # scale as least as possbile + if abs(1 - scale_width) < abs(1 - scale_height): + # fit width + scale_height = scale_width + else: + # fit height + scale_width = scale_height + else: + raise ValueError( + f"resize_method {self.__resize_method} not implemented" + ) + + if self.__resize_method == "lower_bound": + new_height = self.constrain_to_multiple_of( + scale_height * height, min_val=self.__height + ) + new_width = self.constrain_to_multiple_of( + scale_width * width, min_val=self.__width + ) + elif self.__resize_method == "upper_bound": + new_height = self.constrain_to_multiple_of( + scale_height * height, max_val=self.__height + ) + new_width = self.constrain_to_multiple_of( + scale_width * width, max_val=self.__width + ) + elif self.__resize_method == "minimal": + new_height = self.constrain_to_multiple_of(scale_height * height) + new_width = self.constrain_to_multiple_of(scale_width * width) + else: + raise ValueError(f"resize_method {self.__resize_method} not implemented") + + return (new_width, new_height) + + def __call__(self, sample): + width, height = self.get_size( + sample["image"].shape[1], sample["image"].shape[0] + ) + + # resize sample + sample["image"] = cv2.resize( + sample["image"], + (width, height), + interpolation=self.__image_interpolation_method, + ) + + if self.__resize_target: + if "disparity" in sample: + sample["disparity"] = cv2.resize( + sample["disparity"], + (width, height), + interpolation=cv2.INTER_NEAREST, + ) + + if "depth" in sample: + sample["depth"] = cv2.resize( + sample["depth"], (width, height), interpolation=cv2.INTER_NEAREST + ) + + sample["mask"] = cv2.resize( + sample["mask"].astype(np.float32), + (width, height), + interpolation=cv2.INTER_NEAREST, + ) + sample["mask"] = sample["mask"].astype(bool) + + return sample + + +class NormalizeImage(object): + """Normlize image by given mean and std. + """ + + def __init__(self, mean, std): + self.__mean = mean + self.__std = std + + def __call__(self, sample): + sample["image"] = (sample["image"] - self.__mean) / self.__std + + return sample + + +class PrepareForNet(object): + """Prepare sample for usage as network input. + """ + + def __init__(self): + pass + + def __call__(self, sample): + image = np.transpose(sample["image"], (2, 0, 1)) + sample["image"] = np.ascontiguousarray(image).astype(np.float32) + + if "mask" in sample: + sample["mask"] = sample["mask"].astype(np.float32) + sample["mask"] = np.ascontiguousarray(sample["mask"]) + + if "disparity" in sample: + disparity = sample["disparity"].astype(np.float32) + sample["disparity"] = np.ascontiguousarray(disparity) + + if "depth" in sample: + depth = sample["depth"].astype(np.float32) + sample["depth"] = np.ascontiguousarray(depth) + + return sample diff --git a/modules/control/proc/midas/midas/vit.py b/modules/control/proc/midas/midas/vit.py new file mode 100644 index 000000000..f268a9fc4 --- /dev/null +++ b/modules/control/proc/midas/midas/vit.py @@ -0,0 +1,501 @@ +import torch +import torch.nn as nn +import timm +import types +import math +import torch.nn.functional as F + + +class Slice(nn.Module): + def __init__(self, start_index=1): + super(Slice, self).__init__() + self.start_index = start_index + + def forward(self, x): + return x[:, self.start_index :] + + +class AddReadout(nn.Module): + def __init__(self, start_index=1): + super(AddReadout, self).__init__() + self.start_index = start_index + + def forward(self, x): + if self.start_index == 2: + readout = (x[:, 0] + x[:, 1]) / 2 + else: + readout = x[:, 0] + return x[:, self.start_index :] + readout.unsqueeze(1) + + +class ProjectReadout(nn.Module): + def __init__(self, in_features, start_index=1): + super(ProjectReadout, self).__init__() + self.start_index = start_index + + self.project = nn.Sequential(nn.Linear(2 * in_features, in_features), nn.GELU()) + + def forward(self, x): + readout = x[:, 0].unsqueeze(1).expand_as(x[:, self.start_index :]) + features = torch.cat((x[:, self.start_index :], readout), -1) + + return self.project(features) + + +class Transpose(nn.Module): + def __init__(self, dim0, dim1): + super(Transpose, self).__init__() + self.dim0 = dim0 + self.dim1 = dim1 + + def forward(self, x): + x = x.transpose(self.dim0, self.dim1) + return x + + +def forward_vit(pretrained, x): + b, c, h, w = x.shape + + pretrained.model.forward_flex(x) + + layer_1 = pretrained.activations["1"] + layer_2 = pretrained.activations["2"] + layer_3 = pretrained.activations["3"] + layer_4 = pretrained.activations["4"] + + layer_1 = pretrained.act_postprocess1[0:2](layer_1) + layer_2 = pretrained.act_postprocess2[0:2](layer_2) + layer_3 = pretrained.act_postprocess3[0:2](layer_3) + layer_4 = pretrained.act_postprocess4[0:2](layer_4) + + unflatten = nn.Sequential( + nn.Unflatten( + 2, + torch.Size( + [ + h // pretrained.model.patch_size[1], + w // pretrained.model.patch_size[0], + ] + ), + ) + ) + + if layer_1.ndim == 3: + layer_1 = unflatten(layer_1) + if layer_2.ndim == 3: + layer_2 = unflatten(layer_2) + if layer_3.ndim == 3: + layer_3 = unflatten(layer_3) + if layer_4.ndim == 3: + layer_4 = unflatten(layer_4) + + layer_1 = pretrained.act_postprocess1[3 : len(pretrained.act_postprocess1)](layer_1) + layer_2 = pretrained.act_postprocess2[3 : len(pretrained.act_postprocess2)](layer_2) + layer_3 = pretrained.act_postprocess3[3 : len(pretrained.act_postprocess3)](layer_3) + layer_4 = pretrained.act_postprocess4[3 : len(pretrained.act_postprocess4)](layer_4) + + return layer_1, layer_2, layer_3, layer_4 + + +def _resize_pos_embed(self, posemb, gs_h, gs_w): + posemb_tok, posemb_grid = ( + posemb[:, : self.start_index], + posemb[0, self.start_index :], + ) + + gs_old = int(math.sqrt(len(posemb_grid))) + + posemb_grid = posemb_grid.reshape(1, gs_old, gs_old, -1).permute(0, 3, 1, 2) + posemb_grid = F.interpolate(posemb_grid, size=(gs_h, gs_w), mode="bilinear") + posemb_grid = posemb_grid.permute(0, 2, 3, 1).reshape(1, gs_h * gs_w, -1) + + posemb = torch.cat([posemb_tok, posemb_grid], dim=1) + + return posemb + + +def forward_flex(self, x): + b, c, h, w = x.shape + + pos_embed = self._resize_pos_embed( + self.pos_embed, h // self.patch_size[1], w // self.patch_size[0] + ) + + B = x.shape[0] + + if hasattr(self.patch_embed, "backbone"): + x = self.patch_embed.backbone(x) + if isinstance(x, (list, tuple)): + x = x[-1] # last feature if backbone outputs list/tuple of features + + x = self.patch_embed.proj(x).flatten(2).transpose(1, 2) + + if getattr(self, "dist_token", None) is not None: + cls_tokens = self.cls_token.expand( + B, -1, -1 + ) # stole cls_tokens impl from Phil Wang, thanks + dist_token = self.dist_token.expand(B, -1, -1) + x = torch.cat((cls_tokens, dist_token, x), dim=1) + else: + cls_tokens = self.cls_token.expand( + B, -1, -1 + ) # stole cls_tokens impl from Phil Wang, thanks + x = torch.cat((cls_tokens, x), dim=1) + + x = x + pos_embed + x = self.pos_drop(x) + + for blk in self.blocks: + x = blk(x) + + x = self.norm(x) + + return x + + +activations = {} + + +def get_activation(name): + def hook(model, input, output): + activations[name] = output + + return hook + + +def get_readout_oper(vit_features, features, use_readout, start_index=1): + if use_readout == "ignore": + readout_oper = [Slice(start_index)] * len(features) + elif use_readout == "add": + readout_oper = [AddReadout(start_index)] * len(features) + elif use_readout == "project": + readout_oper = [ + ProjectReadout(vit_features, start_index) for out_feat in features + ] + else: + raise AssertionError("wrong operation for readout token, use_readout can be 'ignore', 'add', or 'project'") + + return readout_oper + + +def _make_vit_b16_backbone( + model, + features=None, + size=None, + hooks=None, + vit_features=768, + use_readout="ignore", + start_index=1, +): + if hooks is None: + hooks = [2, 5, 8, 11] + if size is None: + size = [384, 384] + if features is None: + features = [96, 192, 384, 768] + pretrained = nn.Module() + + pretrained.model = model + pretrained.model.blocks[hooks[0]].register_forward_hook(get_activation("1")) + pretrained.model.blocks[hooks[1]].register_forward_hook(get_activation("2")) + pretrained.model.blocks[hooks[2]].register_forward_hook(get_activation("3")) + pretrained.model.blocks[hooks[3]].register_forward_hook(get_activation("4")) + + pretrained.activations = activations + + readout_oper = get_readout_oper(vit_features, features, use_readout, start_index) + + # 32, 48, 136, 384 + pretrained.act_postprocess1 = nn.Sequential( + readout_oper[0], + Transpose(1, 2), + nn.Unflatten(2, torch.Size([size[0] // 16, size[1] // 16])), + nn.Conv2d( + in_channels=vit_features, + out_channels=features[0], + kernel_size=1, + stride=1, + padding=0, + ), + nn.ConvTranspose2d( + in_channels=features[0], + out_channels=features[0], + kernel_size=4, + stride=4, + padding=0, + bias=True, + dilation=1, + groups=1, + ), + ) + + pretrained.act_postprocess2 = nn.Sequential( + readout_oper[1], + Transpose(1, 2), + nn.Unflatten(2, torch.Size([size[0] // 16, size[1] // 16])), + nn.Conv2d( + in_channels=vit_features, + out_channels=features[1], + kernel_size=1, + stride=1, + padding=0, + ), + nn.ConvTranspose2d( + in_channels=features[1], + out_channels=features[1], + kernel_size=2, + stride=2, + padding=0, + bias=True, + dilation=1, + groups=1, + ), + ) + + pretrained.act_postprocess3 = nn.Sequential( + readout_oper[2], + Transpose(1, 2), + nn.Unflatten(2, torch.Size([size[0] // 16, size[1] // 16])), + nn.Conv2d( + in_channels=vit_features, + out_channels=features[2], + kernel_size=1, + stride=1, + padding=0, + ), + ) + + pretrained.act_postprocess4 = nn.Sequential( + readout_oper[3], + Transpose(1, 2), + nn.Unflatten(2, torch.Size([size[0] // 16, size[1] // 16])), + nn.Conv2d( + in_channels=vit_features, + out_channels=features[3], + kernel_size=1, + stride=1, + padding=0, + ), + nn.Conv2d( + in_channels=features[3], + out_channels=features[3], + kernel_size=3, + stride=2, + padding=1, + ), + ) + + pretrained.model.start_index = start_index + pretrained.model.patch_size = [16, 16] + + # We inject this function into the VisionTransformer instances so that + # we can use it with interpolated position embeddings without modifying the library source. + pretrained.model.forward_flex = types.MethodType(forward_flex, pretrained.model) + pretrained.model._resize_pos_embed = types.MethodType( + _resize_pos_embed, pretrained.model + ) + + return pretrained + + +def _make_pretrained_vitl16_384(pretrained, use_readout="ignore", hooks=None): + model = timm.create_model("vit_large_patch16_384", pretrained=pretrained) + + hooks = [5, 11, 17, 23] if hooks is None else hooks + return _make_vit_b16_backbone( + model, + features=[256, 512, 1024, 1024], + hooks=hooks, + vit_features=1024, + use_readout=use_readout, + ) + + +def _make_pretrained_vitb16_384(pretrained, use_readout="ignore", hooks=None): + model = timm.create_model("vit_base_patch16_384", pretrained=pretrained) + + hooks = [2, 5, 8, 11] if hooks is None else hooks + return _make_vit_b16_backbone( + model, features=[96, 192, 384, 768], hooks=hooks, use_readout=use_readout + ) + + +def _make_pretrained_deitb16_384(pretrained, use_readout="ignore", hooks=None): + model = timm.create_model("vit_deit_base_patch16_384", pretrained=pretrained) + + hooks = [2, 5, 8, 11] if hooks is None else hooks + return _make_vit_b16_backbone( + model, features=[96, 192, 384, 768], hooks=hooks, use_readout=use_readout + ) + + +def _make_pretrained_deitb16_distil_384(pretrained, use_readout="ignore", hooks=None): + model = timm.create_model( + "vit_deit_base_distilled_patch16_384", pretrained=pretrained + ) + + hooks = [2, 5, 8, 11] if hooks is None else hooks + return _make_vit_b16_backbone( + model, + features=[96, 192, 384, 768], + hooks=hooks, + use_readout=use_readout, + start_index=2, + ) + + +def _make_vit_b_rn50_backbone( + model, + features=None, + size=None, + hooks=None, + vit_features=768, + use_vit_only=False, + use_readout="ignore", + start_index=1, +): + if hooks is None: + hooks = [0, 1, 8, 11] + if size is None: + size = [384, 384] + if features is None: + features = [256, 512, 768, 768] + pretrained = nn.Module() + + pretrained.model = model + + if use_vit_only is True: + pretrained.model.blocks[hooks[0]].register_forward_hook(get_activation("1")) + pretrained.model.blocks[hooks[1]].register_forward_hook(get_activation("2")) + else: + pretrained.model.patch_embed.backbone.stages[0].register_forward_hook( + get_activation("1") + ) + pretrained.model.patch_embed.backbone.stages[1].register_forward_hook( + get_activation("2") + ) + + pretrained.model.blocks[hooks[2]].register_forward_hook(get_activation("3")) + pretrained.model.blocks[hooks[3]].register_forward_hook(get_activation("4")) + + pretrained.activations = activations + + readout_oper = get_readout_oper(vit_features, features, use_readout, start_index) + + if use_vit_only is True: + pretrained.act_postprocess1 = nn.Sequential( + readout_oper[0], + Transpose(1, 2), + nn.Unflatten(2, torch.Size([size[0] // 16, size[1] // 16])), + nn.Conv2d( + in_channels=vit_features, + out_channels=features[0], + kernel_size=1, + stride=1, + padding=0, + ), + nn.ConvTranspose2d( + in_channels=features[0], + out_channels=features[0], + kernel_size=4, + stride=4, + padding=0, + bias=True, + dilation=1, + groups=1, + ), + ) + + pretrained.act_postprocess2 = nn.Sequential( + readout_oper[1], + Transpose(1, 2), + nn.Unflatten(2, torch.Size([size[0] // 16, size[1] // 16])), + nn.Conv2d( + in_channels=vit_features, + out_channels=features[1], + kernel_size=1, + stride=1, + padding=0, + ), + nn.ConvTranspose2d( + in_channels=features[1], + out_channels=features[1], + kernel_size=2, + stride=2, + padding=0, + bias=True, + dilation=1, + groups=1, + ), + ) + else: + pretrained.act_postprocess1 = nn.Sequential( + nn.Identity(), nn.Identity(), nn.Identity() + ) + pretrained.act_postprocess2 = nn.Sequential( + nn.Identity(), nn.Identity(), nn.Identity() + ) + + pretrained.act_postprocess3 = nn.Sequential( + readout_oper[2], + Transpose(1, 2), + nn.Unflatten(2, torch.Size([size[0] // 16, size[1] // 16])), + nn.Conv2d( + in_channels=vit_features, + out_channels=features[2], + kernel_size=1, + stride=1, + padding=0, + ), + ) + + pretrained.act_postprocess4 = nn.Sequential( + readout_oper[3], + Transpose(1, 2), + nn.Unflatten(2, torch.Size([size[0] // 16, size[1] // 16])), + nn.Conv2d( + in_channels=vit_features, + out_channels=features[3], + kernel_size=1, + stride=1, + padding=0, + ), + nn.Conv2d( + in_channels=features[3], + out_channels=features[3], + kernel_size=3, + stride=2, + padding=1, + ), + ) + + pretrained.model.start_index = start_index + pretrained.model.patch_size = [16, 16] + + # We inject this function into the VisionTransformer instances so that + # we can use it with interpolated position embeddings without modifying the library source. + pretrained.model.forward_flex = types.MethodType(forward_flex, pretrained.model) + + # We inject this function into the VisionTransformer instances so that + # we can use it with interpolated position embeddings without modifying the library source. + pretrained.model._resize_pos_embed = types.MethodType( + _resize_pos_embed, pretrained.model + ) + + return pretrained + + +def _make_pretrained_vitb_rn50_384( + pretrained, use_readout="ignore", hooks=None, use_vit_only=False +): + model = timm.create_model("vit_base_resnet50_384", pretrained=pretrained) + + hooks = [0, 1, 8, 11] if hooks is None else hooks + return _make_vit_b_rn50_backbone( + model, + features=[256, 512, 768, 768], + size=[384, 384], + hooks=hooks, + use_vit_only=use_vit_only, + use_readout=use_readout, + ) diff --git a/modules/control/proc/midas/utils.py b/modules/control/proc/midas/utils.py new file mode 100644 index 000000000..9a9d3b5b6 --- /dev/null +++ b/modules/control/proc/midas/utils.py @@ -0,0 +1,189 @@ +"""Utils for monoDepth.""" +import sys +import re +import numpy as np +import cv2 +import torch + + +def read_pfm(path): + """Read pfm file. + + Args: + path (str): path to file + + Returns: + tuple: (data, scale) + """ + with open(path, "rb") as file: + + color = None + width = None + height = None + scale = None + endian = None + + header = file.readline().rstrip() + if header.decode("ascii") == "PF": + color = True + elif header.decode("ascii") == "Pf": + color = False + else: + raise Exception("Not a PFM file: " + path) + + dim_match = re.match(r"^(\d+)\s(\d+)\s$", file.readline().decode("ascii")) + if dim_match: + width, height = list(map(int, dim_match.groups())) + else: + raise Exception("Malformed PFM header.") + + scale = float(file.readline().decode("ascii").rstrip()) + if scale < 0: + # little-endian + endian = "<" + scale = -scale + else: + # big-endian + endian = ">" + + data = np.fromfile(file, endian + "f") + shape = (height, width, 3) if color else (height, width) + + data = np.reshape(data, shape) + data = np.flipud(data) + + return data, scale + + +def write_pfm(path, image, scale=1): + """Write pfm file. + + Args: + path (str): pathto file + image (array): data + scale (int, optional): Scale. Defaults to 1. + """ + + with open(path, "wb") as file: + color = None + + if image.dtype.name != "float32": + raise Exception("Image dtype must be float32.") + + image = np.flipud(image) + + if len(image.shape) == 3 and image.shape[2] == 3: # color image + color = True + elif ( + len(image.shape) == 2 or len(image.shape) == 3 and image.shape[2] == 1 + ): # greyscale + color = False + else: + raise Exception("Image must have H x W x 3, H x W x 1 or H x W dimensions.") + + file.write("PF\n" if color else "Pf\n".encode()) + file.write("%d %d\n".encode() % (image.shape[1], image.shape[0])) + + endian = image.dtype.byteorder + + if endian == "<" or endian == "=" and sys.byteorder == "little": + scale = -scale + + file.write("%f\n".encode() % scale) + + image.tofile(file) + + +def read_image(path): + """Read image and output RGB image (0-1). + + Args: + path (str): path to file + + Returns: + array: RGB image (0-1) + """ + img = cv2.imread(path) + + if img.ndim == 2: + img = cv2.cvtColor(img, cv2.COLOR_GRAY2BGR) + + img = cv2.cvtColor(img, cv2.COLOR_BGR2RGB) / 255.0 + + return img + + +def resize_image(img): + """Resize image and make it fit for network. + + Args: + img (array): image + + Returns: + tensor: data ready for network + """ + height_orig = img.shape[0] + width_orig = img.shape[1] + + if width_orig > height_orig: + scale = width_orig / 384 + else: + scale = height_orig / 384 + + height = (np.ceil(height_orig / scale / 32) * 32).astype(int) + width = (np.ceil(width_orig / scale / 32) * 32).astype(int) + + img_resized = cv2.resize(img, (width, height), interpolation=cv2.INTER_AREA) + + img_resized = ( + torch.from_numpy(np.transpose(img_resized, (2, 0, 1))).contiguous().float() + ) + img_resized = img_resized.unsqueeze(0) + + return img_resized + + +def resize_depth(depth, width, height): + """Resize depth map and bring to CPU (numpy). + + Args: + depth (tensor): depth + width (int): image width + height (int): image height + + Returns: + array: processed depth + """ + depth = torch.squeeze(depth[0, :, :, :]).to("cpu") + + depth_resized = cv2.resize( + depth.numpy(), (width, height), interpolation=cv2.INTER_CUBIC + ) + + return depth_resized + +def write_depth(path, depth, bits=1): + """Write depth map to pfm and png file. + + Args: + path (str): filepath without extension + depth (array): depth + """ + write_pfm(path + ".pfm", depth.astype(np.float32)) + + depth_min = depth.min() + depth_max = depth.max() + + max_val = (2**(8*bits))-1 + + if depth_max - depth_min > np.finfo("float").eps: + out = max_val * (depth - depth_min) / (depth_max - depth_min) + else: + out = np.zeros(depth.shape, dtype=depth.type) + + if bits == 1: + cv2.imwrite(path + ".png", out.astype("uint8")) + elif bits == 2: + cv2.imwrite(path + ".png", out.astype("uint16")) + + return diff --git a/modules/control/proc/mlsd/LICENSE b/modules/control/proc/mlsd/LICENSE new file mode 100644 index 000000000..d855c6db4 --- /dev/null +++ b/modules/control/proc/mlsd/LICENSE @@ -0,0 +1,201 @@ + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "{}" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright 2021-present NAVER Corp. + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. \ No newline at end of file diff --git a/modules/control/proc/mlsd/__init__.py b/modules/control/proc/mlsd/__init__.py new file mode 100644 index 000000000..456e1050d --- /dev/null +++ b/modules/control/proc/mlsd/__init__.py @@ -0,0 +1,78 @@ +import os +import warnings + +import cv2 +import numpy as np +import torch +from huggingface_hub import hf_hub_download +from PIL import Image + +from modules.control.util import HWC3, resize_image +from .models.mbv2_mlsd_large import MobileV2_MLSD_Large +from .utils import pred_lines + + +class MLSDdetector: + def __init__(self, model): + self.model = model + + @classmethod + def from_pretrained(cls, pretrained_model_or_path, filename=None, cache_dir=None): + if pretrained_model_or_path == "lllyasviel/ControlNet": + filename = filename or "annotator/ckpts/mlsd_large_512_fp32.pth" + else: + filename = filename or "mlsd_large_512_fp32.pth" + + if os.path.isdir(pretrained_model_or_path): + model_path = os.path.join(pretrained_model_or_path, filename) + else: + model_path = hf_hub_download(pretrained_model_or_path, filename, cache_dir=cache_dir) + + model = MobileV2_MLSD_Large() + model.load_state_dict(torch.load(model_path), strict=True) + model.eval() + + return cls(model) + + def to(self, device): + self.model.to(device) + return self + + def __call__(self, input_image, thr_v=0.1, thr_d=0.1, detect_resolution=512, image_resolution=512, output_type="pil", **kwargs): + if "return_pil" in kwargs: + warnings.warn("return_pil is deprecated. Use output_type instead.", DeprecationWarning) + output_type = "pil" if kwargs["return_pil"] else "np" + if type(output_type) is bool: + warnings.warn("Passing `True` or `False` to `output_type` is deprecated and will raise an error in future versions") + if output_type: + output_type = "pil" + + if not isinstance(input_image, np.ndarray): + input_image = np.array(input_image, dtype=np.uint8) + + input_image = HWC3(input_image) + input_image = resize_image(input_image, detect_resolution) + + assert input_image.ndim == 3 + img = input_image + img_output = np.zeros_like(img) + try: + lines = pred_lines(img, self.model, [img.shape[0], img.shape[1]], thr_v, thr_d) + for line in lines: + x_start, y_start, x_end, y_end = [int(val) for val in line] + cv2.line(img_output, (x_start, y_start), (x_end, y_end), [255, 255, 255], 1) + except Exception: + pass + + detected_map = img_output[:, :, 0] + detected_map = HWC3(detected_map) + + img = resize_image(input_image, image_resolution) + H, W, C = img.shape + + detected_map = cv2.resize(detected_map, (W, H), interpolation=cv2.INTER_LINEAR) + + if output_type == "pil": + detected_map = Image.fromarray(detected_map) + + return detected_map diff --git a/modules/control/proc/mlsd/models/__init__.py b/modules/control/proc/mlsd/models/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/modules/control/proc/mlsd/models/mbv2_mlsd_large.py b/modules/control/proc/mlsd/models/mbv2_mlsd_large.py new file mode 100644 index 000000000..39acf8dd5 --- /dev/null +++ b/modules/control/proc/mlsd/models/mbv2_mlsd_large.py @@ -0,0 +1,292 @@ +import os +import sys +import torch +import torch.nn as nn +import torch.utils.model_zoo as model_zoo +from torch.nn import functional as F + + +class BlockTypeA(nn.Module): + def __init__(self, in_c1, in_c2, out_c1, out_c2, upscale = True): + super(BlockTypeA, self).__init__() + self.conv1 = nn.Sequential( + nn.Conv2d(in_c2, out_c2, kernel_size=1), + nn.BatchNorm2d(out_c2), + nn.ReLU(inplace=True) + ) + self.conv2 = nn.Sequential( + nn.Conv2d(in_c1, out_c1, kernel_size=1), + nn.BatchNorm2d(out_c1), + nn.ReLU(inplace=True) + ) + self.upscale = upscale + + def forward(self, a, b): + b = self.conv1(b) + a = self.conv2(a) + if self.upscale: + b = F.interpolate(b, scale_factor=2.0, mode='bilinear', align_corners=True) + return torch.cat((a, b), dim=1) + + +class BlockTypeB(nn.Module): + def __init__(self, in_c, out_c): + super(BlockTypeB, self).__init__() + self.conv1 = nn.Sequential( + nn.Conv2d(in_c, in_c, kernel_size=3, padding=1), + nn.BatchNorm2d(in_c), + nn.ReLU() + ) + self.conv2 = nn.Sequential( + nn.Conv2d(in_c, out_c, kernel_size=3, padding=1), + nn.BatchNorm2d(out_c), + nn.ReLU() + ) + + def forward(self, x): + x = self.conv1(x) + x + x = self.conv2(x) + return x + +class BlockTypeC(nn.Module): + def __init__(self, in_c, out_c): + super(BlockTypeC, self).__init__() + self.conv1 = nn.Sequential( + nn.Conv2d(in_c, in_c, kernel_size=3, padding=5, dilation=5), + nn.BatchNorm2d(in_c), + nn.ReLU() + ) + self.conv2 = nn.Sequential( + nn.Conv2d(in_c, in_c, kernel_size=3, padding=1), + nn.BatchNorm2d(in_c), + nn.ReLU() + ) + self.conv3 = nn.Conv2d(in_c, out_c, kernel_size=1) + + def forward(self, x): + x = self.conv1(x) + x = self.conv2(x) + x = self.conv3(x) + return x + +def _make_divisible(v, divisor, min_value=None): + """ + This function is taken from the original tf repo. + It ensures that all layers have a channel number that is divisible by 8 + It can be seen here: + https://github.com/tensorflow/models/blob/master/research/slim/nets/mobilenet/mobilenet.py + :param v: + :param divisor: + :param min_value: + :return: + """ + if min_value is None: + min_value = divisor + new_v = max(min_value, int(v + divisor / 2) // divisor * divisor) + # Make sure that round down does not go down by more than 10%. + if new_v < 0.9 * v: + new_v += divisor + return new_v + + +class ConvBNReLU(nn.Sequential): + def __init__(self, in_planes, out_planes, kernel_size=3, stride=1, groups=1): + self.channel_pad = out_planes - in_planes + self.stride = stride + #padding = (kernel_size - 1) // 2 + + # TFLite uses slightly different padding than PyTorch + if stride == 2: + padding = 0 + else: + padding = (kernel_size - 1) // 2 + + super(ConvBNReLU, self).__init__( + nn.Conv2d(in_planes, out_planes, kernel_size, stride, padding, groups=groups, bias=False), + nn.BatchNorm2d(out_planes), + nn.ReLU6(inplace=True) + ) + self.max_pool = nn.MaxPool2d(kernel_size=stride, stride=stride) + + + def forward(self, x): + # TFLite uses different padding + if self.stride == 2: + x = F.pad(x, (0, 1, 0, 1), "constant", 0) + #print(x.shape) + + for module in self: + if not isinstance(module, nn.MaxPool2d): + x = module(x) + return x + + +class InvertedResidual(nn.Module): + def __init__(self, inp, oup, stride, expand_ratio): + super(InvertedResidual, self).__init__() + self.stride = stride + assert stride in [1, 2] + + hidden_dim = int(round(inp * expand_ratio)) + self.use_res_connect = self.stride == 1 and inp == oup + + layers = [] + if expand_ratio != 1: + # pw + layers.append(ConvBNReLU(inp, hidden_dim, kernel_size=1)) + layers.extend([ + # dw + ConvBNReLU(hidden_dim, hidden_dim, stride=stride, groups=hidden_dim), + # pw-linear + nn.Conv2d(hidden_dim, oup, 1, 1, 0, bias=False), + nn.BatchNorm2d(oup), + ]) + self.conv = nn.Sequential(*layers) + + def forward(self, x): + if self.use_res_connect: + return x + self.conv(x) + else: + return self.conv(x) + + +class MobileNetV2(nn.Module): + def __init__(self, pretrained=True): + """ + MobileNet V2 main class + Args: + num_classes (int): Number of classes + width_mult (float): Width multiplier - adjusts number of channels in each layer by this amount + inverted_residual_setting: Network structure + round_nearest (int): Round the number of channels in each layer to be a multiple of this number + Set to 1 to turn off rounding + block: Module specifying inverted residual building block for mobilenet + """ + super(MobileNetV2, self).__init__() + + block = InvertedResidual + input_channel = 32 + last_channel = 1280 + width_mult = 1.0 + round_nearest = 8 + + inverted_residual_setting = [ + # t, c, n, s + [1, 16, 1, 1], + [6, 24, 2, 2], + [6, 32, 3, 2], + [6, 64, 4, 2], + [6, 96, 3, 1], + #[6, 160, 3, 2], + #[6, 320, 1, 1], + ] + + # only check the first element, assuming user knows t,c,n,s are required + if len(inverted_residual_setting) == 0 or len(inverted_residual_setting[0]) != 4: + raise ValueError("inverted_residual_setting should be non-empty " + "or a 4-element list, got {}".format(inverted_residual_setting)) + + # building first layer + input_channel = _make_divisible(input_channel * width_mult, round_nearest) + self.last_channel = _make_divisible(last_channel * max(1.0, width_mult), round_nearest) + features = [ConvBNReLU(4, input_channel, stride=2)] + # building inverted residual blocks + for t, c, n, s in inverted_residual_setting: + output_channel = _make_divisible(c * width_mult, round_nearest) + for i in range(n): + stride = s if i == 0 else 1 + features.append(block(input_channel, output_channel, stride, expand_ratio=t)) + input_channel = output_channel + + self.features = nn.Sequential(*features) + self.fpn_selected = [1, 3, 6, 10, 13] + # weight initialization + for m in self.modules(): + if isinstance(m, nn.Conv2d): + nn.init.kaiming_normal_(m.weight, mode='fan_out') + if m.bias is not None: + nn.init.zeros_(m.bias) + elif isinstance(m, nn.BatchNorm2d): + nn.init.ones_(m.weight) + nn.init.zeros_(m.bias) + elif isinstance(m, nn.Linear): + nn.init.normal_(m.weight, 0, 0.01) + nn.init.zeros_(m.bias) + if pretrained: + self._load_pretrained_model() + + def _forward_impl(self, x): + # This exists since TorchScript doesn't support inheritance, so the superclass method + # (this one) needs to have a name other than `forward` that can be accessed in a subclass + fpn_features = [] + for i, f in enumerate(self.features): + if i > self.fpn_selected[-1]: + break + x = f(x) + if i in self.fpn_selected: + fpn_features.append(x) + + c1, c2, c3, c4, c5 = fpn_features + return c1, c2, c3, c4, c5 + + + def forward(self, x): + return self._forward_impl(x) + + def _load_pretrained_model(self): + pretrain_dict = model_zoo.load_url('https://download.pytorch.org/models/mobilenet_v2-b0353104.pth') + model_dict = {} + state_dict = self.state_dict() + for k, v in pretrain_dict.items(): + if k in state_dict: + model_dict[k] = v + state_dict.update(model_dict) + self.load_state_dict(state_dict) + + +class MobileV2_MLSD_Large(nn.Module): + def __init__(self): + super(MobileV2_MLSD_Large, self).__init__() + + self.backbone = MobileNetV2(pretrained=False) + ## A, B + self.block15 = BlockTypeA(in_c1= 64, in_c2= 96, + out_c1= 64, out_c2=64, + upscale=False) + self.block16 = BlockTypeB(128, 64) + + ## A, B + self.block17 = BlockTypeA(in_c1 = 32, in_c2 = 64, + out_c1= 64, out_c2= 64) + self.block18 = BlockTypeB(128, 64) + + ## A, B + self.block19 = BlockTypeA(in_c1=24, in_c2=64, + out_c1=64, out_c2=64) + self.block20 = BlockTypeB(128, 64) + + ## A, B, C + self.block21 = BlockTypeA(in_c1=16, in_c2=64, + out_c1=64, out_c2=64) + self.block22 = BlockTypeB(128, 64) + + self.block23 = BlockTypeC(64, 16) + + def forward(self, x): + c1, c2, c3, c4, c5 = self.backbone(x) + + x = self.block15(c4, c5) + x = self.block16(x) + + x = self.block17(c3, x) + x = self.block18(x) + + x = self.block19(c2, x) + x = self.block20(x) + + x = self.block21(c1, x) + x = self.block22(x) + x = self.block23(x) + x = x[:, 7:, :, :] + + return x diff --git a/modules/control/proc/mlsd/models/mbv2_mlsd_tiny.py b/modules/control/proc/mlsd/models/mbv2_mlsd_tiny.py new file mode 100644 index 000000000..4f043851e --- /dev/null +++ b/modules/control/proc/mlsd/models/mbv2_mlsd_tiny.py @@ -0,0 +1,275 @@ +import os +import sys +import torch +import torch.nn as nn +import torch.utils.model_zoo as model_zoo +from torch.nn import functional as F + + +class BlockTypeA(nn.Module): + def __init__(self, in_c1, in_c2, out_c1, out_c2, upscale = True): + super(BlockTypeA, self).__init__() + self.conv1 = nn.Sequential( + nn.Conv2d(in_c2, out_c2, kernel_size=1), + nn.BatchNorm2d(out_c2), + nn.ReLU(inplace=True) + ) + self.conv2 = nn.Sequential( + nn.Conv2d(in_c1, out_c1, kernel_size=1), + nn.BatchNorm2d(out_c1), + nn.ReLU(inplace=True) + ) + self.upscale = upscale + + def forward(self, a, b): + b = self.conv1(b) + a = self.conv2(a) + b = F.interpolate(b, scale_factor=2.0, mode='bilinear', align_corners=True) + return torch.cat((a, b), dim=1) + + +class BlockTypeB(nn.Module): + def __init__(self, in_c, out_c): + super(BlockTypeB, self).__init__() + self.conv1 = nn.Sequential( + nn.Conv2d(in_c, in_c, kernel_size=3, padding=1), + nn.BatchNorm2d(in_c), + nn.ReLU() + ) + self.conv2 = nn.Sequential( + nn.Conv2d(in_c, out_c, kernel_size=3, padding=1), + nn.BatchNorm2d(out_c), + nn.ReLU() + ) + + def forward(self, x): + x = self.conv1(x) + x + x = self.conv2(x) + return x + +class BlockTypeC(nn.Module): + def __init__(self, in_c, out_c): + super(BlockTypeC, self).__init__() + self.conv1 = nn.Sequential( + nn.Conv2d(in_c, in_c, kernel_size=3, padding=5, dilation=5), + nn.BatchNorm2d(in_c), + nn.ReLU() + ) + self.conv2 = nn.Sequential( + nn.Conv2d(in_c, in_c, kernel_size=3, padding=1), + nn.BatchNorm2d(in_c), + nn.ReLU() + ) + self.conv3 = nn.Conv2d(in_c, out_c, kernel_size=1) + + def forward(self, x): + x = self.conv1(x) + x = self.conv2(x) + x = self.conv3(x) + return x + +def _make_divisible(v, divisor, min_value=None): + """ + This function is taken from the original tf repo. + It ensures that all layers have a channel number that is divisible by 8 + It can be seen here: + https://github.com/tensorflow/models/blob/master/research/slim/nets/mobilenet/mobilenet.py + :param v: + :param divisor: + :param min_value: + :return: + """ + if min_value is None: + min_value = divisor + new_v = max(min_value, int(v + divisor / 2) // divisor * divisor) + # Make sure that round down does not go down by more than 10%. + if new_v < 0.9 * v: + new_v += divisor + return new_v + + +class ConvBNReLU(nn.Sequential): + def __init__(self, in_planes, out_planes, kernel_size=3, stride=1, groups=1): + self.channel_pad = out_planes - in_planes + self.stride = stride + #padding = (kernel_size - 1) // 2 + + # TFLite uses slightly different padding than PyTorch + if stride == 2: + padding = 0 + else: + padding = (kernel_size - 1) // 2 + + super(ConvBNReLU, self).__init__( + nn.Conv2d(in_planes, out_planes, kernel_size, stride, padding, groups=groups, bias=False), + nn.BatchNorm2d(out_planes), + nn.ReLU6(inplace=True) + ) + self.max_pool = nn.MaxPool2d(kernel_size=stride, stride=stride) + + + def forward(self, x): + # TFLite uses different padding + if self.stride == 2: + x = F.pad(x, (0, 1, 0, 1), "constant", 0) + #print(x.shape) + + for module in self: + if not isinstance(module, nn.MaxPool2d): + x = module(x) + return x + + +class InvertedResidual(nn.Module): + def __init__(self, inp, oup, stride, expand_ratio): + super(InvertedResidual, self).__init__() + self.stride = stride + assert stride in [1, 2] + + hidden_dim = int(round(inp * expand_ratio)) + self.use_res_connect = self.stride == 1 and inp == oup + + layers = [] + if expand_ratio != 1: + # pw + layers.append(ConvBNReLU(inp, hidden_dim, kernel_size=1)) + layers.extend([ + # dw + ConvBNReLU(hidden_dim, hidden_dim, stride=stride, groups=hidden_dim), + # pw-linear + nn.Conv2d(hidden_dim, oup, 1, 1, 0, bias=False), + nn.BatchNorm2d(oup), + ]) + self.conv = nn.Sequential(*layers) + + def forward(self, x): + if self.use_res_connect: + return x + self.conv(x) + else: + return self.conv(x) + + +class MobileNetV2(nn.Module): + def __init__(self, pretrained=True): + """ + MobileNet V2 main class + Args: + num_classes (int): Number of classes + width_mult (float): Width multiplier - adjusts number of channels in each layer by this amount + inverted_residual_setting: Network structure + round_nearest (int): Round the number of channels in each layer to be a multiple of this number + Set to 1 to turn off rounding + block: Module specifying inverted residual building block for mobilenet + """ + super(MobileNetV2, self).__init__() + + block = InvertedResidual + input_channel = 32 + last_channel = 1280 + width_mult = 1.0 + round_nearest = 8 + + inverted_residual_setting = [ + # t, c, n, s + [1, 16, 1, 1], + [6, 24, 2, 2], + [6, 32, 3, 2], + [6, 64, 4, 2], + #[6, 96, 3, 1], + #[6, 160, 3, 2], + #[6, 320, 1, 1], + ] + + # only check the first element, assuming user knows t,c,n,s are required + if len(inverted_residual_setting) == 0 or len(inverted_residual_setting[0]) != 4: + raise ValueError("inverted_residual_setting should be non-empty " + "or a 4-element list, got {}".format(inverted_residual_setting)) + + # building first layer + input_channel = _make_divisible(input_channel * width_mult, round_nearest) + self.last_channel = _make_divisible(last_channel * max(1.0, width_mult), round_nearest) + features = [ConvBNReLU(4, input_channel, stride=2)] + # building inverted residual blocks + for t, c, n, s in inverted_residual_setting: + output_channel = _make_divisible(c * width_mult, round_nearest) + for i in range(n): + stride = s if i == 0 else 1 + features.append(block(input_channel, output_channel, stride, expand_ratio=t)) + input_channel = output_channel + self.features = nn.Sequential(*features) + + self.fpn_selected = [3, 6, 10] + # weight initialization + for m in self.modules(): + if isinstance(m, nn.Conv2d): + nn.init.kaiming_normal_(m.weight, mode='fan_out') + if m.bias is not None: + nn.init.zeros_(m.bias) + elif isinstance(m, nn.BatchNorm2d): + nn.init.ones_(m.weight) + nn.init.zeros_(m.bias) + elif isinstance(m, nn.Linear): + nn.init.normal_(m.weight, 0, 0.01) + nn.init.zeros_(m.bias) + + #if pretrained: + # self._load_pretrained_model() + + def _forward_impl(self, x): + # This exists since TorchScript doesn't support inheritance, so the superclass method + # (this one) needs to have a name other than `forward` that can be accessed in a subclass + fpn_features = [] + for i, f in enumerate(self.features): + if i > self.fpn_selected[-1]: + break + x = f(x) + if i in self.fpn_selected: + fpn_features.append(x) + + c2, c3, c4 = fpn_features + return c2, c3, c4 + + + def forward(self, x): + return self._forward_impl(x) + + def _load_pretrained_model(self): + pretrain_dict = model_zoo.load_url('https://download.pytorch.org/models/mobilenet_v2-b0353104.pth') + model_dict = {} + state_dict = self.state_dict() + for k, v in pretrain_dict.items(): + if k in state_dict: + model_dict[k] = v + state_dict.update(model_dict) + self.load_state_dict(state_dict) + + +class MobileV2_MLSD_Tiny(nn.Module): + def __init__(self): + super(MobileV2_MLSD_Tiny, self).__init__() + + self.backbone = MobileNetV2(pretrained=True) + + self.block12 = BlockTypeA(in_c1= 32, in_c2= 64, + out_c1= 64, out_c2=64) + self.block13 = BlockTypeB(128, 64) + + self.block14 = BlockTypeA(in_c1 = 24, in_c2 = 64, + out_c1= 32, out_c2= 32) + self.block15 = BlockTypeB(64, 64) + + self.block16 = BlockTypeC(64, 16) + + def forward(self, x): + c2, c3, c4 = self.backbone(x) + + x = self.block12(c3, c4) + x = self.block13(x) + x = self.block14(c2, x) + x = self.block15(x) + x = self.block16(x) + x = x[:, 7:, :, :] + #print(x.shape) + x = F.interpolate(x, scale_factor=2.0, mode='bilinear', align_corners=True) + + return x diff --git a/modules/control/proc/mlsd/utils.py b/modules/control/proc/mlsd/utils.py new file mode 100644 index 000000000..ca8034370 --- /dev/null +++ b/modules/control/proc/mlsd/utils.py @@ -0,0 +1,580 @@ +''' +modified by lihaoweicv +pytorch version +''' + +''' +M-LSD +Copyright 2021-present NAVER Corp. +Apache License v2.0 +''' + +import os +import numpy as np +import cv2 +import torch +from torch.nn import functional as F + + +def deccode_output_score_and_ptss(tpMap, topk_n = 200, ksize = 5): + ''' + tpMap: + center: tpMap[1, 0, :, :] + displacement: tpMap[1, 1:5, :, :] + ''' + b, c, h, w = tpMap.shape + assert b==1, 'only support bsize==1' + displacement = tpMap[:, 1:5, :, :][0] + center = tpMap[:, 0, :, :] + heat = torch.sigmoid(center) + hmax = F.max_pool2d( heat, (ksize, ksize), stride=1, padding=(ksize-1)//2) + keep = (hmax == heat).float() + heat = heat * keep + heat = heat.reshape(-1, ) + + scores, indices = torch.topk(heat, topk_n, dim=-1, largest=True) + yy = torch.floor_divide(indices, w).unsqueeze(-1) + xx = torch.fmod(indices, w).unsqueeze(-1) + ptss = torch.cat((yy, xx),dim=-1) + + ptss = ptss.detach().cpu().numpy() + scores = scores.detach().cpu().numpy() + displacement = displacement.detach().cpu().numpy() + displacement = displacement.transpose((1,2,0)) + return ptss, scores, displacement + + +def pred_lines(image, model, + input_shape=None, + score_thr=0.10, + dist_thr=20.0): + if input_shape is None: + input_shape = [512, 512] + h, w, _ = image.shape + + device = next(iter(model.parameters())).device + h_ratio, w_ratio = [h / input_shape[0], w / input_shape[1]] + + resized_image = np.concatenate([cv2.resize(image, (input_shape[1], input_shape[0]), interpolation=cv2.INTER_AREA), + np.ones([input_shape[0], input_shape[1], 1])], axis=-1) + + resized_image = resized_image.transpose((2,0,1)) + batch_image = np.expand_dims(resized_image, axis=0).astype('float32') + batch_image = (batch_image / 127.5) - 1.0 + + batch_image = torch.from_numpy(batch_image).float() + batch_image = batch_image.to(device) + outputs = model(batch_image) + pts, pts_score, vmap = deccode_output_score_and_ptss(outputs, 200, 3) + start = vmap[:, :, :2] + end = vmap[:, :, 2:] + dist_map = np.sqrt(np.sum((start - end) ** 2, axis=-1)) + + segments_list = [] + for center, score in zip(pts, pts_score): + y, x = center + distance = dist_map[y, x] + if score > score_thr and distance > dist_thr: + disp_x_start, disp_y_start, disp_x_end, disp_y_end = vmap[y, x, :] + x_start = x + disp_x_start + y_start = y + disp_y_start + x_end = x + disp_x_end + y_end = y + disp_y_end + segments_list.append([x_start, y_start, x_end, y_end]) + + lines = 2 * np.array(segments_list) # 256 > 512 + lines[:, 0] = lines[:, 0] * w_ratio + lines[:, 1] = lines[:, 1] * h_ratio + lines[:, 2] = lines[:, 2] * w_ratio + lines[:, 3] = lines[:, 3] * h_ratio + + return lines + + +def pred_squares(image, + model, + input_shape=None, + params=None): + ''' + shape = [height, width] + ''' + if params is None: + params = {'score': 0.06, 'outside_ratio': 0.28, 'inside_ratio': 0.45, 'w_overlap': 0.0, 'w_degree': 1.95, 'w_length': 0.0, 'w_area': 1.86, 'w_center': 0.14} + if input_shape is None: + input_shape = [512, 512] + h, w, _ = image.shape + original_shape = [h, w] + device = next(iter(model.parameters())).device + + resized_image = np.concatenate([cv2.resize(image, (input_shape[0], input_shape[1]), interpolation=cv2.INTER_AREA), + np.ones([input_shape[0], input_shape[1], 1])], axis=-1) + resized_image = resized_image.transpose((2, 0, 1)) + batch_image = np.expand_dims(resized_image, axis=0).astype('float32') + batch_image = (batch_image / 127.5) - 1.0 + + batch_image = torch.from_numpy(batch_image).float().to(device) + outputs = model(batch_image) + + pts, pts_score, vmap = deccode_output_score_and_ptss(outputs, 200, 3) + start = vmap[:, :, :2] # (x, y) + end = vmap[:, :, 2:] # (x, y) + dist_map = np.sqrt(np.sum((start - end) ** 2, axis=-1)) + + junc_list = [] + segments_list = [] + for junc, score in zip(pts, pts_score): + y, x = junc + distance = dist_map[y, x] + if score > params['score'] and distance > 20.0: + junc_list.append([x, y]) + disp_x_start, disp_y_start, disp_x_end, disp_y_end = vmap[y, x, :] + d_arrow = 1.0 + x_start = x + d_arrow * disp_x_start + y_start = y + d_arrow * disp_y_start + x_end = x + d_arrow * disp_x_end + y_end = y + d_arrow * disp_y_end + segments_list.append([x_start, y_start, x_end, y_end]) + + segments = np.array(segments_list) + + ####### post processing for squares + # 1. get unique lines + point = np.array([[0, 0]]) + point = point[0] + start = segments[:, :2] + end = segments[:, 2:] + diff = start - end + a = diff[:, 1] + b = -diff[:, 0] + c = a * start[:, 0] + b * start[:, 1] + + d = np.abs(a * point[0] + b * point[1] - c) / np.sqrt(a ** 2 + b ** 2 + 1e-10) + theta = np.arctan2(diff[:, 0], diff[:, 1]) * 180 / np.pi + theta[theta < 0.0] += 180 + hough = np.concatenate([d[:, None], theta[:, None]], axis=-1) + + d_quant = 1 + theta_quant = 2 + hough[:, 0] //= d_quant + hough[:, 1] //= theta_quant + _, indices, counts = np.unique(hough, axis=0, return_index=True, return_counts=True) + + acc_map = np.zeros([512 // d_quant + 1, 360 // theta_quant + 1], dtype='float32') + idx_map = np.zeros([512 // d_quant + 1, 360 // theta_quant + 1], dtype='int32') - 1 + yx_indices = hough[indices, :].astype('int32') + acc_map[yx_indices[:, 0], yx_indices[:, 1]] = counts + idx_map[yx_indices[:, 0], yx_indices[:, 1]] = indices + + acc_map_np = acc_map + # acc_map = acc_map[None, :, :, None] + # + # ### fast suppression using tensorflow op + # acc_map = tf.constant(acc_map, dtype=tf.float32) + # max_acc_map = tf.keras.layers.MaxPool2D(pool_size=(5, 5), strides=1, padding='same')(acc_map) + # acc_map = acc_map * tf.cast(tf.math.equal(acc_map, max_acc_map), tf.float32) + # flatten_acc_map = tf.reshape(acc_map, [1, -1]) + # topk_values, topk_indices = tf.math.top_k(flatten_acc_map, k=len(pts)) + # _, h, w, _ = acc_map.shape + # y = tf.expand_dims(topk_indices // w, axis=-1) + # x = tf.expand_dims(topk_indices % w, axis=-1) + # yx = tf.concat([y, x], axis=-1) + + ### fast suppression using pytorch op + acc_map = torch.from_numpy(acc_map_np).unsqueeze(0).unsqueeze(0) + _,_, h, w = acc_map.shape + max_acc_map = F.max_pool2d(acc_map,kernel_size=5, stride=1, padding=2) + acc_map = acc_map * ( (acc_map == max_acc_map).float() ) + flatten_acc_map = acc_map.reshape([-1, ]) + + scores, indices = torch.topk(flatten_acc_map, len(pts), dim=-1, largest=True) + yy = torch.div(indices, w, rounding_mode='floor').unsqueeze(-1) + xx = torch.fmod(indices, w).unsqueeze(-1) + yx = torch.cat((yy, xx), dim=-1) + + yx = yx.detach().cpu().numpy() + + topk_values = scores.detach().cpu().numpy() + indices = idx_map[yx[:, 0], yx[:, 1]] + basis = 5 // 2 + + merged_segments = [] + for yx_pt, max_indice, value in zip(yx, indices, topk_values): + y, x = yx_pt + if max_indice == -1 or value == 0: + continue + segment_list = [] + for y_offset in range(-basis, basis + 1): + for x_offset in range(-basis, basis + 1): + indice = idx_map[y + y_offset, x + x_offset] + cnt = int(acc_map_np[y + y_offset, x + x_offset]) + if indice != -1: + segment_list.append(segments[indice]) + if cnt > 1: + check_cnt = 1 + current_hough = hough[indice] + for new_indice, new_hough in enumerate(hough): + if (current_hough == new_hough).all() and indice != new_indice: + segment_list.append(segments[new_indice]) + check_cnt += 1 + if check_cnt == cnt: + break + group_segments = np.array(segment_list).reshape([-1, 2]) + sorted_group_segments = np.sort(group_segments, axis=0) + x_min, y_min = sorted_group_segments[0, :] + x_max, y_max = sorted_group_segments[-1, :] + + deg = theta[max_indice] + if deg >= 90: + merged_segments.append([x_min, y_max, x_max, y_min]) + else: + merged_segments.append([x_min, y_min, x_max, y_max]) + + # 2. get intersections + new_segments = np.array(merged_segments) # (x1, y1, x2, y2) + start = new_segments[:, :2] # (x1, y1) + end = new_segments[:, 2:] # (x2, y2) + new_centers = (start + end) / 2.0 + diff = start - end + dist_segments = np.sqrt(np.sum(diff ** 2, axis=-1)) + + # ax + by = c + a = diff[:, 1] + b = -diff[:, 0] + c = a * start[:, 0] + b * start[:, 1] + pre_det = a[:, None] * b[None, :] + det = pre_det - np.transpose(pre_det) + + pre_inter_y = a[:, None] * c[None, :] + inter_y = (pre_inter_y - np.transpose(pre_inter_y)) / (det + 1e-10) + pre_inter_x = c[:, None] * b[None, :] + inter_x = (pre_inter_x - np.transpose(pre_inter_x)) / (det + 1e-10) + inter_pts = np.concatenate([inter_x[:, :, None], inter_y[:, :, None]], axis=-1).astype('int32') + + # 3. get corner information + # 3.1 get distance + ''' + dist_segments: + | dist(0), dist(1), dist(2), ...| + dist_inter_to_segment1: + | dist(inter,0), dist(inter,0), dist(inter,0), ... | + | dist(inter,1), dist(inter,1), dist(inter,1), ... | + ... + dist_inter_to_semgnet2: + | dist(inter,0), dist(inter,1), dist(inter,2), ... | + | dist(inter,0), dist(inter,1), dist(inter,2), ... | + ... + ''' + + dist_inter_to_segment1_start = np.sqrt( + np.sum(((inter_pts - start[:, None, :]) ** 2), axis=-1, keepdims=True)) # [n_batch, n_batch, 1] + dist_inter_to_segment1_end = np.sqrt( + np.sum(((inter_pts - end[:, None, :]) ** 2), axis=-1, keepdims=True)) # [n_batch, n_batch, 1] + dist_inter_to_segment2_start = np.sqrt( + np.sum(((inter_pts - start[None, :, :]) ** 2), axis=-1, keepdims=True)) # [n_batch, n_batch, 1] + dist_inter_to_segment2_end = np.sqrt( + np.sum(((inter_pts - end[None, :, :]) ** 2), axis=-1, keepdims=True)) # [n_batch, n_batch, 1] + + # sort ascending + dist_inter_to_segment1 = np.sort( + np.concatenate([dist_inter_to_segment1_start, dist_inter_to_segment1_end], axis=-1), + axis=-1) # [n_batch, n_batch, 2] + dist_inter_to_segment2 = np.sort( + np.concatenate([dist_inter_to_segment2_start, dist_inter_to_segment2_end], axis=-1), + axis=-1) # [n_batch, n_batch, 2] + + # 3.2 get degree + inter_to_start = new_centers[:, None, :] - inter_pts + deg_inter_to_start = np.arctan2(inter_to_start[:, :, 1], inter_to_start[:, :, 0]) * 180 / np.pi + deg_inter_to_start[deg_inter_to_start < 0.0] += 360 + inter_to_end = new_centers[None, :, :] - inter_pts + deg_inter_to_end = np.arctan2(inter_to_end[:, :, 1], inter_to_end[:, :, 0]) * 180 / np.pi + deg_inter_to_end[deg_inter_to_end < 0.0] += 360 + + ''' + B -- G + | | + C -- R + B : blue / G: green / C: cyan / R: red + + 0 -- 1 + | | + 3 -- 2 + ''' + # rename variables + deg1_map, deg2_map = deg_inter_to_start, deg_inter_to_end + # sort deg ascending + deg_sort = np.sort(np.concatenate([deg1_map[:, :, None], deg2_map[:, :, None]], axis=-1), axis=-1) + + deg_diff_map = np.abs(deg1_map - deg2_map) + # we only consider the smallest degree of intersect + deg_diff_map[deg_diff_map > 180] = 360 - deg_diff_map[deg_diff_map > 180] + + # define available degree range + deg_range = [60, 120] + + corner_dict = {corner_info: [] for corner_info in range(4)} + inter_points = [] + for i in range(inter_pts.shape[0]): + for j in range(i + 1, inter_pts.shape[1]): + # i, j > line index, always i < j + x, y = inter_pts[i, j, :] + deg1, deg2 = deg_sort[i, j, :] + deg_diff = deg_diff_map[i, j] + + check_degree = deg_diff > deg_range[0] and deg_diff < deg_range[1] + + outside_ratio = params['outside_ratio'] # over ratio >>> drop it! + inside_ratio = params['inside_ratio'] # over ratio >>> drop it! + check_distance = ((dist_inter_to_segment1[i, j, 1] >= dist_segments[i] and \ + dist_inter_to_segment1[i, j, 0] <= dist_segments[i] * outside_ratio) or \ + (dist_inter_to_segment1[i, j, 1] <= dist_segments[i] and \ + dist_inter_to_segment1[i, j, 0] <= dist_segments[i] * inside_ratio)) and \ + ((dist_inter_to_segment2[i, j, 1] >= dist_segments[j] and \ + dist_inter_to_segment2[i, j, 0] <= dist_segments[j] * outside_ratio) or \ + (dist_inter_to_segment2[i, j, 1] <= dist_segments[j] and \ + dist_inter_to_segment2[i, j, 0] <= dist_segments[j] * inside_ratio)) + + if check_degree and check_distance: + corner_info = None + + if (deg1 >= 0 and deg1 <= 45 and deg2 >= 45 and deg2 <= 120) or \ + (deg2 >= 315 and deg1 >= 45 and deg1 <= 120): + corner_info, _color_info = 0, 'blue' + elif (deg1 >= 45 and deg1 <= 125 and deg2 >= 125 and deg2 <= 225): + corner_info, _color_info = 1, 'green' + elif (deg1 >= 125 and deg1 <= 225 and deg2 >= 225 and deg2 <= 315): + corner_info, _color_info = 2, 'black' + elif (deg1 >= 0 and deg1 <= 45 and deg2 >= 225 and deg2 <= 315) or \ + (deg2 >= 315 and deg1 >= 225 and deg1 <= 315): + corner_info, _color_info = 3, 'cyan' + else: + corner_info, _color_info = 4, 'red' # we don't use it + continue + + corner_dict[corner_info].append([x, y, i, j]) + inter_points.append([x, y]) + + square_list = [] + connect_list = [] + segments_list = [] + for corner0 in corner_dict[0]: + for corner1 in corner_dict[1]: + connect01 = False + for corner0_line in corner0[2:]: + if corner0_line in corner1[2:]: + connect01 = True + break + if connect01: + for corner2 in corner_dict[2]: + connect12 = False + for corner1_line in corner1[2:]: + if corner1_line in corner2[2:]: + connect12 = True + break + if connect12: + for corner3 in corner_dict[3]: + connect23 = False + for corner2_line in corner2[2:]: + if corner2_line in corner3[2:]: + connect23 = True + break + if connect23: + for corner3_line in corner3[2:]: + if corner3_line in corner0[2:]: + # SQUARE!!! + ''' + 0 -- 1 + | | + 3 -- 2 + square_list: + order: 0 > 1 > 2 > 3 + | x0, y0, x1, y1, x2, y2, x3, y3 | + | x0, y0, x1, y1, x2, y2, x3, y3 | + ... + connect_list: + order: 01 > 12 > 23 > 30 + | line_idx01, line_idx12, line_idx23, line_idx30 | + | line_idx01, line_idx12, line_idx23, line_idx30 | + ... + segments_list: + order: 0 > 1 > 2 > 3 + | line_idx0_i, line_idx0_j, line_idx1_i, line_idx1_j, line_idx2_i, line_idx2_j, line_idx3_i, line_idx3_j | + | line_idx0_i, line_idx0_j, line_idx1_i, line_idx1_j, line_idx2_i, line_idx2_j, line_idx3_i, line_idx3_j | + ... + ''' + square_list.append(corner0[:2] + corner1[:2] + corner2[:2] + corner3[:2]) + connect_list.append([corner0_line, corner1_line, corner2_line, corner3_line]) + segments_list.append(corner0[2:] + corner1[2:] + corner2[2:] + corner3[2:]) + + def check_outside_inside(segments_info, connect_idx): + # return 'outside or inside', min distance, cover_param, peri_param + if connect_idx == segments_info[0]: + check_dist_mat = dist_inter_to_segment1 + else: + check_dist_mat = dist_inter_to_segment2 + + i, j = segments_info + min_dist, max_dist = check_dist_mat[i, j, :] + connect_dist = dist_segments[connect_idx] + if max_dist > connect_dist: + return 'outside', min_dist, 0, 1 + else: + return 'inside', min_dist, -1, -1 + + + try: + map_size = input_shape[0] / 2 + squares = np.array(square_list).reshape([-1, 4, 2]) + score_array = [] + connect_array = np.array(connect_list) + segments_array = np.array(segments_list).reshape([-1, 4, 2]) + + # get degree of corners: + squares_rollup = np.roll(squares, 1, axis=1) + squares_rolldown = np.roll(squares, -1, axis=1) + vec1 = squares_rollup - squares + normalized_vec1 = vec1 / (np.linalg.norm(vec1, axis=-1, keepdims=True) + 1e-10) + vec2 = squares_rolldown - squares + normalized_vec2 = vec2 / (np.linalg.norm(vec2, axis=-1, keepdims=True) + 1e-10) + inner_products = np.sum(normalized_vec1 * normalized_vec2, axis=-1) # [n_squares, 4] + squares_degree = np.arccos(inner_products) * 180 / np.pi # [n_squares, 4] + + # get square score + overlap_scores = [] + degree_scores = [] + length_scores = [] + + for connects, segments, square, degree in zip(connect_array, segments_array, squares, squares_degree): + ''' + 0 -- 1 + | | + 3 -- 2 + + # segments: [4, 2] + # connects: [4] + ''' + + ###################################### OVERLAP SCORES + cover = 0 + perimeter = 0 + # check 0 > 1 > 2 > 3 + square_length = [] + + for start_idx in range(4): + end_idx = (start_idx + 1) % 4 + + connect_idx = connects[start_idx] # segment idx of segment01 + start_segments = segments[start_idx] + end_segments = segments[end_idx] + + square[start_idx] + square[end_idx] + + # check whether outside or inside + start_position, start_min, start_cover_param, start_peri_param = check_outside_inside(start_segments, + connect_idx) + end_position, end_min, end_cover_param, end_peri_param = check_outside_inside(end_segments, connect_idx) + + cover += dist_segments[connect_idx] + start_cover_param * start_min + end_cover_param * end_min + perimeter += dist_segments[connect_idx] + start_peri_param * start_min + end_peri_param * end_min + + square_length.append( + dist_segments[connect_idx] + start_peri_param * start_min + end_peri_param * end_min) + + overlap_scores.append(cover / perimeter) + ###################################### + ###################################### DEGREE SCORES + ''' + deg0 vs deg2 + deg1 vs deg3 + ''' + deg0, deg1, deg2, deg3 = degree + deg_ratio1 = deg0 / deg2 + if deg_ratio1 > 1.0: + deg_ratio1 = 1 / deg_ratio1 + deg_ratio2 = deg1 / deg3 + if deg_ratio2 > 1.0: + deg_ratio2 = 1 / deg_ratio2 + degree_scores.append((deg_ratio1 + deg_ratio2) / 2) + ###################################### + ###################################### LENGTH SCORES + ''' + len0 vs len2 + len1 vs len3 + ''' + len0, len1, len2, len3 = square_length + len_ratio1 = len0 / len2 if len2 > len0 else len2 / len0 + len_ratio2 = len1 / len3 if len3 > len1 else len3 / len1 + length_scores.append((len_ratio1 + len_ratio2) / 2) + + ###################################### + + overlap_scores = np.array(overlap_scores) + overlap_scores /= np.max(overlap_scores) + + degree_scores = np.array(degree_scores) + # degree_scores /= np.max(degree_scores) + + length_scores = np.array(length_scores) + + ###################################### AREA SCORES + area_scores = np.reshape(squares, [-1, 4, 2]) + area_x = area_scores[:, :, 0] + area_y = area_scores[:, :, 1] + correction = area_x[:, -1] * area_y[:, 0] - area_y[:, -1] * area_x[:, 0] + area_scores = np.sum(area_x[:, :-1] * area_y[:, 1:], axis=-1) - np.sum(area_y[:, :-1] * area_x[:, 1:], axis=-1) + area_scores = 0.5 * np.abs(area_scores + correction) + area_scores /= (map_size * map_size) # np.max(area_scores) + ###################################### + + ###################################### CENTER SCORES + centers = np.array([[256 // 2, 256 // 2]], dtype='float32') # [1, 2] + # squares: [n, 4, 2] + square_centers = np.mean(squares, axis=1) # [n, 2] + center2center = np.sqrt(np.sum((centers - square_centers) ** 2)) + center_scores = center2center / (map_size / np.sqrt(2.0)) + + ''' + score_w = [overlap, degree, area, center, length] + ''' + score_array = params['w_overlap'] * overlap_scores \ + + params['w_degree'] * degree_scores \ + + params['w_area'] * area_scores \ + - params['w_center'] * center_scores \ + + params['w_length'] * length_scores + + + sorted_idx = np.argsort(score_array)[::-1] + score_array = score_array[sorted_idx] + squares = squares[sorted_idx] + + except Exception: + pass + + '''return list + merged_lines, squares, scores + ''' + + try: + new_segments[:, 0] = new_segments[:, 0] * 2 / input_shape[1] * original_shape[1] + new_segments[:, 1] = new_segments[:, 1] * 2 / input_shape[0] * original_shape[0] + new_segments[:, 2] = new_segments[:, 2] * 2 / input_shape[1] * original_shape[1] + new_segments[:, 3] = new_segments[:, 3] * 2 / input_shape[0] * original_shape[0] + except Exception: + new_segments = [] + + try: + squares[:, :, 0] = squares[:, :, 0] * 2 / input_shape[1] * original_shape[1] + squares[:, :, 1] = squares[:, :, 1] * 2 / input_shape[0] * original_shape[0] + except Exception: + squares = [] + score_array = [] + + try: + inter_points = np.array(inter_points) + inter_points[:, 0] = inter_points[:, 0] * 2 / input_shape[1] * original_shape[1] + inter_points[:, 1] = inter_points[:, 1] * 2 / input_shape[0] * original_shape[0] + except Exception: + inter_points = [] + + return new_segments, squares, score_array, inter_points diff --git a/modules/control/proc/normalbae/LICENSE b/modules/control/proc/normalbae/LICENSE new file mode 100644 index 000000000..16a9d56a3 --- /dev/null +++ b/modules/control/proc/normalbae/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2022 Caroline Chan + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. \ No newline at end of file diff --git a/modules/control/proc/normalbae/__init__.py b/modules/control/proc/normalbae/__init__.py new file mode 100644 index 000000000..852189f68 --- /dev/null +++ b/modules/control/proc/normalbae/__init__.py @@ -0,0 +1,107 @@ +import os +import types +import warnings + +import cv2 +import numpy as np +import torch +import torchvision.transforms as transforms +from einops import rearrange +from huggingface_hub import hf_hub_download +from PIL import Image + +from modules.control.util import HWC3, resize_image +from .nets.NNET import NNET + + +# load model +def load_checkpoint(fpath, model): + ckpt = torch.load(fpath, map_location='cpu')['model'] + + load_dict = {} + for k, v in ckpt.items(): + if k.startswith('module.'): + k_ = k.replace('module.', '') + load_dict[k_] = v + else: + load_dict[k] = v + + model.load_state_dict(load_dict) + return model + +class NormalBaeDetector: + def __init__(self, model): + self.model = model + self.norm = transforms.Normalize(mean=[0.485, 0.456, 0.406], std=[0.229, 0.224, 0.225]) + + @classmethod + def from_pretrained(cls, pretrained_model_or_path, filename=None, cache_dir=None): + filename = filename or "scannet.pt" + + if os.path.isdir(pretrained_model_or_path): + model_path = os.path.join(pretrained_model_or_path, filename) + else: + model_path = hf_hub_download(pretrained_model_or_path, filename, cache_dir=cache_dir) + + args = types.SimpleNamespace() + args.mode = 'client' + args.architecture = 'BN' + args.pretrained = 'scannet' + args.sampling_ratio = 0.4 + args.importance_ratio = 0.7 + model = NNET(args) + model = load_checkpoint(model_path, model) + model.eval() + + return cls(model) + + def to(self, device): + self.model.to(device) + return self + + + def __call__(self, input_image, detect_resolution=512, image_resolution=512, output_type="pil", **kwargs): + if "return_pil" in kwargs: + warnings.warn("return_pil is deprecated. Use output_type instead.", DeprecationWarning) + output_type = "pil" if kwargs["return_pil"] else "np" + if type(output_type) is bool: + warnings.warn("Passing `True` or `False` to `output_type` is deprecated and will raise an error in future versions") + if output_type: + output_type = "pil" + + device = next(iter(self.model.parameters())).device + if not isinstance(input_image, np.ndarray): + input_image = np.array(input_image, dtype=np.uint8) + + input_image = HWC3(input_image) + input_image = resize_image(input_image, detect_resolution) + + assert input_image.ndim == 3 + image_normal = input_image + image_normal = torch.from_numpy(image_normal).float().to(device) + image_normal = image_normal / 255.0 + image_normal = rearrange(image_normal, 'h w c -> 1 c h w') + image_normal = self.norm(image_normal) + + normal = self.model(image_normal) + normal = normal[0][-1][:, :3] + # d = torch.sum(normal ** 2.0, dim=1, keepdim=True) ** 0.5 + # d = torch.maximum(d, torch.ones_like(d) * 1e-5) + # normal /= d + normal = ((normal + 1) * 0.5).clip(0, 1) + + normal = rearrange(normal[0], 'c h w -> h w c').cpu().numpy() + normal_image = (normal * 255.0).clip(0, 255).astype(np.uint8) + + detected_map = normal_image + detected_map = HWC3(detected_map) + + img = resize_image(input_image, image_resolution) + H, W, _C = img.shape + + detected_map = cv2.resize(detected_map, (W, H), interpolation=cv2.INTER_LINEAR) + + if output_type == "pil": + detected_map = Image.fromarray(detected_map) + + return detected_map diff --git a/modules/control/proc/normalbae/nets/NNET.py b/modules/control/proc/normalbae/nets/NNET.py new file mode 100644 index 000000000..1445fa5a0 --- /dev/null +++ b/modules/control/proc/normalbae/nets/NNET.py @@ -0,0 +1,19 @@ +import torch.nn as nn +from .submodules.encoder import Encoder +from .submodules.decoder import Decoder + + +class NNET(nn.Module): + def __init__(self, args): + super(NNET, self).__init__() + self.encoder = Encoder() + self.decoder = Decoder(args) + + def get_1x_lr_params(self): # lr/10 learning rate + return self.encoder.parameters() + + def get_10x_lr_params(self): # lr learning rate + return self.decoder.parameters() + + def forward(self, img, **kwargs): + return self.decoder(self.encoder(img), **kwargs) diff --git a/modules/control/proc/normalbae/nets/__init__.py b/modules/control/proc/normalbae/nets/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/modules/control/proc/normalbae/nets/baseline.py b/modules/control/proc/normalbae/nets/baseline.py new file mode 100644 index 000000000..61d610be3 --- /dev/null +++ b/modules/control/proc/normalbae/nets/baseline.py @@ -0,0 +1,78 @@ +import torch +import torch.nn as nn +import torch.nn.functional as F + +from .submodules.submodules import UpSampleBN, norm_normalize + + +# This is the baseline encoder-decoder we used in the ablation study +class NNET(nn.Module): + def __init__(self, args=None): + super(NNET, self).__init__() + self.encoder = Encoder() + self.decoder = Decoder(num_classes=4) + + def forward(self, x, **kwargs): + out = self.decoder(self.encoder(x), **kwargs) + + # Bilinearly upsample the output to match the input resolution + up_out = F.interpolate(out, size=[x.size(2), x.size(3)], mode='bilinear', align_corners=False) + + # L2-normalize the first three channels / ensure positive value for concentration parameters (kappa) + up_out = norm_normalize(up_out) + return up_out + + def get_1x_lr_params(self): # lr/10 learning rate + return self.encoder.parameters() + + def get_10x_lr_params(self): # lr learning rate + modules = [self.decoder] + for m in modules: + yield from m.parameters() + + +# Encoder +class Encoder(nn.Module): + def __init__(self): + super(Encoder, self).__init__() + + basemodel_name = 'tf_efficientnet_b5_ap' + basemodel = torch.hub.load('rwightman/gen-efficientnet-pytorch', basemodel_name, pretrained=True) + + # Remove last layer + basemodel.global_pool = nn.Identity() + basemodel.classifier = nn.Identity() + + self.original_model = basemodel + + def forward(self, x): + features = [x] + for k, v in self.original_model._modules.items(): + if (k == 'blocks'): + for _ki, vi in v._modules.items(): + features.append(vi(features[-1])) + else: + features.append(v(features[-1])) + return features + + +# Decoder (no pixel-wise MLP, no uncertainty-guided sampling) +class Decoder(nn.Module): + def __init__(self, num_classes=4): + super(Decoder, self).__init__() + self.conv2 = nn.Conv2d(2048, 2048, kernel_size=1, stride=1, padding=0) + self.up1 = UpSampleBN(skip_input=2048 + 176, output_features=1024) + self.up2 = UpSampleBN(skip_input=1024 + 64, output_features=512) + self.up3 = UpSampleBN(skip_input=512 + 40, output_features=256) + self.up4 = UpSampleBN(skip_input=256 + 24, output_features=128) + self.conv3 = nn.Conv2d(128, num_classes, kernel_size=3, stride=1, padding=1) + + def forward(self, features): + x_block0, x_block1, x_block2, x_block3, x_block4 = features[4], features[5], features[6], features[8], features[11] + x_d0 = self.conv2(x_block4) + x_d1 = self.up1(x_d0, x_block3) + x_d2 = self.up2(x_d1, x_block2) + x_d3 = self.up3(x_d2, x_block1) + x_d4 = self.up4(x_d3, x_block0) + out = self.conv3(x_d4) + return out diff --git a/modules/control/proc/normalbae/nets/submodules/__init__.py b/modules/control/proc/normalbae/nets/submodules/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/modules/control/proc/normalbae/nets/submodules/decoder.py b/modules/control/proc/normalbae/nets/submodules/decoder.py new file mode 100644 index 000000000..993203d17 --- /dev/null +++ b/modules/control/proc/normalbae/nets/submodules/decoder.py @@ -0,0 +1,202 @@ +import torch +import torch.nn as nn +import torch.nn.functional as F +from .submodules import UpSampleBN, UpSampleGN, norm_normalize, sample_points + + +class Decoder(nn.Module): + def __init__(self, args): + super(Decoder, self).__init__() + + # hyper-parameter for sampling + self.sampling_ratio = args.sampling_ratio + self.importance_ratio = args.importance_ratio + + # feature-map + self.conv2 = nn.Conv2d(2048, 2048, kernel_size=1, stride=1, padding=0) + if args.architecture == 'BN': + self.up1 = UpSampleBN(skip_input=2048 + 176, output_features=1024) + self.up2 = UpSampleBN(skip_input=1024 + 64, output_features=512) + self.up3 = UpSampleBN(skip_input=512 + 40, output_features=256) + self.up4 = UpSampleBN(skip_input=256 + 24, output_features=128) + + elif args.architecture == 'GN': + self.up1 = UpSampleGN(skip_input=2048 + 176, output_features=1024) + self.up2 = UpSampleGN(skip_input=1024 + 64, output_features=512) + self.up3 = UpSampleGN(skip_input=512 + 40, output_features=256) + self.up4 = UpSampleGN(skip_input=256 + 24, output_features=128) + + else: + raise Exception('invalid architecture') + + # produces 1/8 res output + self.out_conv_res8 = nn.Conv2d(512, 4, kernel_size=3, stride=1, padding=1) + + # produces 1/4 res output + self.out_conv_res4 = nn.Sequential( + nn.Conv1d(512 + 4, 128, kernel_size=1), nn.ReLU(), + nn.Conv1d(128, 128, kernel_size=1), nn.ReLU(), + nn.Conv1d(128, 128, kernel_size=1), nn.ReLU(), + nn.Conv1d(128, 4, kernel_size=1), + ) + + # produces 1/2 res output + self.out_conv_res2 = nn.Sequential( + nn.Conv1d(256 + 4, 128, kernel_size=1), nn.ReLU(), + nn.Conv1d(128, 128, kernel_size=1), nn.ReLU(), + nn.Conv1d(128, 128, kernel_size=1), nn.ReLU(), + nn.Conv1d(128, 4, kernel_size=1), + ) + + # produces 1/1 res output + self.out_conv_res1 = nn.Sequential( + nn.Conv1d(128 + 4, 128, kernel_size=1), nn.ReLU(), + nn.Conv1d(128, 128, kernel_size=1), nn.ReLU(), + nn.Conv1d(128, 128, kernel_size=1), nn.ReLU(), + nn.Conv1d(128, 4, kernel_size=1), + ) + + def forward(self, features, gt_norm_mask=None, mode='test'): + x_block0, x_block1, x_block2, x_block3, x_block4 = features[4], features[5], features[6], features[8], features[11] + + # generate feature-map + + x_d0 = self.conv2(x_block4) # x_d0 : [2, 2048, 15, 20] 1/32 res + x_d1 = self.up1(x_d0, x_block3) # x_d1 : [2, 1024, 30, 40] 1/16 res + x_d2 = self.up2(x_d1, x_block2) # x_d2 : [2, 512, 60, 80] 1/8 res + x_d3 = self.up3(x_d2, x_block1) # x_d3: [2, 256, 120, 160] 1/4 res + x_d4 = self.up4(x_d3, x_block0) # x_d4: [2, 128, 240, 320] 1/2 res + + # 1/8 res output + out_res8 = self.out_conv_res8(x_d2) # out_res8: [2, 4, 60, 80] 1/8 res output + out_res8 = norm_normalize(out_res8) # out_res8: [2, 4, 60, 80] 1/8 res output + + ################################################################################################################ + # out_res4 + ################################################################################################################ + + if mode == 'train': + # upsampling ... out_res8: [2, 4, 60, 80] -> out_res8_res4: [2, 4, 120, 160] + out_res8_res4 = F.interpolate(out_res8, scale_factor=2, mode='bilinear', align_corners=True) + B, _, H, W = out_res8_res4.shape + + # samples: [B, 1, N, 2] + point_coords_res4, rows_int, cols_int = sample_points(out_res8_res4.detach(), gt_norm_mask, + sampling_ratio=self.sampling_ratio, + beta=self.importance_ratio) + + # output (needed for evaluation / visualization) + out_res4 = out_res8_res4 + + # grid_sample feature-map + feat_res4 = F.grid_sample(x_d2, point_coords_res4, mode='bilinear', align_corners=True) # (B, 512, 1, N) + init_pred = F.grid_sample(out_res8, point_coords_res4, mode='bilinear', align_corners=True) # (B, 4, 1, N) + feat_res4 = torch.cat([feat_res4, init_pred], dim=1) # (B, 512+4, 1, N) + + # prediction (needed to compute loss) + samples_pred_res4 = self.out_conv_res4(feat_res4[:, :, 0, :]) # (B, 4, N) + samples_pred_res4 = norm_normalize(samples_pred_res4) # (B, 4, N) - normalized + + for i in range(B): + out_res4[i, :, rows_int[i, :], cols_int[i, :]] = samples_pred_res4[i, :, :] + + else: + # grid_sample feature-map + feat_map = F.interpolate(x_d2, scale_factor=2, mode='bilinear', align_corners=True) + init_pred = F.interpolate(out_res8, scale_factor=2, mode='bilinear', align_corners=True) + feat_map = torch.cat([feat_map, init_pred], dim=1) # (B, 512+4, H, W) + B, _, H, W = feat_map.shape + + # try all pixels + out_res4 = self.out_conv_res4(feat_map.view(B, 512 + 4, -1)) # (B, 4, N) + out_res4 = norm_normalize(out_res4) # (B, 4, N) - normalized + out_res4 = out_res4.view(B, 4, H, W) + samples_pred_res4 = point_coords_res4 = None + + ################################################################################################################ + # out_res2 + ################################################################################################################ + + if mode == 'train': + + # upsampling ... out_res4: [2, 4, 120, 160] -> out_res4_res2: [2, 4, 240, 320] + out_res4_res2 = F.interpolate(out_res4, scale_factor=2, mode='bilinear', align_corners=True) + B, _, H, W = out_res4_res2.shape + + # samples: [B, 1, N, 2] + point_coords_res2, rows_int, cols_int = sample_points(out_res4_res2.detach(), gt_norm_mask, + sampling_ratio=self.sampling_ratio, + beta=self.importance_ratio) + + # output (needed for evaluation / visualization) + out_res2 = out_res4_res2 + + # grid_sample feature-map + feat_res2 = F.grid_sample(x_d3, point_coords_res2, mode='bilinear', align_corners=True) # (B, 256, 1, N) + init_pred = F.grid_sample(out_res4, point_coords_res2, mode='bilinear', align_corners=True) # (B, 4, 1, N) + feat_res2 = torch.cat([feat_res2, init_pred], dim=1) # (B, 256+4, 1, N) + + # prediction (needed to compute loss) + samples_pred_res2 = self.out_conv_res2(feat_res2[:, :, 0, :]) # (B, 4, N) + samples_pred_res2 = norm_normalize(samples_pred_res2) # (B, 4, N) - normalized + + for i in range(B): + out_res2[i, :, rows_int[i, :], cols_int[i, :]] = samples_pred_res2[i, :, :] + + else: + # grid_sample feature-map + feat_map = F.interpolate(x_d3, scale_factor=2, mode='bilinear', align_corners=True) + init_pred = F.interpolate(out_res4, scale_factor=2, mode='bilinear', align_corners=True) + feat_map = torch.cat([feat_map, init_pred], dim=1) # (B, 512+4, H, W) + B, _, H, W = feat_map.shape + + out_res2 = self.out_conv_res2(feat_map.view(B, 256 + 4, -1)) # (B, 4, N) + out_res2 = norm_normalize(out_res2) # (B, 4, N) - normalized + out_res2 = out_res2.view(B, 4, H, W) + samples_pred_res2 = point_coords_res2 = None + + ################################################################################################################ + # out_res1 + ################################################################################################################ + + if mode == 'train': + # upsampling ... out_res4: [2, 4, 120, 160] -> out_res4_res2: [2, 4, 240, 320] + out_res2_res1 = F.interpolate(out_res2, scale_factor=2, mode='bilinear', align_corners=True) + B, _, H, W = out_res2_res1.shape + + # samples: [B, 1, N, 2] + point_coords_res1, rows_int, cols_int = sample_points(out_res2_res1.detach(), gt_norm_mask, + sampling_ratio=self.sampling_ratio, + beta=self.importance_ratio) + + # output (needed for evaluation / visualization) + out_res1 = out_res2_res1 + + # grid_sample feature-map + feat_res1 = F.grid_sample(x_d4, point_coords_res1, mode='bilinear', align_corners=True) # (B, 128, 1, N) + init_pred = F.grid_sample(out_res2, point_coords_res1, mode='bilinear', align_corners=True) # (B, 4, 1, N) + feat_res1 = torch.cat([feat_res1, init_pred], dim=1) # (B, 128+4, 1, N) + + # prediction (needed to compute loss) + samples_pred_res1 = self.out_conv_res1(feat_res1[:, :, 0, :]) # (B, 4, N) + samples_pred_res1 = norm_normalize(samples_pred_res1) # (B, 4, N) - normalized + + for i in range(B): + out_res1[i, :, rows_int[i, :], cols_int[i, :]] = samples_pred_res1[i, :, :] + + else: + # grid_sample feature-map + feat_map = F.interpolate(x_d4, scale_factor=2, mode='bilinear', align_corners=True) + init_pred = F.interpolate(out_res2, scale_factor=2, mode='bilinear', align_corners=True) + feat_map = torch.cat([feat_map, init_pred], dim=1) # (B, 512+4, H, W) + B, _, H, W = feat_map.shape + + out_res1 = self.out_conv_res1(feat_map.view(B, 128 + 4, -1)) # (B, 4, N) + out_res1 = norm_normalize(out_res1) # (B, 4, N) - normalized + out_res1 = out_res1.view(B, 4, H, W) + samples_pred_res1 = point_coords_res1 = None + + return [out_res8, out_res4, out_res2, out_res1], \ + [out_res8, samples_pred_res4, samples_pred_res2, samples_pred_res1], \ + [None, point_coords_res4, point_coords_res2, point_coords_res1] + diff --git a/modules/control/proc/normalbae/nets/submodules/efficientnet_repo/BENCHMARK.md b/modules/control/proc/normalbae/nets/submodules/efficientnet_repo/BENCHMARK.md new file mode 100644 index 000000000..6ead7171c --- /dev/null +++ b/modules/control/proc/normalbae/nets/submodules/efficientnet_repo/BENCHMARK.md @@ -0,0 +1,555 @@ +# Model Performance Benchmarks + +All benchmarks run as per: + +``` +python onnx_export.py --model mobilenetv3_100 ./mobilenetv3_100.onnx +python onnx_optimize.py ./mobilenetv3_100.onnx --output mobilenetv3_100-opt.onnx +python onnx_to_caffe.py ./mobilenetv3_100.onnx --c2-prefix mobilenetv3 +python onnx_to_caffe.py ./mobilenetv3_100-opt.onnx --c2-prefix mobilenetv3-opt +python caffe2_benchmark.py --c2-init ./mobilenetv3.init.pb --c2-predict ./mobilenetv3.predict.pb +python caffe2_benchmark.py --c2-init ./mobilenetv3-opt.init.pb --c2-predict ./mobilenetv3-opt.predict.pb +``` + +## EfficientNet-B0 + +### Unoptimized +``` +Main run finished. Milliseconds per iter: 49.2862. Iters per second: 20.2897 +Time per operator type: + 29.7378 ms. 60.5145%. Conv + 12.1785 ms. 24.7824%. Sigmoid + 3.62811 ms. 7.38297%. SpatialBN + 2.98444 ms. 6.07314%. Mul + 0.326902 ms. 0.665225%. AveragePool + 0.197317 ms. 0.401528%. FC + 0.0852877 ms. 0.173555%. Add + 0.0032607 ms. 0.00663532%. Squeeze + 49.1416 ms in Total +FLOP per operator type: + 0.76907 GFLOP. 95.2696%. Conv + 0.0269508 GFLOP. 3.33857%. SpatialBN + 0.00846444 GFLOP. 1.04855%. Mul + 0.002561 GFLOP. 0.317248%. FC + 0.000210112 GFLOP. 0.0260279%. Add + 0.807256 GFLOP in Total +Feature Memory Read per operator type: + 58.5253 MB. 43.0891%. Mul + 43.2015 MB. 31.807%. Conv + 27.2869 MB. 20.0899%. SpatialBN + 5.12912 MB. 3.77631%. FC + 1.6809 MB. 1.23756%. Add + 135.824 MB in Total +Feature Memory Written per operator type: + 33.8578 MB. 38.1965%. Mul + 26.9881 MB. 30.4465%. Conv + 26.9508 MB. 30.4044%. SpatialBN + 0.840448 MB. 0.948147%. Add + 0.004 MB. 0.00451258%. FC + 88.6412 MB in Total +Parameter Memory per operator type: + 15.8248 MB. 74.9391%. Conv + 5.124 MB. 24.265%. FC + 0.168064 MB. 0.795877%. SpatialBN + 0 MB. 0%. Add + 0 MB. 0%. Mul + 21.1168 MB in Total +``` +### Optimized +``` +Main run finished. Milliseconds per iter: 46.0838. Iters per second: 21.6996 +Time per operator type: + 29.776 ms. 65.002%. Conv + 12.2803 ms. 26.8084%. Sigmoid + 3.15073 ms. 6.87815%. Mul + 0.328651 ms. 0.717456%. AveragePool + 0.186237 ms. 0.406563%. FC + 0.0832429 ms. 0.181722%. Add + 0.0026184 ms. 0.00571606%. Squeeze + 45.8078 ms in Total +FLOP per operator type: + 0.76907 GFLOP. 98.5601%. Conv + 0.00846444 GFLOP. 1.08476%. Mul + 0.002561 GFLOP. 0.328205%. FC + 0.000210112 GFLOP. 0.0269269%. Add + 0.780305 GFLOP in Total +Feature Memory Read per operator type: + 58.5253 MB. 53.8803%. Mul + 43.2855 MB. 39.8501%. Conv + 5.12912 MB. 4.72204%. FC + 1.6809 MB. 1.54749%. Add + 108.621 MB in Total +Feature Memory Written per operator type: + 33.8578 MB. 54.8834%. Mul + 26.9881 MB. 43.7477%. Conv + 0.840448 MB. 1.36237%. Add + 0.004 MB. 0.00648399%. FC + 61.6904 MB in Total +Parameter Memory per operator type: + 15.8248 MB. 75.5403%. Conv + 5.124 MB. 24.4597%. FC + 0 MB. 0%. Add + 0 MB. 0%. Mul + 20.9488 MB in Total +``` + +## EfficientNet-B1 +### Optimized +``` +Main run finished. Milliseconds per iter: 71.8102. Iters per second: 13.9256 +Time per operator type: + 45.7915 ms. 66.3206%. Conv + 17.8718 ms. 25.8841%. Sigmoid + 4.44132 ms. 6.43244%. Mul + 0.51001 ms. 0.738658%. AveragePool + 0.233283 ms. 0.337868%. Add + 0.194986 ms. 0.282402%. FC + 0.00268255 ms. 0.00388519%. Squeeze + 69.0456 ms in Total +FLOP per operator type: + 1.37105 GFLOP. 98.7673%. Conv + 0.0138759 GFLOP. 0.99959%. Mul + 0.002561 GFLOP. 0.184489%. FC + 0.000674432 GFLOP. 0.0485847%. Add + 1.38816 GFLOP in Total +Feature Memory Read per operator type: + 94.624 MB. 54.0789%. Mul + 69.8255 MB. 39.9062%. Conv + 5.39546 MB. 3.08357%. Add + 5.12912 MB. 2.93136%. FC + 174.974 MB in Total +Feature Memory Written per operator type: + 55.5035 MB. 54.555%. Mul + 43.5333 MB. 42.7894%. Conv + 2.69773 MB. 2.65163%. Add + 0.004 MB. 0.00393165%. FC + 101.739 MB in Total +Parameter Memory per operator type: + 25.7479 MB. 83.4024%. Conv + 5.124 MB. 16.5976%. FC + 0 MB. 0%. Add + 0 MB. 0%. Mul + 30.8719 MB in Total +``` + +## EfficientNet-B2 +### Optimized +``` +Main run finished. Milliseconds per iter: 92.28. Iters per second: 10.8366 +Time per operator type: + 61.4627 ms. 67.5845%. Conv + 22.7458 ms. 25.0113%. Sigmoid + 5.59931 ms. 6.15701%. Mul + 0.642567 ms. 0.706568%. AveragePool + 0.272795 ms. 0.299965%. Add + 0.216178 ms. 0.237709%. FC + 0.00268895 ms. 0.00295677%. Squeeze + 90.942 ms in Total +FLOP per operator type: + 1.98431 GFLOP. 98.9343%. Conv + 0.0177039 GFLOP. 0.882686%. Mul + 0.002817 GFLOP. 0.140451%. FC + 0.000853984 GFLOP. 0.0425782%. Add + 2.00568 GFLOP in Total +Feature Memory Read per operator type: + 120.609 MB. 54.9637%. Mul + 86.3512 MB. 39.3519%. Conv + 6.83187 MB. 3.11341%. Add + 5.64163 MB. 2.571%. FC + 219.433 MB in Total +Feature Memory Written per operator type: + 70.8155 MB. 54.6573%. Mul + 55.3273 MB. 42.7031%. Conv + 3.41594 MB. 2.63651%. Add + 0.004 MB. 0.00308731%. FC + 129.563 MB in Total +Parameter Memory per operator type: + 30.4721 MB. 84.3913%. Conv + 5.636 MB. 15.6087%. FC + 0 MB. 0%. Add + 0 MB. 0%. Mul + 36.1081 MB in Total +``` + +## MixNet-M +### Optimized +``` +Main run finished. Milliseconds per iter: 63.1122. Iters per second: 15.8448 +Time per operator type: + 48.1139 ms. 75.2052%. Conv + 7.1341 ms. 11.1511%. Sigmoid + 2.63706 ms. 4.12189%. SpatialBN + 1.73186 ms. 2.70701%. Mul + 1.38707 ms. 2.16809%. Split + 1.29322 ms. 2.02139%. Concat + 1.00093 ms. 1.56452%. Relu + 0.235309 ms. 0.367803%. Add + 0.221579 ms. 0.346343%. FC + 0.219315 ms. 0.342803%. AveragePool + 0.00250145 ms. 0.00390993%. Squeeze + 63.9768 ms in Total +FLOP per operator type: + 0.675273 GFLOP. 95.5827%. Conv + 0.0221072 GFLOP. 3.12921%. SpatialBN + 0.00538445 GFLOP. 0.762152%. Mul + 0.003073 GFLOP. 0.434973%. FC + 0.000642488 GFLOP. 0.0909421%. Add + 0 GFLOP. 0%. Concat + 0 GFLOP. 0%. Relu + 0.70648 GFLOP in Total +Feature Memory Read per operator type: + 46.8424 MB. 30.502%. Conv + 36.8626 MB. 24.0036%. Mul + 22.3152 MB. 14.5309%. SpatialBN + 22.1074 MB. 14.3955%. Concat + 14.1496 MB. 9.21372%. Relu + 6.15414 MB. 4.00735%. FC + 5.1399 MB. 3.34692%. Add + 153.571 MB in Total +Feature Memory Written per operator type: + 32.7672 MB. 28.4331%. Conv + 22.1072 MB. 19.1831%. Concat + 22.1072 MB. 19.1831%. SpatialBN + 21.5378 MB. 18.689%. Mul + 14.1496 MB. 12.2781%. Relu + 2.56995 MB. 2.23003%. Add + 0.004 MB. 0.00347092%. FC + 115.243 MB in Total +Parameter Memory per operator type: + 13.7059 MB. 68.674%. Conv + 6.148 MB. 30.8049%. FC + 0.104 MB. 0.521097%. SpatialBN + 0 MB. 0%. Add + 0 MB. 0%. Concat + 0 MB. 0%. Mul + 0 MB. 0%. Relu + 19.9579 MB in Total +``` + +## TF MobileNet-V3 Large 1.0 + +### Optimized +``` +Main run finished. Milliseconds per iter: 22.0495. Iters per second: 45.3525 +Time per operator type: + 17.437 ms. 80.0087%. Conv + 1.27662 ms. 5.8577%. Add + 1.12759 ms. 5.17387%. Div + 0.701155 ms. 3.21721%. Mul + 0.562654 ms. 2.58171%. Relu + 0.431144 ms. 1.97828%. Clip + 0.156902 ms. 0.719936%. FC + 0.0996858 ms. 0.457402%. AveragePool + 0.00112455 ms. 0.00515993%. Flatten + 21.7939 ms in Total +FLOP per operator type: + 0.43062 GFLOP. 98.1484%. Conv + 0.002561 GFLOP. 0.583713%. FC + 0.00210867 GFLOP. 0.480616%. Mul + 0.00193868 GFLOP. 0.441871%. Add + 0.00151532 GFLOP. 0.345377%. Div + 0 GFLOP. 0%. Relu + 0.438743 GFLOP in Total +Feature Memory Read per operator type: + 34.7967 MB. 43.9391%. Conv + 14.496 MB. 18.3046%. Mul + 9.44828 MB. 11.9307%. Add + 9.26157 MB. 11.6949%. Relu + 6.0614 MB. 7.65395%. Div + 5.12912 MB. 6.47673%. FC + 79.193 MB in Total +Feature Memory Written per operator type: + 17.6247 MB. 35.8656%. Conv + 9.26157 MB. 18.847%. Relu + 8.43469 MB. 17.1643%. Mul + 7.75472 MB. 15.7806%. Add + 6.06128 MB. 12.3345%. Div + 0.004 MB. 0.00813985%. FC + 49.1409 MB in Total +Parameter Memory per operator type: + 16.6851 MB. 76.5052%. Conv + 5.124 MB. 23.4948%. FC + 0 MB. 0%. Add + 0 MB. 0%. Div + 0 MB. 0%. Mul + 0 MB. 0%. Relu + 21.8091 MB in Total +``` + +## MobileNet-V3 (RW) + +### Unoptimized +``` +Main run finished. Milliseconds per iter: 24.8316. Iters per second: 40.2712 +Time per operator type: + 15.9266 ms. 69.2624%. Conv + 2.36551 ms. 10.2873%. SpatialBN + 1.39102 ms. 6.04936%. Add + 1.30327 ms. 5.66773%. Div + 0.737014 ms. 3.20517%. Mul + 0.639697 ms. 2.78195%. Relu + 0.375681 ms. 1.63378%. Clip + 0.153126 ms. 0.665921%. FC + 0.0993787 ms. 0.432184%. AveragePool + 0.0032632 ms. 0.0141912%. Squeeze + 22.9946 ms in Total +FLOP per operator type: + 0.430616 GFLOP. 94.4041%. Conv + 0.0175992 GFLOP. 3.85829%. SpatialBN + 0.002561 GFLOP. 0.561449%. FC + 0.00210961 GFLOP. 0.46249%. Mul + 0.00173891 GFLOP. 0.381223%. Add + 0.00151626 GFLOP. 0.33241%. Div + 0 GFLOP. 0%. Relu + 0.456141 GFLOP in Total +Feature Memory Read per operator type: + 34.7354 MB. 36.4363%. Conv + 17.7944 MB. 18.6658%. SpatialBN + 14.5035 MB. 15.2137%. Mul + 9.25778 MB. 9.71113%. Relu + 7.84641 MB. 8.23064%. Add + 6.06516 MB. 6.36216%. Div + 5.12912 MB. 5.38029%. FC + 95.3317 MB in Total +Feature Memory Written per operator type: + 17.6246 MB. 26.7264%. Conv + 17.5992 MB. 26.6878%. SpatialBN + 9.25778 MB. 14.0387%. Relu + 8.43843 MB. 12.7962%. Mul + 6.95565 MB. 10.5477%. Add + 6.06502 MB. 9.19713%. Div + 0.004 MB. 0.00606568%. FC + 65.9447 MB in Total +Parameter Memory per operator type: + 16.6778 MB. 76.1564%. Conv + 5.124 MB. 23.3979%. FC + 0.0976 MB. 0.445674%. SpatialBN + 0 MB. 0%. Add + 0 MB. 0%. Div + 0 MB. 0%. Mul + 0 MB. 0%. Relu + 21.8994 MB in Total + +``` +### Optimized + +``` +Main run finished. Milliseconds per iter: 22.0981. Iters per second: 45.2527 +Time per operator type: + 17.146 ms. 78.8965%. Conv + 1.38453 ms. 6.37084%. Add + 1.30991 ms. 6.02749%. Div + 0.685417 ms. 3.15391%. Mul + 0.532589 ms. 2.45068%. Relu + 0.418263 ms. 1.92461%. Clip + 0.15128 ms. 0.696106%. FC + 0.102065 ms. 0.469648%. AveragePool + 0.0022143 ms. 0.010189%. Squeeze + 21.7323 ms in Total +FLOP per operator type: + 0.430616 GFLOP. 98.1927%. Conv + 0.002561 GFLOP. 0.583981%. FC + 0.00210961 GFLOP. 0.481051%. Mul + 0.00173891 GFLOP. 0.396522%. Add + 0.00151626 GFLOP. 0.34575%. Div + 0 GFLOP. 0%. Relu + 0.438542 GFLOP in Total +Feature Memory Read per operator type: + 34.7842 MB. 44.833%. Conv + 14.5035 MB. 18.6934%. Mul + 9.25778 MB. 11.9323%. Relu + 7.84641 MB. 10.1132%. Add + 6.06516 MB. 7.81733%. Div + 5.12912 MB. 6.61087%. FC + 77.5861 MB in Total +Feature Memory Written per operator type: + 17.6246 MB. 36.4556%. Conv + 9.25778 MB. 19.1492%. Relu + 8.43843 MB. 17.4544%. Mul + 6.95565 MB. 14.3874%. Add + 6.06502 MB. 12.5452%. Div + 0.004 MB. 0.00827378%. FC + 48.3455 MB in Total +Parameter Memory per operator type: + 16.6778 MB. 76.4973%. Conv + 5.124 MB. 23.5027%. FC + 0 MB. 0%. Add + 0 MB. 0%. Div + 0 MB. 0%. Mul + 0 MB. 0%. Relu + 21.8018 MB in Total + +``` + +## MnasNet-A1 + +### Unoptimized +``` +Main run finished. Milliseconds per iter: 30.0892. Iters per second: 33.2345 +Time per operator type: + 24.4656 ms. 79.0905%. Conv + 4.14958 ms. 13.4144%. SpatialBN + 1.60598 ms. 5.19169%. Relu + 0.295219 ms. 0.95436%. Mul + 0.187609 ms. 0.606486%. FC + 0.120556 ms. 0.389724%. AveragePool + 0.09036 ms. 0.292109%. Add + 0.015727 ms. 0.050841%. Sigmoid + 0.00306205 ms. 0.00989875%. Squeeze + 30.9337 ms in Total +FLOP per operator type: + 0.620598 GFLOP. 95.6434%. Conv + 0.0248873 GFLOP. 3.8355%. SpatialBN + 0.002561 GFLOP. 0.394688%. FC + 0.000597408 GFLOP. 0.0920695%. Mul + 0.000222656 GFLOP. 0.0343146%. Add + 0 GFLOP. 0%. Relu + 0.648867 GFLOP in Total +Feature Memory Read per operator type: + 35.5457 MB. 38.4109%. Conv + 25.1552 MB. 27.1829%. SpatialBN + 22.5235 MB. 24.339%. Relu + 5.12912 MB. 5.54256%. FC + 2.40586 MB. 2.59978%. Mul + 1.78125 MB. 1.92483%. Add + 92.5406 MB in Total +Feature Memory Written per operator type: + 24.9042 MB. 32.9424%. Conv + 24.8873 MB. 32.92%. SpatialBN + 22.5235 MB. 29.7932%. Relu + 2.38963 MB. 3.16092%. Mul + 0.890624 MB. 1.17809%. Add + 0.004 MB. 0.00529106%. FC + 75.5993 MB in Total +Parameter Memory per operator type: + 10.2732 MB. 66.1459%. Conv + 5.124 MB. 32.9917%. FC + 0.133952 MB. 0.86247%. SpatialBN + 0 MB. 0%. Add + 0 MB. 0%. Mul + 0 MB. 0%. Relu + 15.5312 MB in Total +``` + +### Optimized +``` +Main run finished. Milliseconds per iter: 24.2367. Iters per second: 41.2597 +Time per operator type: + 22.0547 ms. 91.1375%. Conv + 1.49096 ms. 6.16116%. Relu + 0.253417 ms. 1.0472%. Mul + 0.18506 ms. 0.76473%. FC + 0.112942 ms. 0.466717%. AveragePool + 0.086769 ms. 0.358559%. Add + 0.0127889 ms. 0.0528479%. Sigmoid + 0.0027346 ms. 0.0113003%. Squeeze + 24.1994 ms in Total +FLOP per operator type: + 0.620598 GFLOP. 99.4581%. Conv + 0.002561 GFLOP. 0.41043%. FC + 0.000597408 GFLOP. 0.0957417%. Mul + 0.000222656 GFLOP. 0.0356832%. Add + 0 GFLOP. 0%. Relu + 0.623979 GFLOP in Total +Feature Memory Read per operator type: + 35.6127 MB. 52.7968%. Conv + 22.5235 MB. 33.3917%. Relu + 5.12912 MB. 7.60406%. FC + 2.40586 MB. 3.56675%. Mul + 1.78125 MB. 2.64075%. Add + 67.4524 MB in Total +Feature Memory Written per operator type: + 24.9042 MB. 49.1092%. Conv + 22.5235 MB. 44.4145%. Relu + 2.38963 MB. 4.71216%. Mul + 0.890624 MB. 1.75624%. Add + 0.004 MB. 0.00788768%. FC + 50.712 MB in Total +Parameter Memory per operator type: + 10.2732 MB. 66.7213%. Conv + 5.124 MB. 33.2787%. FC + 0 MB. 0%. Add + 0 MB. 0%. Mul + 0 MB. 0%. Relu + 15.3972 MB in Total +``` +## MnasNet-B1 + +### Unoptimized +``` +Main run finished. Milliseconds per iter: 28.3109. Iters per second: 35.322 +Time per operator type: + 29.1121 ms. 83.3081%. Conv + 4.14959 ms. 11.8746%. SpatialBN + 1.35823 ms. 3.88675%. Relu + 0.186188 ms. 0.532802%. FC + 0.116244 ms. 0.332647%. Add + 0.018641 ms. 0.0533437%. AveragePool + 0.0040904 ms. 0.0117052%. Squeeze + 34.9451 ms in Total +FLOP per operator type: + 0.626272 GFLOP. 96.2088%. Conv + 0.0218266 GFLOP. 3.35303%. SpatialBN + 0.002561 GFLOP. 0.393424%. FC + 0.000291648 GFLOP. 0.0448034%. Add + 0 GFLOP. 0%. Relu + 0.650951 GFLOP in Total +Feature Memory Read per operator type: + 34.4354 MB. 41.3788%. Conv + 22.1299 MB. 26.5921%. SpatialBN + 19.1923 MB. 23.0622%. Relu + 5.12912 MB. 6.16333%. FC + 2.33318 MB. 2.80364%. Add + 83.2199 MB in Total +Feature Memory Written per operator type: + 21.8266 MB. 34.0955%. Conv + 21.8266 MB. 34.0955%. SpatialBN + 19.1923 MB. 29.9805%. Relu + 1.16659 MB. 1.82234%. Add + 0.004 MB. 0.00624844%. FC + 64.016 MB in Total +Parameter Memory per operator type: + 12.2576 MB. 69.9104%. Conv + 5.124 MB. 29.2245%. FC + 0.15168 MB. 0.865099%. SpatialBN + 0 MB. 0%. Add + 0 MB. 0%. Relu + 17.5332 MB in Total +``` + +### Optimized +``` +Main run finished. Milliseconds per iter: 26.6364. Iters per second: 37.5426 +Time per operator type: + 24.9888 ms. 94.0962%. Conv + 1.26147 ms. 4.75011%. Relu + 0.176234 ms. 0.663619%. FC + 0.113309 ms. 0.426672%. Add + 0.0138708 ms. 0.0522311%. AveragePool + 0.00295685 ms. 0.0111341%. Squeeze + 26.5566 ms in Total +FLOP per operator type: + 0.626272 GFLOP. 99.5466%. Conv + 0.002561 GFLOP. 0.407074%. FC + 0.000291648 GFLOP. 0.0463578%. Add + 0 GFLOP. 0%. Relu + 0.629124 GFLOP in Total +Feature Memory Read per operator type: + 34.5112 MB. 56.4224%. Conv + 19.1923 MB. 31.3775%. Relu + 5.12912 MB. 8.3856%. FC + 2.33318 MB. 3.81452%. Add + 61.1658 MB in Total +Feature Memory Written per operator type: + 21.8266 MB. 51.7346%. Conv + 19.1923 MB. 45.4908%. Relu + 1.16659 MB. 2.76513%. Add + 0.004 MB. 0.00948104%. FC + 42.1895 MB in Total +Parameter Memory per operator type: + 12.2576 MB. 70.5205%. Conv + 5.124 MB. 29.4795%. FC + 0 MB. 0%. Add + 0 MB. 0%. Relu + 17.3816 MB in Total +``` diff --git a/modules/control/proc/normalbae/nets/submodules/efficientnet_repo/LICENSE b/modules/control/proc/normalbae/nets/submodules/efficientnet_repo/LICENSE new file mode 100644 index 000000000..80e7d1550 --- /dev/null +++ b/modules/control/proc/normalbae/nets/submodules/efficientnet_repo/LICENSE @@ -0,0 +1,201 @@ + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "{}" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright 2020 Ross Wightman + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. diff --git a/modules/control/proc/normalbae/nets/submodules/efficientnet_repo/README.md b/modules/control/proc/normalbae/nets/submodules/efficientnet_repo/README.md new file mode 100644 index 000000000..463368280 --- /dev/null +++ b/modules/control/proc/normalbae/nets/submodules/efficientnet_repo/README.md @@ -0,0 +1,323 @@ +# (Generic) EfficientNets for PyTorch + +A 'generic' implementation of EfficientNet, MixNet, MobileNetV3, etc. that covers most of the compute/parameter efficient architectures derived from the MobileNet V1/V2 block sequence, including those found via automated neural architecture search. + +All models are implemented by GenEfficientNet or MobileNetV3 classes, with string based architecture definitions to configure the block layouts (idea from [here](https://github.com/tensorflow/tpu/blob/master/models/official/mnasnet/mnasnet_models.py)) + +## What's New + +### Aug 19, 2020 +* Add updated PyTorch trained EfficientNet-B3 weights trained by myself with `timm` (82.1 top-1) +* Add PyTorch trained EfficientNet-Lite0 contributed by [@hal-314](https://github.com/hal-314) (75.5 top-1) +* Update ONNX and Caffe2 export / utility scripts to work with latest PyTorch / ONNX +* ONNX runtime based validation script added +* activations (mostly) brought in sync with `timm` equivalents + + +### April 5, 2020 +* Add some newly trained MobileNet-V2 models trained with latest h-params, rand augment. They compare quite favourably to EfficientNet-Lite + * 3.5M param MobileNet-V2 100 @ 73% + * 4.5M param MobileNet-V2 110d @ 75% + * 6.1M param MobileNet-V2 140 @ 76.5% + * 5.8M param MobileNet-V2 120d @ 77.3% + +### March 23, 2020 + * Add EfficientNet-Lite models w/ weights ported from [Tensorflow TPU](https://github.com/tensorflow/tpu/tree/master/models/official/efficientnet/lite) + * Add PyTorch trained MobileNet-V3 Large weights with 75.77% top-1 + * IMPORTANT CHANGE (if training from scratch) - weight init changed to better match Tensorflow impl, set `fix_group_fanout=False` in `initialize_weight_goog` for old behavior + +### Feb 12, 2020 + * Add EfficientNet-L2 and B0-B7 NoisyStudent weights ported from [Tensorflow TPU](https://github.com/tensorflow/tpu/tree/master/models/official/efficientnet) + * Port new EfficientNet-B8 (RandAugment) weights from TF TPU, these are different than the B8 AdvProp, different input normalization. + * Add RandAugment PyTorch trained EfficientNet-ES (EdgeTPU-Small) weights with 78.1 top-1. Trained by [Andrew Lavin](https://github.com/andravin) + +### Jan 22, 2020 + * Update weights for EfficientNet B0, B2, B3 and MixNet-XL with latest RandAugment trained weights. Trained with (https://github.com/rwightman/pytorch-image-models) + * Fix torchscript compatibility for PyTorch 1.4, add torchscript support for MixedConv2d using ModuleDict + * Test models, torchscript, onnx export with PyTorch 1.4 -- no issues + +### Nov 22, 2019 + * New top-1 high! Ported official TF EfficientNet AdvProp (https://arxiv.org/abs/1911.09665) weights and B8 model spec. Created a new set of `ap` models since they use a different + preprocessing (Inception mean/std) from the original EfficientNet base/AA/RA weights. + +### Nov 15, 2019 + * Ported official TF MobileNet-V3 float32 large/small/minimalistic weights + * Modifications to MobileNet-V3 model and components to support some additional config needed for differences between TF MobileNet-V3 and mine + +### Oct 30, 2019 + * Many of the models will now work with torch.jit.script, MixNet being the biggest exception + * Improved interface for enabling torchscript or ONNX export compatible modes (via config) + * Add JIT optimized mem-efficient Swish/Mish autograd.fn in addition to memory-efficient autgrad.fn + * Activation factory to select best version of activation by name or override one globally + * Add pretrained checkpoint load helper that handles input conv and classifier changes + +### Oct 27, 2019 + * Add CondConv EfficientNet variants ported from https://github.com/tensorflow/tpu/tree/master/models/official/efficientnet/condconv + * Add RandAug weights for TF EfficientNet B5 and B7 from https://github.com/tensorflow/tpu/tree/master/models/official/efficientnet + * Bring over MixNet-XL model and depth scaling algo from my pytorch-image-models code base + * Switch activations and global pooling to modules + * Add memory-efficient Swish/Mish impl + * Add as_sequential() method to all models and allow as an argument in entrypoint fns + * Move MobileNetV3 into own file since it has a different head + * Remove ChamNet, MobileNet V2/V1 since they will likely never be used here + +## Models + +Implemented models include: + * EfficientNet NoisyStudent (B0-B7, L2) (https://arxiv.org/abs/1911.04252) + * EfficientNet AdvProp (B0-B8) (https://arxiv.org/abs/1911.09665) + * EfficientNet (B0-B8) (https://arxiv.org/abs/1905.11946) + * EfficientNet-EdgeTPU (S, M, L) (https://ai.googleblog.com/2019/08/efficientnet-edgetpu-creating.html) + * EfficientNet-CondConv (https://arxiv.org/abs/1904.04971) + * EfficientNet-Lite (https://github.com/tensorflow/tpu/tree/master/models/official/efficientnet/lite) + * MixNet (https://arxiv.org/abs/1907.09595) + * MNASNet B1, A1 (Squeeze-Excite), and Small (https://arxiv.org/abs/1807.11626) + * MobileNet-V3 (https://arxiv.org/abs/1905.02244) + * FBNet-C (https://arxiv.org/abs/1812.03443) + * Single-Path NAS (https://arxiv.org/abs/1904.02877) + +I originally implemented and trained some these models with code [here](https://github.com/rwightman/pytorch-image-models), this repository contains just the GenEfficientNet models, validation, and associated ONNX/Caffe2 export code. + +## Pretrained + +I've managed to train several of the models to accuracies close to or above the originating papers and official impl. My training code is here: https://github.com/rwightman/pytorch-image-models + + +|Model | Prec@1 (Err) | Prec@5 (Err) | Param#(M) | MAdds(M) | Image Scaling | Resolution | Crop | +|---|---|---|---|---|---|---|---| +| efficientnet_b3 | 82.240 (17.760) | 96.116 (3.884) | 12.23 | TBD | bicubic | 320 | 1.0 | +| efficientnet_b3 | 82.076 (17.924) | 96.020 (3.980) | 12.23 | TBD | bicubic | 300 | 0.904 | +| mixnet_xl | 81.074 (18.926) | 95.282 (4.718) | 11.90 | TBD | bicubic | 256 | 1.0 | +| efficientnet_b2 | 80.612 (19.388) | 95.318 (4.682) | 9.1 | TBD | bicubic | 288 | 1.0 | +| mixnet_xl | 80.476 (19.524) | 94.936 (5.064) | 11.90 | TBD | bicubic | 224 | 0.875 | +| efficientnet_b2 | 80.288 (19.712) | 95.166 (4.834) | 9.1 | 1003 | bicubic | 260 | 0.890 | +| mixnet_l | 78.976 (21.024 | 94.184 (5.816) | 7.33 | TBD | bicubic | 224 | 0.875 | +| efficientnet_b1 | 78.692 (21.308) | 94.086 (5.914) | 7.8 | 694 | bicubic | 240 | 0.882 | +| efficientnet_es | 78.066 (21.934) | 93.926 (6.074) | 5.44 | TBD | bicubic | 224 | 0.875 | +| efficientnet_b0 | 77.698 (22.302) | 93.532 (6.468) | 5.3 | 390 | bicubic | 224 | 0.875 | +| mobilenetv2_120d | 77.294 (22.706 | 93.502 (6.498) | 5.8 | TBD | bicubic | 224 | 0.875 | +| mixnet_m | 77.256 (22.744) | 93.418 (6.582) | 5.01 | 353 | bicubic | 224 | 0.875 | +| mobilenetv2_140 | 76.524 (23.476) | 92.990 (7.010) | 6.1 | TBD | bicubic | 224 | 0.875 | +| mixnet_s | 75.988 (24.012) | 92.794 (7.206) | 4.13 | TBD | bicubic | 224 | 0.875 | +| mobilenetv3_large_100 | 75.766 (24.234) | 92.542 (7.458) | 5.5 | TBD | bicubic | 224 | 0.875 | +| mobilenetv3_rw | 75.634 (24.366) | 92.708 (7.292) | 5.5 | 219 | bicubic | 224 | 0.875 | +| efficientnet_lite0 | 75.472 (24.528) | 92.520 (7.480) | 4.65 | TBD | bicubic | 224 | 0.875 | +| mnasnet_a1 | 75.448 (24.552) | 92.604 (7.396) | 3.9 | 312 | bicubic | 224 | 0.875 | +| fbnetc_100 | 75.124 (24.876) | 92.386 (7.614) | 5.6 | 385 | bilinear | 224 | 0.875 | +| mobilenetv2_110d | 75.052 (24.948) | 92.180 (7.820) | 4.5 | TBD | bicubic | 224 | 0.875 | +| mnasnet_b1 | 74.658 (25.342) | 92.114 (7.886) | 4.4 | 315 | bicubic | 224 | 0.875 | +| spnasnet_100 | 74.084 (25.916) | 91.818 (8.182) | 4.4 | TBD | bilinear | 224 | 0.875 | +| mobilenetv2_100 | 72.978 (27.022) | 91.016 (8.984) | 3.5 | TBD | bicubic | 224 | 0.875 | + + +More pretrained models to come... + + +## Ported Weights + +The weights ported from Tensorflow checkpoints for the EfficientNet models do pretty much match accuracy in Tensorflow once a SAME convolution padding equivalent is added, and the same crop factors, image scaling, etc (see table) are used via cmd line args. + +**IMPORTANT:** +* Tensorflow ported weights for EfficientNet AdvProp (AP), EfficientNet EdgeTPU, EfficientNet-CondConv, EfficientNet-Lite, and MobileNet-V3 models use Inception style (0.5, 0.5, 0.5) for mean and std. +* Enabling the Tensorflow preprocessing pipeline with `--tf-preprocessing` at validation time will improve scores by 0.1-0.5%, very close to original TF impl. + +To run validation for tf_efficientnet_b5: +`python validate.py /path/to/imagenet/validation/ --model tf_efficientnet_b5 -b 64 --img-size 456 --crop-pct 0.934 --interpolation bicubic` + +To run validation w/ TF preprocessing for tf_efficientnet_b5: +`python validate.py /path/to/imagenet/validation/ --model tf_efficientnet_b5 -b 64 --img-size 456 --tf-preprocessing` + +To run validation for a model with Inception preprocessing, ie EfficientNet-B8 AdvProp: +`python validate.py /path/to/imagenet/validation/ --model tf_efficientnet_b8_ap -b 48 --num-gpu 2 --img-size 672 --crop-pct 0.954 --mean 0.5 --std 0.5` + +|Model | Prec@1 (Err) | Prec@5 (Err) | Param # | Image Scaling | Image Size | Crop | +|---|---|---|---|---|---|---| +| tf_efficientnet_l2_ns *tfp | 88.352 (11.648) | 98.652 (1.348) | 480 | bicubic | 800 | N/A | +| tf_efficientnet_l2_ns | TBD | TBD | 480 | bicubic | 800 | 0.961 | +| tf_efficientnet_l2_ns_475 | 88.234 (11.766) | 98.546 (1.454) | 480 | bicubic | 475 | 0.936 | +| tf_efficientnet_l2_ns_475 *tfp | 88.172 (11.828) | 98.566 (1.434) | 480 | bicubic | 475 | N/A | +| tf_efficientnet_b7_ns *tfp | 86.844 (13.156) | 98.084 (1.916) | 66.35 | bicubic | 600 | N/A | +| tf_efficientnet_b7_ns | 86.840 (13.160) | 98.094 (1.906) | 66.35 | bicubic | 600 | N/A | +| tf_efficientnet_b6_ns | 86.452 (13.548) | 97.882 (2.118) | 43.04 | bicubic | 528 | N/A | +| tf_efficientnet_b6_ns *tfp | 86.444 (13.556) | 97.880 (2.120) | 43.04 | bicubic | 528 | N/A | +| tf_efficientnet_b5_ns *tfp | 86.064 (13.936) | 97.746 (2.254) | 30.39 | bicubic | 456 | N/A | +| tf_efficientnet_b5_ns | 86.088 (13.912) | 97.752 (2.248) | 30.39 | bicubic | 456 | N/A | +| tf_efficientnet_b8_ap *tfp | 85.436 (14.564) | 97.272 (2.728) | 87.4 | bicubic | 672 | N/A | +| tf_efficientnet_b8 *tfp | 85.384 (14.616) | 97.394 (2.606) | 87.4 | bicubic | 672 | N/A | +| tf_efficientnet_b8 | 85.370 (14.630) | 97.390 (2.610) | 87.4 | bicubic | 672 | 0.954 | +| tf_efficientnet_b8_ap | 85.368 (14.632) | 97.294 (2.706) | 87.4 | bicubic | 672 | 0.954 | +| tf_efficientnet_b4_ns *tfp | 85.298 (14.702) | 97.504 (2.496) | 19.34 | bicubic | 380 | N/A | +| tf_efficientnet_b4_ns | 85.162 (14.838) | 97.470 (2.530) | 19.34 | bicubic | 380 | 0.922 | +| tf_efficientnet_b7_ap *tfp | 85.154 (14.846) | 97.244 (2.756) | 66.35 | bicubic | 600 | N/A | +| tf_efficientnet_b7_ap | 85.118 (14.882) | 97.252 (2.748) | 66.35 | bicubic | 600 | 0.949 | +| tf_efficientnet_b7 *tfp | 84.940 (15.060) | 97.214 (2.786) | 66.35 | bicubic | 600 | N/A | +| tf_efficientnet_b7 | 84.932 (15.068) | 97.208 (2.792) | 66.35 | bicubic | 600 | 0.949 | +| tf_efficientnet_b6_ap | 84.786 (15.214) | 97.138 (2.862) | 43.04 | bicubic | 528 | 0.942 | +| tf_efficientnet_b6_ap *tfp | 84.760 (15.240) | 97.124 (2.876) | 43.04 | bicubic | 528 | N/A | +| tf_efficientnet_b5_ap *tfp | 84.276 (15.724) | 96.932 (3.068) | 30.39 | bicubic | 456 | N/A | +| tf_efficientnet_b5_ap | 84.254 (15.746) | 96.976 (3.024) | 30.39 | bicubic | 456 | 0.934 | +| tf_efficientnet_b6 *tfp | 84.140 (15.860) | 96.852 (3.148) | 43.04 | bicubic | 528 | N/A | +| tf_efficientnet_b6 | 84.110 (15.890) | 96.886 (3.114) | 43.04 | bicubic | 528 | 0.942 | +| tf_efficientnet_b3_ns *tfp | 84.054 (15.946) | 96.918 (3.082) | 12.23 | bicubic | 300 | N/A | +| tf_efficientnet_b3_ns | 84.048 (15.952) | 96.910 (3.090) | 12.23 | bicubic | 300 | .904 | +| tf_efficientnet_b5 *tfp | 83.822 (16.178) | 96.756 (3.244) | 30.39 | bicubic | 456 | N/A | +| tf_efficientnet_b5 | 83.812 (16.188) | 96.748 (3.252) | 30.39 | bicubic | 456 | 0.934 | +| tf_efficientnet_b4_ap *tfp | 83.278 (16.722) | 96.376 (3.624) | 19.34 | bicubic | 380 | N/A | +| tf_efficientnet_b4_ap | 83.248 (16.752) | 96.388 (3.612) | 19.34 | bicubic | 380 | 0.922 | +| tf_efficientnet_b4 | 83.022 (16.978) | 96.300 (3.700) | 19.34 | bicubic | 380 | 0.922 | +| tf_efficientnet_b4 *tfp | 82.948 (17.052) | 96.308 (3.692) | 19.34 | bicubic | 380 | N/A | +| tf_efficientnet_b2_ns *tfp | 82.436 (17.564) | 96.268 (3.732) | 9.11 | bicubic | 260 | N/A | +| tf_efficientnet_b2_ns | 82.380 (17.620) | 96.248 (3.752) | 9.11 | bicubic | 260 | 0.89 | +| tf_efficientnet_b3_ap *tfp | 81.882 (18.118) | 95.662 (4.338) | 12.23 | bicubic | 300 | N/A | +| tf_efficientnet_b3_ap | 81.828 (18.172) | 95.624 (4.376) | 12.23 | bicubic | 300 | 0.904 | +| tf_efficientnet_b3 | 81.636 (18.364) | 95.718 (4.282) | 12.23 | bicubic | 300 | 0.904 | +| tf_efficientnet_b3 *tfp | 81.576 (18.424) | 95.662 (4.338) | 12.23 | bicubic | 300 | N/A | +| tf_efficientnet_lite4 | 81.528 (18.472) | 95.668 (4.332) | 13.00 | bilinear | 380 | 0.92 | +| tf_efficientnet_b1_ns *tfp | 81.514 (18.486) | 95.776 (4.224) | 7.79 | bicubic | 240 | N/A | +| tf_efficientnet_lite4 *tfp | 81.502 (18.498) | 95.676 (4.324) | 13.00 | bilinear | 380 | N/A | +| tf_efficientnet_b1_ns | 81.388 (18.612) | 95.738 (4.262) | 7.79 | bicubic | 240 | 0.88 | +| tf_efficientnet_el | 80.534 (19.466) | 95.190 (4.810) | 10.59 | bicubic | 300 | 0.904 | +| tf_efficientnet_el *tfp | 80.476 (19.524) | 95.200 (4.800) | 10.59 | bicubic | 300 | N/A | +| tf_efficientnet_b2_ap *tfp | 80.420 (19.580) | 95.040 (4.960) | 9.11 | bicubic | 260 | N/A | +| tf_efficientnet_b2_ap | 80.306 (19.694) | 95.028 (4.972) | 9.11 | bicubic | 260 | 0.890 | +| tf_efficientnet_b2 *tfp | 80.188 (19.812) | 94.974 (5.026) | 9.11 | bicubic | 260 | N/A | +| tf_efficientnet_b2 | 80.086 (19.914) | 94.908 (5.092) | 9.11 | bicubic | 260 | 0.890 | +| tf_efficientnet_lite3 | 79.812 (20.188) | 94.914 (5.086) | 8.20 | bilinear | 300 | 0.904 | +| tf_efficientnet_lite3 *tfp | 79.734 (20.266) | 94.838 (5.162) | 8.20 | bilinear | 300 | N/A | +| tf_efficientnet_b1_ap *tfp | 79.532 (20.468) | 94.378 (5.622) | 7.79 | bicubic | 240 | N/A | +| tf_efficientnet_cc_b1_8e *tfp | 79.464 (20.536)| 94.492 (5.508) | 39.7 | bicubic | 240 | 0.88 | +| tf_efficientnet_cc_b1_8e | 79.298 (20.702) | 94.364 (5.636) | 39.7 | bicubic | 240 | 0.88 | +| tf_efficientnet_b1_ap | 79.278 (20.722) | 94.308 (5.692) | 7.79 | bicubic | 240 | 0.88 | +| tf_efficientnet_b1 *tfp | 79.172 (20.828) | 94.450 (5.550) | 7.79 | bicubic | 240 | N/A | +| tf_efficientnet_em *tfp | 78.958 (21.042) | 94.458 (5.542) | 6.90 | bicubic | 240 | N/A | +| tf_efficientnet_b0_ns *tfp | 78.806 (21.194) | 94.496 (5.504) | 5.29 | bicubic | 224 | N/A | +| tf_mixnet_l *tfp | 78.846 (21.154) | 94.212 (5.788) | 7.33 | bilinear | 224 | N/A | +| tf_efficientnet_b1 | 78.826 (21.174) | 94.198 (5.802) | 7.79 | bicubic | 240 | 0.88 | +| tf_mixnet_l | 78.770 (21.230) | 94.004 (5.996) | 7.33 | bicubic | 224 | 0.875 | +| tf_efficientnet_em | 78.742 (21.258) | 94.332 (5.668) | 6.90 | bicubic | 240 | 0.875 | +| tf_efficientnet_b0_ns | 78.658 (21.342) | 94.376 (5.624) | 5.29 | bicubic | 224 | 0.875 | +| tf_efficientnet_cc_b0_8e *tfp | 78.314 (21.686) | 93.790 (6.210) | 24.0 | bicubic | 224 | 0.875 | +| tf_efficientnet_cc_b0_8e | 77.908 (22.092) | 93.656 (6.344) | 24.0 | bicubic | 224 | 0.875 | +| tf_efficientnet_cc_b0_4e *tfp | 77.746 (22.254) | 93.552 (6.448) | 13.3 | bicubic | 224 | 0.875 | +| tf_efficientnet_cc_b0_4e | 77.304 (22.696) | 93.332 (6.668) | 13.3 | bicubic | 224 | 0.875 | +| tf_efficientnet_es *tfp | 77.616 (22.384) | 93.750 (6.250) | 5.44 | bicubic | 224 | N/A | +| tf_efficientnet_lite2 *tfp | 77.544 (22.456) | 93.800 (6.200) | 6.09 | bilinear | 260 | N/A | +| tf_efficientnet_lite2 | 77.460 (22.540) | 93.746 (6.254) | 6.09 | bicubic | 260 | 0.89 | +| tf_efficientnet_b0_ap *tfp | 77.514 (22.486) | 93.576 (6.424) | 5.29 | bicubic | 224 | N/A | +| tf_efficientnet_es | 77.264 (22.736) | 93.600 (6.400) | 5.44 | bicubic | 224 | N/A | +| tf_efficientnet_b0 *tfp | 77.258 (22.742) | 93.478 (6.522) | 5.29 | bicubic | 224 | N/A | +| tf_efficientnet_b0_ap | 77.084 (22.916) | 93.254 (6.746) | 5.29 | bicubic | 224 | 0.875 | +| tf_mixnet_m *tfp | 77.072 (22.928) | 93.368 (6.632) | 5.01 | bilinear | 224 | N/A | +| tf_mixnet_m | 76.950 (23.050) | 93.156 (6.844) | 5.01 | bicubic | 224 | 0.875 | +| tf_efficientnet_b0 | 76.848 (23.152) | 93.228 (6.772) | 5.29 | bicubic | 224 | 0.875 | +| tf_efficientnet_lite1 *tfp | 76.764 (23.236) | 93.326 (6.674) | 5.42 | bilinear | 240 | N/A | +| tf_efficientnet_lite1 | 76.638 (23.362) | 93.232 (6.768) | 5.42 | bicubic | 240 | 0.882 | +| tf_mixnet_s *tfp | 75.800 (24.200) | 92.788 (7.212) | 4.13 | bilinear | 224 | N/A | +| tf_mobilenetv3_large_100 *tfp | 75.768 (24.232) | 92.710 (7.290) | 5.48 | bilinear | 224 | N/A | +| tf_mixnet_s | 75.648 (24.352) | 92.636 (7.364) | 4.13 | bicubic | 224 | 0.875 | +| tf_mobilenetv3_large_100 | 75.516 (24.484) | 92.600 (7.400) | 5.48 | bilinear | 224 | 0.875 | +| tf_efficientnet_lite0 *tfp | 75.074 (24.926) | 92.314 (7.686) | 4.65 | bilinear | 224 | N/A | +| tf_efficientnet_lite0 | 74.842 (25.158) | 92.170 (7.830) | 4.65 | bicubic | 224 | 0.875 | +| tf_mobilenetv3_large_075 *tfp | 73.730 (26.270) | 91.616 (8.384) | 3.99 | bilinear | 224 |N/A | +| tf_mobilenetv3_large_075 | 73.442 (26.558) | 91.352 (8.648) | 3.99 | bilinear | 224 | 0.875 | +| tf_mobilenetv3_large_minimal_100 *tfp | 72.678 (27.322) | 90.860 (9.140) | 3.92 | bilinear | 224 | N/A | +| tf_mobilenetv3_large_minimal_100 | 72.244 (27.756) | 90.636 (9.364) | 3.92 | bilinear | 224 | 0.875 | +| tf_mobilenetv3_small_100 *tfp | 67.918 (32.082) | 87.958 (12.042 | 2.54 | bilinear | 224 | N/A | +| tf_mobilenetv3_small_100 | 67.918 (32.082) | 87.662 (12.338) | 2.54 | bilinear | 224 | 0.875 | +| tf_mobilenetv3_small_075 *tfp | 66.142 (33.858) | 86.498 (13.502) | 2.04 | bilinear | 224 | N/A | +| tf_mobilenetv3_small_075 | 65.718 (34.282) | 86.136 (13.864) | 2.04 | bilinear | 224 | 0.875 | +| tf_mobilenetv3_small_minimal_100 *tfp | 63.378 (36.622) | 84.802 (15.198) | 2.04 | bilinear | 224 | N/A | +| tf_mobilenetv3_small_minimal_100 | 62.898 (37.102) | 84.230 (15.770) | 2.04 | bilinear | 224 | 0.875 | + + +*tfp models validated with `tf-preprocessing` pipeline + +Google tf and tflite weights ported from official Tensorflow repositories +* https://github.com/tensorflow/tpu/tree/master/models/official/mnasnet +* https://github.com/tensorflow/tpu/tree/master/models/official/efficientnet +* https://github.com/tensorflow/models/tree/master/research/slim/nets/mobilenet + +## Usage + +### Environment + +All development and testing has been done in Conda Python 3 environments on Linux x86-64 systems, specifically Python 3.6.x, 3.7.x, 3.8.x. + +Users have reported that a Python 3 Anaconda install in Windows works. I have not verified this myself. + +PyTorch versions 1.4, 1.5, 1.6 have been tested with this code. + +I've tried to keep the dependencies minimal, the setup is as per the PyTorch default install instructions for Conda: +``` +conda create -n torch-env +conda activate torch-env +conda install -c pytorch pytorch torchvision cudatoolkit=10.2 +``` + +### PyTorch Hub + +Models can be accessed via the PyTorch Hub API + +``` +>>> torch.hub.list('rwightman/gen-efficientnet-pytorch') +['efficientnet_b0', ...] +>>> model = torch.hub.load('rwightman/gen-efficientnet-pytorch', 'efficientnet_b0', pretrained=True) +>>> model.eval() +>>> output = model(torch.randn(1,3,224,224)) +``` + +### Pip +This package can be installed via pip. + +Install (after conda env/install): +``` +pip install geffnet +``` + +Eval use: +``` +>>> import geffnet +>>> m = geffnet.create_model('mobilenetv3_large_100', pretrained=True) +>>> m.eval() +``` + +Train use: +``` +>>> import geffnet +>>> # models can also be created by using the entrypoint directly +>>> m = geffnet.efficientnet_b2(pretrained=True, drop_rate=0.25, drop_connect_rate=0.2) +>>> m.train() +``` + +Create in a nn.Sequential container, for fast.ai, etc: +``` +>>> import geffnet +>>> m = geffnet.mixnet_l(pretrained=True, drop_rate=0.25, drop_connect_rate=0.2, as_sequential=True) +``` + +### Exporting + +Scripts are included to +* export models to ONNX (`onnx_export.py`) +* optimized ONNX graph (`onnx_optimize.py` or `onnx_validate.py` w/ `--onnx-output-opt` arg) +* validate with ONNX runtime (`onnx_validate.py`) +* convert ONNX model to Caffe2 (`onnx_to_caffe.py`) +* validate in Caffe2 (`caffe2_validate.py`) +* benchmark in Caffe2 w/ FLOPs, parameters output (`caffe2_benchmark.py`) + +As an example, to export the MobileNet-V3 pretrained model and then run an Imagenet validation: +``` +python onnx_export.py --model mobilenetv3_large_100 ./mobilenetv3_100.onnx +python onnx_validate.py /imagenet/validation/ --onnx-input ./mobilenetv3_100.onnx +``` + +These scripts were tested to be working as of PyTorch 1.6 and ONNX 1.7 w/ ONNX runtime 1.4. Caffe2 compatible +export now requires additional args mentioned in the export script (not needed in earlier versions). + +#### Export Notes +1. The TF ported weights with the 'SAME' conv padding activated cannot be exported to ONNX unless `_EXPORTABLE` flag in `config.py` is set to True. Use `config.set_exportable(True)` as in the `onnx_export.py` script. +2. TF ported models with 'SAME' padding will have the padding fixed at export time to the resolution used for export. Even though dynamic padding is supported in opset >= 11, I can't get it working. +3. ONNX optimize facility doesn't work reliably in PyTorch 1.6 / ONNX 1.7. Fortunately, the onnxruntime based inference is working very well now and includes on the fly optimization. +3. ONNX / Caffe2 export/import frequently breaks with different PyTorch and ONNX version releases. Please check their respective issue trackers before filing issues here. + + diff --git a/modules/control/proc/normalbae/nets/submodules/efficientnet_repo/__init__.py b/modules/control/proc/normalbae/nets/submodules/efficientnet_repo/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/modules/control/proc/normalbae/nets/submodules/efficientnet_repo/geffnet/__init__.py b/modules/control/proc/normalbae/nets/submodules/efficientnet_repo/geffnet/__init__.py new file mode 100644 index 000000000..ca60ac711 --- /dev/null +++ b/modules/control/proc/normalbae/nets/submodules/efficientnet_repo/geffnet/__init__.py @@ -0,0 +1,5 @@ +from .gen_efficientnet import * +from .mobilenetv3 import * +from .model_factory import create_model +from .config import is_exportable, is_scriptable, set_exportable, set_scriptable +from .activations import * diff --git a/modules/control/proc/normalbae/nets/submodules/efficientnet_repo/geffnet/activations/__init__.py b/modules/control/proc/normalbae/nets/submodules/efficientnet_repo/geffnet/activations/__init__.py new file mode 100644 index 000000000..813421a74 --- /dev/null +++ b/modules/control/proc/normalbae/nets/submodules/efficientnet_repo/geffnet/activations/__init__.py @@ -0,0 +1,137 @@ +from geffnet import config +from geffnet.activations.activations_me import * +from geffnet.activations.activations_jit import * +from geffnet.activations.activations import * +import torch + +_has_silu = 'silu' in dir(torch.nn.functional) + +_ACT_FN_DEFAULT = dict( + silu=F.silu if _has_silu else swish, + swish=F.silu if _has_silu else swish, + mish=mish, + relu=F.relu, + relu6=F.relu6, + sigmoid=sigmoid, + tanh=tanh, + hard_sigmoid=hard_sigmoid, + hard_swish=hard_swish, +) + +_ACT_FN_JIT = dict( + silu=F.silu if _has_silu else swish_jit, + swish=F.silu if _has_silu else swish_jit, + mish=mish_jit, +) + +_ACT_FN_ME = dict( + silu=F.silu if _has_silu else swish_me, + swish=F.silu if _has_silu else swish_me, + mish=mish_me, + hard_swish=hard_swish_me, + hard_sigmoid_jit=hard_sigmoid_me, +) + +_ACT_LAYER_DEFAULT = dict( + silu=nn.SiLU if _has_silu else Swish, + swish=nn.SiLU if _has_silu else Swish, + mish=Mish, + relu=nn.ReLU, + relu6=nn.ReLU6, + sigmoid=Sigmoid, + tanh=Tanh, + hard_sigmoid=HardSigmoid, + hard_swish=HardSwish, +) + +_ACT_LAYER_JIT = dict( + silu=nn.SiLU if _has_silu else SwishJit, + swish=nn.SiLU if _has_silu else SwishJit, + mish=MishJit, +) + +_ACT_LAYER_ME = dict( + silu=nn.SiLU if _has_silu else SwishMe, + swish=nn.SiLU if _has_silu else SwishMe, + mish=MishMe, + hard_swish=HardSwishMe, + hard_sigmoid=HardSigmoidMe +) + +_OVERRIDE_FN = dict() +_OVERRIDE_LAYER = dict() + + +def add_override_act_fn(name, fn): + global _OVERRIDE_FN + _OVERRIDE_FN[name] = fn + + +def update_override_act_fn(overrides): + assert isinstance(overrides, dict) + global _OVERRIDE_FN + _OVERRIDE_FN.update(overrides) + + +def clear_override_act_fn(): + global _OVERRIDE_FN + _OVERRIDE_FN = dict() + + +def add_override_act_layer(name, fn): + _OVERRIDE_LAYER[name] = fn + + +def update_override_act_layer(overrides): + assert isinstance(overrides, dict) + global _OVERRIDE_LAYER + _OVERRIDE_LAYER.update(overrides) + + +def clear_override_act_layer(): + global _OVERRIDE_LAYER + _OVERRIDE_LAYER = dict() + + +def get_act_fn(name='relu'): + """ Activation Function Factory + Fetching activation fns by name with this function allows export or torch script friendly + functions to be returned dynamically based on current config. + """ + if name in _OVERRIDE_FN: + return _OVERRIDE_FN[name] + use_me = not (config.is_exportable() or config.is_scriptable() or config.is_no_jit()) + if use_me and name in _ACT_FN_ME: + # If not exporting or scripting the model, first look for a memory optimized version + # activation with custom autograd, then fallback to jit scripted, then a Python or Torch builtin + return _ACT_FN_ME[name] + if config.is_exportable() and name in ('silu', 'swish'): + # FIXME PyTorch SiLU doesn't ONNX export, this is a temp hack + return swish + use_jit = not (config.is_exportable() or config.is_no_jit()) + # NOTE: export tracing should work with jit scripted components, but I keep running into issues + if use_jit and name in _ACT_FN_JIT: # jit scripted models should be okay for export/scripting + return _ACT_FN_JIT[name] + return _ACT_FN_DEFAULT[name] + + +def get_act_layer(name='relu'): + """ Activation Layer Factory + Fetching activation layers by name with this function allows export or torch script friendly + functions to be returned dynamically based on current config. + """ + if name in _OVERRIDE_LAYER: + return _OVERRIDE_LAYER[name] + use_me = not (config.is_exportable() or config.is_scriptable() or config.is_no_jit()) + if use_me and name in _ACT_LAYER_ME: + return _ACT_LAYER_ME[name] + if config.is_exportable() and name in ('silu', 'swish'): + # FIXME PyTorch SiLU doesn't ONNX export, this is a temp hack + return Swish + use_jit = not (config.is_exportable() or config.is_no_jit()) + # NOTE: export tracing should work with jit scripted components, but I keep running into issues + if use_jit and name in _ACT_FN_JIT: # jit scripted models should be okay for export/scripting + return _ACT_LAYER_JIT[name] + return _ACT_LAYER_DEFAULT[name] + + diff --git a/modules/control/proc/normalbae/nets/submodules/efficientnet_repo/geffnet/activations/activations.py b/modules/control/proc/normalbae/nets/submodules/efficientnet_repo/geffnet/activations/activations.py new file mode 100644 index 000000000..bdea692d1 --- /dev/null +++ b/modules/control/proc/normalbae/nets/submodules/efficientnet_repo/geffnet/activations/activations.py @@ -0,0 +1,102 @@ +""" Activations + +A collection of activations fn and modules with a common interface so that they can +easily be swapped. All have an `inplace` arg even if not used. + +Copyright 2020 Ross Wightman +""" +from torch import nn as nn +from torch.nn import functional as F + + +def swish(x, inplace: bool = False): + """Swish - Described originally as SiLU (https://arxiv.org/abs/1702.03118v3) + and also as Swish (https://arxiv.org/abs/1710.05941). + + TODO Rename to SiLU with addition to PyTorch + """ + return x.mul_(x.sigmoid()) if inplace else x.mul(x.sigmoid()) + + +class Swish(nn.Module): + def __init__(self, inplace: bool = False): + super(Swish, self).__init__() + self.inplace = inplace + + def forward(self, x): + return swish(x, self.inplace) + + +def mish(x, inplace: bool = False): + """Mish: A Self Regularized Non-Monotonic Neural Activation Function - https://arxiv.org/abs/1908.08681 + """ + return x.mul(F.softplus(x).tanh()) + + +class Mish(nn.Module): + def __init__(self, inplace: bool = False): + super(Mish, self).__init__() + self.inplace = inplace + + def forward(self, x): + return mish(x, self.inplace) + + +def sigmoid(x, inplace: bool = False): + return x.sigmoid_() if inplace else x.sigmoid() + + +# PyTorch has this, but not with a consistent inplace argmument interface +class Sigmoid(nn.Module): + def __init__(self, inplace: bool = False): + super(Sigmoid, self).__init__() + self.inplace = inplace + + def forward(self, x): + return x.sigmoid_() if self.inplace else x.sigmoid() + + +def tanh(x, inplace: bool = False): + return x.tanh_() if inplace else x.tanh() + + +# PyTorch has this, but not with a consistent inplace argmument interface +class Tanh(nn.Module): + def __init__(self, inplace: bool = False): + super(Tanh, self).__init__() + self.inplace = inplace + + def forward(self, x): + return x.tanh_() if self.inplace else x.tanh() + + +def hard_swish(x, inplace: bool = False): + inner = F.relu6(x + 3.).div_(6.) + return x.mul_(inner) if inplace else x.mul(inner) + + +class HardSwish(nn.Module): + def __init__(self, inplace: bool = False): + super(HardSwish, self).__init__() + self.inplace = inplace + + def forward(self, x): + return hard_swish(x, self.inplace) + + +def hard_sigmoid(x, inplace: bool = False): + if inplace: + return x.add_(3.).clamp_(0., 6.).div_(6.) + else: + return F.relu6(x + 3.) / 6. + + +class HardSigmoid(nn.Module): + def __init__(self, inplace: bool = False): + super(HardSigmoid, self).__init__() + self.inplace = inplace + + def forward(self, x): + return hard_sigmoid(x, self.inplace) + + diff --git a/modules/control/proc/normalbae/nets/submodules/efficientnet_repo/geffnet/activations/activations_jit.py b/modules/control/proc/normalbae/nets/submodules/efficientnet_repo/geffnet/activations/activations_jit.py new file mode 100644 index 000000000..7176b05e7 --- /dev/null +++ b/modules/control/proc/normalbae/nets/submodules/efficientnet_repo/geffnet/activations/activations_jit.py @@ -0,0 +1,79 @@ +""" Activations (jit) + +A collection of jit-scripted activations fn and modules with a common interface so that they can +easily be swapped. All have an `inplace` arg even if not used. + +All jit scripted activations are lacking in-place variations on purpose, scripted kernel fusion does not +currently work across in-place op boundaries, thus performance is equal to or less than the non-scripted +versions if they contain in-place ops. + +Copyright 2020 Ross Wightman +""" + +import torch +from torch import nn as nn +from torch.nn import functional as F + +__all__ = ['swish_jit', 'SwishJit', 'mish_jit', 'MishJit', + 'hard_sigmoid_jit', 'HardSigmoidJit', 'hard_swish_jit', 'HardSwishJit'] + + +@torch.jit.script +def swish_jit(x, inplace: bool = False): + """Swish - Described originally as SiLU (https://arxiv.org/abs/1702.03118v3) + and also as Swish (https://arxiv.org/abs/1710.05941). + + TODO Rename to SiLU with addition to PyTorch + """ + return x.mul(x.sigmoid()) + + +@torch.jit.script +def mish_jit(x, _inplace: bool = False): + """Mish: A Self Regularized Non-Monotonic Neural Activation Function - https://arxiv.org/abs/1908.08681 + """ + return x.mul(F.softplus(x).tanh()) + + +class SwishJit(nn.Module): + def __init__(self, inplace: bool = False): + super(SwishJit, self).__init__() + + def forward(self, x): + return swish_jit(x) + + +class MishJit(nn.Module): + def __init__(self, inplace: bool = False): + super(MishJit, self).__init__() + + def forward(self, x): + return mish_jit(x) + + +@torch.jit.script +def hard_sigmoid_jit(x, inplace: bool = False): + # return F.relu6(x + 3.) / 6. + return (x + 3).clamp(min=0, max=6).div(6.) # clamp seems ever so slightly faster? + + +class HardSigmoidJit(nn.Module): + def __init__(self, inplace: bool = False): + super(HardSigmoidJit, self).__init__() + + def forward(self, x): + return hard_sigmoid_jit(x) + + +@torch.jit.script +def hard_swish_jit(x, inplace: bool = False): + # return x * (F.relu6(x + 3.) / 6) + return x * (x + 3).clamp(min=0, max=6).div(6.) # clamp seems ever so slightly faster? + + +class HardSwishJit(nn.Module): + def __init__(self, inplace: bool = False): + super(HardSwishJit, self).__init__() + + def forward(self, x): + return hard_swish_jit(x) diff --git a/modules/control/proc/normalbae/nets/submodules/efficientnet_repo/geffnet/activations/activations_me.py b/modules/control/proc/normalbae/nets/submodules/efficientnet_repo/geffnet/activations/activations_me.py new file mode 100644 index 000000000..e91df5a50 --- /dev/null +++ b/modules/control/proc/normalbae/nets/submodules/efficientnet_repo/geffnet/activations/activations_me.py @@ -0,0 +1,174 @@ +""" Activations (memory-efficient w/ custom autograd) + +A collection of activations fn and modules with a common interface so that they can +easily be swapped. All have an `inplace` arg even if not used. + +These activations are not compatible with jit scripting or ONNX export of the model, please use either +the JIT or basic versions of the activations. + +Copyright 2020 Ross Wightman +""" + +import torch +from torch import nn as nn +from torch.nn import functional as F + + +__all__ = ['swish_me', 'SwishMe', 'mish_me', 'MishMe', + 'hard_sigmoid_me', 'HardSigmoidMe', 'hard_swish_me', 'HardSwishMe'] + + +@torch.jit.script +def swish_jit_fwd(x): + return x.mul(torch.sigmoid(x)) + + +@torch.jit.script +def swish_jit_bwd(x, grad_output): + x_sigmoid = torch.sigmoid(x) + return grad_output * (x_sigmoid * (1 + x * (1 - x_sigmoid))) + + +class SwishJitAutoFn(torch.autograd.Function): + """ torch.jit.script optimised Swish w/ memory-efficient checkpoint + Inspired by conversation btw Jeremy Howard & Adam Pazske + https://twitter.com/jeremyphoward/status/1188251041835315200 + + Swish - Described originally as SiLU (https://arxiv.org/abs/1702.03118v3) + and also as Swish (https://arxiv.org/abs/1710.05941). + + TODO Rename to SiLU with addition to PyTorch + """ + + @staticmethod + def forward(ctx, x): + ctx.save_for_backward(x) + return swish_jit_fwd(x) + + @staticmethod + def backward(ctx, grad_output): + x = ctx.saved_tensors[0] + return swish_jit_bwd(x, grad_output) + + +def swish_me(x, inplace=False): + return SwishJitAutoFn.apply(x) + + +class SwishMe(nn.Module): + def __init__(self, inplace: bool = False): + super(SwishMe, self).__init__() + + def forward(self, x): + return SwishJitAutoFn.apply(x) + + +@torch.jit.script +def mish_jit_fwd(x): + return x.mul(torch.tanh(F.softplus(x))) + + +@torch.jit.script +def mish_jit_bwd(x, grad_output): + x_sigmoid = torch.sigmoid(x) + x_tanh_sp = F.softplus(x).tanh() + return grad_output.mul(x_tanh_sp + x * x_sigmoid * (1 - x_tanh_sp * x_tanh_sp)) + + +class MishJitAutoFn(torch.autograd.Function): + """ Mish: A Self Regularized Non-Monotonic Neural Activation Function - https://arxiv.org/abs/1908.08681 + A memory efficient, jit scripted variant of Mish + """ + @staticmethod + def forward(ctx, x): + ctx.save_for_backward(x) + return mish_jit_fwd(x) + + @staticmethod + def backward(ctx, grad_output): + x = ctx.saved_tensors[0] + return mish_jit_bwd(x, grad_output) + + +def mish_me(x, inplace=False): + return MishJitAutoFn.apply(x) + + +class MishMe(nn.Module): + def __init__(self, inplace: bool = False): + super(MishMe, self).__init__() + + def forward(self, x): + return MishJitAutoFn.apply(x) + + +@torch.jit.script +def hard_sigmoid_jit_fwd(x, inplace: bool = False): + return (x + 3).clamp(min=0, max=6).div(6.) + + +@torch.jit.script +def hard_sigmoid_jit_bwd(x, grad_output): + m = torch.ones_like(x) * ((x >= -3.) & (x <= 3.)) / 6. + return grad_output * m + + +class HardSigmoidJitAutoFn(torch.autograd.Function): + @staticmethod + def forward(ctx, x): + ctx.save_for_backward(x) + return hard_sigmoid_jit_fwd(x) + + @staticmethod + def backward(ctx, grad_output): + x = ctx.saved_tensors[0] + return hard_sigmoid_jit_bwd(x, grad_output) + + +def hard_sigmoid_me(x, inplace: bool = False): + return HardSigmoidJitAutoFn.apply(x) + + +class HardSigmoidMe(nn.Module): + def __init__(self, inplace: bool = False): + super(HardSigmoidMe, self).__init__() + + def forward(self, x): + return HardSigmoidJitAutoFn.apply(x) + + +@torch.jit.script +def hard_swish_jit_fwd(x): + return x * (x + 3).clamp(min=0, max=6).div(6.) + + +@torch.jit.script +def hard_swish_jit_bwd(x, grad_output): + m = torch.ones_like(x) * (x >= 3.) + m = torch.where((x >= -3.) & (x <= 3.), x / 3. + .5, m) + return grad_output * m + + +class HardSwishJitAutoFn(torch.autograd.Function): + """A memory efficient, jit-scripted HardSwish activation""" + @staticmethod + def forward(ctx, x): + ctx.save_for_backward(x) + return hard_swish_jit_fwd(x) + + @staticmethod + def backward(ctx, grad_output): + x = ctx.saved_tensors[0] + return hard_swish_jit_bwd(x, grad_output) + + +def hard_swish_me(x, inplace=False): + return HardSwishJitAutoFn.apply(x) + + +class HardSwishMe(nn.Module): + def __init__(self, inplace: bool = False): + super(HardSwishMe, self).__init__() + + def forward(self, x): + return HardSwishJitAutoFn.apply(x) diff --git a/modules/control/proc/normalbae/nets/submodules/efficientnet_repo/geffnet/config.py b/modules/control/proc/normalbae/nets/submodules/efficientnet_repo/geffnet/config.py new file mode 100644 index 000000000..27d5307fd --- /dev/null +++ b/modules/control/proc/normalbae/nets/submodules/efficientnet_repo/geffnet/config.py @@ -0,0 +1,123 @@ +""" Global layer config state +""" +from typing import Any, Optional + +__all__ = [ + 'is_exportable', 'is_scriptable', 'is_no_jit', 'layer_config_kwargs', + 'set_exportable', 'set_scriptable', 'set_no_jit', 'set_layer_config' +] + +# Set to True if prefer to have layers with no jit optimization (includes activations) +_NO_JIT = False + +# Set to True if prefer to have activation layers with no jit optimization +# NOTE not currently used as no difference between no_jit and no_activation jit as only layers obeying +# the jit flags so far are activations. This will change as more layers are updated and/or added. +_NO_ACTIVATION_JIT = False + +# Set to True if exporting a model with Same padding via ONNX +_EXPORTABLE = False + +# Set to True if wanting to use torch.jit.script on a model +_SCRIPTABLE = False + + +def is_no_jit(): + return _NO_JIT + + +class set_no_jit: + def __init__(self, mode: bool) -> None: + global _NO_JIT + self.prev = _NO_JIT + _NO_JIT = mode + + def __enter__(self) -> None: + pass + + def __exit__(self, *args: Any) -> bool: + global _NO_JIT + _NO_JIT = self.prev + return False + + +def is_exportable(): + return _EXPORTABLE + + +class set_exportable: + def __init__(self, mode: bool) -> None: + global _EXPORTABLE + self.prev = _EXPORTABLE + _EXPORTABLE = mode + + def __enter__(self) -> None: + pass + + def __exit__(self, *args: Any) -> bool: + global _EXPORTABLE + _EXPORTABLE = self.prev + return False + + +def is_scriptable(): + return _SCRIPTABLE + + +class set_scriptable: + def __init__(self, mode: bool) -> None: + global _SCRIPTABLE + self.prev = _SCRIPTABLE + _SCRIPTABLE = mode + + def __enter__(self) -> None: + pass + + def __exit__(self, *args: Any) -> bool: + global _SCRIPTABLE + _SCRIPTABLE = self.prev + return False + + +class set_layer_config: + """ Layer config context manager that allows setting all layer config flags at once. + If a flag arg is None, it will not change the current value. + """ + def __init__( + self, + scriptable: Optional[bool] = None, + exportable: Optional[bool] = None, + no_jit: Optional[bool] = None, + no_activation_jit: Optional[bool] = None): + global _SCRIPTABLE + global _EXPORTABLE + global _NO_JIT + global _NO_ACTIVATION_JIT + self.prev = _SCRIPTABLE, _EXPORTABLE, _NO_JIT, _NO_ACTIVATION_JIT + if scriptable is not None: + _SCRIPTABLE = scriptable + if exportable is not None: + _EXPORTABLE = exportable + if no_jit is not None: + _NO_JIT = no_jit + if no_activation_jit is not None: + _NO_ACTIVATION_JIT = no_activation_jit + + def __enter__(self) -> None: + pass + + def __exit__(self, *args: Any) -> bool: + global _SCRIPTABLE + global _EXPORTABLE + global _NO_JIT + global _NO_ACTIVATION_JIT + _SCRIPTABLE, _EXPORTABLE, _NO_JIT, _NO_ACTIVATION_JIT = self.prev + return False + + +def layer_config_kwargs(kwargs): + """ Consume config kwargs and return contextmgr obj """ + return set_layer_config( + scriptable=kwargs.pop('scriptable', None), + exportable=kwargs.pop('exportable', None), + no_jit=kwargs.pop('no_jit', None)) diff --git a/modules/control/proc/normalbae/nets/submodules/efficientnet_repo/geffnet/conv2d_layers.py b/modules/control/proc/normalbae/nets/submodules/efficientnet_repo/geffnet/conv2d_layers.py new file mode 100644 index 000000000..d8467460c --- /dev/null +++ b/modules/control/proc/normalbae/nets/submodules/efficientnet_repo/geffnet/conv2d_layers.py @@ -0,0 +1,304 @@ +""" Conv2D w/ SAME padding, CondConv, MixedConv + +A collection of conv layers and padding helpers needed by EfficientNet, MixNet, and +MobileNetV3 models that maintain weight compatibility with original Tensorflow models. + +Copyright 2020 Ross Wightman +""" +import collections.abc +import math +from functools import partial +from itertools import repeat +from typing import Tuple, Optional + +import numpy as np +import torch +import torch.nn as nn +import torch.nn.functional as F + +from .config import * + + +# From PyTorch internals +def _ntuple(n): + def parse(x): + if isinstance(x, collections.abc.Iterable): + return x + return tuple(repeat(x, n)) + return parse + + +_single = _ntuple(1) +_pair = _ntuple(2) +_triple = _ntuple(3) +_quadruple = _ntuple(4) + + +def _is_static_pad(kernel_size, stride=1, dilation=1, **_): + return stride == 1 and (dilation * (kernel_size - 1)) % 2 == 0 + + +def _get_padding(kernel_size, stride=1, dilation=1, **_): + padding = ((stride - 1) + dilation * (kernel_size - 1)) // 2 + return padding + + +def _calc_same_pad(i: int, k: int, s: int, d: int): + return max((-(i // -s) - 1) * s + (k - 1) * d + 1 - i, 0) + + +def _same_pad_arg(input_size, kernel_size, stride, dilation): + ih, iw = input_size + kh, kw = kernel_size + pad_h = _calc_same_pad(ih, kh, stride[0], dilation[0]) + pad_w = _calc_same_pad(iw, kw, stride[1], dilation[1]) + return [pad_w // 2, pad_w - pad_w // 2, pad_h // 2, pad_h - pad_h // 2] + + +def _split_channels(num_chan, num_groups): + split = [num_chan // num_groups for _ in range(num_groups)] + split[0] += num_chan - sum(split) + return split + + +def conv2d_same( + x, weight: torch.Tensor, bias: Optional[torch.Tensor] = None, stride: Tuple[int, int] = (1, 1), + padding: Tuple[int, int] = (0, 0), dilation: Tuple[int, int] = (1, 1), groups: int = 1): + ih, iw = x.size()[-2:] + kh, kw = weight.size()[-2:] + pad_h = _calc_same_pad(ih, kh, stride[0], dilation[0]) + pad_w = _calc_same_pad(iw, kw, stride[1], dilation[1]) + x = F.pad(x, [pad_w // 2, pad_w - pad_w // 2, pad_h // 2, pad_h - pad_h // 2]) + return F.conv2d(x, weight, bias, stride, (0, 0), dilation, groups) + + +class Conv2dSame(nn.Conv2d): + """ Tensorflow like 'SAME' convolution wrapper for 2D convolutions + """ + + # pylint: disable=unused-argument + def __init__(self, in_channels, out_channels, kernel_size, stride=1, + padding=0, dilation=1, groups=1, bias=True): + super(Conv2dSame, self).__init__( + in_channels, out_channels, kernel_size, stride, 0, dilation, groups, bias) + + def forward(self, x): + return conv2d_same(x, self.weight, self.bias, self.stride, self.padding, self.dilation, self.groups) + + +class Conv2dSameExport(nn.Conv2d): + """ ONNX export friendly Tensorflow like 'SAME' convolution wrapper for 2D convolutions + + NOTE: This does not currently work with torch.jit.script + """ + + # pylint: disable=unused-argument + def __init__(self, in_channels, out_channels, kernel_size, stride=1, + padding=0, dilation=1, groups=1, bias=True): + super(Conv2dSameExport, self).__init__( + in_channels, out_channels, kernel_size, stride, 0, dilation, groups, bias) + self.pad = None + self.pad_input_size = (0, 0) + + def forward(self, x): + input_size = x.size()[-2:] + if self.pad is None: + pad_arg = _same_pad_arg(input_size, self.weight.size()[-2:], self.stride, self.dilation) + self.pad = nn.ZeroPad2d(pad_arg) + self.pad_input_size = input_size + + if self.pad is not None: + x = self.pad(x) + return F.conv2d( + x, self.weight, self.bias, self.stride, self.padding, self.dilation, self.groups) + + +def get_padding_value(padding, kernel_size, **kwargs): + dynamic = False + if isinstance(padding, str): + # for any string padding, the padding will be calculated for you, one of three ways + padding = padding.lower() + if padding == 'same': + # TF compatible 'SAME' padding, has a performance and GPU memory allocation impact + if _is_static_pad(kernel_size, **kwargs): + # static case, no extra overhead + padding = _get_padding(kernel_size, **kwargs) + else: + # dynamic padding + padding = 0 + dynamic = True + elif padding == 'valid': + # 'VALID' padding, same as padding=0 + padding = 0 + else: + # Default to PyTorch style 'same'-ish symmetric padding + padding = _get_padding(kernel_size, **kwargs) + return padding, dynamic + + +def create_conv2d_pad(in_chs, out_chs, kernel_size, **kwargs): + padding = kwargs.pop('padding', '') + kwargs.setdefault('bias', False) + padding, is_dynamic = get_padding_value(padding, kernel_size, **kwargs) + if is_dynamic: + if is_exportable(): + assert not is_scriptable() + return Conv2dSameExport(in_chs, out_chs, kernel_size, **kwargs) + else: + return Conv2dSame(in_chs, out_chs, kernel_size, **kwargs) + else: + return nn.Conv2d(in_chs, out_chs, kernel_size, padding=padding, **kwargs) + + +class MixedConv2d(nn.ModuleDict): + """ Mixed Grouped Convolution + Based on MDConv and GroupedConv in MixNet impl: + https://github.com/tensorflow/tpu/blob/master/models/official/mnasnet/mixnet/custom_layers.py + """ + + def __init__(self, in_channels, out_channels, kernel_size=3, + stride=1, padding='', dilation=1, depthwise=False, **kwargs): + super(MixedConv2d, self).__init__() + + kernel_size = kernel_size if isinstance(kernel_size, list) else [kernel_size] + num_groups = len(kernel_size) + in_splits = _split_channels(in_channels, num_groups) + out_splits = _split_channels(out_channels, num_groups) + self.in_channels = sum(in_splits) + self.out_channels = sum(out_splits) + for idx, (k, in_ch, out_ch) in enumerate(zip(kernel_size, in_splits, out_splits)): + conv_groups = out_ch if depthwise else 1 + self.add_module( + str(idx), + create_conv2d_pad( + in_ch, out_ch, k, stride=stride, + padding=padding, dilation=dilation, groups=conv_groups, **kwargs) + ) + self.splits = in_splits + + def forward(self, x): + x_split = torch.split(x, self.splits, 1) + x_out = [conv(x_split[i]) for i, conv in enumerate(self.values())] + x = torch.cat(x_out, 1) + return x + + +def get_condconv_initializer(initializer, num_experts, expert_shape): + def condconv_initializer(weight): + """CondConv initializer function.""" + num_params = np.prod(expert_shape) + if (len(weight.shape) != 2 or weight.shape[0] != num_experts or + weight.shape[1] != num_params): + raise (ValueError( + 'CondConv variables must have shape [num_experts, num_params]')) + for i in range(num_experts): + initializer(weight[i].view(expert_shape)) + return condconv_initializer + + +class CondConv2d(nn.Module): + """ Conditional Convolution + Inspired by: https://github.com/tensorflow/tpu/blob/master/models/official/efficientnet/condconv/condconv_layers.py + + Grouped convolution hackery for parallel execution of the per-sample kernel filters inspired by this discussion: + https://github.com/pytorch/pytorch/issues/17983 + """ + __constants__ = ['bias', 'in_channels', 'out_channels', 'dynamic_padding'] + + def __init__(self, in_channels, out_channels, kernel_size=3, + stride=1, padding='', dilation=1, groups=1, bias=False, num_experts=4): + super(CondConv2d, self).__init__() + + self.in_channels = in_channels + self.out_channels = out_channels + self.kernel_size = _pair(kernel_size) + self.stride = _pair(stride) + padding_val, is_padding_dynamic = get_padding_value( + padding, kernel_size, stride=stride, dilation=dilation) + self.dynamic_padding = is_padding_dynamic # if in forward to work with torchscript + self.padding = _pair(padding_val) + self.dilation = _pair(dilation) + self.groups = groups + self.num_experts = num_experts + + self.weight_shape = (self.out_channels, self.in_channels // self.groups) + self.kernel_size + weight_num_param = 1 + for wd in self.weight_shape: + weight_num_param *= wd + self.weight = torch.nn.Parameter(torch.Tensor(self.num_experts, weight_num_param)) + + if bias: + self.bias_shape = (self.out_channels,) + self.bias = torch.nn.Parameter(torch.Tensor(self.num_experts, self.out_channels)) + else: + self.register_parameter('bias', None) + + self.reset_parameters() + + def reset_parameters(self): + init_weight = get_condconv_initializer( + partial(nn.init.kaiming_uniform_, a=math.sqrt(5)), self.num_experts, self.weight_shape) + init_weight(self.weight) + if self.bias is not None: + fan_in = np.prod(self.weight_shape[1:]) + bound = 1 / math.sqrt(fan_in) + init_bias = get_condconv_initializer( + partial(nn.init.uniform_, a=-bound, b=bound), self.num_experts, self.bias_shape) + init_bias(self.bias) + + def forward(self, x, routing_weights): + B, C, H, W = x.shape + weight = torch.matmul(routing_weights, self.weight) + new_weight_shape = (B * self.out_channels, self.in_channels // self.groups) + self.kernel_size + weight = weight.view(new_weight_shape) + bias = None + if self.bias is not None: + bias = torch.matmul(routing_weights, self.bias) + bias = bias.view(B * self.out_channels) + # move batch elements with channels so each batch element can be efficiently convolved with separate kernel + x = x.view(1, B * C, H, W) + if self.dynamic_padding: + out = conv2d_same( + x, weight, bias, stride=self.stride, padding=self.padding, + dilation=self.dilation, groups=self.groups * B) + else: + out = F.conv2d( + x, weight, bias, stride=self.stride, padding=self.padding, + dilation=self.dilation, groups=self.groups * B) + out = out.permute([1, 0, 2, 3]).view(B, self.out_channels, out.shape[-2], out.shape[-1]) + + # Literal port (from TF definition) + # x = torch.split(x, 1, 0) + # weight = torch.split(weight, 1, 0) + # if self.bias is not None: + # bias = torch.matmul(routing_weights, self.bias) + # bias = torch.split(bias, 1, 0) + # else: + # bias = [None] * B + # out = [] + # for xi, wi, bi in zip(x, weight, bias): + # wi = wi.view(*self.weight_shape) + # if bi is not None: + # bi = bi.view(*self.bias_shape) + # out.append(self.conv_fn( + # xi, wi, bi, stride=self.stride, padding=self.padding, + # dilation=self.dilation, groups=self.groups)) + # out = torch.cat(out, 0) + return out + + +def select_conv2d(in_chs, out_chs, kernel_size, **kwargs): + assert 'groups' not in kwargs # only use 'depthwise' bool arg + if isinstance(kernel_size, list): + assert 'num_experts' not in kwargs # MixNet + CondConv combo not supported currently + # We're going to use only lists for defining the MixedConv2d kernel groups, + # ints, tuples, other iterables will continue to pass to normal conv and specify h, w. + m = MixedConv2d(in_chs, out_chs, kernel_size, **kwargs) + else: + depthwise = kwargs.pop('depthwise', False) + groups = out_chs if depthwise else 1 + if 'num_experts' in kwargs and kwargs['num_experts'] > 0: + m = CondConv2d(in_chs, out_chs, kernel_size, groups=groups, **kwargs) + else: + m = create_conv2d_pad(in_chs, out_chs, kernel_size, groups=groups, **kwargs) + return m diff --git a/modules/control/proc/normalbae/nets/submodules/efficientnet_repo/geffnet/efficientnet_builder.py b/modules/control/proc/normalbae/nets/submodules/efficientnet_repo/geffnet/efficientnet_builder.py new file mode 100644 index 000000000..0343e3f44 --- /dev/null +++ b/modules/control/proc/normalbae/nets/submodules/efficientnet_repo/geffnet/efficientnet_builder.py @@ -0,0 +1,683 @@ +""" EfficientNet / MobileNetV3 Blocks and Builder + +Copyright 2020 Ross Wightman +""" +import re +from copy import deepcopy + +from .conv2d_layers import * +from geffnet.activations import * + +__all__ = ['get_bn_args_tf', 'resolve_bn_args', 'resolve_se_args', 'resolve_act_layer', 'make_divisible', + 'round_channels', 'drop_connect', 'SqueezeExcite', 'ConvBnAct', 'DepthwiseSeparableConv', + 'InvertedResidual', 'CondConvResidual', 'EdgeResidual', 'EfficientNetBuilder', 'decode_arch_def', + 'initialize_weight_default', 'initialize_weight_goog', 'BN_MOMENTUM_TF_DEFAULT', 'BN_EPS_TF_DEFAULT' +] + +# Defaults used for Google/Tensorflow training of mobile networks /w RMSprop as per +# papers and TF reference implementations. PT momentum equiv for TF decay is (1 - TF decay) +# NOTE: momentum varies btw .99 and .9997 depending on source +# .99 in official TF TPU impl +# .9997 (/w .999 in search space) for paper +# +# PyTorch defaults are momentum = .1, eps = 1e-5 +# +BN_MOMENTUM_TF_DEFAULT = 1 - 0.99 +BN_EPS_TF_DEFAULT = 1e-3 +_BN_ARGS_TF = dict(momentum=BN_MOMENTUM_TF_DEFAULT, eps=BN_EPS_TF_DEFAULT) + + +def get_bn_args_tf(): + return _BN_ARGS_TF.copy() + + +def resolve_bn_args(kwargs): + bn_args = get_bn_args_tf() if kwargs.pop('bn_tf', False) else {} + bn_momentum = kwargs.pop('bn_momentum', None) + if bn_momentum is not None: + bn_args['momentum'] = bn_momentum + bn_eps = kwargs.pop('bn_eps', None) + if bn_eps is not None: + bn_args['eps'] = bn_eps + return bn_args + + +_SE_ARGS_DEFAULT = dict( + gate_fn=sigmoid, + act_layer=None, # None == use containing block's activation layer + reduce_mid=False, + divisor=1) + + +def resolve_se_args(kwargs, in_chs, act_layer=None): + se_kwargs = kwargs.copy() if kwargs is not None else {} + # fill in args that aren't specified with the defaults + for k, v in _SE_ARGS_DEFAULT.items(): + se_kwargs.setdefault(k, v) + # some models, like MobilNetV3, calculate SE reduction chs from the containing block's mid_ch instead of in_ch + if not se_kwargs.pop('reduce_mid'): + se_kwargs['reduced_base_chs'] = in_chs + # act_layer override, if it remains None, the containing block's act_layer will be used + if se_kwargs['act_layer'] is None: + assert act_layer is not None + se_kwargs['act_layer'] = act_layer + return se_kwargs + + +def resolve_act_layer(kwargs, default='relu'): + act_layer = kwargs.pop('act_layer', default) + if isinstance(act_layer, str): + act_layer = get_act_layer(act_layer) + return act_layer + + +def make_divisible(v: int, divisor: int = 8, min_value: int = None): + min_value = min_value or divisor + new_v = max(min_value, int(v + divisor / 2) // divisor * divisor) + if new_v < 0.9 * v: # ensure round down does not go down by more than 10%. + new_v += divisor + return new_v + + +def round_channels(channels, multiplier=1.0, divisor=8, channel_min=None): + """Round number of filters based on depth multiplier.""" + if not multiplier: + return channels + channels *= multiplier + return make_divisible(channels, divisor, channel_min) + + +def drop_connect(inputs, training: bool = False, drop_connect_rate: float = 0.): + """Apply drop connect.""" + if not training: + return inputs + + keep_prob = 1 - drop_connect_rate + random_tensor = keep_prob + torch.rand( + (inputs.size()[0], 1, 1, 1), dtype=inputs.dtype, device=inputs.device) + random_tensor.floor_() # binarize + output = inputs.div(keep_prob) * random_tensor + return output + + +class SqueezeExcite(nn.Module): + + def __init__(self, in_chs, se_ratio=0.25, reduced_base_chs=None, act_layer=nn.ReLU, gate_fn=sigmoid, divisor=1): + super(SqueezeExcite, self).__init__() + reduced_chs = make_divisible((reduced_base_chs or in_chs) * se_ratio, divisor) + self.conv_reduce = nn.Conv2d(in_chs, reduced_chs, 1, bias=True) + self.act1 = act_layer(inplace=True) + self.conv_expand = nn.Conv2d(reduced_chs, in_chs, 1, bias=True) + self.gate_fn = gate_fn + + def forward(self, x): + x_se = x.mean((2, 3), keepdim=True) + x_se = self.conv_reduce(x_se) + x_se = self.act1(x_se) + x_se = self.conv_expand(x_se) + x = x * self.gate_fn(x_se) + return x + + +class ConvBnAct(nn.Module): + def __init__(self, in_chs, out_chs, kernel_size, + stride=1, pad_type='', act_layer=nn.ReLU, norm_layer=nn.BatchNorm2d, norm_kwargs=None): + super(ConvBnAct, self).__init__() + assert stride in [1, 2] + norm_kwargs = norm_kwargs or {} + self.conv = select_conv2d(in_chs, out_chs, kernel_size, stride=stride, padding=pad_type) + self.bn1 = norm_layer(out_chs, **norm_kwargs) + self.act1 = act_layer(inplace=True) + + def forward(self, x): + x = self.conv(x) + x = self.bn1(x) + x = self.act1(x) + return x + + +class DepthwiseSeparableConv(nn.Module): + """ DepthwiseSeparable block + Used for DS convs in MobileNet-V1 and in the place of IR blocks with an expansion + factor of 1.0. This is an alternative to having a IR with optional first pw conv. + """ + def __init__(self, in_chs, out_chs, dw_kernel_size=3, + stride=1, pad_type='', act_layer=nn.ReLU, noskip=False, + pw_kernel_size=1, pw_act=False, se_ratio=0., se_kwargs=None, + norm_layer=nn.BatchNorm2d, norm_kwargs=None, drop_connect_rate=0.): + super(DepthwiseSeparableConv, self).__init__() + assert stride in [1, 2] + norm_kwargs = norm_kwargs or {} + self.has_residual = (stride == 1 and in_chs == out_chs) and not noskip + self.drop_connect_rate = drop_connect_rate + + self.conv_dw = select_conv2d( + in_chs, in_chs, dw_kernel_size, stride=stride, padding=pad_type, depthwise=True) + self.bn1 = norm_layer(in_chs, **norm_kwargs) + self.act1 = act_layer(inplace=True) + + # Squeeze-and-excitation + if se_ratio is not None and se_ratio > 0.: + se_kwargs = resolve_se_args(se_kwargs, in_chs, act_layer) + self.se = SqueezeExcite(in_chs, se_ratio=se_ratio, **se_kwargs) + else: + self.se = nn.Identity() + + self.conv_pw = select_conv2d(in_chs, out_chs, pw_kernel_size, padding=pad_type) + self.bn2 = norm_layer(out_chs, **norm_kwargs) + self.act2 = act_layer(inplace=True) if pw_act else nn.Identity() + + def forward(self, x): + residual = x + + x = self.conv_dw(x) + x = self.bn1(x) + x = self.act1(x) + + x = self.se(x) + + x = self.conv_pw(x) + x = self.bn2(x) + x = self.act2(x) + + if self.has_residual: + if self.drop_connect_rate > 0.: + x = drop_connect(x, self.training, self.drop_connect_rate) + x += residual + return x + + +class InvertedResidual(nn.Module): + """ Inverted residual block w/ optional SE""" + + def __init__(self, in_chs, out_chs, dw_kernel_size=3, + stride=1, pad_type='', act_layer=nn.ReLU, noskip=False, + exp_ratio=1.0, exp_kernel_size=1, pw_kernel_size=1, + se_ratio=0., se_kwargs=None, norm_layer=nn.BatchNorm2d, norm_kwargs=None, + conv_kwargs=None, drop_connect_rate=0.): + super(InvertedResidual, self).__init__() + norm_kwargs = norm_kwargs or {} + conv_kwargs = conv_kwargs or {} + mid_chs: int = make_divisible(in_chs * exp_ratio) + self.has_residual = (in_chs == out_chs and stride == 1) and not noskip + self.drop_connect_rate = drop_connect_rate + + # Point-wise expansion + self.conv_pw = select_conv2d(in_chs, mid_chs, exp_kernel_size, padding=pad_type, **conv_kwargs) + self.bn1 = norm_layer(mid_chs, **norm_kwargs) + self.act1 = act_layer(inplace=True) + + # Depth-wise convolution + self.conv_dw = select_conv2d( + mid_chs, mid_chs, dw_kernel_size, stride=stride, padding=pad_type, depthwise=True, **conv_kwargs) + self.bn2 = norm_layer(mid_chs, **norm_kwargs) + self.act2 = act_layer(inplace=True) + + # Squeeze-and-excitation + if se_ratio is not None and se_ratio > 0.: + se_kwargs = resolve_se_args(se_kwargs, in_chs, act_layer) + self.se = SqueezeExcite(mid_chs, se_ratio=se_ratio, **se_kwargs) + else: + self.se = nn.Identity() # for jit.script compat + + # Point-wise linear projection + self.conv_pwl = select_conv2d(mid_chs, out_chs, pw_kernel_size, padding=pad_type, **conv_kwargs) + self.bn3 = norm_layer(out_chs, **norm_kwargs) + + def forward(self, x): + residual = x + + # Point-wise expansion + x = self.conv_pw(x) + x = self.bn1(x) + x = self.act1(x) + + # Depth-wise convolution + x = self.conv_dw(x) + x = self.bn2(x) + x = self.act2(x) + + # Squeeze-and-excitation + x = self.se(x) + + # Point-wise linear projection + x = self.conv_pwl(x) + x = self.bn3(x) + + if self.has_residual: + if self.drop_connect_rate > 0.: + x = drop_connect(x, self.training, self.drop_connect_rate) + x += residual + return x + + +class CondConvResidual(InvertedResidual): + """ Inverted residual block w/ CondConv routing""" + + def __init__(self, in_chs, out_chs, dw_kernel_size=3, + stride=1, pad_type='', act_layer=nn.ReLU, noskip=False, + exp_ratio=1.0, exp_kernel_size=1, pw_kernel_size=1, + se_ratio=0., se_kwargs=None, norm_layer=nn.BatchNorm2d, norm_kwargs=None, + num_experts=0, drop_connect_rate=0.): + + self.num_experts = num_experts + conv_kwargs = dict(num_experts=self.num_experts) + + super(CondConvResidual, self).__init__( + in_chs, out_chs, dw_kernel_size=dw_kernel_size, stride=stride, pad_type=pad_type, + act_layer=act_layer, noskip=noskip, exp_ratio=exp_ratio, exp_kernel_size=exp_kernel_size, + pw_kernel_size=pw_kernel_size, se_ratio=se_ratio, se_kwargs=se_kwargs, + norm_layer=norm_layer, norm_kwargs=norm_kwargs, conv_kwargs=conv_kwargs, + drop_connect_rate=drop_connect_rate) + + self.routing_fn = nn.Linear(in_chs, self.num_experts) + + def forward(self, x): + residual = x + + # CondConv routing + pooled_inputs = F.adaptive_avg_pool2d(x, 1).flatten(1) + routing_weights = torch.sigmoid(self.routing_fn(pooled_inputs)) + + # Point-wise expansion + x = self.conv_pw(x, routing_weights) + x = self.bn1(x) + x = self.act1(x) + + # Depth-wise convolution + x = self.conv_dw(x, routing_weights) + x = self.bn2(x) + x = self.act2(x) + + # Squeeze-and-excitation + x = self.se(x) + + # Point-wise linear projection + x = self.conv_pwl(x, routing_weights) + x = self.bn3(x) + + if self.has_residual: + if self.drop_connect_rate > 0.: + x = drop_connect(x, self.training, self.drop_connect_rate) + x += residual + return x + + +class EdgeResidual(nn.Module): + """ EdgeTPU Residual block with expansion convolution followed by pointwise-linear w/ stride""" + + def __init__(self, in_chs, out_chs, exp_kernel_size=3, exp_ratio=1.0, fake_in_chs=0, + stride=1, pad_type='', act_layer=nn.ReLU, noskip=False, pw_kernel_size=1, + se_ratio=0., se_kwargs=None, norm_layer=nn.BatchNorm2d, norm_kwargs=None, drop_connect_rate=0.): + super(EdgeResidual, self).__init__() + norm_kwargs = norm_kwargs or {} + mid_chs = make_divisible(fake_in_chs * exp_ratio) if fake_in_chs > 0 else make_divisible(in_chs * exp_ratio) + self.has_residual = (in_chs == out_chs and stride == 1) and not noskip + self.drop_connect_rate = drop_connect_rate + + # Expansion convolution + self.conv_exp = select_conv2d(in_chs, mid_chs, exp_kernel_size, padding=pad_type) + self.bn1 = norm_layer(mid_chs, **norm_kwargs) + self.act1 = act_layer(inplace=True) + + # Squeeze-and-excitation + if se_ratio is not None and se_ratio > 0.: + se_kwargs = resolve_se_args(se_kwargs, in_chs, act_layer) + self.se = SqueezeExcite(mid_chs, se_ratio=se_ratio, **se_kwargs) + else: + self.se = nn.Identity() + + # Point-wise linear projection + self.conv_pwl = select_conv2d(mid_chs, out_chs, pw_kernel_size, stride=stride, padding=pad_type) + self.bn2 = nn.BatchNorm2d(out_chs, **norm_kwargs) + + def forward(self, x): + residual = x + + # Expansion convolution + x = self.conv_exp(x) + x = self.bn1(x) + x = self.act1(x) + + # Squeeze-and-excitation + x = self.se(x) + + # Point-wise linear projection + x = self.conv_pwl(x) + x = self.bn2(x) + + if self.has_residual: + if self.drop_connect_rate > 0.: + x = drop_connect(x, self.training, self.drop_connect_rate) + x += residual + + return x + + +class EfficientNetBuilder: + """ Build Trunk Blocks for Efficient/Mobile Networks + + This ended up being somewhat of a cross between + https://github.com/tensorflow/tpu/blob/master/models/official/mnasnet/mnasnet_models.py + and + https://github.com/facebookresearch/maskrcnn-benchmark/blob/master/maskrcnn_benchmark/modeling/backbone/fbnet_builder.py + + """ + + def __init__(self, channel_multiplier=1.0, channel_divisor=8, channel_min=None, + pad_type='', act_layer=None, se_kwargs=None, + norm_layer=nn.BatchNorm2d, norm_kwargs=None, drop_connect_rate=0.): + self.channel_multiplier = channel_multiplier + self.channel_divisor = channel_divisor + self.channel_min = channel_min + self.pad_type = pad_type + self.act_layer = act_layer + self.se_kwargs = se_kwargs + self.norm_layer = norm_layer + self.norm_kwargs = norm_kwargs + self.drop_connect_rate = drop_connect_rate + + # updated during build + self.in_chs = None + self.block_idx = 0 + self.block_count = 0 + + def _round_channels(self, chs): + return round_channels(chs, self.channel_multiplier, self.channel_divisor, self.channel_min) + + def _make_block(self, ba): + bt = ba.pop('block_type') + ba['in_chs'] = self.in_chs + ba['out_chs'] = self._round_channels(ba['out_chs']) + if 'fake_in_chs' in ba and ba['fake_in_chs']: + # FIXME this is a hack to work around mismatch in origin impl input filters for EdgeTPU + ba['fake_in_chs'] = self._round_channels(ba['fake_in_chs']) + ba['norm_layer'] = self.norm_layer + ba['norm_kwargs'] = self.norm_kwargs + ba['pad_type'] = self.pad_type + # block act fn overrides the model default + ba['act_layer'] = ba['act_layer'] if ba['act_layer'] is not None else self.act_layer + assert ba['act_layer'] is not None + if bt == 'ir': + ba['drop_connect_rate'] = self.drop_connect_rate * self.block_idx / self.block_count + ba['se_kwargs'] = self.se_kwargs + if ba.get('num_experts', 0) > 0: + block = CondConvResidual(**ba) + else: + block = InvertedResidual(**ba) + elif bt == 'ds' or bt == 'dsa': + ba['drop_connect_rate'] = self.drop_connect_rate * self.block_idx / self.block_count + ba['se_kwargs'] = self.se_kwargs + block = DepthwiseSeparableConv(**ba) + elif bt == 'er': + ba['drop_connect_rate'] = self.drop_connect_rate * self.block_idx / self.block_count + ba['se_kwargs'] = self.se_kwargs + block = EdgeResidual(**ba) + elif bt == 'cn': + block = ConvBnAct(**ba) + else: + raise AssertionError('Uknkown block type (%s) while building model.' % bt) + self.in_chs = ba['out_chs'] # update in_chs for arg of next block + return block + + def _make_stack(self, stack_args): + blocks = [] + # each stack (stage) contains a list of block arguments + for i, ba in enumerate(stack_args): + if i >= 1: + # only the first block in any stack can have a stride > 1 + ba['stride'] = 1 + block = self._make_block(ba) + blocks.append(block) + self.block_idx += 1 # incr global idx (across all stacks) + return nn.Sequential(*blocks) + + def __call__(self, in_chs, block_args): + """ Build the blocks + Args: + in_chs: Number of input-channels passed to first block + block_args: A list of lists, outer list defines stages, inner + list contains strings defining block configuration(s) + Return: + List of block stacks (each stack wrapped in nn.Sequential) + """ + self.in_chs = in_chs + self.block_count = sum([len(x) for x in block_args]) + self.block_idx = 0 + blocks = [] + # outer list of block_args defines the stacks ('stages' by some conventions) + for _stack_idx, stack in enumerate(block_args): + assert isinstance(stack, list) + stack = self._make_stack(stack) + blocks.append(stack) + return blocks + + +def _parse_ksize(ss): + if ss.isdigit(): + return int(ss) + else: + return [int(k) for k in ss.split('.')] + + +def _decode_block_str(block_str): + """ Decode block definition string + + Gets a list of block arg (dicts) through a string notation of arguments. + E.g. ir_r2_k3_s2_e1_i32_o16_se0.25_noskip + + All args can exist in any order with the exception of the leading string which + is assumed to indicate the block type. + + leading string - block type ( + ir = InvertedResidual, ds = DepthwiseSep, dsa = DeptwhiseSep with pw act, cn = ConvBnAct) + r - number of repeat blocks, + k - kernel size, + s - strides (1-9), + e - expansion ratio, + c - output channels, + se - squeeze/excitation ratio + n - activation fn ('re', 'r6', 'hs', or 'sw') + Args: + block_str: a string representation of block arguments. + Returns: + A list of block args (dicts) + Raises: + ValueError: if the string def not properly specified (TODO) + """ + assert isinstance(block_str, str) + ops = block_str.split('_') + block_type = ops[0] # take the block type off the front + ops = ops[1:] + options = {} + noskip = False + for op in ops: + # string options being checked on individual basis, combine if they grow + if op == 'noskip': + noskip = True + elif op.startswith('n'): + # activation fn + key = op[0] + v = op[1:] + if v == 're': + value = get_act_layer('relu') + elif v == 'r6': + value = get_act_layer('relu6') + elif v == 'hs': + value = get_act_layer('hard_swish') + elif v == 'sw': + value = get_act_layer('swish') + else: + continue + options[key] = value + else: + # all numeric options + splits = re.split(r'(\d.*)', op) + if len(splits) >= 2: + key, value = splits[:2] + options[key] = value + + # if act_layer is None, the model default (passed to model init) will be used + act_layer = options['n'] if 'n' in options else None + exp_kernel_size = _parse_ksize(options['a']) if 'a' in options else 1 + pw_kernel_size = _parse_ksize(options['p']) if 'p' in options else 1 + fake_in_chs = int(options['fc']) if 'fc' in options else 0 # FIXME hack to deal with in_chs issue in TPU def + + num_repeat = int(options['r']) + # each type of block has different valid arguments, fill accordingly + if block_type == 'ir': + block_args = dict( + block_type=block_type, + dw_kernel_size=_parse_ksize(options['k']), + exp_kernel_size=exp_kernel_size, + pw_kernel_size=pw_kernel_size, + out_chs=int(options['c']), + exp_ratio=float(options['e']), + se_ratio=float(options['se']) if 'se' in options else None, + stride=int(options['s']), + act_layer=act_layer, + noskip=noskip, + ) + if 'cc' in options: + block_args['num_experts'] = int(options['cc']) + elif block_type == 'ds' or block_type == 'dsa': + block_args = dict( + block_type=block_type, + dw_kernel_size=_parse_ksize(options['k']), + pw_kernel_size=pw_kernel_size, + out_chs=int(options['c']), + se_ratio=float(options['se']) if 'se' in options else None, + stride=int(options['s']), + act_layer=act_layer, + pw_act=block_type == 'dsa', + noskip=block_type == 'dsa' or noskip, + ) + elif block_type == 'er': + block_args = dict( + block_type=block_type, + exp_kernel_size=_parse_ksize(options['k']), + pw_kernel_size=pw_kernel_size, + out_chs=int(options['c']), + exp_ratio=float(options['e']), + fake_in_chs=fake_in_chs, + se_ratio=float(options['se']) if 'se' in options else None, + stride=int(options['s']), + act_layer=act_layer, + noskip=noskip, + ) + elif block_type == 'cn': + block_args = dict( + block_type=block_type, + kernel_size=int(options['k']), + out_chs=int(options['c']), + stride=int(options['s']), + act_layer=act_layer, + ) + else: + raise AssertionError('Unknown block type (%s)' % block_type) + + return block_args, num_repeat + + +def _scale_stage_depth(stack_args, repeats, depth_multiplier=1.0, depth_trunc='ceil'): + """ Per-stage depth scaling + Scales the block repeats in each stage. This depth scaling impl maintains + compatibility with the EfficientNet scaling method, while allowing sensible + scaling for other models that may have multiple block arg definitions in each stage. + """ + + # We scale the total repeat count for each stage, there may be multiple + # block arg defs per stage so we need to sum. + num_repeat = sum(repeats) + if depth_trunc == 'round': + # Truncating to int by rounding allows stages with few repeats to remain + # proportionally smaller for longer. This is a good choice when stage definitions + # include single repeat stages that we'd prefer to keep that way as long as possible + num_repeat_scaled = max(1, round(num_repeat * depth_multiplier)) + else: + # The default for EfficientNet truncates repeats to int via 'ceil'. + # Any multiplier > 1.0 will result in an increased depth for every stage. + num_repeat_scaled = int(math.ceil(num_repeat * depth_multiplier)) + + # Proportionally distribute repeat count scaling to each block definition in the stage. + # Allocation is done in reverse as it results in the first block being less likely to be scaled. + # The first block makes less sense to repeat in most of the arch definitions. + repeats_scaled = [] + for r in repeats[::-1]: + rs = max(1, round((r / num_repeat * num_repeat_scaled))) + repeats_scaled.append(rs) + num_repeat -= r + num_repeat_scaled -= rs + repeats_scaled = repeats_scaled[::-1] + + # Apply the calculated scaling to each block arg in the stage + sa_scaled = [] + for ba, rep in zip(stack_args, repeats_scaled): + sa_scaled.extend([deepcopy(ba) for _ in range(rep)]) + return sa_scaled + + +def decode_arch_def(arch_def, depth_multiplier=1.0, depth_trunc='ceil', experts_multiplier=1, fix_first_last=False): + arch_args = [] + for stack_idx, block_strings in enumerate(arch_def): + assert isinstance(block_strings, list) + stack_args = [] + repeats = [] + for block_str in block_strings: + assert isinstance(block_str, str) + ba, rep = _decode_block_str(block_str) + if ba.get('num_experts', 0) > 0 and experts_multiplier > 1: + ba['num_experts'] *= experts_multiplier + stack_args.append(ba) + repeats.append(rep) + if fix_first_last and (stack_idx == 0 or stack_idx == len(arch_def) - 1): + arch_args.append(_scale_stage_depth(stack_args, repeats, 1.0, depth_trunc)) + else: + arch_args.append(_scale_stage_depth(stack_args, repeats, depth_multiplier, depth_trunc)) + return arch_args + + +def initialize_weight_goog(m, n='', fix_group_fanout=True): + # weight init as per Tensorflow Official impl + # https://github.com/tensorflow/tpu/blob/master/models/official/mnasnet/mnasnet_model.py + if isinstance(m, CondConv2d): + fan_out = m.kernel_size[0] * m.kernel_size[1] * m.out_channels + if fix_group_fanout: + fan_out //= m.groups + init_weight_fn = get_condconv_initializer( + lambda w: w.data.normal_(0, math.sqrt(2.0 / fan_out)), m.num_experts, m.weight_shape) + init_weight_fn(m.weight) + if m.bias is not None: + m.bias.data.zero_() + elif isinstance(m, nn.Conv2d): + fan_out = m.kernel_size[0] * m.kernel_size[1] * m.out_channels + if fix_group_fanout: + fan_out //= m.groups + m.weight.data.normal_(0, math.sqrt(2.0 / fan_out)) + if m.bias is not None: + m.bias.data.zero_() + elif isinstance(m, nn.BatchNorm2d): + m.weight.data.fill_(1.0) + m.bias.data.zero_() + elif isinstance(m, nn.Linear): + fan_out = m.weight.size(0) # fan-out + fan_in = 0 + if 'routing_fn' in n: + fan_in = m.weight.size(1) + init_range = 1.0 / math.sqrt(fan_in + fan_out) + m.weight.data.uniform_(-init_range, init_range) + m.bias.data.zero_() + + +def initialize_weight_default(m, n=''): + if isinstance(m, CondConv2d): + init_fn = get_condconv_initializer(partial( + nn.init.kaiming_normal_, mode='fan_out', nonlinearity='relu'), m.num_experts, m.weight_shape) + init_fn(m.weight) + elif isinstance(m, nn.Conv2d): + nn.init.kaiming_normal_(m.weight, mode='fan_out', nonlinearity='relu') + elif isinstance(m, nn.BatchNorm2d): + m.weight.data.fill_(1.0) + m.bias.data.zero_() + elif isinstance(m, nn.Linear): + nn.init.kaiming_uniform_(m.weight, mode='fan_in', nonlinearity='linear') diff --git a/modules/control/proc/normalbae/nets/submodules/efficientnet_repo/geffnet/gen_efficientnet.py b/modules/control/proc/normalbae/nets/submodules/efficientnet_repo/geffnet/gen_efficientnet.py new file mode 100644 index 000000000..cd170d4cc --- /dev/null +++ b/modules/control/proc/normalbae/nets/submodules/efficientnet_repo/geffnet/gen_efficientnet.py @@ -0,0 +1,1450 @@ +""" Generic Efficient Networks + +A generic MobileNet class with building blocks to support a variety of models: + +* EfficientNet (B0-B8, L2 + Tensorflow pretrained AutoAug/RandAug/AdvProp/NoisyStudent ports) + - EfficientNet: Rethinking Model Scaling for CNNs - https://arxiv.org/abs/1905.11946 + - CondConv: Conditionally Parameterized Convolutions for Efficient Inference - https://arxiv.org/abs/1904.04971 + - Adversarial Examples Improve Image Recognition - https://arxiv.org/abs/1911.09665 + - Self-training with Noisy Student improves ImageNet classification - https://arxiv.org/abs/1911.04252 + +* EfficientNet-Lite + +* MixNet (Small, Medium, and Large) + - MixConv: Mixed Depthwise Convolutional Kernels - https://arxiv.org/abs/1907.09595 + +* MNasNet B1, A1 (SE), Small + - MnasNet: Platform-Aware Neural Architecture Search for Mobile - https://arxiv.org/abs/1807.11626 + +* FBNet-C + - FBNet: Hardware-Aware Efficient ConvNet Design via Differentiable NAS - https://arxiv.org/abs/1812.03443 + +* Single-Path NAS Pixel1 + - Single-Path NAS: Designing Hardware-Efficient ConvNets - https://arxiv.org/abs/1904.02877 + +* And likely more... + +Hacked together by / Copyright 2020 Ross Wightman +""" +import torch.nn as nn +import torch.nn.functional as F + +from .config import layer_config_kwargs, is_scriptable +from .conv2d_layers import select_conv2d +from .helpers import load_pretrained +from .efficientnet_builder import * + +__all__ = ['GenEfficientNet', 'mnasnet_050', 'mnasnet_075', 'mnasnet_100', 'mnasnet_b1', 'mnasnet_140', + 'semnasnet_050', 'semnasnet_075', 'semnasnet_100', 'mnasnet_a1', 'semnasnet_140', 'mnasnet_small', + 'mobilenetv2_100', 'mobilenetv2_140', 'mobilenetv2_110d', 'mobilenetv2_120d', + 'fbnetc_100', 'spnasnet_100', 'efficientnet_b0', 'efficientnet_b1', 'efficientnet_b2', 'efficientnet_b3', + 'efficientnet_b4', 'efficientnet_b5', 'efficientnet_b6', 'efficientnet_b7', 'efficientnet_b8', + 'efficientnet_l2', 'efficientnet_es', 'efficientnet_em', 'efficientnet_el', + 'efficientnet_cc_b0_4e', 'efficientnet_cc_b0_8e', 'efficientnet_cc_b1_8e', + 'efficientnet_lite0', 'efficientnet_lite1', 'efficientnet_lite2', 'efficientnet_lite3', 'efficientnet_lite4', + 'tf_efficientnet_b0', 'tf_efficientnet_b1', 'tf_efficientnet_b2', 'tf_efficientnet_b3', + 'tf_efficientnet_b4', 'tf_efficientnet_b5', 'tf_efficientnet_b6', 'tf_efficientnet_b7', 'tf_efficientnet_b8', + 'tf_efficientnet_b0_ap', 'tf_efficientnet_b1_ap', 'tf_efficientnet_b2_ap', 'tf_efficientnet_b3_ap', + 'tf_efficientnet_b4_ap', 'tf_efficientnet_b5_ap', 'tf_efficientnet_b6_ap', 'tf_efficientnet_b7_ap', + 'tf_efficientnet_b8_ap', 'tf_efficientnet_b0_ns', 'tf_efficientnet_b1_ns', 'tf_efficientnet_b2_ns', + 'tf_efficientnet_b3_ns', 'tf_efficientnet_b4_ns', 'tf_efficientnet_b5_ns', 'tf_efficientnet_b6_ns', + 'tf_efficientnet_b7_ns', 'tf_efficientnet_l2_ns', 'tf_efficientnet_l2_ns_475', + 'tf_efficientnet_es', 'tf_efficientnet_em', 'tf_efficientnet_el', + 'tf_efficientnet_cc_b0_4e', 'tf_efficientnet_cc_b0_8e', 'tf_efficientnet_cc_b1_8e', + 'tf_efficientnet_lite0', 'tf_efficientnet_lite1', 'tf_efficientnet_lite2', 'tf_efficientnet_lite3', + 'tf_efficientnet_lite4', + 'mixnet_s', 'mixnet_m', 'mixnet_l', 'mixnet_xl', 'tf_mixnet_s', 'tf_mixnet_m', 'tf_mixnet_l'] + + +model_urls = { + 'mnasnet_050': None, + 'mnasnet_075': None, + 'mnasnet_100': + 'https://github.com/rwightman/pytorch-image-models/releases/download/v0.1-weights/mnasnet_b1-74cb7081.pth', + 'mnasnet_140': None, + 'mnasnet_small': None, + + 'semnasnet_050': None, + 'semnasnet_075': None, + 'semnasnet_100': + 'https://github.com/rwightman/pytorch-image-models/releases/download/v0.1-weights/mnasnet_a1-d9418771.pth', + 'semnasnet_140': None, + + 'mobilenetv2_100': + 'https://github.com/rwightman/pytorch-image-models/releases/download/v0.1-weights/mobilenetv2_100_ra-b33bc2c4.pth', + 'mobilenetv2_110d': + 'https://github.com/rwightman/pytorch-image-models/releases/download/v0.1-weights/mobilenetv2_110d_ra-77090ade.pth', + 'mobilenetv2_120d': + 'https://github.com/rwightman/pytorch-image-models/releases/download/v0.1-weights/mobilenetv2_120d_ra-5987e2ed.pth', + 'mobilenetv2_140': + 'https://github.com/rwightman/pytorch-image-models/releases/download/v0.1-weights/mobilenetv2_140_ra-21a4e913.pth', + + 'fbnetc_100': + 'https://github.com/rwightman/pytorch-image-models/releases/download/v0.1-weights/fbnetc_100-c345b898.pth', + 'spnasnet_100': + 'https://github.com/rwightman/pytorch-image-models/releases/download/v0.1-weights/spnasnet_100-048bc3f4.pth', + + 'efficientnet_b0': + 'https://github.com/rwightman/pytorch-image-models/releases/download/v0.1-weights/efficientnet_b0_ra-3dd342df.pth', + 'efficientnet_b1': + 'https://github.com/rwightman/pytorch-image-models/releases/download/v0.1-weights/efficientnet_b1-533bc792.pth', + 'efficientnet_b2': + 'https://github.com/rwightman/pytorch-image-models/releases/download/v0.1-weights/efficientnet_b2_ra-bcdf34b7.pth', + 'efficientnet_b3': + 'https://github.com/rwightman/pytorch-image-models/releases/download/v0.1-weights/efficientnet_b3_ra2-cf984f9c.pth', + 'efficientnet_b4': None, + 'efficientnet_b5': None, + 'efficientnet_b6': None, + 'efficientnet_b7': None, + 'efficientnet_b8': None, + 'efficientnet_l2': None, + + 'efficientnet_es': + 'https://github.com/rwightman/pytorch-image-models/releases/download/v0.1-weights/efficientnet_es_ra-f111e99c.pth', + 'efficientnet_em': None, + 'efficientnet_el': None, + + 'efficientnet_cc_b0_4e': None, + 'efficientnet_cc_b0_8e': None, + 'efficientnet_cc_b1_8e': None, + + 'efficientnet_lite0': 'https://github.com/rwightman/pytorch-image-models/releases/download/v0.1-weights/efficientnet_lite0_ra-37913777.pth', + 'efficientnet_lite1': None, + 'efficientnet_lite2': None, + 'efficientnet_lite3': None, + 'efficientnet_lite4': None, + + 'tf_efficientnet_b0': + 'https://github.com/rwightman/pytorch-image-models/releases/download/v0.1-weights/tf_efficientnet_b0_aa-827b6e33.pth', + 'tf_efficientnet_b1': + 'https://github.com/rwightman/pytorch-image-models/releases/download/v0.1-weights/tf_efficientnet_b1_aa-ea7a6ee0.pth', + 'tf_efficientnet_b2': + 'https://github.com/rwightman/pytorch-image-models/releases/download/v0.1-weights/tf_efficientnet_b2_aa-60c94f97.pth', + 'tf_efficientnet_b3': + 'https://github.com/rwightman/pytorch-image-models/releases/download/v0.1-weights/tf_efficientnet_b3_aa-84b4657e.pth', + 'tf_efficientnet_b4': + 'https://github.com/rwightman/pytorch-image-models/releases/download/v0.1-weights/tf_efficientnet_b4_aa-818f208c.pth', + 'tf_efficientnet_b5': + 'https://github.com/rwightman/pytorch-image-models/releases/download/v0.1-weights/tf_efficientnet_b5_ra-9a3e5369.pth', + 'tf_efficientnet_b6': + 'https://github.com/rwightman/pytorch-image-models/releases/download/v0.1-weights/tf_efficientnet_b6_aa-80ba17e4.pth', + 'tf_efficientnet_b7': + 'https://github.com/rwightman/pytorch-image-models/releases/download/v0.1-weights/tf_efficientnet_b7_ra-6c08e654.pth', + 'tf_efficientnet_b8': + 'https://github.com/rwightman/pytorch-image-models/releases/download/v0.1-weights/tf_efficientnet_b8_ra-572d5dd9.pth', + + 'tf_efficientnet_b0_ap': + 'https://github.com/rwightman/pytorch-image-models/releases/download/v0.1-weights/tf_efficientnet_b0_ap-f262efe1.pth', + 'tf_efficientnet_b1_ap': + 'https://github.com/rwightman/pytorch-image-models/releases/download/v0.1-weights/tf_efficientnet_b1_ap-44ef0a3d.pth', + 'tf_efficientnet_b2_ap': + 'https://github.com/rwightman/pytorch-image-models/releases/download/v0.1-weights/tf_efficientnet_b2_ap-2f8e7636.pth', + 'tf_efficientnet_b3_ap': + 'https://github.com/rwightman/pytorch-image-models/releases/download/v0.1-weights/tf_efficientnet_b3_ap-aad25bdd.pth', + 'tf_efficientnet_b4_ap': + 'https://github.com/rwightman/pytorch-image-models/releases/download/v0.1-weights/tf_efficientnet_b4_ap-dedb23e6.pth', + 'tf_efficientnet_b5_ap': + 'https://github.com/rwightman/pytorch-image-models/releases/download/v0.1-weights/tf_efficientnet_b5_ap-9e82fae8.pth', + 'tf_efficientnet_b6_ap': + 'https://github.com/rwightman/pytorch-image-models/releases/download/v0.1-weights/tf_efficientnet_b6_ap-4ffb161f.pth', + 'tf_efficientnet_b7_ap': + 'https://github.com/rwightman/pytorch-image-models/releases/download/v0.1-weights/tf_efficientnet_b7_ap-ddb28fec.pth', + 'tf_efficientnet_b8_ap': + 'https://github.com/rwightman/pytorch-image-models/releases/download/v0.1-weights/tf_efficientnet_b8_ap-00e169fa.pth', + + 'tf_efficientnet_b0_ns': + 'https://github.com/rwightman/pytorch-image-models/releases/download/v0.1-weights/tf_efficientnet_b0_ns-c0e6a31c.pth', + 'tf_efficientnet_b1_ns': + 'https://github.com/rwightman/pytorch-image-models/releases/download/v0.1-weights/tf_efficientnet_b1_ns-99dd0c41.pth', + 'tf_efficientnet_b2_ns': + 'https://github.com/rwightman/pytorch-image-models/releases/download/v0.1-weights/tf_efficientnet_b2_ns-00306e48.pth', + 'tf_efficientnet_b3_ns': + 'https://github.com/rwightman/pytorch-image-models/releases/download/v0.1-weights/tf_efficientnet_b3_ns-9d44bf68.pth', + 'tf_efficientnet_b4_ns': + 'https://github.com/rwightman/pytorch-image-models/releases/download/v0.1-weights/tf_efficientnet_b4_ns-d6313a46.pth', + 'tf_efficientnet_b5_ns': + 'https://github.com/rwightman/pytorch-image-models/releases/download/v0.1-weights/tf_efficientnet_b5_ns-6f26d0cf.pth', + 'tf_efficientnet_b6_ns': + 'https://github.com/rwightman/pytorch-image-models/releases/download/v0.1-weights/tf_efficientnet_b6_ns-51548356.pth', + 'tf_efficientnet_b7_ns': + 'https://github.com/rwightman/pytorch-image-models/releases/download/v0.1-weights/tf_efficientnet_b7_ns-1dbc32de.pth', + 'tf_efficientnet_l2_ns_475': + 'https://github.com/rwightman/pytorch-image-models/releases/download/v0.1-weights/tf_efficientnet_l2_ns_475-bebbd00a.pth', + 'tf_efficientnet_l2_ns': + 'https://github.com/rwightman/pytorch-image-models/releases/download/v0.1-weights/tf_efficientnet_l2_ns-df73bb44.pth', + + 'tf_efficientnet_es': + 'https://github.com/rwightman/pytorch-image-models/releases/download/v0.1-weights/tf_efficientnet_es-ca1afbfe.pth', + 'tf_efficientnet_em': + 'https://github.com/rwightman/pytorch-image-models/releases/download/v0.1-weights/tf_efficientnet_em-e78cfe58.pth', + 'tf_efficientnet_el': + 'https://github.com/rwightman/pytorch-image-models/releases/download/v0.1-weights/tf_efficientnet_el-5143854e.pth', + + 'tf_efficientnet_cc_b0_4e': + 'https://github.com/rwightman/pytorch-image-models/releases/download/v0.1-weights/tf_efficientnet_cc_b0_4e-4362b6b2.pth', + 'tf_efficientnet_cc_b0_8e': + 'https://github.com/rwightman/pytorch-image-models/releases/download/v0.1-weights/tf_efficientnet_cc_b0_8e-66184a25.pth', + 'tf_efficientnet_cc_b1_8e': + 'https://github.com/rwightman/pytorch-image-models/releases/download/v0.1-weights/tf_efficientnet_cc_b1_8e-f7c79ae1.pth', + + 'tf_efficientnet_lite0': + 'https://github.com/rwightman/pytorch-image-models/releases/download/v0.1-weights/tf_efficientnet_lite0-0aa007d2.pth', + 'tf_efficientnet_lite1': + 'https://github.com/rwightman/pytorch-image-models/releases/download/v0.1-weights/tf_efficientnet_lite1-bde8b488.pth', + 'tf_efficientnet_lite2': + 'https://github.com/rwightman/pytorch-image-models/releases/download/v0.1-weights/tf_efficientnet_lite2-dcccb7df.pth', + 'tf_efficientnet_lite3': + 'https://github.com/rwightman/pytorch-image-models/releases/download/v0.1-weights/tf_efficientnet_lite3-b733e338.pth', + 'tf_efficientnet_lite4': + 'https://github.com/rwightman/pytorch-image-models/releases/download/v0.1-weights/tf_efficientnet_lite4-741542c3.pth', + + 'mixnet_s': 'https://github.com/rwightman/pytorch-image-models/releases/download/v0.1-weights/mixnet_s-a907afbc.pth', + 'mixnet_m': 'https://github.com/rwightman/pytorch-image-models/releases/download/v0.1-weights/mixnet_m-4647fc68.pth', + 'mixnet_l': 'https://github.com/rwightman/pytorch-image-models/releases/download/v0.1-weights/mixnet_l-5a9a2ed8.pth', + 'mixnet_xl': 'https://github.com/rwightman/pytorch-image-models/releases/download/v0.1-weights/mixnet_xl_ra-aac3c00c.pth', + + 'tf_mixnet_s': + 'https://github.com/rwightman/pytorch-image-models/releases/download/v0.1-weights/tf_mixnet_s-89d3354b.pth', + 'tf_mixnet_m': + 'https://github.com/rwightman/pytorch-image-models/releases/download/v0.1-weights/tf_mixnet_m-0f4d8805.pth', + 'tf_mixnet_l': + 'https://github.com/rwightman/pytorch-image-models/releases/download/v0.1-weights/tf_mixnet_l-6c92e0c8.pth', +} + + +class GenEfficientNet(nn.Module): + """ Generic EfficientNets + + An implementation of mobile optimized networks that covers: + * EfficientNet (B0-B8, L2, CondConv, EdgeTPU) + * MixNet (Small, Medium, and Large, XL) + * MNASNet A1, B1, and small + * FBNet C + * Single-Path NAS Pixel1 + """ + + def __init__(self, block_args, num_classes=1000, in_chans=3, num_features=1280, stem_size=32, fix_stem=False, + channel_multiplier=1.0, channel_divisor=8, channel_min=None, + pad_type='', act_layer=nn.ReLU, drop_rate=0., drop_connect_rate=0., + se_kwargs=None, norm_layer=nn.BatchNorm2d, norm_kwargs=None, + weight_init='goog'): + super(GenEfficientNet, self).__init__() + self.drop_rate = drop_rate + + if not fix_stem: + stem_size = round_channels(stem_size, channel_multiplier, channel_divisor, channel_min) + self.conv_stem = select_conv2d(in_chans, stem_size, 3, stride=2, padding=pad_type) + self.bn1 = norm_layer(stem_size, **norm_kwargs) + self.act1 = act_layer(inplace=True) + in_chs = stem_size + + builder = EfficientNetBuilder( + channel_multiplier, channel_divisor, channel_min, + pad_type, act_layer, se_kwargs, norm_layer, norm_kwargs, drop_connect_rate) + self.blocks = nn.Sequential(*builder(in_chs, block_args)) + in_chs = builder.in_chs + + self.conv_head = select_conv2d(in_chs, num_features, 1, padding=pad_type) + self.bn2 = norm_layer(num_features, **norm_kwargs) + self.act2 = act_layer(inplace=True) + self.global_pool = nn.AdaptiveAvgPool2d(1) + self.classifier = nn.Linear(num_features, num_classes) + + for n, m in self.named_modules(): + if weight_init == 'goog': + initialize_weight_goog(m, n) + else: + initialize_weight_default(m, n) + + def features(self, x): + x = self.conv_stem(x) + x = self.bn1(x) + x = self.act1(x) + x = self.blocks(x) + x = self.conv_head(x) + x = self.bn2(x) + x = self.act2(x) + return x + + def as_sequential(self): + layers = [self.conv_stem, self.bn1, self.act1] + layers.extend(self.blocks) + layers.extend([ + self.conv_head, self.bn2, self.act2, + self.global_pool, nn.Flatten(), nn.Dropout(self.drop_rate), self.classifier]) + return nn.Sequential(*layers) + + def forward(self, x): + x = self.features(x) + x = self.global_pool(x) + x = x.flatten(1) + if self.drop_rate > 0.: + x = F.dropout(x, p=self.drop_rate, training=self.training) + return self.classifier(x) + + +def _create_model(model_kwargs, variant, pretrained=False): + as_sequential = model_kwargs.pop('as_sequential', False) + model = GenEfficientNet(**model_kwargs) + if pretrained: + load_pretrained(model, model_urls[variant]) + if as_sequential: + model = model.as_sequential() + return model + + +def _gen_mnasnet_a1(variant, channel_multiplier=1.0, pretrained=False, **kwargs): + """Creates a mnasnet-a1 model. + + Ref impl: https://github.com/tensorflow/tpu/tree/master/models/official/mnasnet + Paper: https://arxiv.org/pdf/1807.11626.pdf. + + Args: + channel_multiplier: multiplier to number of channels per layer. + """ + arch_def = [ + # stage 0, 112x112 in + ['ds_r1_k3_s1_e1_c16_noskip'], + # stage 1, 112x112 in + ['ir_r2_k3_s2_e6_c24'], + # stage 2, 56x56 in + ['ir_r3_k5_s2_e3_c40_se0.25'], + # stage 3, 28x28 in + ['ir_r4_k3_s2_e6_c80'], + # stage 4, 14x14in + ['ir_r2_k3_s1_e6_c112_se0.25'], + # stage 5, 14x14in + ['ir_r3_k5_s2_e6_c160_se0.25'], + # stage 6, 7x7 in + ['ir_r1_k3_s1_e6_c320'], + ] + with layer_config_kwargs(kwargs): + model_kwargs = dict( + block_args=decode_arch_def(arch_def), + stem_size=32, + channel_multiplier=channel_multiplier, + act_layer=resolve_act_layer(kwargs, 'relu'), + norm_kwargs=resolve_bn_args(kwargs), + **kwargs + ) + model = _create_model(model_kwargs, variant, pretrained) + return model + + +def _gen_mnasnet_b1(variant, channel_multiplier=1.0, pretrained=False, **kwargs): + """Creates a mnasnet-b1 model. + + Ref impl: https://github.com/tensorflow/tpu/tree/master/models/official/mnasnet + Paper: https://arxiv.org/pdf/1807.11626.pdf. + + Args: + channel_multiplier: multiplier to number of channels per layer. + """ + arch_def = [ + # stage 0, 112x112 in + ['ds_r1_k3_s1_c16_noskip'], + # stage 1, 112x112 in + ['ir_r3_k3_s2_e3_c24'], + # stage 2, 56x56 in + ['ir_r3_k5_s2_e3_c40'], + # stage 3, 28x28 in + ['ir_r3_k5_s2_e6_c80'], + # stage 4, 14x14in + ['ir_r2_k3_s1_e6_c96'], + # stage 5, 14x14in + ['ir_r4_k5_s2_e6_c192'], + # stage 6, 7x7 in + ['ir_r1_k3_s1_e6_c320_noskip'] + ] + with layer_config_kwargs(kwargs): + model_kwargs = dict( + block_args=decode_arch_def(arch_def), + stem_size=32, + channel_multiplier=channel_multiplier, + act_layer=resolve_act_layer(kwargs, 'relu'), + norm_kwargs=resolve_bn_args(kwargs), + **kwargs + ) + model = _create_model(model_kwargs, variant, pretrained) + return model + + +def _gen_mnasnet_small(variant, channel_multiplier=1.0, pretrained=False, **kwargs): + """Creates a mnasnet-b1 model. + + Ref impl: https://github.com/tensorflow/tpu/tree/master/models/official/mnasnet + Paper: https://arxiv.org/pdf/1807.11626.pdf. + + Args: + channel_multiplier: multiplier to number of channels per layer. + """ + arch_def = [ + ['ds_r1_k3_s1_c8'], + ['ir_r1_k3_s2_e3_c16'], + ['ir_r2_k3_s2_e6_c16'], + ['ir_r4_k5_s2_e6_c32_se0.25'], + ['ir_r3_k3_s1_e6_c32_se0.25'], + ['ir_r3_k5_s2_e6_c88_se0.25'], + ['ir_r1_k3_s1_e6_c144'] + ] + with layer_config_kwargs(kwargs): + model_kwargs = dict( + block_args=decode_arch_def(arch_def), + stem_size=8, + channel_multiplier=channel_multiplier, + act_layer=resolve_act_layer(kwargs, 'relu'), + norm_kwargs=resolve_bn_args(kwargs), + **kwargs + ) + model = _create_model(model_kwargs, variant, pretrained) + return model + + +def _gen_mobilenet_v2( + variant, channel_multiplier=1.0, depth_multiplier=1.0, fix_stem_head=False, pretrained=False, **kwargs): + """ Generate MobileNet-V2 network + Ref impl: https://github.com/tensorflow/models/blob/master/research/slim/nets/mobilenet/mobilenet_v2.py + Paper: https://arxiv.org/abs/1801.04381 + """ + arch_def = [ + ['ds_r1_k3_s1_c16'], + ['ir_r2_k3_s2_e6_c24'], + ['ir_r3_k3_s2_e6_c32'], + ['ir_r4_k3_s2_e6_c64'], + ['ir_r3_k3_s1_e6_c96'], + ['ir_r3_k3_s2_e6_c160'], + ['ir_r1_k3_s1_e6_c320'], + ] + with layer_config_kwargs(kwargs): + model_kwargs = dict( + block_args=decode_arch_def(arch_def, depth_multiplier=depth_multiplier, fix_first_last=fix_stem_head), + num_features=1280 if fix_stem_head else round_channels(1280, channel_multiplier, 8, None), + stem_size=32, + fix_stem=fix_stem_head, + channel_multiplier=channel_multiplier, + norm_kwargs=resolve_bn_args(kwargs), + act_layer=nn.ReLU6, + **kwargs + ) + model = _create_model(model_kwargs, variant, pretrained) + return model + + +def _gen_fbnetc(variant, channel_multiplier=1.0, pretrained=False, **kwargs): + """ FBNet-C + + Paper: https://arxiv.org/abs/1812.03443 + Ref Impl: https://github.com/facebookresearch/maskrcnn-benchmark/blob/master/maskrcnn_benchmark/modeling/backbone/fbnet_modeldef.py + + NOTE: the impl above does not relate to the 'C' variant here, that was derived from paper, + it was used to confirm some building block details + """ + arch_def = [ + ['ir_r1_k3_s1_e1_c16'], + ['ir_r1_k3_s2_e6_c24', 'ir_r2_k3_s1_e1_c24'], + ['ir_r1_k5_s2_e6_c32', 'ir_r1_k5_s1_e3_c32', 'ir_r1_k5_s1_e6_c32', 'ir_r1_k3_s1_e6_c32'], + ['ir_r1_k5_s2_e6_c64', 'ir_r1_k5_s1_e3_c64', 'ir_r2_k5_s1_e6_c64'], + ['ir_r3_k5_s1_e6_c112', 'ir_r1_k5_s1_e3_c112'], + ['ir_r4_k5_s2_e6_c184'], + ['ir_r1_k3_s1_e6_c352'], + ] + with layer_config_kwargs(kwargs): + model_kwargs = dict( + block_args=decode_arch_def(arch_def), + stem_size=16, + num_features=1984, # paper suggests this, but is not 100% clear + channel_multiplier=channel_multiplier, + act_layer=resolve_act_layer(kwargs, 'relu'), + norm_kwargs=resolve_bn_args(kwargs), + **kwargs + ) + model = _create_model(model_kwargs, variant, pretrained) + return model + + +def _gen_spnasnet(variant, channel_multiplier=1.0, pretrained=False, **kwargs): + """Creates the Single-Path NAS model from search targeted for Pixel1 phone. + + Paper: https://arxiv.org/abs/1904.02877 + + Args: + channel_multiplier: multiplier to number of channels per layer. + """ + arch_def = [ + # stage 0, 112x112 in + ['ds_r1_k3_s1_c16_noskip'], + # stage 1, 112x112 in + ['ir_r3_k3_s2_e3_c24'], + # stage 2, 56x56 in + ['ir_r1_k5_s2_e6_c40', 'ir_r3_k3_s1_e3_c40'], + # stage 3, 28x28 in + ['ir_r1_k5_s2_e6_c80', 'ir_r3_k3_s1_e3_c80'], + # stage 4, 14x14in + ['ir_r1_k5_s1_e6_c96', 'ir_r3_k5_s1_e3_c96'], + # stage 5, 14x14in + ['ir_r4_k5_s2_e6_c192'], + # stage 6, 7x7 in + ['ir_r1_k3_s1_e6_c320_noskip'] + ] + with layer_config_kwargs(kwargs): + model_kwargs = dict( + block_args=decode_arch_def(arch_def), + stem_size=32, + channel_multiplier=channel_multiplier, + act_layer=resolve_act_layer(kwargs, 'relu'), + norm_kwargs=resolve_bn_args(kwargs), + **kwargs + ) + model = _create_model(model_kwargs, variant, pretrained) + return model + + +def _gen_efficientnet(variant, channel_multiplier=1.0, depth_multiplier=1.0, pretrained=False, **kwargs): + """Creates an EfficientNet model. + + Ref impl: https://github.com/tensorflow/tpu/blob/master/models/official/efficientnet/efficientnet_model.py + Paper: https://arxiv.org/abs/1905.11946 + + EfficientNet params + name: (channel_multiplier, depth_multiplier, resolution, dropout_rate) + 'efficientnet-b0': (1.0, 1.0, 224, 0.2), + 'efficientnet-b1': (1.0, 1.1, 240, 0.2), + 'efficientnet-b2': (1.1, 1.2, 260, 0.3), + 'efficientnet-b3': (1.2, 1.4, 300, 0.3), + 'efficientnet-b4': (1.4, 1.8, 380, 0.4), + 'efficientnet-b5': (1.6, 2.2, 456, 0.4), + 'efficientnet-b6': (1.8, 2.6, 528, 0.5), + 'efficientnet-b7': (2.0, 3.1, 600, 0.5), + 'efficientnet-b8': (2.2, 3.6, 672, 0.5), + + Args: + channel_multiplier: multiplier to number of channels per layer + depth_multiplier: multiplier to number of repeats per stage + + """ + arch_def = [ + ['ds_r1_k3_s1_e1_c16_se0.25'], + ['ir_r2_k3_s2_e6_c24_se0.25'], + ['ir_r2_k5_s2_e6_c40_se0.25'], + ['ir_r3_k3_s2_e6_c80_se0.25'], + ['ir_r3_k5_s1_e6_c112_se0.25'], + ['ir_r4_k5_s2_e6_c192_se0.25'], + ['ir_r1_k3_s1_e6_c320_se0.25'], + ] + with layer_config_kwargs(kwargs): + model_kwargs = dict( + block_args=decode_arch_def(arch_def, depth_multiplier), + num_features=round_channels(1280, channel_multiplier, 8, None), + stem_size=32, + channel_multiplier=channel_multiplier, + act_layer=resolve_act_layer(kwargs, 'swish'), + norm_kwargs=resolve_bn_args(kwargs), + **kwargs, + ) + model = _create_model(model_kwargs, variant, pretrained) + return model + + +def _gen_efficientnet_edge(variant, channel_multiplier=1.0, depth_multiplier=1.0, pretrained=False, **kwargs): + arch_def = [ + # NOTE `fc` is present to override a mismatch between stem channels and in chs not + # present in other models + ['er_r1_k3_s1_e4_c24_fc24_noskip'], + ['er_r2_k3_s2_e8_c32'], + ['er_r4_k3_s2_e8_c48'], + ['ir_r5_k5_s2_e8_c96'], + ['ir_r4_k5_s1_e8_c144'], + ['ir_r2_k5_s2_e8_c192'], + ] + with layer_config_kwargs(kwargs): + model_kwargs = dict( + block_args=decode_arch_def(arch_def, depth_multiplier), + num_features=round_channels(1280, channel_multiplier, 8, None), + stem_size=32, + channel_multiplier=channel_multiplier, + act_layer=resolve_act_layer(kwargs, 'relu'), + norm_kwargs=resolve_bn_args(kwargs), + **kwargs, + ) + model = _create_model(model_kwargs, variant, pretrained) + return model + + +def _gen_efficientnet_condconv( + variant, channel_multiplier=1.0, depth_multiplier=1.0, experts_multiplier=1, pretrained=False, **kwargs): + """Creates an efficientnet-condconv model.""" + arch_def = [ + ['ds_r1_k3_s1_e1_c16_se0.25'], + ['ir_r2_k3_s2_e6_c24_se0.25'], + ['ir_r2_k5_s2_e6_c40_se0.25'], + ['ir_r3_k3_s2_e6_c80_se0.25'], + ['ir_r3_k5_s1_e6_c112_se0.25_cc4'], + ['ir_r4_k5_s2_e6_c192_se0.25_cc4'], + ['ir_r1_k3_s1_e6_c320_se0.25_cc4'], + ] + with layer_config_kwargs(kwargs): + model_kwargs = dict( + block_args=decode_arch_def(arch_def, depth_multiplier, experts_multiplier=experts_multiplier), + num_features=round_channels(1280, channel_multiplier, 8, None), + stem_size=32, + channel_multiplier=channel_multiplier, + act_layer=resolve_act_layer(kwargs, 'swish'), + norm_kwargs=resolve_bn_args(kwargs), + **kwargs, + ) + model = _create_model(model_kwargs, variant, pretrained) + return model + + +def _gen_efficientnet_lite(variant, channel_multiplier=1.0, depth_multiplier=1.0, pretrained=False, **kwargs): + """Creates an EfficientNet-Lite model. + + Ref impl: https://github.com/tensorflow/tpu/tree/master/models/official/efficientnet/lite + Paper: https://arxiv.org/abs/1905.11946 + + EfficientNet params + name: (channel_multiplier, depth_multiplier, resolution, dropout_rate) + 'efficientnet-lite0': (1.0, 1.0, 224, 0.2), + 'efficientnet-lite1': (1.0, 1.1, 240, 0.2), + 'efficientnet-lite2': (1.1, 1.2, 260, 0.3), + 'efficientnet-lite3': (1.2, 1.4, 280, 0.3), + 'efficientnet-lite4': (1.4, 1.8, 300, 0.3), + + Args: + channel_multiplier: multiplier to number of channels per layer + depth_multiplier: multiplier to number of repeats per stage + """ + arch_def = [ + ['ds_r1_k3_s1_e1_c16'], + ['ir_r2_k3_s2_e6_c24'], + ['ir_r2_k5_s2_e6_c40'], + ['ir_r3_k3_s2_e6_c80'], + ['ir_r3_k5_s1_e6_c112'], + ['ir_r4_k5_s2_e6_c192'], + ['ir_r1_k3_s1_e6_c320'], + ] + with layer_config_kwargs(kwargs): + model_kwargs = dict( + block_args=decode_arch_def(arch_def, depth_multiplier, fix_first_last=True), + num_features=1280, + stem_size=32, + fix_stem=True, + channel_multiplier=channel_multiplier, + act_layer=nn.ReLU6, + norm_kwargs=resolve_bn_args(kwargs), + **kwargs, + ) + model = _create_model(model_kwargs, variant, pretrained) + return model + + +def _gen_mixnet_s(variant, channel_multiplier=1.0, pretrained=False, **kwargs): + """Creates a MixNet Small model. + + Ref impl: https://github.com/tensorflow/tpu/tree/master/models/official/mnasnet/mixnet + Paper: https://arxiv.org/abs/1907.09595 + """ + arch_def = [ + # stage 0, 112x112 in + ['ds_r1_k3_s1_e1_c16'], # relu + # stage 1, 112x112 in + ['ir_r1_k3_a1.1_p1.1_s2_e6_c24', 'ir_r1_k3_a1.1_p1.1_s1_e3_c24'], # relu + # stage 2, 56x56 in + ['ir_r1_k3.5.7_s2_e6_c40_se0.5_nsw', 'ir_r3_k3.5_a1.1_p1.1_s1_e6_c40_se0.5_nsw'], # swish + # stage 3, 28x28 in + ['ir_r1_k3.5.7_p1.1_s2_e6_c80_se0.25_nsw', 'ir_r2_k3.5_p1.1_s1_e6_c80_se0.25_nsw'], # swish + # stage 4, 14x14in + ['ir_r1_k3.5.7_a1.1_p1.1_s1_e6_c120_se0.5_nsw', 'ir_r2_k3.5.7.9_a1.1_p1.1_s1_e3_c120_se0.5_nsw'], # swish + # stage 5, 14x14in + ['ir_r1_k3.5.7.9.11_s2_e6_c200_se0.5_nsw', 'ir_r2_k3.5.7.9_p1.1_s1_e6_c200_se0.5_nsw'], # swish + # 7x7 + ] + with layer_config_kwargs(kwargs): + model_kwargs = dict( + block_args=decode_arch_def(arch_def), + num_features=1536, + stem_size=16, + channel_multiplier=channel_multiplier, + act_layer=resolve_act_layer(kwargs, 'relu'), + norm_kwargs=resolve_bn_args(kwargs), + **kwargs + ) + model = _create_model(model_kwargs, variant, pretrained) + return model + + +def _gen_mixnet_m(variant, channel_multiplier=1.0, depth_multiplier=1.0, pretrained=False, **kwargs): + """Creates a MixNet Medium-Large model. + + Ref impl: https://github.com/tensorflow/tpu/tree/master/models/official/mnasnet/mixnet + Paper: https://arxiv.org/abs/1907.09595 + """ + arch_def = [ + # stage 0, 112x112 in + ['ds_r1_k3_s1_e1_c24'], # relu + # stage 1, 112x112 in + ['ir_r1_k3.5.7_a1.1_p1.1_s2_e6_c32', 'ir_r1_k3_a1.1_p1.1_s1_e3_c32'], # relu + # stage 2, 56x56 in + ['ir_r1_k3.5.7.9_s2_e6_c40_se0.5_nsw', 'ir_r3_k3.5_a1.1_p1.1_s1_e6_c40_se0.5_nsw'], # swish + # stage 3, 28x28 in + ['ir_r1_k3.5.7_s2_e6_c80_se0.25_nsw', 'ir_r3_k3.5.7.9_a1.1_p1.1_s1_e6_c80_se0.25_nsw'], # swish + # stage 4, 14x14in + ['ir_r1_k3_s1_e6_c120_se0.5_nsw', 'ir_r3_k3.5.7.9_a1.1_p1.1_s1_e3_c120_se0.5_nsw'], # swish + # stage 5, 14x14in + ['ir_r1_k3.5.7.9_s2_e6_c200_se0.5_nsw', 'ir_r3_k3.5.7.9_p1.1_s1_e6_c200_se0.5_nsw'], # swish + # 7x7 + ] + with layer_config_kwargs(kwargs): + model_kwargs = dict( + block_args=decode_arch_def(arch_def, depth_multiplier, depth_trunc='round'), + num_features=1536, + stem_size=24, + channel_multiplier=channel_multiplier, + act_layer=resolve_act_layer(kwargs, 'relu'), + norm_kwargs=resolve_bn_args(kwargs), + **kwargs + ) + model = _create_model(model_kwargs, variant, pretrained) + return model + + +def mnasnet_050(pretrained=False, **kwargs): + """ MNASNet B1, depth multiplier of 0.5. """ + model = _gen_mnasnet_b1('mnasnet_050', 0.5, pretrained=pretrained, **kwargs) + return model + + +def mnasnet_075(pretrained=False, **kwargs): + """ MNASNet B1, depth multiplier of 0.75. """ + model = _gen_mnasnet_b1('mnasnet_075', 0.75, pretrained=pretrained, **kwargs) + return model + + +def mnasnet_100(pretrained=False, **kwargs): + """ MNASNet B1, depth multiplier of 1.0. """ + model = _gen_mnasnet_b1('mnasnet_100', 1.0, pretrained=pretrained, **kwargs) + return model + + +def mnasnet_b1(pretrained=False, **kwargs): + """ MNASNet B1, depth multiplier of 1.0. """ + return mnasnet_100(pretrained, **kwargs) + + +def mnasnet_140(pretrained=False, **kwargs): + """ MNASNet B1, depth multiplier of 1.4 """ + model = _gen_mnasnet_b1('mnasnet_140', 1.4, pretrained=pretrained, **kwargs) + return model + + +def semnasnet_050(pretrained=False, **kwargs): + """ MNASNet A1 (w/ SE), depth multiplier of 0.5 """ + model = _gen_mnasnet_a1('semnasnet_050', 0.5, pretrained=pretrained, **kwargs) + return model + + +def semnasnet_075(pretrained=False, **kwargs): + """ MNASNet A1 (w/ SE), depth multiplier of 0.75. """ + model = _gen_mnasnet_a1('semnasnet_075', 0.75, pretrained=pretrained, **kwargs) + return model + + +def semnasnet_100(pretrained=False, **kwargs): + """ MNASNet A1 (w/ SE), depth multiplier of 1.0. """ + model = _gen_mnasnet_a1('semnasnet_100', 1.0, pretrained=pretrained, **kwargs) + return model + + +def mnasnet_a1(pretrained=False, **kwargs): + """ MNASNet A1 (w/ SE), depth multiplier of 1.0. """ + return semnasnet_100(pretrained, **kwargs) + + +def semnasnet_140(pretrained=False, **kwargs): + """ MNASNet A1 (w/ SE), depth multiplier of 1.4. """ + model = _gen_mnasnet_a1('semnasnet_140', 1.4, pretrained=pretrained, **kwargs) + return model + + +def mnasnet_small(pretrained=False, **kwargs): + """ MNASNet Small, depth multiplier of 1.0. """ + model = _gen_mnasnet_small('mnasnet_small', 1.0, pretrained=pretrained, **kwargs) + return model + + +def mobilenetv2_100(pretrained=False, **kwargs): + """ MobileNet V2 w/ 1.0 channel multiplier """ + model = _gen_mobilenet_v2('mobilenetv2_100', 1.0, pretrained=pretrained, **kwargs) + return model + + +def mobilenetv2_140(pretrained=False, **kwargs): + """ MobileNet V2 w/ 1.4 channel multiplier """ + model = _gen_mobilenet_v2('mobilenetv2_140', 1.4, pretrained=pretrained, **kwargs) + return model + + +def mobilenetv2_110d(pretrained=False, **kwargs): + """ MobileNet V2 w/ 1.1 channel, 1.2 depth multipliers""" + model = _gen_mobilenet_v2( + 'mobilenetv2_110d', 1.1, depth_multiplier=1.2, fix_stem_head=True, pretrained=pretrained, **kwargs) + return model + + +def mobilenetv2_120d(pretrained=False, **kwargs): + """ MobileNet V2 w/ 1.2 channel, 1.4 depth multipliers """ + model = _gen_mobilenet_v2( + 'mobilenetv2_120d', 1.2, depth_multiplier=1.4, fix_stem_head=True, pretrained=pretrained, **kwargs) + return model + + +def fbnetc_100(pretrained=False, **kwargs): + """ FBNet-C """ + if pretrained: + # pretrained model trained with non-default BN epsilon + kwargs['bn_eps'] = BN_EPS_TF_DEFAULT + model = _gen_fbnetc('fbnetc_100', 1.0, pretrained=pretrained, **kwargs) + return model + + +def spnasnet_100(pretrained=False, **kwargs): + """ Single-Path NAS Pixel1""" + model = _gen_spnasnet('spnasnet_100', 1.0, pretrained=pretrained, **kwargs) + return model + + +def efficientnet_b0(pretrained=False, **kwargs): + """ EfficientNet-B0 """ + # NOTE for train set drop_rate=0.2, drop_connect_rate=0.2 + model = _gen_efficientnet( + 'efficientnet_b0', channel_multiplier=1.0, depth_multiplier=1.0, pretrained=pretrained, **kwargs) + return model + + +def efficientnet_b1(pretrained=False, **kwargs): + """ EfficientNet-B1 """ + # NOTE for train set drop_rate=0.2, drop_connect_rate=0.2 + model = _gen_efficientnet( + 'efficientnet_b1', channel_multiplier=1.0, depth_multiplier=1.1, pretrained=pretrained, **kwargs) + return model + + +def efficientnet_b2(pretrained=False, **kwargs): + """ EfficientNet-B2 """ + # NOTE for train set drop_rate=0.3, drop_connect_rate=0.2 + model = _gen_efficientnet( + 'efficientnet_b2', channel_multiplier=1.1, depth_multiplier=1.2, pretrained=pretrained, **kwargs) + return model + + +def efficientnet_b3(pretrained=False, **kwargs): + """ EfficientNet-B3 """ + # NOTE for train set drop_rate=0.3, drop_connect_rate=0.2 + model = _gen_efficientnet( + 'efficientnet_b3', channel_multiplier=1.2, depth_multiplier=1.4, pretrained=pretrained, **kwargs) + return model + + +def efficientnet_b4(pretrained=False, **kwargs): + """ EfficientNet-B4 """ + # NOTE for train set drop_rate=0.4, drop_connect_rate=0.2 + model = _gen_efficientnet( + 'efficientnet_b4', channel_multiplier=1.4, depth_multiplier=1.8, pretrained=pretrained, **kwargs) + return model + + +def efficientnet_b5(pretrained=False, **kwargs): + """ EfficientNet-B5 """ + # NOTE for train set drop_rate=0.4, drop_connect_rate=0.2 + model = _gen_efficientnet( + 'efficientnet_b5', channel_multiplier=1.6, depth_multiplier=2.2, pretrained=pretrained, **kwargs) + return model + + +def efficientnet_b6(pretrained=False, **kwargs): + """ EfficientNet-B6 """ + # NOTE for train set drop_rate=0.5, drop_connect_rate=0.2 + model = _gen_efficientnet( + 'efficientnet_b6', channel_multiplier=1.8, depth_multiplier=2.6, pretrained=pretrained, **kwargs) + return model + + +def efficientnet_b7(pretrained=False, **kwargs): + """ EfficientNet-B7 """ + # NOTE for train set drop_rate=0.5, drop_connect_rate=0.2 + model = _gen_efficientnet( + 'efficientnet_b7', channel_multiplier=2.0, depth_multiplier=3.1, pretrained=pretrained, **kwargs) + return model + + +def efficientnet_b8(pretrained=False, **kwargs): + """ EfficientNet-B8 """ + # NOTE for train set drop_rate=0.5, drop_connect_rate=0.2 + model = _gen_efficientnet( + 'efficientnet_b8', channel_multiplier=2.2, depth_multiplier=3.6, pretrained=pretrained, **kwargs) + return model + + +def efficientnet_l2(pretrained=False, **kwargs): + """ EfficientNet-L2. """ + # NOTE for train, drop_rate should be 0.5 + model = _gen_efficientnet( + 'efficientnet_l2', channel_multiplier=4.3, depth_multiplier=5.3, pretrained=pretrained, **kwargs) + return model + + +def efficientnet_es(pretrained=False, **kwargs): + """ EfficientNet-Edge Small. """ + model = _gen_efficientnet_edge( + 'efficientnet_es', channel_multiplier=1.0, depth_multiplier=1.0, pretrained=pretrained, **kwargs) + return model + + +def efficientnet_em(pretrained=False, **kwargs): + """ EfficientNet-Edge-Medium. """ + model = _gen_efficientnet_edge( + 'efficientnet_em', channel_multiplier=1.0, depth_multiplier=1.1, pretrained=pretrained, **kwargs) + return model + + +def efficientnet_el(pretrained=False, **kwargs): + """ EfficientNet-Edge-Large. """ + model = _gen_efficientnet_edge( + 'efficientnet_el', channel_multiplier=1.2, depth_multiplier=1.4, pretrained=pretrained, **kwargs) + return model + + +def efficientnet_cc_b0_4e(pretrained=False, **kwargs): + """ EfficientNet-CondConv-B0 w/ 8 Experts """ + # NOTE for train set drop_rate=0.25, drop_connect_rate=0.2 + model = _gen_efficientnet_condconv( + 'efficientnet_cc_b0_4e', channel_multiplier=1.0, depth_multiplier=1.0, pretrained=pretrained, **kwargs) + return model + + +def efficientnet_cc_b0_8e(pretrained=False, **kwargs): + """ EfficientNet-CondConv-B0 w/ 8 Experts """ + # NOTE for train set drop_rate=0.25, drop_connect_rate=0.2 + model = _gen_efficientnet_condconv( + 'efficientnet_cc_b0_8e', channel_multiplier=1.0, depth_multiplier=1.0, experts_multiplier=2, + pretrained=pretrained, **kwargs) + return model + + +def efficientnet_cc_b1_8e(pretrained=False, **kwargs): + """ EfficientNet-CondConv-B1 w/ 8 Experts """ + # NOTE for train set drop_rate=0.25, drop_connect_rate=0.2 + model = _gen_efficientnet_condconv( + 'efficientnet_cc_b1_8e', channel_multiplier=1.0, depth_multiplier=1.1, experts_multiplier=2, + pretrained=pretrained, **kwargs) + return model + + +def efficientnet_lite0(pretrained=False, **kwargs): + """ EfficientNet-Lite0 """ + model = _gen_efficientnet_lite( + 'efficientnet_lite0', channel_multiplier=1.0, depth_multiplier=1.0, pretrained=pretrained, **kwargs) + return model + + +def efficientnet_lite1(pretrained=False, **kwargs): + """ EfficientNet-Lite1 """ + model = _gen_efficientnet_lite( + 'efficientnet_lite1', channel_multiplier=1.0, depth_multiplier=1.1, pretrained=pretrained, **kwargs) + return model + + +def efficientnet_lite2(pretrained=False, **kwargs): + """ EfficientNet-Lite2 """ + model = _gen_efficientnet_lite( + 'efficientnet_lite2', channel_multiplier=1.1, depth_multiplier=1.2, pretrained=pretrained, **kwargs) + return model + + +def efficientnet_lite3(pretrained=False, **kwargs): + """ EfficientNet-Lite3 """ + model = _gen_efficientnet_lite( + 'efficientnet_lite3', channel_multiplier=1.2, depth_multiplier=1.4, pretrained=pretrained, **kwargs) + return model + + +def efficientnet_lite4(pretrained=False, **kwargs): + """ EfficientNet-Lite4 """ + model = _gen_efficientnet_lite( + 'efficientnet_lite4', channel_multiplier=1.4, depth_multiplier=1.8, pretrained=pretrained, **kwargs) + return model + + +def tf_efficientnet_b0(pretrained=False, **kwargs): + """ EfficientNet-B0 AutoAug. Tensorflow compatible variant """ + kwargs['bn_eps'] = BN_EPS_TF_DEFAULT + kwargs['pad_type'] = 'same' + model = _gen_efficientnet( + 'tf_efficientnet_b0', channel_multiplier=1.0, depth_multiplier=1.0, pretrained=pretrained, **kwargs) + return model + + +def tf_efficientnet_b1(pretrained=False, **kwargs): + """ EfficientNet-B1 AutoAug. Tensorflow compatible variant """ + kwargs['bn_eps'] = BN_EPS_TF_DEFAULT + kwargs['pad_type'] = 'same' + model = _gen_efficientnet( + 'tf_efficientnet_b1', channel_multiplier=1.0, depth_multiplier=1.1, pretrained=pretrained, **kwargs) + return model + + +def tf_efficientnet_b2(pretrained=False, **kwargs): + """ EfficientNet-B2 AutoAug. Tensorflow compatible variant """ + kwargs['bn_eps'] = BN_EPS_TF_DEFAULT + kwargs['pad_type'] = 'same' + model = _gen_efficientnet( + 'tf_efficientnet_b2', channel_multiplier=1.1, depth_multiplier=1.2, pretrained=pretrained, **kwargs) + return model + + +def tf_efficientnet_b3(pretrained=False, **kwargs): + """ EfficientNet-B3 AutoAug. Tensorflow compatible variant """ + kwargs['bn_eps'] = BN_EPS_TF_DEFAULT + kwargs['pad_type'] = 'same' + model = _gen_efficientnet( + 'tf_efficientnet_b3', channel_multiplier=1.2, depth_multiplier=1.4, pretrained=pretrained, **kwargs) + return model + + +def tf_efficientnet_b4(pretrained=False, **kwargs): + """ EfficientNet-B4 AutoAug. Tensorflow compatible variant """ + kwargs['bn_eps'] = BN_EPS_TF_DEFAULT + kwargs['pad_type'] = 'same' + model = _gen_efficientnet( + 'tf_efficientnet_b4', channel_multiplier=1.4, depth_multiplier=1.8, pretrained=pretrained, **kwargs) + return model + + +def tf_efficientnet_b5(pretrained=False, **kwargs): + """ EfficientNet-B5 RandAug. Tensorflow compatible variant """ + kwargs['bn_eps'] = BN_EPS_TF_DEFAULT + kwargs['pad_type'] = 'same' + model = _gen_efficientnet( + 'tf_efficientnet_b5', channel_multiplier=1.6, depth_multiplier=2.2, pretrained=pretrained, **kwargs) + return model + + +def tf_efficientnet_b6(pretrained=False, **kwargs): + """ EfficientNet-B6 AutoAug. Tensorflow compatible variant """ + kwargs['bn_eps'] = BN_EPS_TF_DEFAULT + kwargs['pad_type'] = 'same' + model = _gen_efficientnet( + 'tf_efficientnet_b6', channel_multiplier=1.8, depth_multiplier=2.6, pretrained=pretrained, **kwargs) + return model + + +def tf_efficientnet_b7(pretrained=False, **kwargs): + """ EfficientNet-B7 RandAug. Tensorflow compatible variant """ + kwargs['bn_eps'] = BN_EPS_TF_DEFAULT + kwargs['pad_type'] = 'same' + model = _gen_efficientnet( + 'tf_efficientnet_b7', channel_multiplier=2.0, depth_multiplier=3.1, pretrained=pretrained, **kwargs) + return model + + +def tf_efficientnet_b8(pretrained=False, **kwargs): + """ EfficientNet-B8 RandAug. Tensorflow compatible variant """ + kwargs['bn_eps'] = BN_EPS_TF_DEFAULT + kwargs['pad_type'] = 'same' + model = _gen_efficientnet( + 'tf_efficientnet_b8', channel_multiplier=2.2, depth_multiplier=3.6, pretrained=pretrained, **kwargs) + return model + + +def tf_efficientnet_b0_ap(pretrained=False, **kwargs): + """ EfficientNet-B0 AdvProp. Tensorflow compatible variant + Paper: Adversarial Examples Improve Image Recognition (https://arxiv.org/abs/1911.09665) + """ + kwargs['bn_eps'] = BN_EPS_TF_DEFAULT + kwargs['pad_type'] = 'same' + model = _gen_efficientnet( + 'tf_efficientnet_b0_ap', channel_multiplier=1.0, depth_multiplier=1.0, pretrained=pretrained, **kwargs) + return model + + +def tf_efficientnet_b1_ap(pretrained=False, **kwargs): + """ EfficientNet-B1 AdvProp. Tensorflow compatible variant + Paper: Adversarial Examples Improve Image Recognition (https://arxiv.org/abs/1911.09665) + """ + kwargs['bn_eps'] = BN_EPS_TF_DEFAULT + kwargs['pad_type'] = 'same' + model = _gen_efficientnet( + 'tf_efficientnet_b1_ap', channel_multiplier=1.0, depth_multiplier=1.1, pretrained=pretrained, **kwargs) + return model + + +def tf_efficientnet_b2_ap(pretrained=False, **kwargs): + """ EfficientNet-B2 AdvProp. Tensorflow compatible variant + Paper: Adversarial Examples Improve Image Recognition (https://arxiv.org/abs/1911.09665) + """ + kwargs['bn_eps'] = BN_EPS_TF_DEFAULT + kwargs['pad_type'] = 'same' + model = _gen_efficientnet( + 'tf_efficientnet_b2_ap', channel_multiplier=1.1, depth_multiplier=1.2, pretrained=pretrained, **kwargs) + return model + + +def tf_efficientnet_b3_ap(pretrained=False, **kwargs): + """ EfficientNet-B3 AdvProp. Tensorflow compatible variant + Paper: Adversarial Examples Improve Image Recognition (https://arxiv.org/abs/1911.09665) + """ + kwargs['bn_eps'] = BN_EPS_TF_DEFAULT + kwargs['pad_type'] = 'same' + model = _gen_efficientnet( + 'tf_efficientnet_b3_ap', channel_multiplier=1.2, depth_multiplier=1.4, pretrained=pretrained, **kwargs) + return model + + +def tf_efficientnet_b4_ap(pretrained=False, **kwargs): + """ EfficientNet-B4 AdvProp. Tensorflow compatible variant + Paper: Adversarial Examples Improve Image Recognition (https://arxiv.org/abs/1911.09665) + """ + kwargs['bn_eps'] = BN_EPS_TF_DEFAULT + kwargs['pad_type'] = 'same' + model = _gen_efficientnet( + 'tf_efficientnet_b4_ap', channel_multiplier=1.4, depth_multiplier=1.8, pretrained=pretrained, **kwargs) + return model + + +def tf_efficientnet_b5_ap(pretrained=False, **kwargs): + """ EfficientNet-B5 AdvProp. Tensorflow compatible variant + Paper: Adversarial Examples Improve Image Recognition (https://arxiv.org/abs/1911.09665) + """ + kwargs['bn_eps'] = BN_EPS_TF_DEFAULT + kwargs['pad_type'] = 'same' + model = _gen_efficientnet( + 'tf_efficientnet_b5_ap', channel_multiplier=1.6, depth_multiplier=2.2, pretrained=pretrained, **kwargs) + return model + + +def tf_efficientnet_b6_ap(pretrained=False, **kwargs): + """ EfficientNet-B6 AdvProp. Tensorflow compatible variant + Paper: Adversarial Examples Improve Image Recognition (https://arxiv.org/abs/1911.09665) + """ + # NOTE for train, drop_rate should be 0.5 + kwargs['bn_eps'] = BN_EPS_TF_DEFAULT + kwargs['pad_type'] = 'same' + model = _gen_efficientnet( + 'tf_efficientnet_b6_ap', channel_multiplier=1.8, depth_multiplier=2.6, pretrained=pretrained, **kwargs) + return model + + +def tf_efficientnet_b7_ap(pretrained=False, **kwargs): + """ EfficientNet-B7 AdvProp. Tensorflow compatible variant + Paper: Adversarial Examples Improve Image Recognition (https://arxiv.org/abs/1911.09665) + """ + # NOTE for train, drop_rate should be 0.5 + kwargs['bn_eps'] = BN_EPS_TF_DEFAULT + kwargs['pad_type'] = 'same' + model = _gen_efficientnet( + 'tf_efficientnet_b7_ap', channel_multiplier=2.0, depth_multiplier=3.1, pretrained=pretrained, **kwargs) + return model + + +def tf_efficientnet_b8_ap(pretrained=False, **kwargs): + """ EfficientNet-B8 AdvProp. Tensorflow compatible variant + Paper: Adversarial Examples Improve Image Recognition (https://arxiv.org/abs/1911.09665) + """ + # NOTE for train, drop_rate should be 0.5 + kwargs['bn_eps'] = BN_EPS_TF_DEFAULT + kwargs['pad_type'] = 'same' + model = _gen_efficientnet( + 'tf_efficientnet_b8_ap', channel_multiplier=2.2, depth_multiplier=3.6, pretrained=pretrained, **kwargs) + return model + + +def tf_efficientnet_b0_ns(pretrained=False, **kwargs): + """ EfficientNet-B0 NoisyStudent. Tensorflow compatible variant + Paper: Self-training with Noisy Student improves ImageNet classification (https://arxiv.org/abs/1911.04252) + """ + kwargs['bn_eps'] = BN_EPS_TF_DEFAULT + kwargs['pad_type'] = 'same' + model = _gen_efficientnet( + 'tf_efficientnet_b0_ns', channel_multiplier=1.0, depth_multiplier=1.0, pretrained=pretrained, **kwargs) + return model + + +def tf_efficientnet_b1_ns(pretrained=False, **kwargs): + """ EfficientNet-B1 NoisyStudent. Tensorflow compatible variant + Paper: Self-training with Noisy Student improves ImageNet classification (https://arxiv.org/abs/1911.04252) + """ + kwargs['bn_eps'] = BN_EPS_TF_DEFAULT + kwargs['pad_type'] = 'same' + model = _gen_efficientnet( + 'tf_efficientnet_b1_ns', channel_multiplier=1.0, depth_multiplier=1.1, pretrained=pretrained, **kwargs) + return model + + +def tf_efficientnet_b2_ns(pretrained=False, **kwargs): + """ EfficientNet-B2 NoisyStudent. Tensorflow compatible variant + Paper: Self-training with Noisy Student improves ImageNet classification (https://arxiv.org/abs/1911.04252) + """ + kwargs['bn_eps'] = BN_EPS_TF_DEFAULT + kwargs['pad_type'] = 'same' + model = _gen_efficientnet( + 'tf_efficientnet_b2_ns', channel_multiplier=1.1, depth_multiplier=1.2, pretrained=pretrained, **kwargs) + return model + + +def tf_efficientnet_b3_ns(pretrained=False, **kwargs): + """ EfficientNet-B3 NoisyStudent. Tensorflow compatible variant + Paper: Self-training with Noisy Student improves ImageNet classification (https://arxiv.org/abs/1911.04252) + """ + kwargs['bn_eps'] = BN_EPS_TF_DEFAULT + kwargs['pad_type'] = 'same' + model = _gen_efficientnet( + 'tf_efficientnet_b3_ns', channel_multiplier=1.2, depth_multiplier=1.4, pretrained=pretrained, **kwargs) + return model + + +def tf_efficientnet_b4_ns(pretrained=False, **kwargs): + """ EfficientNet-B4 NoisyStudent. Tensorflow compatible variant + Paper: Self-training with Noisy Student improves ImageNet classification (https://arxiv.org/abs/1911.04252) + """ + kwargs['bn_eps'] = BN_EPS_TF_DEFAULT + kwargs['pad_type'] = 'same' + model = _gen_efficientnet( + 'tf_efficientnet_b4_ns', channel_multiplier=1.4, depth_multiplier=1.8, pretrained=pretrained, **kwargs) + return model + + +def tf_efficientnet_b5_ns(pretrained=False, **kwargs): + """ EfficientNet-B5 NoisyStudent. Tensorflow compatible variant + Paper: Self-training with Noisy Student improves ImageNet classification (https://arxiv.org/abs/1911.04252) + """ + kwargs['bn_eps'] = BN_EPS_TF_DEFAULT + kwargs['pad_type'] = 'same' + model = _gen_efficientnet( + 'tf_efficientnet_b5_ns', channel_multiplier=1.6, depth_multiplier=2.2, pretrained=pretrained, **kwargs) + return model + + +def tf_efficientnet_b6_ns(pretrained=False, **kwargs): + """ EfficientNet-B6 NoisyStudent. Tensorflow compatible variant + Paper: Self-training with Noisy Student improves ImageNet classification (https://arxiv.org/abs/1911.04252) + """ + # NOTE for train, drop_rate should be 0.5 + kwargs['bn_eps'] = BN_EPS_TF_DEFAULT + kwargs['pad_type'] = 'same' + model = _gen_efficientnet( + 'tf_efficientnet_b6_ns', channel_multiplier=1.8, depth_multiplier=2.6, pretrained=pretrained, **kwargs) + return model + + +def tf_efficientnet_b7_ns(pretrained=False, **kwargs): + """ EfficientNet-B7 NoisyStudent. Tensorflow compatible variant + Paper: Self-training with Noisy Student improves ImageNet classification (https://arxiv.org/abs/1911.04252) + """ + # NOTE for train, drop_rate should be 0.5 + kwargs['bn_eps'] = BN_EPS_TF_DEFAULT + kwargs['pad_type'] = 'same' + model = _gen_efficientnet( + 'tf_efficientnet_b7_ns', channel_multiplier=2.0, depth_multiplier=3.1, pretrained=pretrained, **kwargs) + return model + + +def tf_efficientnet_l2_ns_475(pretrained=False, **kwargs): + """ EfficientNet-L2 NoisyStudent @ 475x475. Tensorflow compatible variant + Paper: Self-training with Noisy Student improves ImageNet classification (https://arxiv.org/abs/1911.04252) + """ + # NOTE for train, drop_rate should be 0.5 + kwargs['bn_eps'] = BN_EPS_TF_DEFAULT + kwargs['pad_type'] = 'same' + model = _gen_efficientnet( + 'tf_efficientnet_l2_ns_475', channel_multiplier=4.3, depth_multiplier=5.3, pretrained=pretrained, **kwargs) + return model + + +def tf_efficientnet_l2_ns(pretrained=False, **kwargs): + """ EfficientNet-L2 NoisyStudent. Tensorflow compatible variant + Paper: Self-training with Noisy Student improves ImageNet classification (https://arxiv.org/abs/1911.04252) + """ + # NOTE for train, drop_rate should be 0.5 + kwargs['bn_eps'] = BN_EPS_TF_DEFAULT + kwargs['pad_type'] = 'same' + model = _gen_efficientnet( + 'tf_efficientnet_l2_ns', channel_multiplier=4.3, depth_multiplier=5.3, pretrained=pretrained, **kwargs) + return model + + +def tf_efficientnet_es(pretrained=False, **kwargs): + """ EfficientNet-Edge Small. Tensorflow compatible variant """ + kwargs['bn_eps'] = BN_EPS_TF_DEFAULT + kwargs['pad_type'] = 'same' + model = _gen_efficientnet_edge( + 'tf_efficientnet_es', channel_multiplier=1.0, depth_multiplier=1.0, pretrained=pretrained, **kwargs) + return model + + +def tf_efficientnet_em(pretrained=False, **kwargs): + """ EfficientNet-Edge-Medium. Tensorflow compatible variant """ + kwargs['bn_eps'] = BN_EPS_TF_DEFAULT + kwargs['pad_type'] = 'same' + model = _gen_efficientnet_edge( + 'tf_efficientnet_em', channel_multiplier=1.0, depth_multiplier=1.1, pretrained=pretrained, **kwargs) + return model + + +def tf_efficientnet_el(pretrained=False, **kwargs): + """ EfficientNet-Edge-Large. Tensorflow compatible variant """ + kwargs['bn_eps'] = BN_EPS_TF_DEFAULT + kwargs['pad_type'] = 'same' + model = _gen_efficientnet_edge( + 'tf_efficientnet_el', channel_multiplier=1.2, depth_multiplier=1.4, pretrained=pretrained, **kwargs) + return model + + +def tf_efficientnet_cc_b0_4e(pretrained=False, **kwargs): + """ EfficientNet-CondConv-B0 w/ 4 Experts """ + kwargs['bn_eps'] = BN_EPS_TF_DEFAULT + kwargs['pad_type'] = 'same' + model = _gen_efficientnet_condconv( + 'tf_efficientnet_cc_b0_4e', channel_multiplier=1.0, depth_multiplier=1.0, pretrained=pretrained, **kwargs) + return model + + +def tf_efficientnet_cc_b0_8e(pretrained=False, **kwargs): + """ EfficientNet-CondConv-B0 w/ 8 Experts """ + kwargs['bn_eps'] = BN_EPS_TF_DEFAULT + kwargs['pad_type'] = 'same' + model = _gen_efficientnet_condconv( + 'tf_efficientnet_cc_b0_8e', channel_multiplier=1.0, depth_multiplier=1.0, experts_multiplier=2, + pretrained=pretrained, **kwargs) + return model + + +def tf_efficientnet_cc_b1_8e(pretrained=False, **kwargs): + """ EfficientNet-CondConv-B1 w/ 8 Experts """ + kwargs['bn_eps'] = BN_EPS_TF_DEFAULT + kwargs['pad_type'] = 'same' + model = _gen_efficientnet_condconv( + 'tf_efficientnet_cc_b1_8e', channel_multiplier=1.0, depth_multiplier=1.1, experts_multiplier=2, + pretrained=pretrained, **kwargs) + return model + + +def tf_efficientnet_lite0(pretrained=False, **kwargs): + """ EfficientNet-Lite0. Tensorflow compatible variant """ + kwargs['bn_eps'] = BN_EPS_TF_DEFAULT + kwargs['pad_type'] = 'same' + model = _gen_efficientnet_lite( + 'tf_efficientnet_lite0', channel_multiplier=1.0, depth_multiplier=1.0, pretrained=pretrained, **kwargs) + return model + + +def tf_efficientnet_lite1(pretrained=False, **kwargs): + """ EfficientNet-Lite1. Tensorflow compatible variant """ + kwargs['bn_eps'] = BN_EPS_TF_DEFAULT + kwargs['pad_type'] = 'same' + model = _gen_efficientnet_lite( + 'tf_efficientnet_lite1', channel_multiplier=1.0, depth_multiplier=1.1, pretrained=pretrained, **kwargs) + return model + + +def tf_efficientnet_lite2(pretrained=False, **kwargs): + """ EfficientNet-Lite2. Tensorflow compatible variant """ + kwargs['bn_eps'] = BN_EPS_TF_DEFAULT + kwargs['pad_type'] = 'same' + model = _gen_efficientnet_lite( + 'tf_efficientnet_lite2', channel_multiplier=1.1, depth_multiplier=1.2, pretrained=pretrained, **kwargs) + return model + + +def tf_efficientnet_lite3(pretrained=False, **kwargs): + """ EfficientNet-Lite3. Tensorflow compatible variant """ + kwargs['bn_eps'] = BN_EPS_TF_DEFAULT + kwargs['pad_type'] = 'same' + model = _gen_efficientnet_lite( + 'tf_efficientnet_lite3', channel_multiplier=1.2, depth_multiplier=1.4, pretrained=pretrained, **kwargs) + return model + + +def tf_efficientnet_lite4(pretrained=False, **kwargs): + """ EfficientNet-Lite4. Tensorflow compatible variant """ + kwargs['bn_eps'] = BN_EPS_TF_DEFAULT + kwargs['pad_type'] = 'same' + model = _gen_efficientnet_lite( + 'tf_efficientnet_lite4', channel_multiplier=1.4, depth_multiplier=1.8, pretrained=pretrained, **kwargs) + return model + + +def mixnet_s(pretrained=False, **kwargs): + """Creates a MixNet Small model. + """ + # NOTE for train set drop_rate=0.2 + model = _gen_mixnet_s( + 'mixnet_s', channel_multiplier=1.0, pretrained=pretrained, **kwargs) + return model + + +def mixnet_m(pretrained=False, **kwargs): + """Creates a MixNet Medium model. + """ + # NOTE for train set drop_rate=0.25 + model = _gen_mixnet_m( + 'mixnet_m', channel_multiplier=1.0, pretrained=pretrained, **kwargs) + return model + + +def mixnet_l(pretrained=False, **kwargs): + """Creates a MixNet Large model. + """ + # NOTE for train set drop_rate=0.25 + model = _gen_mixnet_m( + 'mixnet_l', channel_multiplier=1.3, pretrained=pretrained, **kwargs) + return model + + +def mixnet_xl(pretrained=False, **kwargs): + """Creates a MixNet Extra-Large model. + Not a paper spec, experimental def by RW w/ depth scaling. + """ + # NOTE for train set drop_rate=0.25, drop_connect_rate=0.2 + model = _gen_mixnet_m( + 'mixnet_xl', channel_multiplier=1.6, depth_multiplier=1.2, pretrained=pretrained, **kwargs) + return model + + +def mixnet_xxl(pretrained=False, **kwargs): + """Creates a MixNet Double Extra Large model. + Not a paper spec, experimental def by RW w/ depth scaling. + """ + # NOTE for train set drop_rate=0.3, drop_connect_rate=0.2 + model = _gen_mixnet_m( + 'mixnet_xxl', channel_multiplier=2.4, depth_multiplier=1.3, pretrained=pretrained, **kwargs) + return model + + +def tf_mixnet_s(pretrained=False, **kwargs): + """Creates a MixNet Small model. Tensorflow compatible variant + """ + kwargs['bn_eps'] = BN_EPS_TF_DEFAULT + kwargs['pad_type'] = 'same' + model = _gen_mixnet_s( + 'tf_mixnet_s', channel_multiplier=1.0, pretrained=pretrained, **kwargs) + return model + + +def tf_mixnet_m(pretrained=False, **kwargs): + """Creates a MixNet Medium model. Tensorflow compatible variant + """ + kwargs['bn_eps'] = BN_EPS_TF_DEFAULT + kwargs['pad_type'] = 'same' + model = _gen_mixnet_m( + 'tf_mixnet_m', channel_multiplier=1.0, pretrained=pretrained, **kwargs) + return model + + +def tf_mixnet_l(pretrained=False, **kwargs): + """Creates a MixNet Large model. Tensorflow compatible variant + """ + kwargs['bn_eps'] = BN_EPS_TF_DEFAULT + kwargs['pad_type'] = 'same' + model = _gen_mixnet_m( + 'tf_mixnet_l', channel_multiplier=1.3, pretrained=pretrained, **kwargs) + return model diff --git a/modules/control/proc/normalbae/nets/submodules/efficientnet_repo/geffnet/helpers.py b/modules/control/proc/normalbae/nets/submodules/efficientnet_repo/geffnet/helpers.py new file mode 100644 index 000000000..30c63b2ce --- /dev/null +++ b/modules/control/proc/normalbae/nets/submodules/efficientnet_repo/geffnet/helpers.py @@ -0,0 +1,71 @@ +""" Checkpoint loading / state_dict helpers +Copyright 2020 Ross Wightman +""" +import torch +import os +from collections import OrderedDict +try: + from torch.hub import load_state_dict_from_url +except ImportError: + from torch.utils.model_zoo import load_url as load_state_dict_from_url + + +def load_checkpoint(model, checkpoint_path): + if checkpoint_path and os.path.isfile(checkpoint_path): + print("=> Loading checkpoint '{}'".format(checkpoint_path)) + checkpoint = torch.load(checkpoint_path) + if isinstance(checkpoint, dict) and 'state_dict' in checkpoint: + new_state_dict = OrderedDict() + for k, v in checkpoint['state_dict'].items(): + if k.startswith('module'): + name = k[7:] # remove `module.` + else: + name = k + new_state_dict[name] = v + model.load_state_dict(new_state_dict) + else: + model.load_state_dict(checkpoint) + print("=> Loaded checkpoint '{}'".format(checkpoint_path)) + else: + print("=> Error: No checkpoint found at '{}'".format(checkpoint_path)) + raise FileNotFoundError + + +def load_pretrained(model, url, filter_fn=None, strict=True): + if not url: + print("=> Warning: Pretrained model URL is empty, using random initialization.") + return + + state_dict = load_state_dict_from_url(url, progress=False, map_location='cpu') + + input_conv = 'conv_stem' + classifier = 'classifier' + in_chans = getattr(model, input_conv).weight.shape[1] + num_classes = getattr(model, classifier).weight.shape[0] + + input_conv_weight = input_conv + '.weight' + pretrained_in_chans = state_dict[input_conv_weight].shape[1] + if in_chans != pretrained_in_chans: + if in_chans == 1: + print('=> Converting pretrained input conv {} from {} to 1 channel'.format( + input_conv_weight, pretrained_in_chans)) + conv1_weight = state_dict[input_conv_weight] + state_dict[input_conv_weight] = conv1_weight.sum(dim=1, keepdim=True) + else: + print('=> Discarding pretrained input conv {} since input channel count != {}'.format( + input_conv_weight, pretrained_in_chans)) + del state_dict[input_conv_weight] + strict = False + + classifier_weight = classifier + '.weight' + pretrained_num_classes = state_dict[classifier_weight].shape[0] + if num_classes != pretrained_num_classes: + print('=> Discarding pretrained classifier since num_classes != {}'.format(pretrained_num_classes)) + del state_dict[classifier_weight] + del state_dict[classifier + '.bias'] + strict = False + + if filter_fn is not None: + state_dict = filter_fn(state_dict) + + model.load_state_dict(state_dict, strict=strict) diff --git a/modules/control/proc/normalbae/nets/submodules/efficientnet_repo/geffnet/mobilenetv3.py b/modules/control/proc/normalbae/nets/submodules/efficientnet_repo/geffnet/mobilenetv3.py new file mode 100644 index 000000000..b5966c28f --- /dev/null +++ b/modules/control/proc/normalbae/nets/submodules/efficientnet_repo/geffnet/mobilenetv3.py @@ -0,0 +1,364 @@ +""" MobileNet-V3 + +A PyTorch impl of MobileNet-V3, compatible with TF weights from official impl. + +Paper: Searching for MobileNetV3 - https://arxiv.org/abs/1905.02244 + +Hacked together by / Copyright 2020 Ross Wightman +""" +import torch.nn as nn +import torch.nn.functional as F + +from .activations import get_act_fn, get_act_layer, HardSwish +from .config import layer_config_kwargs +from .conv2d_layers import select_conv2d +from .helpers import load_pretrained +from .efficientnet_builder import * + +__all__ = ['mobilenetv3_rw', 'mobilenetv3_large_075', 'mobilenetv3_large_100', 'mobilenetv3_large_minimal_100', + 'mobilenetv3_small_075', 'mobilenetv3_small_100', 'mobilenetv3_small_minimal_100', + 'tf_mobilenetv3_large_075', 'tf_mobilenetv3_large_100', 'tf_mobilenetv3_large_minimal_100', + 'tf_mobilenetv3_small_075', 'tf_mobilenetv3_small_100', 'tf_mobilenetv3_small_minimal_100'] + +model_urls = { + 'mobilenetv3_rw': + 'https://github.com/rwightman/pytorch-image-models/releases/download/v0.1-weights/mobilenetv3_100-35495452.pth', + 'mobilenetv3_large_075': None, + 'mobilenetv3_large_100': + 'https://github.com/rwightman/pytorch-image-models/releases/download/v0.1-weights/mobilenetv3_large_100_ra-f55367f5.pth', + 'mobilenetv3_large_minimal_100': None, + 'mobilenetv3_small_075': None, + 'mobilenetv3_small_100': None, + 'mobilenetv3_small_minimal_100': None, + 'tf_mobilenetv3_large_075': + 'https://github.com/rwightman/pytorch-image-models/releases/download/v0.1-weights/tf_mobilenetv3_large_075-150ee8b0.pth', + 'tf_mobilenetv3_large_100': + 'https://github.com/rwightman/pytorch-image-models/releases/download/v0.1-weights/tf_mobilenetv3_large_100-427764d5.pth', + 'tf_mobilenetv3_large_minimal_100': + 'https://github.com/rwightman/pytorch-image-models/releases/download/v0.1-weights/tf_mobilenetv3_large_minimal_100-8596ae28.pth', + 'tf_mobilenetv3_small_075': + 'https://github.com/rwightman/pytorch-image-models/releases/download/v0.1-weights/tf_mobilenetv3_small_075-da427f52.pth', + 'tf_mobilenetv3_small_100': + 'https://github.com/rwightman/pytorch-image-models/releases/download/v0.1-weights/tf_mobilenetv3_small_100-37f49e2b.pth', + 'tf_mobilenetv3_small_minimal_100': + 'https://github.com/rwightman/pytorch-image-models/releases/download/v0.1-weights/tf_mobilenetv3_small_minimal_100-922a7843.pth', +} + + +class MobileNetV3(nn.Module): + """ MobileNet-V3 + + A this model utilizes the MobileNet-v3 specific 'efficient head', where global pooling is done before the + head convolution without a final batch-norm layer before the classifier. + + Paper: https://arxiv.org/abs/1905.02244 + """ + + def __init__(self, block_args, num_classes=1000, in_chans=3, stem_size=16, num_features=1280, head_bias=True, + channel_multiplier=1.0, pad_type='', act_layer=HardSwish, drop_rate=0., drop_connect_rate=0., + se_kwargs=None, norm_layer=nn.BatchNorm2d, norm_kwargs=None, weight_init='goog'): + super(MobileNetV3, self).__init__() + self.drop_rate = drop_rate + + stem_size = round_channels(stem_size, channel_multiplier) + self.conv_stem = select_conv2d(in_chans, stem_size, 3, stride=2, padding=pad_type) + self.bn1 = nn.BatchNorm2d(stem_size, **norm_kwargs) + self.act1 = act_layer(inplace=True) + in_chs = stem_size + + builder = EfficientNetBuilder( + channel_multiplier, pad_type=pad_type, act_layer=act_layer, se_kwargs=se_kwargs, + norm_layer=norm_layer, norm_kwargs=norm_kwargs, drop_connect_rate=drop_connect_rate) + self.blocks = nn.Sequential(*builder(in_chs, block_args)) + in_chs = builder.in_chs + + self.global_pool = nn.AdaptiveAvgPool2d(1) + self.conv_head = select_conv2d(in_chs, num_features, 1, padding=pad_type, bias=head_bias) + self.act2 = act_layer(inplace=True) + self.classifier = nn.Linear(num_features, num_classes) + + for m in self.modules(): + if weight_init == 'goog': + initialize_weight_goog(m) + else: + initialize_weight_default(m) + + def as_sequential(self): + layers = [self.conv_stem, self.bn1, self.act1] + layers.extend(self.blocks) + layers.extend([ + self.global_pool, self.conv_head, self.act2, + nn.Flatten(), nn.Dropout(self.drop_rate), self.classifier]) + return nn.Sequential(*layers) + + def features(self, x): + x = self.conv_stem(x) + x = self.bn1(x) + x = self.act1(x) + x = self.blocks(x) + x = self.global_pool(x) + x = self.conv_head(x) + x = self.act2(x) + return x + + def forward(self, x): + x = self.features(x) + x = x.flatten(1) + if self.drop_rate > 0.: + x = F.dropout(x, p=self.drop_rate, training=self.training) + return self.classifier(x) + + +def _create_model(model_kwargs, variant, pretrained=False): + as_sequential = model_kwargs.pop('as_sequential', False) + model = MobileNetV3(**model_kwargs) + if pretrained and model_urls[variant]: + load_pretrained(model, model_urls[variant]) + if as_sequential: + model = model.as_sequential() + return model + + +def _gen_mobilenet_v3_rw(variant, channel_multiplier=1.0, pretrained=False, **kwargs): + """Creates a MobileNet-V3 model (RW variant). + + Paper: https://arxiv.org/abs/1905.02244 + + This was my first attempt at reproducing the MobileNet-V3 from paper alone. It came close to the + eventual Tensorflow reference impl but has a few differences: + 1. This model has no bias on the head convolution + 2. This model forces no residual (noskip) on the first DWS block, this is different than MnasNet + 3. This model always uses ReLU for the SE activation layer, other models in the family inherit their act layer + from their parent block + 4. This model does not enforce divisible by 8 limitation on the SE reduction channel count + + Overall the changes are fairly minor and result in a very small parameter count difference and no + top-1/5 + + Args: + channel_multiplier: multiplier to number of channels per layer. + """ + arch_def = [ + # stage 0, 112x112 in + ['ds_r1_k3_s1_e1_c16_nre_noskip'], # relu + # stage 1, 112x112 in + ['ir_r1_k3_s2_e4_c24_nre', 'ir_r1_k3_s1_e3_c24_nre'], # relu + # stage 2, 56x56 in + ['ir_r3_k5_s2_e3_c40_se0.25_nre'], # relu + # stage 3, 28x28 in + ['ir_r1_k3_s2_e6_c80', 'ir_r1_k3_s1_e2.5_c80', 'ir_r2_k3_s1_e2.3_c80'], # hard-swish + # stage 4, 14x14in + ['ir_r2_k3_s1_e6_c112_se0.25'], # hard-swish + # stage 5, 14x14in + ['ir_r3_k5_s2_e6_c160_se0.25'], # hard-swish + # stage 6, 7x7 in + ['cn_r1_k1_s1_c960'], # hard-swish + ] + with layer_config_kwargs(kwargs): + model_kwargs = dict( + block_args=decode_arch_def(arch_def), + head_bias=False, # one of my mistakes + channel_multiplier=channel_multiplier, + act_layer=resolve_act_layer(kwargs, 'hard_swish'), + se_kwargs=dict(gate_fn=get_act_fn('hard_sigmoid'), reduce_mid=True), + norm_kwargs=resolve_bn_args(kwargs), + **kwargs, + ) + model = _create_model(model_kwargs, variant, pretrained) + return model + + +def _gen_mobilenet_v3(variant, channel_multiplier=1.0, pretrained=False, **kwargs): + """Creates a MobileNet-V3 large/small/minimal models. + + Ref impl: https://github.com/tensorflow/models/blob/master/research/slim/nets/mobilenet/mobilenet_v3.py + Paper: https://arxiv.org/abs/1905.02244 + + Args: + channel_multiplier: multiplier to number of channels per layer. + """ + if 'small' in variant: + num_features = 1024 + if 'minimal' in variant: + act_layer = 'relu' + arch_def = [ + # stage 0, 112x112 in + ['ds_r1_k3_s2_e1_c16'], + # stage 1, 56x56 in + ['ir_r1_k3_s2_e4.5_c24', 'ir_r1_k3_s1_e3.67_c24'], + # stage 2, 28x28 in + ['ir_r1_k3_s2_e4_c40', 'ir_r2_k3_s1_e6_c40'], + # stage 3, 14x14 in + ['ir_r2_k3_s1_e3_c48'], + # stage 4, 14x14in + ['ir_r3_k3_s2_e6_c96'], + # stage 6, 7x7 in + ['cn_r1_k1_s1_c576'], + ] + else: + act_layer = 'hard_swish' + arch_def = [ + # stage 0, 112x112 in + ['ds_r1_k3_s2_e1_c16_se0.25_nre'], # relu + # stage 1, 56x56 in + ['ir_r1_k3_s2_e4.5_c24_nre', 'ir_r1_k3_s1_e3.67_c24_nre'], # relu + # stage 2, 28x28 in + ['ir_r1_k5_s2_e4_c40_se0.25', 'ir_r2_k5_s1_e6_c40_se0.25'], # hard-swish + # stage 3, 14x14 in + ['ir_r2_k5_s1_e3_c48_se0.25'], # hard-swish + # stage 4, 14x14in + ['ir_r3_k5_s2_e6_c96_se0.25'], # hard-swish + # stage 6, 7x7 in + ['cn_r1_k1_s1_c576'], # hard-swish + ] + else: + num_features = 1280 + if 'minimal' in variant: + act_layer = 'relu' + arch_def = [ + # stage 0, 112x112 in + ['ds_r1_k3_s1_e1_c16'], + # stage 1, 112x112 in + ['ir_r1_k3_s2_e4_c24', 'ir_r1_k3_s1_e3_c24'], + # stage 2, 56x56 in + ['ir_r3_k3_s2_e3_c40'], + # stage 3, 28x28 in + ['ir_r1_k3_s2_e6_c80', 'ir_r1_k3_s1_e2.5_c80', 'ir_r2_k3_s1_e2.3_c80'], + # stage 4, 14x14in + ['ir_r2_k3_s1_e6_c112'], + # stage 5, 14x14in + ['ir_r3_k3_s2_e6_c160'], + # stage 6, 7x7 in + ['cn_r1_k1_s1_c960'], + ] + else: + act_layer = 'hard_swish' + arch_def = [ + # stage 0, 112x112 in + ['ds_r1_k3_s1_e1_c16_nre'], # relu + # stage 1, 112x112 in + ['ir_r1_k3_s2_e4_c24_nre', 'ir_r1_k3_s1_e3_c24_nre'], # relu + # stage 2, 56x56 in + ['ir_r3_k5_s2_e3_c40_se0.25_nre'], # relu + # stage 3, 28x28 in + ['ir_r1_k3_s2_e6_c80', 'ir_r1_k3_s1_e2.5_c80', 'ir_r2_k3_s1_e2.3_c80'], # hard-swish + # stage 4, 14x14in + ['ir_r2_k3_s1_e6_c112_se0.25'], # hard-swish + # stage 5, 14x14in + ['ir_r3_k5_s2_e6_c160_se0.25'], # hard-swish + # stage 6, 7x7 in + ['cn_r1_k1_s1_c960'], # hard-swish + ] + with layer_config_kwargs(kwargs): + model_kwargs = dict( + block_args=decode_arch_def(arch_def), + num_features=num_features, + stem_size=16, + channel_multiplier=channel_multiplier, + act_layer=resolve_act_layer(kwargs, act_layer), + se_kwargs=dict( + act_layer=get_act_layer('relu'), gate_fn=get_act_fn('hard_sigmoid'), reduce_mid=True, divisor=8), + norm_kwargs=resolve_bn_args(kwargs), + **kwargs, + ) + model = _create_model(model_kwargs, variant, pretrained) + return model + + +def mobilenetv3_rw(pretrained=False, **kwargs): + """ MobileNet-V3 RW + Attn: See note in gen function for this variant. + """ + # NOTE for train set drop_rate=0.2 + if pretrained: + # pretrained model trained with non-default BN epsilon + kwargs['bn_eps'] = BN_EPS_TF_DEFAULT + model = _gen_mobilenet_v3_rw('mobilenetv3_rw', 1.0, pretrained=pretrained, **kwargs) + return model + + +def mobilenetv3_large_075(pretrained=False, **kwargs): + """ MobileNet V3 Large 0.75""" + # NOTE for train set drop_rate=0.2 + model = _gen_mobilenet_v3('mobilenetv3_large_075', 0.75, pretrained=pretrained, **kwargs) + return model + + +def mobilenetv3_large_100(pretrained=False, **kwargs): + """ MobileNet V3 Large 1.0 """ + # NOTE for train set drop_rate=0.2 + model = _gen_mobilenet_v3('mobilenetv3_large_100', 1.0, pretrained=pretrained, **kwargs) + return model + + +def mobilenetv3_large_minimal_100(pretrained=False, **kwargs): + """ MobileNet V3 Large (Minimalistic) 1.0 """ + # NOTE for train set drop_rate=0.2 + model = _gen_mobilenet_v3('mobilenetv3_large_minimal_100', 1.0, pretrained=pretrained, **kwargs) + return model + + +def mobilenetv3_small_075(pretrained=False, **kwargs): + """ MobileNet V3 Small 0.75 """ + model = _gen_mobilenet_v3('mobilenetv3_small_075', 0.75, pretrained=pretrained, **kwargs) + return model + + +def mobilenetv3_small_100(pretrained=False, **kwargs): + """ MobileNet V3 Small 1.0 """ + model = _gen_mobilenet_v3('mobilenetv3_small_100', 1.0, pretrained=pretrained, **kwargs) + return model + + +def mobilenetv3_small_minimal_100(pretrained=False, **kwargs): + """ MobileNet V3 Small (Minimalistic) 1.0 """ + model = _gen_mobilenet_v3('mobilenetv3_small_minimal_100', 1.0, pretrained=pretrained, **kwargs) + return model + + +def tf_mobilenetv3_large_075(pretrained=False, **kwargs): + """ MobileNet V3 Large 0.75. Tensorflow compat variant. """ + kwargs['bn_eps'] = BN_EPS_TF_DEFAULT + kwargs['pad_type'] = 'same' + model = _gen_mobilenet_v3('tf_mobilenetv3_large_075', 0.75, pretrained=pretrained, **kwargs) + return model + + +def tf_mobilenetv3_large_100(pretrained=False, **kwargs): + """ MobileNet V3 Large 1.0. Tensorflow compat variant. """ + kwargs['bn_eps'] = BN_EPS_TF_DEFAULT + kwargs['pad_type'] = 'same' + model = _gen_mobilenet_v3('tf_mobilenetv3_large_100', 1.0, pretrained=pretrained, **kwargs) + return model + + +def tf_mobilenetv3_large_minimal_100(pretrained=False, **kwargs): + """ MobileNet V3 Large Minimalistic 1.0. Tensorflow compat variant. """ + kwargs['bn_eps'] = BN_EPS_TF_DEFAULT + kwargs['pad_type'] = 'same' + model = _gen_mobilenet_v3('tf_mobilenetv3_large_minimal_100', 1.0, pretrained=pretrained, **kwargs) + return model + + +def tf_mobilenetv3_small_075(pretrained=False, **kwargs): + """ MobileNet V3 Small 0.75. Tensorflow compat variant. """ + kwargs['bn_eps'] = BN_EPS_TF_DEFAULT + kwargs['pad_type'] = 'same' + model = _gen_mobilenet_v3('tf_mobilenetv3_small_075', 0.75, pretrained=pretrained, **kwargs) + return model + + +def tf_mobilenetv3_small_100(pretrained=False, **kwargs): + """ MobileNet V3 Small 1.0. Tensorflow compat variant.""" + kwargs['bn_eps'] = BN_EPS_TF_DEFAULT + kwargs['pad_type'] = 'same' + model = _gen_mobilenet_v3('tf_mobilenetv3_small_100', 1.0, pretrained=pretrained, **kwargs) + return model + + +def tf_mobilenetv3_small_minimal_100(pretrained=False, **kwargs): + """ MobileNet V3 Small Minimalistic 1.0. Tensorflow compat variant. """ + kwargs['bn_eps'] = BN_EPS_TF_DEFAULT + kwargs['pad_type'] = 'same' + model = _gen_mobilenet_v3('tf_mobilenetv3_small_minimal_100', 1.0, pretrained=pretrained, **kwargs) + return model diff --git a/modules/control/proc/normalbae/nets/submodules/efficientnet_repo/geffnet/model_factory.py b/modules/control/proc/normalbae/nets/submodules/efficientnet_repo/geffnet/model_factory.py new file mode 100644 index 000000000..4d46ea8ba --- /dev/null +++ b/modules/control/proc/normalbae/nets/submodules/efficientnet_repo/geffnet/model_factory.py @@ -0,0 +1,27 @@ +from .config import set_layer_config +from .helpers import load_checkpoint + +from .gen_efficientnet import * +from .mobilenetv3 import * + + +def create_model( + model_name='mnasnet_100', + pretrained=None, + num_classes=1000, + in_chans=3, + checkpoint_path='', + **kwargs): + + model_kwargs = dict(num_classes=num_classes, in_chans=in_chans, pretrained=pretrained, **kwargs) + + if model_name in globals(): + create_fn = globals()[model_name] + model = create_fn(**model_kwargs) + else: + raise RuntimeError('Unknown model (%s)' % model_name) + + if checkpoint_path and not pretrained: + load_checkpoint(model, checkpoint_path) + + return model diff --git a/modules/control/proc/normalbae/nets/submodules/efficientnet_repo/geffnet/version.py b/modules/control/proc/normalbae/nets/submodules/efficientnet_repo/geffnet/version.py new file mode 100644 index 000000000..a6221b3de --- /dev/null +++ b/modules/control/proc/normalbae/nets/submodules/efficientnet_repo/geffnet/version.py @@ -0,0 +1 @@ +__version__ = '1.0.2' diff --git a/modules/control/proc/normalbae/nets/submodules/efficientnet_repo/hubconf.py b/modules/control/proc/normalbae/nets/submodules/efficientnet_repo/hubconf.py new file mode 100644 index 000000000..fd1915086 --- /dev/null +++ b/modules/control/proc/normalbae/nets/submodules/efficientnet_repo/hubconf.py @@ -0,0 +1,81 @@ +dependencies = ['torch', 'math'] + +from geffnet import efficientnet_b0 +from geffnet import efficientnet_b1 +from geffnet import efficientnet_b2 +from geffnet import efficientnet_b3 +from geffnet import efficientnet_es +from geffnet import efficientnet_lite0 + +from geffnet import mixnet_s +from geffnet import mixnet_m +from geffnet import mixnet_l +from geffnet import mixnet_xl + +from geffnet import mobilenetv2_100 +from geffnet import mobilenetv2_110d +from geffnet import mobilenetv2_120d +from geffnet import mobilenetv2_140 + +from geffnet import mobilenetv3_large_100 +from geffnet import mobilenetv3_rw +from geffnet import mnasnet_a1 +from geffnet import mnasnet_b1 +from geffnet import fbnetc_100 +from geffnet import spnasnet_100 + +from geffnet import tf_efficientnet_b0 +from geffnet import tf_efficientnet_b1 +from geffnet import tf_efficientnet_b2 +from geffnet import tf_efficientnet_b3 +from geffnet import tf_efficientnet_b4 +from geffnet import tf_efficientnet_b5 +from geffnet import tf_efficientnet_b6 +from geffnet import tf_efficientnet_b7 +from geffnet import tf_efficientnet_b8 + +from geffnet import tf_efficientnet_b0_ap +from geffnet import tf_efficientnet_b1_ap +from geffnet import tf_efficientnet_b2_ap +from geffnet import tf_efficientnet_b3_ap +from geffnet import tf_efficientnet_b4_ap +from geffnet import tf_efficientnet_b5_ap +from geffnet import tf_efficientnet_b6_ap +from geffnet import tf_efficientnet_b7_ap +from geffnet import tf_efficientnet_b8_ap + +from geffnet import tf_efficientnet_b0_ns +from geffnet import tf_efficientnet_b1_ns +from geffnet import tf_efficientnet_b2_ns +from geffnet import tf_efficientnet_b3_ns +from geffnet import tf_efficientnet_b4_ns +from geffnet import tf_efficientnet_b5_ns +from geffnet import tf_efficientnet_b6_ns +from geffnet import tf_efficientnet_b7_ns +from geffnet import tf_efficientnet_l2_ns_475 +from geffnet import tf_efficientnet_l2_ns + +from geffnet import tf_efficientnet_es +from geffnet import tf_efficientnet_em +from geffnet import tf_efficientnet_el + +from geffnet import tf_efficientnet_cc_b0_4e +from geffnet import tf_efficientnet_cc_b0_8e +from geffnet import tf_efficientnet_cc_b1_8e + +from geffnet import tf_efficientnet_lite0 +from geffnet import tf_efficientnet_lite1 +from geffnet import tf_efficientnet_lite2 +from geffnet import tf_efficientnet_lite3 +from geffnet import tf_efficientnet_lite4 + +from geffnet import tf_mixnet_s +from geffnet import tf_mixnet_m +from geffnet import tf_mixnet_l + +from geffnet import tf_mobilenetv3_large_075 +from geffnet import tf_mobilenetv3_large_100 +from geffnet import tf_mobilenetv3_large_minimal_100 +from geffnet import tf_mobilenetv3_small_075 +from geffnet import tf_mobilenetv3_small_100 +from geffnet import tf_mobilenetv3_small_minimal_100 diff --git a/modules/control/proc/normalbae/nets/submodules/efficientnet_repo/requirements.txt b/modules/control/proc/normalbae/nets/submodules/efficientnet_repo/requirements.txt new file mode 100644 index 000000000..ac3ffc13b --- /dev/null +++ b/modules/control/proc/normalbae/nets/submodules/efficientnet_repo/requirements.txt @@ -0,0 +1,2 @@ +torch>=1.2.0 +torchvision>=0.4.0 diff --git a/modules/control/proc/normalbae/nets/submodules/efficientnet_repo/setup.py b/modules/control/proc/normalbae/nets/submodules/efficientnet_repo/setup.py new file mode 100644 index 000000000..83388db37 --- /dev/null +++ b/modules/control/proc/normalbae/nets/submodules/efficientnet_repo/setup.py @@ -0,0 +1,48 @@ +""" Setup +""" +from setuptools import setup, find_packages +from codecs import open +from os import path + +here = path.abspath(path.dirname(__file__)) +__version__ = '0.0.0' + +# Get the long description from the README file +with open(path.join(here, 'README.md'), encoding='utf-8') as f: + long_description = f.read() + +exec(open('geffnet/version.py').read()) +setup( + name='geffnet', + version=__version__, + description='(Generic) EfficientNets for PyTorch', + long_description=long_description, + long_description_content_type='text/markdown', + url='https://github.com/rwightman/gen-efficientnet-pytorch', + author='Ross Wightman', + author_email='hello@rwightman.com', + classifiers=[ + # How mature is this project? Common values are + # 3 - Alpha + # 4 - Beta + # 5 - Production/Stable + 'Development Status :: 3 - Alpha', + 'Intended Audience :: Education', + 'Intended Audience :: Science/Research', + 'License :: OSI Approved :: Apache Software License', + 'Programming Language :: Python :: 3.6', + 'Programming Language :: Python :: 3.7', + 'Programming Language :: Python :: 3.8', + 'Topic :: Scientific/Engineering', + 'Topic :: Scientific/Engineering :: Artificial Intelligence', + 'Topic :: Software Development', + 'Topic :: Software Development :: Libraries', + 'Topic :: Software Development :: Libraries :: Python Modules', + ], + + # Note that this is a string of words separated by whitespace, not a list. + keywords='pytorch pretrained models efficientnet mixnet mobilenetv3 mnasnet', + packages=find_packages(exclude=['data']), + install_requires=['torch >= 1.4', 'torchvision'], + python_requires='>=3.6', +) diff --git a/modules/control/proc/normalbae/nets/submodules/efficientnet_repo/utils.py b/modules/control/proc/normalbae/nets/submodules/efficientnet_repo/utils.py new file mode 100644 index 000000000..d327e8bd8 --- /dev/null +++ b/modules/control/proc/normalbae/nets/submodules/efficientnet_repo/utils.py @@ -0,0 +1,52 @@ +import os + + +class AverageMeter: + """Computes and stores the average and current value""" + def __init__(self): + self.reset() + + def reset(self): + self.val = 0 + self.avg = 0 + self.sum = 0 + self.count = 0 + + def update(self, val, n=1): + self.val = val + self.sum += val * n + self.count += n + self.avg = self.sum / self.count + + +def accuracy(output, target, topk=(1,)): + """Computes the precision@k for the specified values of k""" + maxk = max(topk) + batch_size = target.size(0) + + _, pred = output.topk(maxk, 1, True, True) + pred = pred.t() + correct = pred.eq(target.view(1, -1).expand_as(pred)) + + res = [] + for k in topk: + correct_k = correct[:k].reshape(-1).float().sum(0) + res.append(correct_k.mul_(100.0 / batch_size)) + return res + + +def get_outdir(path, *paths, inc=False): + outdir = os.path.join(path, *paths) + if not os.path.exists(outdir): + os.makedirs(outdir) + elif inc: + count = 1 + outdir_inc = outdir + '-' + str(count) + while os.path.exists(outdir_inc): + count = count + 1 + outdir_inc = outdir + '-' + str(count) + assert count < 100 + outdir = outdir_inc + os.makedirs(outdir) + return outdir + diff --git a/modules/control/proc/normalbae/nets/submodules/efficientnet_repo/validate.py b/modules/control/proc/normalbae/nets/submodules/efficientnet_repo/validate.py new file mode 100644 index 000000000..bed37a389 --- /dev/null +++ b/modules/control/proc/normalbae/nets/submodules/efficientnet_repo/validate.py @@ -0,0 +1,165 @@ +from __future__ import absolute_import +from __future__ import division +from __future__ import print_function + +import argparse +import time +import torch +import torch.nn as nn +import torch.nn.parallel +from contextlib import suppress + +import geffnet +from data import Dataset, create_loader, resolve_data_config +from utils import accuracy, AverageMeter + +has_native_amp = False +try: + if torch.cuda.amp.autocast is not None: + has_native_amp = True +except AttributeError: + pass + +torch.backends.cudnn.benchmark = True + +parser = argparse.ArgumentParser(description='PyTorch ImageNet Validation') +parser.add_argument('data', metavar='DIR', + help='path to dataset') +parser.add_argument('--model', '-m', metavar='MODEL', default='spnasnet1_00', + help='model architecture (default: dpn92)') +parser.add_argument('-j', '--workers', default=4, type=int, metavar='N', + help='number of data loading workers (default: 2)') +parser.add_argument('-b', '--batch-size', default=256, type=int, + metavar='N', help='mini-batch size (default: 256)') +parser.add_argument('--img-size', default=None, type=int, + metavar='N', help='Input image dimension, uses model default if empty') +parser.add_argument('--mean', type=float, nargs='+', default=None, metavar='MEAN', + help='Override mean pixel value of dataset') +parser.add_argument('--std', type=float, nargs='+', default=None, metavar='STD', + help='Override std deviation of of dataset') +parser.add_argument('--crop-pct', type=float, default=None, metavar='PCT', + help='Override default crop pct of 0.875') +parser.add_argument('--interpolation', default='', type=str, metavar='NAME', + help='Image resize interpolation type (overrides model)') +parser.add_argument('--num-classes', type=int, default=1000, + help='Number classes in dataset') +parser.add_argument('--print-freq', '-p', default=10, type=int, + metavar='N', help='print frequency (default: 10)') +parser.add_argument('--checkpoint', default='', type=str, metavar='PATH', + help='path to latest checkpoint (default: none)') +parser.add_argument('--pretrained', dest='pretrained', action='store_true', + help='use pre-trained model') +parser.add_argument('--torchscript', dest='torchscript', action='store_true', + help='convert model torchscript for inference') +parser.add_argument('--num-gpu', type=int, default=1, + help='Number of GPUS to use') +parser.add_argument('--tf-preprocessing', dest='tf_preprocessing', action='store_true', + help='use tensorflow mnasnet preporcessing') +parser.add_argument('--no-cuda', dest='no_cuda', action='store_true', + help='') +parser.add_argument('--channels-last', action='store_true', default=False, + help='Use channels_last memory layout') +parser.add_argument('--amp', action='store_true', default=False, + help='Use native Torch AMP mixed precision.') + + +def main(): + args = parser.parse_args() + + if not args.checkpoint and not args.pretrained: + args.pretrained = True + + amp_autocast = suppress # do nothing + if args.amp: + if not has_native_amp: + print("Native Torch AMP is not available (requires torch >= 1.6), using FP32.") + else: + amp_autocast = torch.cuda.amp.autocast + + # create model + model = geffnet.create_model( + args.model, + num_classes=args.num_classes, + in_chans=3, + pretrained=args.pretrained, + checkpoint_path=args.checkpoint, + scriptable=args.torchscript) + + if args.channels_last: + model = model.to(memory_format=torch.channels_last) + + if args.torchscript: + torch.jit.optimized_execution(True) + model = torch.jit.script(model) + + print('Model %s created, param count: %d' % + (args.model, sum([m.numel() for m in model.parameters()]))) + + data_config = resolve_data_config(model, args) + + criterion = nn.CrossEntropyLoss() + + if not args.no_cuda: + if args.num_gpu > 1: + model = torch.nn.DataParallel(model, device_ids=list(range(args.num_gpu))).cuda() + else: + model = model.cuda() + criterion = criterion.cuda() + + loader = create_loader( + Dataset(args.data, load_bytes=args.tf_preprocessing), + input_size=data_config['input_size'], + batch_size=args.batch_size, + use_prefetcher=not args.no_cuda, + interpolation=data_config['interpolation'], + mean=data_config['mean'], + std=data_config['std'], + num_workers=args.workers, + crop_pct=data_config['crop_pct'], + tensorflow_preprocessing=args.tf_preprocessing) + + batch_time = AverageMeter() + losses = AverageMeter() + top1 = AverageMeter() + top5 = AverageMeter() + + model.eval() + end = time.time() + for i, (input, target) in enumerate(loader): + if not args.no_cuda: + target = target.cuda() + input = input.cuda() + if args.channels_last: + input = input.contiguous(memory_format=torch.channels_last) + + # compute output + with amp_autocast(): + output = model(input) + loss = criterion(output, target) + + # measure accuracy and record loss + prec1, prec5 = accuracy(output.data, target, topk=(1, 5)) + losses.update(loss.item(), input.size(0)) + top1.update(prec1.item(), input.size(0)) + top5.update(prec5.item(), input.size(0)) + + # measure elapsed time + batch_time.update(time.time() - end) + end = time.time() + + if i % args.print_freq == 0: + print('Test: [{0}/{1}]\t' + 'Time {batch_time.val:.3f} ({batch_time.avg:.3f}, {rate_avg:.3f}/s) \t' + 'Loss {loss.val:.4f} ({loss.avg:.4f})\t' + 'Prec@1 {top1.val:.3f} ({top1.avg:.3f})\t' + 'Prec@5 {top5.val:.3f} ({top5.avg:.3f})'.format( + i, len(loader), batch_time=batch_time, + rate_avg=input.size(0) / batch_time.avg, + loss=losses, top1=top1, top5=top5)) + + print(' * Prec@1 {top1.avg:.3f} ({top1a:.3f}) Prec@5 {top5.avg:.3f} ({top5a:.3f})'.format( + top1=top1, top1a=100-top1.avg, top5=top5, top5a=100.-top5.avg)) + + +if __name__ == '__main__': + main() diff --git a/modules/control/proc/normalbae/nets/submodules/encoder.py b/modules/control/proc/normalbae/nets/submodules/encoder.py new file mode 100644 index 000000000..21ee626a2 --- /dev/null +++ b/modules/control/proc/normalbae/nets/submodules/encoder.py @@ -0,0 +1,28 @@ +import os +import torch +import torch.nn as nn + + +class Encoder(nn.Module): + def __init__(self): + super(Encoder, self).__init__() + + basemodel_name = 'tf_efficientnet_b5_ap' + repo_path = os.path.join(os.path.dirname(__file__), 'efficientnet_repo') + basemodel = torch.hub.load(repo_path, basemodel_name, pretrained=False, source='local') + + # Remove last layer + basemodel.global_pool = nn.Identity() + basemodel.classifier = nn.Identity() + + self.original_model = basemodel + + def forward(self, x): + features = [x] + for k, v in self.original_model._modules.items(): + if k == 'blocks': + for _ki, vi in v._modules.items(): + features.append(vi(features[-1])) + else: + features.append(v(features[-1])) + return features diff --git a/modules/control/proc/normalbae/nets/submodules/submodules.py b/modules/control/proc/normalbae/nets/submodules/submodules.py new file mode 100644 index 000000000..fdf12e133 --- /dev/null +++ b/modules/control/proc/normalbae/nets/submodules/submodules.py @@ -0,0 +1,139 @@ +import torch +import torch.nn as nn +import torch.nn.functional as F + + +######################################################################################################################## + + +# Upsample + BatchNorm +class UpSampleBN(nn.Module): + def __init__(self, skip_input, output_features): + super(UpSampleBN, self).__init__() + + self._net = nn.Sequential(nn.Conv2d(skip_input, output_features, kernel_size=3, stride=1, padding=1), + nn.BatchNorm2d(output_features), + nn.LeakyReLU(), + nn.Conv2d(output_features, output_features, kernel_size=3, stride=1, padding=1), + nn.BatchNorm2d(output_features), + nn.LeakyReLU()) + + def forward(self, x, concat_with): + up_x = F.interpolate(x, size=[concat_with.size(2), concat_with.size(3)], mode='bilinear', align_corners=True) + f = torch.cat([up_x, concat_with], dim=1) + return self._net(f) + + +# Upsample + GroupNorm + Weight Standardization +class UpSampleGN(nn.Module): + def __init__(self, skip_input, output_features): + super(UpSampleGN, self).__init__() + + self._net = nn.Sequential(Conv2d(skip_input, output_features, kernel_size=3, stride=1, padding=1), + nn.GroupNorm(8, output_features), + nn.LeakyReLU(), + Conv2d(output_features, output_features, kernel_size=3, stride=1, padding=1), + nn.GroupNorm(8, output_features), + nn.LeakyReLU()) + + def forward(self, x, concat_with): + up_x = F.interpolate(x, size=[concat_with.size(2), concat_with.size(3)], mode='bilinear', align_corners=True) + f = torch.cat([up_x, concat_with], dim=1) + return self._net(f) + + +# Conv2d with weight standardization +class Conv2d(nn.Conv2d): + def __init__(self, in_channels, out_channels, kernel_size, stride=1, + padding=0, dilation=1, groups=1, bias=True): + super(Conv2d, self).__init__(in_channels, out_channels, kernel_size, stride, + padding, dilation, groups, bias) + + def forward(self, x): + weight = self.weight + weight_mean = weight.mean(dim=1, keepdim=True).mean(dim=2, + keepdim=True).mean(dim=3, keepdim=True) + weight = weight - weight_mean + std = weight.view(weight.size(0), -1).std(dim=1).view(-1, 1, 1, 1) + 1e-5 + weight = weight / std.expand_as(weight) + return F.conv2d(x, weight, self.bias, self.stride, + self.padding, self.dilation, self.groups) + + +# normalize +def norm_normalize(norm_out): + min_kappa = 0.01 + norm_x, norm_y, norm_z, kappa = torch.split(norm_out, 1, dim=1) + norm = torch.sqrt(norm_x ** 2.0 + norm_y ** 2.0 + norm_z ** 2.0) + 1e-10 + kappa = F.elu(kappa) + 1.0 + min_kappa + final_out = torch.cat([norm_x / norm, norm_y / norm, norm_z / norm, kappa], dim=1) + return final_out + + +# uncertainty-guided sampling (only used during training) +def sample_points(init_normal, gt_norm_mask, sampling_ratio, beta): + device = init_normal.device + B, _, H, W = init_normal.shape + N = int(sampling_ratio * H * W) + beta = beta + + # uncertainty map + uncertainty_map = -1 * init_normal[:, 3, :, :] # B, H, W + + # gt_invalid_mask (B, H, W) + if gt_norm_mask is not None: + gt_invalid_mask = F.interpolate(gt_norm_mask.float(), size=[H, W], mode='nearest') + gt_invalid_mask = gt_invalid_mask[:, 0, :, :] < 0.5 + uncertainty_map[gt_invalid_mask] = -1e4 + + # (B, H*W) + _, idx = uncertainty_map.view(B, -1).sort(1, descending=True) + + # importance sampling + if int(beta * N) > 0: + importance = idx[:, :int(beta * N)] # B, beta*N + + # remaining + remaining = idx[:, int(beta * N):] # B, H*W - beta*N + + # coverage + num_coverage = N - int(beta * N) + + if num_coverage <= 0: + samples = importance + else: + coverage_list = [] + for i in range(B): + idx_c = torch.randperm(remaining.size()[1]) # shuffles "H*W - beta*N" + coverage_list.append(remaining[i, :][idx_c[:num_coverage]].view(1, -1)) # 1, N-beta*N + coverage = torch.cat(coverage_list, dim=0) # B, N-beta*N + samples = torch.cat((importance, coverage), dim=1) # B, N + + else: + # remaining + remaining = idx[:, :] # B, H*W + + # coverage + num_coverage = N + + coverage_list = [] + for i in range(B): + idx_c = torch.randperm(remaining.size()[1]) # shuffles "H*W - beta*N" + coverage_list.append(remaining[i, :][idx_c[:num_coverage]].view(1, -1)) # 1, N-beta*N + coverage = torch.cat(coverage_list, dim=0) # B, N-beta*N + samples = coverage + + # point coordinates + rows_int = samples // W # 0 for first row, H-1 for last row + rows_float = rows_int / float(H-1) # 0 to 1.0 + rows_float = (rows_float * 2.0) - 1.0 # -1.0 to 1.0 + + cols_int = samples % W # 0 for first column, W-1 for last column + cols_float = cols_int / float(W-1) # 0 to 1.0 + cols_float = (cols_float * 2.0) - 1.0 # -1.0 to 1.0 + + point_coords = torch.zeros(B, 1, N, 2) + point_coords[:, 0, :, 0] = cols_float # x coord + point_coords[:, 0, :, 1] = rows_float # y coord + point_coords = point_coords.to(device) + return point_coords, rows_int, cols_int diff --git a/modules/control/proc/openpose/LICENSE b/modules/control/proc/openpose/LICENSE new file mode 100644 index 000000000..6f60b76d3 --- /dev/null +++ b/modules/control/proc/openpose/LICENSE @@ -0,0 +1,108 @@ +OPENPOSE: MULTIPERSON KEYPOINT DETECTION +SOFTWARE LICENSE AGREEMENT +ACADEMIC OR NON-PROFIT ORGANIZATION NONCOMMERCIAL RESEARCH USE ONLY + +BY USING OR DOWNLOADING THE SOFTWARE, YOU ARE AGREEING TO THE TERMS OF THIS LICENSE AGREEMENT. IF YOU DO NOT AGREE WITH THESE TERMS, YOU MAY NOT USE OR DOWNLOAD THE SOFTWARE. + +This is a license agreement ("Agreement") between your academic institution or non-profit organization or self (called "Licensee" or "You" in this Agreement) and Carnegie Mellon University (called "Licensor" in this Agreement). All rights not specifically granted to you in this Agreement are reserved for Licensor. + +RESERVATION OF OWNERSHIP AND GRANT OF LICENSE: +Licensor retains exclusive ownership of any copy of the Software (as defined below) licensed under this Agreement and hereby grants to Licensee a personal, non-exclusive, +non-transferable license to use the Software for noncommercial research purposes, without the right to sublicense, pursuant to the terms and conditions of this Agreement. As used in this Agreement, the term "Software" means (i) the actual copy of all or any portion of code for program routines made accessible to Licensee by Licensor pursuant to this Agreement, inclusive of backups, updates, and/or merged copies permitted hereunder or subsequently supplied by Licensor, including all or any file structures, programming instructions, user interfaces and screen formats and sequences as well as any and all documentation and instructions related to it, and (ii) all or any derivatives and/or modifications created or made by You to any of the items specified in (i). + +CONFIDENTIALITY: Licensee acknowledges that the Software is proprietary to Licensor, and as such, Licensee agrees to receive all such materials in confidence and use the Software only in accordance with the terms of this Agreement. Licensee agrees to use reasonable effort to protect the Software from unauthorized use, reproduction, distribution, or publication. + +COPYRIGHT: The Software is owned by Licensor and is protected by United +States copyright laws and applicable international treaties and/or conventions. + +PERMITTED USES: The Software may be used for your own noncommercial internal research purposes. You understand and agree that Licensor is not obligated to implement any suggestions and/or feedback you might provide regarding the Software, but to the extent Licensor does so, you are not entitled to any compensation related thereto. + +DERIVATIVES: You may create derivatives of or make modifications to the Software, however, You agree that all and any such derivatives and modifications will be owned by Licensor and become a part of the Software licensed to You under this Agreement. You may only use such derivatives and modifications for your own noncommercial internal research purposes, and you may not otherwise use, distribute or copy such derivatives and modifications in violation of this Agreement. + +BACKUPS: If Licensee is an organization, it may make that number of copies of the Software necessary for internal noncommercial use at a single site within its organization provided that all information appearing in or on the original labels, including the copyright and trademark notices are copied onto the labels of the copies. + +USES NOT PERMITTED: You may not distribute, copy or use the Software except as explicitly permitted herein. Licensee has not been granted any trademark license as part of this Agreement and may not use the name or mark “OpenPose", "Carnegie Mellon" or any renditions thereof without the prior written permission of Licensor. + +You may not sell, rent, lease, sublicense, lend, time-share or transfer, in whole or in part, or provide third parties access to prior or present versions (or any parts thereof) of the Software. + +ASSIGNMENT: You may not assign this Agreement or your rights hereunder without the prior written consent of Licensor. Any attempted assignment without such consent shall be null and void. + +TERM: The term of the license granted by this Agreement is from Licensee's acceptance of this Agreement by downloading the Software or by using the Software until terminated as provided below. + +The Agreement automatically terminates without notice if you fail to comply with any provision of this Agreement. Licensee may terminate this Agreement by ceasing using the Software. Upon any termination of this Agreement, Licensee will delete any and all copies of the Software. You agree that all provisions which operate to protect the proprietary rights of Licensor shall remain in force should breach occur and that the obligation of confidentiality described in this Agreement is binding in perpetuity and, as such, survives the term of the Agreement. + +FEE: Provided Licensee abides completely by the terms and conditions of this Agreement, there is no fee due to Licensor for Licensee's use of the Software in accordance with this Agreement. + +DISCLAIMER OF WARRANTIES: THE SOFTWARE IS PROVIDED "AS-IS" WITHOUT WARRANTY OF ANY KIND INCLUDING ANY WARRANTIES OF PERFORMANCE OR MERCHANTABILITY OR FITNESS FOR A PARTICULAR USE OR PURPOSE OR OF NON-INFRINGEMENT. LICENSEE BEARS ALL RISK RELATING TO QUALITY AND PERFORMANCE OF THE SOFTWARE AND RELATED MATERIALS. + +SUPPORT AND MAINTENANCE: No Software support or training by the Licensor is provided as part of this Agreement. + +EXCLUSIVE REMEDY AND LIMITATION OF LIABILITY: To the maximum extent permitted under applicable law, Licensor shall not be liable for direct, indirect, special, incidental, or consequential damages or lost profits related to Licensee's use of and/or inability to use the Software, even if Licensor is advised of the possibility of such damage. + +EXPORT REGULATION: Licensee agrees to comply with any and all applicable +U.S. export control laws, regulations, and/or other laws related to embargoes and sanction programs administered by the Office of Foreign Assets Control. + +SEVERABILITY: If any provision(s) of this Agreement shall be held to be invalid, illegal, or unenforceable by a court or other tribunal of competent jurisdiction, the validity, legality and enforceability of the remaining provisions shall not in any way be affected or impaired thereby. + +NO IMPLIED WAIVERS: No failure or delay by Licensor in enforcing any right or remedy under this Agreement shall be construed as a waiver of any future or other exercise of such right or remedy by Licensor. + +GOVERNING LAW: This Agreement shall be construed and enforced in accordance with the laws of the Commonwealth of Pennsylvania without reference to conflict of laws principles. You consent to the personal jurisdiction of the courts of this County and waive their rights to venue outside of Allegheny County, Pennsylvania. + +ENTIRE AGREEMENT AND AMENDMENTS: This Agreement constitutes the sole and entire agreement between Licensee and Licensor as to the matter set forth herein and supersedes any previous agreements, understandings, and arrangements between the parties relating hereto. + + + +************************************************************************ + +THIRD-PARTY SOFTWARE NOTICES AND INFORMATION + +This project incorporates material from the project(s) listed below (collectively, "Third Party Code"). This Third Party Code is licensed to you under their original license terms set forth below. We reserves all other rights not expressly granted, whether by implication, estoppel or otherwise. + +1. Caffe, version 1.0.0, (https://github.com/BVLC/caffe/) + +COPYRIGHT + +All contributions by the University of California: +Copyright (c) 2014-2017 The Regents of the University of California (Regents) +All rights reserved. + +All other contributions: +Copyright (c) 2014-2017, the respective contributors +All rights reserved. + +Caffe uses a shared copyright model: each contributor holds copyright over +their contributions to Caffe. The project versioning records all such +contribution and copyright details. If a contributor wants to further mark +their specific copyright on a particular contribution, they should indicate +their copyright solely in the commit message of the change when it is +committed. + +LICENSE + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are met: + +1. Redistributions of source code must retain the above copyright notice, this + list of conditions and the following disclaimer. +2. Redistributions in binary form must reproduce the above copyright notice, + this list of conditions and the following disclaimer in the documentation + and/or other materials provided with the distribution. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND +ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR +ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES +(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; +LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND +ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS +SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +CONTRIBUTION AGREEMENT + +By contributing to the BVLC/caffe repository through pull-request, comment, +or otherwise, the contributor releases their content to the +license and copyright terms herein. + +************END OF THIRD-PARTY SOFTWARE NOTICES AND INFORMATION********** \ No newline at end of file diff --git a/modules/control/proc/openpose/__init__.py b/modules/control/proc/openpose/__init__.py new file mode 100644 index 000000000..398b7ec40 --- /dev/null +++ b/modules/control/proc/openpose/__init__.py @@ -0,0 +1,233 @@ +# Openpose +# Original from CMU https://github.com/CMU-Perceptual-Computing-Lab/openpose +# 2nd Edited by https://github.com/Hzzone/pytorch-openpose +# 3rd Edited by ControlNet +# 4th Edited by ControlNet (added face and correct hands) +# 5th Edited by ControlNet (Improved JSON serialization/deserialization, and lots of bug fixs) +# This preprocessor is licensed by CMU for non-commercial use only. + + +import os + +os.environ["KMP_DUPLICATE_LIB_OK"] = "TRUE" + +import json +import warnings +from typing import Callable, List, NamedTuple, Tuple, Union + +import cv2 +import numpy as np +import torch +from huggingface_hub import hf_hub_download +from PIL import Image + +from modules.control.util import HWC3, resize_image +from . import util +from .body import Body, BodyResult, Keypoint +from .face import Face +from .hand import Hand + +HandResult = List[Keypoint] +FaceResult = List[Keypoint] + +class PoseResult(NamedTuple): + body: BodyResult + left_hand: Union[HandResult, None] + right_hand: Union[HandResult, None] + face: Union[FaceResult, None] + +def draw_poses(poses: List[PoseResult], H, W, draw_body=True, draw_hand=True, draw_face=True): + """ + Draw the detected poses on an empty canvas. + + Args: + poses (List[PoseResult]): A list of PoseResult objects containing the detected poses. + H (int): The height of the canvas. + W (int): The width of the canvas. + draw_body (bool, optional): Whether to draw body keypoints. Defaults to True. + draw_hand (bool, optional): Whether to draw hand keypoints. Defaults to True. + draw_face (bool, optional): Whether to draw face keypoints. Defaults to True. + + Returns: + numpy.ndarray: A 3D numpy array representing the canvas with the drawn poses. + """ + canvas = np.zeros(shape=(H, W, 3), dtype=np.uint8) + + for pose in poses: + if draw_body: + canvas = util.draw_bodypose(canvas, pose.body.keypoints) + + if draw_hand: + canvas = util.draw_handpose(canvas, pose.left_hand) + canvas = util.draw_handpose(canvas, pose.right_hand) + + if draw_face: + canvas = util.draw_facepose(canvas, pose.face) + + return canvas + + +class OpenposeDetector: + """ + A class for detecting human poses in images using the Openpose model. + + Attributes: + model_dir (str): Path to the directory where the pose models are stored. + """ + def __init__(self, body_estimation, hand_estimation=None, face_estimation=None): + self.body_estimation = body_estimation + self.hand_estimation = hand_estimation + self.face_estimation = face_estimation + + @classmethod + def from_pretrained(cls, pretrained_model_or_path, filename=None, hand_filename=None, face_filename=None, cache_dir=None): + + if pretrained_model_or_path == "lllyasviel/ControlNet": + filename = filename or "annotator/ckpts/body_pose_model.pth" + hand_filename = hand_filename or "annotator/ckpts/hand_pose_model.pth" + face_filename = face_filename or "facenet.pth" + + face_pretrained_model_or_path = "lllyasviel/Annotators" + else: + filename = filename or "body_pose_model.pth" + hand_filename = hand_filename or "hand_pose_model.pth" + face_filename = face_filename or "facenet.pth" + + face_pretrained_model_or_path = pretrained_model_or_path + + if os.path.isdir(pretrained_model_or_path): + body_model_path = os.path.join(pretrained_model_or_path, filename) + hand_model_path = os.path.join(pretrained_model_or_path, hand_filename) + face_model_path = os.path.join(face_pretrained_model_or_path, face_filename) + else: + body_model_path = hf_hub_download(pretrained_model_or_path, filename, cache_dir=cache_dir) + hand_model_path = hf_hub_download(pretrained_model_or_path, hand_filename, cache_dir=cache_dir) + face_model_path = hf_hub_download(face_pretrained_model_or_path, face_filename, cache_dir=cache_dir) + + body_estimation = Body(body_model_path) + hand_estimation = Hand(hand_model_path) + face_estimation = Face(face_model_path) + + return cls(body_estimation, hand_estimation, face_estimation) + + def to(self, device): + self.body_estimation.to(device) + self.hand_estimation.to(device) + self.face_estimation.to(device) + return self + + def detect_hands(self, body: BodyResult, oriImg) -> Tuple[Union[HandResult, None], Union[HandResult, None]]: + left_hand = None + right_hand = None + H, W, _ = oriImg.shape + for x, y, w, is_left in util.handDetect(body, oriImg): + peaks = self.hand_estimation(oriImg[y:y+w, x:x+w, :]).astype(np.float32) + if peaks.ndim == 2 and peaks.shape[1] == 2: + peaks[:, 0] = np.where(peaks[:, 0] < 1e-6, -1, peaks[:, 0] + x) / float(W) + peaks[:, 1] = np.where(peaks[:, 1] < 1e-6, -1, peaks[:, 1] + y) / float(H) + + hand_result = [ + Keypoint(x=peak[0], y=peak[1]) + for peak in peaks + ] + + if is_left: + left_hand = hand_result + else: + right_hand = hand_result + + return left_hand, right_hand + + def detect_face(self, body: BodyResult, oriImg) -> Union[FaceResult, None]: + face = util.faceDetect(body, oriImg) + if face is None: + return None + + x, y, w = face + H, W, _ = oriImg.shape + heatmaps = self.face_estimation(oriImg[y:y+w, x:x+w, :]) + peaks = self.face_estimation.compute_peaks_from_heatmaps(heatmaps).astype(np.float32) + if peaks.ndim == 2 and peaks.shape[1] == 2: + peaks[:, 0] = np.where(peaks[:, 0] < 1e-6, -1, peaks[:, 0] + x) / float(W) + peaks[:, 1] = np.where(peaks[:, 1] < 1e-6, -1, peaks[:, 1] + y) / float(H) + return [ + Keypoint(x=peak[0], y=peak[1]) + for peak in peaks + ] + + return None + + def detect_poses(self, oriImg, include_hand=False, include_face=False) -> List[PoseResult]: + """ + Detect poses in the given image. + Args: + oriImg (numpy.ndarray): The input image for pose detection. + include_hand (bool, optional): Whether to include hand detection. Defaults to False. + include_face (bool, optional): Whether to include face detection. Defaults to False. + + Returns: + List[PoseResult]: A list of PoseResult objects containing the detected poses. + """ + oriImg = oriImg[:, :, ::-1].copy() + H, W, C = oriImg.shape + candidate, subset = self.body_estimation(oriImg) + bodies = self.body_estimation.format_body_result(candidate, subset) + + results = [] + for body in bodies: + left_hand, right_hand, face = (None,) * 3 + if include_hand: + left_hand, right_hand = self.detect_hands(body, oriImg) + if include_face: + face = self.detect_face(body, oriImg) + + results.append(PoseResult(BodyResult( + keypoints=[ + Keypoint( + x=keypoint.x / float(W), + y=keypoint.y / float(H) + ) if keypoint is not None else None + for keypoint in body.keypoints + ], + total_score=body.total_score, + total_parts=body.total_parts + ), left_hand, right_hand, face)) + + return results + + def __call__(self, input_image, detect_resolution=512, image_resolution=512, include_body=True, include_hand=False, include_face=False, hand_and_face=None, output_type="pil", **kwargs): + if hand_and_face is not None: + warnings.warn("hand_and_face is deprecated. Use include_hand and include_face instead.", DeprecationWarning) + include_hand = hand_and_face + include_face = hand_and_face + + if "return_pil" in kwargs: + warnings.warn("return_pil is deprecated. Use output_type instead.", DeprecationWarning) + output_type = "pil" if kwargs["return_pil"] else "np" + if type(output_type) is bool: + warnings.warn("Passing `True` or `False` to `output_type` is deprecated and will raise an error in future versions") + if output_type: + output_type = "pil" + + if not isinstance(input_image, np.ndarray): + input_image = np.array(input_image, dtype=np.uint8) + + input_image = HWC3(input_image) + input_image = resize_image(input_image, detect_resolution) + H, W, C = input_image.shape + + poses = self.detect_poses(input_image, include_hand, include_face) + canvas = draw_poses(poses, H, W, draw_body=include_body, draw_hand=include_hand, draw_face=include_face) + + detected_map = canvas + detected_map = HWC3(detected_map) + + img = resize_image(input_image, image_resolution) + H, W, C = img.shape + + detected_map = cv2.resize(detected_map, (W, H), interpolation=cv2.INTER_LINEAR) + + if output_type == "pil": + detected_map = Image.fromarray(detected_map) + + return detected_map diff --git a/modules/control/proc/openpose/body.py b/modules/control/proc/openpose/body.py new file mode 100644 index 000000000..01a23f3d8 --- /dev/null +++ b/modules/control/proc/openpose/body.py @@ -0,0 +1,254 @@ +import math +from typing import List, NamedTuple, Union +import numpy as np +import torch +from scipy.ndimage.filters import gaussian_filter +from . import util +from .model import bodypose_model + + +class Keypoint(NamedTuple): + x: float + y: float + score: float = 1.0 + id: int = -1 + + +class BodyResult(NamedTuple): + # Note: Using `Union` instead of `|` operator as the ladder is a Python + # 3.10 feature. + # Annotator code should be Python 3.8 Compatible, as controlnet repo uses + # Python 3.8 environment. + # https://github.com/lllyasviel/ControlNet/blob/d3284fcd0972c510635a4f5abe2eeb71dc0de524/environment.yaml#L6 + keypoints: List[Union[Keypoint, None]] + total_score: float + total_parts: int + + +class Body(object): + def __init__(self, model_path): + self.model = bodypose_model() + model_dict = util.transfer(self.model, torch.load(model_path)) + self.model.load_state_dict(model_dict) + self.model.eval() + + def to(self, device): + self.model.to(device) + return self + + def __call__(self, oriImg): + device = next(iter(self.model.parameters())).device + # scale_search = [0.5, 1.0, 1.5, 2.0] + scale_search = [0.5] + boxsize = 368 + stride = 8 + padValue = 128 + thre1 = 0.1 + thre2 = 0.05 + multiplier = [x * boxsize / oriImg.shape[0] for x in scale_search] + heatmap_avg = np.zeros((oriImg.shape[0], oriImg.shape[1], 19)) + paf_avg = np.zeros((oriImg.shape[0], oriImg.shape[1], 38)) + + for m in range(len(multiplier)): + scale = multiplier[m] + imageToTest = util.smart_resize_k(oriImg, fx=scale, fy=scale) + imageToTest_padded, pad = util.padRightDownCorner(imageToTest, stride, padValue) + im = np.transpose(np.float32(imageToTest_padded[:, :, :, np.newaxis]), (3, 2, 0, 1)) / 256 - 0.5 + im = np.ascontiguousarray(im) + + data = torch.from_numpy(im).float() + data = data.to(device) + # data = data.permute([2, 0, 1]).unsqueeze(0).float() + Mconv7_stage6_L1, Mconv7_stage6_L2 = self.model(data) + Mconv7_stage6_L1 = Mconv7_stage6_L1.cpu().numpy() + Mconv7_stage6_L2 = Mconv7_stage6_L2.cpu().numpy() + + # extract outputs, resize, and remove padding + # heatmap = np.transpose(np.squeeze(net.blobs[output_blobs.keys()[1]].data), (1, 2, 0)) # output 1 is heatmaps + heatmap = np.transpose(np.squeeze(Mconv7_stage6_L2), (1, 2, 0)) # output 1 is heatmaps + heatmap = util.smart_resize_k(heatmap, fx=stride, fy=stride) + heatmap = heatmap[:imageToTest_padded.shape[0] - pad[2], :imageToTest_padded.shape[1] - pad[3], :] + heatmap = util.smart_resize(heatmap, (oriImg.shape[0], oriImg.shape[1])) + + # paf = np.transpose(np.squeeze(net.blobs[output_blobs.keys()[0]].data), (1, 2, 0)) # output 0 is PAFs + paf = np.transpose(np.squeeze(Mconv7_stage6_L1), (1, 2, 0)) # output 0 is PAFs + paf = util.smart_resize_k(paf, fx=stride, fy=stride) + paf = paf[:imageToTest_padded.shape[0] - pad[2], :imageToTest_padded.shape[1] - pad[3], :] + paf = util.smart_resize(paf, (oriImg.shape[0], oriImg.shape[1])) + + heatmap_avg += heatmap_avg + heatmap / len(multiplier) + paf_avg += paf / len(multiplier) + + all_peaks = [] + peak_counter = 0 + + for part in range(18): + map_ori = heatmap_avg[:, :, part] + one_heatmap = gaussian_filter(map_ori, sigma=3) + + map_left = np.zeros(one_heatmap.shape) + map_left[1:, :] = one_heatmap[:-1, :] + map_right = np.zeros(one_heatmap.shape) + map_right[:-1, :] = one_heatmap[1:, :] + map_up = np.zeros(one_heatmap.shape) + map_up[:, 1:] = one_heatmap[:, :-1] + map_down = np.zeros(one_heatmap.shape) + map_down[:, :-1] = one_heatmap[:, 1:] + + peaks_binary = np.logical_and.reduce( + (one_heatmap >= map_left, one_heatmap >= map_right, one_heatmap >= map_up, one_heatmap >= map_down, one_heatmap > thre1)) + peaks = list(zip(np.nonzero(peaks_binary)[1], np.nonzero(peaks_binary)[0])) # note reverse + peaks_with_score = [x + (map_ori[x[1], x[0]],) for x in peaks] + peak_id = range(peak_counter, peak_counter + len(peaks)) + peaks_with_score_and_id = [peaks_with_score[i] + (peak_id[i],) for i in range(len(peak_id))] + + all_peaks.append(peaks_with_score_and_id) + peak_counter += len(peaks) + + # find connection in the specified sequence, center 29 is in the position 15 + limbSeq = [[2, 3], [2, 6], [3, 4], [4, 5], [6, 7], [7, 8], [2, 9], [9, 10], \ + [10, 11], [2, 12], [12, 13], [13, 14], [2, 1], [1, 15], [15, 17], \ + [1, 16], [16, 18], [3, 17], [6, 18]] + # the middle joints heatmap correpondence + mapIdx = [[31, 32], [39, 40], [33, 34], [35, 36], [41, 42], [43, 44], [19, 20], [21, 22], \ + [23, 24], [25, 26], [27, 28], [29, 30], [47, 48], [49, 50], [53, 54], [51, 52], \ + [55, 56], [37, 38], [45, 46]] + + connection_all = [] + special_k = [] + mid_num = 10 + + for k in range(len(mapIdx)): + score_mid = paf_avg[:, :, [x - 19 for x in mapIdx[k]]] + candA = all_peaks[limbSeq[k][0] - 1] + candB = all_peaks[limbSeq[k][1] - 1] + nA = len(candA) + nB = len(candB) + indexA, indexB = limbSeq[k] + if (nA != 0 and nB != 0): + connection_candidate = [] + for i in range(nA): + for j in range(nB): + vec = np.subtract(candB[j][:2], candA[i][:2]) + norm = math.sqrt(vec[0] * vec[0] + vec[1] * vec[1]) + norm = max(0.001, norm) + vec = np.divide(vec, norm) + + startend = list(zip(np.linspace(candA[i][0], candB[j][0], num=mid_num), \ + np.linspace(candA[i][1], candB[j][1], num=mid_num))) + + vec_x = np.array([score_mid[int(round(startend[x][1])), int(round(startend[x][0])), 0] for x in range(len(startend))]) + vec_y = np.array([score_mid[int(round(startend[x][1])), int(round(startend[x][0])), 1] for x in range(len(startend))]) + + score_midpts = np.multiply(vec_x, vec[0]) + np.multiply(vec_y, vec[1]) + score_with_dist_prior = sum(score_midpts) / len(score_midpts) + min( + 0.5 * oriImg.shape[0] / norm - 1, 0) + criterion1 = len(np.nonzero(score_midpts > thre2)[0]) > 0.8 * len(score_midpts) + criterion2 = score_with_dist_prior > 0 + if criterion1 and criterion2: + connection_candidate.append( + [i, j, score_with_dist_prior, score_with_dist_prior + candA[i][2] + candB[j][2]]) + + connection_candidate = sorted(connection_candidate, key=lambda x: x[2], reverse=True) + connection = np.zeros((0, 5)) + for c in range(len(connection_candidate)): + i, j, s = connection_candidate[c][0:3] + if (i not in connection[:, 3] and j not in connection[:, 4]): + connection = np.vstack([connection, [candA[i][3], candB[j][3], s, i, j]]) + if len(connection) >= min(nA, nB): + break + + connection_all.append(connection) + else: + special_k.append(k) + connection_all.append([]) + + # last number in each row is the total parts number of that person + # the second last number in each row is the score of the overall configuration + subset = -1 * np.ones((0, 20)) + candidate = np.array([item for sublist in all_peaks for item in sublist]) + + for k in range(len(mapIdx)): + if k not in special_k: + partAs = connection_all[k][:, 0] + partBs = connection_all[k][:, 1] + indexA, indexB = np.array(limbSeq[k]) - 1 + + for i in range(len(connection_all[k])): # = 1:size(temp,1) + found = 0 + subset_idx = [-1, -1] + for j in range(len(subset)): # 1:size(subset,1): + if subset[j][indexA] == partAs[i] or subset[j][indexB] == partBs[i]: + subset_idx[found] = j + found += 1 + + if found == 1: + j = subset_idx[0] + if subset[j][indexB] != partBs[i]: + subset[j][indexB] = partBs[i] + subset[j][-1] += 1 + subset[j][-2] += candidate[partBs[i].astype(int), 2] + connection_all[k][i][2] + elif found == 2: # if found 2 and disjoint, merge them + j1, j2 = subset_idx + membership = ((subset[j1] >= 0).astype(int) + (subset[j2] >= 0).astype(int))[:-2] + if len(np.nonzero(membership == 2)[0]) == 0: # merge + subset[j1][:-2] += (subset[j2][:-2] + 1) + subset[j1][-2:] += subset[j2][-2:] + subset[j1][-2] += connection_all[k][i][2] + subset = np.delete(subset, j2, 0) + else: # as like found == 1 + subset[j1][indexB] = partBs[i] + subset[j1][-1] += 1 + subset[j1][-2] += candidate[partBs[i].astype(int), 2] + connection_all[k][i][2] + + # if find no partA in the subset, create a new subset + elif not found and k < 17: + row = -1 * np.ones(20) + row[indexA] = partAs[i] + row[indexB] = partBs[i] + row[-1] = 2 + row[-2] = sum(candidate[connection_all[k][i, :2].astype(int), 2]) + connection_all[k][i][2] + subset = np.vstack([subset, row]) + # delete some rows of subset which has few parts occur + deleteIdx = [] + for i in range(len(subset)): + if subset[i][-1] < 4 or subset[i][-2] / subset[i][-1] < 0.4: + deleteIdx.append(i) + subset = np.delete(subset, deleteIdx, axis=0) + + # subset: n*20 array, 0-17 is the index in candidate, 18 is the total score, 19 is the total parts + # candidate: x, y, score, id + return candidate, subset + + @staticmethod + def format_body_result(candidate: np.ndarray, subset: np.ndarray) -> List[BodyResult]: + """ + Format the body results from the candidate and subset arrays into a list of BodyResult objects. + + Args: + candidate (np.ndarray): An array of candidates containing the x, y coordinates, score, and id + for each body part. + subset (np.ndarray): An array of subsets containing indices to the candidate array for each + person detected. The last two columns of each row hold the total score and total parts + of the person. + + Returns: + List[BodyResult]: A list of BodyResult objects, where each object represents a person with + detected keypoints, total score, and total parts. + """ + return [ + BodyResult( + keypoints=[ + Keypoint( + x=candidate[candidate_index][0], + y=candidate[candidate_index][1], + score=candidate[candidate_index][2], + id=candidate[candidate_index][3] + ) if candidate_index != -1 else None + for candidate_index in person[:18].astype(int) + ], + total_score=person[18], + total_parts=person[19] + ) + for person in subset + ] diff --git a/modules/control/proc/openpose/face.py b/modules/control/proc/openpose/face.py new file mode 100644 index 000000000..e8e34451c --- /dev/null +++ b/modules/control/proc/openpose/face.py @@ -0,0 +1,360 @@ +import numpy as np +import torch +import torch.nn.functional as F +from torch.nn import Conv2d, MaxPool2d, Module, ReLU, init +from torchvision.transforms import ToPILImage, ToTensor + +from . import util + + +class FaceNet(Module): + """Model the cascading heatmaps. """ + def __init__(self): + super(FaceNet, self).__init__() + # cnn to make feature map + self.relu = ReLU() + self.max_pooling_2d = MaxPool2d(kernel_size=2, stride=2) + self.conv1_1 = Conv2d(in_channels=3, out_channels=64, + kernel_size=3, stride=1, padding=1) + self.conv1_2 = Conv2d( + in_channels=64, out_channels=64, kernel_size=3, stride=1, + padding=1) + self.conv2_1 = Conv2d( + in_channels=64, out_channels=128, kernel_size=3, stride=1, + padding=1) + self.conv2_2 = Conv2d( + in_channels=128, out_channels=128, kernel_size=3, stride=1, + padding=1) + self.conv3_1 = Conv2d( + in_channels=128, out_channels=256, kernel_size=3, stride=1, + padding=1) + self.conv3_2 = Conv2d( + in_channels=256, out_channels=256, kernel_size=3, stride=1, + padding=1) + self.conv3_3 = Conv2d( + in_channels=256, out_channels=256, kernel_size=3, stride=1, + padding=1) + self.conv3_4 = Conv2d( + in_channels=256, out_channels=256, kernel_size=3, stride=1, + padding=1) + self.conv4_1 = Conv2d( + in_channels=256, out_channels=512, kernel_size=3, stride=1, + padding=1) + self.conv4_2 = Conv2d( + in_channels=512, out_channels=512, kernel_size=3, stride=1, + padding=1) + self.conv4_3 = Conv2d( + in_channels=512, out_channels=512, kernel_size=3, stride=1, + padding=1) + self.conv4_4 = Conv2d( + in_channels=512, out_channels=512, kernel_size=3, stride=1, + padding=1) + self.conv5_1 = Conv2d( + in_channels=512, out_channels=512, kernel_size=3, stride=1, + padding=1) + self.conv5_2 = Conv2d( + in_channels=512, out_channels=512, kernel_size=3, stride=1, + padding=1) + self.conv5_3_CPM = Conv2d( + in_channels=512, out_channels=128, kernel_size=3, stride=1, + padding=1) + + # stage1 + self.conv6_1_CPM = Conv2d( + in_channels=128, out_channels=512, kernel_size=1, stride=1, + padding=0) + self.conv6_2_CPM = Conv2d( + in_channels=512, out_channels=71, kernel_size=1, stride=1, + padding=0) + + # stage2 + self.Mconv1_stage2 = Conv2d( + in_channels=199, out_channels=128, kernel_size=7, stride=1, + padding=3) + self.Mconv2_stage2 = Conv2d( + in_channels=128, out_channels=128, kernel_size=7, stride=1, + padding=3) + self.Mconv3_stage2 = Conv2d( + in_channels=128, out_channels=128, kernel_size=7, stride=1, + padding=3) + self.Mconv4_stage2 = Conv2d( + in_channels=128, out_channels=128, kernel_size=7, stride=1, + padding=3) + self.Mconv5_stage2 = Conv2d( + in_channels=128, out_channels=128, kernel_size=7, stride=1, + padding=3) + self.Mconv6_stage2 = Conv2d( + in_channels=128, out_channels=128, kernel_size=1, stride=1, + padding=0) + self.Mconv7_stage2 = Conv2d( + in_channels=128, out_channels=71, kernel_size=1, stride=1, + padding=0) + + # stage3 + self.Mconv1_stage3 = Conv2d( + in_channels=199, out_channels=128, kernel_size=7, stride=1, + padding=3) + self.Mconv2_stage3 = Conv2d( + in_channels=128, out_channels=128, kernel_size=7, stride=1, + padding=3) + self.Mconv3_stage3 = Conv2d( + in_channels=128, out_channels=128, kernel_size=7, stride=1, + padding=3) + self.Mconv4_stage3 = Conv2d( + in_channels=128, out_channels=128, kernel_size=7, stride=1, + padding=3) + self.Mconv5_stage3 = Conv2d( + in_channels=128, out_channels=128, kernel_size=7, stride=1, + padding=3) + self.Mconv6_stage3 = Conv2d( + in_channels=128, out_channels=128, kernel_size=1, stride=1, + padding=0) + self.Mconv7_stage3 = Conv2d( + in_channels=128, out_channels=71, kernel_size=1, stride=1, + padding=0) + + # stage4 + self.Mconv1_stage4 = Conv2d( + in_channels=199, out_channels=128, kernel_size=7, stride=1, + padding=3) + self.Mconv2_stage4 = Conv2d( + in_channels=128, out_channels=128, kernel_size=7, stride=1, + padding=3) + self.Mconv3_stage4 = Conv2d( + in_channels=128, out_channels=128, kernel_size=7, stride=1, + padding=3) + self.Mconv4_stage4 = Conv2d( + in_channels=128, out_channels=128, kernel_size=7, stride=1, + padding=3) + self.Mconv5_stage4 = Conv2d( + in_channels=128, out_channels=128, kernel_size=7, stride=1, + padding=3) + self.Mconv6_stage4 = Conv2d( + in_channels=128, out_channels=128, kernel_size=1, stride=1, + padding=0) + self.Mconv7_stage4 = Conv2d( + in_channels=128, out_channels=71, kernel_size=1, stride=1, + padding=0) + + # stage5 + self.Mconv1_stage5 = Conv2d( + in_channels=199, out_channels=128, kernel_size=7, stride=1, + padding=3) + self.Mconv2_stage5 = Conv2d( + in_channels=128, out_channels=128, kernel_size=7, stride=1, + padding=3) + self.Mconv3_stage5 = Conv2d( + in_channels=128, out_channels=128, kernel_size=7, stride=1, + padding=3) + self.Mconv4_stage5 = Conv2d( + in_channels=128, out_channels=128, kernel_size=7, stride=1, + padding=3) + self.Mconv5_stage5 = Conv2d( + in_channels=128, out_channels=128, kernel_size=7, stride=1, + padding=3) + self.Mconv6_stage5 = Conv2d( + in_channels=128, out_channels=128, kernel_size=1, stride=1, + padding=0) + self.Mconv7_stage5 = Conv2d( + in_channels=128, out_channels=71, kernel_size=1, stride=1, + padding=0) + + # stage6 + self.Mconv1_stage6 = Conv2d( + in_channels=199, out_channels=128, kernel_size=7, stride=1, + padding=3) + self.Mconv2_stage6 = Conv2d( + in_channels=128, out_channels=128, kernel_size=7, stride=1, + padding=3) + self.Mconv3_stage6 = Conv2d( + in_channels=128, out_channels=128, kernel_size=7, stride=1, + padding=3) + self.Mconv4_stage6 = Conv2d( + in_channels=128, out_channels=128, kernel_size=7, stride=1, + padding=3) + self.Mconv5_stage6 = Conv2d( + in_channels=128, out_channels=128, kernel_size=7, stride=1, + padding=3) + self.Mconv6_stage6 = Conv2d( + in_channels=128, out_channels=128, kernel_size=1, stride=1, + padding=0) + self.Mconv7_stage6 = Conv2d( + in_channels=128, out_channels=71, kernel_size=1, stride=1, + padding=0) + + for m in self.modules(): + if isinstance(m, Conv2d): + init.constant_(m.bias, 0) + + def forward(self, x): + """Return a list of heatmaps.""" + heatmaps = [] + + h = self.relu(self.conv1_1(x)) + h = self.relu(self.conv1_2(h)) + h = self.max_pooling_2d(h) + h = self.relu(self.conv2_1(h)) + h = self.relu(self.conv2_2(h)) + h = self.max_pooling_2d(h) + h = self.relu(self.conv3_1(h)) + h = self.relu(self.conv3_2(h)) + h = self.relu(self.conv3_3(h)) + h = self.relu(self.conv3_4(h)) + h = self.max_pooling_2d(h) + h = self.relu(self.conv4_1(h)) + h = self.relu(self.conv4_2(h)) + h = self.relu(self.conv4_3(h)) + h = self.relu(self.conv4_4(h)) + h = self.relu(self.conv5_1(h)) + h = self.relu(self.conv5_2(h)) + h = self.relu(self.conv5_3_CPM(h)) + feature_map = h + + # stage1 + h = self.relu(self.conv6_1_CPM(h)) + h = self.conv6_2_CPM(h) + heatmaps.append(h) + + # stage2 + h = torch.cat([h, feature_map], dim=1) # channel concat + h = self.relu(self.Mconv1_stage2(h)) + h = self.relu(self.Mconv2_stage2(h)) + h = self.relu(self.Mconv3_stage2(h)) + h = self.relu(self.Mconv4_stage2(h)) + h = self.relu(self.Mconv5_stage2(h)) + h = self.relu(self.Mconv6_stage2(h)) + h = self.Mconv7_stage2(h) + heatmaps.append(h) + + # stage3 + h = torch.cat([h, feature_map], dim=1) # channel concat + h = self.relu(self.Mconv1_stage3(h)) + h = self.relu(self.Mconv2_stage3(h)) + h = self.relu(self.Mconv3_stage3(h)) + h = self.relu(self.Mconv4_stage3(h)) + h = self.relu(self.Mconv5_stage3(h)) + h = self.relu(self.Mconv6_stage3(h)) + h = self.Mconv7_stage3(h) + heatmaps.append(h) + + # stage4 + h = torch.cat([h, feature_map], dim=1) # channel concat + h = self.relu(self.Mconv1_stage4(h)) + h = self.relu(self.Mconv2_stage4(h)) + h = self.relu(self.Mconv3_stage4(h)) + h = self.relu(self.Mconv4_stage4(h)) + h = self.relu(self.Mconv5_stage4(h)) + h = self.relu(self.Mconv6_stage4(h)) + h = self.Mconv7_stage4(h) + heatmaps.append(h) + + # stage5 + h = torch.cat([h, feature_map], dim=1) # channel concat + h = self.relu(self.Mconv1_stage5(h)) + h = self.relu(self.Mconv2_stage5(h)) + h = self.relu(self.Mconv3_stage5(h)) + h = self.relu(self.Mconv4_stage5(h)) + h = self.relu(self.Mconv5_stage5(h)) + h = self.relu(self.Mconv6_stage5(h)) + h = self.Mconv7_stage5(h) + heatmaps.append(h) + + # stage6 + h = torch.cat([h, feature_map], dim=1) # channel concat + h = self.relu(self.Mconv1_stage6(h)) + h = self.relu(self.Mconv2_stage6(h)) + h = self.relu(self.Mconv3_stage6(h)) + h = self.relu(self.Mconv4_stage6(h)) + h = self.relu(self.Mconv5_stage6(h)) + h = self.relu(self.Mconv6_stage6(h)) + h = self.Mconv7_stage6(h) + heatmaps.append(h) + + return heatmaps + + +TOTEN = ToTensor() +TOPIL = ToPILImage() + + +params = { + 'gaussian_sigma': 2.5, + 'inference_img_size': 736, # 368, 736, 1312 + 'heatmap_peak_thresh': 0.1, + 'crop_scale': 1.5, + 'line_indices': [ + [0, 1], [1, 2], [2, 3], [3, 4], [4, 5], [5, 6], + [6, 7], [7, 8], [8, 9], [9, 10], [10, 11], [11, 12], [12, 13], + [13, 14], [14, 15], [15, 16], + [17, 18], [18, 19], [19, 20], [20, 21], + [22, 23], [23, 24], [24, 25], [25, 26], + [27, 28], [28, 29], [29, 30], + [31, 32], [32, 33], [33, 34], [34, 35], + [36, 37], [37, 38], [38, 39], [39, 40], [40, 41], [41, 36], + [42, 43], [43, 44], [44, 45], [45, 46], [46, 47], [47, 42], + [48, 49], [49, 50], [50, 51], [51, 52], [52, 53], [53, 54], + [54, 55], [55, 56], [56, 57], [57, 58], [58, 59], [59, 48], + [60, 61], [61, 62], [62, 63], [63, 64], [64, 65], [65, 66], + [66, 67], [67, 60] + ], +} + + +class Face(object): + """ + The OpenPose face landmark detector model. + + Args: + inference_size: set the size of the inference image size, suggested: + 368, 736, 1312, default 736 + gaussian_sigma: blur the heatmaps, default 2.5 + heatmap_peak_thresh: return landmark if over threshold, default 0.1 + + """ + def __init__(self, face_model_path, + inference_size=None, + gaussian_sigma=None, + heatmap_peak_thresh=None): + self.inference_size = inference_size or params["inference_img_size"] + self.sigma = gaussian_sigma or params['gaussian_sigma'] + self.threshold = heatmap_peak_thresh or params["heatmap_peak_thresh"] + self.model = FaceNet() + self.model.load_state_dict(torch.load(face_model_path)) + self.model.eval() + + def to(self, device): + self.model.to(device) + return self + + def __call__(self, face_img): + device = next(iter(self.model.parameters())).device + H, W, C = face_img.shape + + w_size = 384 + x_data = torch.from_numpy(util.smart_resize(face_img, (w_size, w_size))).permute([2, 0, 1]) / 256.0 - 0.5 + + x_data = x_data.to(device) + + hs = self.model(x_data[None, ...]) + heatmaps = F.interpolate( + hs[-1], + (H, W), + mode='bilinear', align_corners=True).cpu().numpy()[0] + return heatmaps + + def compute_peaks_from_heatmaps(self, heatmaps): + all_peaks = [] + for part in range(heatmaps.shape[0]): + map_ori = heatmaps[part].copy() + binary = np.ascontiguousarray(map_ori > 0.05, dtype=np.uint8) + + if np.sum(binary) == 0: + continue + + positions = np.where(binary > 0.5) + intensities = map_ori[positions] + mi = np.argmax(intensities) + y, x = positions[0][mi], positions[1][mi] + all_peaks.append([x, y]) + + return np.array(all_peaks) diff --git a/modules/control/proc/openpose/hand.py b/modules/control/proc/openpose/hand.py new file mode 100644 index 000000000..78e00213e --- /dev/null +++ b/modules/control/proc/openpose/hand.py @@ -0,0 +1,89 @@ +import cv2 +import numpy as np +import torch +from scipy.ndimage.filters import gaussian_filter +from skimage.measure import label + +from . import util +from .model import handpose_model + + +class Hand(object): + def __init__(self, model_path): + self.model = handpose_model() + model_dict = util.transfer(self.model, torch.load(model_path)) + self.model.load_state_dict(model_dict) + self.model.eval() + + def to(self, device): + self.model.to(device) + return self + + def __call__(self, oriImgRaw): + device = next(iter(self.model.parameters())).device + scale_search = [0.5, 1.0, 1.5, 2.0] + # scale_search = [0.5] + boxsize = 368 + stride = 8 + padValue = 128 + thre = 0.05 + multiplier = [x * boxsize for x in scale_search] + + wsize = 128 + heatmap_avg = np.zeros((wsize, wsize, 22)) + + Hr, Wr, Cr = oriImgRaw.shape + + oriImg = cv2.GaussianBlur(oriImgRaw, (0, 0), 0.8) + + for m in range(len(multiplier)): + scale = multiplier[m] + imageToTest = util.smart_resize(oriImg, (scale, scale)) + + imageToTest_padded, pad = util.padRightDownCorner(imageToTest, stride, padValue) + im = np.transpose(np.float32(imageToTest_padded[:, :, :, np.newaxis]), (3, 2, 0, 1)) / 256 - 0.5 + im = np.ascontiguousarray(im) + + data = torch.from_numpy(im).float() + data = data.to(device) + + output = self.model(data).cpu().numpy() + + # extract outputs, resize, and remove padding + heatmap = np.transpose(np.squeeze(output), (1, 2, 0)) # output 1 is heatmaps + heatmap = util.smart_resize_k(heatmap, fx=stride, fy=stride) + heatmap = heatmap[:imageToTest_padded.shape[0] - pad[2], :imageToTest_padded.shape[1] - pad[3], :] + heatmap = util.smart_resize(heatmap, (wsize, wsize)) + + heatmap_avg += heatmap / len(multiplier) + + all_peaks = [] + for part in range(21): + map_ori = heatmap_avg[:, :, part] + one_heatmap = gaussian_filter(map_ori, sigma=3) + binary = np.ascontiguousarray(one_heatmap > thre, dtype=np.uint8) + + if np.sum(binary) == 0: + all_peaks.append([0, 0]) + continue + label_img, label_numbers = label(binary, return_num=True, connectivity=binary.ndim) + max_index = np.argmax([np.sum(map_ori[label_img == i]) for i in range(1, label_numbers + 1)]) + 1 + label_img[label_img != max_index] = 0 + map_ori[label_img == 0] = 0 + + y, x = util.npmax(map_ori) + y = int(float(y) * float(Hr) / float(wsize)) + x = int(float(x) * float(Wr) / float(wsize)) + all_peaks.append([x, y]) + return np.array(all_peaks) + +if __name__ == "__main__": + hand_estimation = Hand('../model/hand_pose_model.pth') + + # test_image = '../images/hand.jpg' + test_image = '../images/hand.jpg' + oriImg = cv2.imread(test_image) # B,G,R order + peaks = hand_estimation(oriImg) + canvas = util.draw_handpose(oriImg, peaks, True) + cv2.imshow('', canvas) + cv2.waitKey(0) diff --git a/modules/control/proc/openpose/model.py b/modules/control/proc/openpose/model.py new file mode 100644 index 000000000..cfa390c14 --- /dev/null +++ b/modules/control/proc/openpose/model.py @@ -0,0 +1,215 @@ +from collections import OrderedDict +import torch +import torch.nn as nn + +def make_layers(block, no_relu_layers): + layers = [] + for layer_name, v in block.items(): + if 'pool' in layer_name: + layer = nn.MaxPool2d(kernel_size=v[0], stride=v[1], + padding=v[2]) + layers.append((layer_name, layer)) + else: + conv2d = nn.Conv2d(in_channels=v[0], out_channels=v[1], + kernel_size=v[2], stride=v[3], + padding=v[4]) + layers.append((layer_name, conv2d)) + if layer_name not in no_relu_layers: + layers.append(('relu_'+layer_name, nn.ReLU(inplace=True))) + + return nn.Sequential(OrderedDict(layers)) + +class bodypose_model(nn.Module): + def __init__(self): + super(bodypose_model, self).__init__() + + # these layers have no relu layer + no_relu_layers = ['conv5_5_CPM_L1', 'conv5_5_CPM_L2', 'Mconv7_stage2_L1',\ + 'Mconv7_stage2_L2', 'Mconv7_stage3_L1', 'Mconv7_stage3_L2',\ + 'Mconv7_stage4_L1', 'Mconv7_stage4_L2', 'Mconv7_stage5_L1',\ + 'Mconv7_stage5_L2', 'Mconv7_stage6_L1', 'Mconv7_stage6_L1'] + blocks = {} + block0 = OrderedDict([ + ('conv1_1', [3, 64, 3, 1, 1]), + ('conv1_2', [64, 64, 3, 1, 1]), + ('pool1_stage1', [2, 2, 0]), + ('conv2_1', [64, 128, 3, 1, 1]), + ('conv2_2', [128, 128, 3, 1, 1]), + ('pool2_stage1', [2, 2, 0]), + ('conv3_1', [128, 256, 3, 1, 1]), + ('conv3_2', [256, 256, 3, 1, 1]), + ('conv3_3', [256, 256, 3, 1, 1]), + ('conv3_4', [256, 256, 3, 1, 1]), + ('pool3_stage1', [2, 2, 0]), + ('conv4_1', [256, 512, 3, 1, 1]), + ('conv4_2', [512, 512, 3, 1, 1]), + ('conv4_3_CPM', [512, 256, 3, 1, 1]), + ('conv4_4_CPM', [256, 128, 3, 1, 1]) + ]) + + + # Stage 1 + block1_1 = OrderedDict([ + ('conv5_1_CPM_L1', [128, 128, 3, 1, 1]), + ('conv5_2_CPM_L1', [128, 128, 3, 1, 1]), + ('conv5_3_CPM_L1', [128, 128, 3, 1, 1]), + ('conv5_4_CPM_L1', [128, 512, 1, 1, 0]), + ('conv5_5_CPM_L1', [512, 38, 1, 1, 0]) + ]) + + block1_2 = OrderedDict([ + ('conv5_1_CPM_L2', [128, 128, 3, 1, 1]), + ('conv5_2_CPM_L2', [128, 128, 3, 1, 1]), + ('conv5_3_CPM_L2', [128, 128, 3, 1, 1]), + ('conv5_4_CPM_L2', [128, 512, 1, 1, 0]), + ('conv5_5_CPM_L2', [512, 19, 1, 1, 0]) + ]) + blocks['block1_1'] = block1_1 + blocks['block1_2'] = block1_2 + + self.model0 = make_layers(block0, no_relu_layers) + + # Stages 2 - 6 + for i in range(2, 7): + blocks['block%d_1' % i] = OrderedDict([ + ('Mconv1_stage%d_L1' % i, [185, 128, 7, 1, 3]), + ('Mconv2_stage%d_L1' % i, [128, 128, 7, 1, 3]), + ('Mconv3_stage%d_L1' % i, [128, 128, 7, 1, 3]), + ('Mconv4_stage%d_L1' % i, [128, 128, 7, 1, 3]), + ('Mconv5_stage%d_L1' % i, [128, 128, 7, 1, 3]), + ('Mconv6_stage%d_L1' % i, [128, 128, 1, 1, 0]), + ('Mconv7_stage%d_L1' % i, [128, 38, 1, 1, 0]) + ]) + + blocks['block%d_2' % i] = OrderedDict([ + ('Mconv1_stage%d_L2' % i, [185, 128, 7, 1, 3]), + ('Mconv2_stage%d_L2' % i, [128, 128, 7, 1, 3]), + ('Mconv3_stage%d_L2' % i, [128, 128, 7, 1, 3]), + ('Mconv4_stage%d_L2' % i, [128, 128, 7, 1, 3]), + ('Mconv5_stage%d_L2' % i, [128, 128, 7, 1, 3]), + ('Mconv6_stage%d_L2' % i, [128, 128, 1, 1, 0]), + ('Mconv7_stage%d_L2' % i, [128, 19, 1, 1, 0]) + ]) + + for k in blocks.keys(): + blocks[k] = make_layers(blocks[k], no_relu_layers) + + self.model1_1 = blocks['block1_1'] + self.model2_1 = blocks['block2_1'] + self.model3_1 = blocks['block3_1'] + self.model4_1 = blocks['block4_1'] + self.model5_1 = blocks['block5_1'] + self.model6_1 = blocks['block6_1'] + + self.model1_2 = blocks['block1_2'] + self.model2_2 = blocks['block2_2'] + self.model3_2 = blocks['block3_2'] + self.model4_2 = blocks['block4_2'] + self.model5_2 = blocks['block5_2'] + self.model6_2 = blocks['block6_2'] + + + def forward(self, x): + + out1 = self.model0(x) + + out1_1 = self.model1_1(out1) + out1_2 = self.model1_2(out1) + out2 = torch.cat([out1_1, out1_2, out1], 1) + + out2_1 = self.model2_1(out2) + out2_2 = self.model2_2(out2) + out3 = torch.cat([out2_1, out2_2, out1], 1) + + out3_1 = self.model3_1(out3) + out3_2 = self.model3_2(out3) + out4 = torch.cat([out3_1, out3_2, out1], 1) + + out4_1 = self.model4_1(out4) + out4_2 = self.model4_2(out4) + out5 = torch.cat([out4_1, out4_2, out1], 1) + + out5_1 = self.model5_1(out5) + out5_2 = self.model5_2(out5) + out6 = torch.cat([out5_1, out5_2, out1], 1) + + out6_1 = self.model6_1(out6) + out6_2 = self.model6_2(out6) + + return out6_1, out6_2 + +class handpose_model(nn.Module): + def __init__(self): + super(handpose_model, self).__init__() + + # these layers have no relu layer + no_relu_layers = ['conv6_2_CPM', 'Mconv7_stage2', 'Mconv7_stage3',\ + 'Mconv7_stage4', 'Mconv7_stage5', 'Mconv7_stage6'] + # stage 1 + block1_0 = OrderedDict([ + ('conv1_1', [3, 64, 3, 1, 1]), + ('conv1_2', [64, 64, 3, 1, 1]), + ('pool1_stage1', [2, 2, 0]), + ('conv2_1', [64, 128, 3, 1, 1]), + ('conv2_2', [128, 128, 3, 1, 1]), + ('pool2_stage1', [2, 2, 0]), + ('conv3_1', [128, 256, 3, 1, 1]), + ('conv3_2', [256, 256, 3, 1, 1]), + ('conv3_3', [256, 256, 3, 1, 1]), + ('conv3_4', [256, 256, 3, 1, 1]), + ('pool3_stage1', [2, 2, 0]), + ('conv4_1', [256, 512, 3, 1, 1]), + ('conv4_2', [512, 512, 3, 1, 1]), + ('conv4_3', [512, 512, 3, 1, 1]), + ('conv4_4', [512, 512, 3, 1, 1]), + ('conv5_1', [512, 512, 3, 1, 1]), + ('conv5_2', [512, 512, 3, 1, 1]), + ('conv5_3_CPM', [512, 128, 3, 1, 1]) + ]) + + block1_1 = OrderedDict([ + ('conv6_1_CPM', [128, 512, 1, 1, 0]), + ('conv6_2_CPM', [512, 22, 1, 1, 0]) + ]) + + blocks = {} + blocks['block1_0'] = block1_0 + blocks['block1_1'] = block1_1 + + # stage 2-6 + for i in range(2, 7): + blocks['block%d' % i] = OrderedDict([ + ('Mconv1_stage%d' % i, [150, 128, 7, 1, 3]), + ('Mconv2_stage%d' % i, [128, 128, 7, 1, 3]), + ('Mconv3_stage%d' % i, [128, 128, 7, 1, 3]), + ('Mconv4_stage%d' % i, [128, 128, 7, 1, 3]), + ('Mconv5_stage%d' % i, [128, 128, 7, 1, 3]), + ('Mconv6_stage%d' % i, [128, 128, 1, 1, 0]), + ('Mconv7_stage%d' % i, [128, 22, 1, 1, 0]) + ]) + + for k in blocks.keys(): + blocks[k] = make_layers(blocks[k], no_relu_layers) + + self.model1_0 = blocks['block1_0'] + self.model1_1 = blocks['block1_1'] + self.model2 = blocks['block2'] + self.model3 = blocks['block3'] + self.model4 = blocks['block4'] + self.model5 = blocks['block5'] + self.model6 = blocks['block6'] + + def forward(self, x): + out1_0 = self.model1_0(x) + out1_1 = self.model1_1(out1_0) + concat_stage2 = torch.cat([out1_1, out1_0], 1) + out_stage2 = self.model2(concat_stage2) + concat_stage3 = torch.cat([out_stage2, out1_0], 1) + out_stage3 = self.model3(concat_stage3) + concat_stage4 = torch.cat([out_stage3, out1_0], 1) + out_stage4 = self.model4(concat_stage4) + concat_stage5 = torch.cat([out_stage4, out1_0], 1) + out_stage5 = self.model5(concat_stage5) + concat_stage6 = torch.cat([out_stage5, out1_0], 1) + out_stage6 = self.model6(concat_stage6) + return out_stage6 diff --git a/modules/control/proc/openpose/util.py b/modules/control/proc/openpose/util.py new file mode 100644 index 000000000..0bba54583 --- /dev/null +++ b/modules/control/proc/openpose/util.py @@ -0,0 +1,386 @@ +from typing import List, Tuple, Union +import math +import numpy as np +import cv2 +from .body import BodyResult, Keypoint + +eps = 0.01 + + +def smart_resize(x, s): + Ht, Wt = s + if x.ndim == 2: + Ho, Wo = x.shape + Co = 1 + else: + Ho, Wo, Co = x.shape + if Co == 3 or Co == 1: + k = float(Ht + Wt) / float(Ho + Wo) + return cv2.resize(x, (int(Wt), int(Ht)), interpolation=cv2.INTER_AREA if k < 1 else cv2.INTER_LANCZOS4) + else: + return np.stack([smart_resize(x[:, :, i], s) for i in range(Co)], axis=2) + + +def smart_resize_k(x, fx, fy): + if x.ndim == 2: + Ho, Wo = x.shape + Co = 1 + else: + Ho, Wo, Co = x.shape + Ht, Wt = Ho * fy, Wo * fx + if Co == 3 or Co == 1: + k = float(Ht + Wt) / float(Ho + Wo) + return cv2.resize(x, (int(Wt), int(Ht)), interpolation=cv2.INTER_AREA if k < 1 else cv2.INTER_LANCZOS4) + else: + return np.stack([smart_resize_k(x[:, :, i], fx, fy) for i in range(Co)], axis=2) + + +def padRightDownCorner(img, stride, padValue): + h = img.shape[0] + w = img.shape[1] + + pad = 4 * [None] + pad[0] = 0 # up + pad[1] = 0 # left + pad[2] = 0 if (h % stride == 0) else stride - (h % stride) # down + pad[3] = 0 if (w % stride == 0) else stride - (w % stride) # right + + img_padded = img + pad_up = np.tile(img_padded[0:1, :, :]*0 + padValue, (pad[0], 1, 1)) + img_padded = np.concatenate((pad_up, img_padded), axis=0) + pad_left = np.tile(img_padded[:, 0:1, :]*0 + padValue, (1, pad[1], 1)) + img_padded = np.concatenate((pad_left, img_padded), axis=1) + pad_down = np.tile(img_padded[-2:-1, :, :]*0 + padValue, (pad[2], 1, 1)) + img_padded = np.concatenate((img_padded, pad_down), axis=0) + pad_right = np.tile(img_padded[:, -2:-1, :]*0 + padValue, (1, pad[3], 1)) + img_padded = np.concatenate((img_padded, pad_right), axis=1) + + return img_padded, pad + + +def transfer(model, model_weights): + transfered_model_weights = {} + for weights_name in model.state_dict().keys(): + transfered_model_weights[weights_name] = model_weights['.'.join(weights_name.split('.')[1:])] + return transfered_model_weights + + +def draw_bodypose(canvas: np.ndarray, keypoints: List[Keypoint]) -> np.ndarray: + """ + Draw keypoints and limbs representing body pose on a given canvas. + + Args: + canvas (np.ndarray): A 3D numpy array representing the canvas (image) on which to draw the body pose. + keypoints (List[Keypoint]): A list of Keypoint objects representing the body keypoints to be drawn. + + Returns: + np.ndarray: A 3D numpy array representing the modified canvas with the drawn body pose. + + Note: + The function expects the x and y coordinates of the keypoints to be normalized between 0 and 1. + """ + H, W, _C = canvas.shape + stickwidth = 4 + + limbSeq = [ + [2, 3], [2, 6], [3, 4], [4, 5], + [6, 7], [7, 8], [2, 9], [9, 10], + [10, 11], [2, 12], [12, 13], [13, 14], + [2, 1], [1, 15], [15, 17], [1, 16], + [16, 18], + ] + + colors = [[255, 0, 0], [255, 85, 0], [255, 170, 0], [255, 255, 0], [170, 255, 0], [85, 255, 0], [0, 255, 0], \ + [0, 255, 85], [0, 255, 170], [0, 255, 255], [0, 170, 255], [0, 85, 255], [0, 0, 255], [85, 0, 255], \ + [170, 0, 255], [255, 0, 255], [255, 0, 170], [255, 0, 85]] + + for (k1_index, k2_index), color in zip(limbSeq, colors): + keypoint1 = keypoints[k1_index - 1] + keypoint2 = keypoints[k2_index - 1] + + if keypoint1 is None or keypoint2 is None: + continue + + Y = np.array([keypoint1.x, keypoint2.x]) * float(W) + X = np.array([keypoint1.y, keypoint2.y]) * float(H) + mX = np.mean(X) + mY = np.mean(Y) + length = ((X[0] - X[1]) ** 2 + (Y[0] - Y[1]) ** 2) ** 0.5 + angle = math.degrees(math.atan2(X[0] - X[1], Y[0] - Y[1])) + polygon = cv2.ellipse2Poly((int(mY), int(mX)), (int(length / 2), stickwidth), int(angle), 0, 360, 1) + cv2.fillConvexPoly(canvas, polygon, [int(float(c) * 0.6) for c in color]) + + for keypoint, color in zip(keypoints, colors): + if keypoint is None: + continue + + x, y = keypoint.x, keypoint.y + x = int(x * W) + y = int(y * H) + cv2.circle(canvas, (int(x), int(y)), 4, color, thickness=-1) + + return canvas + + +def draw_handpose(canvas: np.ndarray, keypoints: Union[List[Keypoint], None]) -> np.ndarray: + import matplotlib as mpl + """ + Draw keypoints and connections representing hand pose on a given canvas. + + Args: + canvas (np.ndarray): A 3D numpy array representing the canvas (image) on which to draw the hand pose. + keypoints (List[Keypoint]| None): A list of Keypoint objects representing the hand keypoints to be drawn + or None if no keypoints are present. + + Returns: + np.ndarray: A 3D numpy array representing the modified canvas with the drawn hand pose. + + Note: + The function expects the x and y coordinates of the keypoints to be normalized between 0 and 1. + """ + if not keypoints: + return canvas + + H, W, _C = canvas.shape + + edges = [[0, 1], [1, 2], [2, 3], [3, 4], [0, 5], [5, 6], [6, 7], [7, 8], [0, 9], [9, 10], \ + [10, 11], [11, 12], [0, 13], [13, 14], [14, 15], [15, 16], [0, 17], [17, 18], [18, 19], [19, 20]] + + for ie, (e1, e2) in enumerate(edges): + k1 = keypoints[e1] + k2 = keypoints[e2] + if k1 is None or k2 is None: + continue + + x1 = int(k1.x * W) + y1 = int(k1.y * H) + x2 = int(k2.x * W) + y2 = int(k2.y * H) + if x1 > eps and y1 > eps and x2 > eps and y2 > eps: + cv2.line(canvas, (x1, y1), (x2, y2), mpl.colors.hsv_to_rgb([ie / float(len(edges)), 1.0, 1.0]) * 255, thickness=2) + + for keypoint in keypoints: + x, y = keypoint.x, keypoint.y + x = int(x * W) + y = int(y * H) + if x > eps and y > eps: + cv2.circle(canvas, (x, y), 4, (0, 0, 255), thickness=-1) + return canvas + + +def draw_facepose(canvas: np.ndarray, keypoints: Union[List[Keypoint], None]) -> np.ndarray: + """ + Draw keypoints representing face pose on a given canvas. + + Args: + canvas (np.ndarray): A 3D numpy array representing the canvas (image) on which to draw the face pose. + keypoints (List[Keypoint]| None): A list of Keypoint objects representing the face keypoints to be drawn + or None if no keypoints are present. + + Returns: + np.ndarray: A 3D numpy array representing the modified canvas with the drawn face pose. + + Note: + The function expects the x and y coordinates of the keypoints to be normalized between 0 and 1. + """ + if not keypoints: + return canvas + + H, W, _C = canvas.shape + for keypoint in keypoints: + x, y = keypoint.x, keypoint.y + x = int(x * W) + y = int(y * H) + if x > eps and y > eps: + cv2.circle(canvas, (x, y), 3, (255, 255, 255), thickness=-1) + return canvas + + +# detect hand according to body pose keypoints +# please refer to https://github.com/CMU-Perceptual-Computing-Lab/openpose/blob/master/src/openpose/hand/handDetector.cpp +def handDetect(body: BodyResult, oriImg) -> List[Tuple[int, int, int, bool]]: + """ + Detect hands in the input body pose keypoints and calculate the bounding box for each hand. + + Args: + body (BodyResult): A BodyResult object containing the detected body pose keypoints. + oriImg (numpy.ndarray): A 3D numpy array representing the original input image. + + Returns: + List[Tuple[int, int, int, bool]]: A list of tuples, each containing the coordinates (x, y) of the top-left + corner of the bounding box, the width (height) of the bounding box, and + a boolean flag indicating whether the hand is a left hand (True) or a + right hand (False). + + Notes: + - The width and height of the bounding boxes are equal since the network requires squared input. + - The minimum bounding box size is 20 pixels. + """ + ratioWristElbow = 0.33 + detect_result = [] + image_height, image_width = oriImg.shape[0:2] + + keypoints = body.keypoints + # right hand: wrist 4, elbow 3, shoulder 2 + # left hand: wrist 7, elbow 6, shoulder 5 + left_shoulder = keypoints[5] + left_elbow = keypoints[6] + left_wrist = keypoints[7] + right_shoulder = keypoints[2] + right_elbow = keypoints[3] + right_wrist = keypoints[4] + + # if any of three not detected + has_left = all(keypoint is not None for keypoint in (left_shoulder, left_elbow, left_wrist)) + has_right = all(keypoint is not None for keypoint in (right_shoulder, right_elbow, right_wrist)) + if not (has_left or has_right): + return [] + + hands = [] + #left hand + if has_left: + hands.append([ + left_shoulder.x, left_shoulder.y, + left_elbow.x, left_elbow.y, + left_wrist.x, left_wrist.y, + True + ]) + # right hand + if has_right: + hands.append([ + right_shoulder.x, right_shoulder.y, + right_elbow.x, right_elbow.y, + right_wrist.x, right_wrist.y, + False + ]) + + for x1, y1, x2, y2, x3, y3, is_left in hands: + # pos_hand = pos_wrist + ratio * (pos_wrist - pos_elbox) = (1 + ratio) * pos_wrist - ratio * pos_elbox + # handRectangle.x = posePtr[wrist*3] + ratioWristElbow * (posePtr[wrist*3] - posePtr[elbow*3]); + # handRectangle.y = posePtr[wrist*3+1] + ratioWristElbow * (posePtr[wrist*3+1] - posePtr[elbow*3+1]); + # const auto distanceWristElbow = getDistance(poseKeypoints, person, wrist, elbow); + # const auto distanceElbowShoulder = getDistance(poseKeypoints, person, elbow, shoulder); + # handRectangle.width = 1.5f * fastMax(distanceWristElbow, 0.9f * distanceElbowShoulder); + x = x3 + ratioWristElbow * (x3 - x2) + y = y3 + ratioWristElbow * (y3 - y2) + distanceWristElbow = math.sqrt((x3 - x2) ** 2 + (y3 - y2) ** 2) + distanceElbowShoulder = math.sqrt((x2 - x1) ** 2 + (y2 - y1) ** 2) + width = 1.5 * max(distanceWristElbow, 0.9 * distanceElbowShoulder) + # x-y refers to the center --> offset to topLeft point + # handRectangle.x -= handRectangle.width / 2.f; + # handRectangle.y -= handRectangle.height / 2.f; + x -= width / 2 + y -= width / 2 # width = height + # overflow the image + if x < 0: + x = 0 + if y < 0: + y = 0 + width1 = width + width2 = width + if x + width > image_width: + width1 = image_width - x + if y + width > image_height: + width2 = image_height - y + width = min(width1, width2) + # the max hand box value is 20 pixels + if width >= 20: + detect_result.append((int(x), int(y), int(width), is_left)) + + ''' + return value: [[x, y, w, True if left hand else False]]. + width=height since the network require squared input. + x, y is the coordinate of top left + ''' + return detect_result + + +# Written by Lvmin +def faceDetect(body: BodyResult, oriImg) -> Union[Tuple[int, int, int], None]: + """ + Detect the face in the input body pose keypoints and calculate the bounding box for the face. + + Args: + body (BodyResult): A BodyResult object containing the detected body pose keypoints. + oriImg (numpy.ndarray): A 3D numpy array representing the original input image. + + Returns: + Tuple[int, int, int] | None: A tuple containing the coordinates (x, y) of the top-left corner of the + bounding box and the width (height) of the bounding box, or None if the + face is not detected or the bounding box width is less than 20 pixels. + + Notes: + - The width and height of the bounding box are equal. + - The minimum bounding box size is 20 pixels. + """ + # left right eye ear 14 15 16 17 + image_height, image_width = oriImg.shape[0:2] + + keypoints = body.keypoints + head = keypoints[0] + left_eye = keypoints[14] + right_eye = keypoints[15] + left_ear = keypoints[16] + right_ear = keypoints[17] + + if head is None or all(keypoint is None for keypoint in (left_eye, right_eye, left_ear, right_ear)): + return None + + width = 0.0 + x0, y0 = head.x, head.y + + if left_eye is not None: + x1, y1 = left_eye.x, left_eye.y + d = max(abs(x0 - x1), abs(y0 - y1)) + width = max(width, d * 3.0) + + if right_eye is not None: + x1, y1 = right_eye.x, right_eye.y + d = max(abs(x0 - x1), abs(y0 - y1)) + width = max(width, d * 3.0) + + if left_ear is not None: + x1, y1 = left_ear.x, left_ear.y + d = max(abs(x0 - x1), abs(y0 - y1)) + width = max(width, d * 1.5) + + if right_ear is not None: + x1, y1 = right_ear.x, right_ear.y + d = max(abs(x0 - x1), abs(y0 - y1)) + width = max(width, d * 1.5) + + x, y = x0, y0 + + x -= width + y -= width + + if x < 0: + x = 0 + + if y < 0: + y = 0 + + width1 = width * 2 + width2 = width * 2 + + if x + width > image_width: + width1 = image_width - x + + if y + width > image_height: + width2 = image_height - y + + width = min(width1, width2) + + if width >= 20: + return int(x), int(y), int(width) + else: + return None + + +# get max index of 2d array +def npmax(array): + arrayindex = array.argmax(1) + arrayvalue = array.max(1) + i = arrayvalue.argmax() + j = arrayindex[i] + return i, j diff --git a/modules/control/proc/pidi.py b/modules/control/proc/pidi.py new file mode 100644 index 000000000..4c661b923 --- /dev/null +++ b/modules/control/proc/pidi.py @@ -0,0 +1,83 @@ +import os +import warnings + +import cv2 +import numpy as np +import torch +from einops import rearrange +from huggingface_hub import hf_hub_download +from PIL import Image + +from modules.control.util import HWC3, nms, resize_image, safe_step +from .pidi_model import pidinet + + +class PidiNetDetector: + def __init__(self, netNetwork): + self.netNetwork = netNetwork + + @classmethod + def from_pretrained(cls, pretrained_model_or_path, filename=None, cache_dir=None): + filename = filename or "table5_pidinet.pth" + + if os.path.isdir(pretrained_model_or_path): + model_path = os.path.join(pretrained_model_or_path, filename) + else: + model_path = hf_hub_download(pretrained_model_or_path, filename, cache_dir=cache_dir) + + netNetwork = pidinet() + netNetwork.load_state_dict({k.replace('module.', ''): v for k, v in torch.load(model_path)['state_dict'].items()}) + netNetwork.eval() + + return cls(netNetwork) + + def to(self, device): + self.netNetwork.to(device) + return self + + def __call__(self, input_image, detect_resolution=512, image_resolution=512, safe=False, output_type="pil", scribble=False, apply_filter=False, **kwargs): + if "return_pil" in kwargs: + warnings.warn("return_pil is deprecated. Use output_type instead.", DeprecationWarning) + output_type = "pil" if kwargs["return_pil"] else "np" + if type(output_type) is bool: + warnings.warn("Passing `True` or `False` to `output_type` is deprecated and will raise an error in future versions") + if output_type: + output_type = "pil" + + device = next(iter(self.netNetwork.parameters())).device + if not isinstance(input_image, np.ndarray): + input_image = np.array(input_image, dtype=np.uint8) + + input_image = HWC3(input_image) + input_image = resize_image(input_image, detect_resolution) + assert input_image.ndim == 3 + input_image = input_image[:, :, ::-1].copy() + image_pidi = torch.from_numpy(input_image).float().to(device) + image_pidi = image_pidi / 255.0 + image_pidi = rearrange(image_pidi, 'h w c -> 1 c h w') + edge = self.netNetwork(image_pidi)[-1] + edge = edge.cpu().numpy() + if apply_filter: + edge = edge > 0.5 + if safe: + edge = safe_step(edge) + edge = (edge * 255.0).clip(0, 255).astype(np.uint8) + + detected_map = edge[0, 0] + detected_map = HWC3(detected_map) + + img = resize_image(input_image, image_resolution) + H, W, _C = img.shape + + detected_map = cv2.resize(detected_map, (W, H), interpolation=cv2.INTER_LINEAR) + + if scribble: + detected_map = nms(detected_map, 127, 3.0) + detected_map = cv2.GaussianBlur(detected_map, (0, 0), 3.0) + detected_map[detected_map > 4] = 255 + detected_map[detected_map < 255] = 0 + + if output_type == "pil": + detected_map = Image.fromarray(detected_map) + + return detected_map diff --git a/modules/control/proc/pidi/LICENSE b/modules/control/proc/pidi/LICENSE new file mode 100644 index 000000000..913b6cf92 --- /dev/null +++ b/modules/control/proc/pidi/LICENSE @@ -0,0 +1,21 @@ +It is just for research purpose, and commercial use should be contacted with authors first. + +Copyright (c) 2021 Zhuo Su + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. \ No newline at end of file diff --git a/modules/control/proc/pidi_model.py b/modules/control/proc/pidi_model.py new file mode 100644 index 000000000..16595b35a --- /dev/null +++ b/modules/control/proc/pidi_model.py @@ -0,0 +1,681 @@ +""" +Author: Zhuo Su, Wenzhe Liu +Date: Feb 18, 2021 +""" + +import math + +import cv2 +import numpy as np +import torch +import torch.nn as nn +import torch.nn.functional as F + + +def img2tensor(imgs, bgr2rgb=True, float32=True): + """Numpy array to tensor. + + Args: + imgs (list[ndarray] | ndarray): Input images. + bgr2rgb (bool): Whether to change bgr to rgb. + float32 (bool): Whether to change to float32. + + Returns: + list[tensor] | tensor: Tensor images. If returned results only have + one element, just return tensor. + """ + + def _totensor(img, bgr2rgb, float32): + if img.shape[2] == 3 and bgr2rgb: + if img.dtype == 'float64': + img = img.astype('float32') + img = cv2.cvtColor(img, cv2.COLOR_BGR2RGB) + img = torch.from_numpy(img.transpose(2, 0, 1)) + if float32: + img = img.float() + return img + + if isinstance(imgs, list): + return [_totensor(img, bgr2rgb, float32) for img in imgs] + else: + return _totensor(imgs, bgr2rgb, float32) + +nets = { + 'baseline': { + 'layer0': 'cv', + 'layer1': 'cv', + 'layer2': 'cv', + 'layer3': 'cv', + 'layer4': 'cv', + 'layer5': 'cv', + 'layer6': 'cv', + 'layer7': 'cv', + 'layer8': 'cv', + 'layer9': 'cv', + 'layer10': 'cv', + 'layer11': 'cv', + 'layer12': 'cv', + 'layer13': 'cv', + 'layer14': 'cv', + 'layer15': 'cv', + }, + 'c-v15': { + 'layer0': 'cd', + 'layer1': 'cv', + 'layer2': 'cv', + 'layer3': 'cv', + 'layer4': 'cv', + 'layer5': 'cv', + 'layer6': 'cv', + 'layer7': 'cv', + 'layer8': 'cv', + 'layer9': 'cv', + 'layer10': 'cv', + 'layer11': 'cv', + 'layer12': 'cv', + 'layer13': 'cv', + 'layer14': 'cv', + 'layer15': 'cv', + }, + 'a-v15': { + 'layer0': 'ad', + 'layer1': 'cv', + 'layer2': 'cv', + 'layer3': 'cv', + 'layer4': 'cv', + 'layer5': 'cv', + 'layer6': 'cv', + 'layer7': 'cv', + 'layer8': 'cv', + 'layer9': 'cv', + 'layer10': 'cv', + 'layer11': 'cv', + 'layer12': 'cv', + 'layer13': 'cv', + 'layer14': 'cv', + 'layer15': 'cv', + }, + 'r-v15': { + 'layer0': 'rd', + 'layer1': 'cv', + 'layer2': 'cv', + 'layer3': 'cv', + 'layer4': 'cv', + 'layer5': 'cv', + 'layer6': 'cv', + 'layer7': 'cv', + 'layer8': 'cv', + 'layer9': 'cv', + 'layer10': 'cv', + 'layer11': 'cv', + 'layer12': 'cv', + 'layer13': 'cv', + 'layer14': 'cv', + 'layer15': 'cv', + }, + 'cvvv4': { + 'layer0': 'cd', + 'layer1': 'cv', + 'layer2': 'cv', + 'layer3': 'cv', + 'layer4': 'cd', + 'layer5': 'cv', + 'layer6': 'cv', + 'layer7': 'cv', + 'layer8': 'cd', + 'layer9': 'cv', + 'layer10': 'cv', + 'layer11': 'cv', + 'layer12': 'cd', + 'layer13': 'cv', + 'layer14': 'cv', + 'layer15': 'cv', + }, + 'avvv4': { + 'layer0': 'ad', + 'layer1': 'cv', + 'layer2': 'cv', + 'layer3': 'cv', + 'layer4': 'ad', + 'layer5': 'cv', + 'layer6': 'cv', + 'layer7': 'cv', + 'layer8': 'ad', + 'layer9': 'cv', + 'layer10': 'cv', + 'layer11': 'cv', + 'layer12': 'ad', + 'layer13': 'cv', + 'layer14': 'cv', + 'layer15': 'cv', + }, + 'rvvv4': { + 'layer0': 'rd', + 'layer1': 'cv', + 'layer2': 'cv', + 'layer3': 'cv', + 'layer4': 'rd', + 'layer5': 'cv', + 'layer6': 'cv', + 'layer7': 'cv', + 'layer8': 'rd', + 'layer9': 'cv', + 'layer10': 'cv', + 'layer11': 'cv', + 'layer12': 'rd', + 'layer13': 'cv', + 'layer14': 'cv', + 'layer15': 'cv', + }, + 'cccv4': { + 'layer0': 'cd', + 'layer1': 'cd', + 'layer2': 'cd', + 'layer3': 'cv', + 'layer4': 'cd', + 'layer5': 'cd', + 'layer6': 'cd', + 'layer7': 'cv', + 'layer8': 'cd', + 'layer9': 'cd', + 'layer10': 'cd', + 'layer11': 'cv', + 'layer12': 'cd', + 'layer13': 'cd', + 'layer14': 'cd', + 'layer15': 'cv', + }, + 'aaav4': { + 'layer0': 'ad', + 'layer1': 'ad', + 'layer2': 'ad', + 'layer3': 'cv', + 'layer4': 'ad', + 'layer5': 'ad', + 'layer6': 'ad', + 'layer7': 'cv', + 'layer8': 'ad', + 'layer9': 'ad', + 'layer10': 'ad', + 'layer11': 'cv', + 'layer12': 'ad', + 'layer13': 'ad', + 'layer14': 'ad', + 'layer15': 'cv', + }, + 'rrrv4': { + 'layer0': 'rd', + 'layer1': 'rd', + 'layer2': 'rd', + 'layer3': 'cv', + 'layer4': 'rd', + 'layer5': 'rd', + 'layer6': 'rd', + 'layer7': 'cv', + 'layer8': 'rd', + 'layer9': 'rd', + 'layer10': 'rd', + 'layer11': 'cv', + 'layer12': 'rd', + 'layer13': 'rd', + 'layer14': 'rd', + 'layer15': 'cv', + }, + 'c16': { + 'layer0': 'cd', + 'layer1': 'cd', + 'layer2': 'cd', + 'layer3': 'cd', + 'layer4': 'cd', + 'layer5': 'cd', + 'layer6': 'cd', + 'layer7': 'cd', + 'layer8': 'cd', + 'layer9': 'cd', + 'layer10': 'cd', + 'layer11': 'cd', + 'layer12': 'cd', + 'layer13': 'cd', + 'layer14': 'cd', + 'layer15': 'cd', + }, + 'a16': { + 'layer0': 'ad', + 'layer1': 'ad', + 'layer2': 'ad', + 'layer3': 'ad', + 'layer4': 'ad', + 'layer5': 'ad', + 'layer6': 'ad', + 'layer7': 'ad', + 'layer8': 'ad', + 'layer9': 'ad', + 'layer10': 'ad', + 'layer11': 'ad', + 'layer12': 'ad', + 'layer13': 'ad', + 'layer14': 'ad', + 'layer15': 'ad', + }, + 'r16': { + 'layer0': 'rd', + 'layer1': 'rd', + 'layer2': 'rd', + 'layer3': 'rd', + 'layer4': 'rd', + 'layer5': 'rd', + 'layer6': 'rd', + 'layer7': 'rd', + 'layer8': 'rd', + 'layer9': 'rd', + 'layer10': 'rd', + 'layer11': 'rd', + 'layer12': 'rd', + 'layer13': 'rd', + 'layer14': 'rd', + 'layer15': 'rd', + }, + 'carv4': { + 'layer0': 'cd', + 'layer1': 'ad', + 'layer2': 'rd', + 'layer3': 'cv', + 'layer4': 'cd', + 'layer5': 'ad', + 'layer6': 'rd', + 'layer7': 'cv', + 'layer8': 'cd', + 'layer9': 'ad', + 'layer10': 'rd', + 'layer11': 'cv', + 'layer12': 'cd', + 'layer13': 'ad', + 'layer14': 'rd', + 'layer15': 'cv', + }, + } + +def createConvFunc(op_type): + assert op_type in ['cv', 'cd', 'ad', 'rd'], 'unknown op type: %s' % str(op_type) + if op_type == 'cv': + return F.conv2d + + if op_type == 'cd': + def func(x, weights, bias=None, stride=1, padding=0, dilation=1, groups=1): + assert dilation in [1, 2], 'dilation for cd_conv should be in 1 or 2' + assert weights.size(2) == 3 and weights.size(3) == 3, 'kernel size for cd_conv should be 3x3' + assert padding == dilation, 'padding for cd_conv set wrong' + + weights_c = weights.sum(dim=[2, 3], keepdim=True) + yc = F.conv2d(x, weights_c, stride=stride, padding=0, groups=groups) + y = F.conv2d(x, weights, bias, stride=stride, padding=padding, dilation=dilation, groups=groups) + return y - yc + return func + elif op_type == 'ad': + def func(x, weights, bias=None, stride=1, padding=0, dilation=1, groups=1): + assert dilation in [1, 2], 'dilation for ad_conv should be in 1 or 2' + assert weights.size(2) == 3 and weights.size(3) == 3, 'kernel size for ad_conv should be 3x3' + assert padding == dilation, 'padding for ad_conv set wrong' + + shape = weights.shape + weights = weights.view(shape[0], shape[1], -1) + weights_conv = (weights - weights[:, :, [3, 0, 1, 6, 4, 2, 7, 8, 5]]).view(shape) # clock-wise + y = F.conv2d(x, weights_conv, bias, stride=stride, padding=padding, dilation=dilation, groups=groups) + return y + return func + elif op_type == 'rd': + def func(x, weights, bias=None, stride=1, padding=0, dilation=1, groups=1): + assert dilation in [1, 2], 'dilation for rd_conv should be in 1 or 2' + assert weights.size(2) == 3 and weights.size(3) == 3, 'kernel size for rd_conv should be 3x3' + padding = 2 * dilation + + shape = weights.shape + if weights.is_cuda: + buffer = torch.cuda.FloatTensor(shape[0], shape[1], 5 * 5).fill_(0) + else: + buffer = torch.zeros(shape[0], shape[1], 5 * 5).to(weights.device) + weights = weights.view(shape[0], shape[1], -1) + buffer[:, :, [0, 2, 4, 10, 14, 20, 22, 24]] = weights[:, :, 1:] + buffer[:, :, [6, 7, 8, 11, 13, 16, 17, 18]] = -weights[:, :, 1:] + buffer[:, :, 12] = 0 + buffer = buffer.view(shape[0], shape[1], 5, 5) + y = F.conv2d(x, buffer, bias, stride=stride, padding=padding, dilation=dilation, groups=groups) + return y + return func + else: + print('impossible to be here unless you force that') + return None + +class Conv2d(nn.Module): + def __init__(self, pdc, in_channels, out_channels, kernel_size, stride=1, padding=0, dilation=1, groups=1, bias=False): + super(Conv2d, self).__init__() + if in_channels % groups != 0: + raise ValueError('in_channels must be divisible by groups') + if out_channels % groups != 0: + raise ValueError('out_channels must be divisible by groups') + self.in_channels = in_channels + self.out_channels = out_channels + self.kernel_size = kernel_size + self.stride = stride + self.padding = padding + self.dilation = dilation + self.groups = groups + self.weight = nn.Parameter(torch.Tensor(out_channels, in_channels // groups, kernel_size, kernel_size)) + if bias: + self.bias = nn.Parameter(torch.Tensor(out_channels)) + else: + self.register_parameter('bias', None) + self.reset_parameters() + self.pdc = pdc + + def reset_parameters(self): + nn.init.kaiming_uniform_(self.weight, a=math.sqrt(5)) + if self.bias is not None: + fan_in, _ = nn.init._calculate_fan_in_and_fan_out(self.weight) + bound = 1 / math.sqrt(fan_in) + nn.init.uniform_(self.bias, -bound, bound) + + def forward(self, input): + + return self.pdc(input, self.weight, self.bias, self.stride, self.padding, self.dilation, self.groups) + +class CSAM(nn.Module): + """ + Compact Spatial Attention Module + """ + def __init__(self, channels): + super(CSAM, self).__init__() + + mid_channels = 4 + self.relu1 = nn.ReLU() + self.conv1 = nn.Conv2d(channels, mid_channels, kernel_size=1, padding=0) + self.conv2 = nn.Conv2d(mid_channels, 1, kernel_size=3, padding=1, bias=False) + self.sigmoid = nn.Sigmoid() + nn.init.constant_(self.conv1.bias, 0) + + def forward(self, x): + y = self.relu1(x) + y = self.conv1(y) + y = self.conv2(y) + y = self.sigmoid(y) + + return x * y + +class CDCM(nn.Module): + """ + Compact Dilation Convolution based Module + """ + def __init__(self, in_channels, out_channels): + super(CDCM, self).__init__() + + self.relu1 = nn.ReLU() + self.conv1 = nn.Conv2d(in_channels, out_channels, kernel_size=1, padding=0) + self.conv2_1 = nn.Conv2d(out_channels, out_channels, kernel_size=3, dilation=5, padding=5, bias=False) + self.conv2_2 = nn.Conv2d(out_channels, out_channels, kernel_size=3, dilation=7, padding=7, bias=False) + self.conv2_3 = nn.Conv2d(out_channels, out_channels, kernel_size=3, dilation=9, padding=9, bias=False) + self.conv2_4 = nn.Conv2d(out_channels, out_channels, kernel_size=3, dilation=11, padding=11, bias=False) + nn.init.constant_(self.conv1.bias, 0) + + def forward(self, x): + x = self.relu1(x) + x = self.conv1(x) + x1 = self.conv2_1(x) + x2 = self.conv2_2(x) + x3 = self.conv2_3(x) + x4 = self.conv2_4(x) + return x1 + x2 + x3 + x4 + + +class MapReduce(nn.Module): + """ + Reduce feature maps into a single edge map + """ + def __init__(self, channels): + super(MapReduce, self).__init__() + self.conv = nn.Conv2d(channels, 1, kernel_size=1, padding=0) + nn.init.constant_(self.conv.bias, 0) + + def forward(self, x): + return self.conv(x) + + +class PDCBlock(nn.Module): + def __init__(self, pdc, inplane, ouplane, stride=1): + super(PDCBlock, self).__init__() + self.stride=stride + + self.stride=stride + if self.stride > 1: + self.pool = nn.MaxPool2d(kernel_size=2, stride=2) + self.shortcut = nn.Conv2d(inplane, ouplane, kernel_size=1, padding=0) + self.conv1 = Conv2d(pdc, inplane, inplane, kernel_size=3, padding=1, groups=inplane, bias=False) + self.relu2 = nn.ReLU() + self.conv2 = nn.Conv2d(inplane, ouplane, kernel_size=1, padding=0, bias=False) + + def forward(self, x): + if self.stride > 1: + x = self.pool(x) + y = self.conv1(x) + y = self.relu2(y) + y = self.conv2(y) + if self.stride > 1: + x = self.shortcut(x) + y = y + x + return y + +class PDCBlock_converted(nn.Module): + """ + CPDC, APDC can be converted to vanilla 3x3 convolution + RPDC can be converted to vanilla 5x5 convolution + """ + def __init__(self, pdc, inplane, ouplane, stride=1): + super(PDCBlock_converted, self).__init__() + self.stride=stride + + if self.stride > 1: + self.pool = nn.MaxPool2d(kernel_size=2, stride=2) + self.shortcut = nn.Conv2d(inplane, ouplane, kernel_size=1, padding=0) + if pdc == 'rd': + self.conv1 = nn.Conv2d(inplane, inplane, kernel_size=5, padding=2, groups=inplane, bias=False) + else: + self.conv1 = nn.Conv2d(inplane, inplane, kernel_size=3, padding=1, groups=inplane, bias=False) + self.relu2 = nn.ReLU() + self.conv2 = nn.Conv2d(inplane, ouplane, kernel_size=1, padding=0, bias=False) + + def forward(self, x): + if self.stride > 1: + x = self.pool(x) + y = self.conv1(x) + y = self.relu2(y) + y = self.conv2(y) + if self.stride > 1: + x = self.shortcut(x) + y = y + x + return y + +class PiDiNet(nn.Module): + def __init__(self, inplane, pdcs, dil=None, sa=False, convert=False): + super(PiDiNet, self).__init__() + self.sa = sa + if dil is not None: + assert isinstance(dil, int), 'dil should be an int' + self.dil = dil + + self.fuseplanes = [] + + self.inplane = inplane + if convert: + if pdcs[0] == 'rd': + init_kernel_size = 5 + init_padding = 2 + else: + init_kernel_size = 3 + init_padding = 1 + self.init_block = nn.Conv2d(3, self.inplane, + kernel_size=init_kernel_size, padding=init_padding, bias=False) + block_class = PDCBlock_converted + else: + self.init_block = Conv2d(pdcs[0], 3, self.inplane, kernel_size=3, padding=1) + block_class = PDCBlock + + self.block1_1 = block_class(pdcs[1], self.inplane, self.inplane) + self.block1_2 = block_class(pdcs[2], self.inplane, self.inplane) + self.block1_3 = block_class(pdcs[3], self.inplane, self.inplane) + self.fuseplanes.append(self.inplane) # C + + inplane = self.inplane + self.inplane = self.inplane * 2 + self.block2_1 = block_class(pdcs[4], inplane, self.inplane, stride=2) + self.block2_2 = block_class(pdcs[5], self.inplane, self.inplane) + self.block2_3 = block_class(pdcs[6], self.inplane, self.inplane) + self.block2_4 = block_class(pdcs[7], self.inplane, self.inplane) + self.fuseplanes.append(self.inplane) # 2C + + inplane = self.inplane + self.inplane = self.inplane * 2 + self.block3_1 = block_class(pdcs[8], inplane, self.inplane, stride=2) + self.block3_2 = block_class(pdcs[9], self.inplane, self.inplane) + self.block3_3 = block_class(pdcs[10], self.inplane, self.inplane) + self.block3_4 = block_class(pdcs[11], self.inplane, self.inplane) + self.fuseplanes.append(self.inplane) # 4C + + self.block4_1 = block_class(pdcs[12], self.inplane, self.inplane, stride=2) + self.block4_2 = block_class(pdcs[13], self.inplane, self.inplane) + self.block4_3 = block_class(pdcs[14], self.inplane, self.inplane) + self.block4_4 = block_class(pdcs[15], self.inplane, self.inplane) + self.fuseplanes.append(self.inplane) # 4C + + self.conv_reduces = nn.ModuleList() + if self.sa and self.dil is not None: + self.attentions = nn.ModuleList() + self.dilations = nn.ModuleList() + for i in range(4): + self.dilations.append(CDCM(self.fuseplanes[i], self.dil)) + self.attentions.append(CSAM(self.dil)) + self.conv_reduces.append(MapReduce(self.dil)) + elif self.sa: + self.attentions = nn.ModuleList() + for i in range(4): + self.attentions.append(CSAM(self.fuseplanes[i])) + self.conv_reduces.append(MapReduce(self.fuseplanes[i])) + elif self.dil is not None: + self.dilations = nn.ModuleList() + for i in range(4): + self.dilations.append(CDCM(self.fuseplanes[i], self.dil)) + self.conv_reduces.append(MapReduce(self.dil)) + else: + for i in range(4): + self.conv_reduces.append(MapReduce(self.fuseplanes[i])) + + self.classifier = nn.Conv2d(4, 1, kernel_size=1) # has bias + nn.init.constant_(self.classifier.weight, 0.25) + nn.init.constant_(self.classifier.bias, 0) + + # print('initialization done') + + def get_weights(self): + conv_weights = [] + bn_weights = [] + relu_weights = [] + for pname, p in self.named_parameters(): + if 'bn' in pname: + bn_weights.append(p) + elif 'relu' in pname: + relu_weights.append(p) + else: + conv_weights.append(p) + + return conv_weights, bn_weights, relu_weights + + def forward(self, x): + H, W = x.size()[2:] + + x = self.init_block(x) + + x1 = self.block1_1(x) + x1 = self.block1_2(x1) + x1 = self.block1_3(x1) + + x2 = self.block2_1(x1) + x2 = self.block2_2(x2) + x2 = self.block2_3(x2) + x2 = self.block2_4(x2) + + x3 = self.block3_1(x2) + x3 = self.block3_2(x3) + x3 = self.block3_3(x3) + x3 = self.block3_4(x3) + + x4 = self.block4_1(x3) + x4 = self.block4_2(x4) + x4 = self.block4_3(x4) + x4 = self.block4_4(x4) + + x_fuses = [] + if self.sa and self.dil is not None: + for i, xi in enumerate([x1, x2, x3, x4]): + x_fuses.append(self.attentions[i](self.dilations[i](xi))) + elif self.sa: + for i, xi in enumerate([x1, x2, x3, x4]): + x_fuses.append(self.attentions[i](xi)) + elif self.dil is not None: + for i, xi in enumerate([x1, x2, x3, x4]): + x_fuses.append(self.dilations[i](xi)) + else: + x_fuses = [x1, x2, x3, x4] + + e1 = self.conv_reduces[0](x_fuses[0]) + e1 = F.interpolate(e1, (H, W), mode="bilinear", align_corners=False) + + e2 = self.conv_reduces[1](x_fuses[1]) + e2 = F.interpolate(e2, (H, W), mode="bilinear", align_corners=False) + + e3 = self.conv_reduces[2](x_fuses[2]) + e3 = F.interpolate(e3, (H, W), mode="bilinear", align_corners=False) + + e4 = self.conv_reduces[3](x_fuses[3]) + e4 = F.interpolate(e4, (H, W), mode="bilinear", align_corners=False) + + outputs = [e1, e2, e3, e4] + + output = self.classifier(torch.cat(outputs, dim=1)) + #if not self.training: + # return torch.sigmoid(output) + + outputs.append(output) + outputs = [torch.sigmoid(r) for r in outputs] + return outputs + +def config_model(model): + model_options = list(nets.keys()) + assert model in model_options, \ + 'unrecognized model, please choose from %s' % str(model_options) + + # print(str(nets[model])) + + pdcs = [] + for i in range(16): + layer_name = 'layer%d' % i + op = nets[model][layer_name] + pdcs.append(createConvFunc(op)) + + return pdcs + +def pidinet(): + pdcs = config_model('carv4') + dil = 24 #if args.dil else None + return PiDiNet(60, pdcs, dil=dil, sa=True) + + +if __name__ == '__main__': + model = pidinet() + ckp = torch.load('table5_pidinet.pth')['state_dict'] + model.load_state_dict({k.replace('module.',''):v for k, v in ckp.items()}) + im = cv2.imread('examples/test_my/cat_v4.png') + im = img2tensor(im).unsqueeze(0)/255. + res = model(im)[-1] + res = res>0.5 + res = res.float() + res = (res[0,0].cpu().data.numpy()*255.).astype(np.uint8) + print(res.shape) + cv2.imwrite('edge.png', res) diff --git a/modules/control/proc/reference_sd15.py b/modules/control/proc/reference_sd15.py new file mode 100644 index 000000000..53f8602d3 --- /dev/null +++ b/modules/control/proc/reference_sd15.py @@ -0,0 +1,792 @@ +# Inspired by: https://github.com/Mikubill/sd-webui-controlnet/discussions/1236 and https://github.com/Mikubill/sd-webui-controlnet/discussions/1280 +from typing import Any, Callable, Dict, List, Optional, Tuple, Union +import numpy as np +import PIL.Image +import torch +from diffusers import StableDiffusionPipeline +from diffusers.models.attention import BasicTransformerBlock +from diffusers.models.unet_2d_blocks import CrossAttnDownBlock2D, CrossAttnUpBlock2D, DownBlock2D, UpBlock2D +from diffusers.pipelines.stable_diffusion import StableDiffusionPipelineOutput +from diffusers.pipelines.stable_diffusion.pipeline_stable_diffusion import rescale_noise_cfg +from diffusers.utils import PIL_INTERPOLATION +from diffusers.utils.torch_utils import randn_tensor + + +EXAMPLE_DOC_STRING = """ + Examples: + ```py + >>> import torch + >>> from diffusers import UniPCMultistepScheduler + >>> from diffusers.utils import load_image + + >>> input_image = load_image("https://hf.co/datasets/huggingface/documentation-images/resolve/main/diffusers/input_image_vermeer.png") + + >>> pipe = StableDiffusionReferencePipeline.from_pretrained( + "runwayml/stable-diffusion-v1-5", + safety_checker=None, + torch_dtype=torch.float16 + ).to('cuda:0') + + >>> pipe.scheduler = UniPCMultistepScheduler.from_config(pipe_controlnet.scheduler.config) + + >>> result_img = pipe(ref_image=input_image, + prompt="1girl", + num_inference_steps=20, + reference_attn=True, + reference_adain=True).images[0] + + >>> result_img.show() + ``` +""" + + +def torch_dfs(model: torch.nn.Module): + result = [model] + for child in model.children(): + result += torch_dfs(child) + return result + + +class StableDiffusionReferencePipeline(StableDiffusionPipeline): + def _default_height_width(self, height, width, image): + # NOTE: It is possible that a list of images have different + # dimensions for each image, so just checking the first image + # is not _exactly_ correct, but it is simple. + while isinstance(image, list): + image = image[0] + + if height is None: + if isinstance(image, PIL.Image.Image): + height = image.height + elif isinstance(image, torch.Tensor): + height = image.shape[2] + + height = (height // 8) * 8 # round down to nearest multiple of 8 + + if width is None: + if isinstance(image, PIL.Image.Image): + width = image.width + elif isinstance(image, torch.Tensor): + width = image.shape[3] + + width = (width // 8) * 8 # round down to nearest multiple of 8 + + return height, width + + def prepare_image( + self, + image, + width, + height, + batch_size, + num_images_per_prompt, + device, + dtype, + do_classifier_free_guidance=False, + guess_mode=False, + ): + if not isinstance(image, torch.Tensor): + if isinstance(image, PIL.Image.Image): + image = [image] + + if isinstance(image[0], PIL.Image.Image): + images = [] + + for image_ in image: + image_ = image_.convert("RGB") + image_ = image_.resize((width, height), resample=PIL_INTERPOLATION["lanczos"]) + image_ = np.array(image_) + image_ = image_[None, :] + images.append(image_) + + image = images + + image = np.concatenate(image, axis=0) + image = np.array(image).astype(np.float32) / 255.0 + image = (image - 0.5) / 0.5 + image = image.transpose(0, 3, 1, 2) + image = torch.from_numpy(image) + elif isinstance(image[0], torch.Tensor): + image = torch.cat(image, dim=0) + + image_batch_size = image.shape[0] + + if image_batch_size == 1: + repeat_by = batch_size + else: + # image batch size is the same as prompt batch size + repeat_by = num_images_per_prompt + + image = image.repeat_interleave(repeat_by, dim=0) + + image = image.to(device=device, dtype=dtype) + + if do_classifier_free_guidance and not guess_mode: + image = torch.cat([image] * 2) + + return image + + def prepare_ref_latents(self, refimage, batch_size, dtype, device, generator, do_classifier_free_guidance): + refimage = refimage.to(device=device, dtype=dtype) + + # encode the mask image into latents space so we can concatenate it to the latents + if isinstance(generator, list): + ref_image_latents = [ + self.vae.encode(refimage[i : i + 1]).latent_dist.sample(generator=generator[i]) + for i in range(batch_size) + ] + ref_image_latents = torch.cat(ref_image_latents, dim=0) + else: + ref_image_latents = self.vae.encode(refimage).latent_dist.sample(generator=generator) + ref_image_latents = self.vae.config.scaling_factor * ref_image_latents + + # duplicate mask and ref_image_latents for each generation per prompt, using mps friendly method + if ref_image_latents.shape[0] < batch_size: + if not batch_size % ref_image_latents.shape[0] == 0: + raise ValueError( + "The passed images and the required batch size don't match. Images are supposed to be duplicated" + f" to a total batch size of {batch_size}, but {ref_image_latents.shape[0]} images were passed." + " Make sure the number of images that you pass is divisible by the total requested batch size." + ) + ref_image_latents = ref_image_latents.repeat(batch_size // ref_image_latents.shape[0], 1, 1, 1) + + # aligning device to prevent device errors when concating it with the latent model input + ref_image_latents = ref_image_latents.to(device=device, dtype=dtype) + return ref_image_latents + + def __call__( + self, + prompt: Union[str, List[str]] = None, + ref_image: Union[torch.FloatTensor, PIL.Image.Image] = None, + height: Optional[int] = None, + width: Optional[int] = None, + num_inference_steps: int = 50, + guidance_scale: float = 7.5, + negative_prompt: Optional[Union[str, List[str]]] = None, + num_images_per_prompt: Optional[int] = 1, + eta: float = 0.0, + generator: Optional[Union[torch.Generator, List[torch.Generator]]] = None, + latents: Optional[torch.FloatTensor] = None, + prompt_embeds: Optional[torch.FloatTensor] = None, + negative_prompt_embeds: Optional[torch.FloatTensor] = None, + output_type: Optional[str] = "pil", + return_dict: bool = True, + callback: Optional[Callable[[int, int, torch.FloatTensor], None]] = None, + callback_steps: int = 1, + cross_attention_kwargs: Optional[Dict[str, Any]] = None, + guidance_rescale: float = 0.0, + attention_auto_machine_weight: float = 1.0, + gn_auto_machine_weight: float = 1.0, + style_fidelity: float = 0.5, + reference_attn: bool = True, + reference_adain: bool = True, + ): + r""" + Function invoked when calling the pipeline for generation. + + Args: + prompt (`str` or `List[str]`, *optional*): + The prompt or prompts to guide the image generation. If not defined, one has to pass `prompt_embeds`. + instead. + ref_image (`torch.FloatTensor`, `PIL.Image.Image`): + The Reference Control input condition. Reference Control uses this input condition to generate guidance to Unet. If + the type is specified as `Torch.FloatTensor`, it is passed to Reference Control as is. `PIL.Image.Image` can + also be accepted as an image. + height (`int`, *optional*, defaults to self.unet.config.sample_size * self.vae_scale_factor): + The height in pixels of the generated image. + width (`int`, *optional*, defaults to self.unet.config.sample_size * self.vae_scale_factor): + The width in pixels of the generated image. + num_inference_steps (`int`, *optional*, defaults to 50): + The number of denoising steps. More denoising steps usually lead to a higher quality image at the + expense of slower inference. + guidance_scale (`float`, *optional*, defaults to 7.5): + Guidance scale as defined in [Classifier-Free Diffusion Guidance](https://arxiv.org/abs/2207.12598). + `guidance_scale` is defined as `w` of equation 2. of [Imagen + Paper](https://arxiv.org/pdf/2205.11487.pdf). Guidance scale is enabled by setting `guidance_scale > + 1`. Higher guidance scale encourages to generate images that are closely linked to the text `prompt`, + usually at the expense of lower image quality. + negative_prompt (`str` or `List[str]`, *optional*): + The prompt or prompts not to guide the image generation. If not defined, one has to pass + `negative_prompt_embeds` instead. Ignored when not using guidance (i.e., ignored if `guidance_scale` is + less than `1`). + num_images_per_prompt (`int`, *optional*, defaults to 1): + The number of images to generate per prompt. + eta (`float`, *optional*, defaults to 0.0): + Corresponds to parameter eta (η) in the DDIM paper: https://arxiv.org/abs/2010.02502. Only applies to + [`schedulers.DDIMScheduler`], will be ignored for others. + generator (`torch.Generator` or `List[torch.Generator]`, *optional*): + One or a list of [torch generator(s)](https://pytorch.org/docs/stable/generated/torch.Generator.html) + to make generation deterministic. + latents (`torch.FloatTensor`, *optional*): + Pre-generated noisy latents, sampled from a Gaussian distribution, to be used as inputs for image + generation. Can be used to tweak the same generation with different prompts. If not provided, a latents + tensor will ge generated by sampling using the supplied random `generator`. + prompt_embeds (`torch.FloatTensor`, *optional*): + Pre-generated text embeddings. Can be used to easily tweak text inputs, *e.g.* prompt weighting. If not + provided, text embeddings will be generated from `prompt` input argument. + negative_prompt_embeds (`torch.FloatTensor`, *optional*): + Pre-generated negative text embeddings. Can be used to easily tweak text inputs, *e.g.* prompt + weighting. If not provided, negative_prompt_embeds will be generated from `negative_prompt` input + argument. + output_type (`str`, *optional*, defaults to `"pil"`): + The output format of the generate image. Choose between + [PIL](https://pillow.readthedocs.io/en/stable/): `PIL.Image.Image` or `np.array`. + return_dict (`bool`, *optional*, defaults to `True`): + Whether or not to return a [`~pipelines.stable_diffusion.StableDiffusionPipelineOutput`] instead of a + plain tuple. + callback (`Callable`, *optional*): + A function that will be called every `callback_steps` steps during inference. The function will be + called with the following arguments: `callback(step: int, timestep: int, latents: torch.FloatTensor)`. + callback_steps (`int`, *optional*, defaults to 1): + The frequency at which the `callback` function will be called. If not specified, the callback will be + called at every step. + cross_attention_kwargs (`dict`, *optional*): + A kwargs dictionary that if specified is passed along to the `AttentionProcessor` as defined under + `self.processor` in + [diffusers.models.attention_processor](https://github.com/huggingface/diffusers/blob/main/src/diffusers/models/attention_processor.py). + guidance_rescale (`float`, *optional*, defaults to 0.0): + Guidance rescale factor proposed by [Common Diffusion Noise Schedules and Sample Steps are + Flawed](https://arxiv.org/pdf/2305.08891.pdf) `guidance_scale` is defined as `φ` in equation 16. of + [Common Diffusion Noise Schedules and Sample Steps are Flawed](https://arxiv.org/pdf/2305.08891.pdf). + Guidance rescale factor should fix overexposure when using zero terminal SNR. + attention_auto_machine_weight (`float`): + Weight of using reference query for self attention's context. + If attention_auto_machine_weight=1.0, use reference query for all self attention's context. + gn_auto_machine_weight (`float`): + Weight of using reference adain. If gn_auto_machine_weight=2.0, use all reference adain plugins. + style_fidelity (`float`): + style fidelity of ref_uncond_xt. If style_fidelity=1.0, control more important, + elif style_fidelity=0.0, prompt more important, else balanced. + reference_attn (`bool`): + Whether to use reference query for self attention's context. + reference_adain (`bool`): + Whether to use reference adain. + + Examples: + + Returns: + [`~pipelines.stable_diffusion.StableDiffusionPipelineOutput`] or `tuple`: + [`~pipelines.stable_diffusion.StableDiffusionPipelineOutput`] if `return_dict` is True, otherwise a `tuple. + When returning a tuple, the first element is a list with the generated images, and the second element is a + list of `bool`s denoting whether the corresponding generated image likely represents "not-safe-for-work" + (nsfw) content, according to the `safety_checker`. + """ + assert reference_attn or reference_adain, "`reference_attn` or `reference_adain` must be True." + + # 0. Default height and width to unet + height, width = self._default_height_width(height, width, ref_image) + + # 1. Check inputs. Raise error if not correct + self.check_inputs( + prompt, height, width, callback_steps, negative_prompt, prompt_embeds, negative_prompt_embeds + ) + + # 2. Define call parameters + if prompt is not None and isinstance(prompt, str): + batch_size = 1 + elif prompt is not None and isinstance(prompt, list): + batch_size = len(prompt) + else: + batch_size = prompt_embeds.shape[0] + + device = self._execution_device + # here `guidance_scale` is defined analog to the guidance weight `w` of equation (2) + # of the Imagen paper: https://arxiv.org/pdf/2205.11487.pdf . `guidance_scale = 1` + # corresponds to doing no classifier free guidance. + do_classifier_free_guidance = guidance_scale > 1.0 + + # 3. Encode input prompt + text_encoder_lora_scale = ( + cross_attention_kwargs.get("scale", None) if cross_attention_kwargs is not None else None + ) + prompt_embeds = self._encode_prompt( + prompt, + device, + num_images_per_prompt, + do_classifier_free_guidance, + negative_prompt, + prompt_embeds=prompt_embeds, + negative_prompt_embeds=negative_prompt_embeds, + lora_scale=text_encoder_lora_scale, + ) + + # 4. Preprocess reference image + ref_image = self.prepare_image( + image=ref_image, + width=width, + height=height, + batch_size=batch_size * num_images_per_prompt, + num_images_per_prompt=num_images_per_prompt, + device=device, + dtype=prompt_embeds.dtype, + ) + + # 5. Prepare timesteps + self.scheduler.set_timesteps(num_inference_steps, device=device) + timesteps = self.scheduler.timesteps + + # 6. Prepare latent variables + num_channels_latents = self.unet.config.in_channels + latents = self.prepare_latents( + batch_size * num_images_per_prompt, + num_channels_latents, + height, + width, + prompt_embeds.dtype, + device, + generator, + latents, + ) + + # 7. Prepare reference latent variables + ref_image_latents = self.prepare_ref_latents( + ref_image, + batch_size * num_images_per_prompt, + prompt_embeds.dtype, + device, + generator, + do_classifier_free_guidance, + ) + + # 8. Prepare extra step kwargs. TODO: Logic should ideally just be moved out of the pipeline + extra_step_kwargs = self.prepare_extra_step_kwargs(generator, eta) + + # 9. Modify self attention and group norm + MODE = "write" + uc_mask = ( + torch.Tensor([1] * batch_size * num_images_per_prompt + [0] * batch_size * num_images_per_prompt) + .type_as(ref_image_latents) + .bool() + ) + + def hacked_basic_transformer_inner_forward( + self, + hidden_states: torch.FloatTensor, + attention_mask: Optional[torch.FloatTensor] = None, + encoder_hidden_states: Optional[torch.FloatTensor] = None, + encoder_attention_mask: Optional[torch.FloatTensor] = None, + timestep: Optional[torch.LongTensor] = None, + cross_attention_kwargs: Dict[str, Any] = None, + class_labels: Optional[torch.LongTensor] = None, + ): + if self.use_ada_layer_norm: + norm_hidden_states = self.norm1(hidden_states, timestep) + elif self.use_ada_layer_norm_zero: + norm_hidden_states, gate_msa, shift_mlp, scale_mlp, gate_mlp = self.norm1( + hidden_states, timestep, class_labels, hidden_dtype=hidden_states.dtype + ) + else: + norm_hidden_states = self.norm1(hidden_states) + + # 1. Self-Attention + cross_attention_kwargs = cross_attention_kwargs if cross_attention_kwargs is not None else {} + if self.only_cross_attention: + attn_output = self.attn1( + norm_hidden_states, + encoder_hidden_states=encoder_hidden_states if self.only_cross_attention else None, + attention_mask=attention_mask, + **cross_attention_kwargs, + ) + else: + if MODE == "write": + self.bank.append(norm_hidden_states.detach().clone()) + attn_output = self.attn1( + norm_hidden_states, + encoder_hidden_states=encoder_hidden_states if self.only_cross_attention else None, + attention_mask=attention_mask, + **cross_attention_kwargs, + ) + if MODE == "read": + if attention_auto_machine_weight > self.attn_weight: + attn_output_uc = self.attn1( + norm_hidden_states, + encoder_hidden_states=torch.cat([norm_hidden_states] + self.bank, dim=1), + # attention_mask=attention_mask, + **cross_attention_kwargs, + ) + attn_output_c = attn_output_uc.clone() + if do_classifier_free_guidance and style_fidelity > 0: + attn_output_c[uc_mask] = self.attn1( + norm_hidden_states[uc_mask], + encoder_hidden_states=norm_hidden_states[uc_mask], + **cross_attention_kwargs, + ) + attn_output = style_fidelity * attn_output_c + (1.0 - style_fidelity) * attn_output_uc + self.bank.clear() + else: + attn_output = self.attn1( + norm_hidden_states, + encoder_hidden_states=encoder_hidden_states if self.only_cross_attention else None, + attention_mask=attention_mask, + **cross_attention_kwargs, + ) + if self.use_ada_layer_norm_zero: + attn_output = gate_msa.unsqueeze(1) * attn_output + hidden_states = attn_output + hidden_states + + if self.attn2 is not None: + norm_hidden_states = ( + self.norm2(hidden_states, timestep) if self.use_ada_layer_norm else self.norm2(hidden_states) + ) + + # 2. Cross-Attention + attn_output = self.attn2( + norm_hidden_states, + encoder_hidden_states=encoder_hidden_states, + attention_mask=encoder_attention_mask, + **cross_attention_kwargs, + ) + hidden_states = attn_output + hidden_states + + # 3. Feed-forward + norm_hidden_states = self.norm3(hidden_states) + + if self.use_ada_layer_norm_zero: + norm_hidden_states = norm_hidden_states * (1 + scale_mlp[:, None]) + shift_mlp[:, None] + + ff_output = self.ff(norm_hidden_states) + + if self.use_ada_layer_norm_zero: + ff_output = gate_mlp.unsqueeze(1) * ff_output + + hidden_states = ff_output + hidden_states + + return hidden_states + + def hacked_mid_forward(self, *args, **kwargs): + eps = 1e-6 + x = self.original_forward(*args, **kwargs) + if MODE == "write": + if gn_auto_machine_weight >= self.gn_weight: + var, mean = torch.var_mean(x, dim=(2, 3), keepdim=True, correction=0) + self.mean_bank.append(mean) + self.var_bank.append(var) + if MODE == "read": + if len(self.mean_bank) > 0 and len(self.var_bank) > 0: + var, mean = torch.var_mean(x, dim=(2, 3), keepdim=True, correction=0) + std = torch.maximum(var, torch.zeros_like(var) + eps) ** 0.5 + mean_acc = sum(self.mean_bank) / float(len(self.mean_bank)) + var_acc = sum(self.var_bank) / float(len(self.var_bank)) + std_acc = torch.maximum(var_acc, torch.zeros_like(var_acc) + eps) ** 0.5 + x_uc = (((x - mean) / std) * std_acc) + mean_acc + x_c = x_uc.clone() + if do_classifier_free_guidance and style_fidelity > 0: + x_c[uc_mask] = x[uc_mask] + x = style_fidelity * x_c + (1.0 - style_fidelity) * x_uc + self.mean_bank = [] + self.var_bank = [] + return x + + def hack_CrossAttnDownBlock2D_forward( + self, + hidden_states: torch.FloatTensor, + temb: Optional[torch.FloatTensor] = None, + encoder_hidden_states: Optional[torch.FloatTensor] = None, + attention_mask: Optional[torch.FloatTensor] = None, + cross_attention_kwargs: Optional[Dict[str, Any]] = None, + encoder_attention_mask: Optional[torch.FloatTensor] = None, + ): + eps = 1e-6 + + # TODO(Patrick, William) - attention mask is not used + output_states = () + + for i, (resnet, attn) in enumerate(zip(self.resnets, self.attentions)): + hidden_states = resnet(hidden_states, temb) + hidden_states = attn( + hidden_states, + encoder_hidden_states=encoder_hidden_states, + cross_attention_kwargs=cross_attention_kwargs, + attention_mask=attention_mask, + encoder_attention_mask=encoder_attention_mask, + return_dict=False, + )[0] + if MODE == "write": + if gn_auto_machine_weight >= self.gn_weight: + var, mean = torch.var_mean(hidden_states, dim=(2, 3), keepdim=True, correction=0) + self.mean_bank.append([mean]) + self.var_bank.append([var]) + if MODE == "read": + if len(self.mean_bank) > 0 and len(self.var_bank) > 0: + var, mean = torch.var_mean(hidden_states, dim=(2, 3), keepdim=True, correction=0) + std = torch.maximum(var, torch.zeros_like(var) + eps) ** 0.5 + mean_acc = sum(self.mean_bank[i]) / float(len(self.mean_bank[i])) + var_acc = sum(self.var_bank[i]) / float(len(self.var_bank[i])) + std_acc = torch.maximum(var_acc, torch.zeros_like(var_acc) + eps) ** 0.5 + hidden_states_uc = (((hidden_states - mean) / std) * std_acc) + mean_acc + hidden_states_c = hidden_states_uc.clone() + if do_classifier_free_guidance and style_fidelity > 0: + hidden_states_c[uc_mask] = hidden_states[uc_mask] + hidden_states = style_fidelity * hidden_states_c + (1.0 - style_fidelity) * hidden_states_uc + + output_states = output_states + (hidden_states,) + + if MODE == "read": + self.mean_bank = [] + self.var_bank = [] + + if self.downsamplers is not None: + for downsampler in self.downsamplers: + hidden_states = downsampler(hidden_states) + + output_states = output_states + (hidden_states,) + + return hidden_states, output_states + + def hacked_DownBlock2D_forward(self, hidden_states, temb=None, scale=None): + eps = 1e-6 + + output_states = () + + for i, resnet in enumerate(self.resnets): + hidden_states = resnet(hidden_states, temb) + + if MODE == "write": + if gn_auto_machine_weight >= self.gn_weight: + var, mean = torch.var_mean(hidden_states, dim=(2, 3), keepdim=True, correction=0) + self.mean_bank.append([mean]) + self.var_bank.append([var]) + if MODE == "read": + if len(self.mean_bank) > 0 and len(self.var_bank) > 0: + var, mean = torch.var_mean(hidden_states, dim=(2, 3), keepdim=True, correction=0) + std = torch.maximum(var, torch.zeros_like(var) + eps) ** 0.5 + mean_acc = sum(self.mean_bank[i]) / float(len(self.mean_bank[i])) + var_acc = sum(self.var_bank[i]) / float(len(self.var_bank[i])) + std_acc = torch.maximum(var_acc, torch.zeros_like(var_acc) + eps) ** 0.5 + hidden_states_uc = (((hidden_states - mean) / std) * std_acc) + mean_acc + hidden_states_c = hidden_states_uc.clone() + if do_classifier_free_guidance and style_fidelity > 0: + hidden_states_c[uc_mask] = hidden_states[uc_mask] + hidden_states = style_fidelity * hidden_states_c + (1.0 - style_fidelity) * hidden_states_uc + + output_states = output_states + (hidden_states,) + + if MODE == "read": + self.mean_bank = [] + self.var_bank = [] + + if self.downsamplers is not None: + for downsampler in self.downsamplers: + hidden_states = downsampler(hidden_states) + + output_states = output_states + (hidden_states,) + + return hidden_states, output_states + + def hacked_CrossAttnUpBlock2D_forward( + self, + hidden_states: torch.FloatTensor, + res_hidden_states_tuple: Tuple[torch.FloatTensor, ...], + temb: Optional[torch.FloatTensor] = None, + encoder_hidden_states: Optional[torch.FloatTensor] = None, + cross_attention_kwargs: Optional[Dict[str, Any]] = None, + upsample_size: Optional[int] = None, + attention_mask: Optional[torch.FloatTensor] = None, + encoder_attention_mask: Optional[torch.FloatTensor] = None, + ): + eps = 1e-6 + # TODO(Patrick, William) - attention mask is not used + for i, (resnet, attn) in enumerate(zip(self.resnets, self.attentions)): + # pop res hidden states + res_hidden_states = res_hidden_states_tuple[-1] + res_hidden_states_tuple = res_hidden_states_tuple[:-1] + hidden_states = torch.cat([hidden_states, res_hidden_states], dim=1) + hidden_states = resnet(hidden_states, temb) + hidden_states = attn( + hidden_states, + encoder_hidden_states=encoder_hidden_states, + cross_attention_kwargs=cross_attention_kwargs, + attention_mask=attention_mask, + encoder_attention_mask=encoder_attention_mask, + return_dict=False, + )[0] + + if MODE == "write": + if gn_auto_machine_weight >= self.gn_weight: + var, mean = torch.var_mean(hidden_states, dim=(2, 3), keepdim=True, correction=0) + self.mean_bank.append([mean]) + self.var_bank.append([var]) + if MODE == "read": + if len(self.mean_bank) > 0 and len(self.var_bank) > 0: + var, mean = torch.var_mean(hidden_states, dim=(2, 3), keepdim=True, correction=0) + std = torch.maximum(var, torch.zeros_like(var) + eps) ** 0.5 + mean_acc = sum(self.mean_bank[i]) / float(len(self.mean_bank[i])) + var_acc = sum(self.var_bank[i]) / float(len(self.var_bank[i])) + std_acc = torch.maximum(var_acc, torch.zeros_like(var_acc) + eps) ** 0.5 + hidden_states_uc = (((hidden_states - mean) / std) * std_acc) + mean_acc + hidden_states_c = hidden_states_uc.clone() + if do_classifier_free_guidance and style_fidelity > 0: + hidden_states_c[uc_mask] = hidden_states[uc_mask] + hidden_states = style_fidelity * hidden_states_c + (1.0 - style_fidelity) * hidden_states_uc + + if MODE == "read": + self.mean_bank = [] + self.var_bank = [] + + if self.upsamplers is not None: + for upsampler in self.upsamplers: + hidden_states = upsampler(hidden_states, upsample_size) + + return hidden_states + + def hacked_UpBlock2D_forward(self, hidden_states, res_hidden_states_tuple, temb=None, upsample_size=None, scale=None): + eps = 1e-6 + for i, resnet in enumerate(self.resnets): + # pop res hidden states + res_hidden_states = res_hidden_states_tuple[-1] + res_hidden_states_tuple = res_hidden_states_tuple[:-1] + hidden_states = torch.cat([hidden_states, res_hidden_states], dim=1) + hidden_states = resnet(hidden_states, temb) + + if MODE == "write": + if gn_auto_machine_weight >= self.gn_weight: + var, mean = torch.var_mean(hidden_states, dim=(2, 3), keepdim=True, correction=0) + self.mean_bank.append([mean]) + self.var_bank.append([var]) + if MODE == "read": + if len(self.mean_bank) > 0 and len(self.var_bank) > 0: + var, mean = torch.var_mean(hidden_states, dim=(2, 3), keepdim=True, correction=0) + std = torch.maximum(var, torch.zeros_like(var) + eps) ** 0.5 + mean_acc = sum(self.mean_bank[i]) / float(len(self.mean_bank[i])) + var_acc = sum(self.var_bank[i]) / float(len(self.var_bank[i])) + std_acc = torch.maximum(var_acc, torch.zeros_like(var_acc) + eps) ** 0.5 + hidden_states_uc = (((hidden_states - mean) / std) * std_acc) + mean_acc + hidden_states_c = hidden_states_uc.clone() + if do_classifier_free_guidance and style_fidelity > 0: + hidden_states_c[uc_mask] = hidden_states[uc_mask] + hidden_states = style_fidelity * hidden_states_c + (1.0 - style_fidelity) * hidden_states_uc + + if MODE == "read": + self.mean_bank = [] + self.var_bank = [] + + if self.upsamplers is not None: + for upsampler in self.upsamplers: + hidden_states = upsampler(hidden_states, upsample_size) + + return hidden_states + + if reference_attn: + attn_modules = [module for module in torch_dfs(self.unet) if isinstance(module, BasicTransformerBlock)] + attn_modules = sorted(attn_modules, key=lambda x: -x.norm1.normalized_shape[0]) + + for i, module in enumerate(attn_modules): + module._original_inner_forward = module.forward + module.forward = hacked_basic_transformer_inner_forward.__get__(module, BasicTransformerBlock) + module.bank = [] + module.attn_weight = float(i) / float(len(attn_modules)) + + if reference_adain: + gn_modules = [self.unet.mid_block] + self.unet.mid_block.gn_weight = 0 + + down_blocks = self.unet.down_blocks + for w, module in enumerate(down_blocks): + module.gn_weight = 1.0 - float(w) / float(len(down_blocks)) + gn_modules.append(module) + + up_blocks = self.unet.up_blocks + for w, module in enumerate(up_blocks): + module.gn_weight = float(w) / float(len(up_blocks)) + gn_modules.append(module) + + for i, module in enumerate(gn_modules): + if getattr(module, "original_forward", None) is None: + module.original_forward = module.forward + if i == 0: + # mid_block + module.forward = hacked_mid_forward.__get__(module, torch.nn.Module) + elif isinstance(module, CrossAttnDownBlock2D): + module.forward = hack_CrossAttnDownBlock2D_forward.__get__(module, CrossAttnDownBlock2D) + elif isinstance(module, DownBlock2D): + module.forward = hacked_DownBlock2D_forward.__get__(module, DownBlock2D) + elif isinstance(module, CrossAttnUpBlock2D): + module.forward = hacked_CrossAttnUpBlock2D_forward.__get__(module, CrossAttnUpBlock2D) + elif isinstance(module, UpBlock2D): + module.forward = hacked_UpBlock2D_forward.__get__(module, UpBlock2D) + module.mean_bank = [] + module.var_bank = [] + module.gn_weight *= 2 + + # 10. Denoising loop + num_warmup_steps = len(timesteps) - num_inference_steps * self.scheduler.order + with self.progress_bar(total=num_inference_steps) as progress_bar: + for i, t in enumerate(timesteps): + # expand the latents if we are doing classifier free guidance + latent_model_input = torch.cat([latents] * 2) if do_classifier_free_guidance else latents + latent_model_input = self.scheduler.scale_model_input(latent_model_input, t) + + # ref only part + noise = randn_tensor( + ref_image_latents.shape, generator=generator, device=device, dtype=ref_image_latents.dtype + ) + ref_xt = self.scheduler.add_noise( + ref_image_latents, + noise, + t.reshape( + 1, + ), + ) + ref_xt = torch.cat([ref_xt] * 2) if do_classifier_free_guidance else ref_xt + ref_xt = self.scheduler.scale_model_input(ref_xt, t) + + MODE = "write" + self.unet( + ref_xt, + t, + encoder_hidden_states=prompt_embeds, + cross_attention_kwargs=cross_attention_kwargs, + return_dict=False, + ) + + # predict the noise residual + MODE = "read" + noise_pred = self.unet( + latent_model_input, + t, + encoder_hidden_states=prompt_embeds, + cross_attention_kwargs=cross_attention_kwargs, + return_dict=False, + )[0] + + # perform guidance + if do_classifier_free_guidance: + noise_pred_uncond, noise_pred_text = noise_pred.chunk(2) + noise_pred = noise_pred_uncond + guidance_scale * (noise_pred_text - noise_pred_uncond) + + if do_classifier_free_guidance and guidance_rescale > 0.0: + # Based on 3.4. in https://arxiv.org/pdf/2305.08891.pdf + noise_pred = rescale_noise_cfg(noise_pred, noise_pred_text, guidance_rescale=guidance_rescale) + + # compute the previous noisy sample x_t -> x_t-1 + latents = self.scheduler.step(noise_pred, t, latents, **extra_step_kwargs, return_dict=False)[0] + + # call the callback, if provided + if i == len(timesteps) - 1 or ((i + 1) > num_warmup_steps and (i + 1) % self.scheduler.order == 0): + progress_bar.update() + if callback is not None and i % callback_steps == 0: + step_idx = i // getattr(self.scheduler, "order", 1) + callback(step_idx, t, latents) + + if output_type != "latent": + image = self.vae.decode(latents / self.vae.config.scaling_factor, return_dict=False)[0] + image, has_nsfw_concept = self.run_safety_checker(image, device, prompt_embeds.dtype) + else: + image = latents + has_nsfw_concept = None + + if has_nsfw_concept is None: + do_denormalize = [True] * image.shape[0] + else: + do_denormalize = [not has_nsfw for has_nsfw in has_nsfw_concept] + + image = self.image_processor.postprocess(image, output_type=output_type, do_denormalize=do_denormalize) + + # Offload last model to CPU + if hasattr(self, "final_offload_hook") and self.final_offload_hook is not None: + self.final_offload_hook.offload() + + if not return_dict: + return (image, has_nsfw_concept) + + return StableDiffusionPipelineOutput(images=image, nsfw_content_detected=has_nsfw_concept) diff --git a/modules/control/proc/reference_sdxl.py b/modules/control/proc/reference_sdxl.py new file mode 100644 index 000000000..0ccd9914f --- /dev/null +++ b/modules/control/proc/reference_sdxl.py @@ -0,0 +1,804 @@ +# Based on stable_diffusion_reference.py + +from typing import Any, Callable, Dict, List, Optional, Tuple, Union + +import numpy as np +import PIL.Image +import torch + +from diffusers import StableDiffusionXLPipeline +from diffusers.models.attention import BasicTransformerBlock +from diffusers.models.unet_2d_blocks import ( + CrossAttnDownBlock2D, + CrossAttnUpBlock2D, + DownBlock2D, + UpBlock2D, +) +from diffusers.pipelines.stable_diffusion_xl import StableDiffusionXLPipelineOutput +from diffusers.utils import PIL_INTERPOLATION +from diffusers.utils.torch_utils import randn_tensor + + +EXAMPLE_DOC_STRING = """ + Examples: + ```py + >>> import torch + >>> from diffusers import UniPCMultistepScheduler + >>> from diffusers.utils import load_image + + >>> input_image = load_image("https://hf.co/datasets/huggingface/documentation-images/resolve/main/diffusers/input_image_vermeer.png") + + >>> pipe = StableDiffusionXLReferencePipeline.from_pretrained( + "stabilityai/stable-diffusion-xl-base-1.0", + torch_dtype=torch.float16, + use_safetensors=True, + variant="fp16").to('cuda:0') + + >>> pipe.scheduler = UniPCMultistepScheduler.from_config(pipe.scheduler.config) + >>> result_img = pipe(ref_image=input_image, + prompt="1girl", + num_inference_steps=20, + reference_attn=True, + reference_adain=True).images[0] + + >>> result_img.show() + ``` +""" + + +def torch_dfs(model: torch.nn.Module): + result = [model] + for child in model.children(): + result += torch_dfs(child) + return result + + +# Copied from diffusers.pipelines.stable_diffusion.pipeline_stable_diffusion.rescale_noise_cfg + + +def rescale_noise_cfg(noise_cfg, noise_pred_text, guidance_rescale=0.0): + """ + Rescale `noise_cfg` according to `guidance_rescale`. Based on findings of [Common Diffusion Noise Schedules and + Sample Steps are Flawed](https://arxiv.org/pdf/2305.08891.pdf). See Section 3.4 + """ + std_text = noise_pred_text.std(dim=list(range(1, noise_pred_text.ndim)), keepdim=True) + std_cfg = noise_cfg.std(dim=list(range(1, noise_cfg.ndim)), keepdim=True) + # rescale the results from guidance (fixes overexposure) + noise_pred_rescaled = noise_cfg * (std_text / std_cfg) + # mix with the original results from guidance by factor guidance_rescale to avoid "plain looking" images + noise_cfg = guidance_rescale * noise_pred_rescaled + (1 - guidance_rescale) * noise_cfg + return noise_cfg + + +class StableDiffusionXLReferencePipeline(StableDiffusionXLPipeline): + def _default_height_width(self, height, width, image): + # NOTE: It is possible that a list of images have different + # dimensions for each image, so just checking the first image + # is not _exactly_ correct, but it is simple. + while isinstance(image, list): + image = image[0] + + if height is None: + if isinstance(image, PIL.Image.Image): + height = image.height + elif isinstance(image, torch.Tensor): + height = image.shape[2] + + height = (height // 8) * 8 # round down to nearest multiple of 8 + + if width is None: + if isinstance(image, PIL.Image.Image): + width = image.width + elif isinstance(image, torch.Tensor): + width = image.shape[3] + + width = (width // 8) * 8 + + return height, width + + def prepare_image( + self, + image, + width, + height, + batch_size, + num_images_per_prompt, + device, + dtype, + do_classifier_free_guidance=False, + guess_mode=False, + ): + if not isinstance(image, torch.Tensor): + if isinstance(image, PIL.Image.Image): + image = [image] + + if isinstance(image[0], PIL.Image.Image): + images = [] + + for image_ in image: + image_ = image_.convert("RGB") + image_ = image_.resize((width, height), resample=PIL_INTERPOLATION["lanczos"]) + image_ = np.array(image_) + image_ = image_[None, :] + images.append(image_) + + image = images + + image = np.concatenate(image, axis=0) + image = np.array(image).astype(np.float32) / 255.0 + image = (image - 0.5) / 0.5 + image = image.transpose(0, 3, 1, 2) + image = torch.from_numpy(image) + + elif isinstance(image[0], torch.Tensor): + image = torch.stack(image, dim=0) + + image_batch_size = image.shape[0] + + if image_batch_size == 1: + repeat_by = batch_size + else: + repeat_by = num_images_per_prompt + + image = image.repeat_interleave(repeat_by, dim=0) + + image = image.to(device=device, dtype=dtype) + + if do_classifier_free_guidance and not guess_mode: + image = torch.cat([image] * 2) + + return image + + def prepare_ref_latents(self, refimage, batch_size, dtype, device, generator, do_classifier_free_guidance): + refimage = refimage.to(device=device) + if self.vae.dtype == torch.float16 and self.vae.config.force_upcast: + self.upcast_vae() + refimage = refimage.to(next(iter(self.vae.post_quant_conv.parameters())).dtype) + if refimage.dtype != self.vae.dtype: + refimage = refimage.to(dtype=self.vae.dtype) + # encode the mask image into latents space so we can concatenate it to the latents + if isinstance(generator, list): + ref_image_latents = [ + self.vae.encode(refimage[i : i + 1]).latent_dist.sample(generator=generator[i]) + for i in range(batch_size) + ] + ref_image_latents = torch.cat(ref_image_latents, dim=0) + else: + ref_image_latents = self.vae.encode(refimage).latent_dist.sample(generator=generator) + ref_image_latents = self.vae.config.scaling_factor * ref_image_latents + + # duplicate mask and ref_image_latents for each generation per prompt, using mps friendly method + if ref_image_latents.shape[0] < batch_size: + if not batch_size % ref_image_latents.shape[0] == 0: + raise ValueError( + "The passed images and the required batch size don't match. Images are supposed to be duplicated" + f" to a total batch size of {batch_size}, but {ref_image_latents.shape[0]} images were passed." + " Make sure the number of images that you pass is divisible by the total requested batch size." + ) + ref_image_latents = ref_image_latents.repeat(batch_size // ref_image_latents.shape[0], 1, 1, 1) + + ref_image_latents = torch.cat([ref_image_latents] * 2) if do_classifier_free_guidance else ref_image_latents + + # aligning device to prevent device errors when concating it with the latent model input + ref_image_latents = ref_image_latents.to(device=device, dtype=dtype) + return ref_image_latents + + def __call__( + self, + prompt: Union[str, List[str]] = None, + prompt_2: Optional[Union[str, List[str]]] = None, + ref_image: Union[torch.FloatTensor, PIL.Image.Image] = None, + height: Optional[int] = None, + width: Optional[int] = None, + num_inference_steps: int = 50, + denoising_end: Optional[float] = None, + guidance_scale: float = 5.0, + negative_prompt: Optional[Union[str, List[str]]] = None, + negative_prompt_2: Optional[Union[str, List[str]]] = None, + num_images_per_prompt: Optional[int] = 1, + eta: float = 0.0, + generator: Optional[Union[torch.Generator, List[torch.Generator]]] = None, + latents: Optional[torch.FloatTensor] = None, + prompt_embeds: Optional[torch.FloatTensor] = None, + negative_prompt_embeds: Optional[torch.FloatTensor] = None, + pooled_prompt_embeds: Optional[torch.FloatTensor] = None, + negative_pooled_prompt_embeds: Optional[torch.FloatTensor] = None, + output_type: Optional[str] = "pil", + return_dict: bool = True, + callback: Optional[Callable[[int, int, torch.FloatTensor], None]] = None, + callback_steps: int = 1, + cross_attention_kwargs: Optional[Dict[str, Any]] = None, + guidance_rescale: float = 0.0, + original_size: Optional[Tuple[int, int]] = None, + crops_coords_top_left: Tuple[int, int] = (0, 0), + target_size: Optional[Tuple[int, int]] = None, + attention_auto_machine_weight: float = 1.0, + gn_auto_machine_weight: float = 1.0, + style_fidelity: float = 0.5, + reference_attn: bool = True, + reference_adain: bool = True, + ): + assert reference_attn or reference_adain, "`reference_attn` or `reference_adain` must be True." + + # 0. Default height and width to unet + # height, width = self._default_height_width(height, width, ref_image) + + height = height or self.default_sample_size * self.vae_scale_factor + width = width or self.default_sample_size * self.vae_scale_factor + original_size = original_size or (height, width) + target_size = target_size or (height, width) + + # 1. Check inputs. Raise error if not correct + self.check_inputs( + prompt, + prompt_2, + height, + width, + callback_steps, + negative_prompt, + negative_prompt_2, + prompt_embeds, + negative_prompt_embeds, + pooled_prompt_embeds, + negative_pooled_prompt_embeds, + ) + + # 2. Define call parameters + if prompt is not None and isinstance(prompt, str): + batch_size = 1 + elif prompt is not None and isinstance(prompt, list): + batch_size = len(prompt) + else: + batch_size = prompt_embeds.shape[0] + + device = self._execution_device + + # here `guidance_scale` is defined analog to the guidance weight `w` of equation (2) + # of the Imagen paper: https://arxiv.org/pdf/2205.11487.pdf . `guidance_scale = 1` + # corresponds to doing no classifier free guidance. + do_classifier_free_guidance = guidance_scale > 1.0 + + # 3. Encode input prompt + text_encoder_lora_scale = ( + cross_attention_kwargs.get("scale", None) if cross_attention_kwargs is not None else None + ) + ( + prompt_embeds, + negative_prompt_embeds, + pooled_prompt_embeds, + negative_pooled_prompt_embeds, + ) = self.encode_prompt( + prompt=prompt, + prompt_2=prompt_2, + device=device, + num_images_per_prompt=num_images_per_prompt, + do_classifier_free_guidance=do_classifier_free_guidance, + negative_prompt=negative_prompt, + negative_prompt_2=negative_prompt_2, + prompt_embeds=prompt_embeds, + negative_prompt_embeds=negative_prompt_embeds, + pooled_prompt_embeds=pooled_prompt_embeds, + negative_pooled_prompt_embeds=negative_pooled_prompt_embeds, + lora_scale=text_encoder_lora_scale, + ) + # 4. Preprocess reference image + ref_image = self.prepare_image( + image=ref_image, + width=width, + height=height, + batch_size=batch_size * num_images_per_prompt, + num_images_per_prompt=num_images_per_prompt, + device=device, + dtype=prompt_embeds.dtype, + ) + + # 5. Prepare timesteps + self.scheduler.set_timesteps(num_inference_steps, device=device) + + timesteps = self.scheduler.timesteps + + # 6. Prepare latent variables + num_channels_latents = self.unet.config.in_channels + latents = self.prepare_latents( + batch_size * num_images_per_prompt, + num_channels_latents, + height, + width, + prompt_embeds.dtype, + device, + generator, + latents, + ) + # 7. Prepare reference latent variables + ref_image_latents = self.prepare_ref_latents( + ref_image, + batch_size * num_images_per_prompt, + prompt_embeds.dtype, + device, + generator, + do_classifier_free_guidance, + ) + + # 8. Prepare extra step kwargs. TODO: Logic should ideally just be moved out of the pipeline + extra_step_kwargs = self.prepare_extra_step_kwargs(generator, eta) + + # 9. Modify self attebtion and group norm + MODE = "write" + uc_mask = ( + torch.Tensor([1] * batch_size * num_images_per_prompt + [0] * batch_size * num_images_per_prompt) + .type_as(ref_image_latents) + .bool() + ) + + def hacked_basic_transformer_inner_forward( + self, + hidden_states: torch.FloatTensor, + attention_mask: Optional[torch.FloatTensor] = None, + encoder_hidden_states: Optional[torch.FloatTensor] = None, + encoder_attention_mask: Optional[torch.FloatTensor] = None, + timestep: Optional[torch.LongTensor] = None, + cross_attention_kwargs: Dict[str, Any] = None, + class_labels: Optional[torch.LongTensor] = None, + ): + if self.use_ada_layer_norm: + norm_hidden_states = self.norm1(hidden_states, timestep) + elif self.use_ada_layer_norm_zero: + norm_hidden_states, gate_msa, shift_mlp, scale_mlp, gate_mlp = self.norm1( + hidden_states, timestep, class_labels, hidden_dtype=hidden_states.dtype + ) + else: + norm_hidden_states = self.norm1(hidden_states) + + # 1. Self-Attention + cross_attention_kwargs = cross_attention_kwargs if cross_attention_kwargs is not None else {} + if self.only_cross_attention: + attn_output = self.attn1( + norm_hidden_states, + encoder_hidden_states=encoder_hidden_states if self.only_cross_attention else None, + attention_mask=attention_mask, + **cross_attention_kwargs, + ) + else: + if MODE == "write": + self.bank.append(norm_hidden_states.detach().clone()) + attn_output = self.attn1( + norm_hidden_states, + encoder_hidden_states=encoder_hidden_states if self.only_cross_attention else None, + attention_mask=attention_mask, + **cross_attention_kwargs, + ) + if MODE == "read": + if attention_auto_machine_weight > self.attn_weight: + attn_output_uc = self.attn1( + norm_hidden_states, + encoder_hidden_states=torch.cat([norm_hidden_states] + self.bank, dim=1), + # attention_mask=attention_mask, + **cross_attention_kwargs, + ) + attn_output_c = attn_output_uc.clone() + if do_classifier_free_guidance and style_fidelity > 0: + attn_output_c[uc_mask] = self.attn1( + norm_hidden_states[uc_mask], + encoder_hidden_states=norm_hidden_states[uc_mask], + **cross_attention_kwargs, + ) + attn_output = style_fidelity * attn_output_c + (1.0 - style_fidelity) * attn_output_uc + self.bank.clear() + else: + attn_output = self.attn1( + norm_hidden_states, + encoder_hidden_states=encoder_hidden_states if self.only_cross_attention else None, + attention_mask=attention_mask, + **cross_attention_kwargs, + ) + if self.use_ada_layer_norm_zero: + attn_output = gate_msa.unsqueeze(1) * attn_output + hidden_states = attn_output + hidden_states + + if self.attn2 is not None: + norm_hidden_states = ( + self.norm2(hidden_states, timestep) if self.use_ada_layer_norm else self.norm2(hidden_states) + ) + + # 2. Cross-Attention + attn_output = self.attn2( + norm_hidden_states, + encoder_hidden_states=encoder_hidden_states, + attention_mask=encoder_attention_mask, + **cross_attention_kwargs, + ) + hidden_states = attn_output + hidden_states + + # 3. Feed-forward + norm_hidden_states = self.norm3(hidden_states) + + if self.use_ada_layer_norm_zero: + norm_hidden_states = norm_hidden_states * (1 + scale_mlp[:, None]) + shift_mlp[:, None] + + ff_output = self.ff(norm_hidden_states) + + if self.use_ada_layer_norm_zero: + ff_output = gate_mlp.unsqueeze(1) * ff_output + + hidden_states = ff_output + hidden_states + + return hidden_states + + def hacked_mid_forward(self, *args, **kwargs): + eps = 1e-6 + x = self.original_forward(*args, **kwargs) + if MODE == "write": + if gn_auto_machine_weight >= self.gn_weight: + var, mean = torch.var_mean(x, dim=(2, 3), keepdim=True, correction=0) + self.mean_bank.append(mean) + self.var_bank.append(var) + if MODE == "read": + if len(self.mean_bank) > 0 and len(self.var_bank) > 0: + var, mean = torch.var_mean(x, dim=(2, 3), keepdim=True, correction=0) + std = torch.maximum(var, torch.zeros_like(var) + eps) ** 0.5 + mean_acc = sum(self.mean_bank) / float(len(self.mean_bank)) + var_acc = sum(self.var_bank) / float(len(self.var_bank)) + std_acc = torch.maximum(var_acc, torch.zeros_like(var_acc) + eps) ** 0.5 + x_uc = (((x - mean) / std) * std_acc) + mean_acc + x_c = x_uc.clone() + if do_classifier_free_guidance and style_fidelity > 0: + x_c[uc_mask] = x[uc_mask] + x = style_fidelity * x_c + (1.0 - style_fidelity) * x_uc + self.mean_bank = [] + self.var_bank = [] + return x + + def hack_CrossAttnDownBlock2D_forward( + self, + hidden_states: torch.FloatTensor, + temb: Optional[torch.FloatTensor] = None, + encoder_hidden_states: Optional[torch.FloatTensor] = None, + attention_mask: Optional[torch.FloatTensor] = None, + cross_attention_kwargs: Optional[Dict[str, Any]] = None, + encoder_attention_mask: Optional[torch.FloatTensor] = None, + ): + eps = 1e-6 + + # TODO(Patrick, William) - attention mask is not used + output_states = () + + for i, (resnet, attn) in enumerate(zip(self.resnets, self.attentions)): + hidden_states = resnet(hidden_states, temb) + hidden_states = attn( + hidden_states, + encoder_hidden_states=encoder_hidden_states, + cross_attention_kwargs=cross_attention_kwargs, + attention_mask=attention_mask, + encoder_attention_mask=encoder_attention_mask, + return_dict=False, + )[0] + if MODE == "write": + if gn_auto_machine_weight >= self.gn_weight: + var, mean = torch.var_mean(hidden_states, dim=(2, 3), keepdim=True, correction=0) + self.mean_bank.append([mean]) + self.var_bank.append([var]) + if MODE == "read": + if len(self.mean_bank) > 0 and len(self.var_bank) > 0: + var, mean = torch.var_mean(hidden_states, dim=(2, 3), keepdim=True, correction=0) + std = torch.maximum(var, torch.zeros_like(var) + eps) ** 0.5 + mean_acc = sum(self.mean_bank[i]) / float(len(self.mean_bank[i])) + var_acc = sum(self.var_bank[i]) / float(len(self.var_bank[i])) + std_acc = torch.maximum(var_acc, torch.zeros_like(var_acc) + eps) ** 0.5 + hidden_states_uc = (((hidden_states - mean) / std) * std_acc) + mean_acc + hidden_states_c = hidden_states_uc.clone() + if do_classifier_free_guidance and style_fidelity > 0: + hidden_states_c[uc_mask] = hidden_states[uc_mask] + hidden_states = style_fidelity * hidden_states_c + (1.0 - style_fidelity) * hidden_states_uc + + output_states = output_states + (hidden_states,) + + if MODE == "read": + self.mean_bank = [] + self.var_bank = [] + + if self.downsamplers is not None: + for downsampler in self.downsamplers: + hidden_states = downsampler(hidden_states) + + output_states = output_states + (hidden_states,) + + return hidden_states, output_states + + def hacked_DownBlock2D_forward(self, hidden_states, temb=None): + eps = 1e-6 + + output_states = () + + for i, resnet in enumerate(self.resnets): + hidden_states = resnet(hidden_states, temb) + + if MODE == "write": + if gn_auto_machine_weight >= self.gn_weight: + var, mean = torch.var_mean(hidden_states, dim=(2, 3), keepdim=True, correction=0) + self.mean_bank.append([mean]) + self.var_bank.append([var]) + if MODE == "read": + if len(self.mean_bank) > 0 and len(self.var_bank) > 0: + var, mean = torch.var_mean(hidden_states, dim=(2, 3), keepdim=True, correction=0) + std = torch.maximum(var, torch.zeros_like(var) + eps) ** 0.5 + mean_acc = sum(self.mean_bank[i]) / float(len(self.mean_bank[i])) + var_acc = sum(self.var_bank[i]) / float(len(self.var_bank[i])) + std_acc = torch.maximum(var_acc, torch.zeros_like(var_acc) + eps) ** 0.5 + hidden_states_uc = (((hidden_states - mean) / std) * std_acc) + mean_acc + hidden_states_c = hidden_states_uc.clone() + if do_classifier_free_guidance and style_fidelity > 0: + hidden_states_c[uc_mask] = hidden_states[uc_mask] + hidden_states = style_fidelity * hidden_states_c + (1.0 - style_fidelity) * hidden_states_uc + + output_states = output_states + (hidden_states,) + + if MODE == "read": + self.mean_bank = [] + self.var_bank = [] + + if self.downsamplers is not None: + for downsampler in self.downsamplers: + hidden_states = downsampler(hidden_states) + + output_states = output_states + (hidden_states,) + + return hidden_states, output_states + + def hacked_CrossAttnUpBlock2D_forward( + self, + hidden_states: torch.FloatTensor, + res_hidden_states_tuple: Tuple[torch.FloatTensor, ...], + temb: Optional[torch.FloatTensor] = None, + encoder_hidden_states: Optional[torch.FloatTensor] = None, + cross_attention_kwargs: Optional[Dict[str, Any]] = None, + upsample_size: Optional[int] = None, + attention_mask: Optional[torch.FloatTensor] = None, + encoder_attention_mask: Optional[torch.FloatTensor] = None, + ): + eps = 1e-6 + # TODO(Patrick, William) - attention mask is not used + for i, (resnet, attn) in enumerate(zip(self.resnets, self.attentions)): + # pop res hidden states + res_hidden_states = res_hidden_states_tuple[-1] + res_hidden_states_tuple = res_hidden_states_tuple[:-1] + hidden_states = torch.cat([hidden_states, res_hidden_states], dim=1) + hidden_states = resnet(hidden_states, temb) + hidden_states = attn( + hidden_states, + encoder_hidden_states=encoder_hidden_states, + cross_attention_kwargs=cross_attention_kwargs, + attention_mask=attention_mask, + encoder_attention_mask=encoder_attention_mask, + return_dict=False, + )[0] + + if MODE == "write": + if gn_auto_machine_weight >= self.gn_weight: + var, mean = torch.var_mean(hidden_states, dim=(2, 3), keepdim=True, correction=0) + self.mean_bank.append([mean]) + self.var_bank.append([var]) + if MODE == "read": + if len(self.mean_bank) > 0 and len(self.var_bank) > 0: + var, mean = torch.var_mean(hidden_states, dim=(2, 3), keepdim=True, correction=0) + std = torch.maximum(var, torch.zeros_like(var) + eps) ** 0.5 + mean_acc = sum(self.mean_bank[i]) / float(len(self.mean_bank[i])) + var_acc = sum(self.var_bank[i]) / float(len(self.var_bank[i])) + std_acc = torch.maximum(var_acc, torch.zeros_like(var_acc) + eps) ** 0.5 + hidden_states_uc = (((hidden_states - mean) / std) * std_acc) + mean_acc + hidden_states_c = hidden_states_uc.clone() + if do_classifier_free_guidance and style_fidelity > 0: + hidden_states_c[uc_mask] = hidden_states[uc_mask] + hidden_states = style_fidelity * hidden_states_c + (1.0 - style_fidelity) * hidden_states_uc + + if MODE == "read": + self.mean_bank = [] + self.var_bank = [] + + if self.upsamplers is not None: + for upsampler in self.upsamplers: + hidden_states = upsampler(hidden_states, upsample_size) + + return hidden_states + + def hacked_UpBlock2D_forward(self, hidden_states, res_hidden_states_tuple, temb=None, upsample_size=None): + eps = 1e-6 + for i, resnet in enumerate(self.resnets): + # pop res hidden states + res_hidden_states = res_hidden_states_tuple[-1] + res_hidden_states_tuple = res_hidden_states_tuple[:-1] + hidden_states = torch.cat([hidden_states, res_hidden_states], dim=1) + hidden_states = resnet(hidden_states, temb) + + if MODE == "write": + if gn_auto_machine_weight >= self.gn_weight: + var, mean = torch.var_mean(hidden_states, dim=(2, 3), keepdim=True, correction=0) + self.mean_bank.append([mean]) + self.var_bank.append([var]) + if MODE == "read": + if len(self.mean_bank) > 0 and len(self.var_bank) > 0: + var, mean = torch.var_mean(hidden_states, dim=(2, 3), keepdim=True, correction=0) + std = torch.maximum(var, torch.zeros_like(var) + eps) ** 0.5 + mean_acc = sum(self.mean_bank[i]) / float(len(self.mean_bank[i])) + var_acc = sum(self.var_bank[i]) / float(len(self.var_bank[i])) + std_acc = torch.maximum(var_acc, torch.zeros_like(var_acc) + eps) ** 0.5 + hidden_states_uc = (((hidden_states - mean) / std) * std_acc) + mean_acc + hidden_states_c = hidden_states_uc.clone() + if do_classifier_free_guidance and style_fidelity > 0: + hidden_states_c[uc_mask] = hidden_states[uc_mask] + hidden_states = style_fidelity * hidden_states_c + (1.0 - style_fidelity) * hidden_states_uc + + if MODE == "read": + self.mean_bank = [] + self.var_bank = [] + + if self.upsamplers is not None: + for upsampler in self.upsamplers: + hidden_states = upsampler(hidden_states, upsample_size) + + return hidden_states + + if reference_attn: + attn_modules = [module for module in torch_dfs(self.unet) if isinstance(module, BasicTransformerBlock)] + attn_modules = sorted(attn_modules, key=lambda x: -x.norm1.normalized_shape[0]) + + for i, module in enumerate(attn_modules): + module._original_inner_forward = module.forward + module.forward = hacked_basic_transformer_inner_forward.__get__(module, BasicTransformerBlock) + module.bank = [] + module.attn_weight = float(i) / float(len(attn_modules)) + + if reference_adain: + gn_modules = [self.unet.mid_block] + self.unet.mid_block.gn_weight = 0 + + down_blocks = self.unet.down_blocks + for w, module in enumerate(down_blocks): + module.gn_weight = 1.0 - float(w) / float(len(down_blocks)) + gn_modules.append(module) + + up_blocks = self.unet.up_blocks + for w, module in enumerate(up_blocks): + module.gn_weight = float(w) / float(len(up_blocks)) + gn_modules.append(module) + + for i, module in enumerate(gn_modules): + if getattr(module, "original_forward", None) is None: + module.original_forward = module.forward + if i == 0: + # mid_block + module.forward = hacked_mid_forward.__get__(module, torch.nn.Module) + elif isinstance(module, CrossAttnDownBlock2D): + module.forward = hack_CrossAttnDownBlock2D_forward.__get__(module, CrossAttnDownBlock2D) + elif isinstance(module, DownBlock2D): + module.forward = hacked_DownBlock2D_forward.__get__(module, DownBlock2D) + elif isinstance(module, CrossAttnUpBlock2D): + module.forward = hacked_CrossAttnUpBlock2D_forward.__get__(module, CrossAttnUpBlock2D) + elif isinstance(module, UpBlock2D): + module.forward = hacked_UpBlock2D_forward.__get__(module, UpBlock2D) + module.mean_bank = [] + module.var_bank = [] + module.gn_weight *= 2 + + # 10. Prepare added time ids & embeddings + add_text_embeds = pooled_prompt_embeds + add_time_ids = self._get_add_time_ids( + original_size, crops_coords_top_left, target_size, dtype=prompt_embeds.dtype + ) + + if do_classifier_free_guidance: + prompt_embeds = torch.cat([negative_prompt_embeds, prompt_embeds], dim=0) + add_text_embeds = torch.cat([negative_pooled_prompt_embeds, add_text_embeds], dim=0) + add_time_ids = torch.cat([add_time_ids, add_time_ids], dim=0) + + prompt_embeds = prompt_embeds.to(device) + add_text_embeds = add_text_embeds.to(device) + add_time_ids = add_time_ids.to(device).repeat(batch_size * num_images_per_prompt, 1) + + # 11. Denoising loop + num_warmup_steps = max(len(timesteps) - num_inference_steps * self.scheduler.order, 0) + + # 10.1 Apply denoising_end + if denoising_end is not None and isinstance(denoising_end, float) and denoising_end > 0 and denoising_end < 1: + discrete_timestep_cutoff = int( + round( + self.scheduler.config.num_train_timesteps + - (denoising_end * self.scheduler.config.num_train_timesteps) + ) + ) + num_inference_steps = len(list(filter(lambda ts: ts >= discrete_timestep_cutoff, timesteps))) + timesteps = timesteps[:num_inference_steps] + + with self.progress_bar(total=num_inference_steps) as progress_bar: + for i, t in enumerate(timesteps): + # expand the latents if we are doing classifier free guidance + latent_model_input = torch.cat([latents] * 2) if do_classifier_free_guidance else latents + + latent_model_input = self.scheduler.scale_model_input(latent_model_input, t) + + added_cond_kwargs = {"text_embeds": add_text_embeds, "time_ids": add_time_ids} + + # ref only part + noise = randn_tensor( + ref_image_latents.shape, generator=generator, device=device, dtype=ref_image_latents.dtype + ) + ref_xt = self.scheduler.add_noise( + ref_image_latents, + noise, + t.reshape( + 1, + ), + ) + ref_xt = self.scheduler.scale_model_input(ref_xt, t) + + MODE = "write" + + self.unet( + ref_xt, + t, + encoder_hidden_states=prompt_embeds, + cross_attention_kwargs=cross_attention_kwargs, + added_cond_kwargs=added_cond_kwargs, + return_dict=False, + ) + + # predict the noise residual + MODE = "read" + noise_pred = self.unet( + latent_model_input, + t, + encoder_hidden_states=prompt_embeds, + cross_attention_kwargs=cross_attention_kwargs, + added_cond_kwargs=added_cond_kwargs, + return_dict=False, + )[0] + + # perform guidance + if do_classifier_free_guidance: + noise_pred_uncond, noise_pred_text = noise_pred.chunk(2) + noise_pred = noise_pred_uncond + guidance_scale * (noise_pred_text - noise_pred_uncond) + + if do_classifier_free_guidance and guidance_rescale > 0.0: + # Based on 3.4. in https://arxiv.org/pdf/2305.08891.pdf + noise_pred = rescale_noise_cfg(noise_pred, noise_pred_text, guidance_rescale=guidance_rescale) + + # compute the previous noisy sample x_t -> x_t-1 + latents = self.scheduler.step(noise_pred, t, latents, **extra_step_kwargs, return_dict=False)[0] + + # call the callback, if provided + if i == len(timesteps) - 1 or ((i + 1) > num_warmup_steps and (i + 1) % self.scheduler.order == 0): + progress_bar.update() + if callback is not None and i % callback_steps == 0: + step_idx = i // getattr(self.scheduler, "order", 1) + callback(step_idx, t, latents) + + if output_type != "latent": + # make sure the VAE is in float32 mode, as it overflows in float16 + needs_upcasting = self.vae.dtype == torch.float16 and self.vae.config.force_upcast + + if needs_upcasting: + self.upcast_vae() + latents = latents.to(next(iter(self.vae.post_quant_conv.parameters())).dtype) + + image = self.vae.decode(latents / self.vae.config.scaling_factor, return_dict=False)[0] + + # cast back to fp16 if needed + if needs_upcasting: + self.vae.to(dtype=torch.float16) + else: + image = latents + return StableDiffusionXLPipelineOutput(images=image) + + # apply watermark if available + if self.watermark is not None: + image = self.watermark.apply_watermark(image) + + image = self.image_processor.postprocess(image, output_type=output_type) + + # Offload last model to CPU + if hasattr(self, "final_offload_hook") and self.final_offload_hook is not None: + self.final_offload_hook.offload() + + if not return_dict: + return (image,) + + return StableDiffusionXLPipelineOutput(images=image) diff --git a/modules/control/proc/segment_anything/__init__.py b/modules/control/proc/segment_anything/__init__.py new file mode 100644 index 000000000..bcd7195c7 --- /dev/null +++ b/modules/control/proc/segment_anything/__init__.py @@ -0,0 +1,91 @@ +# Copyright (c) Meta Platforms, Inc. and affiliates. +# All rights reserved. + +# This source code is licensed under the license found in the +# LICENSE file in the root directory of this source tree. + +import os +import warnings +from typing import Union + +import cv2 +import numpy as np +import torch +from huggingface_hub import hf_hub_download +from PIL import Image + +from modules.control.util import HWC3, resize_image +from .automatic_mask_generator import SamAutomaticMaskGenerator +from .build_sam import sam_model_registry + + +class SamDetector: + def __init__(self, mask_generator: SamAutomaticMaskGenerator = None): + self.mask_generator = mask_generator + + @classmethod + def from_pretrained(cls, model_path, filename, model_type, cache_dir=None): + """ + Possible model_type : vit_h, vit_l, vit_b, vit_t + download weights from https://github.com/facebookresearch/segment-anything + """ + model_path = hf_hub_download(model_path, filename, cache_dir=cache_dir) + + sam = sam_model_registry[model_type](checkpoint=model_path) + + if torch.cuda.is_available(): + sam.to("cuda") + + mask_generator = SamAutomaticMaskGenerator(sam) + + return cls(mask_generator) + + + def show_anns(self, anns): + from numpy.random import default_rng + gen = default_rng() + if len(anns) == 0: + return + sorted_anns = sorted(anns, key=(lambda x: x['area']), reverse=True) + h, w = anns[0]['segmentation'].shape + final_img = Image.fromarray(np.zeros((h, w, 3), dtype=np.uint8), mode="RGB") + for ann in sorted_anns: + m = ann['segmentation'] + img = np.empty((m.shape[0], m.shape[1], 3), dtype=np.uint8) + for i in range(3): + img[:,:,i] = gen.integers(255, dtype=np.uint8) + final_img.paste(Image.fromarray(img, mode="RGB"), (0, 0), Image.fromarray(np.uint8(m*255))) + + return np.array(final_img, dtype=np.uint8) + + def __call__(self, input_image: Union[np.ndarray, Image.Image]=None, detect_resolution=512, image_resolution=512, output_type="pil", **kwargs) -> Image.Image: + if "image" in kwargs: + warnings.warn("image is deprecated, please use `input_image=...` instead.", DeprecationWarning) + input_image = kwargs.pop("image") + + if input_image is None: + raise ValueError("input_image must be defined.") + + if not isinstance(input_image, np.ndarray): + input_image = np.array(input_image, dtype=np.uint8) + + input_image = HWC3(input_image) + input_image = resize_image(input_image, detect_resolution) + + # Generate Masks + masks = self.mask_generator.generate(input_image) + # Create map + image_map = self.show_anns(masks) + + detected_map = image_map + detected_map = HWC3(detected_map) + + img = resize_image(input_image, image_resolution) + H, W, _C = img.shape + + detected_map = cv2.resize(detected_map, (W, H), interpolation=cv2.INTER_LINEAR) + + if output_type == "pil": + detected_map = Image.fromarray(detected_map) + + return detected_map diff --git a/modules/control/proc/segment_anything/automatic_mask_generator.py b/modules/control/proc/segment_anything/automatic_mask_generator.py new file mode 100644 index 000000000..a5029053e --- /dev/null +++ b/modules/control/proc/segment_anything/automatic_mask_generator.py @@ -0,0 +1,371 @@ +# Copyright (c) Meta Platforms, Inc. and affiliates. +# All rights reserved. + +# This source code is licensed under the license found in the +# LICENSE file in the root directory of this source tree. + +import numpy as np +import torch +from torchvision.ops.boxes import batched_nms, box_area # type: ignore + +from typing import Any, Dict, List, Optional, Tuple + +from .modeling import Sam +from .predictor import SamPredictor +from .utils.amg import ( + MaskData, + area_from_rle, + batch_iterator, + batched_mask_to_box, + box_xyxy_to_xywh, + build_all_layer_point_grids, + calculate_stability_score, + coco_encode_rle, + generate_crop_boxes, + is_box_near_crop_edge, + mask_to_rle_pytorch, + remove_small_regions, + rle_to_mask, + uncrop_boxes_xyxy, + uncrop_masks, + uncrop_points, +) + + +class SamAutomaticMaskGenerator: + def __init__( + self, + model: Sam, + points_per_side: Optional[int] = 32, + points_per_batch: int = 64, + pred_iou_thresh: float = 0.88, + stability_score_thresh: float = 0.95, + stability_score_offset: float = 1.0, + box_nms_thresh: float = 0.7, + crop_n_layers: int = 0, + crop_nms_thresh: float = 0.7, + crop_overlap_ratio: float = 512 / 1500, + crop_n_points_downscale_factor: int = 1, + point_grids: Optional[List[np.ndarray]] = None, + min_mask_region_area: int = 0, + output_mode: str = "binary_mask", + ) -> None: + """ + Using a SAM model, generates masks for the entire image. + Generates a grid of point prompts over the image, then filters + low quality and duplicate masks. The default settings are chosen + for SAM with a ViT-H backbone. + + Arguments: + model (Sam): The SAM model to use for mask prediction. + points_per_side (int or None): The number of points to be sampled + along one side of the image. The total number of points is + points_per_side**2. If None, 'point_grids' must provide explicit + point sampling. + points_per_batch (int): Sets the number of points run simultaneously + by the model. Higher numbers may be faster but use more GPU memory. + pred_iou_thresh (float): A filtering threshold in [0,1], using the + model's predicted mask quality. + stability_score_thresh (float): A filtering threshold in [0,1], using + the stability of the mask under changes to the cutoff used to binarize + the model's mask predictions. + stability_score_offset (float): The amount to shift the cutoff when + calculated the stability score. + box_nms_thresh (float): The box IoU cutoff used by non-maximal + suppression to filter duplicate masks. + crop_n_layers (int): If >0, mask prediction will be run again on + crops of the image. Sets the number of layers to run, where each + layer has 2**i_layer number of image crops. + crop_nms_thresh (float): The box IoU cutoff used by non-maximal + suppression to filter duplicate masks between different crops. + crop_overlap_ratio (float): Sets the degree to which crops overlap. + In the first crop layer, crops will overlap by this fraction of + the image length. Later layers with more crops scale down this overlap. + crop_n_points_downscale_factor (int): The number of points-per-side + sampled in layer n is scaled down by crop_n_points_downscale_factor**n. + point_grids (list(np.ndarray) or None): A list over explicit grids + of points used for sampling, normalized to [0,1]. The nth grid in the + list is used in the nth crop layer. Exclusive with points_per_side. + min_mask_region_area (int): If >0, postprocessing will be applied + to remove disconnected regions and holes in masks with area smaller + than min_mask_region_area. Requires opencv. + output_mode (str): The form masks are returned in. Can be 'binary_mask', + 'uncompressed_rle', or 'coco_rle'. 'coco_rle' requires pycocotools. + For large resolutions, 'binary_mask' may consume large amounts of + memory. + """ + + assert (points_per_side is None) != ( + point_grids is None + ), "Exactly one of points_per_side or point_grid must be provided." + if points_per_side is not None: + self.point_grids = build_all_layer_point_grids( + points_per_side, + crop_n_layers, + crop_n_points_downscale_factor, + ) + elif point_grids is not None: + self.point_grids = point_grids + else: + raise ValueError("Can't have both points_per_side and point_grid be None.") + + assert output_mode in [ + "binary_mask", + "uncompressed_rle", + "coco_rle", + ], f"Unknown output_mode {output_mode}." + if output_mode == "coco_rle": + from pycocotools import mask as mask_utils # type: ignore + + if min_mask_region_area > 0: + import cv2 # type: ignore + + self.predictor = SamPredictor(model) + self.points_per_batch = points_per_batch + self.pred_iou_thresh = pred_iou_thresh + self.stability_score_thresh = stability_score_thresh + self.stability_score_offset = stability_score_offset + self.box_nms_thresh = box_nms_thresh + self.crop_n_layers = crop_n_layers + self.crop_nms_thresh = crop_nms_thresh + self.crop_overlap_ratio = crop_overlap_ratio + self.crop_n_points_downscale_factor = crop_n_points_downscale_factor + self.min_mask_region_area = min_mask_region_area + self.output_mode = output_mode + + def generate(self, image: np.ndarray) -> List[Dict[str, Any]]: + """ + Generates masks for the given image. + + Arguments: + image (np.ndarray): The image to generate masks for, in HWC uint8 format. + + Returns: + list(dict(str, any)): A list over records for masks. Each record is + a dict containing the following keys: + segmentation (dict(str, any) or np.ndarray): The mask. If + output_mode='binary_mask', is an array of shape HW. Otherwise, + is a dictionary containing the RLE. + bbox (list(float)): The box around the mask, in XYWH format. + area (int): The area in pixels of the mask. + predicted_iou (float): The model's own prediction of the mask's + quality. This is filtered by the pred_iou_thresh parameter. + point_coords (list(list(float))): The point coordinates input + to the model to generate this mask. + stability_score (float): A measure of the mask's quality. This + is filtered on using the stability_score_thresh parameter. + crop_box (list(float)): The crop of the image used to generate + the mask, given in XYWH format. + """ + + # Generate masks + mask_data = self._generate_masks(image) + + # Filter small disconnected regions and holes in masks + if self.min_mask_region_area > 0: + mask_data = self.postprocess_small_regions( + mask_data, + self.min_mask_region_area, + max(self.box_nms_thresh, self.crop_nms_thresh), + ) + + # Encode masks + if self.output_mode == "coco_rle": + mask_data["segmentations"] = [coco_encode_rle(rle) for rle in mask_data["rles"]] + elif self.output_mode == "binary_mask": + mask_data["segmentations"] = [rle_to_mask(rle) for rle in mask_data["rles"]] + else: + mask_data["segmentations"] = mask_data["rles"] + + # Write mask records + curr_anns = [] + for idx in range(len(mask_data["segmentations"])): + ann = { + "segmentation": mask_data["segmentations"][idx], + "area": area_from_rle(mask_data["rles"][idx]), + "bbox": box_xyxy_to_xywh(mask_data["boxes"][idx]).tolist(), + "predicted_iou": mask_data["iou_preds"][idx].item(), + "point_coords": [mask_data["points"][idx].tolist()], + "stability_score": mask_data["stability_score"][idx].item(), + "crop_box": box_xyxy_to_xywh(mask_data["crop_boxes"][idx]).tolist(), + } + curr_anns.append(ann) + + return curr_anns + + def _generate_masks(self, image: np.ndarray) -> MaskData: + orig_size = image.shape[:2] + crop_boxes, layer_idxs = generate_crop_boxes( + orig_size, self.crop_n_layers, self.crop_overlap_ratio + ) + + # Iterate over image crops + data = MaskData() + for crop_box, layer_idx in zip(crop_boxes, layer_idxs): + crop_data = self._process_crop(image, crop_box, layer_idx, orig_size) + data.cat(crop_data) + + # Remove duplicate masks between crops + if len(crop_boxes) > 1: + # Prefer masks from smaller crops + scores = 1 / box_area(data["crop_boxes"]) + scores = scores.to(data["boxes"].device) + keep_by_nms = batched_nms( + data["boxes"].float(), + scores, + torch.zeros_like(data["boxes"][:, 0]), # categories + iou_threshold=self.crop_nms_thresh, + ) + data.filter(keep_by_nms) + + data.to_numpy() + return data + + def _process_crop( + self, + image: np.ndarray, + crop_box: List[int], + crop_layer_idx: int, + orig_size: Tuple[int, ...], + ) -> MaskData: + # Crop the image and calculate embeddings + x0, y0, x1, y1 = crop_box + cropped_im = image[y0:y1, x0:x1, :] + cropped_im_size = cropped_im.shape[:2] + self.predictor.set_image(cropped_im) + + # Get points for this crop + points_scale = np.array(cropped_im_size)[None, ::-1] + points_for_image = self.point_grids[crop_layer_idx] * points_scale + + # Generate masks for this crop in batches + data = MaskData() + for (points,) in batch_iterator(self.points_per_batch, points_for_image): + batch_data = self._process_batch(points, cropped_im_size, crop_box, orig_size) + data.cat(batch_data) + del batch_data + self.predictor.reset_image() + + # Remove duplicates within this crop. + keep_by_nms = batched_nms( + data["boxes"].float(), + data["iou_preds"], + torch.zeros_like(data["boxes"][:, 0]), # categories + iou_threshold=self.box_nms_thresh, + ) + data.filter(keep_by_nms) + + # Return to the original image frame + data["boxes"] = uncrop_boxes_xyxy(data["boxes"], crop_box) + data["points"] = uncrop_points(data["points"], crop_box) + data["crop_boxes"] = torch.tensor([crop_box for _ in range(len(data["rles"]))]) + + return data + + def _process_batch( + self, + points: np.ndarray, + im_size: Tuple[int, ...], + crop_box: List[int], + orig_size: Tuple[int, ...], + ) -> MaskData: + orig_h, orig_w = orig_size + + # Run model on this batch + transformed_points = self.predictor.transform.apply_coords(points, im_size) + in_points = torch.as_tensor(transformed_points, device=self.predictor.device) + in_labels = torch.ones(in_points.shape[0], dtype=torch.int, device=in_points.device) + masks, iou_preds, _ = self.predictor.predict_torch( + in_points[:, None, :], + in_labels[:, None], + multimask_output=True, + return_logits=True, + ) + + # Serialize predictions and store in MaskData + data = MaskData( + masks=masks.flatten(0, 1), + iou_preds=iou_preds.flatten(0, 1), + points=torch.as_tensor(points.repeat(masks.shape[1], axis=0)), + ) + del masks + + # Filter by predicted IoU + if self.pred_iou_thresh > 0.0: + keep_mask = data["iou_preds"] > self.pred_iou_thresh + data.filter(keep_mask) + + # Calculate stability score + data["stability_score"] = calculate_stability_score( + data["masks"], self.predictor.model.mask_threshold, self.stability_score_offset + ) + if self.stability_score_thresh > 0.0: + keep_mask = data["stability_score"] >= self.stability_score_thresh + data.filter(keep_mask) + + # Threshold masks and calculate boxes + data["masks"] = data["masks"] > self.predictor.model.mask_threshold + data["boxes"] = batched_mask_to_box(data["masks"]) + + # Filter boxes that touch crop boundaries + keep_mask = ~is_box_near_crop_edge(data["boxes"], crop_box, [0, 0, orig_w, orig_h]) + if not torch.all(keep_mask): + data.filter(keep_mask) + + # Compress to RLE + data["masks"] = uncrop_masks(data["masks"], crop_box, orig_h, orig_w) + data["rles"] = mask_to_rle_pytorch(data["masks"]) + del data["masks"] + + return data + + @staticmethod + def postprocess_small_regions( + mask_data: MaskData, min_area: int, nms_thresh: float + ) -> MaskData: + """ + Removes small disconnected regions and holes in masks, then reruns + box NMS to remove any new duplicates. + + Edits mask_data in place. + + Requires open-cv as a dependency. + """ + if len(mask_data["rles"]) == 0: + return mask_data + + # Filter small disconnected regions and holes + new_masks = [] + scores = [] + for rle in mask_data["rles"]: + mask = rle_to_mask(rle) + + mask, changed = remove_small_regions(mask, min_area, mode="holes") + unchanged = not changed + mask, changed = remove_small_regions(mask, min_area, mode="islands") + unchanged = unchanged and not changed + + new_masks.append(torch.as_tensor(mask).unsqueeze(0)) + # Give score=0 to changed masks and score=1 to unchanged masks + # so NMS will prefer ones that didn't need postprocessing + scores.append(float(unchanged)) + + # Recalculate boxes and remove any new duplicates + masks = torch.cat(new_masks, dim=0) + boxes = batched_mask_to_box(masks) + keep_by_nms = batched_nms( + boxes.float(), + torch.as_tensor(scores), + torch.zeros_like(boxes[:, 0]), # categories + iou_threshold=nms_thresh, + ) + + # Only recalculate RLEs for masks that have changed + for i_mask in keep_by_nms: + if scores[i_mask] == 0.0: + mask_torch = masks[i_mask].unsqueeze(0) + mask_data["rles"][i_mask] = mask_to_rle_pytorch(mask_torch)[0] + mask_data["boxes"][i_mask] = boxes[i_mask] # update res directly + mask_data.filter(keep_by_nms) + + return mask_data diff --git a/modules/control/proc/segment_anything/build_sam.py b/modules/control/proc/segment_anything/build_sam.py new file mode 100644 index 000000000..9a52c506b --- /dev/null +++ b/modules/control/proc/segment_anything/build_sam.py @@ -0,0 +1,159 @@ +# Copyright (c) Meta Platforms, Inc. and affiliates. +# All rights reserved. + +# This source code is licensed under the license found in the +# LICENSE file in the root directory of this source tree. + +import torch + +from functools import partial + +from .modeling import ImageEncoderViT, MaskDecoder, PromptEncoder, Sam, TwoWayTransformer, TinyViT + + +def build_sam_vit_h(checkpoint=None): + return _build_sam( + encoder_embed_dim=1280, + encoder_depth=32, + encoder_num_heads=16, + encoder_global_attn_indexes=[7, 15, 23, 31], + checkpoint=checkpoint, + ) + + +build_sam = build_sam_vit_h + + +def build_sam_vit_l(checkpoint=None): + return _build_sam( + encoder_embed_dim=1024, + encoder_depth=24, + encoder_num_heads=16, + encoder_global_attn_indexes=[5, 11, 17, 23], + checkpoint=checkpoint, + ) + + +def build_sam_vit_b(checkpoint=None): + return _build_sam( + encoder_embed_dim=768, + encoder_depth=12, + encoder_num_heads=12, + encoder_global_attn_indexes=[2, 5, 8, 11], + checkpoint=checkpoint, + ) + + +def build_sam_vit_t(checkpoint=None): + prompt_embed_dim = 256 + image_size = 1024 + vit_patch_size = 16 + image_embedding_size = image_size // vit_patch_size + mobile_sam = Sam( + image_encoder=TinyViT(img_size=1024, in_chans=3, num_classes=1000, + embed_dims=[64, 128, 160, 320], + depths=[2, 2, 6, 2], + num_heads=[2, 4, 5, 10], + window_sizes=[7, 7, 14, 7], + mlp_ratio=4., + drop_rate=0., + drop_path_rate=0.0, + use_checkpoint=False, + mbconv_expand_ratio=4.0, + local_conv_size=3, + layer_lr_decay=0.8 + ), + prompt_encoder=PromptEncoder( + embed_dim=prompt_embed_dim, + image_embedding_size=(image_embedding_size, image_embedding_size), + input_image_size=(image_size, image_size), + mask_in_chans=16, + ), + mask_decoder=MaskDecoder( + num_multimask_outputs=3, + transformer=TwoWayTransformer( + depth=2, + embedding_dim=prompt_embed_dim, + mlp_dim=2048, + num_heads=8, + ), + transformer_dim=prompt_embed_dim, + iou_head_depth=3, + iou_head_hidden_dim=256, + ), + pixel_mean=[123.675, 116.28, 103.53], + pixel_std=[58.395, 57.12, 57.375], + ) + + mobile_sam.eval() + if checkpoint is not None: + with open(checkpoint, "rb") as f: + state_dict = torch.load(f) + mobile_sam.load_state_dict(state_dict) + return mobile_sam + + +sam_model_registry = { + "default": build_sam_vit_h, + "vit_h": build_sam_vit_h, + "vit_l": build_sam_vit_l, + "vit_b": build_sam_vit_b, + "vit_t": build_sam_vit_t, +} + + +def _build_sam( + encoder_embed_dim, + encoder_depth, + encoder_num_heads, + encoder_global_attn_indexes, + checkpoint=None, +): + prompt_embed_dim = 256 + image_size = 1024 + vit_patch_size = 16 + image_embedding_size = image_size // vit_patch_size + sam = Sam( + image_encoder=ImageEncoderViT( + depth=encoder_depth, + embed_dim=encoder_embed_dim, + img_size=image_size, + mlp_ratio=4, + norm_layer=partial(torch.nn.LayerNorm, eps=1e-6), + num_heads=encoder_num_heads, + patch_size=vit_patch_size, + qkv_bias=True, + use_rel_pos=True, + global_attn_indexes=encoder_global_attn_indexes, + window_size=14, + out_chans=prompt_embed_dim, + ), + prompt_encoder=PromptEncoder( + embed_dim=prompt_embed_dim, + image_embedding_size=(image_embedding_size, image_embedding_size), + input_image_size=(image_size, image_size), + mask_in_chans=16, + ), + mask_decoder=MaskDecoder( + num_multimask_outputs=3, + transformer=TwoWayTransformer( + depth=2, + embedding_dim=prompt_embed_dim, + mlp_dim=2048, + num_heads=8, + ), + transformer_dim=prompt_embed_dim, + iou_head_depth=3, + iou_head_hidden_dim=256, + ), + pixel_mean=[123.675, 116.28, 103.53], + pixel_std=[58.395, 57.12, 57.375], + ) + sam.eval() + if checkpoint is not None: + with open(checkpoint, "rb") as f: + state_dict = torch.load(f) + sam.load_state_dict(state_dict) + return sam + + diff --git a/modules/control/proc/segment_anything/modeling/__init__.py b/modules/control/proc/segment_anything/modeling/__init__.py new file mode 100644 index 000000000..7aa261b83 --- /dev/null +++ b/modules/control/proc/segment_anything/modeling/__init__.py @@ -0,0 +1,12 @@ +# Copyright (c) Meta Platforms, Inc. and affiliates. +# All rights reserved. + +# This source code is licensed under the license found in the +# LICENSE file in the root directory of this source tree. + +from .sam import Sam +from .image_encoder import ImageEncoderViT +from .mask_decoder import MaskDecoder +from .prompt_encoder import PromptEncoder +from .transformer import TwoWayTransformer +from .tiny_vit_sam import TinyViT diff --git a/modules/control/proc/segment_anything/modeling/common.py b/modules/control/proc/segment_anything/modeling/common.py new file mode 100644 index 000000000..dc410acda --- /dev/null +++ b/modules/control/proc/segment_anything/modeling/common.py @@ -0,0 +1,43 @@ +# Copyright (c) Meta Platforms, Inc. and affiliates. +# All rights reserved. + +# This source code is licensed under the license found in the +# LICENSE file in the root directory of this source tree. + +import torch +import torch.nn as nn + +from typing import Type + + +class MLPBlock(nn.Module): + def __init__( + self, + embedding_dim: int, + mlp_dim: int, + act: Type[nn.Module] = nn.GELU, + ) -> None: + super().__init__() + self.lin1 = nn.Linear(embedding_dim, mlp_dim) + self.lin2 = nn.Linear(mlp_dim, embedding_dim) + self.act = act() + + def forward(self, x: torch.Tensor) -> torch.Tensor: + return self.lin2(self.act(self.lin1(x))) + + +# From https://github.com/facebookresearch/detectron2/blob/main/detectron2/layers/batch_norm.py +# Itself from https://github.com/facebookresearch/ConvNeXt/blob/d1fa8f6fef0a165b27399986cc2bdacc92777e40/models/convnext.py#L119 +class LayerNorm2d(nn.Module): + def __init__(self, num_channels: int, eps: float = 1e-6) -> None: + super().__init__() + self.weight = nn.Parameter(torch.ones(num_channels)) + self.bias = nn.Parameter(torch.zeros(num_channels)) + self.eps = eps + + def forward(self, x: torch.Tensor) -> torch.Tensor: + u = x.mean(1, keepdim=True) + s = (x - u).pow(2).mean(1, keepdim=True) + x = (x - u) / torch.sqrt(s + self.eps) + x = self.weight[:, None, None] * x + self.bias[:, None, None] + return x diff --git a/modules/control/proc/segment_anything/modeling/image_encoder.py b/modules/control/proc/segment_anything/modeling/image_encoder.py new file mode 100644 index 000000000..689972ea8 --- /dev/null +++ b/modules/control/proc/segment_anything/modeling/image_encoder.py @@ -0,0 +1,395 @@ +# Copyright (c) Meta Platforms, Inc. and affiliates. +# All rights reserved. + +# This source code is licensed under the license found in the +# LICENSE file in the root directory of this source tree. + +import torch +import torch.nn as nn +import torch.nn.functional as F + +from typing import Optional, Tuple, Type + +from .common import LayerNorm2d, MLPBlock + + +# This class and its supporting functions below lightly adapted from the ViTDet backbone available at: https://github.com/facebookresearch/detectron2/blob/main/detectron2/modeling/backbone/vit.py +class ImageEncoderViT(nn.Module): + def __init__( + self, + img_size: int = 1024, + patch_size: int = 16, + in_chans: int = 3, + embed_dim: int = 768, + depth: int = 12, + num_heads: int = 12, + mlp_ratio: float = 4.0, + out_chans: int = 256, + qkv_bias: bool = True, + norm_layer: Type[nn.Module] = nn.LayerNorm, + act_layer: Type[nn.Module] = nn.GELU, + use_abs_pos: bool = True, + use_rel_pos: bool = False, + rel_pos_zero_init: bool = True, + window_size: int = 0, + global_attn_indexes: Tuple[int, ...] = (), + ) -> None: + """ + Args: + img_size (int): Input image size. + patch_size (int): Patch size. + in_chans (int): Number of input image channels. + embed_dim (int): Patch embedding dimension. + depth (int): Depth of ViT. + num_heads (int): Number of attention heads in each ViT block. + mlp_ratio (float): Ratio of mlp hidden dim to embedding dim. + qkv_bias (bool): If True, add a learnable bias to query, key, value. + norm_layer (nn.Module): Normalization layer. + act_layer (nn.Module): Activation layer. + use_abs_pos (bool): If True, use absolute positional embeddings. + use_rel_pos (bool): If True, add relative positional embeddings to the attention map. + rel_pos_zero_init (bool): If True, zero initialize relative positional parameters. + window_size (int): Window size for window attention blocks. + global_attn_indexes (list): Indexes for blocks using global attention. + """ + super().__init__() + self.img_size = img_size + + self.patch_embed = PatchEmbed( + kernel_size=(patch_size, patch_size), + stride=(patch_size, patch_size), + in_chans=in_chans, + embed_dim=embed_dim, + ) + + self.pos_embed: Optional[nn.Parameter] = None + if use_abs_pos: + # Initialize absolute positional embedding with pretrain image size. + self.pos_embed = nn.Parameter( + torch.zeros(1, img_size // patch_size, img_size // patch_size, embed_dim) + ) + + self.blocks = nn.ModuleList() + for i in range(depth): + block = Block( + dim=embed_dim, + num_heads=num_heads, + mlp_ratio=mlp_ratio, + qkv_bias=qkv_bias, + norm_layer=norm_layer, + act_layer=act_layer, + use_rel_pos=use_rel_pos, + rel_pos_zero_init=rel_pos_zero_init, + window_size=window_size if i not in global_attn_indexes else 0, + input_size=(img_size // patch_size, img_size // patch_size), + ) + self.blocks.append(block) + + self.neck = nn.Sequential( + nn.Conv2d( + embed_dim, + out_chans, + kernel_size=1, + bias=False, + ), + LayerNorm2d(out_chans), + nn.Conv2d( + out_chans, + out_chans, + kernel_size=3, + padding=1, + bias=False, + ), + LayerNorm2d(out_chans), + ) + + def forward(self, x: torch.Tensor) -> torch.Tensor: + x = self.patch_embed(x) + if self.pos_embed is not None: + x = x + self.pos_embed + + for blk in self.blocks: + x = blk(x) + + x = self.neck(x.permute(0, 3, 1, 2)) + + return x + + +class Block(nn.Module): + """Transformer blocks with support of window attention and residual propagation blocks""" + + def __init__( + self, + dim: int, + num_heads: int, + mlp_ratio: float = 4.0, + qkv_bias: bool = True, + norm_layer: Type[nn.Module] = nn.LayerNorm, + act_layer: Type[nn.Module] = nn.GELU, + use_rel_pos: bool = False, + rel_pos_zero_init: bool = True, + window_size: int = 0, + input_size: Optional[Tuple[int, int]] = None, + ) -> None: + """ + Args: + dim (int): Number of input channels. + num_heads (int): Number of attention heads in each ViT block. + mlp_ratio (float): Ratio of mlp hidden dim to embedding dim. + qkv_bias (bool): If True, add a learnable bias to query, key, value. + norm_layer (nn.Module): Normalization layer. + act_layer (nn.Module): Activation layer. + use_rel_pos (bool): If True, add relative positional embeddings to the attention map. + rel_pos_zero_init (bool): If True, zero initialize relative positional parameters. + window_size (int): Window size for window attention blocks. If it equals 0, then + use global attention. + input_size (tuple(int, int) or None): Input resolution for calculating the relative + positional parameter size. + """ + super().__init__() + self.norm1 = norm_layer(dim) + self.attn = Attention( + dim, + num_heads=num_heads, + qkv_bias=qkv_bias, + use_rel_pos=use_rel_pos, + rel_pos_zero_init=rel_pos_zero_init, + input_size=input_size if window_size == 0 else (window_size, window_size), + ) + + self.norm2 = norm_layer(dim) + self.mlp = MLPBlock(embedding_dim=dim, mlp_dim=int(dim * mlp_ratio), act=act_layer) + + self.window_size = window_size + + def forward(self, x: torch.Tensor) -> torch.Tensor: + shortcut = x + x = self.norm1(x) + # Window partition + if self.window_size > 0: + H, W = x.shape[1], x.shape[2] + x, pad_hw = window_partition(x, self.window_size) + + x = self.attn(x) + # Reverse window partition + if self.window_size > 0: + x = window_unpartition(x, self.window_size, pad_hw, (H, W)) + + x = shortcut + x + x = x + self.mlp(self.norm2(x)) + + return x + + +class Attention(nn.Module): + """Multi-head Attention block with relative position embeddings.""" + + def __init__( + self, + dim: int, + num_heads: int = 8, + qkv_bias: bool = True, + use_rel_pos: bool = False, + rel_pos_zero_init: bool = True, + input_size: Optional[Tuple[int, int]] = None, + ) -> None: + """ + Args: + dim (int): Number of input channels. + num_heads (int): Number of attention heads. + qkv_bias (bool): If True, add a learnable bias to query, key, value. + rel_pos (bool): If True, add relative positional embeddings to the attention map. + rel_pos_zero_init (bool): If True, zero initialize relative positional parameters. + input_size (tuple(int, int) or None): Input resolution for calculating the relative + positional parameter size. + """ + super().__init__() + self.num_heads = num_heads + head_dim = dim // num_heads + self.scale = head_dim**-0.5 + + self.qkv = nn.Linear(dim, dim * 3, bias=qkv_bias) + self.proj = nn.Linear(dim, dim) + + self.use_rel_pos = use_rel_pos + if self.use_rel_pos: + assert ( + input_size is not None + ), "Input size must be provided if using relative positional encoding." + # initialize relative positional embeddings + self.rel_pos_h = nn.Parameter(torch.zeros(2 * input_size[0] - 1, head_dim)) + self.rel_pos_w = nn.Parameter(torch.zeros(2 * input_size[1] - 1, head_dim)) + + def forward(self, x: torch.Tensor) -> torch.Tensor: + B, H, W, _ = x.shape + # qkv with shape (3, B, nHead, H * W, C) + qkv = self.qkv(x).reshape(B, H * W, 3, self.num_heads, -1).permute(2, 0, 3, 1, 4) + # q, k, v with shape (B * nHead, H * W, C) + q, k, v = qkv.reshape(3, B * self.num_heads, H * W, -1).unbind(0) + + attn = (q * self.scale) @ k.transpose(-2, -1) + + if self.use_rel_pos: + attn = add_decomposed_rel_pos(attn, q, self.rel_pos_h, self.rel_pos_w, (H, W), (H, W)) + + attn = attn.softmax(dim=-1) + x = (attn @ v).view(B, self.num_heads, H, W, -1).permute(0, 2, 3, 1, 4).reshape(B, H, W, -1) + x = self.proj(x) + + return x + + +def window_partition(x: torch.Tensor, window_size: int) -> Tuple[torch.Tensor, Tuple[int, int]]: + """ + Partition into non-overlapping windows with padding if needed. + Args: + x (tensor): input tokens with [B, H, W, C]. + window_size (int): window size. + + Returns: + windows: windows after partition with [B * num_windows, window_size, window_size, C]. + (Hp, Wp): padded height and width before partition + """ + B, H, W, C = x.shape + + pad_h = (window_size - H % window_size) % window_size + pad_w = (window_size - W % window_size) % window_size + if pad_h > 0 or pad_w > 0: + x = F.pad(x, (0, 0, 0, pad_w, 0, pad_h)) + Hp, Wp = H + pad_h, W + pad_w + + x = x.view(B, Hp // window_size, window_size, Wp // window_size, window_size, C) + windows = x.permute(0, 1, 3, 2, 4, 5).contiguous().view(-1, window_size, window_size, C) + return windows, (Hp, Wp) + + +def window_unpartition( + windows: torch.Tensor, window_size: int, pad_hw: Tuple[int, int], hw: Tuple[int, int] +) -> torch.Tensor: + """ + Window unpartition into original sequences and removing padding. + Args: + windows (tensor): input tokens with [B * num_windows, window_size, window_size, C]. + window_size (int): window size. + pad_hw (Tuple): padded height and width (Hp, Wp). + hw (Tuple): original height and width (H, W) before padding. + + Returns: + x: unpartitioned sequences with [B, H, W, C]. + """ + Hp, Wp = pad_hw + H, W = hw + B = windows.shape[0] // (Hp * Wp // window_size // window_size) + x = windows.view(B, Hp // window_size, Wp // window_size, window_size, window_size, -1) + x = x.permute(0, 1, 3, 2, 4, 5).contiguous().view(B, Hp, Wp, -1) + + if Hp > H or Wp > W: + x = x[:, :H, :W, :].contiguous() + return x + + +def get_rel_pos(q_size: int, k_size: int, rel_pos: torch.Tensor) -> torch.Tensor: + """ + Get relative positional embeddings according to the relative positions of + query and key sizes. + Args: + q_size (int): size of query q. + k_size (int): size of key k. + rel_pos (Tensor): relative position embeddings (L, C). + + Returns: + Extracted positional embeddings according to relative positions. + """ + max_rel_dist = int(2 * max(q_size, k_size) - 1) + # Interpolate rel pos if needed. + if rel_pos.shape[0] != max_rel_dist: + # Interpolate rel pos. + rel_pos_resized = F.interpolate( + rel_pos.reshape(1, rel_pos.shape[0], -1).permute(0, 2, 1), + size=max_rel_dist, + mode="linear", + ) + rel_pos_resized = rel_pos_resized.reshape(-1, max_rel_dist).permute(1, 0) + else: + rel_pos_resized = rel_pos + + # Scale the coords with short length if shapes for q and k are different. + q_coords = torch.arange(q_size)[:, None] * max(k_size / q_size, 1.0) + k_coords = torch.arange(k_size)[None, :] * max(q_size / k_size, 1.0) + relative_coords = (q_coords - k_coords) + (k_size - 1) * max(q_size / k_size, 1.0) + + return rel_pos_resized[relative_coords.long()] + + +def add_decomposed_rel_pos( + attn: torch.Tensor, + q: torch.Tensor, + rel_pos_h: torch.Tensor, + rel_pos_w: torch.Tensor, + q_size: Tuple[int, int], + k_size: Tuple[int, int], +) -> torch.Tensor: + """ + Calculate decomposed Relative Positional Embeddings from :paper:`mvitv2`. + https://github.com/facebookresearch/mvit/blob/19786631e330df9f3622e5402b4a419a263a2c80/mvit/models/attention.py # noqa B950 + Args: + attn (Tensor): attention map. + q (Tensor): query q in the attention layer with shape (B, q_h * q_w, C). + rel_pos_h (Tensor): relative position embeddings (Lh, C) for height axis. + rel_pos_w (Tensor): relative position embeddings (Lw, C) for width axis. + q_size (Tuple): spatial sequence size of query q with (q_h, q_w). + k_size (Tuple): spatial sequence size of key k with (k_h, k_w). + + Returns: + attn (Tensor): attention map with added relative positional embeddings. + """ + q_h, q_w = q_size + k_h, k_w = k_size + Rh = get_rel_pos(q_h, k_h, rel_pos_h) + Rw = get_rel_pos(q_w, k_w, rel_pos_w) + + B, _, dim = q.shape + r_q = q.reshape(B, q_h, q_w, dim) + rel_h = torch.einsum("bhwc,hkc->bhwk", r_q, Rh) + rel_w = torch.einsum("bhwc,wkc->bhwk", r_q, Rw) + + attn = ( + attn.view(B, q_h, q_w, k_h, k_w) + rel_h[:, :, :, :, None] + rel_w[:, :, :, None, :] + ).view(B, q_h * q_w, k_h * k_w) + + return attn + + +class PatchEmbed(nn.Module): + """ + Image to Patch Embedding. + """ + + def __init__( + self, + kernel_size: Tuple[int, int] = (16, 16), + stride: Tuple[int, int] = (16, 16), + padding: Tuple[int, int] = (0, 0), + in_chans: int = 3, + embed_dim: int = 768, + ) -> None: + """ + Args: + kernel_size (Tuple): kernel size of the projection layer. + stride (Tuple): stride of the projection layer. + padding (Tuple): padding size of the projection layer. + in_chans (int): Number of input image channels. + embed_dim (int): Patch embedding dimension. + """ + super().__init__() + + self.proj = nn.Conv2d( + in_chans, embed_dim, kernel_size=kernel_size, stride=stride, padding=padding + ) + + def forward(self, x: torch.Tensor) -> torch.Tensor: + x = self.proj(x) + # B C H W -> B H W C + x = x.permute(0, 2, 3, 1) + return x diff --git a/modules/control/proc/segment_anything/modeling/mask_decoder.py b/modules/control/proc/segment_anything/modeling/mask_decoder.py new file mode 100644 index 000000000..27e577120 --- /dev/null +++ b/modules/control/proc/segment_anything/modeling/mask_decoder.py @@ -0,0 +1,176 @@ +# Copyright (c) Meta Platforms, Inc. and affiliates. +# All rights reserved. + +# This source code is licensed under the license found in the +# LICENSE file in the root directory of this source tree. + +import torch +from torch import nn +from torch.nn import functional as F + +from typing import List, Tuple, Type + +from .common import LayerNorm2d + + +class MaskDecoder(nn.Module): + def __init__( + self, + *, + transformer_dim: int, + transformer: nn.Module, + num_multimask_outputs: int = 3, + activation: Type[nn.Module] = nn.GELU, + iou_head_depth: int = 3, + iou_head_hidden_dim: int = 256, + ) -> None: + """ + Predicts masks given an image and prompt embeddings, using a + transformer architecture. + + Arguments: + transformer_dim (int): the channel dimension of the transformer + transformer (nn.Module): the transformer used to predict masks + num_multimask_outputs (int): the number of masks to predict + when disambiguating masks + activation (nn.Module): the type of activation to use when + upscaling masks + iou_head_depth (int): the depth of the MLP used to predict + mask quality + iou_head_hidden_dim (int): the hidden dimension of the MLP + used to predict mask quality + """ + super().__init__() + self.transformer_dim = transformer_dim + self.transformer = transformer + + self.num_multimask_outputs = num_multimask_outputs + + self.iou_token = nn.Embedding(1, transformer_dim) + self.num_mask_tokens = num_multimask_outputs + 1 + self.mask_tokens = nn.Embedding(self.num_mask_tokens, transformer_dim) + + self.output_upscaling = nn.Sequential( + nn.ConvTranspose2d(transformer_dim, transformer_dim // 4, kernel_size=2, stride=2), + LayerNorm2d(transformer_dim // 4), + activation(), + nn.ConvTranspose2d(transformer_dim // 4, transformer_dim // 8, kernel_size=2, stride=2), + activation(), + ) + self.output_hypernetworks_mlps = nn.ModuleList( + [ + MLP(transformer_dim, transformer_dim, transformer_dim // 8, 3) + for i in range(self.num_mask_tokens) + ] + ) + + self.iou_prediction_head = MLP( + transformer_dim, iou_head_hidden_dim, self.num_mask_tokens, iou_head_depth + ) + + def forward( + self, + image_embeddings: torch.Tensor, + image_pe: torch.Tensor, + sparse_prompt_embeddings: torch.Tensor, + dense_prompt_embeddings: torch.Tensor, + multimask_output: bool, + ) -> Tuple[torch.Tensor, torch.Tensor]: + """ + Predict masks given image and prompt embeddings. + + Arguments: + image_embeddings (torch.Tensor): the embeddings from the image encoder + image_pe (torch.Tensor): positional encoding with the shape of image_embeddings + sparse_prompt_embeddings (torch.Tensor): the embeddings of the points and boxes + dense_prompt_embeddings (torch.Tensor): the embeddings of the mask inputs + multimask_output (bool): Whether to return multiple masks or a single + mask. + + Returns: + torch.Tensor: batched predicted masks + torch.Tensor: batched predictions of mask quality + """ + masks, iou_pred = self.predict_masks( + image_embeddings=image_embeddings, + image_pe=image_pe, + sparse_prompt_embeddings=sparse_prompt_embeddings, + dense_prompt_embeddings=dense_prompt_embeddings, + ) + + # Select the correct mask or masks for output + if multimask_output: + mask_slice = slice(1, None) + else: + mask_slice = slice(0, 1) + masks = masks[:, mask_slice, :, :] + iou_pred = iou_pred[:, mask_slice] + + # Prepare output + return masks, iou_pred + + def predict_masks( + self, + image_embeddings: torch.Tensor, + image_pe: torch.Tensor, + sparse_prompt_embeddings: torch.Tensor, + dense_prompt_embeddings: torch.Tensor, + ) -> Tuple[torch.Tensor, torch.Tensor]: + """Predicts masks. See 'forward' for more details.""" + # Concatenate output tokens + output_tokens = torch.cat([self.iou_token.weight, self.mask_tokens.weight], dim=0) + output_tokens = output_tokens.unsqueeze(0).expand(sparse_prompt_embeddings.size(0), -1, -1) + tokens = torch.cat((output_tokens, sparse_prompt_embeddings), dim=1) + + # Expand per-image data in batch direction to be per-mask + src = torch.repeat_interleave(image_embeddings, tokens.shape[0], dim=0) + src = src + dense_prompt_embeddings + pos_src = torch.repeat_interleave(image_pe, tokens.shape[0], dim=0) + b, c, h, w = src.shape + + # Run the transformer + hs, src = self.transformer(src, pos_src, tokens) + iou_token_out = hs[:, 0, :] + mask_tokens_out = hs[:, 1 : (1 + self.num_mask_tokens), :] + + # Upscale mask embeddings and predict masks using the mask tokens + src = src.transpose(1, 2).view(b, c, h, w) + upscaled_embedding = self.output_upscaling(src) + hyper_in_list: List[torch.Tensor] = [] + for i in range(self.num_mask_tokens): + hyper_in_list.append(self.output_hypernetworks_mlps[i](mask_tokens_out[:, i, :])) + hyper_in = torch.stack(hyper_in_list, dim=1) + b, c, h, w = upscaled_embedding.shape + masks = (hyper_in @ upscaled_embedding.view(b, c, h * w)).view(b, -1, h, w) + + # Generate mask quality predictions + iou_pred = self.iou_prediction_head(iou_token_out) + + return masks, iou_pred + + +# Lightly adapted from +# https://github.com/facebookresearch/MaskFormer/blob/main/mask_former/modeling/transformer/transformer_predictor.py +class MLP(nn.Module): + def __init__( + self, + input_dim: int, + hidden_dim: int, + output_dim: int, + num_layers: int, + sigmoid_output: bool = False, + ) -> None: + super().__init__() + self.num_layers = num_layers + h = [hidden_dim] * (num_layers - 1) + self.layers = nn.ModuleList( + nn.Linear(n, k) for n, k in zip([input_dim] + h, h + [output_dim]) + ) + self.sigmoid_output = sigmoid_output + + def forward(self, x): + for i, layer in enumerate(self.layers): + x = F.relu(layer(x)) if i < self.num_layers - 1 else layer(x) + if self.sigmoid_output: + x = F.sigmoid(x) + return x diff --git a/modules/control/proc/segment_anything/modeling/prompt_encoder.py b/modules/control/proc/segment_anything/modeling/prompt_encoder.py new file mode 100644 index 000000000..c3143f4f8 --- /dev/null +++ b/modules/control/proc/segment_anything/modeling/prompt_encoder.py @@ -0,0 +1,214 @@ +# Copyright (c) Meta Platforms, Inc. and affiliates. +# All rights reserved. + +# This source code is licensed under the license found in the +# LICENSE file in the root directory of this source tree. + +import numpy as np +import torch +from torch import nn + +from typing import Any, Optional, Tuple, Type + +from .common import LayerNorm2d + + +class PromptEncoder(nn.Module): + def __init__( + self, + embed_dim: int, + image_embedding_size: Tuple[int, int], + input_image_size: Tuple[int, int], + mask_in_chans: int, + activation: Type[nn.Module] = nn.GELU, + ) -> None: + """ + Encodes prompts for input to SAM's mask decoder. + + Arguments: + embed_dim (int): The prompts' embedding dimension + image_embedding_size (tuple(int, int)): The spatial size of the + image embedding, as (H, W). + input_image_size (int): The padded size of the image as input + to the image encoder, as (H, W). + mask_in_chans (int): The number of hidden channels used for + encoding input masks. + activation (nn.Module): The activation to use when encoding + input masks. + """ + super().__init__() + self.embed_dim = embed_dim + self.input_image_size = input_image_size + self.image_embedding_size = image_embedding_size + self.pe_layer = PositionEmbeddingRandom(embed_dim // 2) + + self.num_point_embeddings: int = 4 # pos/neg point + 2 box corners + point_embeddings = [nn.Embedding(1, embed_dim) for i in range(self.num_point_embeddings)] + self.point_embeddings = nn.ModuleList(point_embeddings) + self.not_a_point_embed = nn.Embedding(1, embed_dim) + + self.mask_input_size = (4 * image_embedding_size[0], 4 * image_embedding_size[1]) + self.mask_downscaling = nn.Sequential( + nn.Conv2d(1, mask_in_chans // 4, kernel_size=2, stride=2), + LayerNorm2d(mask_in_chans // 4), + activation(), + nn.Conv2d(mask_in_chans // 4, mask_in_chans, kernel_size=2, stride=2), + LayerNorm2d(mask_in_chans), + activation(), + nn.Conv2d(mask_in_chans, embed_dim, kernel_size=1), + ) + self.no_mask_embed = nn.Embedding(1, embed_dim) + + def get_dense_pe(self) -> torch.Tensor: + """ + Returns the positional encoding used to encode point prompts, + applied to a dense set of points the shape of the image encoding. + + Returns: + torch.Tensor: Positional encoding with shape + 1x(embed_dim)x(embedding_h)x(embedding_w) + """ + return self.pe_layer(self.image_embedding_size).unsqueeze(0) + + def _embed_points( + self, + points: torch.Tensor, + labels: torch.Tensor, + pad: bool, + ) -> torch.Tensor: + """Embeds point prompts.""" + points = points + 0.5 # Shift to center of pixel + if pad: + padding_point = torch.zeros((points.shape[0], 1, 2), device=points.device) + padding_label = -torch.ones((labels.shape[0], 1), device=labels.device) + points = torch.cat([points, padding_point], dim=1) + labels = torch.cat([labels, padding_label], dim=1) + point_embedding = self.pe_layer.forward_with_coords(points, self.input_image_size) + point_embedding[labels == -1] = 0.0 + point_embedding[labels == -1] += self.not_a_point_embed.weight + point_embedding[labels == 0] += self.point_embeddings[0].weight + point_embedding[labels == 1] += self.point_embeddings[1].weight + return point_embedding + + def _embed_boxes(self, boxes: torch.Tensor) -> torch.Tensor: + """Embeds box prompts.""" + boxes = boxes + 0.5 # Shift to center of pixel + coords = boxes.reshape(-1, 2, 2) + corner_embedding = self.pe_layer.forward_with_coords(coords, self.input_image_size) + corner_embedding[:, 0, :] += self.point_embeddings[2].weight + corner_embedding[:, 1, :] += self.point_embeddings[3].weight + return corner_embedding + + def _embed_masks(self, masks: torch.Tensor) -> torch.Tensor: + """Embeds mask inputs.""" + mask_embedding = self.mask_downscaling(masks) + return mask_embedding + + def _get_batch_size( + self, + points: Optional[Tuple[torch.Tensor, torch.Tensor]], + boxes: Optional[torch.Tensor], + masks: Optional[torch.Tensor], + ) -> int: + """ + Gets the batch size of the output given the batch size of the input prompts. + """ + if points is not None: + return points[0].shape[0] + elif boxes is not None: + return boxes.shape[0] + elif masks is not None: + return masks.shape[0] + else: + return 1 + + def _get_device(self) -> torch.device: + return self.point_embeddings[0].weight.device + + def forward( + self, + points: Optional[Tuple[torch.Tensor, torch.Tensor]], + boxes: Optional[torch.Tensor], + masks: Optional[torch.Tensor], + ) -> Tuple[torch.Tensor, torch.Tensor]: + """ + Embeds different types of prompts, returning both sparse and dense + embeddings. + + Arguments: + points (tuple(torch.Tensor, torch.Tensor) or none): point coordinates + and labels to embed. + boxes (torch.Tensor or none): boxes to embed + masks (torch.Tensor or none): masks to embed + + Returns: + torch.Tensor: sparse embeddings for the points and boxes, with shape + BxNx(embed_dim), where N is determined by the number of input points + and boxes. + torch.Tensor: dense embeddings for the masks, in the shape + Bx(embed_dim)x(embed_H)x(embed_W) + """ + bs = self._get_batch_size(points, boxes, masks) + sparse_embeddings = torch.empty((bs, 0, self.embed_dim), device=self._get_device()) + if points is not None: + coords, labels = points + point_embeddings = self._embed_points(coords, labels, pad=(boxes is None)) + sparse_embeddings = torch.cat([sparse_embeddings, point_embeddings], dim=1) + if boxes is not None: + box_embeddings = self._embed_boxes(boxes) + sparse_embeddings = torch.cat([sparse_embeddings, box_embeddings], dim=1) + + if masks is not None: + dense_embeddings = self._embed_masks(masks) + else: + dense_embeddings = self.no_mask_embed.weight.reshape(1, -1, 1, 1).expand( + bs, -1, self.image_embedding_size[0], self.image_embedding_size[1] + ) + + return sparse_embeddings, dense_embeddings + + +class PositionEmbeddingRandom(nn.Module): + """ + Positional encoding using random spatial frequencies. + """ + + def __init__(self, num_pos_feats: int = 64, scale: Optional[float] = None) -> None: + super().__init__() + if scale is None or scale <= 0.0: + scale = 1.0 + self.register_buffer( + "positional_encoding_gaussian_matrix", + scale * torch.randn((2, num_pos_feats)), + ) + + def _pe_encoding(self, coords: torch.Tensor) -> torch.Tensor: + """Positionally encode points that are normalized to [0,1].""" + # assuming coords are in [0, 1]^2 square and have d_1 x ... x d_n x 2 shape + coords = 2 * coords - 1 + coords = coords @ self.positional_encoding_gaussian_matrix + coords = 2 * np.pi * coords + # outputs d_1 x ... x d_n x C shape + return torch.cat([torch.sin(coords), torch.cos(coords)], dim=-1) + + def forward(self, size: Tuple[int, int]) -> torch.Tensor: + """Generate positional encoding for a grid of the specified size.""" + h, w = size + device: Any = self.positional_encoding_gaussian_matrix.device + grid = torch.ones((h, w), device=device, dtype=torch.float32) + y_embed = grid.cumsum(dim=0) - 0.5 + x_embed = grid.cumsum(dim=1) - 0.5 + y_embed = y_embed / h + x_embed = x_embed / w + + pe = self._pe_encoding(torch.stack([x_embed, y_embed], dim=-1)) + return pe.permute(2, 0, 1) # C x H x W + + def forward_with_coords( + self, coords_input: torch.Tensor, image_size: Tuple[int, int] + ) -> torch.Tensor: + """Positionally encode points that are not normalized to [0,1].""" + coords = coords_input.clone() + coords[:, :, 0] = coords[:, :, 0] / image_size[1] + coords[:, :, 1] = coords[:, :, 1] / image_size[0] + return self._pe_encoding(coords.to(torch.float)) # B x N x C diff --git a/modules/control/proc/segment_anything/modeling/sam.py b/modules/control/proc/segment_anything/modeling/sam.py new file mode 100644 index 000000000..614fd7483 --- /dev/null +++ b/modules/control/proc/segment_anything/modeling/sam.py @@ -0,0 +1,178 @@ +# Copyright (c) Meta Platforms, Inc. and affiliates. +# All rights reserved. + +# This source code is licensed under the license found in the +# LICENSE file in the root directory of this source tree. + +import torch +from torch import nn +from torch.nn import functional as F + +from typing import Any, Dict, List, Tuple, Union + +from .tiny_vit_sam import TinyViT +from .image_encoder import ImageEncoderViT +from .mask_decoder import MaskDecoder +from .prompt_encoder import PromptEncoder + + +class Sam(nn.Module): + mask_threshold: float = 0.0 + image_format: str = "RGB" + + def __init__( + self, + image_encoder: Union[ImageEncoderViT, TinyViT], + prompt_encoder: PromptEncoder, + mask_decoder: MaskDecoder, + pixel_mean: List[float] = None, + pixel_std: List[float] = None, + ) -> None: + """ + SAM predicts object masks from an image and input prompts. + + Arguments: + image_encoder (ImageEncoderViT): The backbone used to encode the + image into image embeddings that allow for efficient mask prediction. + prompt_encoder (PromptEncoder): Encodes various types of input prompts. + mask_decoder (MaskDecoder): Predicts masks from the image embeddings + and encoded prompts. + pixel_mean (list(float)): Mean values for normalizing pixels in the input image. + pixel_std (list(float)): Std values for normalizing pixels in the input image. + """ + if pixel_std is None: + pixel_std = [58.395, 57.12, 57.375] + if pixel_mean is None: + pixel_mean = [123.675, 116.28, 103.53] + super().__init__() + self.image_encoder = image_encoder + self.prompt_encoder = prompt_encoder + self.mask_decoder = mask_decoder + self.register_buffer("pixel_mean", torch.Tensor(pixel_mean).view(-1, 1, 1), False) + self.register_buffer("pixel_std", torch.Tensor(pixel_std).view(-1, 1, 1), False) + + @property + def device(self) -> Any: + return self.pixel_mean.device + + def forward( + self, + batched_input: List[Dict[str, Any]], + multimask_output: bool, + ) -> List[Dict[str, torch.Tensor]]: + """ + Predicts masks end-to-end from provided images and prompts. + If prompts are not known in advance, using SamPredictor is + recommended over calling the model directly. + + Arguments: + batched_input (list(dict)): A list over input images, each a + dictionary with the following keys. A prompt key can be + excluded if it is not present. + 'image': The image as a torch tensor in 3xHxW format, + already transformed for input to the model. + 'original_size': (tuple(int, int)) The original size of + the image before transformation, as (H, W). + 'point_coords': (torch.Tensor) Batched point prompts for + this image, with shape BxNx2. Already transformed to the + input frame of the model. + 'point_labels': (torch.Tensor) Batched labels for point prompts, + with shape BxN. + 'boxes': (torch.Tensor) Batched box inputs, with shape Bx4. + Already transformed to the input frame of the model. + 'mask_inputs': (torch.Tensor) Batched mask inputs to the model, + in the form Bx1xHxW. + multimask_output (bool): Whether the model should predict multiple + disambiguating masks, or return a single mask. + + Returns: + (list(dict)): A list over input images, where each element is + as dictionary with the following keys. + 'masks': (torch.Tensor) Batched binary mask predictions, + with shape BxCxHxW, where B is the number of input prompts, + C is determined by multimask_output, and (H, W) is the + original size of the image. + 'iou_predictions': (torch.Tensor) The model's predictions + of mask quality, in shape BxC. + 'low_res_logits': (torch.Tensor) Low resolution logits with + shape BxCxHxW, where H=W=256. Can be passed as mask input + to subsequent iterations of prediction. + """ + input_images = torch.stack([self.preprocess(x["image"]) for x in batched_input], dim=0) + image_embeddings = self.image_encoder(input_images) + + outputs = [] + for image_record, curr_embedding in zip(batched_input, image_embeddings): + if "point_coords" in image_record: + points = (image_record["point_coords"], image_record["point_labels"]) + else: + points = None + sparse_embeddings, dense_embeddings = self.prompt_encoder( + points=points, + boxes=image_record.get("boxes", None), + masks=image_record.get("mask_inputs", None), + ) + low_res_masks, iou_predictions = self.mask_decoder( + image_embeddings=curr_embedding.unsqueeze(0), + image_pe=self.prompt_encoder.get_dense_pe(), + sparse_prompt_embeddings=sparse_embeddings, + dense_prompt_embeddings=dense_embeddings, + multimask_output=multimask_output, + ) + masks = self.postprocess_masks( + low_res_masks, + input_size=image_record["image"].shape[-2:], + original_size=image_record["original_size"], + ) + masks = masks > self.mask_threshold + outputs.append( + { + "masks": masks, + "iou_predictions": iou_predictions, + "low_res_logits": low_res_masks, + } + ) + return outputs + + def postprocess_masks( + self, + masks: torch.Tensor, + input_size: Tuple[int, ...], + original_size: Tuple[int, ...], + ) -> torch.Tensor: + """ + Remove padding and upscale masks to the original image size. + + Arguments: + masks (torch.Tensor): Batched masks from the mask_decoder, + in BxCxHxW format. + input_size (tuple(int, int)): The size of the image input to the + model, in (H, W) format. Used to remove padding. + original_size (tuple(int, int)): The original size of the image + before resizing for input to the model, in (H, W) format. + + Returns: + (torch.Tensor): Batched masks in BxCxHxW format, where (H, W) + is given by original_size. + """ + masks = F.interpolate( + masks, + (self.image_encoder.img_size, self.image_encoder.img_size), + mode="bilinear", + align_corners=False, + ) + masks = masks[..., : input_size[0], : input_size[1]] + masks = F.interpolate(masks, original_size, mode="bilinear", align_corners=False) + return masks + + def preprocess(self, x: torch.Tensor) -> torch.Tensor: + """Normalize pixel values and pad to a square input.""" + # Normalize colors + x = (x - self.pixel_mean) / self.pixel_std + + # Pad + h, w = x.shape[-2:] + padh = self.image_encoder.img_size - h + padw = self.image_encoder.img_size - w + x = F.pad(x, (0, padw, 0, padh)) + return x diff --git a/modules/control/proc/segment_anything/modeling/tiny_vit_sam.py b/modules/control/proc/segment_anything/modeling/tiny_vit_sam.py new file mode 100644 index 000000000..165bd11bb --- /dev/null +++ b/modules/control/proc/segment_anything/modeling/tiny_vit_sam.py @@ -0,0 +1,721 @@ +# -------------------------------------------------------- +# TinyViT Model Architecture +# Copyright (c) 2022 Microsoft +# Adapted from LeViT and Swin Transformer +# LeViT: (https://github.com/facebookresearch/levit) +# Swin: (https://github.com/microsoft/swin-transformer) +# Build the TinyViT Model +# -------------------------------------------------------- + +import itertools +import torch +import torch.nn as nn +import torch.nn.functional as F +import torch.utils.checkpoint as checkpoint +from timm.models.layers import DropPath as TimmDropPath,\ + to_2tuple, trunc_normal_ +from timm.models.registry import register_model +from typing import Tuple + + +class Conv2d_BN(torch.nn.Sequential): + def __init__(self, a, b, ks=1, stride=1, pad=0, dilation=1, + groups=1, bn_weight_init=1): + super().__init__() + self.add_module('c', torch.nn.Conv2d( + a, b, ks, stride, pad, dilation, groups, bias=False)) + bn = torch.nn.BatchNorm2d(b) + torch.nn.init.constant_(bn.weight, bn_weight_init) + torch.nn.init.constant_(bn.bias, 0) + self.add_module('bn', bn) + + def fuse(self): + c, bn = self._modules.values() + w = bn.weight / (bn.running_var + bn.eps)**0.5 + w = c.weight * w[:, None, None, None] + b = bn.bias - bn.running_mean * bn.weight / \ + (bn.running_var + bn.eps)**0.5 + m = torch.nn.Conv2d(w.size(1) * self.c.groups, w.size( + 0), w.shape[2:], stride=self.c.stride, padding=self.c.padding, dilation=self.c.dilation, groups=self.c.groups) + m.weight.data.copy_(w) + m.bias.data.copy_(b) + return m + + +class DropPath(TimmDropPath): + def __init__(self, drop_prob=None): + super().__init__(drop_prob=drop_prob) + self.drop_prob = drop_prob + + def __repr__(self): + msg = super().__repr__() + msg += f'(drop_prob={self.drop_prob})' + return msg + + +class PatchEmbed(nn.Module): + def __init__(self, in_chans, embed_dim, resolution, activation): + super().__init__() + img_size: Tuple[int, int] = to_2tuple(resolution) + self.patches_resolution = (img_size[0] // 4, img_size[1] // 4) + self.num_patches = self.patches_resolution[0] * \ + self.patches_resolution[1] + self.in_chans = in_chans + self.embed_dim = embed_dim + n = embed_dim + self.seq = nn.Sequential( + Conv2d_BN(in_chans, n // 2, 3, 2, 1), + activation(), + Conv2d_BN(n // 2, n, 3, 2, 1), + ) + + def forward(self, x): + return self.seq(x) + + +class MBConv(nn.Module): + def __init__(self, in_chans, out_chans, expand_ratio, + activation, drop_path): + super().__init__() + self.in_chans = in_chans + self.hidden_chans = int(in_chans * expand_ratio) + self.out_chans = out_chans + + self.conv1 = Conv2d_BN(in_chans, self.hidden_chans, ks=1) + self.act1 = activation() + + self.conv2 = Conv2d_BN(self.hidden_chans, self.hidden_chans, + ks=3, stride=1, pad=1, groups=self.hidden_chans) + self.act2 = activation() + + self.conv3 = Conv2d_BN( + self.hidden_chans, out_chans, ks=1, bn_weight_init=0.0) + self.act3 = activation() + + self.drop_path = DropPath( + drop_path) if drop_path > 0. else nn.Identity() + + def forward(self, x): + shortcut = x + + x = self.conv1(x) + x = self.act1(x) + + x = self.conv2(x) + x = self.act2(x) + + x = self.conv3(x) + + x = self.drop_path(x) + + x += shortcut + x = self.act3(x) + + return x + + +class PatchMerging(nn.Module): + def __init__(self, input_resolution, dim, out_dim, activation): + super().__init__() + + self.input_resolution = input_resolution + self.dim = dim + self.out_dim = out_dim + self.act = activation() + self.conv1 = Conv2d_BN(dim, out_dim, 1, 1, 0) + stride_c=2 + if(out_dim==320 or out_dim==448 or out_dim==576): + stride_c=1 + self.conv2 = Conv2d_BN(out_dim, out_dim, 3, stride_c, 1, groups=out_dim) + self.conv3 = Conv2d_BN(out_dim, out_dim, 1, 1, 0) + + def forward(self, x): + if x.ndim == 3: + H, W = self.input_resolution + B = len(x) + # (B, C, H, W) + x = x.view(B, H, W, -1).permute(0, 3, 1, 2) + + x = self.conv1(x) + x = self.act(x) + + x = self.conv2(x) + x = self.act(x) + x = self.conv3(x) + x = x.flatten(2).transpose(1, 2) + return x + + +class ConvLayer(nn.Module): + def __init__(self, dim, input_resolution, depth, + activation, + drop_path=0., downsample=None, use_checkpoint=False, + out_dim=None, + conv_expand_ratio=4., + ): + + super().__init__() + self.dim = dim + self.input_resolution = input_resolution + self.depth = depth + self.use_checkpoint = use_checkpoint + + # build blocks + self.blocks = nn.ModuleList([ + MBConv(dim, dim, conv_expand_ratio, activation, + drop_path[i] if isinstance(drop_path, list) else drop_path, + ) + for i in range(depth)]) + + # patch merging layer + if downsample is not None: + self.downsample = downsample( + input_resolution, dim=dim, out_dim=out_dim, activation=activation) + else: + self.downsample = None + + def forward(self, x): + for blk in self.blocks: + if self.use_checkpoint: + x = checkpoint.checkpoint(blk, x) + else: + x = blk(x) + if self.downsample is not None: + x = self.downsample(x) + return x + + +class Mlp(nn.Module): + def __init__(self, in_features, hidden_features=None, + out_features=None, act_layer=nn.GELU, drop=0.): + super().__init__() + out_features = out_features or in_features + hidden_features = hidden_features or in_features + self.norm = nn.LayerNorm(in_features) + self.fc1 = nn.Linear(in_features, hidden_features) + self.fc2 = nn.Linear(hidden_features, out_features) + self.act = act_layer() + self.drop = nn.Dropout(drop) + + def forward(self, x): + x = self.norm(x) + + x = self.fc1(x) + x = self.act(x) + x = self.drop(x) + x = self.fc2(x) + x = self.drop(x) + return x + + +class Attention(torch.nn.Module): + def __init__(self, dim, key_dim, num_heads=8, + attn_ratio=4, + resolution=(14, 14), + ): + super().__init__() + # (h, w) + assert isinstance(resolution, tuple) and len(resolution) == 2 + self.num_heads = num_heads + self.scale = key_dim ** -0.5 + self.key_dim = key_dim + self.nh_kd = nh_kd = key_dim * num_heads + self.d = int(attn_ratio * key_dim) + self.dh = int(attn_ratio * key_dim) * num_heads + self.attn_ratio = attn_ratio + h = self.dh + nh_kd * 2 + + self.norm = nn.LayerNorm(dim) + self.qkv = nn.Linear(dim, h) + self.proj = nn.Linear(self.dh, dim) + + points = list(itertools.product( + range(resolution[0]), range(resolution[1]))) + N = len(points) + attention_offsets = {} + idxs = [] + for p1 in points: + for p2 in points: + offset = (abs(p1[0] - p2[0]), abs(p1[1] - p2[1])) + if offset not in attention_offsets: + attention_offsets[offset] = len(attention_offsets) + idxs.append(attention_offsets[offset]) + self.attention_biases = torch.nn.Parameter( + torch.zeros(num_heads, len(attention_offsets))) + self.register_buffer('attention_bias_idxs', + torch.LongTensor(idxs).view(N, N), + persistent=False) + + def train(self, mode=True): + super().train(mode) + if mode and hasattr(self, 'ab'): + del self.ab + else: + self.ab = self.attention_biases[:, self.attention_bias_idxs] + + def forward(self, x): # x (B,N,C) + B, N, _ = x.shape + + # Normalization + x = self.norm(x) + + qkv = self.qkv(x) + # (B, N, num_heads, d) + q, k, v = qkv.view(B, N, self.num_heads, - + 1).split([self.key_dim, self.key_dim, self.d], dim=3) + # (B, num_heads, N, d) + q = q.permute(0, 2, 1, 3) + k = k.permute(0, 2, 1, 3) + v = v.permute(0, 2, 1, 3) + + attn = ( + (q @ k.transpose(-2, -1)) * self.scale + + + (self.attention_biases[:, self.attention_bias_idxs] + if self.training else self.ab) + ) + attn = attn.softmax(dim=-1) + x = (attn @ v).transpose(1, 2).reshape(B, N, self.dh) + x = self.proj(x) + return x + + +class TinyViTBlock(nn.Module): + r""" TinyViT Block. + + Args: + dim (int): Number of input channels. + input_resolution (tuple[int, int]): Input resulotion. + num_heads (int): Number of attention heads. + window_size (int): Window size. + mlp_ratio (float): Ratio of mlp hidden dim to embedding dim. + drop (float, optional): Dropout rate. Default: 0.0 + drop_path (float, optional): Stochastic depth rate. Default: 0.0 + local_conv_size (int): the kernel size of the convolution between + Attention and MLP. Default: 3 + activation: the activation function. Default: nn.GELU + """ + + def __init__(self, dim, input_resolution, num_heads, window_size=7, + mlp_ratio=4., drop=0., drop_path=0., + local_conv_size=3, + activation=nn.GELU, + ): + super().__init__() + self.dim = dim + self.input_resolution = input_resolution + self.num_heads = num_heads + assert window_size > 0, 'window_size must be greater than 0' + self.window_size = window_size + self.mlp_ratio = mlp_ratio + + self.drop_path = DropPath( + drop_path) if drop_path > 0. else nn.Identity() + + assert dim % num_heads == 0, 'dim must be divisible by num_heads' + head_dim = dim // num_heads + + window_resolution = (window_size, window_size) + self.attn = Attention(dim, head_dim, num_heads, + attn_ratio=1, resolution=window_resolution) + + mlp_hidden_dim = int(dim * mlp_ratio) + mlp_activation = activation + self.mlp = Mlp(in_features=dim, hidden_features=mlp_hidden_dim, + act_layer=mlp_activation, drop=drop) + + pad = local_conv_size // 2 + self.local_conv = Conv2d_BN( + dim, dim, ks=local_conv_size, stride=1, pad=pad, groups=dim) + + def forward(self, x): + H, W = self.input_resolution + B, L, C = x.shape + assert L == H * W, "input feature has wrong size" + res_x = x + if H == self.window_size and W == self.window_size: + x = self.attn(x) + else: + x = x.view(B, H, W, C) + pad_b = (self.window_size - H % + self.window_size) % self.window_size + pad_r = (self.window_size - W % + self.window_size) % self.window_size + padding = pad_b > 0 or pad_r > 0 + + if padding: + x = F.pad(x, (0, 0, 0, pad_r, 0, pad_b)) + + pH, pW = H + pad_b, W + pad_r + nH = pH // self.window_size + nW = pW // self.window_size + # window partition + x = x.view(B, nH, self.window_size, nW, self.window_size, C).transpose(2, 3).reshape( + B * nH * nW, self.window_size * self.window_size, C) + x = self.attn(x) + # window reverse + x = x.view(B, nH, nW, self.window_size, self.window_size, + C).transpose(2, 3).reshape(B, pH, pW, C) + + if padding: + x = x[:, :H, :W].contiguous() + + x = x.view(B, L, C) + + x = res_x + self.drop_path(x) + + x = x.transpose(1, 2).reshape(B, C, H, W) + x = self.local_conv(x) + x = x.view(B, C, L).transpose(1, 2) + + x = x + self.drop_path(self.mlp(x)) + return x + + def extra_repr(self) -> str: + return f"dim={self.dim}, input_resolution={self.input_resolution}, num_heads={self.num_heads}, window_size={self.window_size}, mlp_ratio={self.mlp_ratio}" + + +class BasicLayer(nn.Module): + """ A basic TinyViT layer for one stage. + + Args: + dim (int): Number of input channels. + input_resolution (tuple[int]): Input resolution. + depth (int): Number of blocks. + num_heads (int): Number of attention heads. + window_size (int): Local window size. + mlp_ratio (float): Ratio of mlp hidden dim to embedding dim. + drop (float, optional): Dropout rate. Default: 0.0 + drop_path (float | tuple[float], optional): Stochastic depth rate. Default: 0.0 + downsample (nn.Module | None, optional): Downsample layer at the end of the layer. Default: None + use_checkpoint (bool): Whether to use checkpointing to save memory. Default: False. + local_conv_size: the kernel size of the depthwise convolution between attention and MLP. Default: 3 + activation: the activation function. Default: nn.GELU + out_dim: the output dimension of the layer. Default: dim + """ + + def __init__(self, dim, input_resolution, depth, num_heads, window_size, + mlp_ratio=4., drop=0., + drop_path=0., downsample=None, use_checkpoint=False, + local_conv_size=3, + activation=nn.GELU, + out_dim=None, + ): + + super().__init__() + self.dim = dim + self.input_resolution = input_resolution + self.depth = depth + self.use_checkpoint = use_checkpoint + + # build blocks + self.blocks = nn.ModuleList([ + TinyViTBlock(dim=dim, input_resolution=input_resolution, + num_heads=num_heads, window_size=window_size, + mlp_ratio=mlp_ratio, + drop=drop, + drop_path=drop_path[i] if isinstance( + drop_path, list) else drop_path, + local_conv_size=local_conv_size, + activation=activation, + ) + for i in range(depth)]) + + # patch merging layer + if downsample is not None: + self.downsample = downsample( + input_resolution, dim=dim, out_dim=out_dim, activation=activation) + else: + self.downsample = None + + def forward(self, x): + for blk in self.blocks: + if self.use_checkpoint: + x = checkpoint.checkpoint(blk, x) + else: + x = blk(x) + if self.downsample is not None: + x = self.downsample(x) + return x + + def extra_repr(self) -> str: + return f"dim={self.dim}, input_resolution={self.input_resolution}, depth={self.depth}" + +class LayerNorm2d(nn.Module): + def __init__(self, num_channels: int, eps: float = 1e-6) -> None: + super().__init__() + self.weight = nn.Parameter(torch.ones(num_channels)) + self.bias = nn.Parameter(torch.zeros(num_channels)) + self.eps = eps + + def forward(self, x: torch.Tensor) -> torch.Tensor: + u = x.mean(1, keepdim=True) + s = (x - u).pow(2).mean(1, keepdim=True) + x = (x - u) / torch.sqrt(s + self.eps) + x = self.weight[:, None, None] * x + self.bias[:, None, None] + return x +class TinyViT(nn.Module): + def __init__(self, img_size=224, in_chans=3, num_classes=1000, + embed_dims=None, depths=None, + num_heads=None, + window_sizes=None, + mlp_ratio=4., + drop_rate=0., + drop_path_rate=0.1, + use_checkpoint=False, + mbconv_expand_ratio=4.0, + local_conv_size=3, + layer_lr_decay=1.0, + ): + if window_sizes is None: + window_sizes = [7, 7, 14, 7] + if num_heads is None: + num_heads = [3, 6, 12, 24] + if depths is None: + depths = [2, 2, 6, 2] + if embed_dims is None: + embed_dims = [96, 192, 384, 768] + super().__init__() + self.img_size=img_size + self.num_classes = num_classes + self.depths = depths + self.num_layers = len(depths) + self.mlp_ratio = mlp_ratio + + activation = nn.GELU + + self.patch_embed = PatchEmbed(in_chans=in_chans, + embed_dim=embed_dims[0], + resolution=img_size, + activation=activation) + + patches_resolution = self.patch_embed.patches_resolution + self.patches_resolution = patches_resolution + + # stochastic depth + dpr = [x.item() for x in torch.linspace(0, drop_path_rate, + sum(depths))] # stochastic depth decay rule + + # build layers + self.layers = nn.ModuleList() + for i_layer in range(self.num_layers): + kwargs = dict(dim=embed_dims[i_layer], + input_resolution=(patches_resolution[0] // (2 ** (i_layer-1 if i_layer == 3 else i_layer)), + patches_resolution[1] // (2 ** (i_layer-1 if i_layer == 3 else i_layer))), + # input_resolution=(patches_resolution[0] // (2 ** i_layer), + # patches_resolution[1] // (2 ** i_layer)), + depth=depths[i_layer], + drop_path=dpr[sum(depths[:i_layer]):sum(depths[:i_layer + 1])], + downsample=PatchMerging if ( + i_layer < self.num_layers - 1) else None, + use_checkpoint=use_checkpoint, + out_dim=embed_dims[min( + i_layer + 1, len(embed_dims) - 1)], + activation=activation, + ) + if i_layer == 0: + layer = ConvLayer( + conv_expand_ratio=mbconv_expand_ratio, + **kwargs, + ) + else: + layer = BasicLayer( + num_heads=num_heads[i_layer], + window_size=window_sizes[i_layer], + mlp_ratio=self.mlp_ratio, + drop=drop_rate, + local_conv_size=local_conv_size, + **kwargs) + self.layers.append(layer) + + # Classifier head + self.norm_head = nn.LayerNorm(embed_dims[-1]) + self.head = nn.Linear( + embed_dims[-1], num_classes) if num_classes > 0 else torch.nn.Identity() + + # init weights + self.apply(self._init_weights) + self.set_layer_lr_decay(layer_lr_decay) + self.neck = nn.Sequential( + nn.Conv2d( + embed_dims[-1], + 256, + kernel_size=1, + bias=False, + ), + LayerNorm2d(256), + nn.Conv2d( + 256, + 256, + kernel_size=3, + padding=1, + bias=False, + ), + LayerNorm2d(256), + ) + def set_layer_lr_decay(self, layer_lr_decay): + decay_rate = layer_lr_decay + + # layers -> blocks (depth) + depth = sum(self.depths) + lr_scales = [decay_rate ** (depth - i - 1) for i in range(depth)] + #print("LR SCALES:", lr_scales) + + def _set_lr_scale(m, scale): + for p in m.parameters(): + p.lr_scale = scale + + self.patch_embed.apply(lambda x: _set_lr_scale(x, lr_scales[0])) + i = 0 + for layer in self.layers: + for block in layer.blocks: + block.apply(lambda x: _set_lr_scale(x, lr_scales[i])) # noqa + i += 1 + if layer.downsample is not None: + layer.downsample.apply( + lambda x: _set_lr_scale(x, lr_scales[i - 1])) # noqa + assert i == depth + for m in [self.norm_head, self.head]: + m.apply(lambda x: _set_lr_scale(x, lr_scales[-1])) + + for k, p in self.named_parameters(): + p.param_name = k + + def _check_lr_scale(m): + for p in m.parameters(): + assert hasattr(p, 'lr_scale'), p.param_name + + self.apply(_check_lr_scale) + + def _init_weights(self, m): + if isinstance(m, nn.Linear): + trunc_normal_(m.weight, std=.02) + if isinstance(m, nn.Linear) and m.bias is not None: + nn.init.constant_(m.bias, 0) + elif isinstance(m, nn.LayerNorm): + nn.init.constant_(m.bias, 0) + nn.init.constant_(m.weight, 1.0) + + @torch.jit.ignore + def no_weight_decay_keywords(self): + return {'attention_biases'} + + def forward_features(self, x): + # x: (N, C, H, W) + x = self.patch_embed(x) + + x = self.layers[0](x) + start_i = 1 + + for i in range(start_i, len(self.layers)): + layer = self.layers[i] + x = layer(x) + B,_,C=x.size() + x = x.view(B, 64, 64, C) + x=x.permute(0, 3, 1, 2) + x=self.neck(x) + return x + + def forward(self, x): + x = self.forward_features(x) + #x = self.norm_head(x) + #x = self.head(x) + return x + + +_checkpoint_url_format = \ + 'https://github.com/wkcn/TinyViT-model-zoo/releases/download/checkpoints/{}.pth' +_provided_checkpoints = { + 'tiny_vit_5m_224': 'tiny_vit_5m_22kto1k_distill', + 'tiny_vit_11m_224': 'tiny_vit_11m_22kto1k_distill', + 'tiny_vit_21m_224': 'tiny_vit_21m_22kto1k_distill', + 'tiny_vit_21m_384': 'tiny_vit_21m_22kto1k_384_distill', + 'tiny_vit_21m_512': 'tiny_vit_21m_22kto1k_512_distill', +} + + +def register_tiny_vit_model(fn): + '''Register a TinyViT model + It is a wrapper of `register_model` with loading the pretrained checkpoint. + ''' + def fn_wrapper(pretrained=False, **kwargs): + model = fn() + if pretrained: + model_name = fn.__name__ + assert model_name in _provided_checkpoints, \ + f'Sorry that the checkpoint `{model_name}` is not provided yet.' + url = _checkpoint_url_format.format( + _provided_checkpoints[model_name]) + checkpoint = torch.hub.load_state_dict_from_url( + url=url, + map_location='cpu', check_hash=False, + ) + model.load_state_dict(checkpoint['model']) + + return model + + # rename the name of fn_wrapper + fn_wrapper.__name__ = fn.__name__ + return register_model(fn_wrapper) + + +@register_tiny_vit_model +def tiny_vit_5m_224(pretrained=False, num_classes=1000, drop_path_rate=0.0): + return TinyViT( + num_classes=num_classes, + embed_dims=[64, 128, 160, 320], + depths=[2, 2, 6, 2], + num_heads=[2, 4, 5, 10], + window_sizes=[7, 7, 14, 7], + drop_path_rate=drop_path_rate, + ) + + +@register_tiny_vit_model +def tiny_vit_11m_224(pretrained=False, num_classes=1000, drop_path_rate=0.1): + return TinyViT( + num_classes=num_classes, + embed_dims=[64, 128, 256, 448], + depths=[2, 2, 6, 2], + num_heads=[2, 4, 8, 14], + window_sizes=[7, 7, 14, 7], + drop_path_rate=drop_path_rate, + ) + + +@register_tiny_vit_model +def tiny_vit_21m_224(pretrained=False, num_classes=1000, drop_path_rate=0.2): + return TinyViT( + num_classes=num_classes, + embed_dims=[96, 192, 384, 576], + depths=[2, 2, 6, 2], + num_heads=[3, 6, 12, 18], + window_sizes=[7, 7, 14, 7], + drop_path_rate=drop_path_rate, + ) + + +@register_tiny_vit_model +def tiny_vit_21m_384(pretrained=False, num_classes=1000, drop_path_rate=0.1): + return TinyViT( + img_size=384, + num_classes=num_classes, + embed_dims=[96, 192, 384, 576], + depths=[2, 2, 6, 2], + num_heads=[3, 6, 12, 18], + window_sizes=[12, 12, 24, 12], + drop_path_rate=drop_path_rate, + ) + + +@register_tiny_vit_model +def tiny_vit_21m_512(pretrained=False, num_classes=1000, drop_path_rate=0.1): + return TinyViT( + img_size=512, + num_classes=num_classes, + embed_dims=[96, 192, 384, 576], + depths=[2, 2, 6, 2], + num_heads=[3, 6, 12, 18], + window_sizes=[16, 16, 32, 16], + drop_path_rate=drop_path_rate, + ) diff --git a/modules/control/proc/segment_anything/modeling/transformer.py b/modules/control/proc/segment_anything/modeling/transformer.py new file mode 100644 index 000000000..28fafea52 --- /dev/null +++ b/modules/control/proc/segment_anything/modeling/transformer.py @@ -0,0 +1,240 @@ +# Copyright (c) Meta Platforms, Inc. and affiliates. +# All rights reserved. + +# This source code is licensed under the license found in the +# LICENSE file in the root directory of this source tree. + +import torch +from torch import Tensor, nn + +import math +from typing import Tuple, Type + +from .common import MLPBlock + + +class TwoWayTransformer(nn.Module): + def __init__( + self, + depth: int, + embedding_dim: int, + num_heads: int, + mlp_dim: int, + activation: Type[nn.Module] = nn.ReLU, + attention_downsample_rate: int = 2, + ) -> None: + """ + A transformer decoder that attends to an input image using + queries whose positional embedding is supplied. + + Args: + depth (int): number of layers in the transformer + embedding_dim (int): the channel dimension for the input embeddings + num_heads (int): the number of heads for multihead attention. Must + divide embedding_dim + mlp_dim (int): the channel dimension internal to the MLP block + activation (nn.Module): the activation to use in the MLP block + """ + super().__init__() + self.depth = depth + self.embedding_dim = embedding_dim + self.num_heads = num_heads + self.mlp_dim = mlp_dim + self.layers = nn.ModuleList() + + for i in range(depth): + self.layers.append( + TwoWayAttentionBlock( + embedding_dim=embedding_dim, + num_heads=num_heads, + mlp_dim=mlp_dim, + activation=activation, + attention_downsample_rate=attention_downsample_rate, + skip_first_layer_pe=(i == 0), + ) + ) + + self.final_attn_token_to_image = Attention( + embedding_dim, num_heads, downsample_rate=attention_downsample_rate + ) + self.norm_final_attn = nn.LayerNorm(embedding_dim) + + def forward( + self, + image_embedding: Tensor, + image_pe: Tensor, + point_embedding: Tensor, + ) -> Tuple[Tensor, Tensor]: + """ + Args: + image_embedding (torch.Tensor): image to attend to. Should be shape + B x embedding_dim x h x w for any h and w. + image_pe (torch.Tensor): the positional encoding to add to the image. Must + have the same shape as image_embedding. + point_embedding (torch.Tensor): the embedding to add to the query points. + Must have shape B x N_points x embedding_dim for any N_points. + + Returns: + torch.Tensor: the processed point_embedding + torch.Tensor: the processed image_embedding + """ + # BxCxHxW -> BxHWxC == B x N_image_tokens x C + bs, c, h, w = image_embedding.shape + image_embedding = image_embedding.flatten(2).permute(0, 2, 1) + image_pe = image_pe.flatten(2).permute(0, 2, 1) + + # Prepare queries + queries = point_embedding + keys = image_embedding + + # Apply transformer blocks and final layernorm + for layer in self.layers: + queries, keys = layer( + queries=queries, + keys=keys, + query_pe=point_embedding, + key_pe=image_pe, + ) + + # Apply the final attention layer from the points to the image + q = queries + point_embedding + k = keys + image_pe + attn_out = self.final_attn_token_to_image(q=q, k=k, v=keys) + queries = queries + attn_out + queries = self.norm_final_attn(queries) + + return queries, keys + + +class TwoWayAttentionBlock(nn.Module): + def __init__( + self, + embedding_dim: int, + num_heads: int, + mlp_dim: int = 2048, + activation: Type[nn.Module] = nn.ReLU, + attention_downsample_rate: int = 2, + skip_first_layer_pe: bool = False, + ) -> None: + """ + A transformer block with four layers: (1) self-attention of sparse + inputs, (2) cross attention of sparse inputs to dense inputs, (3) mlp + block on sparse inputs, and (4) cross attention of dense inputs to sparse + inputs. + + Arguments: + embedding_dim (int): the channel dimension of the embeddings + num_heads (int): the number of heads in the attention layers + mlp_dim (int): the hidden dimension of the mlp block + activation (nn.Module): the activation of the mlp block + skip_first_layer_pe (bool): skip the PE on the first layer + """ + super().__init__() + self.self_attn = Attention(embedding_dim, num_heads) + self.norm1 = nn.LayerNorm(embedding_dim) + + self.cross_attn_token_to_image = Attention( + embedding_dim, num_heads, downsample_rate=attention_downsample_rate + ) + self.norm2 = nn.LayerNorm(embedding_dim) + + self.mlp = MLPBlock(embedding_dim, mlp_dim, activation) + self.norm3 = nn.LayerNorm(embedding_dim) + + self.norm4 = nn.LayerNorm(embedding_dim) + self.cross_attn_image_to_token = Attention( + embedding_dim, num_heads, downsample_rate=attention_downsample_rate + ) + + self.skip_first_layer_pe = skip_first_layer_pe + + def forward( + self, queries: Tensor, keys: Tensor, query_pe: Tensor, key_pe: Tensor + ) -> Tuple[Tensor, Tensor]: + # Self attention block + if self.skip_first_layer_pe: + queries = self.self_attn(q=queries, k=queries, v=queries) + else: + q = queries + query_pe + attn_out = self.self_attn(q=q, k=q, v=queries) + queries = queries + attn_out + queries = self.norm1(queries) + + # Cross attention block, tokens attending to image embedding + q = queries + query_pe + k = keys + key_pe + attn_out = self.cross_attn_token_to_image(q=q, k=k, v=keys) + queries = queries + attn_out + queries = self.norm2(queries) + + # MLP block + mlp_out = self.mlp(queries) + queries = queries + mlp_out + queries = self.norm3(queries) + + # Cross attention block, image embedding attending to tokens + q = queries + query_pe + k = keys + key_pe + attn_out = self.cross_attn_image_to_token(q=k, k=q, v=queries) + keys = keys + attn_out + keys = self.norm4(keys) + + return queries, keys + + +class Attention(nn.Module): + """ + An attention layer that allows for downscaling the size of the embedding + after projection to queries, keys, and values. + """ + + def __init__( + self, + embedding_dim: int, + num_heads: int, + downsample_rate: int = 1, + ) -> None: + super().__init__() + self.embedding_dim = embedding_dim + self.internal_dim = embedding_dim // downsample_rate + self.num_heads = num_heads + assert self.internal_dim % num_heads == 0, "num_heads must divide embedding_dim." + + self.q_proj = nn.Linear(embedding_dim, self.internal_dim) + self.k_proj = nn.Linear(embedding_dim, self.internal_dim) + self.v_proj = nn.Linear(embedding_dim, self.internal_dim) + self.out_proj = nn.Linear(self.internal_dim, embedding_dim) + + def _separate_heads(self, x: Tensor, num_heads: int) -> Tensor: + b, n, c = x.shape + x = x.reshape(b, n, num_heads, c // num_heads) + return x.transpose(1, 2) # B x N_heads x N_tokens x C_per_head + + def _recombine_heads(self, x: Tensor) -> Tensor: + b, n_heads, n_tokens, c_per_head = x.shape + x = x.transpose(1, 2) + return x.reshape(b, n_tokens, n_heads * c_per_head) # B x N_tokens x C + + def forward(self, q: Tensor, k: Tensor, v: Tensor) -> Tensor: + # Input projections + q = self.q_proj(q) + k = self.k_proj(k) + v = self.v_proj(v) + + # Separate into heads + q = self._separate_heads(q, self.num_heads) + k = self._separate_heads(k, self.num_heads) + v = self._separate_heads(v, self.num_heads) + + # Attention + _, _, _, c_per_head = q.shape + attn = q @ k.permute(0, 1, 3, 2) # B x N_heads x N_tokens x N_tokens + attn = attn / math.sqrt(c_per_head) + attn = torch.softmax(attn, dim=-1) + + # Get output + out = attn @ v + out = self._recombine_heads(out) + out = self.out_proj(out) + + return out diff --git a/modules/control/proc/segment_anything/predictor.py b/modules/control/proc/segment_anything/predictor.py new file mode 100644 index 000000000..742a34ef1 --- /dev/null +++ b/modules/control/proc/segment_anything/predictor.py @@ -0,0 +1,267 @@ +# Copyright (c) Meta Platforms, Inc. and affiliates. +# All rights reserved. + +# This source code is licensed under the license found in the +# LICENSE file in the root directory of this source tree. + +import numpy as np +import torch + +from .modeling import Sam + +from typing import Optional, Tuple + +from .utils.transforms import ResizeLongestSide + + +class SamPredictor: + def __init__( + self, + sam_model: Sam, + ) -> None: + """ + Uses SAM to calculate the image embedding for an image, and then + allow repeated, efficient mask prediction given prompts. + + Arguments: + sam_model (Sam): The model to use for mask prediction. + """ + super().__init__() + self.model = sam_model + self.transform = ResizeLongestSide(sam_model.image_encoder.img_size) + self.reset_image() + + def set_image( + self, + image: np.ndarray, + image_format: str = "RGB", + ) -> None: + """ + Calculates the image embeddings for the provided image, allowing + masks to be predicted with the 'predict' method. + + Arguments: + image (np.ndarray): The image for calculating masks. Expects an + image in HWC uint8 format, with pixel values in [0, 255]. + image_format (str): The color format of the image, in ['RGB', 'BGR']. + """ + assert image_format in [ + "RGB", + "BGR", + ], f"image_format must be in ['RGB', 'BGR'], is {image_format}." + if image_format != self.model.image_format: + image = image[..., ::-1] + + # Transform the image to the form expected by the model + input_image = self.transform.apply_image(image) + input_image_torch = torch.as_tensor(input_image, device=self.device) + input_image_torch = input_image_torch.permute(2, 0, 1).contiguous()[None, :, :, :] + + self.set_torch_image(input_image_torch, image.shape[:2]) + + def set_torch_image( + self, + transformed_image: torch.Tensor, + original_image_size: Tuple[int, ...], + ) -> None: + """ + Calculates the image embeddings for the provided image, allowing + masks to be predicted with the 'predict' method. Expects the input + image to be already transformed to the format expected by the model. + + Arguments: + transformed_image (torch.Tensor): The input image, with shape + 1x3xHxW, which has been transformed with ResizeLongestSide. + original_image_size (tuple(int, int)): The size of the image + before transformation, in (H, W) format. + """ + assert ( + len(transformed_image.shape) == 4 + and transformed_image.shape[1] == 3 + and max(*transformed_image.shape[2:]) == self.model.image_encoder.img_size + ), f"set_torch_image input must be BCHW with long side {self.model.image_encoder.img_size}." + self.reset_image() + + self.original_size = original_image_size + self.input_size = tuple(transformed_image.shape[-2:]) + input_image = self.model.preprocess(transformed_image) + self.features = self.model.image_encoder(input_image) + self.is_image_set = True + + def predict( + self, + point_coords: Optional[np.ndarray] = None, + point_labels: Optional[np.ndarray] = None, + box: Optional[np.ndarray] = None, + mask_input: Optional[np.ndarray] = None, + multimask_output: bool = True, + return_logits: bool = False, + ) -> Tuple[np.ndarray, np.ndarray, np.ndarray]: + """ + Predict masks for the given input prompts, using the currently set image. + + Arguments: + point_coords (np.ndarray or None): A Nx2 array of point prompts to the + model. Each point is in (X,Y) in pixels. + point_labels (np.ndarray or None): A length N array of labels for the + point prompts. 1 indicates a foreground point and 0 indicates a + background point. + box (np.ndarray or None): A length 4 array given a box prompt to the + model, in XYXY format. + mask_input (np.ndarray): A low resolution mask input to the model, typically + coming from a previous prediction iteration. Has form 1xHxW, where + for SAM, H=W=256. + multimask_output (bool): If true, the model will return three masks. + For ambiguous input prompts (such as a single click), this will often + produce better masks than a single prediction. If only a single + mask is needed, the model's predicted quality score can be used + to select the best mask. For non-ambiguous prompts, such as multiple + input prompts, multimask_output=False can give better results. + return_logits (bool): If true, returns un-thresholded masks logits + instead of a binary mask. + + Returns: + (np.ndarray): The output masks in CxHxW format, where C is the + number of masks, and (H, W) is the original image size. + (np.ndarray): An array of length C containing the model's + predictions for the quality of each mask. + (np.ndarray): An array of shape CxHxW, where C is the number + of masks and H=W=256. These low resolution logits can be passed to + a subsequent iteration as mask input. + """ + if not self.is_image_set: + raise RuntimeError("An image must be set with .set_image(...) before mask prediction.") + + # Transform input prompts + coords_torch, labels_torch, box_torch, mask_input_torch = None, None, None, None + if point_coords is not None: + assert ( + point_labels is not None + ), "point_labels must be supplied if point_coords is supplied." + point_coords = self.transform.apply_coords(point_coords, self.original_size) + coords_torch = torch.as_tensor(point_coords, dtype=torch.float, device=self.device) + labels_torch = torch.as_tensor(point_labels, dtype=torch.int, device=self.device) + coords_torch, labels_torch = coords_torch[None, :, :], labels_torch[None, :] + if box is not None: + box = self.transform.apply_boxes(box, self.original_size) + box_torch = torch.as_tensor(box, dtype=torch.float, device=self.device) + box_torch = box_torch[None, :] + if mask_input is not None: + mask_input_torch = torch.as_tensor(mask_input, dtype=torch.float, device=self.device) + mask_input_torch = mask_input_torch[None, :, :, :] + + masks, iou_predictions, low_res_masks = self.predict_torch( + coords_torch, + labels_torch, + box_torch, + mask_input_torch, + multimask_output, + return_logits=return_logits, + ) + + masks_np = masks[0].detach().cpu().numpy() + iou_predictions_np = iou_predictions[0].detach().cpu().numpy() + low_res_masks_np = low_res_masks[0].detach().cpu().numpy() + return masks_np, iou_predictions_np, low_res_masks_np + + def predict_torch( + self, + point_coords: Optional[torch.Tensor], + point_labels: Optional[torch.Tensor], + boxes: Optional[torch.Tensor] = None, + mask_input: Optional[torch.Tensor] = None, + multimask_output: bool = True, + return_logits: bool = False, + ) -> Tuple[torch.Tensor, torch.Tensor, torch.Tensor]: + """ + Predict masks for the given input prompts, using the currently set image. + Input prompts are batched torch tensors and are expected to already be + transformed to the input frame using ResizeLongestSide. + + Arguments: + point_coords (torch.Tensor or None): A BxNx2 array of point prompts to the + model. Each point is in (X,Y) in pixels. + point_labels (torch.Tensor or None): A BxN array of labels for the + point prompts. 1 indicates a foreground point and 0 indicates a + background point. + boxes (np.ndarray or None): A Bx4 array given a box prompt to the + model, in XYXY format. + mask_input (np.ndarray): A low resolution mask input to the model, typically + coming from a previous prediction iteration. Has form Bx1xHxW, where + for SAM, H=W=256. Masks returned by a previous iteration of the + predict method do not need further transformation. + multimask_output (bool): If true, the model will return three masks. + For ambiguous input prompts (such as a single click), this will often + produce better masks than a single prediction. If only a single + mask is needed, the model's predicted quality score can be used + to select the best mask. For non-ambiguous prompts, such as multiple + input prompts, multimask_output=False can give better results. + return_logits (bool): If true, returns un-thresholded masks logits + instead of a binary mask. + + Returns: + (torch.Tensor): The output masks in BxCxHxW format, where C is the + number of masks, and (H, W) is the original image size. + (torch.Tensor): An array of shape BxC containing the model's + predictions for the quality of each mask. + (torch.Tensor): An array of shape BxCxHxW, where C is the number + of masks and H=W=256. These low res logits can be passed to + a subsequent iteration as mask input. + """ + if not self.is_image_set: + raise RuntimeError("An image must be set with .set_image(...) before mask prediction.") + + if point_coords is not None: + points = (point_coords, point_labels) + else: + points = None + + # Embed prompts + sparse_embeddings, dense_embeddings = self.model.prompt_encoder( + points=points, + boxes=boxes, + masks=mask_input, + ) + + # Predict masks + low_res_masks, iou_predictions = self.model.mask_decoder( + image_embeddings=self.features, + image_pe=self.model.prompt_encoder.get_dense_pe(), + sparse_prompt_embeddings=sparse_embeddings, + dense_prompt_embeddings=dense_embeddings, + multimask_output=multimask_output, + ) + + # Upscale the masks to the original image resolution + masks = self.model.postprocess_masks(low_res_masks, self.input_size, self.original_size) + + if not return_logits: + masks = masks > self.model.mask_threshold + + return masks, iou_predictions, low_res_masks + + def get_image_embedding(self) -> torch.Tensor: + """ + Returns the image embeddings for the currently set image, with + shape 1xCxHxW, where C is the embedding dimension and (H,W) are + the embedding spatial dimension of SAM (typically C=256, H=W=64). + """ + if not self.is_image_set: + raise RuntimeError( + "An image must be set with .set_image(...) to generate an embedding." + ) + assert self.features is not None, "Features must exist if an image has been set." + return self.features + + @property + def device(self) -> torch.device: + return self.model.device + + def reset_image(self) -> None: + """Resets the currently set image.""" + self.is_image_set = False + self.features = None + self.orig_h = None + self.orig_w = None + self.input_h = None + self.input_w = None diff --git a/modules/control/proc/segment_anything/utils/__init__.py b/modules/control/proc/segment_anything/utils/__init__.py new file mode 100644 index 000000000..5277f4615 --- /dev/null +++ b/modules/control/proc/segment_anything/utils/__init__.py @@ -0,0 +1,5 @@ +# Copyright (c) Meta Platforms, Inc. and affiliates. +# All rights reserved. + +# This source code is licensed under the license found in the +# LICENSE file in the root directory of this source tree. diff --git a/modules/control/proc/segment_anything/utils/amg.py b/modules/control/proc/segment_anything/utils/amg.py new file mode 100644 index 000000000..be064071e --- /dev/null +++ b/modules/control/proc/segment_anything/utils/amg.py @@ -0,0 +1,346 @@ +# Copyright (c) Meta Platforms, Inc. and affiliates. +# All rights reserved. + +# This source code is licensed under the license found in the +# LICENSE file in the root directory of this source tree. + +import numpy as np +import torch + +import math +from copy import deepcopy +from itertools import product +from typing import Any, Dict, Generator, ItemsView, List, Tuple + + +class MaskData: + """ + A structure for storing masks and their related data in batched format. + Implements basic filtering and concatenation. + """ + + def __init__(self, **kwargs) -> None: + for v in kwargs.values(): + assert isinstance( + v, (list, np.ndarray, torch.Tensor) + ), "MaskData only supports list, numpy arrays, and torch tensors." + self._stats = dict(**kwargs) + + def __setitem__(self, key: str, item: Any) -> None: + assert isinstance( + item, (list, np.ndarray, torch.Tensor) + ), "MaskData only supports list, numpy arrays, and torch tensors." + self._stats[key] = item + + def __delitem__(self, key: str) -> None: + del self._stats[key] + + def __getitem__(self, key: str) -> Any: + return self._stats[key] + + def items(self) -> ItemsView[str, Any]: + return self._stats.items() + + def filter(self, keep: torch.Tensor) -> None: + for k, v in self._stats.items(): + if v is None: + self._stats[k] = None + elif isinstance(v, torch.Tensor): + self._stats[k] = v[torch.as_tensor(keep, device=v.device)] + elif isinstance(v, np.ndarray): + self._stats[k] = v[keep.detach().cpu().numpy()] + elif isinstance(v, list) and keep.dtype == torch.bool: + self._stats[k] = [a for i, a in enumerate(v) if keep[i]] + elif isinstance(v, list): + self._stats[k] = [v[i] for i in keep] + else: + raise TypeError(f"MaskData key {k} has an unsupported type {type(v)}.") + + def cat(self, new_stats: "MaskData") -> None: + for k, v in new_stats.items(): + if k not in self._stats or self._stats[k] is None: + self._stats[k] = deepcopy(v) + elif isinstance(v, torch.Tensor): + self._stats[k] = torch.cat([self._stats[k], v], dim=0) + elif isinstance(v, np.ndarray): + self._stats[k] = np.concatenate([self._stats[k], v], axis=0) + elif isinstance(v, list): + self._stats[k] = self._stats[k] + deepcopy(v) + else: + raise TypeError(f"MaskData key {k} has an unsupported type {type(v)}.") + + def to_numpy(self) -> None: + for k, v in self._stats.items(): + if isinstance(v, torch.Tensor): + self._stats[k] = v.detach().cpu().numpy() + + +def is_box_near_crop_edge( + boxes: torch.Tensor, crop_box: List[int], orig_box: List[int], atol: float = 20.0 +) -> torch.Tensor: + """Filter masks at the edge of a crop, but not at the edge of the original image.""" + crop_box_torch = torch.as_tensor(crop_box, dtype=torch.float, device=boxes.device) + orig_box_torch = torch.as_tensor(orig_box, dtype=torch.float, device=boxes.device) + boxes = uncrop_boxes_xyxy(boxes, crop_box).float() + near_crop_edge = torch.isclose(boxes, crop_box_torch[None, :], atol=atol, rtol=0) + near_image_edge = torch.isclose(boxes, orig_box_torch[None, :], atol=atol, rtol=0) + near_crop_edge = torch.logical_and(near_crop_edge, ~near_image_edge) + return torch.any(near_crop_edge, dim=1) + + +def box_xyxy_to_xywh(box_xyxy: torch.Tensor) -> torch.Tensor: + box_xywh = deepcopy(box_xyxy) + box_xywh[2] = box_xywh[2] - box_xywh[0] + box_xywh[3] = box_xywh[3] - box_xywh[1] + return box_xywh + + +def batch_iterator(batch_size: int, *args) -> Generator[List[Any], None, None]: + assert len(args) > 0 and all( + len(a) == len(args[0]) for a in args + ), "Batched iteration must have inputs of all the same size." + n_batches = len(args[0]) // batch_size + int(len(args[0]) % batch_size != 0) + for b in range(n_batches): + yield [arg[b * batch_size : (b + 1) * batch_size] for arg in args] + + +def mask_to_rle_pytorch(tensor: torch.Tensor) -> List[Dict[str, Any]]: + """ + Encodes masks to an uncompressed RLE, in the format expected by + pycoco tools. + """ + # Put in fortran order and flatten h,w + b, h, w = tensor.shape + tensor = tensor.permute(0, 2, 1).flatten(1) + + # Compute change indices + diff = tensor[:, 1:] ^ tensor[:, :-1] + change_indices = diff.nonzero() + + # Encode run length + out = [] + for i in range(b): + cur_idxs = change_indices[change_indices[:, 0] == i, 1] + cur_idxs = torch.cat( + [ + torch.tensor([0], dtype=cur_idxs.dtype, device=cur_idxs.device), + cur_idxs + 1, + torch.tensor([h * w], dtype=cur_idxs.dtype, device=cur_idxs.device), + ] + ) + btw_idxs = cur_idxs[1:] - cur_idxs[:-1] + counts = [] if tensor[i, 0] == 0 else [0] + counts.extend(btw_idxs.detach().cpu().tolist()) + out.append({"size": [h, w], "counts": counts}) + return out + + +def rle_to_mask(rle: Dict[str, Any]) -> np.ndarray: + """Compute a binary mask from an uncompressed RLE.""" + h, w = rle["size"] + mask = np.empty(h * w, dtype=bool) + idx = 0 + parity = False + for count in rle["counts"]: + mask[idx : idx + count] = parity + idx += count + parity ^= True + mask = mask.reshape(w, h) + return mask.transpose() # Put in C order + + +def area_from_rle(rle: Dict[str, Any]) -> int: + return sum(rle["counts"][1::2]) + + +def calculate_stability_score( + masks: torch.Tensor, mask_threshold: float, threshold_offset: float +) -> torch.Tensor: + """ + Computes the stability score for a batch of masks. The stability + score is the IoU between the binary masks obtained by thresholding + the predicted mask logits at high and low values. + """ + # One mask is always contained inside the other. + # Save memory by preventing unnecessary cast to torch.int64 + intersections = ( + (masks > (mask_threshold + threshold_offset)) + .sum(-1, dtype=torch.int16) + .sum(-1, dtype=torch.int32) + ) + unions = ( + (masks > (mask_threshold - threshold_offset)) + .sum(-1, dtype=torch.int16) + .sum(-1, dtype=torch.int32) + ) + return intersections / unions + + +def build_point_grid(n_per_side: int) -> np.ndarray: + """Generates a 2D grid of points evenly spaced in [0,1]x[0,1].""" + offset = 1 / (2 * n_per_side) + points_one_side = np.linspace(offset, 1 - offset, n_per_side) + points_x = np.tile(points_one_side[None, :], (n_per_side, 1)) + points_y = np.tile(points_one_side[:, None], (1, n_per_side)) + points = np.stack([points_x, points_y], axis=-1).reshape(-1, 2) + return points + + +def build_all_layer_point_grids( + n_per_side: int, n_layers: int, scale_per_layer: int +) -> List[np.ndarray]: + """Generates point grids for all crop layers.""" + points_by_layer = [] + for i in range(n_layers + 1): + n_points = int(n_per_side / (scale_per_layer**i)) + points_by_layer.append(build_point_grid(n_points)) + return points_by_layer + + +def generate_crop_boxes( + im_size: Tuple[int, ...], n_layers: int, overlap_ratio: float +) -> Tuple[List[List[int]], List[int]]: + """ + Generates a list of crop boxes of different sizes. Each layer + has (2**i)**2 boxes for the ith layer. + """ + crop_boxes, layer_idxs = [], [] + im_h, im_w = im_size + short_side = min(im_h, im_w) + + # Original image + crop_boxes.append([0, 0, im_w, im_h]) + layer_idxs.append(0) + + def crop_len(orig_len, n_crops, overlap): + return int(math.ceil((overlap * (n_crops - 1) + orig_len) / n_crops)) + + for i_layer in range(n_layers): + n_crops_per_side = 2 ** (i_layer + 1) + overlap = int(overlap_ratio * short_side * (2 / n_crops_per_side)) + + crop_w = crop_len(im_w, n_crops_per_side, overlap) + crop_h = crop_len(im_h, n_crops_per_side, overlap) + + crop_box_x0 = [int((crop_w - overlap) * i) for i in range(n_crops_per_side)] + crop_box_y0 = [int((crop_h - overlap) * i) for i in range(n_crops_per_side)] + + # Crops in XYWH format + for x0, y0 in product(crop_box_x0, crop_box_y0): + box = [x0, y0, min(x0 + crop_w, im_w), min(y0 + crop_h, im_h)] + crop_boxes.append(box) + layer_idxs.append(i_layer + 1) + + return crop_boxes, layer_idxs + + +def uncrop_boxes_xyxy(boxes: torch.Tensor, crop_box: List[int]) -> torch.Tensor: + x0, y0, _, _ = crop_box + offset = torch.tensor([[x0, y0, x0, y0]], device=boxes.device) + # Check if boxes has a channel dimension + if len(boxes.shape) == 3: + offset = offset.unsqueeze(1) + return boxes + offset + + +def uncrop_points(points: torch.Tensor, crop_box: List[int]) -> torch.Tensor: + x0, y0, _, _ = crop_box + offset = torch.tensor([[x0, y0]], device=points.device) + # Check if points has a channel dimension + if len(points.shape) == 3: + offset = offset.unsqueeze(1) + return points + offset + + +def uncrop_masks( + masks: torch.Tensor, crop_box: List[int], orig_h: int, orig_w: int +) -> torch.Tensor: + x0, y0, x1, y1 = crop_box + if x0 == 0 and y0 == 0 and x1 == orig_w and y1 == orig_h: + return masks + # Coordinate transform masks + pad_x, pad_y = orig_w - (x1 - x0), orig_h - (y1 - y0) + pad = (x0, pad_x - x0, y0, pad_y - y0) + return torch.nn.functional.pad(masks, pad, value=0) + + +def remove_small_regions( + mask: np.ndarray, area_thresh: float, mode: str +) -> Tuple[np.ndarray, bool]: + """ + Removes small disconnected regions and holes in a mask. Returns the + mask and an indicator of if the mask has been modified. + """ + import cv2 # type: ignore + + assert mode in ["holes", "islands"] + correct_holes = mode == "holes" + working_mask = (correct_holes ^ mask).astype(np.uint8) + n_labels, regions, stats, _ = cv2.connectedComponentsWithStats(working_mask, 8) + sizes = stats[:, -1][1:] # Row 0 is background label + small_regions = [i + 1 for i, s in enumerate(sizes) if s < area_thresh] + if len(small_regions) == 0: + return mask, False + fill_labels = [0] + small_regions + if not correct_holes: + fill_labels = [i for i in range(n_labels) if i not in fill_labels] + # If every region is below threshold, keep largest + if len(fill_labels) == 0: + fill_labels = [int(np.argmax(sizes)) + 1] + mask = np.isin(regions, fill_labels) + return mask, True + + +def coco_encode_rle(uncompressed_rle: Dict[str, Any]) -> Dict[str, Any]: + from pycocotools import mask as mask_utils # type: ignore + + h, w = uncompressed_rle["size"] + rle = mask_utils.frPyObjects(uncompressed_rle, h, w) + rle["counts"] = rle["counts"].decode("utf-8") # Necessary to serialize with json + return rle + + +def batched_mask_to_box(masks: torch.Tensor) -> torch.Tensor: + """ + Calculates boxes in XYXY format around masks. Return [0,0,0,0] for + an empty mask. For input shape C1xC2x...xHxW, the output shape is C1xC2x...x4. + """ + # torch.max below raises an error on empty inputs, just skip in this case + if torch.numel(masks) == 0: + return torch.zeros(*masks.shape[:-2], 4, device=masks.device) + + # Normalize shape to CxHxW + shape = masks.shape + h, w = shape[-2:] + if len(shape) > 2: + masks = masks.flatten(0, -3) + else: + masks = masks.unsqueeze(0) + + # Get top and bottom edges + in_height, _ = torch.max(masks, dim=-1) + in_height_coords = in_height * torch.arange(h, device=in_height.device)[None, :] + bottom_edges, _ = torch.max(in_height_coords, dim=-1) + in_height_coords = in_height_coords + h * (~in_height) + top_edges, _ = torch.min(in_height_coords, dim=-1) + + # Get left and right edges + in_width, _ = torch.max(masks, dim=-2) + in_width_coords = in_width * torch.arange(w, device=in_width.device)[None, :] + right_edges, _ = torch.max(in_width_coords, dim=-1) + in_width_coords = in_width_coords + w * (~in_width) + left_edges, _ = torch.min(in_width_coords, dim=-1) + + # If the mask is empty the right edge will be to the left of the left edge. + # Replace these boxes with [0, 0, 0, 0] + empty_filter = (right_edges < left_edges) | (bottom_edges < top_edges) + out = torch.stack([left_edges, top_edges, right_edges, bottom_edges], dim=-1) + out = out * (~empty_filter).unsqueeze(-1) + + # Return to original shape + if len(shape) > 2: + out = out.reshape(*shape[:-2], 4) + else: + out = out[0] + + return out diff --git a/modules/control/proc/segment_anything/utils/onnx.py b/modules/control/proc/segment_anything/utils/onnx.py new file mode 100644 index 000000000..103867faf --- /dev/null +++ b/modules/control/proc/segment_anything/utils/onnx.py @@ -0,0 +1,143 @@ +# Copyright (c) Meta Platforms, Inc. and affiliates. +# All rights reserved. + +# This source code is licensed under the license found in the +# LICENSE file in the root directory of this source tree. + +import torch +import torch.nn as nn +from torch.nn import functional as F + +from typing import Tuple + +from ..modeling import Sam +from .amg import calculate_stability_score + + +class SamOnnxModel(nn.Module): + """ + This model should not be called directly, but is used in ONNX export. + It combines the prompt encoder, mask decoder, and mask postprocessing of Sam, + with some functions modified to enable model tracing. Also supports extra + options controlling what information. See the ONNX export script for details. + """ + + def __init__( + self, + model: Sam, + return_single_mask: bool, + use_stability_score: bool = False, + return_extra_metrics: bool = False, + ) -> None: + super().__init__() + self.mask_decoder = model.mask_decoder + self.model = model + self.img_size = model.image_encoder.img_size + self.return_single_mask = return_single_mask + self.use_stability_score = use_stability_score + self.stability_score_offset = 1.0 + self.return_extra_metrics = return_extra_metrics + + @staticmethod + def resize_longest_image_size( + input_image_size: torch.Tensor, longest_side: int + ) -> torch.Tensor: + input_image_size = input_image_size.to(torch.float32) + scale = longest_side / torch.max(input_image_size) + transformed_size = scale * input_image_size + transformed_size = torch.floor(transformed_size + 0.5).to(torch.int64) + return transformed_size + + def _embed_points(self, point_coords: torch.Tensor, point_labels: torch.Tensor) -> torch.Tensor: + point_coords = point_coords + 0.5 + point_coords = point_coords / self.img_size + point_embedding = self.model.prompt_encoder.pe_layer._pe_encoding(point_coords) + point_labels = point_labels.unsqueeze(-1).expand_as(point_embedding) + + point_embedding = point_embedding * (point_labels != -1) + point_embedding = point_embedding + self.model.prompt_encoder.not_a_point_embed.weight * ( + point_labels == -1 + ) + + for i in range(self.model.prompt_encoder.num_point_embeddings): + point_embedding = point_embedding + self.model.prompt_encoder.point_embeddings[ + i + ].weight * (point_labels == i) + + return point_embedding + + def _embed_masks(self, input_mask: torch.Tensor, has_mask_input: torch.Tensor) -> torch.Tensor: + mask_embedding = has_mask_input * self.model.prompt_encoder.mask_downscaling(input_mask) + mask_embedding = mask_embedding + ( + 1 - has_mask_input + ) * self.model.prompt_encoder.no_mask_embed.weight.reshape(1, -1, 1, 1) + return mask_embedding + + def mask_postprocessing(self, masks: torch.Tensor, orig_im_size: torch.Tensor) -> torch.Tensor: + masks = F.interpolate( + masks, + size=(self.img_size, self.img_size), + mode="bilinear", + align_corners=False, + ) + + prepadded_size = self.resize_longest_image_size(orig_im_size, self.img_size).to(torch.int64) + masks = masks[..., : prepadded_size[0], : prepadded_size[1]] # type: ignore + + orig_im_size = orig_im_size.to(torch.int64) + h, w = orig_im_size[0], orig_im_size[1] + masks = F.interpolate(masks, size=(h, w), mode="bilinear", align_corners=False) + return masks + + def select_masks( + self, masks: torch.Tensor, iou_preds: torch.Tensor, num_points: int + ) -> Tuple[torch.Tensor, torch.Tensor]: + # Determine if we should return the multiclick mask or not from the number of points. + # The reweighting is used to avoid control flow. + score_reweight = torch.tensor( + [[1000] + [0] * (self.model.mask_decoder.num_mask_tokens - 1)] + ).to(iou_preds.device) + score = iou_preds + (num_points - 2.5) * score_reweight + best_idx = torch.argmax(score, dim=1) + masks = masks[torch.arange(masks.shape[0]), best_idx, :, :].unsqueeze(1) + iou_preds = iou_preds[torch.arange(masks.shape[0]), best_idx].unsqueeze(1) + + return masks, iou_preds + + def forward( + self, + image_embeddings: torch.Tensor, + point_coords: torch.Tensor, + point_labels: torch.Tensor, + mask_input: torch.Tensor, + has_mask_input: torch.Tensor, + orig_im_size: torch.Tensor, + ): + sparse_embedding = self._embed_points(point_coords, point_labels) + dense_embedding = self._embed_masks(mask_input, has_mask_input) + + masks, scores = self.model.mask_decoder.predict_masks( + image_embeddings=image_embeddings, + image_pe=self.model.prompt_encoder.get_dense_pe(), + sparse_prompt_embeddings=sparse_embedding, + dense_prompt_embeddings=dense_embedding, + ) + + if self.use_stability_score: + scores = calculate_stability_score( + masks, self.model.mask_threshold, self.stability_score_offset + ) + + if self.return_single_mask: + masks, scores = self.select_masks(masks, scores, point_coords.shape[1]) + + upscaled_masks = self.mask_postprocessing(masks, orig_im_size) + + if self.return_extra_metrics: + stability_scores = calculate_stability_score( + upscaled_masks, self.model.mask_threshold, self.stability_score_offset + ) + areas = (upscaled_masks > self.model.mask_threshold).sum(-1).sum(-1) + return upscaled_masks, scores, stability_scores, areas, masks + + return upscaled_masks, scores, masks diff --git a/modules/control/proc/segment_anything/utils/transforms.py b/modules/control/proc/segment_anything/utils/transforms.py new file mode 100644 index 000000000..c08ba1e3d --- /dev/null +++ b/modules/control/proc/segment_anything/utils/transforms.py @@ -0,0 +1,102 @@ +# Copyright (c) Meta Platforms, Inc. and affiliates. +# All rights reserved. + +# This source code is licensed under the license found in the +# LICENSE file in the root directory of this source tree. + +import numpy as np +import torch +from torch.nn import functional as F +from torchvision.transforms.functional import resize, to_pil_image # type: ignore + +from copy import deepcopy +from typing import Tuple + + +class ResizeLongestSide: + """ + Resizes images to the longest side 'target_length', as well as provides + methods for resizing coordinates and boxes. Provides methods for + transforming both numpy array and batched torch tensors. + """ + + def __init__(self, target_length: int) -> None: + self.target_length = target_length + + def apply_image(self, image: np.ndarray) -> np.ndarray: + """ + Expects a numpy array with shape HxWxC in uint8 format. + """ + target_size = self.get_preprocess_shape(image.shape[0], image.shape[1], self.target_length) + return np.array(resize(to_pil_image(image), target_size)) + + def apply_coords(self, coords: np.ndarray, original_size: Tuple[int, ...]) -> np.ndarray: + """ + Expects a numpy array of length 2 in the final dimension. Requires the + original image size in (H, W) format. + """ + old_h, old_w = original_size + new_h, new_w = self.get_preprocess_shape( + original_size[0], original_size[1], self.target_length + ) + coords = deepcopy(coords).astype(float) + coords[..., 0] = coords[..., 0] * (new_w / old_w) + coords[..., 1] = coords[..., 1] * (new_h / old_h) + return coords + + def apply_boxes(self, boxes: np.ndarray, original_size: Tuple[int, ...]) -> np.ndarray: + """ + Expects a numpy array shape Bx4. Requires the original image size + in (H, W) format. + """ + boxes = self.apply_coords(boxes.reshape(-1, 2, 2), original_size) + return boxes.reshape(-1, 4) + + def apply_image_torch(self, image: torch.Tensor) -> torch.Tensor: + """ + Expects batched images with shape BxCxHxW and float format. This + transformation may not exactly match apply_image. apply_image is + the transformation expected by the model. + """ + # Expects an image in BCHW format. May not exactly match apply_image. + target_size = self.get_preprocess_shape(image.shape[2], image.shape[3], self.target_length) + return F.interpolate( + image, target_size, mode="bilinear", align_corners=False, antialias=True + ) + + def apply_coords_torch( + self, coords: torch.Tensor, original_size: Tuple[int, ...] + ) -> torch.Tensor: + """ + Expects a torch tensor with length 2 in the last dimension. Requires the + original image size in (H, W) format. + """ + old_h, old_w = original_size + new_h, new_w = self.get_preprocess_shape( + original_size[0], original_size[1], self.target_length + ) + coords = deepcopy(coords).to(torch.float) + coords[..., 0] = coords[..., 0] * (new_w / old_w) + coords[..., 1] = coords[..., 1] * (new_h / old_h) + return coords + + def apply_boxes_torch( + self, boxes: torch.Tensor, original_size: Tuple[int, ...] + ) -> torch.Tensor: + """ + Expects a torch tensor with shape Bx4. Requires the original image + size in (H, W) format. + """ + boxes = self.apply_coords_torch(boxes.reshape(-1, 2, 2), original_size) + return boxes.reshape(-1, 4) + + @staticmethod + def get_preprocess_shape(oldh: int, oldw: int, long_side_length: int) -> Tuple[int, int]: + """ + Compute the output size given input size and target long side length. + """ + scale = long_side_length * 1.0 / max(oldh, oldw) + newh, neww = oldh * scale, oldw * scale + neww = int(neww + 0.5) + newh = int(newh + 0.5) + return (newh, neww) diff --git a/modules/control/proc/shuffle.py b/modules/control/proc/shuffle.py new file mode 100644 index 000000000..3ee285857 --- /dev/null +++ b/modules/control/proc/shuffle.py @@ -0,0 +1,100 @@ +import warnings +import random +import cv2 +import numpy as np +from PIL import Image + +from modules.control.util import HWC3, img2mask, make_noise_disk, resize_image + + +class ContentShuffleDetector: + def __call__(self, input_image, h=None, w=None, f=None, detect_resolution=512, image_resolution=512, output_type="pil", **kwargs): + if "return_pil" in kwargs: + warnings.warn("return_pil is deprecated. Use output_type instead.", DeprecationWarning) + output_type = "pil" if kwargs["return_pil"] else "np" + if type(output_type) is bool: + warnings.warn("Passing `True` or `False` to `output_type` is deprecated and will raise an error in future versions") + if output_type: + output_type = "pil" + + if not isinstance(input_image, np.ndarray): + input_image = np.array(input_image, dtype=np.uint8) + + input_image = HWC3(input_image) + input_image = resize_image(input_image, detect_resolution) + + H, W, _C = input_image.shape + if h is None: + h = H + if w is None: + w = W + if f is None: + f = 256 + x = make_noise_disk(h, w, 1, f) * float(W - 1) + y = make_noise_disk(h, w, 1, f) * float(H - 1) + flow = np.concatenate([x, y], axis=2).astype(np.float32) + detected_map = cv2.remap(input_image, flow, None, cv2.INTER_LINEAR) + + img = resize_image(input_image, image_resolution) + H, W, _C = img.shape + + detected_map = cv2.resize(detected_map, (W, H), interpolation=cv2.INTER_LINEAR) + + if output_type == "pil": + detected_map = Image.fromarray(detected_map) + + return detected_map + + +class ColorShuffleDetector: + def __call__(self, img): + H, W, C = img.shape + F = np.random.randint(64, 384) # noqa + A = make_noise_disk(H, W, 3, F) + B = make_noise_disk(H, W, 3, F) + C = (A + B) / 2.0 + A = (C + (A - C) * 3.0).clip(0, 1) + B = (C + (B - C) * 3.0).clip(0, 1) + L = img.astype(np.float32) / 255.0 + Y = A * L + B * (1 - L) + Y -= np.min(Y, axis=(0, 1), keepdims=True) + Y /= np.maximum(np.max(Y, axis=(0, 1), keepdims=True), 1e-5) + Y *= 255.0 + return Y.clip(0, 255).astype(np.uint8) + + +class GrayDetector: + def __call__(self, img): + eps = 1e-5 + X = img.astype(np.float32) + r, g, b = X[:, :, 0], X[:, :, 1], X[:, :, 2] + kr, kg, kb = [random.random() + eps for _ in range(3)] + ks = kr + kg + kb + kr /= ks + kg /= ks + kb /= ks + Y = r * kr + g * kg + b * kb + Y = np.stack([Y] * 3, axis=2) + return Y.clip(0, 255).astype(np.uint8) + + +class DownSampleDetector: + def __call__(self, img, level=3, k=16.0): + h = img.astype(np.float32) + for _ in range(level): + h += np.random.normal(loc=0.0, scale=k, size=h.shape) # noqa + h = cv2.pyrDown(h) + for _ in range(level): + h = cv2.pyrUp(h) + h += np.random.normal(loc=0.0, scale=k, size=h.shape) # noqa + return h.clip(0, 255).astype(np.uint8) + + +class Image2MaskShuffleDetector: + def __init__(self, resolution=(640, 512)): + self.H, self.W = resolution + + def __call__(self, img): + m = img2mask(img, self.H, self.W) + m *= 255.0 + return m.clip(0, 255).astype(np.uint8) diff --git a/modules/control/proc/zoe/LICENSE b/modules/control/proc/zoe/LICENSE new file mode 100644 index 000000000..7a1e90d00 --- /dev/null +++ b/modules/control/proc/zoe/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2022 Intelligent Systems Lab Org + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. \ No newline at end of file diff --git a/modules/control/proc/zoe/__init__.py b/modules/control/proc/zoe/__init__.py new file mode 100644 index 000000000..e140496a4 --- /dev/null +++ b/modules/control/proc/zoe/__init__.py @@ -0,0 +1,97 @@ +import os + +import cv2 +import numpy as np +import torch +from einops import rearrange +from huggingface_hub import hf_hub_download +from PIL import Image +import safetensors + +from modules.control.util import HWC3, resize_image +from .zoedepth.models.zoedepth.zoedepth_v1 import ZoeDepth +from .zoedepth.models.zoedepth_nk.zoedepth_nk_v1 import ZoeDepthNK +from .zoedepth.utils.config import get_config + + +class ZoeDetector: + def __init__(self, model): + self.model = model + + @classmethod + def from_pretrained(cls, pretrained_model_or_path, model_type="zoedepth", filename=None, cache_dir=None): + filename = filename or "ZoeD_M12_N.pt" + if os.path.isdir(pretrained_model_or_path): + model_path = os.path.join(pretrained_model_or_path, filename) + else: + model_path = hf_hub_download(pretrained_model_or_path, filename, cache_dir=cache_dir) + + if model_type == "zoedepth": + model_cls = ZoeDepth + elif model_type == "zoedepth_nk": + model_cls = ZoeDepthNK + else: + raise ValueError(f"ZoeDepth unknown model type {model_type}") + conf = get_config(model_type, "infer") + model = model_cls.build_from_config(conf) + # model.load_state_dict(torch.load(model_path, map_location=torch.device('cpu'))['model']) + if model_path.lower().endswith('.safetensors'): + model_dict = safetensors.torch.load_file(model_path, device='cpu') + else: + model_dict = torch.load(model_path, map_location=torch.device('cpu')) + if hasattr(model_dict, 'model'): + model_dict = model_dict['model'] + model.load_state_dict(model_dict, strict=False) + # timm compatibility issue + for b in model.core.core.pretrained.model.blocks: + b.drop_path = torch.nn.Identity() + model.eval() + return cls(model) + + def to(self, device): + self.model.to(device) + return self + + def __call__(self, input_image, detect_resolution=512, image_resolution=512, output_type=None, gamma_corrected=False): + device = next(iter(self.model.parameters())).device + if not isinstance(input_image, np.ndarray): + input_image = np.array(input_image, dtype=np.uint8) + output_type = output_type or "pil" + else: + output_type = output_type or "np" + + input_image = HWC3(input_image) + input_image = resize_image(input_image, detect_resolution) + + assert input_image.ndim == 3 + image_depth = input_image + image_depth = torch.from_numpy(image_depth).float().to(device) + image_depth = image_depth / 255.0 + image_depth = rearrange(image_depth, 'h w c -> 1 c h w') + depth = self.model.infer(image_depth) + + depth = depth[0, 0].cpu().numpy() + + vmin = np.percentile(depth, 2) + vmax = np.percentile(depth, 85) + + depth -= vmin + depth /= vmax - vmin + depth = 1.0 - depth + + if gamma_corrected: + depth = np.power(depth, 2.2) + depth_image = (depth * 255.0).clip(0, 255).astype(np.uint8) + + detected_map = depth_image + detected_map = HWC3(detected_map) + + img = resize_image(input_image, image_resolution) + H, W, _C = img.shape + + detected_map = cv2.resize(detected_map, (W, H), interpolation=cv2.INTER_LINEAR) + + if output_type == "pil": + detected_map = Image.fromarray(detected_map) + + return detected_map diff --git a/modules/control/proc/zoe/zoedepth/__init__.py b/modules/control/proc/zoe/zoedepth/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/modules/control/proc/zoe/zoedepth/models/__init__.py b/modules/control/proc/zoe/zoedepth/models/__init__.py new file mode 100644 index 000000000..5f2668792 --- /dev/null +++ b/modules/control/proc/zoe/zoedepth/models/__init__.py @@ -0,0 +1,24 @@ +# MIT License + +# Copyright (c) 2022 Intelligent Systems Lab Org + +# Permission is hereby granted, free of charge, to any person obtaining a copy +# of this software and associated documentation files (the "Software"), to deal +# in the Software without restriction, including without limitation the rights +# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +# copies of the Software, and to permit persons to whom the Software is +# furnished to do so, subject to the following conditions: + +# The above copyright notice and this permission notice shall be included in all +# copies or substantial portions of the Software. + +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +# SOFTWARE. + +# File author: Shariq Farooq Bhat + diff --git a/modules/control/proc/zoe/zoedepth/models/base_models/__init__.py b/modules/control/proc/zoe/zoedepth/models/base_models/__init__.py new file mode 100644 index 000000000..5f2668792 --- /dev/null +++ b/modules/control/proc/zoe/zoedepth/models/base_models/__init__.py @@ -0,0 +1,24 @@ +# MIT License + +# Copyright (c) 2022 Intelligent Systems Lab Org + +# Permission is hereby granted, free of charge, to any person obtaining a copy +# of this software and associated documentation files (the "Software"), to deal +# in the Software without restriction, including without limitation the rights +# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +# copies of the Software, and to permit persons to whom the Software is +# furnished to do so, subject to the following conditions: + +# The above copyright notice and this permission notice shall be included in all +# copies or substantial portions of the Software. + +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +# SOFTWARE. + +# File author: Shariq Farooq Bhat + diff --git a/modules/control/proc/zoe/zoedepth/models/base_models/midas.py b/modules/control/proc/zoe/zoedepth/models/base_models/midas.py new file mode 100644 index 000000000..683bd0329 --- /dev/null +++ b/modules/control/proc/zoe/zoedepth/models/base_models/midas.py @@ -0,0 +1,378 @@ +# MIT License +import os + +# Copyright (c) 2022 Intelligent Systems Lab Org + +# Permission is hereby granted, free of charge, to any person obtaining a copy +# of this software and associated documentation files (the "Software"), to deal +# in the Software without restriction, including without limitation the rights +# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +# copies of the Software, and to permit persons to whom the Software is +# furnished to do so, subject to the following conditions: + +# The above copyright notice and this permission notice shall be included in all +# copies or substantial portions of the Software. + +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +# SOFTWARE. + +# File author: Shariq Farooq Bhat + +import torch +import torch.nn as nn +import numpy as np +from torchvision.transforms import Normalize + + +def denormalize(x): + """Reverses the imagenet normalization applied to the input. + + Args: + x (torch.Tensor - shape(N,3,H,W)): input tensor + + Returns: + torch.Tensor - shape(N,3,H,W): Denormalized input + """ + mean = torch.Tensor([0.485, 0.456, 0.406]).view(1, 3, 1, 1).to(x.device) + std = torch.Tensor([0.229, 0.224, 0.225]).view(1, 3, 1, 1).to(x.device) + return x * std + mean + +def get_activation(name, bank): + def hook(model, input, output): + bank[name] = output + return hook + + +class Resize(object): + """Resize sample to given size (width, height). + """ + + def __init__( + self, + width, + height, + resize_target=True, + keep_aspect_ratio=False, + ensure_multiple_of=1, + resize_method="lower_bound", + ): + """Init. + Args: + width (int): desired output width + height (int): desired output height + resize_target (bool, optional): + True: Resize the full sample (image, mask, target). + False: Resize image only. + Defaults to True. + keep_aspect_ratio (bool, optional): + True: Keep the aspect ratio of the input sample. + Output sample might not have the given width and height, and + resize behaviour depends on the parameter 'resize_method'. + Defaults to False. + ensure_multiple_of (int, optional): + Output width and height is constrained to be multiple of this parameter. + Defaults to 1. + resize_method (str, optional): + "lower_bound": Output will be at least as large as the given size. + "upper_bound": Output will be at max as large as the given size. (Output size might be smaller than given size.) + "minimal": Scale as least as possible. (Output size might be smaller than given size.) + Defaults to "lower_bound". + """ + # print("Params passed to Resize transform:") + # print("\twidth: ", width) + # print("\theight: ", height) + # print("\tresize_target: ", resize_target) + # print("\tkeep_aspect_ratio: ", keep_aspect_ratio) + # print("\tensure_multiple_of: ", ensure_multiple_of) + # print("\tresize_method: ", resize_method) + + self.__width = width + self.__height = height + + self.__keep_aspect_ratio = keep_aspect_ratio + self.__multiple_of = ensure_multiple_of + self.__resize_method = resize_method + + def constrain_to_multiple_of(self, x, min_val=0, max_val=None): + y = (np.round(x / self.__multiple_of) * self.__multiple_of).astype(int) + + if max_val is not None and y > max_val: + y = (np.floor(x / self.__multiple_of) + * self.__multiple_of).astype(int) + + if y < min_val: + y = (np.ceil(x / self.__multiple_of) + * self.__multiple_of).astype(int) + + return y + + def get_size(self, width, height): + # determine new height and width + scale_height = self.__height / height + scale_width = self.__width / width + + if self.__keep_aspect_ratio: + if self.__resize_method == "lower_bound": + # scale such that output size is lower bound + if scale_width > scale_height: + # fit width + scale_height = scale_width + else: + # fit height + scale_width = scale_height + elif self.__resize_method == "upper_bound": + # scale such that output size is upper bound + if scale_width < scale_height: + # fit width + scale_height = scale_width + else: + # fit height + scale_width = scale_height + elif self.__resize_method == "minimal": + # scale as least as possbile + if abs(1 - scale_width) < abs(1 - scale_height): + # fit width + scale_height = scale_width + else: + # fit height + scale_width = scale_height + else: + raise ValueError( + f"resize_method {self.__resize_method} not implemented" + ) + + if self.__resize_method == "lower_bound": + new_height = self.constrain_to_multiple_of( + scale_height * height, min_val=self.__height + ) + new_width = self.constrain_to_multiple_of( + scale_width * width, min_val=self.__width + ) + elif self.__resize_method == "upper_bound": + new_height = self.constrain_to_multiple_of( + scale_height * height, max_val=self.__height + ) + new_width = self.constrain_to_multiple_of( + scale_width * width, max_val=self.__width + ) + elif self.__resize_method == "minimal": + new_height = self.constrain_to_multiple_of(scale_height * height) + new_width = self.constrain_to_multiple_of(scale_width * width) + else: + raise ValueError( + f"resize_method {self.__resize_method} not implemented") + + return (new_width, new_height) + + def __call__(self, x): + width, height = self.get_size(*x.shape[-2:][::-1]) + return nn.functional.interpolate(x, (int(width), int(height)), mode='bilinear', align_corners=True) + +class PrepForMidas(object): + def __init__(self, resize_mode="minimal", keep_aspect_ratio=True, img_size=384, do_resize=True): + if isinstance(img_size, int): + img_size = (img_size, img_size) + net_h, net_w = img_size + self.normalization = Normalize( + mean=[0.5, 0.5, 0.5], std=[0.5, 0.5, 0.5]) + self.resizer = Resize(net_w, net_h, keep_aspect_ratio=keep_aspect_ratio, ensure_multiple_of=32, resize_method=resize_mode) \ + if do_resize else nn.Identity() + + def __call__(self, x): + return self.normalization(self.resizer(x)) + + +class MidasCore(nn.Module): + def __init__(self, midas, trainable=False, fetch_features=True, layer_names=('out_conv', 'l4_rn', 'r4', 'r3', 'r2', 'r1'), freeze_bn=False, keep_aspect_ratio=True, + img_size=384, **kwargs): + """Midas Base model used for multi-scale feature extraction. + + Args: + midas (torch.nn.Module): Midas model. + trainable (bool, optional): Train midas model. Defaults to False. + fetch_features (bool, optional): Extract multi-scale features. Defaults to True. + layer_names (tuple, optional): Layers used for feature extraction. Order = (head output features, last layer features, ...decoder features). Defaults to ('out_conv', 'l4_rn', 'r4', 'r3', 'r2', 'r1'). + freeze_bn (bool, optional): Freeze BatchNorm. Generally results in better finetuning performance. Defaults to False. + keep_aspect_ratio (bool, optional): Keep the aspect ratio of input images while resizing. Defaults to True. + img_size (int, tuple, optional): Input resolution. Defaults to 384. + """ + super().__init__() + self.core = midas + self.output_channels = None + self.core_out = {} + self.trainable = trainable + self.fetch_features = fetch_features + # midas.scratch.output_conv = nn.Identity() + self.handles = [] + # self.layer_names = ['out_conv','l4_rn', 'r4', 'r3', 'r2', 'r1'] + self.layer_names = layer_names + + self.set_trainable(trainable) + self.set_fetch_features(fetch_features) + + self.prep = PrepForMidas(keep_aspect_ratio=keep_aspect_ratio, + img_size=img_size, do_resize=kwargs.get('do_resize', True)) + + if freeze_bn: + self.freeze_bn() + + def set_trainable(self, trainable): + self.trainable = trainable + if trainable: + self.unfreeze() + else: + self.freeze() + return self + + def set_fetch_features(self, fetch_features): + self.fetch_features = fetch_features + if fetch_features: + if len(self.handles) == 0: + self.attach_hooks(self.core) + else: + self.remove_hooks() + return self + + def freeze(self): + for p in self.parameters(): + p.requires_grad = False + self.trainable = False + return self + + def unfreeze(self): + for p in self.parameters(): + p.requires_grad = True + self.trainable = True + return self + + def freeze_bn(self): + for m in self.modules(): + if isinstance(m, nn.BatchNorm2d): + m.eval() + return self + + def forward(self, x, denorm=False, return_rel_depth=False): + if denorm: + x = denormalize(x) + x = self.prep(x) + # print("Shape after prep: ", x.shape) + + with torch.set_grad_enabled(self.trainable): + + # print("Input size to Midascore", x.shape) + rel_depth = self.core(x) + # print("Output from midas shape", rel_depth.shape) + if not self.fetch_features: + return rel_depth + out = [self.core_out[k] for k in self.layer_names] + + if return_rel_depth: + return rel_depth, out + return out + + def get_rel_pos_params(self): + for name, p in self.core.pretrained.named_parameters(): + if "relative_position" in name: + yield p + + def get_enc_params_except_rel_pos(self): + for name, p in self.core.pretrained.named_parameters(): + if "relative_position" not in name: + yield p + + def freeze_encoder(self, freeze_rel_pos=False): + if freeze_rel_pos: + for p in self.core.pretrained.parameters(): + p.requires_grad = False + else: + for p in self.get_enc_params_except_rel_pos(): + p.requires_grad = False + return self + + def attach_hooks(self, midas): + if len(self.handles) > 0: + self.remove_hooks() + if "out_conv" in self.layer_names: + self.handles.append(list(midas.scratch.output_conv.children())[ + 3].register_forward_hook(get_activation("out_conv", self.core_out))) + if "r4" in self.layer_names: + self.handles.append(midas.scratch.refinenet4.register_forward_hook( + get_activation("r4", self.core_out))) + if "r3" in self.layer_names: + self.handles.append(midas.scratch.refinenet3.register_forward_hook( + get_activation("r3", self.core_out))) + if "r2" in self.layer_names: + self.handles.append(midas.scratch.refinenet2.register_forward_hook( + get_activation("r2", self.core_out))) + if "r1" in self.layer_names: + self.handles.append(midas.scratch.refinenet1.register_forward_hook( + get_activation("r1", self.core_out))) + if "l4_rn" in self.layer_names: + self.handles.append(midas.scratch.layer4_rn.register_forward_hook( + get_activation("l4_rn", self.core_out))) + + return self + + def remove_hooks(self): + for h in self.handles: + h.remove() + return self + + def __del__(self): + self.remove_hooks() + + def set_output_channels(self, model_type): + self.output_channels = MIDAS_SETTINGS[model_type] + + @staticmethod + def build(midas_model_type="DPT_BEiT_L_384", train_midas=False, use_pretrained_midas=True, fetch_features=False, freeze_bn=True, force_keep_ar=False, force_reload=False, **kwargs): + if midas_model_type not in MIDAS_SETTINGS: + raise ValueError( + f"Invalid model type: {midas_model_type}. Must be one of {list(MIDAS_SETTINGS.keys())}") + if "img_size" in kwargs: + kwargs = MidasCore.parse_img_size(kwargs) + img_size = kwargs.pop("img_size", [384, 384]) + # print("img_size", img_size) + midas_path = os.path.join(os.path.dirname(__file__), 'midas_repo') + midas = torch.hub.load(midas_path, midas_model_type, + pretrained=use_pretrained_midas, force_reload=force_reload, source='local') + kwargs.update({'keep_aspect_ratio': force_keep_ar}) + midas_core = MidasCore(midas, trainable=train_midas, fetch_features=fetch_features, + freeze_bn=freeze_bn, img_size=img_size, **kwargs) + midas_core.set_output_channels(midas_model_type) + return midas_core + + @staticmethod + def build_from_config(config): + return MidasCore.build(**config) + + @staticmethod + def parse_img_size(config): + assert 'img_size' in config + if isinstance(config['img_size'], str): + assert "," in config['img_size'], "img_size should be a string with comma separated img_size=H,W" + config['img_size'] = list(map(int, config['img_size'].split(","))) + assert len( + config['img_size']) == 2, "img_size should be a string with comma separated img_size=H,W" + elif isinstance(config['img_size'], int): + config['img_size'] = [config['img_size'], config['img_size']] + else: + assert isinstance(config['img_size'], list) and len( + config['img_size']) == 2, "img_size should be a list of H,W" + return config + + +nchannels2models = { + tuple([256]*5): ["DPT_BEiT_L_384", "DPT_BEiT_L_512", "DPT_BEiT_B_384", "DPT_SwinV2_L_384", "DPT_SwinV2_B_384", "DPT_SwinV2_T_256", "DPT_Large", "DPT_Hybrid"], + (512, 256, 128, 64, 64): ["MiDaS_small"] +} + +# Model name to number of output channels +MIDAS_SETTINGS = {m: k for k, v in nchannels2models.items() + for m in v + } diff --git a/modules/control/proc/zoe/zoedepth/models/base_models/midas_repo/LICENSE b/modules/control/proc/zoe/zoedepth/models/base_models/midas_repo/LICENSE new file mode 100644 index 000000000..277b5c11b --- /dev/null +++ b/modules/control/proc/zoe/zoedepth/models/base_models/midas_repo/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2019 Intel ISL (Intel Intelligent Systems Lab) + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/modules/control/proc/zoe/zoedepth/models/base_models/midas_repo/README.md b/modules/control/proc/zoe/zoedepth/models/base_models/midas_repo/README.md new file mode 100644 index 000000000..9568ea71c --- /dev/null +++ b/modules/control/proc/zoe/zoedepth/models/base_models/midas_repo/README.md @@ -0,0 +1,259 @@ +## Towards Robust Monocular Depth Estimation: Mixing Datasets for Zero-shot Cross-dataset Transfer + +This repository contains code to compute depth from a single image. It accompanies our [paper](https://arxiv.org/abs/1907.01341v3): + +>Towards Robust Monocular Depth Estimation: Mixing Datasets for Zero-shot Cross-dataset Transfer +René Ranftl, Katrin Lasinger, David Hafner, Konrad Schindler, Vladlen Koltun + + +and our [preprint](https://arxiv.org/abs/2103.13413): + +> Vision Transformers for Dense Prediction +> René Ranftl, Alexey Bochkovskiy, Vladlen Koltun + + +MiDaS was trained on up to 12 datasets (ReDWeb, DIML, Movies, MegaDepth, WSVD, TartanAir, HRWSI, ApolloScape, BlendedMVS, IRS, KITTI, NYU Depth V2) with +multi-objective optimization. +The original model that was trained on 5 datasets (`MIX 5` in the paper) can be found [here](https://github.com/isl-org/MiDaS/releases/tag/v2). +The figure below shows an overview of the different MiDaS models; the bubble size scales with number of parameters. + +![](figures/Improvement_vs_FPS.png) + +### Setup + +1) Pick one or more models and download the corresponding weights to the `weights` folder: + +MiDaS 3.1 +- For highest quality: [dpt_beit_large_512](https://github.com/isl-org/MiDaS/releases/download/v3_1/dpt_beit_large_512.pt) +- For moderately less quality, but better speed-performance trade-off: [dpt_swin2_large_384](https://github.com/isl-org/MiDaS/releases/download/v3_1/dpt_swin2_large_384.pt) +- For embedded devices: [dpt_swin2_tiny_256](https://github.com/isl-org/MiDaS/releases/download/v3_1/dpt_swin2_tiny_256.pt), [dpt_levit_224](https://github.com/isl-org/MiDaS/releases/download/v3_1/dpt_levit_224.pt) +- For inference on Intel CPUs, OpenVINO may be used for the small legacy model: openvino_midas_v21_small [.xml](https://github.com/isl-org/MiDaS/releases/download/v3_1/openvino_midas_v21_small_256.xml), [.bin](https://github.com/isl-org/MiDaS/releases/download/v3_1/openvino_midas_v21_small_256.bin) + +MiDaS 3.0: Legacy transformer models [dpt_large_384](https://github.com/isl-org/MiDaS/releases/download/v3/dpt_large_384.pt) and [dpt_hybrid_384](https://github.com/isl-org/MiDaS/releases/download/v3/dpt_hybrid_384.pt) + +MiDaS 2.1: Legacy convolutional models [midas_v21_384](https://github.com/isl-org/MiDaS/releases/download/v2_1/midas_v21_384.pt) and [midas_v21_small_256](https://github.com/isl-org/MiDaS/releases/download/v2_1/midas_v21_small_256.pt) + +1) Set up dependencies: + + ```shell + conda env create -f environment.yaml + conda activate midas-py310 + ``` + +#### optional + +For the Next-ViT model, execute + +```shell +git submodule add https://github.com/isl-org/Next-ViT midas/external/next_vit +``` + +For the OpenVINO model, install + +```shell +pip install openvino +``` + +### Usage + +1) Place one or more input images in the folder `input`. + +2) Run the model with + + ```shell + python run.py --model_type --input_path input --output_path output + ``` + where `````` is chosen from [dpt_beit_large_512](#model_type), [dpt_beit_large_384](#model_type), + [dpt_beit_base_384](#model_type), [dpt_swin2_large_384](#model_type), [dpt_swin2_base_384](#model_type), + [dpt_swin2_tiny_256](#model_type), [dpt_swin_large_384](#model_type), [dpt_next_vit_large_384](#model_type), + [dpt_levit_224](#model_type), [dpt_large_384](#model_type), [dpt_hybrid_384](#model_type), + [midas_v21_384](#model_type), [midas_v21_small_256](#model_type), [openvino_midas_v21_small_256](#model_type). + +3) The resulting depth maps are written to the `output` folder. + +#### optional + +1) By default, the inference resizes the height of input images to the size of a model to fit into the encoder. This + size is given by the numbers in the model names of the [accuracy table](#accuracy). Some models do not only support a single + inference height but a range of different heights. Feel free to explore different heights by appending the extra + command line argument `--height`. Unsupported height values will throw an error. Note that using this argument may + decrease the model accuracy. +2) By default, the inference keeps the aspect ratio of input images when feeding them into the encoder if this is + supported by a model (all models except for Swin, Swin2, LeViT). In order to resize to a square resolution, + disregarding the aspect ratio while preserving the height, use the command line argument `--square`. + +#### via Camera + + If you want the input images to be grabbed from the camera and shown in a window, leave the input and output paths + away and choose a model type as shown above: + + ```shell + python run.py --model_type --side + ``` + + The argument `--side` is optional and causes both the input RGB image and the output depth map to be shown + side-by-side for comparison. + +#### via Docker + +1) Make sure you have installed Docker and the + [NVIDIA Docker runtime](https://github.com/NVIDIA/nvidia-docker/wiki/Installation-\(Native-GPU-Support\)). + +2) Build the Docker image: + + ```shell + docker build -t midas . + ``` + +3) Run inference: + + ```shell + docker run --rm --gpus all -v $PWD/input:/opt/MiDaS/input -v $PWD/output:/opt/MiDaS/output -v $PWD/weights:/opt/MiDaS/weights midas + ``` + + This command passes through all of your NVIDIA GPUs to the container, mounts the + `input` and `output` directories and then runs the inference. + +#### via PyTorch Hub + +The pretrained model is also available on [PyTorch Hub](https://pytorch.org/hub/intelisl_midas_v2/) + +#### via TensorFlow or ONNX + +See [README](https://github.com/isl-org/MiDaS/tree/master/tf) in the `tf` subdirectory. + +Currently only supports MiDaS v2.1. + + +#### via Mobile (iOS / Android) + +See [README](https://github.com/isl-org/MiDaS/tree/master/mobile) in the `mobile` subdirectory. + +#### via ROS1 (Robot Operating System) + +See [README](https://github.com/isl-org/MiDaS/tree/master/ros) in the `ros` subdirectory. + +Currently only supports MiDaS v2.1. DPT-based models to be added. + + +### Accuracy + +We provide a **zero-shot error** $\epsilon_d$ which is evaluated for 6 different datasets +(see [paper](https://arxiv.org/abs/1907.01341v3)). **Lower error values are better**. +$\color{green}{\textsf{Overall model quality is represented by the improvement}}$ ([Imp.](#improvement)) with respect to +MiDaS 3.0 DPTL-384. The models are grouped by the height used for inference, whereas the square training resolution is given by +the numbers in the model names. The table also shows the **number of parameters** (in millions) and the +**frames per second** for inference at the training resolution (for GPU RTX 3090): + +| MiDaS Model | DIW
WHDR | Eth3d
AbsRel | Sintel
AbsRel | TUM
δ1 | KITTI
δ1 | NYUv2
δ1 | $\color{green}{\textsf{Imp.}}$
% | Par.
M | FPS
  | +|-----------------------------------------------------------------------------------------------------------------------|-------------------------:|-----------------------------:|------------------------------:|-------------------------:|-------------------------:|-------------------------:|-------------------------------------------------:|----------------------:|--------------------------:| +| **Inference height 512** | | | | | | | | | | +| [v3.1 BEiTL-512](https://github.com/isl-org/MiDaS/releases/download/v3_1/dpt_beit_large_512.pt) | 0.1137 | 0.0659 | 0.2366 | **6.13** | 11.56* | **1.86*** | $\color{green}{\textsf{19}}$ | **345** | **5.7** | +| [v3.1 BEiTL-512](https://github.com/isl-org/MiDaS/releases/download/v3_1/dpt_beit_large_512.pt)$\tiny{\square}$ | **0.1121** | **0.0614** | **0.2090** | 6.46 | **5.00*** | 1.90* | $\color{green}{\textsf{34}}$ | **345** | **5.7** | +| | | | | | | | | | | +| **Inference height 384** | | | | | | | | | | +| [v3.1 BEiTL-512](https://github.com/isl-org/MiDaS/releases/download/v3_1/dpt_beit_large_512.pt) | 0.1245 | 0.0681 | **0.2176** | **6.13** | 6.28* | **2.16*** | $\color{green}{\textsf{28}}$ | 345 | 12 | +| [v3.1 Swin2L-384](https://github.com/isl-org/MiDaS/releases/download/v3_1/dpt_swin2_large_384.pt)$\tiny{\square}$ | 0.1106 | 0.0732 | 0.2442 | 8.87 | **5.84*** | 2.92* | $\color{green}{\textsf{22}}$ | 213 | 41 | +| [v3.1 Swin2B-384](https://github.com/isl-org/MiDaS/releases/download/v3_1/dpt_swin2_base_384.pt)$\tiny{\square}$ | 0.1095 | 0.0790 | 0.2404 | 8.93 | 5.97* | 3.28* | $\color{green}{\textsf{22}}$ | 102 | 39 | +| [v3.1 SwinL-384](https://github.com/isl-org/MiDaS/releases/download/v3_1/dpt_swin_large_384.pt)$\tiny{\square}$ | 0.1126 | 0.0853 | 0.2428 | 8.74 | 6.60* | 3.34* | $\color{green}{\textsf{17}}$ | 213 | 49 | +| [v3.1 BEiTL-384](https://github.com/isl-org/MiDaS/releases/download/v3_1/dpt_beit_large_384.pt) | 0.1239 | **0.0667** | 0.2545 | 7.17 | 9.84* | 2.21* | $\color{green}{\textsf{17}}$ | 344 | 13 | +| [v3.1 Next-ViTL-384](https://github.com/isl-org/MiDaS/releases/download/v3_1/dpt_next_vit_large_384.pt) | **0.1031** | 0.0954 | 0.2295 | 9.21 | 6.89* | 3.47* | $\color{green}{\textsf{16}}$ | **72** | 30 | +| [v3.1 BEiTB-384](https://github.com/isl-org/MiDaS/releases/download/v3_1/dpt_beit_base_384.pt) | 0.1159 | 0.0967 | 0.2901 | 9.88 | 26.60* | 3.91* | $\color{green}{\textsf{-31}}$ | 112 | 31 | +| [v3.0 DPTL-384](https://github.com/isl-org/MiDaS/releases/download/v3/dpt_large_384.pt) | 0.1082 | 0.0888 | 0.2697 | 9.97 | 8.46 | 8.32 | $\color{green}{\textsf{0}}$ | 344 | **61** | +| [v3.0 DPTH-384](https://github.com/isl-org/MiDaS/releases/download/v3/dpt_hybrid_384.pt) | 0.1106 | 0.0934 | 0.2741 | 10.89 | 11.56 | 8.69 | $\color{green}{\textsf{-10}}$ | 123 | 50 | +| [v2.1 Large384](https://github.com/isl-org/MiDaS/releases/download/v2_1/midas_v21_384.pt) | 0.1295 | 0.1155 | 0.3285 | 12.51 | 16.08 | 8.71 | $\color{green}{\textsf{-32}}$ | 105 | 47 | +| | | | | | | | | | | +| **Inference height 256** | | | | | | | | | | +| [v3.1 Swin2T-256](https://github.com/isl-org/MiDaS/releases/download/v3_1/dpt_swin2_tiny_256.pt)$\tiny{\square}$ | **0.1211** | **0.1106** | **0.2868** | **13.43** | **10.13*** | **5.55*** | $\color{green}{\textsf{-11}}$ | 42 | 64 | +| [v2.1 Small256](https://github.com/isl-org/MiDaS/releases/download/v2_1/midas_v21_small_256.pt) | 0.1344 | 0.1344 | 0.3370 | 14.53 | 29.27 | 13.43 | $\color{green}{\textsf{-76}}$ | **21** | **90** | +| | | | | | | | | | | +| **Inference height 224** | | | | | | | | | | +| [v3.1 LeViT224](https://github.com/isl-org/MiDaS/releases/download/v3_1/dpt_levit_224.pt)$\tiny{\square}$ | **0.1314** | **0.1206** | **0.3148** | **18.21** | **15.27*** | **8.64*** | $\color{green}{\textsf{-40}}$ | **51** | **73** | + +* No zero-shot error, because models are also trained on KITTI and NYU Depth V2\ +$\square$ Validation performed at **square resolution**, either because the transformer encoder backbone of a model +does not support non-square resolutions (Swin, Swin2, LeViT) or for comparison with these models. All other +validations keep the aspect ratio. A difference in resolution limits the comparability of the zero-shot error and the +improvement, because these quantities are averages over the pixels of an image and do not take into account the +advantage of more details due to a higher resolution.\ +Best values per column and same validation height in bold + +#### Improvement + +The improvement in the above table is defined as the relative zero-shot error with respect to MiDaS v3.0 +DPTL-384 and averaging over the datasets. So, if $\epsilon_d$ is the zero-shot error for dataset $d$, then +the $\color{green}{\textsf{improvement}}$ is given by $100(1-(1/6)\sum_d\epsilon_d/\epsilon_{d,\rm{DPT_{L-384}}})$%. + +Note that the improvements of 10% for MiDaS v2.0 → v2.1 and 21% for MiDaS v2.1 → v3.0 are not visible from the +improvement column (Imp.) in the table but would require an evaluation with respect to MiDaS v2.1 Large384 +and v2.0 Large384 respectively instead of v3.0 DPTL-384. + +### Depth map comparison + +Zoom in for better visibility +![](figures/Comparison.png) + +### Speed on Camera Feed + +Test configuration +- Windows 10 +- 11th Gen Intel Core i7-1185G7 3.00GHz +- 16GB RAM +- Camera resolution 640x480 +- openvino_midas_v21_small_256 + +Speed: 22 FPS + +### Changelog + +* [Dec 2022] Released MiDaS v3.1: + - New models based on 5 different types of transformers ([BEiT](https://arxiv.org/pdf/2106.08254.pdf), [Swin2](https://arxiv.org/pdf/2111.09883.pdf), [Swin](https://arxiv.org/pdf/2103.14030.pdf), [Next-ViT](https://arxiv.org/pdf/2207.05501.pdf), [LeViT](https://arxiv.org/pdf/2104.01136.pdf)) + - Training datasets extended from 10 to 12, including also KITTI and NYU Depth V2 using [BTS](https://github.com/cleinc/bts) split + - Best model, BEiTLarge 512, with resolution 512x512, is on average about [28% more accurate](#Accuracy) than MiDaS v3.0 + - Integrated live depth estimation from camera feed +* [Sep 2021] Integrated to [Huggingface Spaces](https://huggingface.co/spaces) with [Gradio](https://github.com/gradio-app/gradio). See [Gradio Web Demo](https://huggingface.co/spaces/akhaliq/DPT-Large). +* [Apr 2021] Released MiDaS v3.0: + - New models based on [Dense Prediction Transformers](https://arxiv.org/abs/2103.13413) are on average [21% more accurate](#Accuracy) than MiDaS v2.1 + - Additional models can be found [here](https://github.com/isl-org/DPT) +* [Nov 2020] Released MiDaS v2.1: + - New model that was trained on 10 datasets and is on average about [10% more accurate](#Accuracy) than [MiDaS v2.0](https://github.com/isl-org/MiDaS/releases/tag/v2) + - New light-weight model that achieves [real-time performance](https://github.com/isl-org/MiDaS/tree/master/mobile) on mobile platforms. + - Sample applications for [iOS](https://github.com/isl-org/MiDaS/tree/master/mobile/ios) and [Android](https://github.com/isl-org/MiDaS/tree/master/mobile/android) + - [ROS package](https://github.com/isl-org/MiDaS/tree/master/ros) for easy deployment on robots +* [Jul 2020] Added TensorFlow and ONNX code. Added [online demo](http://35.202.76.57/). +* [Dec 2019] Released new version of MiDaS - the new model is significantly more accurate and robust +* [Jul 2019] Initial release of MiDaS ([Link](https://github.com/isl-org/MiDaS/releases/tag/v1)) + +### Citation + +Please cite our paper if you use this code or any of the models: +``` +@ARTICLE {Ranftl2022, + author = "Ren\'{e} Ranftl and Katrin Lasinger and David Hafner and Konrad Schindler and Vladlen Koltun", + title = "Towards Robust Monocular Depth Estimation: Mixing Datasets for Zero-Shot Cross-Dataset Transfer", + journal = "IEEE Transactions on Pattern Analysis and Machine Intelligence", + year = "2022", + volume = "44", + number = "3" +} +``` + +If you use a DPT-based model, please also cite: + +``` +@article{Ranftl2021, + author = {Ren\'{e} Ranftl and Alexey Bochkovskiy and Vladlen Koltun}, + title = {Vision Transformers for Dense Prediction}, + journal = {ICCV}, + year = {2021}, +} +``` + +### Acknowledgements + +Our work builds on and uses code from [timm](https://github.com/rwightman/pytorch-image-models) and [Next-ViT](https://github.com/bytedance/Next-ViT). +We'd like to thank the authors for making these libraries available. + +### License + +MIT License diff --git a/modules/control/proc/zoe/zoedepth/models/base_models/midas_repo/__init__.py b/modules/control/proc/zoe/zoedepth/models/base_models/midas_repo/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/modules/control/proc/zoe/zoedepth/models/base_models/midas_repo/hubconf.py b/modules/control/proc/zoe/zoedepth/models/base_models/midas_repo/hubconf.py new file mode 100644 index 000000000..43291563d --- /dev/null +++ b/modules/control/proc/zoe/zoedepth/models/base_models/midas_repo/hubconf.py @@ -0,0 +1,435 @@ +dependencies = ["torch"] + +import torch + +from midas.dpt_depth import DPTDepthModel +from midas.midas_net import MidasNet +from midas.midas_net_custom import MidasNet_small + +def DPT_BEiT_L_512(pretrained=True, **kwargs): + """ # This docstring shows up in hub.help() + MiDaS DPT_BEiT_L_512 model for monocular depth estimation + pretrained (bool): load pretrained weights into model + """ + + model = DPTDepthModel( + path=None, + backbone="beitl16_512", + non_negative=True, + ) + + if pretrained: + checkpoint = ( + "https://github.com/isl-org/MiDaS/releases/download/v3_1/dpt_beit_large_512.pt" + ) + state_dict = torch.hub.load_state_dict_from_url( + checkpoint, map_location=torch.device('cpu'), progress=True, check_hash=True + ) + model.load_state_dict(state_dict) + + return model + +def DPT_BEiT_L_384(pretrained=True, **kwargs): + """ # This docstring shows up in hub.help() + MiDaS DPT_BEiT_L_384 model for monocular depth estimation + pretrained (bool): load pretrained weights into model + """ + + model = DPTDepthModel( + path=None, + backbone="beitl16_384", + non_negative=True, + ) + + if pretrained: + checkpoint = ( + "https://github.com/isl-org/MiDaS/releases/download/v3_1/dpt_beit_large_384.pt" + ) + state_dict = torch.hub.load_state_dict_from_url( + checkpoint, map_location=torch.device('cpu'), progress=True, check_hash=True + ) + model.load_state_dict(state_dict) + + return model + +def DPT_BEiT_B_384(pretrained=True, **kwargs): + """ # This docstring shows up in hub.help() + MiDaS DPT_BEiT_B_384 model for monocular depth estimation + pretrained (bool): load pretrained weights into model + """ + + model = DPTDepthModel( + path=None, + backbone="beitb16_384", + non_negative=True, + ) + + if pretrained: + checkpoint = ( + "https://github.com/isl-org/MiDaS/releases/download/v3_1/dpt_beit_base_384.pt" + ) + state_dict = torch.hub.load_state_dict_from_url( + checkpoint, map_location=torch.device('cpu'), progress=True, check_hash=True + ) + model.load_state_dict(state_dict) + + return model + +def DPT_SwinV2_L_384(pretrained=True, **kwargs): + """ # This docstring shows up in hub.help() + MiDaS DPT_SwinV2_L_384 model for monocular depth estimation + pretrained (bool): load pretrained weights into model + """ + + model = DPTDepthModel( + path=None, + backbone="swin2l24_384", + non_negative=True, + ) + + if pretrained: + checkpoint = ( + "https://github.com/isl-org/MiDaS/releases/download/v3_1/dpt_swin2_large_384.pt" + ) + state_dict = torch.hub.load_state_dict_from_url( + checkpoint, map_location=torch.device('cpu'), progress=True, check_hash=True + ) + model.load_state_dict(state_dict) + + return model + +def DPT_SwinV2_B_384(pretrained=True, **kwargs): + """ # This docstring shows up in hub.help() + MiDaS DPT_SwinV2_B_384 model for monocular depth estimation + pretrained (bool): load pretrained weights into model + """ + + model = DPTDepthModel( + path=None, + backbone="swin2b24_384", + non_negative=True, + ) + + if pretrained: + checkpoint = ( + "https://github.com/isl-org/MiDaS/releases/download/v3_1/dpt_swin2_base_384.pt" + ) + state_dict = torch.hub.load_state_dict_from_url( + checkpoint, map_location=torch.device('cpu'), progress=True, check_hash=True + ) + model.load_state_dict(state_dict) + + return model + +def DPT_SwinV2_T_256(pretrained=True, **kwargs): + """ # This docstring shows up in hub.help() + MiDaS DPT_SwinV2_T_256 model for monocular depth estimation + pretrained (bool): load pretrained weights into model + """ + + model = DPTDepthModel( + path=None, + backbone="swin2t16_256", + non_negative=True, + ) + + if pretrained: + checkpoint = ( + "https://github.com/isl-org/MiDaS/releases/download/v3_1/dpt_swin2_tiny_256.pt" + ) + state_dict = torch.hub.load_state_dict_from_url( + checkpoint, map_location=torch.device('cpu'), progress=True, check_hash=True + ) + model.load_state_dict(state_dict) + + return model + +def DPT_Swin_L_384(pretrained=True, **kwargs): + """ # This docstring shows up in hub.help() + MiDaS DPT_Swin_L_384 model for monocular depth estimation + pretrained (bool): load pretrained weights into model + """ + + model = DPTDepthModel( + path=None, + backbone="swinl12_384", + non_negative=True, + ) + + if pretrained: + checkpoint = ( + "https://github.com/isl-org/MiDaS/releases/download/v3_1/dpt_swin_large_384.pt" + ) + state_dict = torch.hub.load_state_dict_from_url( + checkpoint, map_location=torch.device('cpu'), progress=True, check_hash=True + ) + model.load_state_dict(state_dict) + + return model + +def DPT_Next_ViT_L_384(pretrained=True, **kwargs): + """ # This docstring shows up in hub.help() + MiDaS DPT_Next_ViT_L_384 model for monocular depth estimation + pretrained (bool): load pretrained weights into model + """ + + model = DPTDepthModel( + path=None, + backbone="next_vit_large_6m", + non_negative=True, + ) + + if pretrained: + checkpoint = ( + "https://github.com/isl-org/MiDaS/releases/download/v3_1/dpt_next_vit_large_384.pt" + ) + state_dict = torch.hub.load_state_dict_from_url( + checkpoint, map_location=torch.device('cpu'), progress=True, check_hash=True + ) + model.load_state_dict(state_dict) + + return model + +def DPT_LeViT_224(pretrained=True, **kwargs): + """ # This docstring shows up in hub.help() + MiDaS DPT_LeViT_224 model for monocular depth estimation + pretrained (bool): load pretrained weights into model + """ + + model = DPTDepthModel( + path=None, + backbone="levit_384", + non_negative=True, + head_features_1=64, + head_features_2=8, + ) + + if pretrained: + checkpoint = ( + "https://github.com/isl-org/MiDaS/releases/download/v3_1/dpt_levit_224.pt" + ) + state_dict = torch.hub.load_state_dict_from_url( + checkpoint, map_location=torch.device('cpu'), progress=True, check_hash=True + ) + model.load_state_dict(state_dict) + + return model + +def DPT_Large(pretrained=True, **kwargs): + """ # This docstring shows up in hub.help() + MiDaS DPT-Large model for monocular depth estimation + pretrained (bool): load pretrained weights into model + """ + + model = DPTDepthModel( + path=None, + backbone="vitl16_384", + non_negative=True, + ) + + if pretrained: + checkpoint = ( + "https://github.com/isl-org/MiDaS/releases/download/v3/dpt_large_384.pt" + ) + state_dict = torch.hub.load_state_dict_from_url( + checkpoint, map_location=torch.device('cpu'), progress=True, check_hash=True + ) + model.load_state_dict(state_dict) + + return model + +def DPT_Hybrid(pretrained=True, **kwargs): + """ # This docstring shows up in hub.help() + MiDaS DPT-Hybrid model for monocular depth estimation + pretrained (bool): load pretrained weights into model + """ + + model = DPTDepthModel( + path=None, + backbone="vitb_rn50_384", + non_negative=True, + ) + + if pretrained: + checkpoint = ( + "https://github.com/isl-org/MiDaS/releases/download/v3/dpt_hybrid_384.pt" + ) + state_dict = torch.hub.load_state_dict_from_url( + checkpoint, map_location=torch.device('cpu'), progress=True, check_hash=True + ) + model.load_state_dict(state_dict) + + return model + +def MiDaS(pretrained=True, **kwargs): + """ # This docstring shows up in hub.help() + MiDaS v2.1 model for monocular depth estimation + pretrained (bool): load pretrained weights into model + """ + + model = MidasNet() + + if pretrained: + checkpoint = ( + "https://github.com/isl-org/MiDaS/releases/download/v2_1/midas_v21_384.pt" + ) + state_dict = torch.hub.load_state_dict_from_url( + checkpoint, map_location=torch.device('cpu'), progress=True, check_hash=True + ) + model.load_state_dict(state_dict) + + return model + +def MiDaS_small(pretrained=True, **kwargs): + """ # This docstring shows up in hub.help() + MiDaS v2.1 small model for monocular depth estimation on resource-constrained devices + pretrained (bool): load pretrained weights into model + """ + + model = MidasNet_small(None, features=64, backbone="efficientnet_lite3", exportable=True, non_negative=True, blocks={'expand': True}) + + if pretrained: + checkpoint = ( + "https://github.com/isl-org/MiDaS/releases/download/v2_1/midas_v21_small_256.pt" + ) + state_dict = torch.hub.load_state_dict_from_url( + checkpoint, map_location=torch.device('cpu'), progress=True, check_hash=True + ) + model.load_state_dict(state_dict) + + return model + + +def transforms(): + import cv2 + from torchvision.transforms import Compose + from midas.transforms import Resize, NormalizeImage, PrepareForNet + from midas import transforms + + transforms.default_transform = Compose( + [ + lambda img: {"image": img / 255.0}, + Resize( + 384, + 384, + resize_target=None, + keep_aspect_ratio=True, + ensure_multiple_of=32, + resize_method="upper_bound", + image_interpolation_method=cv2.INTER_CUBIC, + ), + NormalizeImage(mean=[0.485, 0.456, 0.406], std=[0.229, 0.224, 0.225]), + PrepareForNet(), + lambda sample: torch.from_numpy(sample["image"]).unsqueeze(0), + ] + ) + + transforms.small_transform = Compose( + [ + lambda img: {"image": img / 255.0}, + Resize( + 256, + 256, + resize_target=None, + keep_aspect_ratio=True, + ensure_multiple_of=32, + resize_method="upper_bound", + image_interpolation_method=cv2.INTER_CUBIC, + ), + NormalizeImage(mean=[0.485, 0.456, 0.406], std=[0.229, 0.224, 0.225]), + PrepareForNet(), + lambda sample: torch.from_numpy(sample["image"]).unsqueeze(0), + ] + ) + + transforms.dpt_transform = Compose( + [ + lambda img: {"image": img / 255.0}, + Resize( + 384, + 384, + resize_target=None, + keep_aspect_ratio=True, + ensure_multiple_of=32, + resize_method="minimal", + image_interpolation_method=cv2.INTER_CUBIC, + ), + NormalizeImage(mean=[0.5, 0.5, 0.5], std=[0.5, 0.5, 0.5]), + PrepareForNet(), + lambda sample: torch.from_numpy(sample["image"]).unsqueeze(0), + ] + ) + + transforms.beit512_transform = Compose( + [ + lambda img: {"image": img / 255.0}, + Resize( + 512, + 512, + resize_target=None, + keep_aspect_ratio=True, + ensure_multiple_of=32, + resize_method="minimal", + image_interpolation_method=cv2.INTER_CUBIC, + ), + NormalizeImage(mean=[0.5, 0.5, 0.5], std=[0.5, 0.5, 0.5]), + PrepareForNet(), + lambda sample: torch.from_numpy(sample["image"]).unsqueeze(0), + ] + ) + + transforms.swin384_transform = Compose( + [ + lambda img: {"image": img / 255.0}, + Resize( + 384, + 384, + resize_target=None, + keep_aspect_ratio=False, + ensure_multiple_of=32, + resize_method="minimal", + image_interpolation_method=cv2.INTER_CUBIC, + ), + NormalizeImage(mean=[0.5, 0.5, 0.5], std=[0.5, 0.5, 0.5]), + PrepareForNet(), + lambda sample: torch.from_numpy(sample["image"]).unsqueeze(0), + ] + ) + + transforms.swin256_transform = Compose( + [ + lambda img: {"image": img / 255.0}, + Resize( + 256, + 256, + resize_target=None, + keep_aspect_ratio=False, + ensure_multiple_of=32, + resize_method="minimal", + image_interpolation_method=cv2.INTER_CUBIC, + ), + NormalizeImage(mean=[0.5, 0.5, 0.5], std=[0.5, 0.5, 0.5]), + PrepareForNet(), + lambda sample: torch.from_numpy(sample["image"]).unsqueeze(0), + ] + ) + + transforms.levit_transform = Compose( + [ + lambda img: {"image": img / 255.0}, + Resize( + 224, + 224, + resize_target=None, + keep_aspect_ratio=False, + ensure_multiple_of=32, + resize_method="minimal", + image_interpolation_method=cv2.INTER_CUBIC, + ), + NormalizeImage(mean=[0.5, 0.5, 0.5], std=[0.5, 0.5, 0.5]), + PrepareForNet(), + lambda sample: torch.from_numpy(sample["image"]).unsqueeze(0), + ] + ) + + return transforms diff --git a/modules/control/proc/zoe/zoedepth/models/base_models/midas_repo/midas/__init__.py b/modules/control/proc/zoe/zoedepth/models/base_models/midas_repo/midas/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/modules/control/proc/zoe/zoedepth/models/base_models/midas_repo/midas/backbones/__init__.py b/modules/control/proc/zoe/zoedepth/models/base_models/midas_repo/midas/backbones/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/modules/control/proc/zoe/zoedepth/models/base_models/midas_repo/midas/backbones/beit.py b/modules/control/proc/zoe/zoedepth/models/base_models/midas_repo/midas/backbones/beit.py new file mode 100644 index 000000000..ab7458704 --- /dev/null +++ b/modules/control/proc/zoe/zoedepth/models/base_models/midas_repo/midas/backbones/beit.py @@ -0,0 +1,202 @@ +import timm +import torch +import types + +import numpy as np +import torch.nn.functional as F + +from .utils import forward_adapted_unflatten, make_backbone_default +from timm.models.beit import gen_relative_position_index +from torch.utils.checkpoint import checkpoint +from typing import Optional + + +def forward_beit(pretrained, x): + return forward_adapted_unflatten(pretrained, x, "forward_features") + + +def patch_embed_forward(self, x): + """ + Modification of timm.models.layers.patch_embed.py: PatchEmbed.forward to support arbitrary window sizes. + """ + x = self.proj(x) + if self.flatten: + x = x.flatten(2).transpose(1, 2) + x = self.norm(x) + return x + + +def _get_rel_pos_bias(self, window_size): + """ + Modification of timm.models.beit.py: Attention._get_rel_pos_bias to support arbitrary window sizes. + """ + old_height = 2 * self.window_size[0] - 1 + old_width = 2 * self.window_size[1] - 1 + + new_height = 2 * window_size[0] - 1 + new_width = 2 * window_size[1] - 1 + + old_relative_position_bias_table = self.relative_position_bias_table + + old_num_relative_distance = self.num_relative_distance + new_num_relative_distance = new_height * new_width + 3 + + old_sub_table = old_relative_position_bias_table[:old_num_relative_distance - 3] + + old_sub_table = old_sub_table.reshape(1, old_width, old_height, -1).permute(0, 3, 1, 2) + new_sub_table = F.interpolate(old_sub_table, size=(int(new_height), int(new_width)), mode="bilinear") + new_sub_table = new_sub_table.permute(0, 2, 3, 1).reshape(new_num_relative_distance - 3, -1) + + new_relative_position_bias_table = torch.cat( + [new_sub_table, old_relative_position_bias_table[old_num_relative_distance - 3:]]) + + key = str(window_size[1]) + "," + str(window_size[0]) + if key not in self.relative_position_indices.keys(): + self.relative_position_indices[key] = gen_relative_position_index(window_size) + + relative_position_bias = new_relative_position_bias_table[ + self.relative_position_indices[key].view(-1)].view( + window_size[0] * window_size[1] + 1, + window_size[0] * window_size[1] + 1, -1) # Wh*Ww,Wh*Ww,nH + relative_position_bias = relative_position_bias.permute(2, 0, 1).contiguous() # nH, Wh*Ww, Wh*Ww + return relative_position_bias.unsqueeze(0) + + +def attention_forward(self, x, resolution, shared_rel_pos_bias: Optional[torch.Tensor] = None): + """ + Modification of timm.models.beit.py: Attention.forward to support arbitrary window sizes. + """ + B, N, C = x.shape + + qkv_bias = torch.cat((self.q_bias, self.k_bias, self.v_bias)) if self.q_bias is not None else None + qkv = F.linear(input=x, weight=self.qkv.weight, bias=qkv_bias) + qkv = qkv.reshape(B, N, 3, self.num_heads, -1).permute(2, 0, 3, 1, 4) + q, k, v = qkv.unbind(0) # make torchscript happy (cannot use tensor as tuple) + + q = q * self.scale + attn = (q @ k.transpose(-2, -1)) + + if self.relative_position_bias_table is not None: + window_size = tuple(np.array(resolution) // 16) + attn = attn + self._get_rel_pos_bias(window_size) + if shared_rel_pos_bias is not None: + attn = attn + shared_rel_pos_bias + + attn = attn.softmax(dim=-1) + attn = self.attn_drop(attn) + + x = (attn @ v).transpose(1, 2).reshape(B, N, -1) + x = self.proj(x) + x = self.proj_drop(x) + return x + + +def block_forward(self, x, resolution, shared_rel_pos_bias: Optional[torch.Tensor] = None): + """ + Modification of timm.models.beit.py: Block.forward to support arbitrary window sizes. + """ + if self.gamma_1 is None: + x = x + self.drop_path(self.attn(self.norm1(x), resolution, shared_rel_pos_bias=shared_rel_pos_bias)) + x = x + self.drop_path(self.mlp(self.norm2(x))) + else: + x = x + self.drop_path(self.gamma_1 * self.attn(self.norm1(x), resolution, + shared_rel_pos_bias=shared_rel_pos_bias)) + x = x + self.drop_path(self.gamma_2 * self.mlp(self.norm2(x))) + return x + + +def beit_forward_features(self, x): + """ + Modification of timm.models.beit.py: Beit.forward_features to support arbitrary window sizes. + """ + resolution = x.shape[2:] + + x = self.patch_embed(x) + x = torch.cat((self.cls_token.expand(x.shape[0], -1, -1), x), dim=1) + if self.pos_embed is not None: + x = x + self.pos_embed + x = self.pos_drop(x) + + rel_pos_bias = self.rel_pos_bias() if self.rel_pos_bias is not None else None + for blk in self.blocks: + if self.grad_checkpointing and not torch.jit.is_scripting(): + x = checkpoint(blk, x, shared_rel_pos_bias=rel_pos_bias) + else: + x = blk(x, resolution, shared_rel_pos_bias=rel_pos_bias) + x = self.norm(x) + return x + + +def _make_beit_backbone( + model, + features=None, + size=None, + hooks=None, + vit_features=768, + use_readout="ignore", + start_index=1, + start_index_readout=1, +): + if hooks is None: + hooks = [0, 4, 8, 11] + if size is None: + size = [384, 384] + if features is None: + features = [96, 192, 384, 768] + backbone = make_backbone_default(model, features, size, hooks, vit_features, use_readout, start_index, + start_index_readout) + + backbone.model.patch_embed.forward = types.MethodType(patch_embed_forward, backbone.model.patch_embed) + backbone.model.forward_features = types.MethodType(beit_forward_features, backbone.model) + + for block in backbone.model.blocks: + attn = block.attn + attn._get_rel_pos_bias = types.MethodType(_get_rel_pos_bias, attn) + attn.forward = types.MethodType(attention_forward, attn) + attn.relative_position_indices = {} + + block.forward = types.MethodType(block_forward, block) + + return backbone + + +def _make_pretrained_beitl16_512(pretrained, use_readout="ignore", hooks=None): + model = timm.create_model("beit_large_patch16_512", pretrained=pretrained) + + hooks = [5, 11, 17, 23] if hooks is None else hooks + + features = [256, 512, 1024, 1024] + + return _make_beit_backbone( + model, + features=features, + size=[512, 512], + hooks=hooks, + vit_features=1024, + use_readout=use_readout, + ) + + +def _make_pretrained_beitl16_384(pretrained, use_readout="ignore", hooks=None): + model = timm.create_model("beit_large_patch16_384", pretrained=pretrained) + + hooks = [5, 11, 17, 23] if hooks is None else hooks + return _make_beit_backbone( + model, + features=[256, 512, 1024, 1024], + hooks=hooks, + vit_features=1024, + use_readout=use_readout, + ) + + +def _make_pretrained_beitb16_384(pretrained, use_readout="ignore", hooks=None): + model = timm.create_model("beit_base_patch16_384", pretrained=pretrained) + + hooks = [2, 5, 8, 11] if hooks is None else hooks + return _make_beit_backbone( + model, + features=[96, 192, 384, 768], + hooks=hooks, + use_readout=use_readout, + ) diff --git a/modules/control/proc/zoe/zoedepth/models/base_models/midas_repo/midas/backbones/levit.py b/modules/control/proc/zoe/zoedepth/models/base_models/midas_repo/midas/backbones/levit.py new file mode 100644 index 000000000..84287762c --- /dev/null +++ b/modules/control/proc/zoe/zoedepth/models/base_models/midas_repo/midas/backbones/levit.py @@ -0,0 +1,109 @@ +import timm +import torch +import torch.nn as nn +import numpy as np + +from .utils import activations, get_activation, Transpose + + +def forward_levit(pretrained, x): + pretrained.model.forward_features(x) + + layer_1 = pretrained.activations["1"] + layer_2 = pretrained.activations["2"] + layer_3 = pretrained.activations["3"] + + layer_1 = pretrained.act_postprocess1(layer_1) + layer_2 = pretrained.act_postprocess2(layer_2) + layer_3 = pretrained.act_postprocess3(layer_3) + + return layer_1, layer_2, layer_3 + + +def _make_levit_backbone( + model, + hooks=None, + patch_grid=None +): + if patch_grid is None: + patch_grid = [14, 14] + if hooks is None: + hooks = [3, 11, 21] + pretrained = nn.Module() + + pretrained.model = model + pretrained.model.blocks[hooks[0]].register_forward_hook(get_activation("1")) + pretrained.model.blocks[hooks[1]].register_forward_hook(get_activation("2")) + pretrained.model.blocks[hooks[2]].register_forward_hook(get_activation("3")) + + pretrained.activations = activations + + patch_grid_size = np.array(patch_grid, dtype=int) + + pretrained.act_postprocess1 = nn.Sequential( + Transpose(1, 2), + nn.Unflatten(2, torch.Size(patch_grid_size.tolist())) + ) + pretrained.act_postprocess2 = nn.Sequential( + Transpose(1, 2), + nn.Unflatten(2, torch.Size((np.ceil(patch_grid_size / 2).astype(int)).tolist())) + ) + pretrained.act_postprocess3 = nn.Sequential( + Transpose(1, 2), + nn.Unflatten(2, torch.Size((np.ceil(patch_grid_size / 4).astype(int)).tolist())) + ) + + return pretrained + + +class ConvTransposeNorm(nn.Sequential): + """ + Modification of + https://github.com/rwightman/pytorch-image-models/blob/master/timm/models/levit.py: ConvNorm + such that ConvTranspose2d is used instead of Conv2d. + """ + + def __init__( + self, in_chs, out_chs, kernel_size=1, stride=1, pad=0, dilation=1, + groups=1, bn_weight_init=1): + super().__init__() + self.add_module('c', + nn.ConvTranspose2d(in_chs, out_chs, kernel_size, stride, pad, dilation, groups, bias=False)) + self.add_module('bn', nn.BatchNorm2d(out_chs)) + + nn.init.constant_(self.bn.weight, bn_weight_init) + + def fuse(self): + c, bn = self._modules.values() + w = bn.weight / (bn.running_var + bn.eps) ** 0.5 + w = c.weight * w[:, None, None, None] + b = bn.bias - bn.running_mean * bn.weight / (bn.running_var + bn.eps) ** 0.5 + m = nn.ConvTranspose2d( + w.size(1), w.size(0), w.shape[2:], stride=self.c.stride, + padding=self.c.padding, dilation=self.c.dilation, groups=self.c.groups) + m.weight.data.copy_(w) + m.bias.data.copy_(b) + return m + + +def stem_b4_transpose(in_chs, out_chs, activation): + """ + Modification of + https://github.com/rwightman/pytorch-image-models/blob/master/timm/models/levit.py: stem_b16 + such that ConvTranspose2d is used instead of Conv2d and stem is also reduced to the half. + """ + return nn.Sequential( + ConvTransposeNorm(in_chs, out_chs, 3, 2, 1), + activation(), + ConvTransposeNorm(out_chs, out_chs // 2, 3, 2, 1), + activation()) + + +def _make_pretrained_levit_384(pretrained, hooks=None): + model = timm.create_model("levit_384", pretrained=pretrained) + + hooks = [3, 11, 21] if hooks is None else hooks + return _make_levit_backbone( + model, + hooks=hooks + ) diff --git a/modules/control/proc/zoe/zoedepth/models/base_models/midas_repo/midas/backbones/next_vit.py b/modules/control/proc/zoe/zoedepth/models/base_models/midas_repo/midas/backbones/next_vit.py new file mode 100644 index 000000000..a55c0a224 --- /dev/null +++ b/modules/control/proc/zoe/zoedepth/models/base_models/midas_repo/midas/backbones/next_vit.py @@ -0,0 +1,37 @@ +import timm +import torch.nn as nn +from .utils import activations, forward_default, get_activation +from ..external.next_vit.classification.nextvit import * # noqa + + +def forward_next_vit(pretrained, x): + return forward_default(pretrained, x, "forward") + + +def _make_next_vit_backbone( + model, + hooks=None, +): + if hooks is None: + hooks = [2, 6, 36, 39] + pretrained = nn.Module() + + pretrained.model = model + pretrained.model.features[hooks[0]].register_forward_hook(get_activation("1")) + pretrained.model.features[hooks[1]].register_forward_hook(get_activation("2")) + pretrained.model.features[hooks[2]].register_forward_hook(get_activation("3")) + pretrained.model.features[hooks[3]].register_forward_hook(get_activation("4")) + + pretrained.activations = activations + + return pretrained + + +def _make_pretrained_next_vit_large_6m(hooks=None): + model = timm.create_model("nextvit_large") + + hooks = [2, 6, 36, 39] if hooks is None else hooks + return _make_next_vit_backbone( + model, + hooks=hooks, + ) diff --git a/modules/control/proc/zoe/zoedepth/models/base_models/midas_repo/midas/backbones/swin.py b/modules/control/proc/zoe/zoedepth/models/base_models/midas_repo/midas/backbones/swin.py new file mode 100644 index 000000000..66850a0fb --- /dev/null +++ b/modules/control/proc/zoe/zoedepth/models/base_models/midas_repo/midas/backbones/swin.py @@ -0,0 +1,13 @@ +import timm + +from .swin_common import _make_swin_backbone + + +def _make_pretrained_swinl12_384(pretrained, hooks=None): + model = timm.create_model("swin_large_patch4_window12_384", pretrained=pretrained) + + hooks = [1, 1, 17, 1] if hooks is None else hooks + return _make_swin_backbone( + model, + hooks=hooks + ) diff --git a/modules/control/proc/zoe/zoedepth/models/base_models/midas_repo/midas/backbones/swin2.py b/modules/control/proc/zoe/zoedepth/models/base_models/midas_repo/midas/backbones/swin2.py new file mode 100644 index 000000000..ee917d836 --- /dev/null +++ b/modules/control/proc/zoe/zoedepth/models/base_models/midas_repo/midas/backbones/swin2.py @@ -0,0 +1,34 @@ +import timm + +from .swin_common import _make_swin_backbone + + +def _make_pretrained_swin2l24_384(pretrained, hooks=None): + model = timm.create_model("swinv2_large_window12to24_192to384_22kft1k", pretrained=pretrained) + + hooks = [1, 1, 17, 1] if hooks is None else hooks + return _make_swin_backbone( + model, + hooks=hooks + ) + + +def _make_pretrained_swin2b24_384(pretrained, hooks=None): + model = timm.create_model("swinv2_base_window12to24_192to384_22kft1k", pretrained=pretrained) + + hooks = [1, 1, 17, 1] if hooks is None else hooks + return _make_swin_backbone( + model, + hooks=hooks + ) + + +def _make_pretrained_swin2t16_256(pretrained, hooks=None): + model = timm.create_model("swinv2_tiny_window16_256", pretrained=pretrained) + + hooks = [1, 1, 5, 1] if hooks is None else hooks + return _make_swin_backbone( + model, + hooks=hooks, + patch_grid=[64, 64] + ) diff --git a/modules/control/proc/zoe/zoedepth/models/base_models/midas_repo/midas/backbones/swin_common.py b/modules/control/proc/zoe/zoedepth/models/base_models/midas_repo/midas/backbones/swin_common.py new file mode 100644 index 000000000..2f0c1225a --- /dev/null +++ b/modules/control/proc/zoe/zoedepth/models/base_models/midas_repo/midas/backbones/swin_common.py @@ -0,0 +1,56 @@ +import torch + +import torch.nn as nn +import numpy as np + +from .utils import activations, forward_default, get_activation, Transpose + + +def forward_swin(pretrained, x): + return forward_default(pretrained, x) + + +def _make_swin_backbone( + model, + hooks=None, + patch_grid=None +): + if patch_grid is None: + patch_grid = [96, 96] + if hooks is None: + hooks = [1, 1, 17, 1] + pretrained = nn.Module() + + pretrained.model = model + pretrained.model.layers[0].blocks[hooks[0]].register_forward_hook(get_activation("1")) + pretrained.model.layers[1].blocks[hooks[1]].register_forward_hook(get_activation("2")) + pretrained.model.layers[2].blocks[hooks[2]].register_forward_hook(get_activation("3")) + pretrained.model.layers[3].blocks[hooks[3]].register_forward_hook(get_activation("4")) + + pretrained.activations = activations + + if hasattr(model, "patch_grid"): + used_patch_grid = model.patch_grid + else: + used_patch_grid = patch_grid + + patch_grid_size = np.array(used_patch_grid, dtype=int) + + pretrained.act_postprocess1 = nn.Sequential( + Transpose(1, 2), + nn.Unflatten(2, torch.Size(patch_grid_size.tolist())) + ) + pretrained.act_postprocess2 = nn.Sequential( + Transpose(1, 2), + nn.Unflatten(2, torch.Size((patch_grid_size // 2).tolist())) + ) + pretrained.act_postprocess3 = nn.Sequential( + Transpose(1, 2), + nn.Unflatten(2, torch.Size((patch_grid_size // 4).tolist())) + ) + pretrained.act_postprocess4 = nn.Sequential( + Transpose(1, 2), + nn.Unflatten(2, torch.Size((patch_grid_size // 8).tolist())) + ) + + return pretrained diff --git a/modules/control/proc/zoe/zoedepth/models/base_models/midas_repo/midas/backbones/utils.py b/modules/control/proc/zoe/zoedepth/models/base_models/midas_repo/midas/backbones/utils.py new file mode 100644 index 000000000..bed17f97d --- /dev/null +++ b/modules/control/proc/zoe/zoedepth/models/base_models/midas_repo/midas/backbones/utils.py @@ -0,0 +1,253 @@ +import torch + +import torch.nn as nn + + +class Slice(nn.Module): + def __init__(self, start_index=1): + super(Slice, self).__init__() + self.start_index = start_index + + def forward(self, x): + return x[:, self.start_index:] + + +class AddReadout(nn.Module): + def __init__(self, start_index=1): + super(AddReadout, self).__init__() + self.start_index = start_index + + def forward(self, x): + if self.start_index == 2: + readout = (x[:, 0] + x[:, 1]) / 2 + else: + readout = x[:, 0] + return x[:, self.start_index:] + readout.unsqueeze(1) + + +class ProjectReadout(nn.Module): + def __init__(self, in_features, start_index=1): + super(ProjectReadout, self).__init__() + self.start_index = start_index + + self.project = nn.Sequential(nn.Linear(2 * in_features, in_features), nn.GELU()) + + def forward(self, x): + readout = x[:, 0].unsqueeze(1).expand_as(x[:, self.start_index:]) + features = torch.cat((x[:, self.start_index:], readout), -1) + + return self.project(features) + + +class Transpose(nn.Module): + def __init__(self, dim0, dim1): + super(Transpose, self).__init__() + self.dim0 = dim0 + self.dim1 = dim1 + + def forward(self, x): + x = x.transpose(self.dim0, self.dim1) + return x + + +activations = {} + + +def get_activation(name): + def hook(model, input, output): + activations[name] = output + + return hook + + +def forward_default(pretrained, x, function_name="forward_features"): + exec(f"pretrained.model.{function_name}(x)") + + layer_1 = pretrained.activations["1"] + layer_2 = pretrained.activations["2"] + layer_3 = pretrained.activations["3"] + layer_4 = pretrained.activations["4"] + + if hasattr(pretrained, "act_postprocess1"): + layer_1 = pretrained.act_postprocess1(layer_1) + if hasattr(pretrained, "act_postprocess2"): + layer_2 = pretrained.act_postprocess2(layer_2) + if hasattr(pretrained, "act_postprocess3"): + layer_3 = pretrained.act_postprocess3(layer_3) + if hasattr(pretrained, "act_postprocess4"): + layer_4 = pretrained.act_postprocess4(layer_4) + + return layer_1, layer_2, layer_3, layer_4 + + +def forward_adapted_unflatten(pretrained, x, function_name="forward_features"): + b, c, h, w = x.shape + + exec(f"glob = pretrained.model.{function_name}(x)") + + layer_1 = pretrained.activations["1"] + layer_2 = pretrained.activations["2"] + layer_3 = pretrained.activations["3"] + layer_4 = pretrained.activations["4"] + + layer_1 = pretrained.act_postprocess1[0:2](layer_1) + layer_2 = pretrained.act_postprocess2[0:2](layer_2) + layer_3 = pretrained.act_postprocess3[0:2](layer_3) + layer_4 = pretrained.act_postprocess4[0:2](layer_4) + + unflatten = nn.Sequential( + nn.Unflatten( + 2, + torch.Size( + [ + h // pretrained.model.patch_size[1], + w // pretrained.model.patch_size[0], + ] + ), + ) + ) + + if layer_1.ndim == 3: + layer_1 = unflatten(layer_1) + if layer_2.ndim == 3: + layer_2 = unflatten(layer_2) + if layer_3.ndim == 3: + layer_3 = unflatten(layer_3) + if layer_4.ndim == 3: + layer_4 = unflatten(layer_4) + + layer_1 = pretrained.act_postprocess1[3: len(pretrained.act_postprocess1)](layer_1) + layer_2 = pretrained.act_postprocess2[3: len(pretrained.act_postprocess2)](layer_2) + layer_3 = pretrained.act_postprocess3[3: len(pretrained.act_postprocess3)](layer_3) + layer_4 = pretrained.act_postprocess4[3: len(pretrained.act_postprocess4)](layer_4) + + return layer_1, layer_2, layer_3, layer_4 + + +def get_readout_oper(vit_features, features, use_readout, start_index=1): + if use_readout == "ignore": + readout_oper = [Slice(start_index)] * len(features) + elif use_readout == "add": + readout_oper = [AddReadout(start_index)] * len(features) + elif use_readout == "project": + readout_oper = [ + ProjectReadout(vit_features, start_index) for out_feat in features + ] + else: + raise AssertionError("wrong operation for readout token, use_readout can be 'ignore', 'add', or 'project'") + + return readout_oper + + +def make_backbone_default( + model, + features=None, + size=None, + hooks=None, + vit_features=768, + use_readout="ignore", + start_index=1, + start_index_readout=1, +): + if hooks is None: + hooks = [2, 5, 8, 11] + if size is None: + size = [384, 384] + if features is None: + features = [96, 192, 384, 768] + pretrained = nn.Module() + + pretrained.model = model + pretrained.model.blocks[hooks[0]].register_forward_hook(get_activation("1")) + pretrained.model.blocks[hooks[1]].register_forward_hook(get_activation("2")) + pretrained.model.blocks[hooks[2]].register_forward_hook(get_activation("3")) + pretrained.model.blocks[hooks[3]].register_forward_hook(get_activation("4")) + + pretrained.activations = activations + + readout_oper = get_readout_oper(vit_features, features, use_readout, start_index_readout) + + # 32, 48, 136, 384 + pretrained.act_postprocess1 = nn.Sequential( + readout_oper[0], + Transpose(1, 2), + nn.Unflatten(2, torch.Size([size[0] // 16, size[1] // 16])), + nn.Conv2d( + in_channels=vit_features, + out_channels=features[0], + kernel_size=1, + stride=1, + padding=0, + ), + nn.ConvTranspose2d( + in_channels=features[0], + out_channels=features[0], + kernel_size=4, + stride=4, + padding=0, + bias=True, + dilation=1, + groups=1, + ), + ) + + pretrained.act_postprocess2 = nn.Sequential( + readout_oper[1], + Transpose(1, 2), + nn.Unflatten(2, torch.Size([size[0] // 16, size[1] // 16])), + nn.Conv2d( + in_channels=vit_features, + out_channels=features[1], + kernel_size=1, + stride=1, + padding=0, + ), + nn.ConvTranspose2d( + in_channels=features[1], + out_channels=features[1], + kernel_size=2, + stride=2, + padding=0, + bias=True, + dilation=1, + groups=1, + ), + ) + + pretrained.act_postprocess3 = nn.Sequential( + readout_oper[2], + Transpose(1, 2), + nn.Unflatten(2, torch.Size([size[0] // 16, size[1] // 16])), + nn.Conv2d( + in_channels=vit_features, + out_channels=features[2], + kernel_size=1, + stride=1, + padding=0, + ), + ) + + pretrained.act_postprocess4 = nn.Sequential( + readout_oper[3], + Transpose(1, 2), + nn.Unflatten(2, torch.Size([size[0] // 16, size[1] // 16])), + nn.Conv2d( + in_channels=vit_features, + out_channels=features[3], + kernel_size=1, + stride=1, + padding=0, + ), + nn.Conv2d( + in_channels=features[3], + out_channels=features[3], + kernel_size=3, + stride=2, + padding=1, + ), + ) + + pretrained.model.start_index = start_index + pretrained.model.patch_size = [16, 16] + + return pretrained diff --git a/modules/control/proc/zoe/zoedepth/models/base_models/midas_repo/midas/backbones/vit.py b/modules/control/proc/zoe/zoedepth/models/base_models/midas_repo/midas/backbones/vit.py new file mode 100644 index 000000000..71e864cdf --- /dev/null +++ b/modules/control/proc/zoe/zoedepth/models/base_models/midas_repo/midas/backbones/vit.py @@ -0,0 +1,235 @@ +import torch +import torch.nn as nn +import timm +import types +import math +import torch.nn.functional as F + +from .utils import (activations, forward_adapted_unflatten, get_activation, get_readout_oper, + make_backbone_default, Transpose) + + +def forward_vit(pretrained, x): + return forward_adapted_unflatten(pretrained, x, "forward_flex") + + +def _resize_pos_embed(self, posemb, gs_h, gs_w): + posemb_tok, posemb_grid = ( + posemb[:, : self.start_index], + posemb[0, self.start_index:], + ) + + gs_old = int(math.sqrt(len(posemb_grid))) + + posemb_grid = posemb_grid.reshape(1, gs_old, gs_old, -1).permute(0, 3, 1, 2) + posemb_grid = F.interpolate(posemb_grid, size=(gs_h, gs_w), mode="bilinear") + posemb_grid = posemb_grid.permute(0, 2, 3, 1).reshape(1, gs_h * gs_w, -1) + + posemb = torch.cat([posemb_tok, posemb_grid], dim=1) + + return posemb + + +def forward_flex(self, x): + b, c, h, w = x.shape + + pos_embed = self._resize_pos_embed( + self.pos_embed, h // self.patch_size[1], w // self.patch_size[0] + ) + + B = x.shape[0] + + if hasattr(self.patch_embed, "backbone"): + x = self.patch_embed.backbone(x) + if isinstance(x, (list, tuple)): + x = x[-1] # last feature if backbone outputs list/tuple of features + + x = self.patch_embed.proj(x).flatten(2).transpose(1, 2) + + if getattr(self, "dist_token", None) is not None: + cls_tokens = self.cls_token.expand( + B, -1, -1 + ) # stole cls_tokens impl from Phil Wang, thanks + dist_token = self.dist_token.expand(B, -1, -1) + x = torch.cat((cls_tokens, dist_token, x), dim=1) + else: + if self.no_embed_class: + x = x + pos_embed + cls_tokens = self.cls_token.expand( + B, -1, -1 + ) # stole cls_tokens impl from Phil Wang, thanks + x = torch.cat((cls_tokens, x), dim=1) + + if not self.no_embed_class: + x = x + pos_embed + x = self.pos_drop(x) + + for blk in self.blocks: + x = blk(x) + + x = self.norm(x) + + return x + + +def _make_vit_b16_backbone( + model, + features=None, + size=None, + hooks=None, + vit_features=768, + use_readout="ignore", + start_index=1, + start_index_readout=1, +): + if hooks is None: + hooks = [2, 5, 8, 11] + if size is None: + size = [384, 384] + if features is None: + features = [96, 192, 384, 768] + pretrained = make_backbone_default(model, features, size, hooks, vit_features, use_readout, start_index, + start_index_readout) + + # We inject this function into the VisionTransformer instances so that + # we can use it with interpolated position embeddings without modifying the library source. + pretrained.model.forward_flex = types.MethodType(forward_flex, pretrained.model) + pretrained.model._resize_pos_embed = types.MethodType( + _resize_pos_embed, pretrained.model + ) + + return pretrained + + +def _make_pretrained_vitl16_384(pretrained, use_readout="ignore", hooks=None): + model = timm.create_model("vit_large_patch16_384", pretrained=pretrained) + + hooks = [5, 11, 17, 23] if hooks is None else hooks + return _make_vit_b16_backbone( + model, + features=[256, 512, 1024, 1024], + hooks=hooks, + vit_features=1024, + use_readout=use_readout, + ) + + +def _make_pretrained_vitb16_384(pretrained, use_readout="ignore", hooks=None): + model = timm.create_model("vit_base_patch16_384", pretrained=pretrained) + + hooks = [2, 5, 8, 11] if hooks is None else hooks + return _make_vit_b16_backbone( + model, features=[96, 192, 384, 768], hooks=hooks, use_readout=use_readout + ) + + +def _make_vit_b_rn50_backbone( + model, + features=None, + size=None, + hooks=None, + vit_features=768, + patch_size=None, + number_stages=2, + use_vit_only=False, + use_readout="ignore", + start_index=1, +): + if patch_size is None: + patch_size = [16, 16] + if hooks is None: + hooks = [0, 1, 8, 11] + if size is None: + size = [384, 384] + if features is None: + features = [256, 512, 768, 768] + pretrained = nn.Module() + + pretrained.model = model + + used_number_stages = 0 if use_vit_only else number_stages + for s in range(used_number_stages): + pretrained.model.patch_embed.backbone.stages[s].register_forward_hook( + get_activation(str(s + 1)) + ) + for s in range(used_number_stages, 4): + pretrained.model.blocks[hooks[s]].register_forward_hook(get_activation(str(s + 1))) + + pretrained.activations = activations + + readout_oper = get_readout_oper(vit_features, features, use_readout, start_index) + + for s in range(used_number_stages): + nn.Sequential(nn.Identity(), nn.Identity(), nn.Identity()) + exec(f"pretrained.act_postprocess{s + 1}=value") + for s in range(used_number_stages, 4): + if s < number_stages: + final_layer = nn.ConvTranspose2d( + in_channels=features[s], + out_channels=features[s], + kernel_size=4 // (2 ** s), + stride=4 // (2 ** s), + padding=0, + bias=True, + dilation=1, + groups=1, + ) + elif s > number_stages: + final_layer = nn.Conv2d( + in_channels=features[3], + out_channels=features[3], + kernel_size=3, + stride=2, + padding=1, + ) + else: + final_layer = None + + layers = [ + readout_oper[s], + Transpose(1, 2), + nn.Unflatten(2, torch.Size([size[0] // 16, size[1] // 16])), + nn.Conv2d( + in_channels=vit_features, + out_channels=features[s], + kernel_size=1, + stride=1, + padding=0, + ), + ] + if final_layer is not None: + layers.append(final_layer) + + nn.Sequential(*layers) + exec(f"pretrained.act_postprocess{s + 1}=value") + + pretrained.model.start_index = start_index + pretrained.model.patch_size = patch_size + + # We inject this function into the VisionTransformer instances so that + # we can use it with interpolated position embeddings without modifying the library source. + pretrained.model.forward_flex = types.MethodType(forward_flex, pretrained.model) + + # We inject this function into the VisionTransformer instances so that + # we can use it with interpolated position embeddings without modifying the library source. + pretrained.model._resize_pos_embed = types.MethodType( + _resize_pos_embed, pretrained.model + ) + + return pretrained + + +def _make_pretrained_vitb_rn50_384( + pretrained, use_readout="ignore", hooks=None, use_vit_only=False +): + model = timm.create_model("vit_base_resnet50_384", pretrained=pretrained) + + hooks = [0, 1, 8, 11] if hooks is None else hooks + return _make_vit_b_rn50_backbone( + model, + features=[256, 512, 768, 768], + size=[384, 384], + hooks=hooks, + use_vit_only=use_vit_only, + use_readout=use_readout, + ) diff --git a/modules/control/proc/zoe/zoedepth/models/base_models/midas_repo/midas/base_model.py b/modules/control/proc/zoe/zoedepth/models/base_models/midas_repo/midas/base_model.py new file mode 100644 index 000000000..5cf430239 --- /dev/null +++ b/modules/control/proc/zoe/zoedepth/models/base_models/midas_repo/midas/base_model.py @@ -0,0 +1,16 @@ +import torch + + +class BaseModel(torch.nn.Module): + def load(self, path): + """Load model from file. + + Args: + path (str): file path + """ + parameters = torch.load(path, map_location=torch.device('cpu')) + + if "optimizer" in parameters: + parameters = parameters["model"] + + self.load_state_dict(parameters) diff --git a/modules/control/proc/zoe/zoedepth/models/base_models/midas_repo/midas/blocks.py b/modules/control/proc/zoe/zoedepth/models/base_models/midas_repo/midas/blocks.py new file mode 100644 index 000000000..998a94bda --- /dev/null +++ b/modules/control/proc/zoe/zoedepth/models/base_models/midas_repo/midas/blocks.py @@ -0,0 +1,441 @@ +import torch +import torch.nn as nn + +from .backbones.beit import ( + _make_pretrained_beitl16_512, + _make_pretrained_beitl16_384, + _make_pretrained_beitb16_384, + forward_beit, +) +from .backbones.swin_common import ( + forward_swin, +) +from .backbones.swin2 import ( + _make_pretrained_swin2l24_384, + _make_pretrained_swin2b24_384, + _make_pretrained_swin2t16_256, +) +from .backbones.swin import ( + _make_pretrained_swinl12_384, +) +from .backbones.levit import ( + _make_pretrained_levit_384, + forward_levit, +) +from .backbones.vit import ( + _make_pretrained_vitb_rn50_384, + _make_pretrained_vitl16_384, + _make_pretrained_vitb16_384, + forward_vit, +) + +def _make_encoder(backbone, features, use_pretrained, groups=1, expand=False, exportable=True, hooks=None, + use_vit_only=False, use_readout="ignore", in_features=None): + if in_features is None: + in_features = [96, 256, 512, 1024] + if backbone == "beitl16_512": + pretrained = _make_pretrained_beitl16_512( + use_pretrained, hooks=hooks, use_readout=use_readout + ) + scratch = _make_scratch( + [256, 512, 1024, 1024], features, groups=groups, expand=expand + ) # BEiT_512-L (backbone) + elif backbone == "beitl16_384": + pretrained = _make_pretrained_beitl16_384( + use_pretrained, hooks=hooks, use_readout=use_readout + ) + scratch = _make_scratch( + [256, 512, 1024, 1024], features, groups=groups, expand=expand + ) # BEiT_384-L (backbone) + elif backbone == "beitb16_384": + pretrained = _make_pretrained_beitb16_384( + use_pretrained, hooks=hooks, use_readout=use_readout + ) + scratch = _make_scratch( + [96, 192, 384, 768], features, groups=groups, expand=expand + ) # BEiT_384-B (backbone) + elif backbone == "swin2l24_384": + pretrained = _make_pretrained_swin2l24_384( + use_pretrained, hooks=hooks + ) + scratch = _make_scratch( + [192, 384, 768, 1536], features, groups=groups, expand=expand + ) # Swin2-L/12to24 (backbone) + elif backbone == "swin2b24_384": + pretrained = _make_pretrained_swin2b24_384( + use_pretrained, hooks=hooks + ) + scratch = _make_scratch( + [128, 256, 512, 1024], features, groups=groups, expand=expand + ) # Swin2-B/12to24 (backbone) + elif backbone == "swin2t16_256": + pretrained = _make_pretrained_swin2t16_256( + use_pretrained, hooks=hooks + ) + scratch = _make_scratch( + [96, 192, 384, 768], features, groups=groups, expand=expand + ) # Swin2-T/16 (backbone) + elif backbone == "swinl12_384": + pretrained = _make_pretrained_swinl12_384( + use_pretrained, hooks=hooks + ) + scratch = _make_scratch( + [192, 384, 768, 1536], features, groups=groups, expand=expand + ) # Swin-L/12 (backbone) + elif backbone == "next_vit_large_6m": + from .backbones.next_vit import _make_pretrained_next_vit_large_6m + pretrained = _make_pretrained_next_vit_large_6m(hooks=hooks) + scratch = _make_scratch( + in_features, features, groups=groups, expand=expand + ) # Next-ViT-L on ImageNet-1K-6M (backbone) + elif backbone == "levit_384": + pretrained = _make_pretrained_levit_384( + use_pretrained, hooks=hooks + ) + scratch = _make_scratch( + [384, 512, 768], features, groups=groups, expand=expand + ) # LeViT 384 (backbone) + elif backbone == "vitl16_384": + pretrained = _make_pretrained_vitl16_384( + use_pretrained, hooks=hooks, use_readout=use_readout + ) + scratch = _make_scratch( + [256, 512, 1024, 1024], features, groups=groups, expand=expand + ) # ViT-L/16 - 85.0% Top1 (backbone) + elif backbone == "vitb_rn50_384": + pretrained = _make_pretrained_vitb_rn50_384( + use_pretrained, + hooks=hooks, + use_vit_only=use_vit_only, + use_readout=use_readout, + ) + scratch = _make_scratch( + [256, 512, 768, 768], features, groups=groups, expand=expand + ) # ViT-H/16 - 85.0% Top1 (backbone) + elif backbone == "vitb16_384": + pretrained = _make_pretrained_vitb16_384( + use_pretrained, hooks=hooks, use_readout=use_readout + ) + scratch = _make_scratch( + [96, 192, 384, 768], features, groups=groups, expand=expand + ) # ViT-B/16 - 84.6% Top1 (backbone) + elif backbone == "resnext101_wsl": + pretrained = _make_pretrained_resnext101_wsl(use_pretrained) + scratch = _make_scratch([256, 512, 1024, 2048], features, groups=groups, expand=expand) # efficientnet_lite3 + elif backbone == "efficientnet_lite3": + pretrained = _make_pretrained_efficientnet_lite3(use_pretrained, exportable=exportable) + scratch = _make_scratch([32, 48, 136, 384], features, groups=groups, expand=expand) # efficientnet_lite3 + else: + print(f"Backbone '{backbone}' not implemented") + raise AssertionError + + return pretrained, scratch + + +def _make_scratch(in_shape, out_shape, groups=1, expand=False): + scratch = nn.Module() + + out_shape1 = out_shape + out_shape2 = out_shape + out_shape3 = out_shape + if len(in_shape) >= 4: + out_shape4 = out_shape + + if expand: + out_shape1 = out_shape + out_shape2 = out_shape*2 + out_shape3 = out_shape*4 + if len(in_shape) >= 4: + out_shape4 = out_shape*8 + + scratch.layer1_rn = nn.Conv2d( + in_shape[0], out_shape1, kernel_size=3, stride=1, padding=1, bias=False, groups=groups + ) + scratch.layer2_rn = nn.Conv2d( + in_shape[1], out_shape2, kernel_size=3, stride=1, padding=1, bias=False, groups=groups + ) + scratch.layer3_rn = nn.Conv2d( + in_shape[2], out_shape3, kernel_size=3, stride=1, padding=1, bias=False, groups=groups + ) + if len(in_shape) >= 4: + scratch.layer4_rn = nn.Conv2d( + in_shape[3], out_shape4, kernel_size=3, stride=1, padding=1, bias=False, groups=groups + ) + + return scratch + + +def _make_pretrained_efficientnet_lite3(use_pretrained, exportable=False): + efficientnet = torch.hub.load( + "rwightman/gen-efficientnet-pytorch", + "tf_efficientnet_lite3", + pretrained=use_pretrained, + exportable=exportable + ) + return _make_efficientnet_backbone(efficientnet) + + +def _make_efficientnet_backbone(effnet): + pretrained = nn.Module() + + pretrained.layer1 = nn.Sequential( + effnet.conv_stem, effnet.bn1, effnet.act1, *effnet.blocks[0:2] + ) + pretrained.layer2 = nn.Sequential(*effnet.blocks[2:3]) + pretrained.layer3 = nn.Sequential(*effnet.blocks[3:5]) + pretrained.layer4 = nn.Sequential(*effnet.blocks[5:9]) + + return pretrained + + +def _make_resnet_backbone(resnet): + pretrained = nn.Module() + pretrained.layer1 = nn.Sequential( + resnet.conv1, resnet.bn1, resnet.relu, resnet.maxpool, resnet.layer1 + ) + + pretrained.layer2 = resnet.layer2 + pretrained.layer3 = resnet.layer3 + pretrained.layer4 = resnet.layer4 + + return pretrained + + +def _make_pretrained_resnext101_wsl(use_pretrained): + resnet = torch.hub.load("facebookresearch/WSL-Images", "resnext101_32x8d_wsl") + return _make_resnet_backbone(resnet) + + + +class Interpolate(nn.Module): + """Interpolation module. + """ + + def __init__(self, scale_factor, mode, align_corners=False): + """Init. + + Args: + scale_factor (float): scaling + mode (str): interpolation mode + """ + super(Interpolate, self).__init__() + + self.interp = nn.functional.interpolate + self.scale_factor = scale_factor + self.mode = mode + self.align_corners = align_corners + + def forward(self, x): + """Forward pass. + + Args: + x (tensor): input + + Returns: + tensor: interpolated data + """ + + x = self.interp( + x, scale_factor=self.scale_factor, mode=self.mode, align_corners=self.align_corners + ) + + return x + + +class ResidualConvUnit(nn.Module): + """Residual convolution module. + """ + + def __init__(self, features): + """Init. + + Args: + features (int): number of features + """ + super().__init__() + + self.conv1 = nn.Conv2d( + features, features, kernel_size=3, stride=1, padding=1, bias=True + ) + + self.conv2 = nn.Conv2d( + features, features, kernel_size=3, stride=1, padding=1, bias=True + ) + + self.relu = nn.ReLU(inplace=True) + + def forward(self, x): + """Forward pass. + + Args: + x (tensor): input + + Returns: + tensor: output + """ + out = self.relu(x) + out = self.conv1(out) + out = self.relu(out) + out = self.conv2(out) + + return out + x + + +class FeatureFusionBlock(nn.Module): + """Feature fusion block. + """ + + def __init__(self, features): + """Init. + + Args: + features (int): number of features + """ + super(FeatureFusionBlock, self).__init__() + + self.resConfUnit1 = ResidualConvUnit(features) + self.resConfUnit2 = ResidualConvUnit(features) + + def forward(self, *xs): + """Forward pass. + + Returns: + tensor: output + """ + output = xs[0] + + if len(xs) == 2: + output += self.resConfUnit1(xs[1]) + + output = self.resConfUnit2(output) + + output = nn.functional.interpolate( + output, scale_factor=2, mode="bilinear", align_corners=True + ) + + return output + + + + +class ResidualConvUnit_custom(nn.Module): + """Residual convolution module. + """ + + def __init__(self, features, activation, bn): + """Init. + + Args: + features (int): number of features + """ + super().__init__() + + self.bn = bn + + self.groups=1 + + self.conv1 = nn.Conv2d( + features, features, kernel_size=3, stride=1, padding=1, bias=True, groups=self.groups + ) + + self.conv2 = nn.Conv2d( + features, features, kernel_size=3, stride=1, padding=1, bias=True, groups=self.groups + ) + + if self.bn is True: + self.bn1 = nn.BatchNorm2d(features) + self.bn2 = nn.BatchNorm2d(features) + + self.activation = activation + + self.skip_add = nn.quantized.FloatFunctional() + + def forward(self, x): + """Forward pass. + + Args: + x (tensor): input + + Returns: + tensor: output + """ + + out = self.activation(x) + out = self.conv1(out) + if self.bn is True: + out = self.bn1(out) + + out = self.activation(out) + out = self.conv2(out) + if self.bn is True: + out = self.bn2(out) + + if self.groups > 1: + out = self.conv_merge(out) + + return self.skip_add.add(out, x) + + # return out + x + + +class FeatureFusionBlock_custom(nn.Module): + """Feature fusion block. + """ + + def __init__(self, features, activation, deconv=False, bn=False, expand=False, align_corners=True, size=None): + """Init. + + Args: + features (int): number of features + """ + super(FeatureFusionBlock_custom, self).__init__() + + self.deconv = deconv + self.align_corners = align_corners + + self.groups=1 + + self.expand = expand + out_features = features + if self.expand is True: + out_features = features//2 + + self.out_conv = nn.Conv2d(features, out_features, kernel_size=1, stride=1, padding=0, bias=True, groups=1) + + self.resConfUnit1 = ResidualConvUnit_custom(features, activation, bn) + self.resConfUnit2 = ResidualConvUnit_custom(features, activation, bn) + + self.skip_add = nn.quantized.FloatFunctional() + + self.size=size + + def forward(self, *xs, size=None): + """Forward pass. + + Returns: + tensor: output + """ + output = xs[0] + + if len(xs) == 2: + res = self.resConfUnit1(xs[1]) + output = self.skip_add.add(output, res) + # output += res + + output = self.resConfUnit2(output) + + if (size is None) and (self.size is None): + modifier = {"scale_factor": 2} + elif size is None: + modifier = {"size": self.size} + else: + modifier = {"size": size} + + output = nn.functional.interpolate( + output, **modifier, mode="bilinear", align_corners=self.align_corners + ) + + output = self.out_conv(output) + + return output + diff --git a/modules/control/proc/zoe/zoedepth/models/base_models/midas_repo/midas/dpt_depth.py b/modules/control/proc/zoe/zoedepth/models/base_models/midas_repo/midas/dpt_depth.py new file mode 100644 index 000000000..afb997051 --- /dev/null +++ b/modules/control/proc/zoe/zoedepth/models/base_models/midas_repo/midas/dpt_depth.py @@ -0,0 +1,166 @@ +import torch +import torch.nn as nn + +from .base_model import BaseModel +from .blocks import ( + FeatureFusionBlock_custom, + Interpolate, + _make_encoder, + forward_beit, + forward_swin, + forward_levit, + forward_vit, +) +from .backbones.levit import stem_b4_transpose +from timm.models.layers import get_act_layer + + +def _make_fusion_block(features, use_bn, size = None): + return FeatureFusionBlock_custom( + features, + nn.ReLU(False), + deconv=False, + bn=use_bn, + expand=False, + align_corners=True, + size=size, + ) + + +class DPT(BaseModel): + def __init__( + self, + head, + features=256, + backbone="vitb_rn50_384", + readout="project", + channels_last=False, + use_bn=False, + **kwargs + ): + + super(DPT, self).__init__() + + self.channels_last = channels_last + + # For the Swin, Swin 2, LeViT and Next-ViT Transformers, the hierarchical architectures prevent setting the + # hooks freely. Instead, the hooks have to be chosen according to the ranges specified in the comments. + hooks = { + "beitl16_512": [5, 11, 17, 23], + "beitl16_384": [5, 11, 17, 23], + "beitb16_384": [2, 5, 8, 11], + "swin2l24_384": [1, 1, 17, 1], # Allowed ranges: [0, 1], [0, 1], [ 0, 17], [ 0, 1] + "swin2b24_384": [1, 1, 17, 1], # [0, 1], [0, 1], [ 0, 17], [ 0, 1] + "swin2t16_256": [1, 1, 5, 1], # [0, 1], [0, 1], [ 0, 5], [ 0, 1] + "swinl12_384": [1, 1, 17, 1], # [0, 1], [0, 1], [ 0, 17], [ 0, 1] + "next_vit_large_6m": [2, 6, 36, 39], # [0, 2], [3, 6], [ 7, 36], [37, 39] + "levit_384": [3, 11, 21], # [0, 3], [6, 11], [14, 21] + "vitb_rn50_384": [0, 1, 8, 11], + "vitb16_384": [2, 5, 8, 11], + "vitl16_384": [5, 11, 17, 23], + }[backbone] + + if "next_vit" in backbone: + in_features = { + "next_vit_large_6m": [96, 256, 512, 1024], + }[backbone] + else: + in_features = None + + # Instantiate backbone and reassemble blocks + self.pretrained, self.scratch = _make_encoder( + backbone, + features, + False, # Set to true of you want to train from scratch, uses ImageNet weights + groups=1, + expand=False, + exportable=False, + hooks=hooks, + use_readout=readout, + in_features=in_features, + ) + + self.number_layers = len(hooks) if hooks is not None else 4 + size_refinenet3 = None + self.scratch.stem_transpose = None + + if "beit" in backbone: + self.forward_transformer = forward_beit + elif "swin" in backbone: + self.forward_transformer = forward_swin + elif "next_vit" in backbone: + from .backbones.next_vit import forward_next_vit + self.forward_transformer = forward_next_vit + elif "levit" in backbone: + self.forward_transformer = forward_levit + size_refinenet3 = 7 + self.scratch.stem_transpose = stem_b4_transpose(256, 128, get_act_layer("hard_swish")) + else: + self.forward_transformer = forward_vit + + self.scratch.refinenet1 = _make_fusion_block(features, use_bn) + self.scratch.refinenet2 = _make_fusion_block(features, use_bn) + self.scratch.refinenet3 = _make_fusion_block(features, use_bn, size_refinenet3) + if self.number_layers >= 4: + self.scratch.refinenet4 = _make_fusion_block(features, use_bn) + + self.scratch.output_conv = head + + + def forward(self, x): + if self.channels_last is True: + x.contiguous(memory_format=torch.channels_last) + + layers = self.forward_transformer(self.pretrained, x) + if self.number_layers == 3: + layer_1, layer_2, layer_3 = layers + else: + layer_1, layer_2, layer_3, layer_4 = layers + + layer_1_rn = self.scratch.layer1_rn(layer_1) + layer_2_rn = self.scratch.layer2_rn(layer_2) + layer_3_rn = self.scratch.layer3_rn(layer_3) + if self.number_layers >= 4: + layer_4_rn = self.scratch.layer4_rn(layer_4) + + if self.number_layers == 3: + path_3 = self.scratch.refinenet3(layer_3_rn, size=layer_2_rn.shape[2:]) + else: + path_4 = self.scratch.refinenet4(layer_4_rn, size=layer_3_rn.shape[2:]) + path_3 = self.scratch.refinenet3(path_4, layer_3_rn, size=layer_2_rn.shape[2:]) + path_2 = self.scratch.refinenet2(path_3, layer_2_rn, size=layer_1_rn.shape[2:]) + path_1 = self.scratch.refinenet1(path_2, layer_1_rn) + + if self.scratch.stem_transpose is not None: + path_1 = self.scratch.stem_transpose(path_1) + + out = self.scratch.output_conv(path_1) + + return out + + +class DPTDepthModel(DPT): + def __init__(self, path=None, non_negative=True, **kwargs): + features = kwargs["features"] if "features" in kwargs else 256 + head_features_1 = kwargs["head_features_1"] if "head_features_1" in kwargs else features + head_features_2 = kwargs["head_features_2"] if "head_features_2" in kwargs else 32 + kwargs.pop("head_features_1", None) + kwargs.pop("head_features_2", None) + + head = nn.Sequential( + nn.Conv2d(head_features_1, head_features_1 // 2, kernel_size=3, stride=1, padding=1), + Interpolate(scale_factor=2, mode="bilinear", align_corners=True), + nn.Conv2d(head_features_1 // 2, head_features_2, kernel_size=3, stride=1, padding=1), + nn.ReLU(True), + nn.Conv2d(head_features_2, 1, kernel_size=1, stride=1, padding=0), + nn.ReLU(True) if non_negative else nn.Identity(), + nn.Identity(), + ) + + super().__init__(head, **kwargs) + + if path is not None: + self.load(path) + + def forward(self, x): + return super().forward(x).squeeze(dim=1) diff --git a/modules/control/proc/zoe/zoedepth/models/base_models/midas_repo/midas/midas_net.py b/modules/control/proc/zoe/zoedepth/models/base_models/midas_repo/midas/midas_net.py new file mode 100644 index 000000000..8a9549778 --- /dev/null +++ b/modules/control/proc/zoe/zoedepth/models/base_models/midas_repo/midas/midas_net.py @@ -0,0 +1,76 @@ +"""MidashNet: Network for monocular depth estimation trained by mixing several datasets. +This file contains code that is adapted from +https://github.com/thomasjpfan/pytorch_refinenet/blob/master/pytorch_refinenet/refinenet/refinenet_4cascade.py +""" +import torch +import torch.nn as nn + +from .base_model import BaseModel +from .blocks import FeatureFusionBlock, Interpolate, _make_encoder + + +class MidasNet(BaseModel): + """Network for monocular depth estimation. + """ + + def __init__(self, path=None, features=256, non_negative=True): + """Init. + + Args: + path (str, optional): Path to saved model. Defaults to None. + features (int, optional): Number of features. Defaults to 256. + backbone (str, optional): Backbone network for encoder. Defaults to resnet50 + """ + print("Loading weights: ", path) + + super(MidasNet, self).__init__() + + use_pretrained = False if path is None else True + + self.pretrained, self.scratch = _make_encoder(backbone="resnext101_wsl", features=features, use_pretrained=use_pretrained) + + self.scratch.refinenet4 = FeatureFusionBlock(features) + self.scratch.refinenet3 = FeatureFusionBlock(features) + self.scratch.refinenet2 = FeatureFusionBlock(features) + self.scratch.refinenet1 = FeatureFusionBlock(features) + + self.scratch.output_conv = nn.Sequential( + nn.Conv2d(features, 128, kernel_size=3, stride=1, padding=1), + Interpolate(scale_factor=2, mode="bilinear"), + nn.Conv2d(128, 32, kernel_size=3, stride=1, padding=1), + nn.ReLU(True), + nn.Conv2d(32, 1, kernel_size=1, stride=1, padding=0), + nn.ReLU(True) if non_negative else nn.Identity(), + ) + + if path: + self.load(path) + + def forward(self, x): + """Forward pass. + + Args: + x (tensor): input data (image) + + Returns: + tensor: depth + """ + + layer_1 = self.pretrained.layer1(x) + layer_2 = self.pretrained.layer2(layer_1) + layer_3 = self.pretrained.layer3(layer_2) + layer_4 = self.pretrained.layer4(layer_3) + + layer_1_rn = self.scratch.layer1_rn(layer_1) + layer_2_rn = self.scratch.layer2_rn(layer_2) + layer_3_rn = self.scratch.layer3_rn(layer_3) + layer_4_rn = self.scratch.layer4_rn(layer_4) + + path_4 = self.scratch.refinenet4(layer_4_rn) + path_3 = self.scratch.refinenet3(path_4, layer_3_rn) + path_2 = self.scratch.refinenet2(path_3, layer_2_rn) + path_1 = self.scratch.refinenet1(path_2, layer_1_rn) + + out = self.scratch.output_conv(path_1) + + return torch.squeeze(out, dim=1) diff --git a/modules/control/proc/zoe/zoedepth/models/base_models/midas_repo/midas/midas_net_custom.py b/modules/control/proc/zoe/zoedepth/models/base_models/midas_repo/midas/midas_net_custom.py new file mode 100644 index 000000000..cba1bcfff --- /dev/null +++ b/modules/control/proc/zoe/zoedepth/models/base_models/midas_repo/midas/midas_net_custom.py @@ -0,0 +1,130 @@ +"""MidashNet: Network for monocular depth estimation trained by mixing several datasets. +This file contains code that is adapted from +https://github.com/thomasjpfan/pytorch_refinenet/blob/master/pytorch_refinenet/refinenet/refinenet_4cascade.py +""" +import torch +import torch.nn as nn + +from .base_model import BaseModel +from .blocks import FeatureFusionBlock, FeatureFusionBlock_custom, Interpolate, _make_encoder + + +class MidasNet_small(BaseModel): + """Network for monocular depth estimation. + """ + + def __init__(self, path=None, features=64, backbone="efficientnet_lite3", non_negative=True, exportable=True, channels_last=False, align_corners=True, + blocks=None): + """Init. + + Args: + path (str, optional): Path to saved model. Defaults to None. + features (int, optional): Number of features. Defaults to 256. + backbone (str, optional): Backbone network for encoder. Defaults to resnet50 + """ + if blocks is None: + blocks = {"expand": True} + print("Loading weights: ", path) + + super(MidasNet_small, self).__init__() + + use_pretrained = False if path else True + + self.channels_last = channels_last + self.blocks = blocks + self.backbone = backbone + + self.groups = 1 + + features1=features + features2=features + features3=features + features4=features + self.expand = False + if "expand" in self.blocks and self.blocks['expand'] is True: + self.expand = True + features1=features + features2=features*2 + features3=features*4 + features4=features*8 + + self.pretrained, self.scratch = _make_encoder(self.backbone, features, use_pretrained, groups=self.groups, expand=self.expand, exportable=exportable) + + self.scratch.activation = nn.ReLU(False) + + self.scratch.refinenet4 = FeatureFusionBlock_custom(features4, self.scratch.activation, deconv=False, bn=False, expand=self.expand, align_corners=align_corners) + self.scratch.refinenet3 = FeatureFusionBlock_custom(features3, self.scratch.activation, deconv=False, bn=False, expand=self.expand, align_corners=align_corners) + self.scratch.refinenet2 = FeatureFusionBlock_custom(features2, self.scratch.activation, deconv=False, bn=False, expand=self.expand, align_corners=align_corners) + self.scratch.refinenet1 = FeatureFusionBlock_custom(features1, self.scratch.activation, deconv=False, bn=False, align_corners=align_corners) + + + self.scratch.output_conv = nn.Sequential( + nn.Conv2d(features, features//2, kernel_size=3, stride=1, padding=1, groups=self.groups), + Interpolate(scale_factor=2, mode="bilinear"), + nn.Conv2d(features//2, 32, kernel_size=3, stride=1, padding=1), + self.scratch.activation, + nn.Conv2d(32, 1, kernel_size=1, stride=1, padding=0), + nn.ReLU(True) if non_negative else nn.Identity(), + nn.Identity(), + ) + + if path: + self.load(path) + + + def forward(self, x): + """Forward pass. + + Args: + x (tensor): input data (image) + + Returns: + tensor: depth + """ + if self.channels_last is True: + print("self.channels_last = ", self.channels_last) + x.contiguous(memory_format=torch.channels_last) + + + layer_1 = self.pretrained.layer1(x) + layer_2 = self.pretrained.layer2(layer_1) + layer_3 = self.pretrained.layer3(layer_2) + layer_4 = self.pretrained.layer4(layer_3) + + layer_1_rn = self.scratch.layer1_rn(layer_1) + layer_2_rn = self.scratch.layer2_rn(layer_2) + layer_3_rn = self.scratch.layer3_rn(layer_3) + layer_4_rn = self.scratch.layer4_rn(layer_4) + + + path_4 = self.scratch.refinenet4(layer_4_rn) + path_3 = self.scratch.refinenet3(path_4, layer_3_rn) + path_2 = self.scratch.refinenet2(path_3, layer_2_rn) + path_1 = self.scratch.refinenet1(path_2, layer_1_rn) + + out = self.scratch.output_conv(path_1) + + return torch.squeeze(out, dim=1) + + + +def fuse_model(m): + prev_previous_type = nn.Identity() + prev_previous_name = '' + previous_type = nn.Identity() + previous_name = '' + for name, module in m.named_modules(): + if prev_previous_type == nn.Conv2d and previous_type == nn.BatchNorm2d and type(module) == nn.ReLU: + # print("FUSED ", prev_previous_name, previous_name, name) + torch.quantization.fuse_modules(m, [prev_previous_name, previous_name, name], inplace=True) + elif prev_previous_type == nn.Conv2d and previous_type == nn.BatchNorm2d: + # print("FUSED ", prev_previous_name, previous_name) + torch.quantization.fuse_modules(m, [prev_previous_name, previous_name], inplace=True) + # elif previous_type == nn.Conv2d and type(module) == nn.ReLU: + # print("FUSED ", previous_name, name) + # torch.quantization.fuse_modules(m, [previous_name, name], inplace=True) + + prev_previous_type = previous_type + prev_previous_name = previous_name + previous_type = type(module) + previous_name = name diff --git a/modules/control/proc/zoe/zoedepth/models/base_models/midas_repo/midas/model_loader.py b/modules/control/proc/zoe/zoedepth/models/base_models/midas_repo/midas/model_loader.py new file mode 100644 index 000000000..98cbc296a --- /dev/null +++ b/modules/control/proc/zoe/zoedepth/models/base_models/midas_repo/midas/model_loader.py @@ -0,0 +1,242 @@ +import cv2 +import torch + +from midas.dpt_depth import DPTDepthModel +from midas.midas_net import MidasNet +from midas.midas_net_custom import MidasNet_small +from midas.transforms import Resize, NormalizeImage, PrepareForNet + +from torchvision.transforms import Compose + +default_models = { + "dpt_beit_large_512": "weights/dpt_beit_large_512.pt", + "dpt_beit_large_384": "weights/dpt_beit_large_384.pt", + "dpt_beit_base_384": "weights/dpt_beit_base_384.pt", + "dpt_swin2_large_384": "weights/dpt_swin2_large_384.pt", + "dpt_swin2_base_384": "weights/dpt_swin2_base_384.pt", + "dpt_swin2_tiny_256": "weights/dpt_swin2_tiny_256.pt", + "dpt_swin_large_384": "weights/dpt_swin_large_384.pt", + "dpt_next_vit_large_384": "weights/dpt_next_vit_large_384.pt", + "dpt_levit_224": "weights/dpt_levit_224.pt", + "dpt_large_384": "weights/dpt_large_384.pt", + "dpt_hybrid_384": "weights/dpt_hybrid_384.pt", + "midas_v21_384": "weights/midas_v21_384.pt", + "midas_v21_small_256": "weights/midas_v21_small_256.pt", + "openvino_midas_v21_small_256": "weights/openvino_midas_v21_small_256.xml", +} + + +def load_model(device, model_path, model_type="dpt_large_384", optimize=True, height=None, square=False): + """Load the specified network. + + Args: + device (device): the torch device used + model_path (str): path to saved model + model_type (str): the type of the model to be loaded + optimize (bool): optimize the model to half-integer on CUDA? + height (int): inference encoder image height + square (bool): resize to a square resolution? + + Returns: + The loaded network, the transform which prepares images as input to the network and the dimensions of the + network input + """ + if "openvino" in model_type: + from openvino.runtime import Core + + keep_aspect_ratio = not square + + if model_type == "dpt_beit_large_512": + model = DPTDepthModel( + path=model_path, + backbone="beitl16_512", + non_negative=True, + ) + net_w, net_h = 512, 512 + resize_mode = "minimal" + normalization = NormalizeImage(mean=[0.5, 0.5, 0.5], std=[0.5, 0.5, 0.5]) + + elif model_type == "dpt_beit_large_384": + model = DPTDepthModel( + path=model_path, + backbone="beitl16_384", + non_negative=True, + ) + net_w, net_h = 384, 384 + resize_mode = "minimal" + normalization = NormalizeImage(mean=[0.5, 0.5, 0.5], std=[0.5, 0.5, 0.5]) + + elif model_type == "dpt_beit_base_384": + model = DPTDepthModel( + path=model_path, + backbone="beitb16_384", + non_negative=True, + ) + net_w, net_h = 384, 384 + resize_mode = "minimal" + normalization = NormalizeImage(mean=[0.5, 0.5, 0.5], std=[0.5, 0.5, 0.5]) + + elif model_type == "dpt_swin2_large_384": + model = DPTDepthModel( + path=model_path, + backbone="swin2l24_384", + non_negative=True, + ) + net_w, net_h = 384, 384 + keep_aspect_ratio = False + resize_mode = "minimal" + normalization = NormalizeImage(mean=[0.5, 0.5, 0.5], std=[0.5, 0.5, 0.5]) + + elif model_type == "dpt_swin2_base_384": + model = DPTDepthModel( + path=model_path, + backbone="swin2b24_384", + non_negative=True, + ) + net_w, net_h = 384, 384 + keep_aspect_ratio = False + resize_mode = "minimal" + normalization = NormalizeImage(mean=[0.5, 0.5, 0.5], std=[0.5, 0.5, 0.5]) + + elif model_type == "dpt_swin2_tiny_256": + model = DPTDepthModel( + path=model_path, + backbone="swin2t16_256", + non_negative=True, + ) + net_w, net_h = 256, 256 + keep_aspect_ratio = False + resize_mode = "minimal" + normalization = NormalizeImage(mean=[0.5, 0.5, 0.5], std=[0.5, 0.5, 0.5]) + + elif model_type == "dpt_swin_large_384": + model = DPTDepthModel( + path=model_path, + backbone="swinl12_384", + non_negative=True, + ) + net_w, net_h = 384, 384 + keep_aspect_ratio = False + resize_mode = "minimal" + normalization = NormalizeImage(mean=[0.5, 0.5, 0.5], std=[0.5, 0.5, 0.5]) + + elif model_type == "dpt_next_vit_large_384": + model = DPTDepthModel( + path=model_path, + backbone="next_vit_large_6m", + non_negative=True, + ) + net_w, net_h = 384, 384 + resize_mode = "minimal" + normalization = NormalizeImage(mean=[0.5, 0.5, 0.5], std=[0.5, 0.5, 0.5]) + + # We change the notation from dpt_levit_224 (MiDaS notation) to levit_384 (timm notation) here, where the 224 refers + # to the resolution 224x224 used by LeViT and 384 is the first entry of the embed_dim, see _cfg and model_cfgs of + # https://github.com/rwightman/pytorch-image-models/blob/main/timm/models/levit.py + # (commit id: 927f031293a30afb940fff0bee34b85d9c059b0e) + elif model_type == "dpt_levit_224": + model = DPTDepthModel( + path=model_path, + backbone="levit_384", + non_negative=True, + head_features_1=64, + head_features_2=8, + ) + net_w, net_h = 224, 224 + keep_aspect_ratio = False + resize_mode = "minimal" + normalization = NormalizeImage(mean=[0.5, 0.5, 0.5], std=[0.5, 0.5, 0.5]) + + elif model_type == "dpt_large_384": + model = DPTDepthModel( + path=model_path, + backbone="vitl16_384", + non_negative=True, + ) + net_w, net_h = 384, 384 + resize_mode = "minimal" + normalization = NormalizeImage(mean=[0.5, 0.5, 0.5], std=[0.5, 0.5, 0.5]) + + elif model_type == "dpt_hybrid_384": + model = DPTDepthModel( + path=model_path, + backbone="vitb_rn50_384", + non_negative=True, + ) + net_w, net_h = 384, 384 + resize_mode = "minimal" + normalization = NormalizeImage(mean=[0.5, 0.5, 0.5], std=[0.5, 0.5, 0.5]) + + elif model_type == "midas_v21_384": + model = MidasNet(model_path, non_negative=True) + net_w, net_h = 384, 384 + resize_mode = "upper_bound" + normalization = NormalizeImage( + mean=[0.485, 0.456, 0.406], std=[0.229, 0.224, 0.225] + ) + + elif model_type == "midas_v21_small_256": + model = MidasNet_small(model_path, features=64, backbone="efficientnet_lite3", exportable=True, + non_negative=True, blocks={'expand': True}) + net_w, net_h = 256, 256 + resize_mode = "upper_bound" + normalization = NormalizeImage( + mean=[0.485, 0.456, 0.406], std=[0.229, 0.224, 0.225] + ) + + elif model_type == "openvino_midas_v21_small_256": + ie = Core() + uncompiled_model = ie.read_model(model=model_path) + model = ie.compile_model(uncompiled_model, "CPU") + net_w, net_h = 256, 256 + resize_mode = "upper_bound" + normalization = NormalizeImage( + mean=[0.485, 0.456, 0.406], std=[0.229, 0.224, 0.225] + ) + + else: + print(f"model_type '{model_type}' not implemented, use: --model_type large") + raise AssertionError + + if "openvino" not in model_type: + print("Model loaded, number of parameters = {:.0f}M".format(sum(p.numel() for p in model.parameters()) / 1e6)) + else: + print("Model loaded, optimized with OpenVINO") + + if "openvino" in model_type: + keep_aspect_ratio = False + + if height is not None: + net_w, net_h = height, height + + transform = Compose( + [ + Resize( + net_w, + net_h, + resize_target=None, + keep_aspect_ratio=keep_aspect_ratio, + ensure_multiple_of=32, + resize_method=resize_mode, + image_interpolation_method=cv2.INTER_CUBIC, + ), + normalization, + PrepareForNet(), + ] + ) + + if "openvino" not in model_type: + model.eval() + + if optimize and (device == torch.device("cuda")): + if "openvino" not in model_type: + model = model.to(memory_format=torch.channels_last) + model = model.half() + else: + print("Error: OpenVINO models are already optimized. No optimization to half-float possible.") + exit() + + if "openvino" not in model_type: + model.to(device) + + return model, transform, net_w, net_h diff --git a/modules/control/proc/zoe/zoedepth/models/base_models/midas_repo/midas/transforms.py b/modules/control/proc/zoe/zoedepth/models/base_models/midas_repo/midas/transforms.py new file mode 100644 index 000000000..350cbc116 --- /dev/null +++ b/modules/control/proc/zoe/zoedepth/models/base_models/midas_repo/midas/transforms.py @@ -0,0 +1,234 @@ +import numpy as np +import cv2 +import math + + +def apply_min_size(sample, size, image_interpolation_method=cv2.INTER_AREA): + """Rezise the sample to ensure the given size. Keeps aspect ratio. + + Args: + sample (dict): sample + size (tuple): image size + + Returns: + tuple: new size + """ + shape = list(sample["disparity"].shape) + + if shape[0] >= size[0] and shape[1] >= size[1]: + return sample + + scale = [0, 0] + scale[0] = size[0] / shape[0] + scale[1] = size[1] / shape[1] + + scale = max(scale) + + shape[0] = math.ceil(scale * shape[0]) + shape[1] = math.ceil(scale * shape[1]) + + # resize + sample["image"] = cv2.resize( + sample["image"], tuple(shape[::-1]), interpolation=image_interpolation_method + ) + + sample["disparity"] = cv2.resize( + sample["disparity"], tuple(shape[::-1]), interpolation=cv2.INTER_NEAREST + ) + sample["mask"] = cv2.resize( + sample["mask"].astype(np.float32), + tuple(shape[::-1]), + interpolation=cv2.INTER_NEAREST, + ) + sample["mask"] = sample["mask"].astype(bool) + + return tuple(shape) + + +class Resize(object): + """Resize sample to given size (width, height). + """ + + def __init__( + self, + width, + height, + resize_target=True, + keep_aspect_ratio=False, + ensure_multiple_of=1, + resize_method="lower_bound", + image_interpolation_method=cv2.INTER_AREA, + ): + """Init. + + Args: + width (int): desired output width + height (int): desired output height + resize_target (bool, optional): + True: Resize the full sample (image, mask, target). + False: Resize image only. + Defaults to True. + keep_aspect_ratio (bool, optional): + True: Keep the aspect ratio of the input sample. + Output sample might not have the given width and height, and + resize behaviour depends on the parameter 'resize_method'. + Defaults to False. + ensure_multiple_of (int, optional): + Output width and height is constrained to be multiple of this parameter. + Defaults to 1. + resize_method (str, optional): + "lower_bound": Output will be at least as large as the given size. + "upper_bound": Output will be at max as large as the given size. (Output size might be smaller than given size.) + "minimal": Scale as least as possible. (Output size might be smaller than given size.) + Defaults to "lower_bound". + """ + self.__width = width + self.__height = height + + self.__resize_target = resize_target + self.__keep_aspect_ratio = keep_aspect_ratio + self.__multiple_of = ensure_multiple_of + self.__resize_method = resize_method + self.__image_interpolation_method = image_interpolation_method + + def constrain_to_multiple_of(self, x, min_val=0, max_val=None): + y = (np.round(x / self.__multiple_of) * self.__multiple_of).astype(int) + + if max_val is not None and y > max_val: + y = (np.floor(x / self.__multiple_of) * self.__multiple_of).astype(int) + + if y < min_val: + y = (np.ceil(x / self.__multiple_of) * self.__multiple_of).astype(int) + + return y + + def get_size(self, width, height): + # determine new height and width + scale_height = self.__height / height + scale_width = self.__width / width + + if self.__keep_aspect_ratio: + if self.__resize_method == "lower_bound": + # scale such that output size is lower bound + if scale_width > scale_height: + # fit width + scale_height = scale_width + else: + # fit height + scale_width = scale_height + elif self.__resize_method == "upper_bound": + # scale such that output size is upper bound + if scale_width < scale_height: + # fit width + scale_height = scale_width + else: + # fit height + scale_width = scale_height + elif self.__resize_method == "minimal": + # scale as least as possbile + if abs(1 - scale_width) < abs(1 - scale_height): + # fit width + scale_height = scale_width + else: + # fit height + scale_width = scale_height + else: + raise ValueError( + f"resize_method {self.__resize_method} not implemented" + ) + + if self.__resize_method == "lower_bound": + new_height = self.constrain_to_multiple_of( + scale_height * height, min_val=self.__height + ) + new_width = self.constrain_to_multiple_of( + scale_width * width, min_val=self.__width + ) + elif self.__resize_method == "upper_bound": + new_height = self.constrain_to_multiple_of( + scale_height * height, max_val=self.__height + ) + new_width = self.constrain_to_multiple_of( + scale_width * width, max_val=self.__width + ) + elif self.__resize_method == "minimal": + new_height = self.constrain_to_multiple_of(scale_height * height) + new_width = self.constrain_to_multiple_of(scale_width * width) + else: + raise ValueError(f"resize_method {self.__resize_method} not implemented") + + return (new_width, new_height) + + def __call__(self, sample): + width, height = self.get_size( + sample["image"].shape[1], sample["image"].shape[0] + ) + + # resize sample + sample["image"] = cv2.resize( + sample["image"], + (width, height), + interpolation=self.__image_interpolation_method, + ) + + if self.__resize_target: + if "disparity" in sample: + sample["disparity"] = cv2.resize( + sample["disparity"], + (width, height), + interpolation=cv2.INTER_NEAREST, + ) + + if "depth" in sample: + sample["depth"] = cv2.resize( + sample["depth"], (width, height), interpolation=cv2.INTER_NEAREST + ) + + sample["mask"] = cv2.resize( + sample["mask"].astype(np.float32), + (width, height), + interpolation=cv2.INTER_NEAREST, + ) + sample["mask"] = sample["mask"].astype(bool) + + return sample + + +class NormalizeImage(object): + """Normlize image by given mean and std. + """ + + def __init__(self, mean, std): + self.__mean = mean + self.__std = std + + def __call__(self, sample): + sample["image"] = (sample["image"] - self.__mean) / self.__std + + return sample + + +class PrepareForNet(object): + """Prepare sample for usage as network input. + """ + + def __init__(self): + pass + + def __call__(self, sample): + image = np.transpose(sample["image"], (2, 0, 1)) + sample["image"] = np.ascontiguousarray(image).astype(np.float32) + + if "mask" in sample: + sample["mask"] = sample["mask"].astype(np.float32) + sample["mask"] = np.ascontiguousarray(sample["mask"]) + + if "disparity" in sample: + disparity = sample["disparity"].astype(np.float32) + sample["disparity"] = np.ascontiguousarray(disparity) + + if "depth" in sample: + depth = sample["depth"].astype(np.float32) + sample["depth"] = np.ascontiguousarray(depth) + + return sample diff --git a/modules/control/proc/zoe/zoedepth/models/builder.py b/modules/control/proc/zoe/zoedepth/models/builder.py new file mode 100644 index 000000000..39bad8d39 --- /dev/null +++ b/modules/control/proc/zoe/zoedepth/models/builder.py @@ -0,0 +1,51 @@ +# MIT License + +# Copyright (c) 2022 Intelligent Systems Lab Org + +# Permission is hereby granted, free of charge, to any person obtaining a copy +# of this software and associated documentation files (the "Software"), to deal +# in the Software without restriction, including without limitation the rights +# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +# copies of the Software, and to permit persons to whom the Software is +# furnished to do so, subject to the following conditions: + +# The above copyright notice and this permission notice shall be included in all +# copies or substantial portions of the Software. + +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +# SOFTWARE. + +# File author: Shariq Farooq Bhat + +from importlib import import_module +from .depth_model import DepthModel + +def build_model(config) -> DepthModel: + """Builds a model from a config. The model is specified by the model name and version in the config. The model is then constructed using the build_from_config function of the model interface. + This function should be used to construct models for training and evaluation. + + Args: + config (dict): Config dict. Config is constructed in utils/config.py. Each model has its own config file(s) saved in its root model folder. + + Returns: + torch.nn.Module: Model corresponding to name and version as specified in config + """ + module_name = f"zoedepth.models.{config.model}" + try: + module = import_module(module_name) + except ModuleNotFoundError as e: + # print the original error message + print(e) + raise ValueError( + f"Model {config.model} not found. Refer above error for details.") from e + try: + get_version = module.get_version + except AttributeError as e: + raise ValueError( + f"Model {config.model} has no get_version function.") from e + return get_version(config.version_name).build_from_config(config) diff --git a/modules/control/proc/zoe/zoedepth/models/depth_model.py b/modules/control/proc/zoe/zoedepth/models/depth_model.py new file mode 100644 index 000000000..37bb610fd --- /dev/null +++ b/modules/control/proc/zoe/zoedepth/models/depth_model.py @@ -0,0 +1,150 @@ +# MIT License + +# Copyright (c) 2022 Intelligent Systems Lab Org + +# Permission is hereby granted, free of charge, to any person obtaining a copy +# of this software and associated documentation files (the "Software"), to deal +# in the Software without restriction, including without limitation the rights +# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +# copies of the Software, and to permit persons to whom the Software is +# furnished to do so, subject to the following conditions: + +# The above copyright notice and this permission notice shall be included in all +# copies or substantial portions of the Software. + +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +# SOFTWARE. + +# File author: Shariq Farooq Bhat + +import numpy as np +import torch +import torch.nn as nn +import torch.nn.functional as F +from torchvision import transforms +import PIL.Image +from PIL import Image +from typing import Union + + +class DepthModel(nn.Module): + def __init__(self, device='cpu'): + super().__init__() + self.device = device + + def to(self, device) -> nn.Module: + self.device = device + return super().to(device) + + def forward(self, x, *args, **kwargs): + raise NotImplementedError + + def _infer(self, x: torch.Tensor): + """ + Inference interface for the model + Args: + x (torch.Tensor): input tensor of shape (b, c, h, w) + Returns: + torch.Tensor: output tensor of shape (b, 1, h, w) + """ + return self(x)['metric_depth'] + + def _infer_with_pad_aug(self, x: torch.Tensor, pad_input: bool=True, fh: float=3, fw: float=3, upsampling_mode: str='bicubic', padding_mode="reflect", **kwargs) -> torch.Tensor: + """ + Inference interface for the model with padding augmentation + Padding augmentation fixes the boundary artifacts in the output depth map. + Boundary artifacts are sometimes caused by the fact that the model is trained on NYU raw dataset which has a black or white border around the image. + This augmentation pads the input image and crops the prediction back to the original size / view. + + Note: This augmentation is not required for the models trained with 'avoid_boundary'=True. + Args: + x (torch.Tensor): input tensor of shape (b, c, h, w) + pad_input (bool, optional): whether to pad the input or not. Defaults to True. + fh (float, optional): height padding factor. The padding is calculated as sqrt(h/2) * fh. Defaults to 3. + fw (float, optional): width padding factor. The padding is calculated as sqrt(w/2) * fw. Defaults to 3. + upsampling_mode (str, optional): upsampling mode. Defaults to 'bicubic'. + padding_mode (str, optional): padding mode. Defaults to "reflect". + Returns: + torch.Tensor: output tensor of shape (b, 1, h, w) + """ + # assert x is nchw and c = 3 + assert x.dim() == 4, "x must be 4 dimensional, got {}".format(x.dim()) + assert x.shape[1] == 3, "x must have 3 channels, got {}".format(x.shape[1]) + + if pad_input: + assert fh > 0 or fw > 0, "atlease one of fh and fw must be greater than 0" + pad_h = int(np.sqrt(x.shape[2]/2) * fh) + pad_w = int(np.sqrt(x.shape[3]/2) * fw) + padding = [pad_w, pad_w] + if pad_h > 0: + padding += [pad_h, pad_h] + + x = F.pad(x, padding, mode=padding_mode, **kwargs) + out = self._infer(x) + if out.shape[-2:] != x.shape[-2:]: + out = F.interpolate(out, size=(x.shape[2], x.shape[3]), mode=upsampling_mode, align_corners=False) + if pad_input: + # crop to the original size, handling the case where pad_h and pad_w is 0 + if pad_h > 0: + out = out[:, :, pad_h:-pad_h,:] + if pad_w > 0: + out = out[:, :, :, pad_w:-pad_w] + return out + + def infer_with_flip_aug(self, x, pad_input: bool=True, **kwargs) -> torch.Tensor: + """ + Inference interface for the model with horizontal flip augmentation + Horizontal flip augmentation improves the accuracy of the model by averaging the output of the model with and without horizontal flip. + Args: + x (torch.Tensor): input tensor of shape (b, c, h, w) + pad_input (bool, optional): whether to use padding augmentation. Defaults to True. + Returns: + torch.Tensor: output tensor of shape (b, 1, h, w) + """ + # infer with horizontal flip and average + out = self._infer_with_pad_aug(x, pad_input=pad_input, **kwargs) + out_flip = self._infer_with_pad_aug(torch.flip(x, dims=[3]), pad_input=pad_input, **kwargs) + out = (out + torch.flip(out_flip, dims=[3])) / 2 + return out + + def infer(self, x, pad_input: bool=True, with_flip_aug: bool=True, **kwargs) -> torch.Tensor: + """ + Inference interface for the model + Args: + x (torch.Tensor): input tensor of shape (b, c, h, w) + pad_input (bool, optional): whether to use padding augmentation. Defaults to True. + with_flip_aug (bool, optional): whether to use horizontal flip augmentation. Defaults to True. + Returns: + torch.Tensor: output tensor of shape (b, 1, h, w) + """ + if with_flip_aug: + return self.infer_with_flip_aug(x, pad_input=pad_input, **kwargs) + else: + return self._infer_with_pad_aug(x, pad_input=pad_input, **kwargs) + + def infer_pil(self, pil_img, pad_input: bool=True, with_flip_aug: bool=True, output_type: str="numpy", **kwargs) -> Union[np.ndarray, PIL.Image.Image, torch.Tensor]: + """ + Inference interface for the model for PIL image + Args: + pil_img (PIL.Image.Image): input PIL image + pad_input (bool, optional): whether to use padding augmentation. Defaults to True. + with_flip_aug (bool, optional): whether to use horizontal flip augmentation. Defaults to True. + output_type (str, optional): output type. Supported values are 'numpy', 'pil' and 'tensor'. Defaults to "numpy". + """ + x = transforms.ToTensor()(pil_img).unsqueeze(0).to(self.device) + out_tensor = self.infer(x, pad_input=pad_input, with_flip_aug=with_flip_aug, **kwargs) + if output_type == "numpy": + return out_tensor.squeeze().cpu().numpy() + elif output_type == "pil": + # uint16 is required for depth pil image + out_16bit_numpy = (out_tensor.squeeze().cpu().numpy()*256).astype(np.uint16) + return Image.fromarray(out_16bit_numpy) + elif output_type == "tensor": + return out_tensor.squeeze().cpu() + else: + raise ValueError(f"output_type {output_type} not supported. Supported values are 'numpy', 'pil' and 'tensor'") diff --git a/modules/control/proc/zoe/zoedepth/models/layers/__init__.py b/modules/control/proc/zoe/zoedepth/models/layers/__init__.py new file mode 100644 index 000000000..c344f725c --- /dev/null +++ b/modules/control/proc/zoe/zoedepth/models/layers/__init__.py @@ -0,0 +1,23 @@ +# MIT License + +# Copyright (c) 2022 Intelligent Systems Lab Org + +# Permission is hereby granted, free of charge, to any person obtaining a copy +# of this software and associated documentation files (the "Software"), to deal +# in the Software without restriction, including without limitation the rights +# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +# copies of the Software, and to permit persons to whom the Software is +# furnished to do so, subject to the following conditions: + +# The above copyright notice and this permission notice shall be included in all +# copies or substantial portions of the Software. + +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +# SOFTWARE. + +# File author: Shariq Farooq Bhat diff --git a/modules/control/proc/zoe/zoedepth/models/layers/attractor.py b/modules/control/proc/zoe/zoedepth/models/layers/attractor.py new file mode 100644 index 000000000..c2fe653ed --- /dev/null +++ b/modules/control/proc/zoe/zoedepth/models/layers/attractor.py @@ -0,0 +1,208 @@ +# MIT License + +# Copyright (c) 2022 Intelligent Systems Lab Org + +# Permission is hereby granted, free of charge, to any person obtaining a copy +# of this software and associated documentation files (the "Software"), to deal +# in the Software without restriction, including without limitation the rights +# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +# copies of the Software, and to permit persons to whom the Software is +# furnished to do so, subject to the following conditions: + +# The above copyright notice and this permission notice shall be included in all +# copies or substantial portions of the Software. + +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +# SOFTWARE. + +# File author: Shariq Farooq Bhat + +import torch +import torch.nn as nn + + +@torch.jit.script +def exp_attractor(dx, alpha: float = 300, gamma: int = 2): + """Exponential attractor: dc = exp(-alpha*|dx|^gamma) * dx , where dx = a - c, a = attractor point, c = bin center, dc = shift in bin centermmary for exp_attractor + + Args: + dx (torch.Tensor): The difference tensor dx = Ai - Cj, where Ai is the attractor point and Cj is the bin center. + alpha (float, optional): Proportional Attractor strength. Determines the absolute strength. Lower alpha = greater attraction. Defaults to 300. + gamma (int, optional): Exponential Attractor strength. Determines the "region of influence" and indirectly number of bin centers affected. Lower gamma = farther reach. Defaults to 2. + + Returns: + torch.Tensor : Delta shifts - dc; New bin centers = Old bin centers + dc + """ + return torch.exp(-alpha*(torch.abs(dx)**gamma)) * (dx) + + +@torch.jit.script +def inv_attractor(dx, alpha: float = 300, gamma: int = 2): + """Inverse attractor: dc = dx / (1 + alpha*dx^gamma), where dx = a - c, a = attractor point, c = bin center, dc = shift in bin center + This is the default one according to the accompanying paper. + + Args: + dx (torch.Tensor): The difference tensor dx = Ai - Cj, where Ai is the attractor point and Cj is the bin center. + alpha (float, optional): Proportional Attractor strength. Determines the absolute strength. Lower alpha = greater attraction. Defaults to 300. + gamma (int, optional): Exponential Attractor strength. Determines the "region of influence" and indirectly number of bin centers affected. Lower gamma = farther reach. Defaults to 2. + + Returns: + torch.Tensor: Delta shifts - dc; New bin centers = Old bin centers + dc + """ + return dx.div(1+alpha*dx.pow(gamma)) + + +class AttractorLayer(nn.Module): + def __init__(self, in_features, n_bins, n_attractors=16, mlp_dim=128, min_depth=1e-3, max_depth=10, + alpha=300, gamma=2, kind='sum', attractor_type='exp', memory_efficient=False): + """ + Attractor layer for bin centers. Bin centers are bounded on the interval (min_depth, max_depth) + """ + super().__init__() + + self.n_attractors = n_attractors + self.n_bins = n_bins + self.min_depth = min_depth + self.max_depth = max_depth + self.alpha = alpha + self.gamma = gamma + self.kind = kind + self.attractor_type = attractor_type + self.memory_efficient = memory_efficient + + self._net = nn.Sequential( + nn.Conv2d(in_features, mlp_dim, 1, 1, 0), + nn.ReLU(inplace=True), + nn.Conv2d(mlp_dim, n_attractors*2, 1, 1, 0), # x2 for linear norm + nn.ReLU(inplace=True) + ) + + def forward(self, x, b_prev, prev_b_embedding=None, interpolate=True, is_for_query=False): + """ + Args: + x (torch.Tensor) : feature block; shape - n, c, h, w + b_prev (torch.Tensor) : previous bin centers normed; shape - n, prev_nbins, h, w + + Returns: + tuple(torch.Tensor,torch.Tensor) : new bin centers normed and scaled; shape - n, nbins, h, w + """ + if prev_b_embedding is not None: + if interpolate: + prev_b_embedding = nn.functional.interpolate( + prev_b_embedding, x.shape[-2:], mode='bilinear', align_corners=True) + x = x + prev_b_embedding + + A = self._net(x) + eps = 1e-3 + A = A + eps + n, c, h, w = A.shape + A = A.view(n, self.n_attractors, 2, h, w) + A_normed = A / A.sum(dim=2, keepdim=True) # n, a, 2, h, w + A_normed = A[:, :, 0, ...] # n, na, h, w + + b_prev = nn.functional.interpolate( + b_prev, (h, w), mode='bilinear', align_corners=True) + b_centers = b_prev + + if self.attractor_type == 'exp': + dist = exp_attractor + else: + dist = inv_attractor + + if not self.memory_efficient: + func = {'mean': torch.mean, 'sum': torch.sum}[self.kind] + # .shape N, nbins, h, w + delta_c = func(dist(A_normed.unsqueeze( + 2) - b_centers.unsqueeze(1)), dim=1) + else: + delta_c = torch.zeros_like(b_centers, device=b_centers.device) + for i in range(self.n_attractors): + # .shape N, nbins, h, w + delta_c += dist(A_normed[:, i, ...].unsqueeze(1) - b_centers) + + if self.kind == 'mean': + delta_c = delta_c / self.n_attractors + + b_new_centers = b_centers + delta_c + B_centers = (self.max_depth - self.min_depth) * \ + b_new_centers + self.min_depth + B_centers, _ = torch.sort(B_centers, dim=1) + B_centers = torch.clip(B_centers, self.min_depth, self.max_depth) + return b_new_centers, B_centers + + +class AttractorLayerUnnormed(nn.Module): + def __init__(self, in_features, n_bins, n_attractors=16, mlp_dim=128, min_depth=1e-3, max_depth=10, + alpha=300, gamma=2, kind='sum', attractor_type='exp', memory_efficient=False): + """ + Attractor layer for bin centers. Bin centers are unbounded + """ + super().__init__() + + self.n_attractors = n_attractors + self.n_bins = n_bins + self.min_depth = min_depth + self.max_depth = max_depth + self.alpha = alpha + self.gamma = gamma + self.kind = kind + self.attractor_type = attractor_type + self.memory_efficient = memory_efficient + + self._net = nn.Sequential( + nn.Conv2d(in_features, mlp_dim, 1, 1, 0), + nn.ReLU(inplace=True), + nn.Conv2d(mlp_dim, n_attractors, 1, 1, 0), + nn.Softplus() + ) + + def forward(self, x, b_prev, prev_b_embedding=None, interpolate=True, is_for_query=False): + """ + Args: + x (torch.Tensor) : feature block; shape - n, c, h, w + b_prev (torch.Tensor) : previous bin centers normed; shape - n, prev_nbins, h, w + + Returns: + tuple(torch.Tensor,torch.Tensor) : new bin centers unbounded; shape - n, nbins, h, w. Two outputs just to keep the API consistent with the normed version + """ + if prev_b_embedding is not None: + if interpolate: + prev_b_embedding = nn.functional.interpolate( + prev_b_embedding, x.shape[-2:], mode='bilinear', align_corners=True) + x = x + prev_b_embedding + + A = self._net(x) + n, c, h, w = A.shape + + b_prev = nn.functional.interpolate( + b_prev, (h, w), mode='bilinear', align_corners=True) + b_centers = b_prev + + if self.attractor_type == 'exp': + dist = exp_attractor + else: + dist = inv_attractor + + if not self.memory_efficient: + func = {'mean': torch.mean, 'sum': torch.sum}[self.kind] + # .shape N, nbins, h, w + delta_c = func( + dist(A.unsqueeze(2) - b_centers.unsqueeze(1)), dim=1) + else: + delta_c = torch.zeros_like(b_centers, device=b_centers.device) + for i in range(self.n_attractors): + delta_c += dist(A[:, i, ...].unsqueeze(1) - + b_centers) # .shape N, nbins, h, w + + if self.kind == 'mean': + delta_c = delta_c / self.n_attractors + + b_new_centers = b_centers + delta_c + B_centers = b_new_centers + + return b_new_centers, B_centers diff --git a/modules/control/proc/zoe/zoedepth/models/layers/dist_layers.py b/modules/control/proc/zoe/zoedepth/models/layers/dist_layers.py new file mode 100644 index 000000000..3208405df --- /dev/null +++ b/modules/control/proc/zoe/zoedepth/models/layers/dist_layers.py @@ -0,0 +1,121 @@ +# MIT License + +# Copyright (c) 2022 Intelligent Systems Lab Org + +# Permission is hereby granted, free of charge, to any person obtaining a copy +# of this software and associated documentation files (the "Software"), to deal +# in the Software without restriction, including without limitation the rights +# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +# copies of the Software, and to permit persons to whom the Software is +# furnished to do so, subject to the following conditions: + +# The above copyright notice and this permission notice shall be included in all +# copies or substantial portions of the Software. + +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +# SOFTWARE. + +# File author: Shariq Farooq Bhat + +import torch +import torch.nn as nn + + +def log_binom(n, k, eps=1e-7): + """ log(nCk) using stirling approximation """ + n = n + eps + k = k + eps + return n * torch.log(n) - k * torch.log(k) - (n-k) * torch.log(n-k+eps) + + +class LogBinomial(nn.Module): + def __init__(self, n_classes=256, act=torch.softmax): + """Compute log binomial distribution for n_classes + + Args: + n_classes (int, optional): number of output classes. Defaults to 256. + """ + super().__init__() + self.K = n_classes + self.act = act + self.register_buffer('k_idx', torch.arange( + 0, n_classes).view(1, -1, 1, 1)) + self.register_buffer('K_minus_1', torch.Tensor( + [self.K-1]).view(1, -1, 1, 1)) + + def forward(self, x, t=1., eps=1e-4): + """Compute log binomial distribution for x + + Args: + x (torch.Tensor - NCHW): probabilities + t (float, torch.Tensor - NCHW, optional): Temperature of distribution. Defaults to 1.. + eps (float, optional): Small number for numerical stability. Defaults to 1e-4. + + Returns: + torch.Tensor -NCHW: log binomial distribution logbinomial(p;t) + """ + if x.ndim == 3: + x = x.unsqueeze(1) # make it nchw + + one_minus_x = torch.clamp(1 - x, eps, 1) + x = torch.clamp(x, eps, 1) + y = log_binom(self.K_minus_1, self.k_idx) + self.k_idx * \ + torch.log(x) + (self.K - 1 - self.k_idx) * torch.log(one_minus_x) + return self.act(y/t, dim=1) + + +class ConditionalLogBinomial(nn.Module): + def __init__(self, in_features, condition_dim, n_classes=256, bottleneck_factor=2, p_eps=1e-4, max_temp=50, min_temp=1e-7, act=torch.softmax): + """Conditional Log Binomial distribution + + Args: + in_features (int): number of input channels in main feature + condition_dim (int): number of input channels in condition feature + n_classes (int, optional): Number of classes. Defaults to 256. + bottleneck_factor (int, optional): Hidden dim factor. Defaults to 2. + p_eps (float, optional): small eps value. Defaults to 1e-4. + max_temp (float, optional): Maximum temperature of output distribution. Defaults to 50. + min_temp (float, optional): Minimum temperature of output distribution. Defaults to 1e-7. + """ + super().__init__() + self.p_eps = p_eps + self.max_temp = max_temp + self.min_temp = min_temp + self.log_binomial_transform = LogBinomial(n_classes, act=act) + bottleneck = (in_features + condition_dim) // bottleneck_factor + self.mlp = nn.Sequential( + nn.Conv2d(in_features + condition_dim, bottleneck, + kernel_size=1, stride=1, padding=0), + nn.GELU(), + # 2 for p linear norm, 2 for t linear norm + nn.Conv2d(bottleneck, 2+2, kernel_size=1, stride=1, padding=0), + nn.Softplus() + ) + + def forward(self, x, cond): + """Forward pass + + Args: + x (torch.Tensor - NCHW): Main feature + cond (torch.Tensor - NCHW): condition feature + + Returns: + torch.Tensor: Output log binomial distribution + """ + pt = self.mlp(torch.concat((x, cond), dim=1)) + p, t = pt[:, :2, ...], pt[:, 2:, ...] + + p = p + self.p_eps + p = p[:, 0, ...] / (p[:, 0, ...] + p[:, 1, ...]) + + t = t + self.p_eps + t = t[:, 0, ...] / (t[:, 0, ...] + t[:, 1, ...]) + t = t.unsqueeze(1) + t = (self.max_temp - self.min_temp) * t + self.min_temp + + return self.log_binomial_transform(p, t) diff --git a/modules/control/proc/zoe/zoedepth/models/layers/localbins_layers.py b/modules/control/proc/zoe/zoedepth/models/layers/localbins_layers.py new file mode 100644 index 000000000..91d08de0f --- /dev/null +++ b/modules/control/proc/zoe/zoedepth/models/layers/localbins_layers.py @@ -0,0 +1,169 @@ +# MIT License + +# Copyright (c) 2022 Intelligent Systems Lab Org + +# Permission is hereby granted, free of charge, to any person obtaining a copy +# of this software and associated documentation files (the "Software"), to deal +# in the Software without restriction, including without limitation the rights +# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +# copies of the Software, and to permit persons to whom the Software is +# furnished to do so, subject to the following conditions: + +# The above copyright notice and this permission notice shall be included in all +# copies or substantial portions of the Software. + +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +# SOFTWARE. + +# File author: Shariq Farooq Bhat + +import torch +import torch.nn as nn + + +class SeedBinRegressor(nn.Module): + def __init__(self, in_features, n_bins=16, mlp_dim=256, min_depth=1e-3, max_depth=10): + """Bin center regressor network. Bin centers are bounded on (min_depth, max_depth) interval. + + Args: + in_features (int): input channels + n_bins (int, optional): Number of bin centers. Defaults to 16. + mlp_dim (int, optional): Hidden dimension. Defaults to 256. + min_depth (float, optional): Min depth value. Defaults to 1e-3. + max_depth (float, optional): Max depth value. Defaults to 10. + """ + super().__init__() + self.version = "1_1" + self.min_depth = min_depth + self.max_depth = max_depth + + self._net = nn.Sequential( + nn.Conv2d(in_features, mlp_dim, 1, 1, 0), + nn.ReLU(inplace=True), + nn.Conv2d(mlp_dim, n_bins, 1, 1, 0), + nn.ReLU(inplace=True) + ) + + def forward(self, x): + """ + Returns tensor of bin_width vectors (centers). One vector b for every pixel + """ + B = self._net(x) + eps = 1e-3 + B = B + eps + B_widths_normed = B / B.sum(dim=1, keepdim=True) + B_widths = (self.max_depth - self.min_depth) * \ + B_widths_normed # .shape NCHW + # pad has the form (left, right, top, bottom, front, back) + B_widths = nn.functional.pad( + B_widths, (0, 0, 0, 0, 1, 0), mode='constant', value=self.min_depth) + B_edges = torch.cumsum(B_widths, dim=1) # .shape NCHW + + B_centers = 0.5 * (B_edges[:, :-1, ...] + B_edges[:, 1:, ...]) + return B_widths_normed, B_centers + + +class SeedBinRegressorUnnormed(nn.Module): + def __init__(self, in_features, n_bins=16, mlp_dim=256, min_depth=1e-3, max_depth=10): + """Bin center regressor network. Bin centers are unbounded + + Args: + in_features (int): input channels + n_bins (int, optional): Number of bin centers. Defaults to 16. + mlp_dim (int, optional): Hidden dimension. Defaults to 256. + min_depth (float, optional): Not used. (for compatibility with SeedBinRegressor) + max_depth (float, optional): Not used. (for compatibility with SeedBinRegressor) + """ + super().__init__() + self.version = "1_1" + self._net = nn.Sequential( + nn.Conv2d(in_features, mlp_dim, 1, 1, 0), + nn.ReLU(inplace=True), + nn.Conv2d(mlp_dim, n_bins, 1, 1, 0), + nn.Softplus() + ) + + def forward(self, x): + """ + Returns tensor of bin_width vectors (centers). One vector b for every pixel + """ + B_centers = self._net(x) + return B_centers, B_centers + + +class Projector(nn.Module): + def __init__(self, in_features, out_features, mlp_dim=128): + """Projector MLP + + Args: + in_features (int): input channels + out_features (int): output channels + mlp_dim (int, optional): hidden dimension. Defaults to 128. + """ + super().__init__() + + self._net = nn.Sequential( + nn.Conv2d(in_features, mlp_dim, 1, 1, 0), + nn.ReLU(inplace=True), + nn.Conv2d(mlp_dim, out_features, 1, 1, 0), + ) + + def forward(self, x): + return self._net(x) + + + +class LinearSplitter(nn.Module): + def __init__(self, in_features, prev_nbins, split_factor=2, mlp_dim=128, min_depth=1e-3, max_depth=10): + super().__init__() + + self.prev_nbins = prev_nbins + self.split_factor = split_factor + self.min_depth = min_depth + self.max_depth = max_depth + + self._net = nn.Sequential( + nn.Conv2d(in_features, mlp_dim, 1, 1, 0), + nn.GELU(), + nn.Conv2d(mlp_dim, prev_nbins * split_factor, 1, 1, 0), + nn.ReLU() + ) + + def forward(self, x, b_prev, prev_b_embedding=None, interpolate=True, is_for_query=False): + """ + x : feature block; shape - n, c, h, w + b_prev : previous bin widths normed; shape - n, prev_nbins, h, w + """ + if prev_b_embedding is not None: + if interpolate: + prev_b_embedding = nn.functional.interpolate(prev_b_embedding, x.shape[-2:], mode='bilinear', align_corners=True) + x = x + prev_b_embedding + S = self._net(x) + eps = 1e-3 + S = S + eps + n, c, h, w = S.shape + S = S.view(n, self.prev_nbins, self.split_factor, h, w) + S_normed = S / S.sum(dim=2, keepdim=True) # fractional splits + + b_prev = nn.functional.interpolate(b_prev, (h,w), mode='bilinear', align_corners=True) + + + b_prev = b_prev / b_prev.sum(dim=1, keepdim=True) # renormalize for gurantees + # print(b_prev.shape, S_normed.shape) + # if is_for_query:(1).expand(-1, b_prev.size(0)//n, -1, -1, -1, -1).flatten(0,1) # TODO ? can replace all this with a single torch.repeat? + b = b_prev.unsqueeze(2) * S_normed + b = b.flatten(1,2) # .shape n, prev_nbins * split_factor, h, w + + # calculate bin centers for loss calculation + B_widths = (self.max_depth - self.min_depth) * b # .shape N, nprev * splitfactor, H, W + # pad has the form (left, right, top, bottom, front, back) + B_widths = nn.functional.pad(B_widths, (0,0,0,0,1,0), mode='constant', value=self.min_depth) + B_edges = torch.cumsum(B_widths, dim=1) # .shape NCHW + + B_centers = 0.5 * (B_edges[:, :-1, ...] + B_edges[:,1:,...]) + return b, B_centers diff --git a/modules/control/proc/zoe/zoedepth/models/layers/patch_transformer.py b/modules/control/proc/zoe/zoedepth/models/layers/patch_transformer.py new file mode 100644 index 000000000..23386a068 --- /dev/null +++ b/modules/control/proc/zoe/zoedepth/models/layers/patch_transformer.py @@ -0,0 +1,91 @@ +# MIT License + +# Copyright (c) 2022 Intelligent Systems Lab Org + +# Permission is hereby granted, free of charge, to any person obtaining a copy +# of this software and associated documentation files (the "Software"), to deal +# in the Software without restriction, including without limitation the rights +# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +# copies of the Software, and to permit persons to whom the Software is +# furnished to do so, subject to the following conditions: + +# The above copyright notice and this permission notice shall be included in all +# copies or substantial portions of the Software. + +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +# SOFTWARE. + +# File author: Shariq Farooq Bhat + +import torch +import torch.nn as nn + + +class PatchTransformerEncoder(nn.Module): + def __init__(self, in_channels, patch_size=10, embedding_dim=128, num_heads=4, use_class_token=False): + """ViT-like transformer block + + Args: + in_channels (int): Input channels + patch_size (int, optional): patch size. Defaults to 10. + embedding_dim (int, optional): Embedding dimension in transformer model. Defaults to 128. + num_heads (int, optional): number of attention heads. Defaults to 4. + use_class_token (bool, optional): Whether to use extra token at the start for global accumulation (called as "class token"). Defaults to False. + """ + super(PatchTransformerEncoder, self).__init__() + self.use_class_token = use_class_token + encoder_layers = nn.TransformerEncoderLayer( + embedding_dim, num_heads, dim_feedforward=1024) + self.transformer_encoder = nn.TransformerEncoder( + encoder_layers, num_layers=4) # takes shape S,N,E + + self.embedding_convPxP = nn.Conv2d(in_channels, embedding_dim, + kernel_size=patch_size, stride=patch_size, padding=0) + + def positional_encoding_1d(self, sequence_length, batch_size, embedding_dim, device='cpu'): + """Generate positional encodings + + Args: + sequence_length (int): Sequence length + embedding_dim (int): Embedding dimension + + Returns: + torch.Tensor SBE: Positional encodings + """ + position = torch.arange( + 0, sequence_length, dtype=torch.float32, device=device).unsqueeze(1) + index = torch.arange( + 0, embedding_dim, 2, dtype=torch.float32, device=device).unsqueeze(0) + div_term = torch.exp(index * (-torch.log(torch.tensor(10000.0, device=device)) / embedding_dim)) + pos_encoding = position * div_term + pos_encoding = torch.cat([torch.sin(pos_encoding), torch.cos(pos_encoding)], dim=1) + pos_encoding = pos_encoding.unsqueeze(1).repeat(1, batch_size, 1) + return pos_encoding + + + def forward(self, x): + """Forward pass + + Args: + x (torch.Tensor - NCHW): Input feature tensor + + Returns: + torch.Tensor - SNE: Transformer output embeddings. S - sequence length (=HW/patch_size^2), N - batch size, E - embedding dim + """ + embeddings = self.embedding_convPxP(x).flatten( + 2) # .shape = n,c,s = n, embedding_dim, s + if self.use_class_token: + # extra special token at start ? + embeddings = nn.functional.pad(embeddings, (1, 0)) + + # change to S,N,E format required by transformer + embeddings = embeddings.permute(2, 0, 1) + S, N, E = embeddings.shape + embeddings = embeddings + self.positional_encoding_1d(S, N, E, device=embeddings.device) + x = self.transformer_encoder(embeddings) # .shape = S, N, E + return x diff --git a/modules/control/proc/zoe/zoedepth/models/model_io.py b/modules/control/proc/zoe/zoedepth/models/model_io.py new file mode 100644 index 000000000..c42f51641 --- /dev/null +++ b/modules/control/proc/zoe/zoedepth/models/model_io.py @@ -0,0 +1,91 @@ +# MIT License + +# Copyright (c) 2022 Intelligent Systems Lab Org + +# Permission is hereby granted, free of charge, to any person obtaining a copy +# of this software and associated documentation files (the "Software"), to deal +# in the Software without restriction, including without limitation the rights +# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +# copies of the Software, and to permit persons to whom the Software is +# furnished to do so, subject to the following conditions: + +# The above copyright notice and this permission notice shall be included in all +# copies or substantial portions of the Software. + +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +# SOFTWARE. + +# File author: Shariq Farooq Bhat + +import torch + +def load_state_dict(model, state_dict): + """Load state_dict into model, handling DataParallel and DistributedDataParallel. Also checks for "model" key in state_dict. + + DataParallel prefixes state_dict keys with 'module.' when saving. + If the model is not a DataParallel model but the state_dict is, then prefixes are removed. + If the model is a DataParallel model but the state_dict is not, then prefixes are added. + """ + state_dict = state_dict.get('model', state_dict) + # if model is a DataParallel model, then state_dict keys are prefixed with 'module.' + + do_prefix = isinstance( + model, (torch.nn.DataParallel, torch.nn.parallel.DistributedDataParallel)) + state = {} + for k, v in state_dict.items(): + if k.startswith('module.') and not do_prefix: + k = k[7:] + + if not k.startswith('module.') and do_prefix: + k = 'module.' + k + + state[k] = v + + model.load_state_dict(state) + print("Loaded successfully") + return model + + +def load_wts(model, checkpoint_path): + ckpt = torch.load(checkpoint_path, map_location='cpu') + return load_state_dict(model, ckpt) + + +def load_state_dict_from_url(model, url, **kwargs): + state_dict = torch.hub.load_state_dict_from_url(url, map_location='cpu', **kwargs) + return load_state_dict(model, state_dict) + + +def load_state_from_resource(model, resource: str): + """Loads weights to the model from a given resource. A resource can be of following types: + 1. URL. Prefixed with "url::" + e.g. url::http(s)://url.resource.com/ckpt.pt + + 2. Local path. Prefixed with "local::" + e.g. local::/path/to/ckpt.pt + + + Args: + model (torch.nn.Module): Model + resource (str): resource string + + Returns: + torch.nn.Module: Model with loaded weights + """ + print(f"Using pretrained resource {resource}") + + if resource.startswith('url::'): + url = resource.split('url::')[1] + return load_state_dict_from_url(model, url, progress=True) + + elif resource.startswith('local::'): + path = resource.split('local::')[1] + return load_wts(model, path) + + else: + raise ValueError("Invalid resource type, only url:: and local:: are supported") diff --git a/modules/control/proc/zoe/zoedepth/models/zoedepth/__init__.py b/modules/control/proc/zoe/zoedepth/models/zoedepth/__init__.py new file mode 100644 index 000000000..8532e9b9b --- /dev/null +++ b/modules/control/proc/zoe/zoedepth/models/zoedepth/__init__.py @@ -0,0 +1,31 @@ +# MIT License + +# Copyright (c) 2022 Intelligent Systems Lab Org + +# Permission is hereby granted, free of charge, to any person obtaining a copy +# of this software and associated documentation files (the "Software"), to deal +# in the Software without restriction, including without limitation the rights +# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +# copies of the Software, and to permit persons to whom the Software is +# furnished to do so, subject to the following conditions: + +# The above copyright notice and this permission notice shall be included in all +# copies or substantial portions of the Software. + +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +# SOFTWARE. + +# File author: Shariq Farooq Bhat + +from .zoedepth_v1 import ZoeDepth + +all_versions = { + "v1": ZoeDepth, +} + +get_version = lambda v : all_versions[v] diff --git a/modules/control/proc/zoe/zoedepth/models/zoedepth/config_zoedepth.json b/modules/control/proc/zoe/zoedepth/models/zoedepth/config_zoedepth.json new file mode 100644 index 000000000..3112ed78c --- /dev/null +++ b/modules/control/proc/zoe/zoedepth/models/zoedepth/config_zoedepth.json @@ -0,0 +1,58 @@ +{ + "model": { + "name": "ZoeDepth", + "version_name": "v1", + "n_bins": 64, + "bin_embedding_dim": 128, + "bin_centers_type": "softplus", + "n_attractors":[16, 8, 4, 1], + "attractor_alpha": 1000, + "attractor_gamma": 2, + "attractor_kind" : "mean", + "attractor_type" : "inv", + "midas_model_type" : "DPT_BEiT_L_384", + "min_temp": 0.0212, + "max_temp": 50.0, + "output_distribution": "logbinomial", + "memory_efficient": true, + "inverse_midas": false, + "img_size": [384, 512] + }, + + "train": { + "train_midas": true, + "use_pretrained_midas": true, + "trainer": "zoedepth", + "epochs": 5, + "bs": 16, + "optim_kwargs": {"lr": 0.000161, "wd": 0.01}, + "sched_kwargs": {"div_factor": 1, "final_div_factor": 10000, "pct_start": 0.7, "three_phase":false, "cycle_momentum": true}, + "same_lr": false, + "w_si": 1, + "w_domain": 0.2, + "w_reg": 0, + "w_grad": 0, + "avoid_boundary": false, + "random_crop": false, + "input_width": 640, + "input_height": 480, + "midas_lr_factor": 1, + "encoder_lr_factor":10, + "pos_enc_lr_factor":10, + "freeze_midas_bn": true + + }, + + "infer":{ + "train_midas": false, + "use_pretrained_midas": false, + "pretrained_resource" : null, + "force_keep_ar": true + }, + + "eval":{ + "train_midas": false, + "use_pretrained_midas": false, + "pretrained_resource" : null + } +} \ No newline at end of file diff --git a/modules/control/proc/zoe/zoedepth/models/zoedepth/config_zoedepth_kitti.json b/modules/control/proc/zoe/zoedepth/models/zoedepth/config_zoedepth_kitti.json new file mode 100644 index 000000000..b51802aa4 --- /dev/null +++ b/modules/control/proc/zoe/zoedepth/models/zoedepth/config_zoedepth_kitti.json @@ -0,0 +1,22 @@ +{ + "model": { + "bin_centers_type": "normed", + "img_size": [384, 768] + }, + + "train": { + }, + + "infer":{ + "train_midas": false, + "use_pretrained_midas": false, + "pretrained_resource" : "url::https://github.com/isl-org/ZoeDepth/releases/download/v1.0/ZoeD_M12_K.pt", + "force_keep_ar": true + }, + + "eval":{ + "train_midas": false, + "use_pretrained_midas": false, + "pretrained_resource" : "url::https://github.com/isl-org/ZoeDepth/releases/download/v1.0/ZoeD_M12_K.pt" + } +} \ No newline at end of file diff --git a/modules/control/proc/zoe/zoedepth/models/zoedepth/zoedepth_v1.py b/modules/control/proc/zoe/zoedepth/models/zoedepth/zoedepth_v1.py new file mode 100644 index 000000000..1705442c6 --- /dev/null +++ b/modules/control/proc/zoe/zoedepth/models/zoedepth/zoedepth_v1.py @@ -0,0 +1,252 @@ +# MIT License + +# Copyright (c) 2022 Intelligent Systems Lab Org + +# Permission is hereby granted, free of charge, to any person obtaining a copy +# of this software and associated documentation files (the "Software"), to deal +# in the Software without restriction, including without limitation the rights +# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +# copies of the Software, and to permit persons to whom the Software is +# furnished to do so, subject to the following conditions: + +# The above copyright notice and this permission notice shall be included in all +# copies or substantial portions of the Software. + +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +# SOFTWARE. + +# File author: Shariq Farooq Bhat + +import itertools + +import torch +import torch.nn as nn +from ..depth_model import DepthModel +from ..base_models.midas import MidasCore +from ..layers.attractor import AttractorLayer, AttractorLayerUnnormed +from ..layers.dist_layers import ConditionalLogBinomial +from ..layers.localbins_layers import (Projector, SeedBinRegressor, + SeedBinRegressorUnnormed) +from ..model_io import load_state_from_resource + + +class ZoeDepth(DepthModel): + def __init__(self, core, n_bins=64, bin_centers_type="softplus", bin_embedding_dim=128, min_depth=1e-3, max_depth=10, + n_attractors=None, attractor_alpha=300, attractor_gamma=2, attractor_kind='sum', attractor_type='exp', min_temp=5, max_temp=50, train_midas=True, + midas_lr_factor=10, encoder_lr_factor=10, pos_enc_lr_factor=10, inverse_midas=False, **kwargs): + """ZoeDepth model. This is the version of ZoeDepth that has a single metric head + + Args: + core (models.base_models.midas.MidasCore): The base midas model that is used for extraction of "relative" features + n_bins (int, optional): Number of bin centers. Defaults to 64. + bin_centers_type (str, optional): "normed" or "softplus". Activation type used for bin centers. For "normed" bin centers, linear normalization trick is applied. This results in bounded bin centers. + For "softplus", softplus activation is used and thus are unbounded. Defaults to "softplus". + bin_embedding_dim (int, optional): bin embedding dimension. Defaults to 128. + min_depth (float, optional): Lower bound for normed bin centers. Defaults to 1e-3. + max_depth (float, optional): Upper bound for normed bin centers. Defaults to 10. + n_attractors (List[int], optional): Number of bin attractors at decoder layers. Defaults to [16, 8, 4, 1]. + attractor_alpha (int, optional): Proportional attractor strength. Refer to models.layers.attractor for more details. Defaults to 300. + attractor_gamma (int, optional): Exponential attractor strength. Refer to models.layers.attractor for more details. Defaults to 2. + attractor_kind (str, optional): Attraction aggregation "sum" or "mean". Defaults to 'sum'. + attractor_type (str, optional): Type of attractor to use; "inv" (Inverse attractor) or "exp" (Exponential attractor). Defaults to 'exp'. + min_temp (int, optional): Lower bound for temperature of output probability distribution. Defaults to 5. + max_temp (int, optional): Upper bound for temperature of output probability distribution. Defaults to 50. + train_midas (bool, optional): Whether to train "core", the base midas model. Defaults to True. + midas_lr_factor (int, optional): Learning rate reduction factor for base midas model except its encoder and positional encodings. Defaults to 10. + encoder_lr_factor (int, optional): Learning rate reduction factor for the encoder in midas model. Defaults to 10. + pos_enc_lr_factor (int, optional): Learning rate reduction factor for positional encodings in the base midas model. Defaults to 10. + """ + if n_attractors is None: + n_attractors = [16, 8, 4, 1] + super().__init__() + + self.core = core + self.max_depth = max_depth + self.min_depth = min_depth + self.min_temp = min_temp + self.bin_centers_type = bin_centers_type + + self.midas_lr_factor = midas_lr_factor + self.encoder_lr_factor = encoder_lr_factor + self.pos_enc_lr_factor = pos_enc_lr_factor + self.train_midas = train_midas + self.inverse_midas = inverse_midas + + if self.encoder_lr_factor <= 0: + self.core.freeze_encoder( + freeze_rel_pos=self.pos_enc_lr_factor <= 0) + + N_MIDAS_OUT = 32 + btlnck_features = self.core.output_channels[0] + num_out_features = self.core.output_channels[1:] + + self.conv2 = nn.Conv2d(btlnck_features, btlnck_features, + kernel_size=1, stride=1, padding=0) # btlnck conv + + if bin_centers_type == "normed": + SeedBinRegressorLayer = SeedBinRegressor + Attractor = AttractorLayer + elif bin_centers_type == "softplus": + SeedBinRegressorLayer = SeedBinRegressorUnnormed + Attractor = AttractorLayerUnnormed + elif bin_centers_type == "hybrid1": + SeedBinRegressorLayer = SeedBinRegressor + Attractor = AttractorLayerUnnormed + elif bin_centers_type == "hybrid2": + SeedBinRegressorLayer = SeedBinRegressorUnnormed + Attractor = AttractorLayer + else: + raise ValueError( + "bin_centers_type should be one of 'normed', 'softplus', 'hybrid1', 'hybrid2'") + + self.seed_bin_regressor = SeedBinRegressorLayer( + btlnck_features, n_bins=n_bins, min_depth=min_depth, max_depth=max_depth) + self.seed_projector = Projector(btlnck_features, bin_embedding_dim) + self.projectors = nn.ModuleList([ + Projector(num_out, bin_embedding_dim) + for num_out in num_out_features + ]) + self.attractors = nn.ModuleList([ + Attractor(bin_embedding_dim, n_bins, n_attractors=n_attractors[i], min_depth=min_depth, max_depth=max_depth, + alpha=attractor_alpha, gamma=attractor_gamma, kind=attractor_kind, attractor_type=attractor_type) + for i in range(len(num_out_features)) + ]) + + last_in = N_MIDAS_OUT + 1 # +1 for relative depth + + # use log binomial instead of softmax + self.conditional_log_binomial = ConditionalLogBinomial( + last_in, bin_embedding_dim, n_classes=n_bins, min_temp=min_temp, max_temp=max_temp) + + def forward(self, x, return_final_centers=False, denorm=False, return_probs=False, **kwargs): + """ + Args: + x (torch.Tensor): Input image tensor of shape (B, C, H, W) + return_final_centers (bool, optional): Whether to return the final bin centers. Defaults to False. + denorm (bool, optional): Whether to denormalize the input image. This reverses ImageNet normalization as midas normalization is different. Defaults to False. + return_probs (bool, optional): Whether to return the output probability distribution. Defaults to False. + + Returns: + dict: Dictionary containing the following keys: + - rel_depth (torch.Tensor): Relative depth map of shape (B, H, W) + - metric_depth (torch.Tensor): Metric depth map of shape (B, 1, H, W) + - bin_centers (torch.Tensor): Bin centers of shape (B, n_bins). Present only if return_final_centers is True + - probs (torch.Tensor): Output probability distribution of shape (B, n_bins, H, W). Present only if return_probs is True + + """ + b, c, h, w = x.shape + # print("input shape ", x.shape) + self.orig_input_width = w + self.orig_input_height = h + rel_depth, out = self.core(x, denorm=denorm, return_rel_depth=True) + # print("output shapes", rel_depth.shape, out.shape) + + outconv_activation = out[0] + btlnck = out[1] + x_blocks = out[2:] + + x_d0 = self.conv2(btlnck) + x = x_d0 + _, seed_b_centers = self.seed_bin_regressor(x) + + if self.bin_centers_type == 'normed' or self.bin_centers_type == 'hybrid2': + b_prev = (seed_b_centers - self.min_depth) / \ + (self.max_depth - self.min_depth) + else: + b_prev = seed_b_centers + + prev_b_embedding = self.seed_projector(x) + + # unroll this loop for better performance + for projector, attractor, x in zip(self.projectors, self.attractors, x_blocks): + b_embedding = projector(x) + b, b_centers = attractor( + b_embedding, b_prev, prev_b_embedding, interpolate=True) + b_prev = b.clone() + prev_b_embedding = b_embedding.clone() + + last = outconv_activation + + if self.inverse_midas: + # invert depth followed by normalization + rel_depth = 1.0 / (rel_depth + 1e-6) + rel_depth = (rel_depth - rel_depth.min()) / \ + (rel_depth.max() - rel_depth.min()) + # concat rel depth with last. First interpolate rel depth to last size + rel_cond = rel_depth.unsqueeze(1) + rel_cond = nn.functional.interpolate( + rel_cond, size=last.shape[2:], mode='bilinear', align_corners=True) + last = torch.cat([last, rel_cond], dim=1) + + b_embedding = nn.functional.interpolate( + b_embedding, last.shape[-2:], mode='bilinear', align_corners=True) + x = self.conditional_log_binomial(last, b_embedding) + + # Now depth value is Sum px * cx , where cx are bin_centers from the last bin tensor + # print(x.shape, b_centers.shape) + b_centers = nn.functional.interpolate( + b_centers, x.shape[-2:], mode='bilinear', align_corners=True) + out = torch.sum(x * b_centers, dim=1, keepdim=True) + + # Structure output dict + output = dict(metric_depth=out) + if return_final_centers or return_probs: + output['bin_centers'] = b_centers + + if return_probs: + output['probs'] = x + + return output + + def get_lr_params(self, lr): + """ + Learning rate configuration for different layers of the model + Args: + lr (float) : Base learning rate + Returns: + list : list of parameters to optimize and their learning rates, in the format required by torch optimizers. + """ + param_conf = [] + if self.train_midas: + if self.encoder_lr_factor > 0: + param_conf.append({'params': self.core.get_enc_params_except_rel_pos( + ), 'lr': lr / self.encoder_lr_factor}) + + if self.pos_enc_lr_factor > 0: + param_conf.append( + {'params': self.core.get_rel_pos_params(), 'lr': lr / self.pos_enc_lr_factor}) + + midas_params = self.core.core.scratch.parameters() + midas_lr_factor = self.midas_lr_factor + param_conf.append( + {'params': midas_params, 'lr': lr / midas_lr_factor}) + + remaining_modules = [] + for name, child in self.named_children(): + if name != 'core': + remaining_modules.append(child) + remaining_params = itertools.chain( + *[child.parameters() for child in remaining_modules]) + + param_conf.append({'params': remaining_params, 'lr': lr}) + + return param_conf + + @staticmethod + def build(midas_model_type="DPT_BEiT_L_384", pretrained_resource=None, use_pretrained_midas=False, train_midas=False, freeze_midas_bn=True, **kwargs): + core = MidasCore.build(midas_model_type=midas_model_type, use_pretrained_midas=use_pretrained_midas, + train_midas=train_midas, fetch_features=True, freeze_bn=freeze_midas_bn, **kwargs) + model = ZoeDepth(core, **kwargs) + if pretrained_resource: + assert isinstance(pretrained_resource, str), "pretrained_resource must be a string" + model = load_state_from_resource(model, pretrained_resource) + return model + + @staticmethod + def build_from_config(config): + return ZoeDepth.build(**config) diff --git a/modules/control/proc/zoe/zoedepth/models/zoedepth_nk/__init__.py b/modules/control/proc/zoe/zoedepth/models/zoedepth_nk/__init__.py new file mode 100644 index 000000000..61cd507ca --- /dev/null +++ b/modules/control/proc/zoe/zoedepth/models/zoedepth_nk/__init__.py @@ -0,0 +1,31 @@ +# MIT License + +# Copyright (c) 2022 Intelligent Systems Lab Org + +# Permission is hereby granted, free of charge, to any person obtaining a copy +# of this software and associated documentation files (the "Software"), to deal +# in the Software without restriction, including without limitation the rights +# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +# copies of the Software, and to permit persons to whom the Software is +# furnished to do so, subject to the following conditions: + +# The above copyright notice and this permission notice shall be included in all +# copies or substantial portions of the Software. + +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +# SOFTWARE. + +# File author: Shariq Farooq Bhat + +from .zoedepth_nk_v1 import ZoeDepthNK + +all_versions = { + "v1": ZoeDepthNK, +} + +get_version = lambda v : all_versions[v] diff --git a/modules/control/proc/zoe/zoedepth/models/zoedepth_nk/config_zoedepth_nk.json b/modules/control/proc/zoe/zoedepth/models/zoedepth_nk/config_zoedepth_nk.json new file mode 100644 index 000000000..42bab2a3a --- /dev/null +++ b/modules/control/proc/zoe/zoedepth/models/zoedepth_nk/config_zoedepth_nk.json @@ -0,0 +1,67 @@ +{ + "model": { + "name": "ZoeDepthNK", + "version_name": "v1", + "bin_conf" : [ + { + "name": "nyu", + "n_bins": 64, + "min_depth": 1e-3, + "max_depth": 10.0 + }, + { + "name": "kitti", + "n_bins": 64, + "min_depth": 1e-3, + "max_depth": 80.0 + } + ], + "bin_embedding_dim": 128, + "bin_centers_type": "softplus", + "n_attractors":[16, 8, 4, 1], + "attractor_alpha": 1000, + "attractor_gamma": 2, + "attractor_kind" : "mean", + "attractor_type" : "inv", + "min_temp": 0.0212, + "max_temp": 50.0, + "memory_efficient": true, + "midas_model_type" : "DPT_BEiT_L_384", + "img_size": [384, 512] + }, + + "train": { + "train_midas": true, + "use_pretrained_midas": true, + "trainer": "zoedepth_nk", + "epochs": 5, + "bs": 16, + "optim_kwargs": {"lr": 0.0002512, "wd": 0.01}, + "sched_kwargs": {"div_factor": 1, "final_div_factor": 10000, "pct_start": 0.7, "three_phase":false, "cycle_momentum": true}, + "same_lr": false, + "w_si": 1, + "w_domain": 100, + "avoid_boundary": false, + "random_crop": false, + "input_width": 640, + "input_height": 480, + "w_grad": 0, + "w_reg": 0, + "midas_lr_factor": 10, + "encoder_lr_factor":10, + "pos_enc_lr_factor":10 + }, + + "infer": { + "train_midas": false, + "pretrained_resource": "url::https://github.com/isl-org/ZoeDepth/releases/download/v1.0/ZoeD_M12_NK.pt", + "use_pretrained_midas": false, + "force_keep_ar": true + }, + + "eval": { + "train_midas": false, + "pretrained_resource": "url::https://github.com/isl-org/ZoeDepth/releases/download/v1.0/ZoeD_M12_NK.pt", + "use_pretrained_midas": false + } +} \ No newline at end of file diff --git a/modules/control/proc/zoe/zoedepth/models/zoedepth_nk/zoedepth_nk_v1.py b/modules/control/proc/zoe/zoedepth/models/zoedepth_nk/zoedepth_nk_v1.py new file mode 100644 index 000000000..889b1e282 --- /dev/null +++ b/modules/control/proc/zoe/zoedepth/models/zoedepth_nk/zoedepth_nk_v1.py @@ -0,0 +1,333 @@ +# MIT License + +# Copyright (c) 2022 Intelligent Systems Lab Org + +# Permission is hereby granted, free of charge, to any person obtaining a copy +# of this software and associated documentation files (the "Software"), to deal +# in the Software without restriction, including without limitation the rights +# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +# copies of the Software, and to permit persons to whom the Software is +# furnished to do so, subject to the following conditions: + +# The above copyright notice and this permission notice shall be included in all +# copies or substantial portions of the Software. + +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +# SOFTWARE. + +# File author: Shariq Farooq Bhat + +import itertools + +import torch +import torch.nn as nn + +from ..depth_model import DepthModel +from ..base_models.midas import MidasCore +from ..layers.attractor import AttractorLayer, AttractorLayerUnnormed +from ..layers.dist_layers import ConditionalLogBinomial +from ..layers.localbins_layers import (Projector, SeedBinRegressor, + SeedBinRegressorUnnormed) +from ..layers.patch_transformer import PatchTransformerEncoder +from ..model_io import load_state_from_resource + +class ZoeDepthNK(DepthModel): + def __init__(self, core, bin_conf, bin_centers_type="softplus", bin_embedding_dim=128, + n_attractors=None, attractor_alpha=300, attractor_gamma=2, attractor_kind='sum', attractor_type='exp', + min_temp=5, max_temp=50, + memory_efficient=False, train_midas=True, + is_midas_pretrained=True, midas_lr_factor=1, encoder_lr_factor=10, pos_enc_lr_factor=10, inverse_midas=False, **kwargs): + """ZoeDepthNK model. This is the version of ZoeDepth that has two metric heads and uses a learned router to route to experts. + + Args: + core (models.base_models.midas.MidasCore): The base midas model that is used for extraction of "relative" features + + bin_conf (List[dict]): A list of dictionaries that contain the bin configuration for each metric head. Each dictionary should contain the following keys: + "name" (str, typically same as the dataset name), "n_bins" (int), "min_depth" (float), "max_depth" (float) + + The length of this list determines the number of metric heads. + bin_centers_type (str, optional): "normed" or "softplus". Activation type used for bin centers. For "normed" bin centers, linear normalization trick is applied. This results in bounded bin centers. + For "softplus", softplus activation is used and thus are unbounded. Defaults to "normed". + bin_embedding_dim (int, optional): bin embedding dimension. Defaults to 128. + + n_attractors (List[int], optional): Number of bin attractors at decoder layers. Defaults to [16, 8, 4, 1]. + attractor_alpha (int, optional): Proportional attractor strength. Refer to models.layers.attractor for more details. Defaults to 300. + attractor_gamma (int, optional): Exponential attractor strength. Refer to models.layers.attractor for more details. Defaults to 2. + attractor_kind (str, optional): Attraction aggregation "sum" or "mean". Defaults to 'sum'. + attractor_type (str, optional): Type of attractor to use; "inv" (Inverse attractor) or "exp" (Exponential attractor). Defaults to 'exp'. + + min_temp (int, optional): Lower bound for temperature of output probability distribution. Defaults to 5. + max_temp (int, optional): Upper bound for temperature of output probability distribution. Defaults to 50. + + memory_efficient (bool, optional): Whether to use memory efficient version of attractor layers. Memory efficient version is slower but is recommended incase of multiple metric heads in order save GPU memory. Defaults to False. + + train_midas (bool, optional): Whether to train "core", the base midas model. Defaults to True. + is_midas_pretrained (bool, optional): Is "core" pretrained? Defaults to True. + midas_lr_factor (int, optional): Learning rate reduction factor for base midas model except its encoder and positional encodings. Defaults to 10. + encoder_lr_factor (int, optional): Learning rate reduction factor for the encoder in midas model. Defaults to 10. + pos_enc_lr_factor (int, optional): Learning rate reduction factor for positional encodings in the base midas model. Defaults to 10. + + """ + + if n_attractors is None: + n_attractors = [16, 8, 4, 1] + super().__init__() + + self.core = core + self.bin_conf = bin_conf + self.min_temp = min_temp + self.max_temp = max_temp + self.memory_efficient = memory_efficient + self.train_midas = train_midas + self.is_midas_pretrained = is_midas_pretrained + self.midas_lr_factor = midas_lr_factor + self.encoder_lr_factor = encoder_lr_factor + self.pos_enc_lr_factor = pos_enc_lr_factor + self.inverse_midas = inverse_midas + + N_MIDAS_OUT = 32 + btlnck_features = self.core.output_channels[0] + num_out_features = self.core.output_channels[1:] + # self.scales = [16, 8, 4, 2] # spatial scale factors + + self.conv2 = nn.Conv2d( + btlnck_features, btlnck_features, kernel_size=1, stride=1, padding=0) + + # Transformer classifier on the bottleneck + self.patch_transformer = PatchTransformerEncoder( + btlnck_features, 1, 128, use_class_token=True) + self.mlp_classifier = nn.Sequential( + nn.Linear(128, 128), + nn.ReLU(), + nn.Linear(128, 2) + ) + + if bin_centers_type == "normed": + SeedBinRegressorLayer = SeedBinRegressor + Attractor = AttractorLayer + elif bin_centers_type == "softplus": + SeedBinRegressorLayer = SeedBinRegressorUnnormed + Attractor = AttractorLayerUnnormed + elif bin_centers_type == "hybrid1": + SeedBinRegressorLayer = SeedBinRegressor + Attractor = AttractorLayerUnnormed + elif bin_centers_type == "hybrid2": + SeedBinRegressorLayer = SeedBinRegressorUnnormed + Attractor = AttractorLayer + else: + raise ValueError( + "bin_centers_type should be one of 'normed', 'softplus', 'hybrid1', 'hybrid2'") + self.bin_centers_type = bin_centers_type + # We have bins for each bin conf. + # Create a map (ModuleDict) of 'name' -> seed_bin_regressor + self.seed_bin_regressors = nn.ModuleDict( + {conf['name']: SeedBinRegressorLayer(btlnck_features, conf["n_bins"], mlp_dim=bin_embedding_dim//2, min_depth=conf["min_depth"], max_depth=conf["max_depth"]) + for conf in bin_conf} + ) + + self.seed_projector = Projector( + btlnck_features, bin_embedding_dim, mlp_dim=bin_embedding_dim//2) + self.projectors = nn.ModuleList([ + Projector(num_out, bin_embedding_dim, mlp_dim=bin_embedding_dim//2) + for num_out in num_out_features + ]) + + # Create a map (ModuleDict) of 'name' -> attractors (ModuleList) + self.attractors = nn.ModuleDict( + {conf['name']: nn.ModuleList([ + Attractor(bin_embedding_dim, n_attractors[i], + mlp_dim=bin_embedding_dim, alpha=attractor_alpha, + gamma=attractor_gamma, kind=attractor_kind, + attractor_type=attractor_type, memory_efficient=memory_efficient, + min_depth=conf["min_depth"], max_depth=conf["max_depth"]) + for i in range(len(n_attractors)) + ]) + for conf in bin_conf} + ) + + last_in = N_MIDAS_OUT + # conditional log binomial for each bin conf + self.conditional_log_binomial = nn.ModuleDict( + {conf['name']: ConditionalLogBinomial(last_in, bin_embedding_dim, conf['n_bins'], bottleneck_factor=4, min_temp=self.min_temp, max_temp=self.max_temp) + for conf in bin_conf} + ) + + def forward(self, x, return_final_centers=False, denorm=False, return_probs=False, **kwargs): + """ + Args: + x (torch.Tensor): Input image tensor of shape (B, C, H, W). Assumes all images are from the same domain. + return_final_centers (bool, optional): Whether to return the final centers of the attractors. Defaults to False. + denorm (bool, optional): Whether to denormalize the input image. Defaults to False. + return_probs (bool, optional): Whether to return the probabilities of the bins. Defaults to False. + + Returns: + dict: Dictionary of outputs with keys: + - "rel_depth": Relative depth map of shape (B, 1, H, W) + - "metric_depth": Metric depth map of shape (B, 1, H, W) + - "domain_logits": Domain logits of shape (B, 2) + - "bin_centers": Bin centers of shape (B, N, H, W). Present only if return_final_centers is True + - "probs": Bin probabilities of shape (B, N, H, W). Present only if return_probs is True + """ + b, c, h, w = x.shape + self.orig_input_width = w + self.orig_input_height = h + rel_depth, out = self.core(x, denorm=denorm, return_rel_depth=True) + + outconv_activation = out[0] + btlnck = out[1] + x_blocks = out[2:] + + x_d0 = self.conv2(btlnck) + x = x_d0 + + # Predict which path to take + embedding = self.patch_transformer(x)[0] # N, E + domain_logits = self.mlp_classifier(embedding) # N, 2 + domain_vote = torch.softmax(domain_logits.sum( + dim=0, keepdim=True), dim=-1) # 1, 2 + + # Get the path + bin_conf_name = ["nyu", "kitti"][torch.argmax( + domain_vote, dim=-1).squeeze().item()] + + try: + conf = [c for c in self.bin_conf if c.name == bin_conf_name][0] + except IndexError as e: + raise ValueError(f"bin_conf_name {bin_conf_name} not found in bin_confs") from e + + min_depth = conf['min_depth'] + max_depth = conf['max_depth'] + + seed_bin_regressor = self.seed_bin_regressors[bin_conf_name] + _, seed_b_centers = seed_bin_regressor(x) + if self.bin_centers_type == 'normed' or self.bin_centers_type == 'hybrid2': + b_prev = (seed_b_centers - min_depth)/(max_depth - min_depth) + else: + b_prev = seed_b_centers + prev_b_embedding = self.seed_projector(x) + + attractors = self.attractors[bin_conf_name] + for projector, attractor, x in zip(self.projectors, attractors, x_blocks): + b_embedding = projector(x) + b, b_centers = attractor( + b_embedding, b_prev, prev_b_embedding, interpolate=True) + b_prev = b + prev_b_embedding = b_embedding + + last = outconv_activation + + b_centers = nn.functional.interpolate( + b_centers, last.shape[-2:], mode='bilinear', align_corners=True) + b_embedding = nn.functional.interpolate( + b_embedding, last.shape[-2:], mode='bilinear', align_corners=True) + + clb = self.conditional_log_binomial[bin_conf_name] + x = clb(last, b_embedding) + + # Now depth value is Sum px * cx , where cx are bin_centers from the last bin tensor + # print(x.shape, b_centers.shape) + # b_centers = nn.functional.interpolate(b_centers, x.shape[-2:], mode='bilinear', align_corners=True) + out = torch.sum(x * b_centers, dim=1, keepdim=True) + + output = dict(domain_logits=domain_logits, metric_depth=out) + if return_final_centers or return_probs: + output['bin_centers'] = b_centers + + if return_probs: + output['probs'] = x + return output + + def get_lr_params(self, lr): + """ + Learning rate configuration for different layers of the model + + Args: + lr (float) : Base learning rate + Returns: + list : list of parameters to optimize and their learning rates, in the format required by torch optimizers. + """ + param_conf = [] + if self.train_midas: + def get_rel_pos_params(): + for name, p in self.core.core.pretrained.named_parameters(): + if "relative_position" in name: + yield p + + def get_enc_params_except_rel_pos(): + for name, p in self.core.core.pretrained.named_parameters(): + if "relative_position" not in name: + yield p + + encoder_params = get_enc_params_except_rel_pos() + rel_pos_params = get_rel_pos_params() + midas_params = self.core.core.scratch.parameters() + midas_lr_factor = self.midas_lr_factor if self.is_midas_pretrained else 1.0 + param_conf.extend([ + {'params': encoder_params, 'lr': lr / self.encoder_lr_factor}, + {'params': rel_pos_params, 'lr': lr / self.pos_enc_lr_factor}, + {'params': midas_params, 'lr': lr / midas_lr_factor} + ]) + + remaining_modules = [] + for name, child in self.named_children(): + if name != 'core': + remaining_modules.append(child) + remaining_params = itertools.chain( + *[child.parameters() for child in remaining_modules]) + param_conf.append({'params': remaining_params, 'lr': lr}) + return param_conf + + def get_conf_parameters(self, conf_name): + """ + Returns parameters of all the ModuleDicts children that are exclusively used for the given bin configuration + """ + params = [] + for _name, child in self.named_children(): + if isinstance(child, nn.ModuleDict): + for bin_conf_name, module in child.items(): + if bin_conf_name == conf_name: + params += list(module.parameters()) + return params + + def freeze_conf(self, conf_name): + """ + Freezes all the parameters of all the ModuleDicts children that are exclusively used for the given bin configuration + """ + for p in self.get_conf_parameters(conf_name): + p.requires_grad = False + + def unfreeze_conf(self, conf_name): + """ + Unfreezes all the parameters of all the ModuleDicts children that are exclusively used for the given bin configuration + """ + for p in self.get_conf_parameters(conf_name): + p.requires_grad = True + + def freeze_all_confs(self): + """ + Freezes all the parameters of all the ModuleDicts children + """ + for _name, child in self.named_children(): + if isinstance(child, nn.ModuleDict): + for _bin_conf_name, module in child.items(): + for p in module.parameters(): + p.requires_grad = False + + @staticmethod + def build(midas_model_type="DPT_BEiT_L_384", pretrained_resource=None, use_pretrained_midas=False, train_midas=False, freeze_midas_bn=True, **kwargs): + core = MidasCore.build(midas_model_type=midas_model_type, use_pretrained_midas=use_pretrained_midas, + train_midas=train_midas, fetch_features=True, freeze_bn=freeze_midas_bn, **kwargs) + model = ZoeDepthNK(core, **kwargs) + if pretrained_resource: + assert isinstance(pretrained_resource, str), "pretrained_resource must be a string" + model = load_state_from_resource(model, pretrained_resource) + return model + + @staticmethod + def build_from_config(config): + return ZoeDepthNK.build(**config) diff --git a/modules/control/proc/zoe/zoedepth/utils/__init__.py b/modules/control/proc/zoe/zoedepth/utils/__init__.py new file mode 100644 index 000000000..5f2668792 --- /dev/null +++ b/modules/control/proc/zoe/zoedepth/utils/__init__.py @@ -0,0 +1,24 @@ +# MIT License + +# Copyright (c) 2022 Intelligent Systems Lab Org + +# Permission is hereby granted, free of charge, to any person obtaining a copy +# of this software and associated documentation files (the "Software"), to deal +# in the Software without restriction, including without limitation the rights +# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +# copies of the Software, and to permit persons to whom the Software is +# furnished to do so, subject to the following conditions: + +# The above copyright notice and this permission notice shall be included in all +# copies or substantial portions of the Software. + +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +# SOFTWARE. + +# File author: Shariq Farooq Bhat + diff --git a/modules/control/proc/zoe/zoedepth/utils/arg_utils.py b/modules/control/proc/zoe/zoedepth/utils/arg_utils.py new file mode 100644 index 000000000..8a3004ec3 --- /dev/null +++ b/modules/control/proc/zoe/zoedepth/utils/arg_utils.py @@ -0,0 +1,33 @@ + + +def infer_type(x): # hacky way to infer type from string args + if not isinstance(x, str): + return x + + try: + x = int(x) + return x + except ValueError: + pass + + try: + x = float(x) + return x + except ValueError: + pass + + return x + + +def parse_unknown(unknown_args): + clean = [] + for a in unknown_args: + if "=" in a: + k, v = a.split("=") + clean.extend([k, v]) + else: + clean.append(a) + + keys = clean[::2] + values = clean[1::2] + return {k.replace("--", ""): infer_type(v) for k, v in zip(keys, values)} diff --git a/modules/control/proc/zoe/zoedepth/utils/config.py b/modules/control/proc/zoe/zoedepth/utils/config.py new file mode 100644 index 000000000..24525d947 --- /dev/null +++ b/modules/control/proc/zoe/zoedepth/utils/config.py @@ -0,0 +1,437 @@ +# MIT License + +# Copyright (c) 2022 Intelligent Systems Lab Org + +# Permission is hereby granted, free of charge, to any person obtaining a copy +# of this software and associated documentation files (the "Software"), to deal +# in the Software without restriction, including without limitation the rights +# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +# copies of the Software, and to permit persons to whom the Software is +# furnished to do so, subject to the following conditions: + +# The above copyright notice and this permission notice shall be included in all +# copies or substantial portions of the Software. + +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +# SOFTWARE. + +# File author: Shariq Farooq Bhat + +import json +import os + +from .easydict import EasyDict as edict +from .arg_utils import infer_type + +import pathlib +import platform + +ROOT = pathlib.Path(__file__).parent.parent.resolve() + +HOME_DIR = os.path.expanduser("~") + +COMMON_CONFIG = { + "save_dir": os.path.expanduser("~/shortcuts/monodepth3_checkpoints"), + "project": "ZoeDepth", + "tags": '', + "notes": "", + "gpu": None, + "root": ".", + "uid": None, + "print_losses": False +} + +DATASETS_CONFIG = { + "kitti": { + "dataset": "kitti", + "min_depth": 0.001, + "max_depth": 80, + "data_path": os.path.join(HOME_DIR, "shortcuts/datasets/kitti/raw"), + "gt_path": os.path.join(HOME_DIR, "shortcuts/datasets/kitti/gts"), + "filenames_file": "./train_test_inputs/kitti_eigen_train_files_with_gt.txt", + "input_height": 352, + "input_width": 1216, # 704 + "data_path_eval": os.path.join(HOME_DIR, "shortcuts/datasets/kitti/raw"), + "gt_path_eval": os.path.join(HOME_DIR, "shortcuts/datasets/kitti/gts"), + "filenames_file_eval": "./train_test_inputs/kitti_eigen_test_files_with_gt.txt", + + "min_depth_eval": 1e-3, + "max_depth_eval": 80, + + "do_random_rotate": True, + "degree": 1.0, + "do_kb_crop": True, + "garg_crop": True, + "eigen_crop": False, + "use_right": False + }, + "kitti_test": { + "dataset": "kitti", + "min_depth": 0.001, + "max_depth": 80, + "data_path": os.path.join(HOME_DIR, "shortcuts/datasets/kitti/raw"), + "gt_path": os.path.join(HOME_DIR, "shortcuts/datasets/kitti/gts"), + "filenames_file": "./train_test_inputs/kitti_eigen_train_files_with_gt.txt", + "input_height": 352, + "input_width": 1216, + "data_path_eval": os.path.join(HOME_DIR, "shortcuts/datasets/kitti/raw"), + "gt_path_eval": os.path.join(HOME_DIR, "shortcuts/datasets/kitti/gts"), + "filenames_file_eval": "./train_test_inputs/kitti_eigen_test_files_with_gt.txt", + + "min_depth_eval": 1e-3, + "max_depth_eval": 80, + + "do_random_rotate": False, + "degree": 1.0, + "do_kb_crop": True, + "garg_crop": True, + "eigen_crop": False, + "use_right": False + }, + "nyu": { + "dataset": "nyu", + "avoid_boundary": False, + "min_depth": 1e-3, # originally 0.1 + "max_depth": 10, + "data_path": os.path.join(HOME_DIR, "shortcuts/datasets/nyu_depth_v2/sync/"), + "gt_path": os.path.join(HOME_DIR, "shortcuts/datasets/nyu_depth_v2/sync/"), + "filenames_file": "./train_test_inputs/nyudepthv2_train_files_with_gt.txt", + "input_height": 480, + "input_width": 640, + "data_path_eval": os.path.join(HOME_DIR, "shortcuts/datasets/nyu_depth_v2/official_splits/test/"), + "gt_path_eval": os.path.join(HOME_DIR, "shortcuts/datasets/nyu_depth_v2/official_splits/test/"), + "filenames_file_eval": "./train_test_inputs/nyudepthv2_test_files_with_gt.txt", + "min_depth_eval": 1e-3, + "max_depth_eval": 10, + "min_depth_diff": -10, + "max_depth_diff": 10, + + "do_random_rotate": True, + "degree": 1.0, + "do_kb_crop": False, + "garg_crop": False, + "eigen_crop": True + }, + "ibims": { + "dataset": "ibims", + "ibims_root": os.path.join(HOME_DIR, "shortcuts/datasets/ibims/ibims1_core_raw/"), + "eigen_crop": True, + "garg_crop": False, + "do_kb_crop": False, + "min_depth_eval": 0, + "max_depth_eval": 10, + "min_depth": 1e-3, + "max_depth": 10 + }, + "sunrgbd": { + "dataset": "sunrgbd", + "sunrgbd_root": os.path.join(HOME_DIR, "shortcuts/datasets/SUNRGBD/test/"), + "eigen_crop": True, + "garg_crop": False, + "do_kb_crop": False, + "min_depth_eval": 0, + "max_depth_eval": 8, + "min_depth": 1e-3, + "max_depth": 10 + }, + "diml_indoor": { + "dataset": "diml_indoor", + "diml_indoor_root": os.path.join(HOME_DIR, "shortcuts/datasets/diml_indoor_test/"), + "eigen_crop": True, + "garg_crop": False, + "do_kb_crop": False, + "min_depth_eval": 0, + "max_depth_eval": 10, + "min_depth": 1e-3, + "max_depth": 10 + }, + "diml_outdoor": { + "dataset": "diml_outdoor", + "diml_outdoor_root": os.path.join(HOME_DIR, "shortcuts/datasets/diml_outdoor_test/"), + "eigen_crop": False, + "garg_crop": True, + "do_kb_crop": False, + "min_depth_eval": 2, + "max_depth_eval": 80, + "min_depth": 1e-3, + "max_depth": 80 + }, + "diode_indoor": { + "dataset": "diode_indoor", + "diode_indoor_root": os.path.join(HOME_DIR, "shortcuts/datasets/diode_indoor/"), + "eigen_crop": True, + "garg_crop": False, + "do_kb_crop": False, + "min_depth_eval": 1e-3, + "max_depth_eval": 10, + "min_depth": 1e-3, + "max_depth": 10 + }, + "diode_outdoor": { + "dataset": "diode_outdoor", + "diode_outdoor_root": os.path.join(HOME_DIR, "shortcuts/datasets/diode_outdoor/"), + "eigen_crop": False, + "garg_crop": True, + "do_kb_crop": False, + "min_depth_eval": 1e-3, + "max_depth_eval": 80, + "min_depth": 1e-3, + "max_depth": 80 + }, + "hypersim_test": { + "dataset": "hypersim_test", + "hypersim_test_root": os.path.join(HOME_DIR, "shortcuts/datasets/hypersim_test/"), + "eigen_crop": True, + "garg_crop": False, + "do_kb_crop": False, + "min_depth_eval": 1e-3, + "max_depth_eval": 80, + "min_depth": 1e-3, + "max_depth": 10 + }, + "vkitti": { + "dataset": "vkitti", + "vkitti_root": os.path.join(HOME_DIR, "shortcuts/datasets/vkitti_test/"), + "eigen_crop": False, + "garg_crop": True, + "do_kb_crop": True, + "min_depth_eval": 1e-3, + "max_depth_eval": 80, + "min_depth": 1e-3, + "max_depth": 80 + }, + "vkitti2": { + "dataset": "vkitti2", + "vkitti2_root": os.path.join(HOME_DIR, "shortcuts/datasets/vkitti2/"), + "eigen_crop": False, + "garg_crop": True, + "do_kb_crop": True, + "min_depth_eval": 1e-3, + "max_depth_eval": 80, + "min_depth": 1e-3, + "max_depth": 80, + }, + "ddad": { + "dataset": "ddad", + "ddad_root": os.path.join(HOME_DIR, "shortcuts/datasets/ddad/ddad_val/"), + "eigen_crop": False, + "garg_crop": True, + "do_kb_crop": True, + "min_depth_eval": 1e-3, + "max_depth_eval": 80, + "min_depth": 1e-3, + "max_depth": 80, + }, +} + +ALL_INDOOR = ["nyu", "ibims", "sunrgbd", "diode_indoor", "hypersim_test"] +ALL_OUTDOOR = ["kitti", "diml_outdoor", "diode_outdoor", "vkitti2", "ddad"] +ALL_EVAL_DATASETS = ALL_INDOOR + ALL_OUTDOOR + +COMMON_TRAINING_CONFIG = { + "dataset": "nyu", + "distributed": True, + "workers": 16, + "clip_grad": 0.1, + "use_shared_dict": False, + "shared_dict": None, + "use_amp": False, + + "aug": True, + "random_crop": False, + "random_translate": False, + "translate_prob": 0.2, + "max_translation": 100, + + "validate_every": 0.25, + "log_images_every": 0.1, + "prefetch": False, +} + + +def flatten(config, except_keys=('bin_conf')): + def recurse(inp): + if isinstance(inp, dict): + for key, value in inp.items(): + if key in except_keys: + yield (key, value) + if isinstance(value, dict): + yield from recurse(value) + else: + yield (key, value) + + return dict(list(recurse(config))) + + +def split_combined_args(kwargs): + """Splits the arguments that are combined with '__' into multiple arguments. + Combined arguments should have equal number of keys and values. + Keys are separated by '__' and Values are separated with ';'. + For example, '__n_bins__lr=256;0.001' + + Args: + kwargs (dict): key-value pairs of arguments where key-value is optionally combined according to the above format. + + Returns: + dict: Parsed dict with the combined arguments split into individual key-value pairs. + """ + new_kwargs = dict(kwargs) + for key, value in kwargs.items(): + if key.startswith("__"): + keys = key.split("__")[1:] + values = value.split(";") + assert len(keys) == len( + values), f"Combined arguments should have equal number of keys and values. Keys are separated by '__' and Values are separated with ';'. For example, '__n_bins__lr=256;0.001. Given (keys,values) is ({keys}, {values})" + for k, v in zip(keys, values): + new_kwargs[k] = v + return new_kwargs + + +def parse_list(config, key, dtype=int): + """Parse a list of values for the key if the value is a string. The values are separated by a comma. + Modifies the config in place. + """ + if key in config: + if isinstance(config[key], str): + config[key] = list(map(dtype, config[key].split(','))) + assert isinstance(config[key], list) and all(isinstance(e, dtype) for e in config[key] + ), f"{key} should be a list of values dtype {dtype}. Given {config[key]} of type {type(config[key])} with values of type {[type(e) for e in config[key]]}." + + +def get_model_config(model_name, model_version=None): + """Find and parse the .json config file for the model. + + Args: + model_name (str): name of the model. The config file should be named config_{model_name}[_{model_version}].json under the models/{model_name} directory. + model_version (str, optional): Specific config version. If specified config_{model_name}_{model_version}.json is searched for and used. Otherwise config_{model_name}.json is used. Defaults to None. + + Returns: + easydict: the config dictionary for the model. + """ + config_fname = f"config_{model_name}_{model_version}.json" if model_version is not None else f"config_{model_name}.json" + config_file = os.path.join(ROOT, "models", model_name, config_fname) + if not os.path.exists(config_file): + return None + + with open(config_file, "r") as f: + config = edict(json.load(f)) + + # handle dictionary inheritance + # only training config is supported for inheritance + if "inherit" in config.train and config.train.inherit is not None: + inherit_config = get_model_config(config.train["inherit"]).train + for key, value in inherit_config.items(): + if key not in config.train: + config.train[key] = value + return edict(config) + + +def update_model_config(config, mode, model_name, model_version=None, strict=False): + model_config = get_model_config(model_name, model_version) + if model_config is not None: + config = {**config, ** + flatten({**model_config.model, **model_config[mode]})} + elif strict: + raise ValueError(f"Config file for model {model_name} not found.") + return config + + +def check_choices(name, value, choices): + # return # No checks in dev branch + if value not in choices: + raise ValueError(f"{name} {value} not in supported choices {choices}") + + +KEYS_TYPE_BOOL = ["use_amp", "distributed", "use_shared_dict", "same_lr", "aug", "three_phase", + "prefetch", "cycle_momentum"] # Casting is not necessary as their int casted values in config are 0 or 1 + + +def get_config(model_name, mode='train', dataset=None, **overwrite_kwargs): + """Main entry point to get the config for the model. + + Args: + model_name (str): name of the desired model. + mode (str, optional): "train" or "infer". Defaults to 'train'. + dataset (str, optional): If specified, the corresponding dataset configuration is loaded as well. Defaults to None. + + Keyword Args: key-value pairs of arguments to overwrite the default config. + + The order of precedence for overwriting the config is (Higher precedence first): + # 1. overwrite_kwargs + # 2. "config_version": Config file version if specified in overwrite_kwargs. The corresponding config loaded is config_{model_name}_{config_version}.json + # 3. "version_name": Default Model version specific config specified in overwrite_kwargs. The corresponding config loaded is config_{model_name}_{version_name}.json + # 4. common_config: Default config for all models specified in COMMON_CONFIG + + Returns: + easydict: The config dictionary for the model. + """ + + + check_choices("Model", model_name, ["zoedepth", "zoedepth_nk"]) + check_choices("Mode", mode, ["train", "infer", "eval"]) + if mode == "train": + check_choices("Dataset", dataset, ["nyu", "kitti", "mix", None]) + + config = flatten({**COMMON_CONFIG, **COMMON_TRAINING_CONFIG}) + config = update_model_config(config, mode, model_name) + + # update with model version specific config + version_name = overwrite_kwargs.get("version_name", config["version_name"]) + config = update_model_config(config, mode, model_name, version_name) + + # update with config version if specified + config_version = overwrite_kwargs.get("config_version", None) + if config_version is not None: + print("Overwriting config with config_version", config_version) + config = update_model_config(config, mode, model_name, config_version) + + # update with overwrite_kwargs + # Combined args are useful for hyperparameter search + overwrite_kwargs = split_combined_args(overwrite_kwargs) + config = {**config, **overwrite_kwargs} + + # Casting to bool # TODO: Not necessary. Remove and test + for key in KEYS_TYPE_BOOL: + if key in config: + config[key] = bool(config[key]) + + # Model specific post processing of config + parse_list(config, "n_attractors") + + # adjust n_bins for each bin configuration if bin_conf is given and n_bins is passed in overwrite_kwargs + if 'bin_conf' in config and 'n_bins' in overwrite_kwargs: + bin_conf = config['bin_conf'] # list of dicts + n_bins = overwrite_kwargs['n_bins'] + new_bin_conf = [] + for conf in bin_conf: + conf['n_bins'] = n_bins + new_bin_conf.append(conf) + config['bin_conf'] = new_bin_conf + + if mode == "train": + orig_dataset = dataset + if dataset == "mix": + dataset = 'nyu' # Use nyu as default for mix. Dataset config is changed accordingly while loading the dataloader + if dataset is not None: + config['project'] = f"MonoDepth3-{orig_dataset}" # Set project for wandb + + if dataset is not None: + config['dataset'] = dataset + config = {**DATASETS_CONFIG[dataset], **config} + + + config['model'] = model_name + typed_config = {k: infer_type(v) for k, v in config.items()} + # add hostname to config + config['hostname'] = platform.node() + return edict(typed_config) + + +def change_dataset(config, new_dataset): + config.update(DATASETS_CONFIG[new_dataset]) + return config diff --git a/modules/control/proc/zoe/zoedepth/utils/easydict/__init__.py b/modules/control/proc/zoe/zoedepth/utils/easydict/__init__.py new file mode 100644 index 000000000..fe47f0173 --- /dev/null +++ b/modules/control/proc/zoe/zoedepth/utils/easydict/__init__.py @@ -0,0 +1,158 @@ +""" +EasyDict +Copy/pasted from https://github.com/makinacorpus/easydict +Original author: Mathieu Leplatre +""" + +class EasyDict(dict): + """ + Get attributes + + >>> d = EasyDict({'foo':3}) + >>> d['foo'] + 3 + >>> d.foo + 3 + >>> d.bar + Traceback (most recent call last): + ... + AttributeError: 'EasyDict' object has no attribute 'bar' + + Works recursively + + >>> d = EasyDict({'foo':3, 'bar':{'x':1, 'y':2}}) + >>> isinstance(d.bar, dict) + True + >>> d.bar.x + 1 + + Bullet-proof + + >>> EasyDict({}) + {} + >>> EasyDict(d={}) + {} + >>> EasyDict(None) + {} + >>> d = {'a': 1} + >>> EasyDict(**d) + {'a': 1} + >>> EasyDict((('a', 1), ('b', 2))) + {'a': 1, 'b': 2} + + Set attributes + + >>> d = EasyDict() + >>> d.foo = 3 + >>> d.foo + 3 + >>> d.bar = {'prop': 'value'} + >>> d.bar.prop + 'value' + >>> d + {'foo': 3, 'bar': {'prop': 'value'}} + >>> d.bar.prop = 'newer' + >>> d.bar.prop + 'newer' + + + Values extraction + + >>> d = EasyDict({'foo':0, 'bar':[{'x':1, 'y':2}, {'x':3, 'y':4}]}) + >>> isinstance(d.bar, list) + True + >>> from operator import attrgetter + >>> list(map(attrgetter('x'), d.bar)) + [1, 3] + >>> list(map(attrgetter('y'), d.bar)) + [2, 4] + >>> d = EasyDict() + >>> list(d.keys()) + [] + >>> d = EasyDict(foo=3, bar=dict(x=1, y=2)) + >>> d.foo + 3 + >>> d.bar.x + 1 + + Still like a dict though + + >>> o = EasyDict({'clean':True}) + >>> list(o.items()) + [('clean', True)] + + And like a class + + >>> class Flower(EasyDict): + ... power = 1 + ... + >>> f = Flower() + >>> f.power + 1 + >>> f = Flower({'height': 12}) + >>> f.height + 12 + >>> f['power'] + 1 + >>> sorted(f.keys()) + ['height', 'power'] + + update and pop items + >>> d = EasyDict(a=1, b='2') + >>> e = EasyDict(c=3.0, a=9.0) + >>> d.update(e) + >>> d.c + 3.0 + >>> d['c'] + 3.0 + >>> d.get('c') + 3.0 + >>> d.update(a=4, b=4) + >>> d.b + 4 + >>> d.pop('a') + 4 + >>> d.a + Traceback (most recent call last): + ... + AttributeError: 'EasyDict' object has no attribute 'a' + """ + def __init__(self, d=None, **kwargs): + if d is None: + d = {} + else: + d = dict(d) + if kwargs: + d.update(**kwargs) + for k, v in d.items(): + setattr(self, k, v) + # Class attributes + for k in self.__class__.__dict__.keys(): + if not (k.startswith('__') and k.endswith('__')) and k not in ('update', 'pop'): + setattr(self, k, getattr(self, k)) + + def __setattr__(self, name, value): + if isinstance(value, (list, tuple)): + value = [self.__class__(x) + if isinstance(x, dict) else x for x in value] + elif isinstance(value, dict) and not isinstance(value, self.__class__): + value = self.__class__(value) + super(EasyDict, self).__setattr__(name, value) + super(EasyDict, self).__setitem__(name, value) + + __setitem__ = __setattr__ + + def update(self, e=None, **f): + d = e or dict() + d.update(f) + for k in d: + setattr(self, k, d[k]) + + def pop(self, k, d=None): + delattr(self, k) + return super(EasyDict, self).pop(k, d) + + +if __name__ == "__main__": + import doctest + doctest.testmod() diff --git a/modules/control/processors.py b/modules/control/processors.py new file mode 100644 index 000000000..f6877cf89 --- /dev/null +++ b/modules/control/processors.py @@ -0,0 +1,214 @@ +import os +import time +import torch +from PIL import Image +from modules.shared import log +from modules.errors import display + +from modules.control.proc.hed import HEDdetector +from modules.control.proc.canny import CannyDetector +from modules.control.proc.edge import EdgeDetector +from modules.control.proc.lineart import LineartDetector +from modules.control.proc.lineart_anime import LineartAnimeDetector +from modules.control.proc.pidi import PidiNetDetector +from modules.control.proc.mediapipe_face import MediapipeFaceDetector +from modules.control.proc.shuffle import ContentShuffleDetector + +from modules.control.proc.leres import LeresDetector +from modules.control.proc.midas import MidasDetector +from modules.control.proc.mlsd import MLSDdetector +from modules.control.proc.normalbae import NormalBaeDetector +from modules.control.proc.openpose import OpenposeDetector +from modules.control.proc.dwpose import DWposeDetector +from modules.control.proc.segment_anything import SamDetector +from modules.control.proc.zoe import ZoeDetector + + +models = {} +cache_dir = 'models/control/processors' +debug = log.debug if os.environ.get('SD_CONTROL_DEBUG', None) is not None else lambda *args, **kwargs: None +config = { + # pose models + 'OpenPose': {'class': OpenposeDetector, 'checkpoint': True, 'params': {'include_body': True, 'include_hand': False, 'include_face': False}}, + 'DWPose': {'class': DWposeDetector, 'checkpoint': False, 'model': 'Tiny', 'params': {'min_confidence': 0.3}}, + 'MediaPipe Face': {'class': MediapipeFaceDetector, 'checkpoint': False, 'params': {'max_faces': 1, 'min_confidence': 0.5}}, + # outline models + 'Canny': {'class': CannyDetector, 'checkpoint': False, 'params': {'low_threshold': 100, 'high_threshold': 200}}, + 'Edge': {'class': EdgeDetector, 'checkpoint': False, 'params': {'pf': True, 'mode': 'edge'}}, + 'LineArt Realistic': {'class': LineartDetector, 'checkpoint': True, 'params': {'coarse': False}}, + 'LineArt Anime': {'class': LineartAnimeDetector, 'checkpoint': True, 'params': {}}, + 'HED': {'class': HEDdetector, 'checkpoint': True, 'params': {'scribble': False, 'safe': False}}, + 'PidiNet': {'class': PidiNetDetector, 'checkpoint': True, 'params': {'scribble': False, 'safe': False, 'apply_filter': False}}, + # depth models + 'Midas Depth Hybrid': {'class': MidasDetector, 'checkpoint': True, 'params': {'bg_th': 0.1, 'depth_and_normal': False}}, + 'Leres Depth': {'class': LeresDetector, 'checkpoint': True, 'params': {'boost': False, 'thr_a':0, 'thr_b':0}}, + 'Zoe Depth': {'class': ZoeDetector, 'checkpoint': True, 'params': {}, 'load_config': {'pretrained_model_or_path': 'halffried/gyre_zoedepth', 'filename': 'ZoeD_M12_N.safetensors', 'model_type': "zoedepth"}}, + 'Normal Bae': {'class': NormalBaeDetector, 'checkpoint': True, 'params': {}}, + # segmentation models + 'SegmentAnything': {'class': SamDetector, 'checkpoint': True, 'model': 'Base', 'params': {}}, + # other models + 'MLSD': {'class': MLSDdetector, 'checkpoint': True, 'params': {'thr_v': 0.1, 'thr_d': 0.1}}, + 'Shuffle': {'class': ContentShuffleDetector, 'checkpoint': False, 'params': {}}, + # 'Midas Depth Large': {'class': MidasDetector, 'checkpoint': True, 'params': {'bg_th': 0.1, 'depth_and_normal': False}, 'load_config': {'pretrained_model_or_path': 'Intel/dpt-large', 'model_type': "dpt_large", 'filename': ''}}, + # 'Zoe Depth Zoe': {'class': ZoeDetector, 'checkpoint': True, 'params': {}}, + # 'Zoe Depth NK': {'class': ZoeDetector, 'checkpoint': True, 'params': {}, 'load_config': {'pretrained_model_or_path': 'halffried/gyre_zoedepth', 'filename': 'ZoeD_M12_NK.safetensors', 'model_type': "zoedepth_nk"}}, +} + + +def list_models(refresh=False): + global models # pylint: disable=global-statement + if not refresh and len(models) > 0: + return models + models = ['None'] + list(config) + debug(f'Control list processors: path={cache_dir} models={models}') + return models + + +def update_settings(*settings): + debug(f'Control settings: {settings}') + def update(what, val): + processor_id = what[0] + if len(what) == 2 and config[processor_id][what[1]] != val: + config[processor_id][what[1]] = val + config[processor_id]['dirty'] = True + log.debug(f'Control settings: id="{processor_id}" {what[-1]}={val}') + elif len(what) == 3 and config[processor_id][what[1]][what[2]] != val: + config[processor_id][what[1]][what[2]] = val + config[processor_id]['dirty'] = True + log.debug(f'Control settings: id="{processor_id}" {what[-1]}={val}') + elif len(what) == 4 and config[processor_id][what[1]][what[2]][what[3]] != val: + config[processor_id][what[1]][what[2]][what[3]] = val + config[processor_id]['dirty'] = True + log.debug(f'Control settings: id="{processor_id}" {what[-1]}={val}') + + update(['HED', 'params', 'scribble'], settings[0]) + update(['Midas Depth Hybrid', 'params', 'bg_th'], settings[1]) + update(['Midas Depth Hybrid', 'params', 'depth_and_normal'], settings[2]) + update(['MLSD', 'params', 'thr_v'], settings[3]) + update(['MLSD', 'params', 'thr_d'], settings[4]) + update(['OpenPose', 'params', 'include_body'], settings[5]) + update(['OpenPose', 'params', 'include_hand'], settings[6]) + update(['OpenPose', 'params', 'include_face'], settings[7]) + update(['PidiNet', 'params', 'scribble'], settings[8]) + update(['PidiNet', 'params', 'apply_filter'], settings[9]) + update(['LineArt Realistic', 'params', 'coarse'], settings[10]) + update(['Leres Depth', 'params', 'boost'], settings[11]) + update(['Leres Depth', 'params', 'thr_a'], settings[12]) + update(['Leres Depth', 'params', 'thr_b'], settings[13]) + update(['MediaPipe Face', 'params', 'max_faces'], settings[14]) + update(['MediaPipe Face', 'params', 'min_confidence'], settings[15]) + update(['Canny', 'params', 'low_threshold'], settings[16]) + update(['Canny', 'params', 'high_threshold'], settings[17]) + update(['DWPose', 'model'], settings[18]) + update(['DWPose', 'params', 'min_confidence'], settings[19]) + update(['SegmentAnything', 'model'], settings[20]) + update(['Edge', 'params', 'pf'], settings[21]) + update(['Edge', 'params', 'mode'], settings[22]) + + +class Processor(): + def __init__(self, processor_id: str = None, resize = True, load_config = None): + self.model = None + self.resize = resize + self.processor_id = processor_id + self.override = None # override input image + self.load_config = { 'cache_dir': cache_dir } + from_config = config.get(processor_id, {}).get('load_config', None) + if load_config is not None: + self.load_config.update(load_config) + if from_config is not None: + self.load_config.update(from_config) + if processor_id is not None: + self.load() + + def reset(self): + if self.model is not None: + log.debug(f'Control processor unloaded: id="{self.processor_id}"') + self.model = None + self.processor_id = None + self.override = None + + def load(self, processor_id: str = None): + try: + t0 = time.time() + processor_id = processor_id or self.processor_id + if processor_id is None or processor_id == 'None': + self.reset() + return + from_config = config.get(processor_id, {}).get('load_config', None) + if from_config is not None: + self.load_config.update(from_config) + cls = config[processor_id]['class'] + log.debug(f'Control processor loading: id="{processor_id}" class={cls.__name__}') + if 'DWPose' in processor_id: + det_ckpt = 'https://download.openmmlab.com/mmdetection/v2.0/yolox/yolox_l_8x8_300e_coco/yolox_l_8x8_300e_coco_20211126_140236-d3bd2b23.pth' + if 'Tiny' == config['DWPose']['model']: + pose_config = 'config/rtmpose-t_8xb64-270e_coco-ubody-wholebody-256x192.py' + pose_ckpt = 'https://huggingface.co/yzd-v/DWPose/resolve/main/dw-tt_ucoco.pth' + elif 'Medium' == config['DWPose']['model']: + pose_config = 'config/rtmpose-m_8xb64-270e_coco-ubody-wholebody-256x192.py' + pose_ckpt = 'https://huggingface.co/yzd-v/DWPose/resolve/main/dw-mm_ucoco.pth' + elif 'Large' == config['DWPose']['model']: + pose_config = 'config/rtmpose-l_8xb32-270e_coco-ubody-wholebody-384x288.py' + pose_ckpt = 'https://huggingface.co/yzd-v/DWPose/resolve/main/dw-ll_ucoco_384.pth' + else: + log.error(f'Control processor load failed: id="{processor_id}" error=unknown model type') + return + self.model = cls(det_ckpt=det_ckpt, pose_config=pose_config, pose_ckpt=pose_ckpt, device="cpu") + elif 'SegmentAnything' in processor_id: + if 'Base' == config['SegmentAnything']['model']: + self.model = cls.from_pretrained(model_path = 'segments-arnaud/sam_vit_b', filename='sam_vit_b_01ec64.pth', model_type='vit_b', **self.load_config) + elif 'Large' == config['SegmentAnything']['model']: + self.model = cls.from_pretrained(model_path = 'segments-arnaud/sam_vit_l', filename='sam_vit_l_0b3195.pth', model_type='vit_l', **self.load_config) + else: + log.error(f'Control processor load failed: id="{processor_id}" error=unknown model type') + return + elif config[processor_id].get('load_config', None) is not None: + self.model = cls.from_pretrained(**self.load_config) + elif config[processor_id]['checkpoint']: + self.model = cls.from_pretrained("lllyasviel/Annotators", **self.load_config) + else: + self.model = cls() # class instance only + t1 = time.time() + self.processor_id = processor_id + log.debug(f'Control processor loaded: id="{processor_id}" class={self.model.__class__.__name__} time={t1-t0:.2f}') + except Exception as e: + log.error(f'Control processor load failed: id="{processor_id}" error={e}') + display(e, 'Control processor load') + + def __call__(self, image_input: Image): + if self.override is not None: + image_input = self.override + image_process = image_input + if image_input is None: + log.error('Control processor: no input') + return image_process + if self.model is None: + # log.error('Control processor: model not loaded') + return image_process + if config[self.processor_id].get('dirty', False): + processor_id = self.processor_id + config[processor_id].pop('dirty') + self.reset() + self.load(processor_id) + try: + t0 = time.time() + kwargs = config.get(self.processor_id, {}).get('params', None) + if self.resize: + orig_size = image_input.size + image_resized = image_input.resize((512, 512)) + else: + image_resized = image_input + with torch.no_grad(): + image_process = self.model(image_resized, **kwargs) + if self.resize: + image_process = image_process.resize(orig_size, Image.Resampling.LANCZOS) + t1 = time.time() + log.debug(f'Control processor: id="{self.processor_id}" args={kwargs} time={t1-t0:.2f}') + except Exception as e: + log.error(f'Control processor failed: id="{self.processor_id}" error={e}') + display(e, 'Control processor') + return image_process + + def preview(self, image_input: Image): + return self.__call__(image_input) diff --git a/modules/control/reference.py b/modules/control/reference.py new file mode 100644 index 000000000..4d84aeca5 --- /dev/null +++ b/modules/control/reference.py @@ -0,0 +1,57 @@ +import time +from diffusers import StableDiffusionPipeline, StableDiffusionXLPipeline +from modules.shared import log +from modules.control.proc.reference_sd15 import StableDiffusionReferencePipeline +from modules.control.proc.reference_sdxl import StableDiffusionXLReferencePipeline + + +what = 'ControlNet-XS' + + +def list_models(): + return ['Reference'] + + +class ReferencePipeline(): + def __init__(self, pipeline: StableDiffusionXLPipeline | StableDiffusionPipeline, dtype = None): + t0 = time.time() + self.orig_pipeline = pipeline + self.pipeline = None + if pipeline is None: + log.error(f'Control {what} model pipeline: model not loaded') + return + if isinstance(pipeline, StableDiffusionXLPipeline): + self.pipeline = StableDiffusionXLReferencePipeline( + vae=pipeline.vae, + text_encoder=pipeline.text_encoder, + text_encoder_2=pipeline.text_encoder_2, + tokenizer=pipeline.tokenizer, + tokenizer_2=pipeline.tokenizer_2, + unet=pipeline.unet, + scheduler=pipeline.scheduler, + ).to(pipeline.device) + elif isinstance(pipeline, StableDiffusionPipeline): + self.pipeline = StableDiffusionReferencePipeline( + vae=pipeline.vae, + text_encoder=pipeline.text_encoder, + tokenizer=pipeline.tokenizer, + unet=pipeline.unet, + scheduler=pipeline.scheduler, + requires_safety_checker=False, + safety_checker=None, + feature_extractor=None, + ).to(pipeline.device) + else: + log.error(f'Control {what} pipeline: class={pipeline.__class__.__name__} unsupported model type') + return + if dtype is not None and self.pipeline is not None: + self.pipeline = self.pipeline.to(dtype) + t1 = time.time() + if self.pipeline is not None: + log.debug(f'Control {what} pipeline: class={self.pipeline.__class__.__name__} time={t1-t0:.2f}') + else: + log.error(f'Control {what} pipeline: not initialized') + + def restore(self): + self.pipeline = None + return self.orig_pipeline diff --git a/modules/control/run.py b/modules/control/run.py new file mode 100644 index 000000000..fe99413a2 --- /dev/null +++ b/modules/control/run.py @@ -0,0 +1,433 @@ +import os +import time +import math +from typing import List, Union +import cv2 +import numpy as np +import diffusers +from PIL import Image +from modules.control import util +from modules.control import unit +from modules.control import processors +from modules.control import controlnets # lllyasviel ControlNet +from modules.control import controlnetsxs # VisLearn ControlNet-XS +from modules.control import adapters # TencentARC T2I-Adapter +from modules.control import reference # ControlNet-Reference +from modules import devices, shared, errors, processing, images, sd_models, sd_samplers + + +debug = shared.log.debug if os.environ.get('SD_CONTROL_DEBUG', None) is not None else lambda *args, **kwargs: None +pipe = None +original_pipeline = None + + +class ControlProcessing(processing.StableDiffusionProcessingImg2Img): + def __init__(self, **kwargs): + super().__init__(**kwargs) + self.strength = None + self.adapter_conditioning_scale = None + self.adapter_conditioning_factor = None + self.guess_mode = None + self.controlnet_conditioning_scale = None + self.control_guidance_start = None + self.control_guidance_end = None + self.reference_attn = None + self.reference_adain = None + self.attention_auto_machine_weight = None + self.gn_auto_machine_weight = None + self.style_fidelity = None + self.ref_image = None + self.image = None + self.query_weight = None + self.adain_weight = None + self.adapter_conditioning_factor = 1.0 + self.attention = 'Attention' + self.fidelity = 0.5 + self.override = None + + def sample(self, conditioning, unconditional_conditioning, seeds, subseeds, subseed_strength, prompts): # abstract + pass + + +def restore_pipeline(): + global pipe # pylint: disable=global-statement + pipe = None + if original_pipeline is not None: + shared.sd_model = original_pipeline + debug(f'Control restored pipeline: class={shared.sd_model.__class__.__name__}') + devices.torch_gc() + + +def control_run(units: List[unit.Unit], inputs, unit_type: str, is_generator: bool, + prompt, negative, styles, steps, sampler_index, + seed, subseed, subseed_strength, seed_resize_from_h, seed_resize_from_w, + cfg_scale, clip_skip, image_cfg_scale, diffusers_guidance_rescale, full_quality, restore_faces, tiling, + hdr_clamp, hdr_boundary, hdr_threshold, hdr_center, hdr_channel_shift, hdr_full_shift, hdr_maximize, hdr_max_center, hdr_max_boundry, + resize_mode, resize_name, width, height, scale_by, selected_scale_tab, resize_time, + denoising_strength, batch_count, batch_size, + video_skip_frames, video_type, video_duration, video_loop, video_pad, video_interpolate, + ): + global pipe, original_pipeline # pylint: disable=global-statement + debug(f'Control {unit_type}: input={inputs}') + if inputs is None or (type(inputs) is list and len(inputs) == 0): + inputs = [None] + output_images: List[Image.Image] = [] # output images + active_process: List[processors.Processor] = [] # all active preprocessors + active_model: List[Union[controlnets.ControlNet, controlnetsxs.ControlNetXS, adapters.Adapter]] = [] # all active models + active_strength: List[float] = [] # strength factors for all active models + active_start: List[float] = [] # start step for all active models + active_end: List[float] = [] # end step for all active models + processed_image: Image.Image = None # last processed image + width = 8 * math.ceil(width / 8) + height = 8 * math.ceil(height / 8) + + p = ControlProcessing( + prompt = prompt, + negative_prompt = negative, + styles = styles, + steps = steps, + sampler_name = sd_samplers.samplers[sampler_index].name, + latent_sampler = sd_samplers.samplers[sampler_index].name, + seed = seed, + subseed = subseed, + subseed_strength = subseed_strength, + seed_resize_from_h = seed_resize_from_h, + seed_resize_from_w = seed_resize_from_w, + cfg_scale = cfg_scale, + clip_skip = clip_skip, + image_cfg_scale = image_cfg_scale, + diffusers_guidance_rescale = diffusers_guidance_rescale, + full_quality = full_quality, + restore_faces = restore_faces, + tiling = tiling, + hdr_clamp = hdr_clamp, + hdr_boundary = hdr_boundary, + hdr_threshold = hdr_threshold, + hdr_center = hdr_center, + hdr_channel_shift = hdr_channel_shift, + hdr_full_shift = hdr_full_shift, + hdr_maximize = hdr_maximize, + hdr_max_center = hdr_max_center, + hdr_max_boundry = hdr_max_boundry, + resize_mode = resize_mode, + resize_name = resize_name, + scale_by = scale_by, + selected_scale_tab = selected_scale_tab, + denoising_strength = denoising_strength, + n_iter = batch_count, + batch_size = batch_size, + ) + if resize_mode != 0 or inputs is None or inputs == [None]: + p.width = width # pylint: disable=attribute-defined-outside-init + p.height = height # pylint: disable=attribute-defined-outside-init + if selected_scale_tab == 1: + width = int(width * scale_by) + height = int(height * scale_by) + else: + del p.width + del p.height + + + t0 = time.time() + for u in units: + if not u.enabled or u.type != unit_type: + continue + if unit_type == 'adapter' and u.adapter.model is not None: + active_process.append(u.process) + active_model.append(u.adapter) + active_strength.append(u.strength) + p.adapter_conditioning_factor = u.factor + shared.log.debug(f'Control T2I-Adapter unit: process={u.process.processor_id} model={u.adapter.model_id} strength={u.strength} factor={u.factor}') + elif unit_type == 'controlnet' and u.controlnet.model is not None: + active_process.append(u.process) + active_model.append(u.controlnet) + active_strength.append(u.strength) + active_start.append(u.start) + active_end.append(u.end) + p.guess_mode = u.guess + shared.log.debug(f'Control ControlNet unit: process={u.process.processor_id} model={u.controlnet.model_id} strength={u.strength} guess={u.guess} start={u.start} end={u.end}') + elif unit_type == 'xs' and u.controlnet.model is not None: + active_process.append(u.process) + active_model.append(u.controlnet) + active_strength.append(u.strength) + active_start.append(u.start) + active_end.append(u.end) + p.guess_mode = u.guess + shared.log.debug(f'Control ControlNet-XS unit: process={u.process.processor_id} model={u.controlnet.model_id} strength={u.strength} guess={u.guess} start={u.start} end={u.end}') + elif unit_type == 'reference': + p.override = u.override + p.attention = u.attention + p.query_weight = u.query_weight + p.adain_weight = u.adain_weight + p.fidelity = u.fidelity + shared.log.debug('Control Reference unit') + else: + active_process.append(u.process) + # active_model.append(model) + active_strength.append(u.strength) + """ + if (len(active_process) == 0) and (unit_type != 'reference'): + msg = 'Control: no active units' + shared.log.warning(msg) + restore_pipeline() + return msg + """ + p.ops.append('control') + + has_models = False + selected_models: List[Union[controlnets.ControlNetModel, controlnetsxs.ControlNetXSModel, adapters.AdapterModel]] = None + if unit_type == 'adapter' or unit_type == 'controlnet' or unit_type == 'xs': + if len(active_model) == 0: + selected_models = None + elif len(active_model) == 1: + selected_models = active_model[0].model if active_model[0].model is not None else None + p.extra_generation_params["Control model"] = (active_model[0].model_id or '') if active_model[0].model is not None else None + has_models = selected_models is not None + else: + selected_models = [m.model for m in active_model if m.model is not None] + p.extra_generation_params["Control model"] = ', '.join([(m.model_id or '') for m in active_model if m.model is not None]) + has_models = len(selected_models) > 0 + use_conditioning = active_strength[0] if len(active_strength) == 1 else list(active_strength) # strength or list[strength] + else: + pass + shared.sd_model = sd_models.set_diffuser_pipe(shared.sd_model, sd_models.DiffusersTaskType.TEXT_2_IMAGE) # reset current pipeline + + if not has_models and (unit_type == 'reference' or unit_type == 'controlnet' or unit_type == 'xs'): # run in img2img mode + if len(active_strength) > 0: + p.strength = active_strength[0] + pipe = diffusers.AutoPipelineForImage2Image.from_pipe(shared.sd_model) # use set_diffuser_pipe + elif unit_type == 'adapter' and has_models: + p.extra_generation_params["Control mode"] = 'Adapter' + p.extra_generation_params["Control conditioning"] = use_conditioning + p.adapter_conditioning_scale = use_conditioning + instance = adapters.AdapterPipeline(selected_models, shared.sd_model) + pipe = instance.pipeline + elif unit_type == 'controlnet' and has_models: + p.extra_generation_params["Control mode"] = 'ControlNet' + p.extra_generation_params["Control conditioning"] = use_conditioning + p.controlnet_conditioning_scale = use_conditioning + p.control_guidance_start = active_start[0] if len(active_start) == 1 else list(active_start) + p.control_guidance_end = active_end[0] if len(active_end) == 1 else list(active_end) + instance = controlnets.ControlNetPipeline(selected_models, shared.sd_model) + pipe = instance.pipeline + elif unit_type == 'xs' and has_models: + p.extra_generation_params["Control mode"] = 'ControlNet-XS' + p.extra_generation_params["Control conditioning"] = use_conditioning + p.controlnet_conditioning_scale = use_conditioning + p.control_guidance_start = active_start[0] if len(active_start) == 1 else list(active_start) + p.control_guidance_end = active_end[0] if len(active_end) == 1 else list(active_end) + instance = controlnetsxs.ControlNetPipeline(selected_models, shared.sd_model) + pipe = instance.pipeline + elif unit_type == 'reference': + p.extra_generation_params["Control mode"] = 'Reference' + p.extra_generation_params["Control attention"] = p.attention + p.reference_attn = 'Attention' in p.attention + p.reference_adain = 'Adain' in p.attention + p.attention_auto_machine_weight = p.query_weight + p.gn_auto_machine_weight = p.adain_weight + p.style_fidelity = p.fidelity + instance = reference.ReferencePipeline(shared.sd_model) + pipe = instance.pipeline + else: + shared.log.error('Control: unknown unit type') + pipe = None + debug(f'Control pipeline: class={pipe.__class__} args={vars(p)}') + t1, t2, t3 = time.time(), 0, 0 + status = True + frame = None + video = None + output_filename = None + index = 0 + frames = 0 + + original_pipeline = shared.sd_model + if pipe is not None: + shared.sd_model = pipe + if not ((shared.opts.diffusers_model_cpu_offload or shared.cmd_opts.medvram) or (shared.opts.diffusers_seq_cpu_offload or shared.cmd_opts.lowvram)): + shared.sd_model.to(shared.device) + sd_models.copy_diffuser_options(shared.sd_model, original_pipeline) # copy options from original pipeline + sd_models.set_diffuser_options(shared.sd_model) + + try: + with devices.inference_context(): + if isinstance(inputs, str): # only video, the rest is a list + try: + video = cv2.VideoCapture(inputs) + if not video.isOpened(): + msg = f'Control: video open failed: path={inputs}' + shared.log.error(msg) + restore_pipeline() + return msg + frames = int(video.get(cv2.CAP_PROP_FRAME_COUNT)) + fps = int(video.get(cv2.CAP_PROP_FPS)) + w, h = int(video.get(cv2.CAP_PROP_FRAME_WIDTH)), int(video.get(cv2.CAP_PROP_FRAME_HEIGHT)) + codec = util.decode_fourcc(video.get(cv2.CAP_PROP_FOURCC)) + status, frame = video.read() + shared.log.debug(f'Control: input video: path={inputs} frames={frames} fps={fps} size={w}x{h} codec={codec}') + except Exception as e: + msg = f'Control: video open failed: path={inputs} {e}' + shared.log.error(msg) + restore_pipeline() + return msg + + while status: + processed_image = None + if frame is not None: + inputs = [Image.fromarray(frame)] # cv2 to pil + for i, input_image in enumerate(inputs): + if shared.state.skipped: + shared.state.skipped = False + continue + if shared.state.interrupted: + shared.state.interrupted = False + restore_pipeline() + return 'Control interrupted' + # get input + if isinstance(input_image, str): + try: + input_image = Image.open(input_image) + except Exception as e: + shared.log.error(f'Control: image open failed: path={input_image} error={e}') + continue + index += 1 + if video is not None and index % (video_skip_frames + 1) != 0: + continue + + # resize + if resize_mode != 0 and input_image is not None: + p.extra_generation_params["Control resize"] = f'{resize_time}: {resize_name}' + if resize_mode != 0 and input_image is not None and resize_time == 'Before': + debug(f'Control resize: image={input_image} width={width} height={height} mode={resize_mode} name={resize_name} sequence={resize_time}') + input_image = images.resize_image(resize_mode, input_image, width, height, resize_name) + + # process + if input_image is None: + p.image = None + processed_image = None + debug('Control: process=None image=None') + elif len(active_process) == 0 and unit_type == 'reference': + p.ref_image = p.override or input_image + p.task_args['ref_image'] = p.ref_image + debug(f'Control: process=None image={p.ref_image}') + if p.ref_image is None: + msg = 'Control: attempting reference mode but image is none' + shared.log.error(msg) + restore_pipeline() + return msg + processed_image = p.ref_image + elif len(active_process) == 1: + p.image = active_process[0](input_image) + p.task_args['image'] = p.image + p.extra_generation_params["Control process"] = active_process[0].processor_id + debug(f'Control: process={active_process[0].processor_id} image={p.image}') + if p.image is None: + msg = 'Control: attempting process but output is none' + shared.log.error(msg) + restore_pipeline() + return msg + processed_image = p.image + else: + if len(active_process) > 0: + p.image = [p(input_image) for p in active_process] # list[image] + else: + p.image = [input_image] + p.task_args['image'] = p.image + p.extra_generation_params["Control process"] = [p.processor_id for p in active_process] + debug(f'Control: process={[p.processor_id for p in active_process]} image={p.image}') + if any(img is None for img in p.image): + msg = 'Control: attempting process but output is none' + shared.log.error(msg) + restore_pipeline() + return msg + processed_image = [np.array(i) for i in p.image] + processed_image = util.blend(processed_image) # blend all processed images into one + processed_image = Image.fromarray(processed_image) + if is_generator: + image_txt = f'{processed_image.width}x{processed_image.height}' if processed_image is not None else 'None' + msg = f'process | {index} of {frames if video is not None else len(inputs)} | {"Image" if video is None else "Frame"} {image_txt}' + debug(f'Control yield: {msg}') + yield (None, processed_image, f'Control {msg}') + t2 += time.time() - t2 + + # pipeline + output = None + if pipe is not None: # run new pipeline + if not has_models and (unit_type == 'reference' or unit_type == 'controlnet'): # run in img2img mode + if p.image is None: + if hasattr(p, 'init_images'): + del p.init_images + shared.sd_model = sd_models.set_diffuser_pipe(shared.sd_model, sd_models.DiffusersTaskType.TEXT_2_IMAGE) # reset current pipeline + else: + p.init_images = [processed_image] # pylint: disable=attribute-defined-outside-init + processed_image.save('/tmp/test.png') + shared.sd_model = sd_models.set_diffuser_pipe(shared.sd_model, sd_models.DiffusersTaskType.IMAGE_2_IMAGE) # reset current pipeline + else: + if hasattr(p, 'init_images'): + del p.init_images + shared.sd_model = sd_models.set_diffuser_pipe(shared.sd_model, sd_models.DiffusersTaskType.TEXT_2_IMAGE) # reset current pipeline + processed: processing.Processed = processing.process_images(p) # run actual pipeline + output = processed.images if processed is not None else None + # output = pipe(**vars(p)).images # alternative direct pipe exec call + else: # blend all processed images and return + output = [processed_image] + t3 += time.time() - t3 + + # outputs + if output is not None and len(output) > 0: + output_image = output[0] + + # resize + if resize_mode != 0 and resize_time == 'After': + debug(f'Control resize: image={input_image} width={width} height={height} mode={resize_mode} name={resize_name} sequence={resize_time}') + output_image = images.resize_image(resize_mode, output_image, width, height, resize_name) + elif hasattr(p, 'width') and hasattr(p, 'height'): + output_image = output_image.resize((p.width, p.height), Image.Resampling.LANCZOS) + + output_images.append(output_image) + if is_generator: + image_txt = f'{output_image.width}x{output_image.height}' if output_image is not None else 'None' + if video is not None: + msg = f'Control output | {index} of {frames} skip {video_skip_frames} | Frame {image_txt}' + else: + msg = f'Control output | {index} of {len(inputs)} | Image {image_txt}' + yield (output_image, processed_image, msg) # result is control_output, proces_output + + if video is not None and frame is not None: + status, frame = video.read() + debug(f'Control: video frame={index} frames={frames} status={status} skip={index % (video_skip_frames + 1)} progress={index/frames:.2f}') + else: + status = False + + if video is not None: + video.release() + + shared.log.info(f'Control: pipeline units={len(active_model)} process={len(active_process)} time={t3-t0:.2f} init={t1-t0:.2f} proc={t2-t1:.2f} ctrl={t3-t2:.2f} outputs={len(output_images)}') + except Exception as e: + shared.log.error(f'Control pipeline failed: type={unit_type} units={len(active_model)} error={e}') + errors.display(e, 'Control') + + shared.sd_model = original_pipeline + pipe = None + devices.torch_gc() + + if len(output_images) == 0: + output_images = None + image_txt = 'images=None' + elif len(output_images) == 1: + output_images = output_images[0] + image_txt = f'| Images 1 | Size {output_images.width}x{output_images.height}' if output_image is not None else 'None' + else: + image_txt = f'| Images {len(output_images)} | Size {output_images[0].width}x{output_images[0].height}' if output_image is not None else 'None' + + if video_type != 'None' and isinstance(output_images, list): + p.do_not_save_grid = True # pylint: disable=attribute-defined-outside-init + output_filename = images.save_video(p, filename=None, images=output_images, video_type=video_type, duration=video_duration, loop=video_loop, pad=video_pad, interpolate=video_interpolate, sync=True) + image_txt = f'| Frames {len(output_images)} | Size {output_images[0].width}x{output_images[0].height}' + + image_txt += f' | {util.dict2str(p.extra_generation_params)}' + debug(f'Control ready: {image_txt}') + restore_pipeline() + if is_generator: + yield (output_images, processed_image, f'Control ready {image_txt}', output_filename) + else: + return (output_images, processed_image, f'Control ready {image_txt}', output_filename) diff --git a/modules/control/test.py b/modules/control/test.py new file mode 100644 index 000000000..0c5b61053 --- /dev/null +++ b/modules/control/test.py @@ -0,0 +1,40 @@ +import math +from PIL import Image +from modules.control import processors # patrickvonplaten controlnet_aux +from modules import shared + + +def test_processors(image): + if image is None: + shared.log.error('Image not loaded') + return None, None, None + from PIL import ImageDraw, ImageFont + images = [] + for processor_id in processors.list_models(): + shared.log.info(f'Testing processor: {processor_id}') + processor = processors.Processor(processor_id) + if processor is None: + shared.log.error(f'Processor load failed: id="{processor_id}"') + continue + output = processor(image) + processor.reset() + draw = ImageDraw.Draw(output) + font = ImageFont.truetype('DejaVuSansMono', 48) + draw.text((10, 10), processor_id, (0,0,0), font=font) + draw.text((8, 8), processor_id, (255,255,255), font=font) + images.append(output) + yield output, None, None, images + rows = round(math.sqrt(len(images))) + cols = math.ceil(len(images) / rows) + w, h = 256, 256 + size = (cols * w + cols, rows * h + rows) + grid = Image.new('RGB', size=size, color='black') + shared.log.info(f'Test processors: images={len(images)} grid={grid}') + for i, image in enumerate(images): + x = (i % cols * w) + (i % cols) + y = (i // cols * h) + (i // cols) + thumb = image.copy().convert('RGB') + thumb.thumbnail((w, h), Image.Resampling.HAMMING) + grid.paste(thumb, box=(x, y)) + yield None, grid, None, images + return None, grid, None, images # preview_process, output_image, output_video, output_gallery diff --git a/modules/control/unit.py b/modules/control/unit.py new file mode 100644 index 000000000..3d52be522 --- /dev/null +++ b/modules/control/unit.py @@ -0,0 +1,154 @@ +from typing import Union +from PIL import Image +from modules.shared import log +from modules.control import processors +from modules.control import controlnets +from modules.control import controlnetsxs +from modules.control import adapters +from modules.control import reference # pylint: disable=unused-import + + +default_device = None +default_dtype = None + + +class Unit(): # mashup of gradio controls and mapping to actual implementation classes + def __init__(self, + # values + enabled: bool = None, + strength: float = None, + unit_type: str = None, + start: float = 0, + end: float = 1, + # ui bindings + enabled_cb = None, + reset_btn = None, + process_id = None, + preview_btn = None, + model_id = None, + model_strength = None, + image_input = None, + preview_process = None, + image_upload = None, + control_start = None, + control_end = None, + extra_controls: list = [], # noqa B006 + ): + self.enabled = enabled or False + self.type = unit_type + self.strength = strength or 1.0 + self.start = start or 0 + self.end = end or 1 + self.start = min(self.start, self.end) + self.end = max(self.start, self.end) + # processor always exists, adapter and controlnet are optional + self.process: processors.Processor = processors.Processor() + self.adapter: adapters.Adapter = None + self.controlnet: Union[controlnets.ControlNet, controlnetsxs.ControlNetXS] = None + # map to input image + self.input: Image = image_input + self.override: Image = None + # global settings but passed per-unit + self.factor = 1.0 + self.guess = False + self.start = 0 + self.end = 1 + # reference settings + self.attention = 'Attention' + self.fidelity = 0.5 + self.query_weight = 1.0 + self.adain_weight = 1.0 + + def reset(): + if self.process is not None: + self.process.reset() + if self.adapter is not None: + self.adapter.reset() + if self.controlnet is not None: + self.controlnet.reset() + self.override = None + return [True, 'None', 'None', 1.0] # reset ui values + + def enabled_change(val): + self.enabled = val + + def strength_change(val): + self.strength = val + + def control_change(start, end): + self.start = min(start, end) + self.end = max(start, end) + + def adapter_extra(c1): + self.factor = c1 + + def controlnet_extra(c1): + self.guess = c1 + + def controlnetxs_extra(_c1): + pass # gr.component passed directly to load method + + def reference_extra(c1, c2, c3, c4): + self.attention = c1 + self.fidelity = c2 + self.query_weight = c3 + self.adain_weight = c4 + + def upload_image(image_file): + try: + self.process.override = Image.open(image_file.name) + self.override = self.process.override + log.debug(f'Control process upload image: path="{image_file.name}" image={self.process.override}') + except Exception as e: + log.error(f'Control process upload image failed: path="{image_file.name}" error={e}') + + # actual init + if self.type == 'adapter': + self.adapter = adapters.Adapter(device=default_device, dtype=default_dtype) + elif self.type == 'controlnet': + self.controlnet = controlnets.ControlNet(device=default_device, dtype=default_dtype) + elif self.type == 'xs': + self.controlnet = controlnetsxs.ControlNetXS(device=default_device, dtype=default_dtype) + elif self.type == 'reference': + pass + else: + log.error(f'Control unknown type: unit={unit_type}') + return + + # bind ui controls to properties if present + if self.type == 'adapter': + if model_id is not None: + model_id.change(fn=self.adapter.load, inputs=[model_id], show_progress=True) + if extra_controls is not None and len(extra_controls) > 0: + extra_controls[0].change(fn=adapter_extra, inputs=extra_controls) + elif self.type == 'controlnet': + if model_id is not None: + model_id.change(fn=self.controlnet.load, inputs=[model_id], show_progress=True) + if extra_controls is not None and len(extra_controls) > 0: + extra_controls[0].change(fn=controlnet_extra, inputs=extra_controls) + elif self.type == 'xs': + if model_id is not None: + model_id.change(fn=self.controlnet.load, inputs=[model_id, extra_controls[0]], show_progress=True) + if extra_controls is not None and len(extra_controls) > 0: + extra_controls[0].change(fn=controlnetxs_extra, inputs=extra_controls) + elif self.type == 'reference': + if extra_controls is not None and len(extra_controls) > 0: + extra_controls[0].change(fn=reference_extra, inputs=extra_controls) + extra_controls[1].change(fn=reference_extra, inputs=extra_controls) + extra_controls[2].change(fn=reference_extra, inputs=extra_controls) + extra_controls[3].change(fn=reference_extra, inputs=extra_controls) + if enabled_cb is not None: + enabled_cb.change(fn=enabled_change, inputs=[enabled_cb]) + if model_strength is not None: + model_strength.change(fn=strength_change, inputs=[model_strength]) + if process_id is not None: + process_id.change(fn=self.process.load, inputs=[process_id], show_progress=True) + if reset_btn is not None: + reset_btn.click(fn=reset, inputs=[], outputs=[enabled_cb, model_id, process_id, model_strength]) + if preview_btn is not None: + preview_btn.click(fn=self.process.preview, inputs=[self.input], outputs=[preview_process]) # return list of images for gallery + if image_upload is not None: + image_upload.upload(fn=upload_image, inputs=[image_upload], outputs=[]) # return list of images for gallery + if control_start is not None and control_end is not None: + control_start.change(fn=control_change, inputs=[control_start, control_end]) + control_end.change(fn=control_change, inputs=[control_start, control_end]) diff --git a/modules/control/util.py b/modules/control/util.py new file mode 100644 index 000000000..9fe42877e --- /dev/null +++ b/modules/control/util.py @@ -0,0 +1,160 @@ +import os +import sys +import random +import cv2 +import numpy as np +import torch + + +annotator_ckpts_path = os.path.join(os.path.dirname(__file__), 'ckpts') + + +def dict2str(d: dict): + arr = [f'{name}: {d[name]}' for i, name in enumerate(d)] + return ' | '.join(arr) + + +def HWC3(x): + assert x.dtype == np.uint8 + if x.ndim == 2: + x = x[:, :, None] + assert x.ndim == 3 + _H, _W, C = x.shape + assert C == 1 or C == 3 or C == 4 + if C == 3: + return x + if C == 1: + return np.concatenate([x, x, x], axis=2) + if C == 4: + color = x[:, :, 0:3].astype(np.float32) + alpha = x[:, :, 3:4].astype(np.float32) / 255.0 + y = color * alpha + 255.0 * (1.0 - alpha) + y = y.clip(0, 255).astype(np.uint8) + return y + + +def make_noise_disk(H, W, C, F): + noise = np.random.uniform(low=0, high=1, size=((H // F) + 2, (W // F) + 2, C)) # noqa + noise = cv2.resize(noise, (W + 2 * F, H + 2 * F), interpolation=cv2.INTER_CUBIC) + noise = noise[F: F + H, F: F + W] + noise -= np.min(noise) + noise /= np.max(noise) + if C == 1: + noise = noise[:, :, None] + return noise + + +def nms(x, t, s): + x = cv2.GaussianBlur(x.astype(np.float32), (0, 0), s) + + f1 = np.array([[0, 0, 0], [1, 1, 1], [0, 0, 0]], dtype=np.uint8) + f2 = np.array([[0, 1, 0], [0, 1, 0], [0, 1, 0]], dtype=np.uint8) + f3 = np.array([[1, 0, 0], [0, 1, 0], [0, 0, 1]], dtype=np.uint8) + f4 = np.array([[0, 0, 1], [0, 1, 0], [1, 0, 0]], dtype=np.uint8) + y = np.zeros_like(x) + for f in [f1, f2, f3, f4]: + np.putmask(y, cv2.dilate(x, kernel=f) == x, x) + z = np.zeros_like(y, dtype=np.uint8) + z[y > t] = 255 + return z + +def min_max_norm(x): + x -= np.min(x) + x /= np.maximum(np.max(x), 1e-5) + return x + + +def safe_step(x, step=2): + y = x.astype(np.float32) * float(step + 1) + y = y.astype(np.int32).astype(np.float32) / float(step) + return y + + +def img2mask(img, H, W, low=10, high=90): + assert img.ndim == 3 or img.ndim == 2 + assert img.dtype == np.uint8 + if img.ndim == 3: + y = img[:, :, random.randrange(0, img.shape[2])] + else: + y = img + y = cv2.resize(y, (W, H), interpolation=cv2.INTER_CUBIC) + if random.uniform(0, 1) < 0.5: + y = 255 - y + return y < np.percentile(y, random.randrange(low, high)) + + +def resize_image(input_image, resolution): + H, W, _C = input_image.shape + H = float(H) + W = float(W) + k = float(resolution) / min(H, W) + H *= k + W *= k + H = int(np.round(H / 64.0)) * 64 + W = int(np.round(W / 64.0)) * 64 + img = cv2.resize(input_image, (W, H), interpolation=cv2.INTER_LANCZOS4 if k > 1 else cv2.INTER_AREA) + return img + + +def torch_gc(): + if torch.cuda.is_available(): + torch.cuda.empty_cache() + torch.cuda.ipc_collect() + + +def ade_palette(): + """ADE20K palette that maps each class to RGB values.""" + return [[120, 120, 120], [180, 120, 120], [6, 230, 230], [80, 50, 50], + [4, 200, 3], [120, 120, 80], [140, 140, 140], [204, 5, 255], + [230, 230, 230], [4, 250, 7], [224, 5, 255], [235, 255, 7], + [150, 5, 61], [120, 120, 70], [8, 255, 51], [255, 6, 82], + [143, 255, 140], [204, 255, 4], [255, 51, 7], [204, 70, 3], + [0, 102, 200], [61, 230, 250], [255, 6, 51], [11, 102, 255], + [255, 7, 71], [255, 9, 224], [9, 7, 230], [220, 220, 220], + [255, 9, 92], [112, 9, 255], [8, 255, 214], [7, 255, 224], + [255, 184, 6], [10, 255, 71], [255, 41, 10], [7, 255, 255], + [224, 255, 8], [102, 8, 255], [255, 61, 6], [255, 194, 7], + [255, 122, 8], [0, 255, 20], [255, 8, 41], [255, 5, 153], + [6, 51, 255], [235, 12, 255], [160, 150, 20], [0, 163, 255], + [140, 140, 140], [250, 10, 15], [20, 255, 0], [31, 255, 0], + [255, 31, 0], [255, 224, 0], [153, 255, 0], [0, 0, 255], + [255, 71, 0], [0, 235, 255], [0, 173, 255], [31, 0, 255], + [11, 200, 200], [255, 82, 0], [0, 255, 245], [0, 61, 255], + [0, 255, 112], [0, 255, 133], [255, 0, 0], [255, 163, 0], + [255, 102, 0], [194, 255, 0], [0, 143, 255], [51, 255, 0], + [0, 82, 255], [0, 255, 41], [0, 255, 173], [10, 0, 255], + [173, 255, 0], [0, 255, 153], [255, 92, 0], [255, 0, 255], + [255, 0, 245], [255, 0, 102], [255, 173, 0], [255, 0, 20], + [255, 184, 184], [0, 31, 255], [0, 255, 61], [0, 71, 255], + [255, 0, 204], [0, 255, 194], [0, 255, 82], [0, 10, 255], + [0, 112, 255], [51, 0, 255], [0, 194, 255], [0, 122, 255], + [0, 255, 163], [255, 153, 0], [0, 255, 10], [255, 112, 0], + [143, 255, 0], [82, 0, 255], [163, 255, 0], [255, 235, 0], + [8, 184, 170], [133, 0, 255], [0, 255, 92], [184, 0, 255], + [255, 0, 31], [0, 184, 255], [0, 214, 255], [255, 0, 112], + [92, 255, 0], [0, 224, 255], [112, 224, 255], [70, 184, 160], + [163, 0, 255], [153, 0, 255], [71, 255, 0], [255, 0, 163], + [255, 204, 0], [255, 0, 143], [0, 255, 235], [133, 255, 0], + [255, 0, 235], [245, 0, 255], [255, 0, 122], [255, 245, 0], + [10, 190, 212], [214, 255, 0], [0, 204, 255], [20, 0, 255], + [255, 255, 0], [0, 153, 255], [0, 41, 255], [0, 255, 204], + [41, 0, 255], [41, 255, 0], [173, 0, 255], [0, 245, 255], + [71, 0, 255], [122, 0, 255], [0, 255, 184], [0, 92, 255], + [184, 255, 0], [0, 133, 255], [255, 214, 0], [25, 194, 194], + [102, 255, 0], [92, 0, 255]] + + +def blend(images): + if images is None or len(images) == 0: + return images + y = np.zeros(images[0].shape, dtype=np.float32) + for img in images: + y = cv2.add(y, img.astype(np.float32)) + y = y.clip(0, 255).astype(np.uint8) + return y + + +def decode_fourcc(cc): + cc_bytes = int(cc).to_bytes(4, byteorder=sys.byteorder) # convert code to a bytearray + cc_str = cc_bytes.decode() # decode byteaarray to a string + return cc_str diff --git a/modules/ui.py b/modules/ui.py index 0ad53a98e..ac328469f 100644 --- a/modules/ui.py +++ b/modules/ui.py @@ -10,7 +10,7 @@ from PIL import Image from modules.call_queue import wrap_gradio_gpu_call, wrap_queued_call, wrap_gradio_call -from modules import sd_hijack, sd_models, script_callbacks, ui_extensions, deepbooru, extra_networks, ui_common, ui_postprocessing, ui_loadsave, ui_train, ui_models, ui_interrogate, modelloader +from modules import sd_hijack, sd_models, script_callbacks, ui_extensions, deepbooru, extra_networks, ui_common, ui_postprocessing, ui_loadsave, ui_train, ui_models, ui_control, ui_interrogate, modelloader from modules.ui_components import FormRow, FormGroup, ToolButton, FormHTML from modules.paths import script_path, data_path from modules.shared import opts, cmd_opts @@ -906,6 +906,10 @@ def select_img2img_tab(tab): modules.scripts.scripts_current = None + with gr.Blocks(analytics_enabled=False) as control_interface: + ui_control.create_ui() + timer.startup.record("ui-control") + with gr.Blocks(analytics_enabled=False) as extras_interface: ui_postprocessing.create_ui() timer.startup.record("ui-extras") @@ -1125,6 +1129,7 @@ def reload_sd_weights(): interfaces = [ (txt2img_interface, "Text", "txt2img"), (img2img_interface, "Image", "img2img"), + (control_interface, "Control", "control"), (extras_interface, "Process", "process"), (train_interface, "Train", "train"), (models_interface, "Models", "models"), diff --git a/modules/ui_control.py b/modules/ui_control.py new file mode 100644 index 000000000..65fe65840 --- /dev/null +++ b/modules/ui_control.py @@ -0,0 +1,479 @@ +import os +import gradio as gr +from modules.control import unit +from modules.control import controlnets # lllyasviel ControlNet +from modules.control import controlnetsxs # vislearn ControlNet-XS +from modules.control import adapters # TencentARC T2I-Adapter +from modules.control import processors # patrickvonplaten controlnet_aux +from modules.control import reference # reference pipeline +from modules import errors, shared, progress, sd_samplers, ui, ui_components, ui_symbols, ui_common, generation_parameters_copypaste, call_queue +from modules.ui_components import FormRow, FormGroup + + +gr_height = 512 +max_units = 10 +units: list[unit.Unit] = [] # main state variable +input_source = None +debug = os.environ.get('SD_CONTROL_DEBUG', None) is not None + + +def initialize(): + from modules import devices + shared.log.debug(f'Control initialize: models={shared.opts.control_dir} debug={debug}') + controlnets.cache_dir = os.path.join(shared.opts.control_dir, 'controlnets') + controlnetsxs.cache_dir = os.path.join(shared.opts.control_dir, 'controlnetsxs') + adapters.cache_dir = os.path.join(shared.opts.control_dir, 'adapters') + processors.cache_dir = os.path.join(shared.opts.control_dir, 'processors') + unit.default_device = devices.device + unit.default_dtype = devices.dtype + os.makedirs(shared.opts.control_dir, exist_ok=True) + os.makedirs(controlnets.cache_dir, exist_ok=True) + os.makedirs(controlnetsxs.cache_dir, exist_ok=True) + os.makedirs(adapters.cache_dir, exist_ok=True) + os.makedirs(processors.cache_dir, exist_ok=True) + + +def return_controls(res): + # return preview, image, video, gallery, text + if debug: + shared.log.debug(f'Control received: type={type(res)} {res}') + if isinstance(res, str): # error response + return [None, None, None, None, res] + elif isinstance(res, tuple): # standard response received as tuple via control_run->yield(output_images, process_image, result_txt) + preview_image = res[1] # may be None + output_image = res[0][0] if isinstance(res[0], list) else res[0] # may be image or list of images + if isinstance(res[0], list): + output_gallery = res[0] if res[0][0] is not None else [] + else: + output_gallery = [res[0]] if res[0] is not None else [] # must return list, but can receive single image + result_txt = res[2] if len(res) > 2 else '' # do we have a message + output_video = res[3] if len(res) > 3 else None # do we have a video filename + return [preview_image, output_image, output_video, output_gallery, result_txt] + else: # unexpected + return [None, None, None, None, f'Control: Unexpected response: {type(res)}'] + + +def generate_click(job_id: str, active_tab: str, *args): + from modules.control.run import control_run + shared.log.debug(f'Control: tab={active_tab} job={job_id} args={args}') + if active_tab not in ['controlnet', 'xs', 'adapter', 'reference']: + return None, None, None, None, f'Control: Unknown mode: {active_tab} args={args}' + shared.state.begin('control') + progress.add_task_to_queue(job_id) + with call_queue.queue_lock: + yield [None, None, None, None, 'Control: starting'] + shared.mem_mon.reset() + progress.start_task(job_id) + try: + for results in control_run(units, input_source, active_tab, True, *args): + progress.record_results(job_id, results) + yield return_controls(results) + except Exception as e: + shared.log.error(f"Control exception: {e}") + errors.display(e, 'Control') + return None, None, None, None, f'Control: Exception: {e}' + progress.finish_task(job_id) + shared.state.end() + + +def display_units(num_units): + return (num_units * [gr.update(visible=True)]) + ((max_units - num_units) * [gr.update(visible=False)]) + + +def get_video(filepath: str): + try: + import cv2 + from modules.control.util import decode_fourcc + video = cv2.VideoCapture(filepath) + if not video.isOpened(): + msg = f'Control: video open failed: path="{filepath}"' + shared.log.error(msg) + return msg + frames = int(video.get(cv2.CAP_PROP_FRAME_COUNT)) + fps = video.get(cv2.CAP_PROP_FPS) + duration = float(frames) / fps + w, h = int(video.get(cv2.CAP_PROP_FRAME_WIDTH)), int(video.get(cv2.CAP_PROP_FRAME_HEIGHT)) + codec = decode_fourcc(video.get(cv2.CAP_PROP_FOURCC)) + video.release() + shared.log.debug(f'Control: input video: path={filepath} frames={frames} fps={fps} size={w}x{h} codec={codec}') + msg = f'Control input | Video | Size {w}x{h} | Frames {frames} | FPS {fps:.2f} | Duration {duration:.2f} | Codec {codec}' + return msg + except Exception as e: + msg = f'Control: video open failed: path={filepath} {e}' + shared.log.error(msg) + return msg + + +def select_input(selected_input): + global input_source # pylint: disable=global-statement + input_type = type(selected_input) + status = 'Control input | Unknown' + if hasattr(selected_input, 'size'): # image via upload -> image + input_source = [selected_input] + input_type = 'PIL.Image' + shared.log.debug(f'Control input: type={input_type} input={input_source}') + status = f'Control input | Image | Size {selected_input.width}x{selected_input.height} | Mode {selected_input.mode}' + return [gr.Tabs.update(selected='out-gallery'), status] + elif isinstance(selected_input, gr.components.image.Image): # not likely + input_source = [selected_input.value] + input_type = 'gr.Image' + shared.log.debug(f'Control input: type={input_type} input={input_source}') + return [gr.Tabs.update(selected='out-gallery'), status] + elif isinstance(selected_input, str): # video via upload > tmp filepath to video + input_source = selected_input + input_type = 'gr.Video' + shared.log.debug(f'Control input: type={input_type} input={input_source}') + status = get_video(input_source) + return [gr.Tabs.update(selected='out-video'), status] + elif isinstance(selected_input, list): # batch or folder via upload -> list of tmp filepaths + if hasattr(selected_input[0], 'name'): + input_type = 'tempfiles' + input_source = [f.name for f in selected_input] # tempfile + else: + input_type = 'files' + input_source = selected_input + status = f'Control input | Images | Files {len(input_source)}' + shared.log.debug(f'Control input: type={input_type} input={input_source}') + return [gr.Tabs.update(selected='out-gallery'), status] + else: # unknown + input_source = None + return [gr.Tabs.update(selected='out-gallery'), status] + + +def video_type_change(video_type): + return [ + gr.update(visible=video_type != 'None'), + gr.update(visible=video_type == 'GIF' or video_type == 'PNG'), + gr.update(visible=video_type == 'MP4'), + gr.update(visible=video_type == 'MP4'), + ] + + +def create_ui(_blocks: gr.Blocks=None): + initialize() + with gr.Blocks(analytics_enabled = False) as control_ui: + prompt, styles, negative, btn_generate, _btn_interrogate, _btn_deepbooru, btn_paste, btn_extra, prompt_counter, btn_prompt_counter, negative_counter, btn_negative_counter = ui.create_toprow(is_img2img=False, id_part='control') + with FormGroup(elem_id="control_interface", equal_height=False): + with gr.Row(elem_id='control_settings'): + resize_mode, resize_name, width, height, scale_by, selected_scale_tab, resize_time = ui.create_resize_inputs('control', [], time_selector=True, scale_visible=False) + + with gr.Accordion(open=False, label="Sampler", elem_id="control_sampler", elem_classes=["small-accordion"]): + sd_samplers.set_samplers() + steps, sampler_index = ui.create_sampler_and_steps_selection(sd_samplers.samplers, "control") + + batch_count, batch_size = ui.create_batch_inputs('control') + seed, _reuse_seed, subseed, _reuse_subseed, subseed_strength, seed_resize_from_h, seed_resize_from_w = ui.create_seed_inputs('control', reuse_visible=False) + cfg_scale, clip_skip, image_cfg_scale, diffusers_guidance_rescale, full_quality, restore_faces, tiling, hdr_clamp, hdr_boundary, hdr_threshold, hdr_center, hdr_channel_shift, hdr_full_shift, hdr_maximize, hdr_max_center, hdr_max_boundry = ui.create_advanced_inputs('control') + + with gr.Accordion(open=False, label="Denoise", elem_id="control_denoise", elem_classes=["small-accordion"]): + denoising_strength = gr.Slider(minimum=0.0, maximum=0.99, step=0.01, label='Denoising strength', value=0.50, elem_id="control_denoising_strength") + + with gr.Accordion(open=False, label="Video", elem_id="control_video", elem_classes=["small-accordion"]): + with gr.Row(): + video_skip_frames = gr.Slider(minimum=0, maximum=100, step=1, label='Skip input frames', value=0, elem_id="control_video_skip_frames") + with gr.Row(): + video_type = gr.Dropdown(label='Video file', choices=['None', 'GIF', 'PNG', 'MP4'], value='None') + video_duration = gr.Slider(label='Duration', minimum=0.25, maximum=10, step=0.25, value=2, visible=False) + with gr.Row(): + video_loop = gr.Checkbox(label='Loop', value=True, visible=False) + video_pad = gr.Slider(label='Pad frames', minimum=0, maximum=24, step=1, value=1, visible=False) + video_interpolate = gr.Slider(label='Interpolate frames', minimum=0, maximum=24, step=1, value=0, visible=False) + video_type.change(fn=video_type_change, inputs=[video_type], outputs=[video_duration, video_loop, video_pad, video_interpolate]) + + override_settings = ui.create_override_inputs('control') + + with FormRow(variant='compact', elem_id="control_extra_networks", visible=False) as extra_networks_ui: + from modules import timer, ui_extra_networks + extra_networks_ui = ui_extra_networks.create_ui(extra_networks_ui, btn_extra, 'control', skip_indexing=shared.opts.extra_network_skip_indexing) + timer.startup.record('ui-extra-networks') + + with gr.Row(elem_id='control_status'): + result_txt = gr.HTML(elem_classes=['control-result'], elem_id='control-result') + + with gr.Row(elem_id='control-inputs'): + with gr.Column(scale=9, elem_id='control-input-column'): + gr.HTML('Input

') + with gr.Tabs(elem_classes=['control-tabs'], elem_id='control-tab-input'): + with gr.Tab('Image', id='in-image') as tab_image: + input_image = gr.Image(label="Input", show_label=False, type="pil", source="upload", interactive=True, tool="editor", height=gr_height) + with gr.Tab('Video', id='in-video') as tab_video: + input_video = gr.Video(label="Input", show_label=False, interactive=True, height=gr_height) + with gr.Tab('Batch', id='in-batch') as tab_batch: + input_batch = gr.File(label="Input", show_label=False, file_count='multiple', file_types=['image'], type='file', interactive=True, height=gr_height) + with gr.Tab('Folder', id='in-folder') as tab_folder: + input_folder = gr.File(label="Input", show_label=False, file_count='directory', file_types=['image'], type='file', interactive=True, height=gr_height) + with gr.Column(scale=9, elem_id='control-output-column'): + gr.HTML('Output

') + with gr.Tabs(elem_classes=['control-tabs'], elem_id='control-tab-output') as output_tabs: + with gr.Tab('Gallery', id='out-gallery'): + output_gallery, _output_gen_info, _output_html_info, _output_html_info_formatted, _output_html_log = ui_common.create_output_panel("control", preview=True) + with gr.Tab('Image', id='out-image'): + output_image = gr.Image(label="Input", show_label=False, type="pil", interactive=False, tool="editor", height=gr_height) + with gr.Tab('Video', id='out-video'): + output_video = gr.Video(label="Input", show_label=False, height=gr_height) + with gr.Column(scale=9, elem_id='control-preview-column'): + gr.HTML('Preview

') + with gr.Tabs(elem_classes=['control-tabs'], elem_id='control-tab-preview'): + with gr.Tab('Preview', id='preview-image') as tab_image: + preview_process = gr.Image(label="Input", show_label=False, type="pil", source="upload", interactive=False, height=gr_height, visible=True) + + input_image.change(fn=select_input, inputs=[input_image], outputs=[output_tabs, result_txt]) + input_video.change(fn=select_input, inputs=[input_video], outputs=[output_tabs, result_txt]) + input_batch.change(fn=select_input, inputs=[input_batch], outputs=[output_tabs, result_txt]) + input_folder.change(fn=select_input, inputs=[input_folder], outputs=[output_tabs, result_txt]) + tab_image.select(fn=select_input, inputs=[input_image], outputs=[output_tabs, result_txt]) + tab_video.select(fn=select_input, inputs=[input_video], outputs=[output_tabs, result_txt]) + tab_batch.select(fn=select_input, inputs=[input_batch], outputs=[output_tabs, result_txt]) + tab_folder.select(fn=select_input, inputs=[input_folder], outputs=[output_tabs, result_txt]) + + with gr.Tabs(elem_id='control-tabs') as _tabs_control_type: + + with gr.Tab('ControlNet') as _tab_controlnet: + gr.HTML('ControlNet') + with gr.Row(): + extra_controls = [ + gr.Checkbox(label="Guess mode", value=False, scale=3), + ] + num_controlnet_units = gr.Slider(label="Units", minimum=1, maximum=max_units, step=1, value=1, scale=1) + controlnet_ui_units = [] # list of hidable accordions + for i in range(max_units): + with gr.Accordion(f'Control unit {i+1}', visible= i < num_controlnet_units.value) as unit_ui: + with gr.Row(): + with gr.Column(): + with gr.Row(): + enabled_cb = gr.Checkbox(value= i==0, label="") + process_id = gr.Dropdown(label="Processor", choices=processors.list_models(), value='None') + model_id = gr.Dropdown(label="ControlNet", choices=controlnets.list_models(), value='None') + ui_common.create_refresh_button(model_id, controlnets.list_models, lambda: {"choices": controlnets.list_models(refresh=True)}, 'refresh_control_models') + model_strength = gr.Slider(label="Strength", minimum=0.0, maximum=1.0, step=0.1, value=1.0-i/10) + control_start = gr.Slider(label="Start", minimum=0.0, maximum=1.0, step=0.1, value=0) + control_end = gr.Slider(label="End", minimum=0.0, maximum=1.0, step=0.1, value=1.0) + reset_btn = ui_components.ToolButton(value=ui_symbols.reset) + image_upload = gr.UploadButton(label=ui_symbols.upload, file_types=['image'], elem_classes=['form', 'gradio-button', 'tool']) + process_btn= ui_components.ToolButton(value=ui_symbols.preview) + controlnet_ui_units.append(unit_ui) + units.append(unit.Unit( + unit_type = 'controlnet', + image_input = input_image, + enabled_cb = enabled_cb, + reset_btn = reset_btn, + process_id = process_id, + model_id = model_id, + model_strength = model_strength, + preview_process = preview_process, + preview_btn = process_btn, + image_upload = image_upload, + control_start = control_start, + control_end = control_end, + extra_controls = extra_controls, + ) + ) + if i == 0: + units[-1].enabled = True # enable first unit in group + num_controlnet_units.change(fn=display_units, inputs=[num_controlnet_units], outputs=controlnet_ui_units) + + with gr.Tab('XS') as _tab_controlnetxs: + gr.HTML('ControlNet XS') + with gr.Row(): + extra_controls = [ + gr.Slider(label="Time embedding mix", minimum=0.0, maximum=1.0, step=0.05, value=0.0, scale=3) + ] + num_controlnet_units = gr.Slider(label="Units", minimum=1, maximum=max_units, step=1, value=1, scale=1) + controlnetxs_ui_units = [] # list of hidable accordions + for i in range(max_units): + with gr.Accordion(f'Control unit {i+1}', visible= i < num_controlnet_units.value) as unit_ui: + with gr.Row(): + with gr.Column(): + with gr.Row(): + enabled_cb = gr.Checkbox(value= i==0, label="") + process_id = gr.Dropdown(label="Processor", choices=processors.list_models(), value='None') + model_id = gr.Dropdown(label="ControlNet-XS", choices=controlnetsxs.list_models(), value='None') + ui_common.create_refresh_button(model_id, controlnetsxs.list_models, lambda: {"choices": controlnetsxs.list_models(refresh=True)}, 'refresh_control_models') + model_strength = gr.Slider(label="Strength", minimum=0.0, maximum=1.0, step=0.1, value=1.0-i/10) + control_start = gr.Slider(label="Start", minimum=0.0, maximum=1.0, step=0.1, value=0) + control_end = gr.Slider(label="End", minimum=0.0, maximum=1.0, step=0.1, value=1.0) + reset_btn = ui_components.ToolButton(value=ui_symbols.reset) + image_upload = gr.UploadButton(label=ui_symbols.upload, file_types=['image'], elem_classes=['form', 'gradio-button', 'tool']) + process_btn= ui_components.ToolButton(value=ui_symbols.preview) + controlnetxs_ui_units.append(unit_ui) + units.append(unit.Unit( + unit_type = 'xs', + image_input = input_image, + enabled_cb = enabled_cb, + reset_btn = reset_btn, + process_id = process_id, + model_id = model_id, + model_strength = model_strength, + preview_process = preview_process, + preview_btn = process_btn, + image_upload = image_upload, + control_start = control_start, + control_end = control_end, + extra_controls = extra_controls, + ) + ) + if i == 0: + units[-1].enabled = True # enable first unit in group + num_controlnet_units.change(fn=display_units, inputs=[num_controlnet_units], outputs=controlnetxs_ui_units) + + with gr.Tab('Adapter') as _tab_adapter: + gr.HTML('T2I-Adapter') + with gr.Row(): + extra_controls = [ + gr.Slider(label="Control factor", minimum=0.0, maximum=1.0, step=0.1, value=1.0, scale=3), + ] + num_adaptor_units = gr.Slider(label="Units", minimum=1, maximum=max_units, step=1, value=1, scale=1) + adaptor_ui_units = [] # list of hidable accordions + for i in range(max_units): + with gr.Accordion(f'Adapter unit {i+1}', visible= i < num_adaptor_units.value) as unit_ui: + with gr.Row(): + with gr.Column(): + with gr.Row(): + enabled_cb = gr.Checkbox(value= i == 0, label="Enabled") + process_id = gr.Dropdown(label="Processor", choices=processors.list_models(), value='None') + model_id = gr.Dropdown(label="Adapter", choices=adapters.list_models(), value='None') + ui_common.create_refresh_button(model_id, adapters.list_models, lambda: {"choices": adapters.list_models(refresh=True)}, 'refresh_adapter_models') + model_strength = gr.Slider(label="Strength", minimum=0.0, maximum=1.0, step=0.1, value=1.0-i/10) + reset_btn = ui_components.ToolButton(value=ui_symbols.reset) + image_upload = gr.UploadButton(label=ui_symbols.upload, file_types=['image'], elem_classes=['form', 'gradio-button', 'tool']) + process_btn= ui_components.ToolButton(value=ui_symbols.preview) + adaptor_ui_units.append(unit_ui) + units.append(unit.Unit( + unit_type = 'adapter', + image_input = input_image, + enabled_cb = enabled_cb, + reset_btn = reset_btn, + process_id = process_id, + model_id = model_id, + model_strength = model_strength, + preview_process = preview_process, + preview_btn = process_btn, + image_upload = image_upload, + extra_controls = extra_controls, + ) + ) + if i == 0: + units[-1].enabled = True # enable first unit in group + num_adaptor_units.change(fn=display_units, inputs=[num_adaptor_units], outputs=adaptor_ui_units) + + with gr.Tab('Reference') as _tab_reference: + gr.HTML('ControlNet reference-only control') + with gr.Row(): + extra_controls = [ + gr.Radio(label="Reference context", choices=['Attention', 'Adain', 'Attention Adain'], value='Attention', interactive=True), + gr.Slider(label="Style fidelity", minimum=0.0, maximum=1.0, step=0.1, value=0.5, interactive=True), # prompt vs control importance + gr.Slider(label="Reference query weight", minimum=0.0, maximum=1.0, step=0.1, value=1.0, interactive=True), + gr.Slider(label="Reference adain weight", minimum=0.0, maximum=2.0, step=0.1, value=1.0, interactive=True), + ] + for i in range(1): # can only have one reference unit + with gr.Accordion(f'Reference unit {i+1}', visible=True) as unit_ui: + with gr.Row(): + with gr.Column(): + with gr.Row(): + enabled_cb = gr.Checkbox(value= i == 0, label="Enabled", visible=False) + model_id = gr.Dropdown(label="Reference", choices=reference.list_models(), value='Reference', visible=False) + model_strength = gr.Slider(label="Strength", minimum=0.0, maximum=1.0, step=0.1, value=1.0, visible=False) + reset_btn = ui_components.ToolButton(value=ui_symbols.reset) + image_upload = gr.UploadButton(label=ui_symbols.upload, file_types=['image'], elem_classes=['form', 'gradio-button', 'tool']) + process_btn= ui_components.ToolButton(value=ui_symbols.preview) + units.append(unit.Unit( + unit_type = 'reference', + image_input = input_image, + enabled_cb = enabled_cb, + reset_btn = reset_btn, + process_id = process_id, + model_id = model_id, + model_strength = model_strength, + preview_process = preview_process, + preview_btn = process_btn, + image_upload = image_upload, + extra_controls = extra_controls, + ) + ) + if i == 0: + units[-1].enabled = True # enable first unit in group + + with gr.Tab('Processor settings') as _tab_settings: + with gr.Group(elem_classes=['processor-group']): + settings = [] + with gr.Accordion('HED', open=True, elem_classes=['processor-settings']): + settings.append(gr.Checkbox(label="Scribble", value=False)) + with gr.Accordion('Midas depth', open=True, elem_classes=['processor-settings']): + settings.append(gr.Slider(label="Background threshold", minimum=0.0, maximum=1.0, step=0.01, value=0.1)) + settings.append(gr.Checkbox(label="Depth and normal", value=False)) + with gr.Accordion('MLSD', open=True, elem_classes=['processor-settings']): + settings.append(gr.Slider(label="Score threshold", minimum=0.0, maximum=1.0, step=0.01, value=0.1)) + settings.append(gr.Slider(label="Distance threshold", minimum=0.0, maximum=1.0, step=0.01, value=0.1)) + with gr.Accordion('OpenBody', open=True, elem_classes=['processor-settings']): + settings.append(gr.Checkbox(label="Body", value=True)) + settings.append(gr.Checkbox(label="Hands", value=False)) + settings.append(gr.Checkbox(label="Face", value=False)) + with gr.Accordion('PidiNet', open=True, elem_classes=['processor-settings']): + settings.append(gr.Checkbox(label="Scribble", value=False)) + settings.append(gr.Checkbox(label="Apply filter", value=False)) + with gr.Accordion('LineArt', open=True, elem_classes=['processor-settings']): + settings.append(gr.Checkbox(label="Coarse", value=False)) + with gr.Accordion('Leres Depth', open=True, elem_classes=['processor-settings']): + settings.append(gr.Checkbox(label="Boost", value=False)) + settings.append(gr.Slider(label="Near threshold", minimum=0.0, maximum=1.0, step=0.01, value=0.0)) + settings.append(gr.Slider(label="Background threshold", minimum=0.0, maximum=1.0, step=0.01, value=0.0)) + with gr.Accordion('MediaPipe Face', open=True, elem_classes=['processor-settings']): + settings.append(gr.Slider(label="Max faces", minimum=1, maximum=10, step=1, value=1)) + settings.append(gr.Slider(label="Min confidence", minimum=0.0, maximum=1.0, step=0.01, value=0.5)) + with gr.Accordion('Canny', open=True, elem_classes=['processor-settings']): + settings.append(gr.Slider(label="Low threshold", minimum=0, maximum=1000, step=1, value=100)) + settings.append(gr.Slider(label="High threshold", minimum=0, maximum=1000, step=1, value=200)) + with gr.Accordion('DWPose', open=True, elem_classes=['processor-settings']): + settings.append(gr.Radio(label="Model", choices=['Tiny', 'Medium', 'Large'], value='Tiny')) + settings.append(gr.Slider(label="Min confidence", minimum=0.0, maximum=1.0, step=0.01, value=0.3)) + with gr.Accordion('SegmentAnything', open=True, elem_classes=['processor-settings']): + settings.append(gr.Radio(label="Model", choices=['Base', 'Large'], value='Base')) + with gr.Accordion('Edge', open=True, elem_classes=['processor-settings']): + settings.append(gr.Checkbox(label="Parameter free", value=True)) + settings.append(gr.Radio(label="Mode", choices=['edge', 'gradient'], value='edge')) + for setting in settings: + setting.change(fn=processors.update_settings, inputs=settings, outputs=[]) + + tabs_state = gr.Text(value='none', visible=False) + input_fields = [ + prompt, negative, styles, + steps, sampler_index, + seed, subseed, subseed_strength, seed_resize_from_h, seed_resize_from_w, + cfg_scale, clip_skip, image_cfg_scale, diffusers_guidance_rescale, full_quality, restore_faces, tiling, hdr_clamp, hdr_boundary, hdr_threshold, hdr_center, hdr_channel_shift, hdr_full_shift, hdr_maximize, hdr_max_center, hdr_max_boundry, + resize_mode, resize_name, width, height, scale_by, selected_scale_tab, resize_time, + denoising_strength, batch_count, batch_size, + video_skip_frames, video_type, video_duration, video_loop, video_pad, video_interpolate, + ] + output_fields = [ + preview_process, + output_image, + output_video, + output_gallery, + result_txt, + ] + paste_fields = [] # TODO paste fields + control_dict = dict( + fn=generate_click, + _js="submit_control", + inputs=[tabs_state, tabs_state] + input_fields, + outputs=output_fields, + show_progress=False, + ) + prompt.submit(**control_dict) + btn_generate.click(**control_dict) + + btn_prompt_counter.click(fn=call_queue.wrap_queued_call(ui.update_token_counter), inputs=[prompt, steps], outputs=[prompt_counter]) + btn_negative_counter.click(fn=call_queue.wrap_queued_call(ui.update_token_counter), inputs=[negative, steps], outputs=[negative_counter]) + + generation_parameters_copypaste.add_paste_fields("control", input_image, paste_fields, override_settings) + bindings = generation_parameters_copypaste.ParamBinding(paste_button=btn_paste, tabname="control", source_text_component=prompt, source_image_component=output_gallery) + generation_parameters_copypaste.register_paste_params_button(bindings) + + if debug: + from modules.control.test import test_processors + gr.HTML('

Debug


') + with gr.Row(): + run_test_processors_btn = gr.Button(value="Test all processors", variant='primary', elem_classes=['control-button']) + run_test_processors_btn.click(fn=test_processors, inputs=[input_image], outputs=[preview_process, output_image, output_video, output_gallery]) + + return [(control_ui, 'Control', 'control')] diff --git a/pyproject.toml b/pyproject.toml index 931e8ba5e..afb5b4fd0 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -39,6 +39,7 @@ exclude = [ "repositories/taming", "repositories/blip", "repositories/codeformer", + "modules/control/proc/normalbae/nets/submodules/efficientnet_repo/geffnet", ] ignore = [ "A003", # Class attirbute shadowing builtin @@ -47,17 +48,19 @@ ignore = [ "E731", # Do not assign a `lambda` expression, use a `def` "I001", # Import block is un-sorted or un-formatted "W605", # Invalid escape sequence, messes with some docstrings + "B028", # No explicit stacklevel "B905", # Without explicit scrict "C408", # Rewrite as a literal "E402", # Module level import not at top of file "E721", # Do not compare types, use `isinstance()` - "F401", # Imported but unused "EXE001", # Shebang present + "F401", # Imported but unused "ISC003", # Implicit string concatenation "RUF005", # Consider concatenation "RUF012", # Mutable class attributes "RUF013", # Implict optional "RUF015", # Prefer `next` + "TID252", # Relative imports from parent modules ] [tool.ruff.flake8-bugbear] From d336c1fe0dcd425d99962ca87c6de8117f6c40b6 Mon Sep 17 00:00:00 2001 From: Vladimir Mandic Date: Wed, 20 Dec 2023 16:17:56 -0500 Subject: [PATCH 093/143] control detect diffusers capabilities --- CHANGELOG.md | 15 +- javascript/emerald-paradise.css | 297 +++++++++++++++++++++++++++++++ javascript/orchid-dreams.css | 297 +++++++++++++++++++++++++++++++ javascript/timeless-beige.css | 297 +++++++++++++++++++++++++++++++ modules/control/controlnetsxs.py | 17 +- modules/ui_control.py | 5 + wiki | 2 +- 7 files changed, 918 insertions(+), 12 deletions(-) create mode 100644 javascript/emerald-paradise.css create mode 100644 javascript/orchid-dreams.css create mode 100644 javascript/timeless-beige.css diff --git a/CHANGELOG.md b/CHANGELOG.md index 31bfd82ea..efaaa1de4 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,9 +6,9 @@ - **Control** - native implementation of **ControlNet**, **ControlNet XS** and **T2I Adapters** - - top-level control next to **text** and **image** generate + - top-level **Control** next to **Text** and **Image** generate - supports all variations of **SD15** and **SD-XL** models - - supports *text*, *image*, *batch* and *video* processing + - supports *Text*, *Image*, *Batch* and *Video* processing - for details see Wiki documentation: - **Diffusers** @@ -44,6 +44,12 @@ - **Schedulers** - add timesteps range, changing it will make scheduler to be over-complete or under-complete - add rescale betas with zero SNR option (applicable to Euler, Euler a and DDIM, allows for higher dynamic range) + - **UI** + - 3 new native UI themes: **orchid-dreams**, **emerald-paradise** and **timeless-beige**, thanks @illu_Zn + - more dynamic controls depending on the backend (original or diffusers) + controls that are not applicable in current mode are now hidden + - allow setting of resize method directly in image tab + (previously via settings -> upscaler_for_img2img) - **General** - support for **Torch 2.1.2** - **Process** create videos from batch or folder processing @@ -71,11 +77,6 @@ - **OpenVINO**, thanks @disty0 - add *Directory for OpenVINO cache* option to *System Paths* - remove Intel ARC specific 1024x1024 workaround - - **UI** - - more dynamic controls depending on the backend (original or diffusers) - controls that are not applicable in current mode are now hidden - - allow setting of resize method directly in image tab - (previously via settings -> upscaler_for_img2img) - **HDR controls** - batch-aware for enhancement of multiple images or video frames - available in image tab diff --git a/javascript/emerald-paradise.css b/javascript/emerald-paradise.css new file mode 100644 index 000000000..9694a153d --- /dev/null +++ b/javascript/emerald-paradise.css @@ -0,0 +1,297 @@ +/* generic html tags */ +:root, .light, .dark { + --font: 'system-ui', 'ui-sans-serif', 'system-ui', "Roboto", sans-serif; + --font-mono: 'ui-monospace', 'Consolas', monospace; + --font-size: 16px; + --primary-100: #1e2223; /* bg color*/ + --primary-200: #242a2c; /* drop down menu/ prompt window fill*/ + --primary-300: #0a0c0e; /* black */ + --primary-400: #2a302c; /* small buttons*/ + --primary-500: #4b695d; /* main accent color green*/ + --primary-700: #273538; /* extension box fill*/ + --primary-800: #d15e84; /* pink(hover accent)*/ + --highlight-color: var(--primary-500); + --inactive-color: var(--primary--800); + --body-text-color: var(--neutral-100); + --body-text-color-subdued: var(--neutral-300); + --background-color: var(--primary-100); + --background-fill-primary: var(--input-background-fill); + --input-padding: 8px; + --input-background-fill: var(--primary-200); + --input-shadow: none; + --button-secondary-text-color: white; + --button-secondary-background-fill: var(--primary-400); + --button-secondary-background-fill-hover: var(--primary-700); + --block-title-text-color: var(--neutral-300); + --radius-sm: 1px; + --radius-lg: 6px; + --spacing-md: 4px; + --spacing-xxl: 8px; + --line-sm: 1.2em; + --line-md: 1.4em; +} + +html { font-size: var(--font-size); } +body, button, input, select, textarea { font-family: var(--font);} +button { font-size: 1.2rem; max-width: 400px; } +img { background-color: var(--background-color); } +input[type=range] { height: var(--line-sm); appearance: none; margin-top: 0; min-width: 160px; background-color: var(--background-color); width: 100%; background: transparent; } +input[type=range]::-webkit-slider-runnable-track, input[type=range]::-moz-range-track { width: 100%; height: 6px; cursor: pointer; background: var(--primary-400); border-radius: var(--radius-lg); border: 0px solid #222222; } +input[type=range]::-webkit-slider-thumb, input[type=range]::-moz-range-thumb { border: 0px solid #000000; height: var(--line-sm); width: 8px; border-radius: var(--radius-lg); background: white; cursor: pointer; appearance: none; margin-top: 0px; } +input[type=range]::-moz-range-progress { background-color: var(--primary-500); height: 6px; border-radius: var(--radius-lg); } +::-webkit-scrollbar-track { background: #333333; } +::-webkit-scrollbar-thumb { background-color: var(--highlight-color); border-radius: var(--radius-lg); border-width: 0; box-shadow: 2px 2px 3px #111111; } +div.form { border-width: 0; box-shadow: none; background: transparent; overflow: visible; margin-bottom: 6px; } +div.compact { gap: 1em; } + +/* gradio style classes */ +fieldset .gr-block.gr-box, label.block span { padding: 0; margin-top: -4px; } +.border-2 { border-width: 0; } +.border-b-2 { border-bottom-width: 2px; border-color: var(--highlight-color) !important; padding-bottom: 2px; margin-bottom: 8px; } +.bg-white { color: lightyellow; background-color: var(--inactive-color); } +.gr-box { border-radius: var(--radius-sm) !important; background-color: #111111 !important; box-shadow: 2px 2px 3px #111111; border-width: 0; padding: 4px; margin: 12px 0px 12px 0px } +.gr-button { font-weight: normal; box-shadow: 2px 2px 3px #111111; font-size: 0.8rem; min-width: 32px; min-height: 32px; padding: 3px; margin: 3px; } +.gr-check-radio { background-color: var(--inactive-color); border-width: 0; border-radius: var(--radius-lg); box-shadow: 2px 2px 3px #111111; } +.gr-check-radio:checked { background-color: var(--highlight-color); } +.gr-compact { background-color: var(--background-color); } +.gr-form { border-width: 0; } +.gr-input { background-color: #333333 !important; padding: 4px; margin: 4px; } +.gr-input-label { color: lightyellow; border-width: 0; background: transparent; padding: 2px !important; } +.gr-panel { background-color: var(--background-color); } +.eta-bar { display: none !important } +svg.feather.feather-image, .feather .feather-image { display: none } +.gap-2 { padding-top: 8px; } +.gr-box > div > div > input.gr-text-input { right: 0; width: 4em; padding: 0; top: -12px; border: none; max-height: 20px; } +.output-html { line-height: 1.2rem; overflow-x: hidden; } +.output-html > div { margin-bottom: 8px; } +.overflow-hidden .flex .flex-col .relative col .gap-4 { min-width: var(--left-column); max-width: var(--left-column); } /* this is a problematic one */ +.p-2 { padding: 0; } +.px-4 { padding-lefT: 1rem; padding-right: 1rem; } +.py-6 { padding-bottom: 0; } +.tabs { background-color: var(--background-color); } +.block.token-counter span { background-color: var(--input-background-fill) !important; box-shadow: 2px 2px 2px #111; border: none !important; font-size: 0.8rem; } +.tab-nav { zoom: 120%; margin-top: 10px; margin-bottom: 10px; border-bottom: 2px solid var(--highlight-color) !important; padding-bottom: 2px; } +div.tab-nav button.selected {background-color: var(--button-primary-background-fill);} +#settings div.tab-nav button.selected {background-color: var(--background-color); color: var(--primary-800); font-weight: bold;} +.label-wrap { background-color: #191919; /* extension tab color*/ padding: 16px 8px 8px 8px; border-radius: var(--radius-lg); padding-left: 8px !important; } +.small-accordion .label-wrap { padding: 8px 0px 8px 0px; } +.small-accordion .label-wrap .icon { margin-right: 1em; } +.gradio-button.tool { border: none; box-shadow: none; border-radius: var(--radius-lg);} +button.selected {background: var(--button-primary-background-fill);} +.center.boundedheight.flex {background-color: var(--input-background-fill);} +.compact {border-radius: var(--border-radius-lg);} +#logMonitorData {background-color: var(--input-background-fill);} +#tab_extensions table td, #tab_extensions table th, #tab_config table td, #tab_config table th { border: none; padding: 0.5em; background-color: var(--primary-200); } +#tab_extensions table, #tab_config table { width: 96vw; } +#tab_extensions table input[type=checkbox] {appearance: none; border-radius: 0px;} +#tab_extensions button:hover { background-color: var(--button-secondary-background-fill-hover);} + +/* automatic style classes */ +.progressDiv { border-radius: var(--radius-sm) !important; position: fixed; top: 44px; right: 26px; max-width: 262px; height: 48px; z-index: 99; box-shadow: var(--button-shadow); } +.progressDiv .progress { border-radius: var(--radius-lg) !important; background: var(--highlight-color); line-height: 3rem; height: 48px; } +.gallery-item { box-shadow: none !important; } +.performance { color: #888; } +.extra-networks { border-left: 2px solid var(--highlight-color) !important; padding-left: 4px; } +.image-buttons { gap: 10px !important; justify-content: center; } +.image-buttons > button { max-width: 160px; } +.tooltip { background: var(--primary-800); color: white; border: none; border-radius: var(--radius-lg) } +#system_row > button, #settings_row > button, #config_row > button { max-width: 190px; } + +/* gradio elements overrides */ +#div.gradio-container { overflow-x: hidden; } +#img2img_label_copy_to_img2img { font-weight: normal; } +#txt2img_prompt, #txt2img_neg_prompt, #img2img_prompt, #img2img_neg_prompt { background-color: var(--background-color); box-shadow: 4px 4px 4px 0px #333333 !important; } +#txt2img_prompt > label > textarea, #txt2img_neg_prompt > label > textarea, #img2img_prompt > label > textarea, #img2img_neg_prompt > label > textarea { font-size: 1.1rem; } +#img2img_settings { min-width: calc(2 * var(--left-column)); max-width: calc(2 * var(--left-column)); background-color: #111111; padding-top: 16px; } +#interrogate, #deepbooru { margin: 0 0px 10px 0px; max-width: 80px; max-height: 80px; font-weight: normal; font-size: 0.95em; } +#quicksettings .gr-button-tool { font-size: 1.6rem; box-shadow: none; margin-top: -2px; height: 2.4em; } +#quicksettings button {padding: 0 0.5em 0.1em 0.5em;} +#open_folder_extras, #footer, #style_pos_col, #style_neg_col, #roll_col, #extras_upscaler_2, #extras_upscaler_2_visibility, #txt2img_seed_resize_from_w, #txt2img_seed_resize_from_h { display: none; } +#save-animation { border-radius: var(--radius-sm) !important; margin-bottom: 16px; background-color: #111111; } +#script_list { padding: 4px; margin-top: 16px; margin-bottom: 8px; } +#settings > div.flex-wrap { width: 15em; } +#txt2img_cfg_scale { min-width: 200px; } +#txt2img_checkboxes, #img2img_checkboxes { background-color: transparent; } +#txt2img_checkboxes, #img2img_checkboxes { margin-bottom: 0.2em; } +#txt2img_actions_column, #img2img_actions_column { flex-flow: wrap; justify-content: space-between; } +#txt2img_enqueue_wrapper, #img2img_enqueue_wrapper { min-width: unset; width: 48%; } +#txt2img_generate_box, #img2img_generate_box { min-width: unset; width: 48%; } + +#extras_upscale { margin-top: 10px } +#txt2img_progress_row > div { min-width: var(--left-column); max-width: var(--left-column); } +#txt2img_settings { min-width: var(--left-column); max-width: var(--left-column); background-color: #111111; padding-top: 16px; } +#pnginfo_html2_info { margin-top: -18px; background-color: var(--input-background-fill); padding: var(--input-padding) } +#txt2img_tools, #img2img_tools { margin-top: -4px; margin-bottom: -4px; } +#txt2img_styles_row, #img2img_styles_row { margin-top: -6px; z-index: 200; } + +/* based on gradio built-in dark theme */ +:root, .light, .dark { + --body-background-fill: var(--background-color); + --color-accent-soft: var(--neutral-700); + --background-fill-secondary: none; + --border-color-accent: var(--background-color); + --border-color-primary: var(--background-color); + --link-text-color-active: var(--primary-500); + --link-text-color: var(--secondary-500); + --link-text-color-hover: var(--secondary-400); + --link-text-color-visited: var(--secondary-600); + --shadow-spread: 1px; + --block-background-fill: None; + --block-border-color: var(--border-color-primary); + --block_border_width: None; + --block-info-text-color: var(--body-text-color-subdued); + --block-label-background-fill: var(--background-fill-secondary); + --block-label-border-color: var(--border-color-primary); + --block_label_border_width: None; + --block-label-text-color: var(--neutral-200); + --block_shadow: None; + --block_title_background_fill: None; + --block_title_border_color: None; + --block_title_border_width: None; + --panel-background-fill: var(--background-fill-secondary); + --panel-border-color: var(--border-color-primary); + --panel_border_width: None; + --checkbox-background-color: var(--primary-200); + --checkbox-background-color-focus: var(--primary-700); + --checkbox-background-color-hover: var(--primary-700); + --checkbox-background-color-selected: var(--primary-500); + --checkbox-border-color: transparent; + --checkbox-border-color-focus: var(--primary-800); + --checkbox-border-color-hover: var(--primary-800); + --checkbox-border-color-selected: var(--primary-800); + --checkbox-border-width: var(--input-border-width); + --checkbox-label-background-fill: None; + --checkbox-label-background-fill-hover: None; + --checkbox-label-background-fill-selected: var(--checkbox-label-background-fill); + --checkbox-label-border-color: var(--border-color-primary); + --checkbox-label-border-color-hover: var(--checkbox-label-border-color); + --checkbox-label-border-width: var(--input-border-width); + --checkbox-label-text-color: var(--body-text-color); + --checkbox-label-text-color-selected: var(--checkbox-label-text-color); + --error-background-fill: var(--background-fill-primary); + --error-border-color: var(--border-color-primary); + --error-text-color: #f768b7; /*was ef4444*/ + --input-background-fill-focus: var(--secondary-600); + --input-background-fill-hover: var(--input-background-fill); + --input-border-color: var(--background-color); + --input-border-color-focus: var(--primary-800); + --input-placeholder-color: var(--neutral-500); + --input-shadow-focus: None; + --loader_color: None; + --slider_color: None; + --stat-background-fill: linear-gradient(to right, var(--primary-400), var(--primary-800)); + --table-border-color: var(--neutral-700); + --table-even-background-fill: var(--primary-300); + --table-odd-background-fill: var(--primary-200); + --table-row-focus: var(--color-accent-soft); + --button-border-width: var(--input-border-width); + --button-cancel-background-fill: linear-gradient(to bottom right, #dc2626, #b91c1c); + --button-cancel-background-fill-hover: linear-gradient(to bottom right, #dc2626, #dc2626); + --button-cancel-border-color: #dc2626; + --button-cancel-border-color-hover: var(--button-cancel-border-color); + --button-cancel-text-color: white; + --button-cancel-text-color-hover: var(--button-cancel-text-color); + --button-primary-background-fill: var(--primary-500); + --button-primary-background-fill-hover: var(--primary-800); + --button-primary-border-color: var(--primary-500); + --button-primary-border-color-hover: var(--button-primary-border-color); + --button-primary-text-color: white; + --button-primary-text-color-hover: var(--button-primary-text-color); + --button-secondary-border-color: var(--neutral-600); + --button-secondary-border-color-hover: var(--button-secondary-border-color); + --button-secondary-text-color-hover: var(--button-secondary-text-color); + --secondary-50: #eff6ff; + --secondary-100: #dbeafe; + --secondary-200: #bfdbfe; + --secondary-300: #93c5fd; + --secondary-400: #60a5fa; + --secondary-500: #3b82f6; + --secondary-600: #2563eb; + --secondary-700: #1d4ed8; + --secondary-800: #1e40af; + --secondary-900: #1e3a8a; + --secondary-950: #1d3660; + --neutral-50: #f0f0f0; /* */ + --neutral-100: #e8e8e3;/* majority of text (neutral gray yellow) */ + --neutral-200: #d0d0d0; + --neutral-300: #b3b5ac; /* top tab /sub text (light accent) */ + --neutral-400: #ffba85;/* tab title (bright orange) */ + --neutral-500: #48665b; /* prompt text (desat accent)*/ + --neutral-600: #373f39; /* tab outline color (accent color)*/ + --neutral-700: #2b373b; /* small settings tab accent */ + --neutral-800: #f379c2; /* bright pink accent */ + --neutral-900: #111827; + --neutral-950: #0b0f19; + --radius-xxs: 0; + --radius-xs: 0; + --radius-md: 0; + --radius-xl: 0; + --radius-xxl: 0; + --body-text-size: var(--text-md); + --body-text-weight: 400; + --embed-radius: var(--radius-lg); + --color-accent: var(--primary-500); + --shadow-drop: 0; + --shadow-drop-lg: 0 1px 3px 0 rgb(0 0 0 / 0.1), 0 1px 2px -1px rgb(0 0 0 / 0.1); + --shadow-inset: rgba(0,0,0,0.05) 0px 2px 4px 0px inset; + --block-border-width: 1px; + --block-info-text-size: var(--text-sm); + --block-info-text-weight: 400; + --block-label-border-width: 1px; + --block-label-margin: 0; + --block-label-padding: var(--spacing-sm) var(--spacing-lg); + --block-label-radius: calc(var(--radius-lg) - 1px) 0 calc(var(--radius-lg) - 1px) 0; + --block-label-right-radius: 0 calc(var(--radius-lg) - 1px) 0 calc(var(--radius-lg) - 1px); + --block-label-text-size: var(--text-sm); + --block-label-text-weight: 400; + --block-padding: var(--spacing-xl) calc(var(--spacing-xl) + 2px); + --block-radius: var(--radius-lg); + --block-shadow: var(--shadow-drop); + --block-title-background-fill: none; + --block-title-border-color: none; + --block-title-border-width: 0; + --block-title-padding: 0; + --block-title-radius: none; + --block-title-text-size: var(--text-md); + --block-title-text-weight: 400; + --container-radius: var(--radius-lg); + --form-gap-width: 1px; + --layout-gap: var(--spacing-xxl); + --panel-border-width: 0; + --section-header-text-size: var(--text-md); + --section-header-text-weight: 400; + --checkbox-border-radius: var(--radius-sm); + --checkbox-label-gap: 2px; + --checkbox-label-padding: var(--spacing-md); + --checkbox-label-shadow: var(--shadow-drop); + --checkbox-label-text-size: var(--text-md); + --checkbox-label-text-weight: 400; + --checkbox-check: url("data:image/svg+xml,%3csvg viewBox='0 0 16 16' fill='white' xmlns='http://www.w3.org/2000/svg'%3e%3cpath d='M12.207 4.793a1 1 0 010 1.414l-5 5a1 1 0 01-1.414 0l-2-2a1 1 0 011.414-1.414L6.5 9.086l4.293-4.293a1 1 0 011.414 0z'/%3e%3c/svg%3e"); + --radio-circle: url("data:image/svg+xml,%3csvg viewBox='0 0 16 16' fill='white' xmlns='http://www.w3.org/2000/svg'%3e%3ccircle cx='8' cy='8' r='3'/%3e%3c/svg%3e"); + --checkbox-shadow: var(--input-shadow); + --error-border-width: 1px; + --input-border-width: 1px; + --input-radius: var(--radius-lg); + --input-text-size: var(--text-md); + --input-text-weight: 400; + --loader-color: var(--color-accent); + --prose-text-size: var(--text-md); + --prose-text-weight: 400; + --prose-header-text-weight: 600; + --slider-color: ; + --table-radius: var(--radius-lg); + --button-large-padding: 2px 6px; + --button-large-radius: var(--radius-lg); + --button-large-text-size: var(--text-lg); + --button-large-text-weight: 400; + --button-shadow: none; + --button-shadow-active: none; + --button-shadow-hover: none; + --button-small-padding: var(--spacing-sm) calc(2 * var(--spacing-sm)); + --button-small-radius: var(--radius-lg); + --button-small-text-size: var(--text-md); + --button-small-text-weight: 400; + --button-transition: none; + --size-9: 64px; + --size-14: 64px; +} diff --git a/javascript/orchid-dreams.css b/javascript/orchid-dreams.css new file mode 100644 index 000000000..8eab006cb --- /dev/null +++ b/javascript/orchid-dreams.css @@ -0,0 +1,297 @@ +/* generic html tags */ +:root, .light, .dark { + --font: 'system-ui', 'ui-sans-serif', 'system-ui', "Roboto", sans-serif; + --font-mono: 'ui-monospace', 'Consolas', monospace; + --font-size: 16px; + --primary-100: #2a2a34; /* bg color*/ + --primary-200: #1f2028; /* drop down menu/ prompt*/ + --primary-300: #0a0c0e; /* black */ + --primary-400: #40435c; /* small buttons*/ + --primary-500: #4c48b5; /* main accent color purple*/ + --primary-700: #1f2028; /* darker hover accent*/ + --primary-800: #e95ee3; /* pink accent*/ + --highlight-color: var(--primary-500); + --inactive-color: var(--primary--800); + --body-text-color: var(--neutral-100); + --body-text-color-subdued: var(--neutral-300); + --background-color: var(--primary-100); + --background-fill-primary: var(--input-background-fill); + --input-padding: 8px; + --input-background-fill: var(--primary-200); + --input-shadow: none; + --button-secondary-text-color: white; + --button-secondary-background-fill: var(--primary-400); + --button-secondary-background-fill-hover: var(--primary-700); + --block-title-text-color: var(--neutral-300); + --radius-sm: 1px; + --radius-lg: 6px; + --spacing-md: 4px; + --spacing-xxl: 8px; + --line-sm: 1.2em; + --line-md: 1.4em; +} + +html { font-size: var(--font-size); } +body, button, input, select, textarea { font-family: var(--font);} +button { font-size: 1.2rem; max-width: 400px; } +img { background-color: var(--background-color); } +input[type=range] { height: var(--line-sm); appearance: none; margin-top: 0; min-width: 160px; background-color: var(--background-color); width: 100%; background: transparent; } +input[type=range]::-webkit-slider-runnable-track, input[type=range]::-moz-range-track { width: 100%; height: 6px; cursor: pointer; background: var(--primary-400); border-radius: var(--radius-lg); border: 0px solid #222222; } +input[type=range]::-webkit-slider-thumb, input[type=range]::-moz-range-thumb { border: 0px solid #000000; height: var(--line-sm); width: 8px; border-radius: var(--radius-lg); background: white; cursor: pointer; appearance: none; margin-top: 0px; } +input[type=range]::-moz-range-progress { background-color: var(--primary-500); height: 6px; border-radius: var(--radius-lg); } +::-webkit-scrollbar-track { background: #333333; } +::-webkit-scrollbar-thumb { background-color: var(--highlight-color); border-radius: var(--radius-lg); border-width: 0; box-shadow: 2px 2px 3px #111111; } +div.form { border-width: 0; box-shadow: none; background: transparent; overflow: visible; margin-bottom: 6px; } +div.compact { gap: 1em; } + +/* gradio style classes */ +fieldset .gr-block.gr-box, label.block span { padding: 0; margin-top: -4px; } +.border-2 { border-width: 0; } +.border-b-2 { border-bottom-width: 2px; border-color: var(--highlight-color) !important; padding-bottom: 2px; margin-bottom: 8px; } +.bg-white { color: lightyellow; background-color: var(--inactive-color); } +.gr-box { border-radius: var(--radius-sm) !important; background-color: #111111 !important; box-shadow: 2px 2px 3px #111111; border-width: 0; padding: 4px; margin: 12px 0px 12px 0px } +.gr-button { font-weight: normal; box-shadow: 2px 2px 3px #111111; font-size: 0.8rem; min-width: 32px; min-height: 32px; padding: 3px; margin: 3px; } +.gr-check-radio { background-color: var(--inactive-color); border-width: 0; border-radius: var(--radius-lg); box-shadow: 2px 2px 3px #111111; } +.gr-check-radio:checked { background-color: var(--highlight-color); } +.gr-compact { background-color: var(--background-color); } +.gr-form { border-width: 0; } +.gr-input { background-color: #333333 !important; padding: 4px; margin: 4px; } +.gr-input-label { color: lightyellow; border-width: 0; background: transparent; padding: 2px !important; } +.gr-panel { background-color: var(--background-color); } +.eta-bar { display: none !important } +svg.feather.feather-image, .feather .feather-image { display: none } +.gap-2 { padding-top: 8px; } +.gr-box > div > div > input.gr-text-input { right: 0; width: 4em; padding: 0; top: -12px; border: none; max-height: 20px; } +.output-html { line-height: 1.2rem; overflow-x: hidden; } +.output-html > div { margin-bottom: 8px; } +.overflow-hidden .flex .flex-col .relative col .gap-4 { min-width: var(--left-column); max-width: var(--left-column); } /* this is a problematic one */ +.p-2 { padding: 0; } +.px-4 { padding-lefT: 1rem; padding-right: 1rem; } +.py-6 { padding-bottom: 0; } +.tabs { background-color: var(--background-color); } +.block.token-counter span { background-color: var(--input-background-fill) !important; box-shadow: 2px 2px 2px #111; border: none !important; font-size: 0.8rem; } +.tab-nav { zoom: 120%; margin-top: 10px; margin-bottom: 10px; border-bottom: 2px solid var(--highlight-color) !important; padding-bottom: 2px; } +div.tab-nav button.selected {background-color: var(--button-primary-background-fill);} +#settings div.tab-nav button.selected {background-color: var(--background-color); color: var(--primary-800); font-weight: bold;} +.label-wrap { background-color: #18181e; /* extension tab color*/ padding: 16px 8px 8px 8px; border-radius: var(--radius-lg); padding-left: 8px !important; } +.small-accordion .label-wrap { padding: 8px 0px 8px 0px; } +.small-accordion .label-wrap .icon { margin-right: 1em; } +.gradio-button.tool { border: none; box-shadow: none; border-radius: var(--radius-lg);} +button.selected {background: var(--button-primary-background-fill);} +.center.boundedheight.flex {background-color: var(--input-background-fill);} +.compact {border-radius: var(--border-radius-lg);} +#logMonitorData {background-color: var(--input-background-fill);} +#tab_extensions table td, #tab_extensions table th, #tab_config table td, #tab_config table th { border: none; padding: 0.5em; background-color: var(--primary-200); } +#tab_extensions table, #tab_config table { width: 96vw; } +#tab_extensions table input[type=checkbox] {appearance: none; border-radius: 0px;} +#tab_extensions button:hover { background-color: var(--button-secondary-background-fill-hover);} + +/* automatic style classes */ +.progressDiv { border-radius: var(--radius-sm) !important; position: fixed; top: 44px; right: 26px; max-width: 262px; height: 48px; z-index: 99; box-shadow: var(--button-shadow); } +.progressDiv .progress { border-radius: var(--radius-lg) !important; background: var(--highlight-color); line-height: 3rem; height: 48px; } +.gallery-item { box-shadow: none !important; } +.performance { color: #888; } +.extra-networks { border-left: 2px solid var(--highlight-color) !important; padding-left: 4px; } +.image-buttons { gap: 10px !important; justify-content: center; } +.image-buttons > button { max-width: 160px; } +.tooltip { background: var(--primary-800); color: white; border: none; border-radius: var(--radius-lg) } +#system_row > button, #settings_row > button, #config_row > button { max-width: 190px; } + +/* gradio elements overrides */ +#div.gradio-container { overflow-x: hidden; } +#img2img_label_copy_to_img2img { font-weight: normal; } +#txt2img_prompt, #txt2img_neg_prompt, #img2img_prompt, #img2img_neg_prompt { background-color: var(--background-color); box-shadow: 4px 4px 4px 0px #333333 !important; } +#txt2img_prompt > label > textarea, #txt2img_neg_prompt > label > textarea, #img2img_prompt > label > textarea, #img2img_neg_prompt > label > textarea { font-size: 1.1rem; } +#img2img_settings { min-width: calc(2 * var(--left-column)); max-width: calc(2 * var(--left-column)); background-color: #111111; padding-top: 16px; } +#interrogate, #deepbooru { margin: 0 0px 10px 0px; max-width: 80px; max-height: 80px; font-weight: normal; font-size: 0.95em; } +#quicksettings .gr-button-tool { font-size: 1.6rem; box-shadow: none; margin-top: -2px; height: 2.4em; } +#quicksettings button {padding: 0 0.5em 0.1em 0.5em;} +#open_folder_extras, #footer, #style_pos_col, #style_neg_col, #roll_col, #extras_upscaler_2, #extras_upscaler_2_visibility, #txt2img_seed_resize_from_w, #txt2img_seed_resize_from_h { display: none; } +#save-animation { border-radius: var(--radius-sm) !important; margin-bottom: 16px; background-color: #111111; } +#script_list { padding: 4px; margin-top: 16px; margin-bottom: 8px; } +#settings > div.flex-wrap { width: 15em; } +#txt2img_cfg_scale { min-width: 200px; } +#txt2img_checkboxes, #img2img_checkboxes { background-color: transparent; } +#txt2img_checkboxes, #img2img_checkboxes { margin-bottom: 0.2em; } +#txt2img_actions_column, #img2img_actions_column { flex-flow: wrap; justify-content: space-between; } +#txt2img_enqueue_wrapper, #img2img_enqueue_wrapper { min-width: unset; width: 48%; } +#txt2img_generate_box, #img2img_generate_box { min-width: unset; width: 48%; } + +#extras_upscale { margin-top: 10px } +#txt2img_progress_row > div { min-width: var(--left-column); max-width: var(--left-column); } +#txt2img_settings { min-width: var(--left-column); max-width: var(--left-column); background-color: #111111; padding-top: 16px; } +#pnginfo_html2_info { margin-top: -18px; background-color: var(--input-background-fill); padding: var(--input-padding) } +#txt2img_tools, #img2img_tools { margin-top: -4px; margin-bottom: -4px; } +#txt2img_styles_row, #img2img_styles_row { margin-top: -6px; z-index: 200; } + +/* based on gradio built-in dark theme */ +:root, .light, .dark { + --body-background-fill: var(--background-color); + --color-accent-soft: var(--neutral-700); + --background-fill-secondary: none; + --border-color-accent: var(--background-color); + --border-color-primary: var(--background-color); + --link-text-color-active: var(--primary-500); + --link-text-color: var(--secondary-500); + --link-text-color-hover: var(--secondary-400); + --link-text-color-visited: var(--secondary-600); + --shadow-spread: 1px; + --block-background-fill: None; + --block-border-color: var(--border-color-primary); + --block_border_width: None; + --block-info-text-color: var(--body-text-color-subdued); + --block-label-background-fill: var(--background-fill-secondary); + --block-label-border-color: var(--border-color-primary); + --block_label_border_width: None; + --block-label-text-color: var(--neutral-200); + --block_shadow: None; + --block_title_background_fill: None; + --block_title_border_color: None; + --block_title_border_width: None; + --panel-background-fill: var(--background-fill-secondary); + --panel-border-color: var(--border-color-primary); + --panel_border_width: None; + --checkbox-background-color: var(--primary-200); + --checkbox-background-color-focus: var(--primary-400); + --checkbox-background-color-hover: var(--primary-200); + --checkbox-background-color-selected: var(--primary-400); + --checkbox-border-color: transparent; + --checkbox-border-color-focus: var(--primary-800); + --checkbox-border-color-hover: var(--primary-800); + --checkbox-border-color-selected: var(--primary-800); + --checkbox-border-width: var(--input-border-width); + --checkbox-label-background-fill: None; + --checkbox-label-background-fill-hover: None; + --checkbox-label-background-fill-selected: var(--checkbox-label-background-fill); + --checkbox-label-border-color: var(--border-color-primary); + --checkbox-label-border-color-hover: var(--checkbox-label-border-color); + --checkbox-label-border-width: var(--input-border-width); + --checkbox-label-text-color: var(--body-text-color); + --checkbox-label-text-color-selected: var(--checkbox-label-text-color); + --error-background-fill: var(--background-fill-primary); + --error-border-color: var(--border-color-primary); + --error-text-color: #f768b7; /*was ef4444*/ + --input-background-fill-focus: var(--secondary-600); + --input-background-fill-hover: var(--input-background-fill); + --input-border-color: var(--background-color); + --input-border-color-focus: var(--primary-800); + --input-placeholder-color: var(--neutral-500); + --input-shadow-focus: None; + --loader_color: None; + --slider_color: None; + --stat-background-fill: linear-gradient(to right, var(--primary-400), var(--primary-800)); + --table-border-color: var(--neutral-700); + --table-even-background-fill: var(--primary-300); + --table-odd-background-fill: var(--primary-200); + --table-row-focus: var(--color-accent-soft); + --button-border-width: var(--input-border-width); + --button-cancel-background-fill: linear-gradient(to bottom right, #dc2626, #b91c1c); + --button-cancel-background-fill-hover: linear-gradient(to bottom right, #dc2626, #dc2626); + --button-cancel-border-color: #dc2626; + --button-cancel-border-color-hover: var(--button-cancel-border-color); + --button-cancel-text-color: white; + --button-cancel-text-color-hover: var(--button-cancel-text-color); + --button-primary-background-fill: var(--primary-500); + --button-primary-background-fill-hover: var(--primary-800); + --button-primary-border-color: var(--primary-500); + --button-primary-border-color-hover: var(--button-primary-border-color); + --button-primary-text-color: white; + --button-primary-text-color-hover: var(--button-primary-text-color); + --button-secondary-border-color: var(--neutral-600); + --button-secondary-border-color-hover: var(--button-secondary-border-color); + --button-secondary-text-color-hover: var(--button-secondary-text-color); + --secondary-50: #eff6ff; + --secondary-100: #dbeafe; + --secondary-200: #bfdbfe; + --secondary-300: #93c5fd; + --secondary-400: #60a5fa; + --secondary-500: #3b82f6; + --secondary-600: #2563eb; + --secondary-700: #1d4ed8; + --secondary-800: #1e40af; + --secondary-900: #1e3a8a; + --secondary-950: #1d3660; + --neutral-50: #f0f0f0; /* */ + --neutral-100: #ddd5e8;/* majority of text (neutral gray purple) */ + --neutral-200: #d0d0d0; + --neutral-300: #bfbad6; /* top tab text (light accent) */ + --neutral-400: #ffba85;/* tab title (bright orange) */ + --neutral-500: #545b94; /* prompt text (desat accent)*/ + --neutral-600: #1f2028; /* tab outline color (accent color)*/ + --neutral-700: #20212c; /* unchanged settings tab accent (dark)*/ + --neutral-800: #e055dc; /* bright pink accent */ + --neutral-900: #111827; + --neutral-950: #0b0f19; + --radius-xxs: 0; + --radius-xs: 0; + --radius-md: 0; + --radius-xl: 0; + --radius-xxl: 0; + --body-text-size: var(--text-md); + --body-text-weight: 400; + --embed-radius: var(--radius-lg); + --color-accent: var(--primary-500); + --shadow-drop: 0; + --shadow-drop-lg: 0 1px 3px 0 rgb(0 0 0 / 0.1), 0 1px 2px -1px rgb(0 0 0 / 0.1); + --shadow-inset: rgba(0,0,0,0.05) 0px 2px 4px 0px inset; + --block-border-width: 1px; + --block-info-text-size: var(--text-sm); + --block-info-text-weight: 400; + --block-label-border-width: 1px; + --block-label-margin: 0; + --block-label-padding: var(--spacing-sm) var(--spacing-lg); + --block-label-radius: calc(var(--radius-lg) - 1px) 0 calc(var(--radius-lg) - 1px) 0; + --block-label-right-radius: 0 calc(var(--radius-lg) - 1px) 0 calc(var(--radius-lg) - 1px); + --block-label-text-size: var(--text-sm); + --block-label-text-weight: 400; + --block-padding: var(--spacing-xl) calc(var(--spacing-xl) + 2px); + --block-radius: var(--radius-lg); + --block-shadow: var(--shadow-drop); + --block-title-background-fill: none; + --block-title-border-color: none; + --block-title-border-width: 0; + --block-title-padding: 0; + --block-title-radius: none; + --block-title-text-size: var(--text-md); + --block-title-text-weight: 400; + --container-radius: var(--radius-lg); + --form-gap-width: 1px; + --layout-gap: var(--spacing-xxl); + --panel-border-width: 0; + --section-header-text-size: var(--text-md); + --section-header-text-weight: 400; + --checkbox-border-radius: var(--radius-sm); + --checkbox-label-gap: 2px; + --checkbox-label-padding: var(--spacing-md); + --checkbox-label-shadow: var(--shadow-drop); + --checkbox-label-text-size: var(--text-md); + --checkbox-label-text-weight: 400; + --checkbox-check: url("data:image/svg+xml,%3csvg viewBox='0 0 16 16' fill='white' xmlns='http://www.w3.org/2000/svg'%3e%3cpath d='M12.207 4.793a1 1 0 010 1.414l-5 5a1 1 0 01-1.414 0l-2-2a1 1 0 011.414-1.414L6.5 9.086l4.293-4.293a1 1 0 011.414 0z'/%3e%3c/svg%3e"); + --radio-circle: url("data:image/svg+xml,%3csvg viewBox='0 0 16 16' fill='white' xmlns='http://www.w3.org/2000/svg'%3e%3ccircle cx='8' cy='8' r='3'/%3e%3c/svg%3e"); + --checkbox-shadow: var(--input-shadow); + --error-border-width: 1px; + --input-border-width: 1px; + --input-radius: var(--radius-lg); + --input-text-size: var(--text-md); + --input-text-weight: 400; + --loader-color: var(--color-accent); + --prose-text-size: var(--text-md); + --prose-text-weight: 400; + --prose-header-text-weight: 600; + --slider-color: ; + --table-radius: var(--radius-lg); + --button-large-padding: 2px 6px; + --button-large-radius: var(--radius-lg); + --button-large-text-size: var(--text-lg); + --button-large-text-weight: 400; + --button-shadow: none; + --button-shadow-active: none; + --button-shadow-hover: none; + --button-small-padding: var(--spacing-sm) calc(2 * var(--spacing-sm)); + --button-small-radius: var(--radius-lg); + --button-small-text-size: var(--text-md); + --button-small-text-weight: 400; + --button-transition: none; + --size-9: 64px; + --size-14: 64px; +} diff --git a/javascript/timeless-beige.css b/javascript/timeless-beige.css new file mode 100644 index 000000000..b4b8c57e3 --- /dev/null +++ b/javascript/timeless-beige.css @@ -0,0 +1,297 @@ +/* generic html tags */ +:root, .light, .dark { + --font: 'system-ui', 'ui-sans-serif', 'system-ui', "Roboto", sans-serif; + --font-mono: 'ui-monospace', 'Consolas', monospace; + --font-size: 16px; + --primary-100: #212226; /* bg color*/ + --primary-200: #17181b; /* drop down menu/ prompt window fill*/ + --primary-300: #0a0c0e; /* black */ + --primary-400: #2f3034; /* small buttons*/ + --primary-500: #434242; /* main accent color retro beige*/ + --primary-700: #e75d5d; /* light blue gray*/ + --primary-800: #e75d5d; /* sat orange(hover accent)*/ + --highlight-color: var(--primary-500); + --inactive-color: var(--primary--800); + --body-text-color: var(--neutral-100); + --body-text-color-subdued: var(--neutral-300); + --background-color: var(--primary-100); + --background-fill-primary: var(--input-background-fill); + --input-padding: 8px; + --input-background-fill: var(--primary-200); + --input-shadow: none; + --button-secondary-text-color: white; + --button-secondary-background-fill: var(--primary-400); + --button-secondary-background-fill-hover: var(--primary-700); + --block-title-text-color: var(--neutral-300); + --radius-sm: 1px; + --radius-lg: 6px; + --spacing-md: 4px; + --spacing-xxl: 8px; + --line-sm: 1.2em; + --line-md: 1.4em; +} + +html { font-size: var(--font-size); } +body, button, input, select, textarea { font-family: var(--font);} +button { font-size: 1.2rem; max-width: 400px; } +img { background-color: var(--background-color); } +input[type=range] { height: var(--line-sm); appearance: none; margin-top: 0; min-width: 160px; background-color: var(--background-color); width: 100%; background: transparent; } +input[type=range]::-webkit-slider-runnable-track, input[type=range]::-moz-range-track { width: 100%; height: 6px; cursor: pointer; background: var(--primary-400); border-radius: var(--radius-lg); border: 0px solid #222222; } +input[type=range]::-webkit-slider-thumb, input[type=range]::-moz-range-thumb { border: 0px solid #000000; height: var(--line-sm); width: 8px; border-radius: var(--radius-lg); background: white; cursor: pointer; appearance: none; margin-top: 0px; } +input[type=range]::-moz-range-progress { background-color: var(--primary-500); height: 6px; border-radius: var(--radius-lg); } +::-webkit-scrollbar-track { background: #333333; } +::-webkit-scrollbar-thumb { background-color: var(--highlight-color); border-radius: var(--radius-lg); border-width: 0; box-shadow: 2px 2px 3px #111111; } +div.form { border-width: 0; box-shadow: none; background: transparent; overflow: visible; margin-bottom: 6px; } +div.compact { gap: 1em; } + +/* gradio style classes */ +fieldset .gr-block.gr-box, label.block span { padding: 0; margin-top: -4px; } +.border-2 { border-width: 0; } +.border-b-2 { border-bottom-width: 2px; border-color: var(--highlight-color) !important; padding-bottom: 2px; margin-bottom: 8px; } +.bg-white { color: lightyellow; background-color: var(--inactive-color); } +.gr-box { border-radius: var(--radius-sm) !important; background-color: #111111 !important; box-shadow: 2px 2px 3px #111111; border-width: 0; padding: 4px; margin: 12px 0px 12px 0px } +.gr-button { font-weight: normal; box-shadow: 2px 2px 3px #111111; font-size: 0.8rem; min-width: 32px; min-height: 32px; padding: 3px; margin: 3px; } +.gr-check-radio { background-color: var(--inactive-color); border-width: 0; border-radius: var(--radius-lg); box-shadow: 2px 2px 3px #111111; } +.gr-check-radio:checked { background-color: var(--highlight-color); } +.gr-compact { background-color: var(--background-color); } +.gr-form { border-width: 0; } +.gr-input { background-color: #333333 !important; padding: 4px; margin: 4px; } +.gr-input-label { color: lightyellow; border-width: 0; background: transparent; padding: 2px !important; } +.gr-panel { background-color: var(--background-color); } +.eta-bar { display: none !important } +svg.feather.feather-image, .feather .feather-image { display: none } +.gap-2 { padding-top: 8px; } +.gr-box > div > div > input.gr-text-input { right: 0; width: 4em; padding: 0; top: -12px; border: none; max-height: 20px; } +.output-html { line-height: 1.2rem; overflow-x: hidden; } +.output-html > div { margin-bottom: 8px; } +.overflow-hidden .flex .flex-col .relative col .gap-4 { min-width: var(--left-column); max-width: var(--left-column); } /* this is a problematic one */ +.p-2 { padding: 0; } +.px-4 { padding-lefT: 1rem; padding-right: 1rem; } +.py-6 { padding-bottom: 0; } +.tabs { background-color: var(--background-color); } +.block.token-counter span { background-color: var(--input-background-fill) !important; box-shadow: 2px 2px 2px #111; border: none !important; font-size: 0.8rem; } +.tab-nav { zoom: 120%; margin-top: 10px; margin-bottom: 10px; border-bottom: 2px solid var(--highlight-color) !important; padding-bottom: 2px; } +div.tab-nav button.selected {background-color: var(--button-primary-background-fill);} +#settings div.tab-nav button.selected {background-color: var(--background-color); color: var(--primary-800); font-weight: bold;} +.label-wrap { background-color: #292b30; /* extension tab color*/ padding: 16px 8px 8px 8px; border-radius: var(--radius-lg); padding-left: 8px !important; } +.small-accordion .label-wrap { padding: 8px 0px 8px 0px; } +.small-accordion .label-wrap .icon { margin-right: 1em; } +.gradio-button.tool { border: none; box-shadow: none; border-radius: var(--radius-lg);} +button.selected {background: var(--button-primary-background-fill);} +.center.boundedheight.flex {background-color: var(--input-background-fill);} +.compact {border-radius: var(--border-radius-lg);} +#logMonitorData {background-color: var(--input-background-fill);} +#tab_extensions table td, #tab_extensions table th, #tab_config table td, #tab_config table th { border: none; padding: 0.5em; background-color: var(--primary-200); } +#tab_extensions table, #tab_config table { width: 96vw; } +#tab_extensions table input[type=checkbox] {appearance: none; border-radius: 0px;} +#tab_extensions button:hover { background-color: var(--button-secondary-background-fill-hover);} + +/* automatic style classes */ +.progressDiv { border-radius: var(--radius-sm) !important; position: fixed; top: 44px; right: 26px; max-width: 262px; height: 48px; z-index: 99; box-shadow: var(--button-shadow); } +.progressDiv .progress { border-radius: var(--radius-lg) !important; background: var(--highlight-color); line-height: 3rem; height: 48px; } +.gallery-item { box-shadow: none !important; } +.performance { color: #888; } +.extra-networks { border-left: 2px solid var(--highlight-color) !important; padding-left: 4px; } +.image-buttons { gap: 10px !important; justify-content: center; } +.image-buttons > button { max-width: 160px; } +.tooltip { background: var(--primary-800); color: white; border: none; border-radius: var(--radius-lg) } +#system_row > button, #settings_row > button, #config_row > button { max-width: 190px; } + +/* gradio elements overrides */ +#div.gradio-container { overflow-x: hidden; } +#img2img_label_copy_to_img2img { font-weight: normal; } +#txt2img_prompt, #txt2img_neg_prompt, #img2img_prompt, #img2img_neg_prompt { background-color: var(--background-color); box-shadow: 4px 4px 4px 0px #333333 !important; } +#txt2img_prompt > label > textarea, #txt2img_neg_prompt > label > textarea, #img2img_prompt > label > textarea, #img2img_neg_prompt > label > textarea { font-size: 1.1rem; } +#img2img_settings { min-width: calc(2 * var(--left-column)); max-width: calc(2 * var(--left-column)); background-color: #111111; padding-top: 16px; } +#interrogate, #deepbooru { margin: 0 0px 10px 0px; max-width: 80px; max-height: 80px; font-weight: normal; font-size: 0.95em; } +#quicksettings .gr-button-tool { font-size: 1.6rem; box-shadow: none; margin-top: -2px; height: 2.4em; } +#quicksettings button {padding: 0 0.5em 0.1em 0.5em;} +#open_folder_extras, #footer, #style_pos_col, #style_neg_col, #roll_col, #extras_upscaler_2, #extras_upscaler_2_visibility, #txt2img_seed_resize_from_w, #txt2img_seed_resize_from_h { display: none; } +#save-animation { border-radius: var(--radius-sm) !important; margin-bottom: 16px; background-color: #111111; } +#script_list { padding: 4px; margin-top: 16px; margin-bottom: 8px; } +#settings > div.flex-wrap { width: 15em; } +#txt2img_cfg_scale { min-width: 200px; } +#txt2img_checkboxes, #img2img_checkboxes { background-color: transparent; } +#txt2img_checkboxes, #img2img_checkboxes { margin-bottom: 0.2em; } +#txt2img_actions_column, #img2img_actions_column { flex-flow: wrap; justify-content: space-between; } +#txt2img_enqueue_wrapper, #img2img_enqueue_wrapper { min-width: unset; width: 48%; } +#txt2img_generate_box, #img2img_generate_box { min-width: unset; width: 48%; } + +#extras_upscale { margin-top: 10px } +#txt2img_progress_row > div { min-width: var(--left-column); max-width: var(--left-column); } +#txt2img_settings { min-width: var(--left-column); max-width: var(--left-column); background-color: #111111; padding-top: 16px; } +#pnginfo_html2_info { margin-top: -18px; background-color: var(--input-background-fill); padding: var(--input-padding) } +#txt2img_tools, #img2img_tools { margin-top: -4px; margin-bottom: -4px; } +#txt2img_styles_row, #img2img_styles_row { margin-top: -6px; z-index: 200; } + +/* based on gradio built-in dark theme */ +:root, .light, .dark { + --body-background-fill: var(--background-color); + --color-accent-soft: var(--neutral-700); + --background-fill-secondary: none; + --border-color-accent: var(--background-color); + --border-color-primary: var(--background-color); + --link-text-color-active: var(--primary-500); + --link-text-color: var(--secondary-500); + --link-text-color-hover: var(--secondary-400); + --link-text-color-visited: var(--secondary-600); + --shadow-spread: 1px; + --block-background-fill: None; + --block-border-color: var(--border-color-primary); + --block_border_width: None; + --block-info-text-color: var(--body-text-color-subdued); + --block-label-background-fill: var(--background-fill-secondary); + --block-label-border-color: var(--border-color-primary); + --block_label_border_width: None; + --block-label-text-color: var(--neutral-200); + --block_shadow: None; + --block_title_background_fill: None; + --block_title_border_color: None; + --block_title_border_width: None; + --panel-background-fill: var(--background-fill-secondary); + --panel-border-color: var(--border-color-primary); + --panel_border_width: None; + --checkbox-background-color: var(--primary-400); + --checkbox-background-color-focus: var(--primary-700); + --checkbox-background-color-hover: var(--primary-700); + --checkbox-background-color-selected: var(--primary-500); + --checkbox-border-color: transparent; + --checkbox-border-color-focus: var(--primary-800); + --checkbox-border-color-hover: var(--primary-800); + --checkbox-border-color-selected: var(--primary-800); + --checkbox-border-width: var(--input-border-width); + --checkbox-label-background-fill: None; + --checkbox-label-background-fill-hover: None; + --checkbox-label-background-fill-selected: var(--checkbox-label-background-fill); + --checkbox-label-border-color: var(--border-color-primary); + --checkbox-label-border-color-hover: var(--checkbox-label-border-color); + --checkbox-label-border-width: var(--input-border-width); + --checkbox-label-text-color: var(--body-text-color); + --checkbox-label-text-color-selected: var(--checkbox-label-text-color); + --error-background-fill: var(--background-fill-primary); + --error-border-color: var(--border-color-primary); + --error-text-color: #f768b7; /*was ef4444*/ + --input-background-fill-focus: var(--secondary-600); + --input-background-fill-hover: var(--input-background-fill); + --input-border-color: var(--background-color); + --input-border-color-focus: var(--primary-800); + --input-placeholder-color: var(--neutral-500); + --input-shadow-focus: None; + --loader_color: None; + --slider_color: None; + --stat-background-fill: linear-gradient(to right, var(--primary-400), var(--primary-800)); + --table-border-color: var(--neutral-700); + --table-even-background-fill: var(--primary-300); + --table-odd-background-fill: var(--primary-200); + --table-row-focus: var(--color-accent-soft); + --button-border-width: var(--input-border-width); + --button-cancel-background-fill: linear-gradient(to bottom right, #dc2626, #b91c1c); + --button-cancel-background-fill-hover: linear-gradient(to bottom right, #dc2626, #dc2626); + --button-cancel-border-color: #dc2626; + --button-cancel-border-color-hover: var(--button-cancel-border-color); + --button-cancel-text-color: white; + --button-cancel-text-color-hover: var(--button-cancel-text-color); + --button-primary-background-fill: var(--primary-500); + --button-primary-background-fill-hover: var(--primary-800); + --button-primary-border-color: var(--primary-500); + --button-primary-border-color-hover: var(--button-primary-border-color); + --button-primary-text-color: white; + --button-primary-text-color-hover: var(--button-primary-text-color); + --button-secondary-border-color: var(--neutral-600); + --button-secondary-border-color-hover: var(--button-secondary-border-color); + --button-secondary-text-color-hover: var(--button-secondary-text-color); + --secondary-50: #eff6ff; + --secondary-100: #dbeafe; + --secondary-200: #bfdbfe; + --secondary-300: #93c5fd; + --secondary-400: #60a5fa; + --secondary-500: #3b82f6; + --secondary-600: #2563eb; + --secondary-700: #1d4ed8; + --secondary-800: #1e40af; + --secondary-900: #1e3a8a; + --secondary-950: #1d3660; + --neutral-50: #f0f0f0; /* */ + --neutral-100: #e0dedc;/* majority of text (neutral gray yellow) */ + --neutral-200: #d0d0d0; + --neutral-300: #9d9dab; /* top tab text (light accent) */ + --neutral-400: #ffba85;/* tab title (light beige) */ + --neutral-500: #484746; /* prompt text (desat accent)*/ + --neutral-600: #605a54; /* tab outline color (accent color)*/ + --neutral-700: #1b1c1e; /* small settings tab accent (dark)*/ + --neutral-800: #e75d5d; /* bright orange accent */ + --neutral-900: #111827; + --neutral-950: #0b0f19; + --radius-xxs: 0; + --radius-xs: 0; + --radius-md: 0; + --radius-xl: 0; + --radius-xxl: 0; + --body-text-size: var(--text-md); + --body-text-weight: 400; + --embed-radius: var(--radius-lg); + --color-accent: var(--primary-500); + --shadow-drop: 0; + --shadow-drop-lg: 0 1px 3px 0 rgb(0 0 0 / 0.1), 0 1px 2px -1px rgb(0 0 0 / 0.1); + --shadow-inset: rgba(0,0,0,0.05) 0px 2px 4px 0px inset; + --block-border-width: 1px; + --block-info-text-size: var(--text-sm); + --block-info-text-weight: 400; + --block-label-border-width: 1px; + --block-label-margin: 0; + --block-label-padding: var(--spacing-sm) var(--spacing-lg); + --block-label-radius: calc(var(--radius-lg) - 1px) 0 calc(var(--radius-lg) - 1px) 0; + --block-label-right-radius: 0 calc(var(--radius-lg) - 1px) 0 calc(var(--radius-lg) - 1px); + --block-label-text-size: var(--text-sm); + --block-label-text-weight: 400; + --block-padding: var(--spacing-xl) calc(var(--spacing-xl) + 2px); + --block-radius: var(--radius-lg); + --block-shadow: var(--shadow-drop); + --block-title-background-fill: none; + --block-title-border-color: none; + --block-title-border-width: 0; + --block-title-padding: 0; + --block-title-radius: none; + --block-title-text-size: var(--text-md); + --block-title-text-weight: 400; + --container-radius: var(--radius-lg); + --form-gap-width: 1px; + --layout-gap: var(--spacing-xxl); + --panel-border-width: 0; + --section-header-text-size: var(--text-md); + --section-header-text-weight: 400; + --checkbox-border-radius: var(--radius-sm); + --checkbox-label-gap: 2px; + --checkbox-label-padding: var(--spacing-md); + --checkbox-label-shadow: var(--shadow-drop); + --checkbox-label-text-size: var(--text-md); + --checkbox-label-text-weight: 400; + --checkbox-check: url("data:image/svg+xml,%3csvg viewBox='0 0 16 16' fill='white' xmlns='http://www.w3.org/2000/svg'%3e%3cpath d='M12.207 4.793a1 1 0 010 1.414l-5 5a1 1 0 01-1.414 0l-2-2a1 1 0 011.414-1.414L6.5 9.086l4.293-4.293a1 1 0 011.414 0z'/%3e%3c/svg%3e"); + --radio-circle: url("data:image/svg+xml,%3csvg viewBox='0 0 16 16' fill='white' xmlns='http://www.w3.org/2000/svg'%3e%3ccircle cx='8' cy='8' r='3'/%3e%3c/svg%3e"); + --checkbox-shadow: var(--input-shadow); + --error-border-width: 1px; + --input-border-width: 1px; + --input-radius: var(--radius-lg); + --input-text-size: var(--text-md); + --input-text-weight: 400; + --loader-color: var(--color-accent); + --prose-text-size: var(--text-md); + --prose-text-weight: 400; + --prose-header-text-weight: 600; + --slider-color: ; + --table-radius: var(--radius-lg); + --button-large-padding: 2px 6px; + --button-large-radius: var(--radius-lg); + --button-large-text-size: var(--text-lg); + --button-large-text-weight: 400; + --button-shadow: none; + --button-shadow-active: none; + --button-shadow-hover: none; + --button-small-padding: var(--spacing-sm) calc(2 * var(--spacing-sm)); + --button-small-radius: var(--radius-lg); + --button-small-text-size: var(--text-md); + --button-small-text-weight: 400; + --button-transition: none; + --size-9: 64px; + --size-14: 64px; +} diff --git a/modules/control/controlnetsxs.py b/modules/control/controlnetsxs.py index 712fdf7b9..7fbf25d3c 100644 --- a/modules/control/controlnetsxs.py +++ b/modules/control/controlnetsxs.py @@ -1,11 +1,18 @@ import os import time -from diffusers import StableDiffusionPipeline -from diffusers import StableDiffusionXLPipeline -from diffusers import ControlNetXSModel, StableDiffusionControlNetXSPipeline, StableDiffusionXLControlNetXSPipeline from modules.shared import log, opts from modules import errors +ok = True +try: + from diffusers import StableDiffusionPipeline + from diffusers import StableDiffusionXLPipeline + from diffusers import ControlNetXSModel, StableDiffusionControlNetXSPipeline, StableDiffusionXLControlNetXSPipeline +except Exception: + from diffusers import ControlNetModel + ControlNetXSModel = ControlNetModel # dummy + ok = False + what = 'ControlNet-XS' debug = log.debug if os.environ.get('SD_CONTROL_DEBUG', None) is not None else lambda *args, **kwargs: None @@ -35,8 +42,10 @@ def find_models(): def list_models(refresh=False): - import modules.shared global models # pylint: disable=global-statement + if not ok: + return models + import modules.shared if not refresh and len(models) > 0: return models models = {} diff --git a/modules/ui_control.py b/modules/ui_control.py index 65fe65840..8a7bf8a99 100644 --- a/modules/ui_control.py +++ b/modules/ui_control.py @@ -151,6 +151,11 @@ def video_type_change(video_type): def create_ui(_blocks: gr.Blocks=None): initialize() + if shared.backend == shared.Backend.ORIGINAL: + with gr.Blocks(analytics_enabled = False) as control_ui: + pass + return [(control_ui, 'Control', 'control')] + with gr.Blocks(analytics_enabled = False) as control_ui: prompt, styles, negative, btn_generate, _btn_interrogate, _btn_deepbooru, btn_paste, btn_extra, prompt_counter, btn_prompt_counter, negative_counter, btn_negative_counter = ui.create_toprow(is_img2img=False, id_part='control') with FormGroup(elem_id="control_interface", equal_height=False): diff --git a/wiki b/wiki index 6b1f994d4..5ce9b8d0b 160000 --- a/wiki +++ b/wiki @@ -1 +1 @@ -Subproject commit 6b1f994d4b19d08b6c39c1df2b84459573b3d04f +Subproject commit 5ce9b8d0b798194e108094701bc55589fd01cbd1 From 5cfc044ec6351789291e2718706897588be0328f Mon Sep 17 00:00:00 2001 From: Vladimir Mandic Date: Wed, 20 Dec 2023 17:33:19 -0500 Subject: [PATCH 094/143] tracing and control improvements --- modules/control/adapters.py | 16 +++++---- modules/control/controlnets.py | 5 +-- modules/control/controlnetsxs.py | 3 +- modules/control/processors.py | 13 ++++--- modules/control/run.py | 40 ++++++++++++---------- modules/control/test.py | 2 ++ modules/generation_parameters_copypaste.py | 1 + modules/img2img.py | 1 + modules/paths.py | 1 + modules/processing.py | 1 + modules/processing_correction.py | 1 + modules/prompt_parser.py | 1 + modules/prompt_parser_diffusers.py | 2 +- modules/sd_samplers.py | 1 + modules/sd_samplers_diffusers.py | 1 + modules/txt2img.py | 1 + modules/ui_control.py | 2 ++ modules/ui_extra_networks.py | 1 + webui.py | 1 + 19 files changed, 61 insertions(+), 33 deletions(-) diff --git a/modules/control/adapters.py b/modules/control/adapters.py index fccb093c3..38269f779 100644 --- a/modules/control/adapters.py +++ b/modules/control/adapters.py @@ -1,12 +1,13 @@ import os import time -from diffusers import StableDiffusionPipeline, StableDiffusionXLPipeline, T2IAdapter, StableDiffusionAdapterPipeline, StableDiffusionXLAdapterPipeline +from diffusers import StableDiffusionPipeline, StableDiffusionXLPipeline, T2IAdapter, MultiAdapter, StableDiffusionAdapterPipeline, StableDiffusionXLAdapterPipeline from modules.shared import log from modules import errors what = 'T2I-Adapter' -debug = log.debug if os.environ.get('SD_CONTROL_DEBUG', None) is not None else lambda *args, **kwargs: None +debug = log.trace if os.environ.get('SD_CONTROL_DEBUG', None) is not None else lambda *args, **kwargs: None +debug('Trace: CONTROL') predefined_sd15 = { 'Canny': 'TencentARC/t2iadapter_canny_sd15v2', 'Depth': 'TencentARC/t2iadapter_depth_sd15v2', @@ -37,11 +38,12 @@ def list_models(refresh=False): models = {} if modules.shared.sd_model_type == 'none': models = ['None'] - if modules.shared.sd_model_type == 'sdxl': + elif modules.shared.sd_model_type == 'sdxl': models = ['None'] + sorted(predefined_sdxl) - if modules.shared.sd_model_type == 'sd': + elif modules.shared.sd_model_type == 'sd': models = ['None'] + sorted(predefined_sd15) else: + log.warning(f'Control {what} model list failed: unknown model type') models = ['None'] + sorted(list(predefined_sd15) + list(predefined_sdxl)) debug(f'Control list {what}: path={cache_dir} models={models}') return models @@ -102,6 +104,8 @@ def __init__(self, adapter: T2IAdapter | list[T2IAdapter], pipeline: StableDiffu if pipeline is None: log.error(f'Control {what} pipeline: model not loaded') return + # if isinstance(adapter, list) and len(adapter) > 1: # TODO use MultiAdapter + # adapter = MultiAdapter(adapter) if isinstance(pipeline, StableDiffusionXLPipeline): self.pipeline = StableDiffusionXLAdapterPipeline( vae=pipeline.vae, @@ -111,7 +115,7 @@ def __init__(self, adapter: T2IAdapter | list[T2IAdapter], pipeline: StableDiffu tokenizer_2=pipeline.tokenizer_2, unet=pipeline.unet, scheduler=pipeline.scheduler, - adapter=adapter, # can be a list + adapter=adapter, ).to(pipeline.device) elif isinstance(pipeline, StableDiffusionPipeline): self.pipeline = StableDiffusionAdapterPipeline( @@ -123,7 +127,7 @@ def __init__(self, adapter: T2IAdapter | list[T2IAdapter], pipeline: StableDiffu requires_safety_checker=False, safety_checker=None, feature_extractor=None, - adapter=adapter, # can be a list + adapter=adapter, ).to(pipeline.device) else: log.error(f'Control {what} pipeline: class={pipeline.__class__.__name__} unsupported model type') diff --git a/modules/control/controlnets.py b/modules/control/controlnets.py index f884c9822..14a73c2cf 100644 --- a/modules/control/controlnets.py +++ b/modules/control/controlnets.py @@ -8,7 +8,8 @@ what = 'ControlNet' -debug = log.debug if os.environ.get('SD_CONTROL_DEBUG', None) is not None else lambda *args, **kwargs: None +debug = log.trace if os.environ.get('SD_CONTROL_DEBUG', None) is not None else lambda *args, **kwargs: None +debug('Trace: CONTROL') predefined_sd15 = { 'OpenPose': "lllyasviel/control_v11p_sd15_openpose", 'Canny': "lllyasviel/control_v11p_sd15_canny", @@ -65,7 +66,7 @@ def list_models(refresh=False): elif modules.shared.sd_model_type == 'sd': models = ['None'] + sorted(predefined_sd15) + sorted(find_models()) else: - log.error('Control model list failed: unknown model type') + log.warning(f'Control {what} model list failed: unknown model type') models = ['None'] + sorted(predefined_sd15) + sorted(predefined_sdxl) + sorted(find_models()) debug(f'Control list {what}: path={cache_dir} models={models}') return models diff --git a/modules/control/controlnetsxs.py b/modules/control/controlnetsxs.py index 7fbf25d3c..1cd54cbe5 100644 --- a/modules/control/controlnetsxs.py +++ b/modules/control/controlnetsxs.py @@ -15,7 +15,8 @@ what = 'ControlNet-XS' -debug = log.debug if os.environ.get('SD_CONTROL_DEBUG', None) is not None else lambda *args, **kwargs: None +debug = log.trace if os.environ.get('SD_CONTROL_DEBUG', None) is not None else lambda *args, **kwargs: None +debug('Trace: CONTROL') predefined_sd15 = { } predefined_sdxl = { diff --git a/modules/control/processors.py b/modules/control/processors.py index f6877cf89..c8cc9438e 100644 --- a/modules/control/processors.py +++ b/modules/control/processors.py @@ -26,7 +26,8 @@ models = {} cache_dir = 'models/control/processors' -debug = log.debug if os.environ.get('SD_CONTROL_DEBUG', None) is not None else lambda *args, **kwargs: None +debug = log.trace if os.environ.get('SD_CONTROL_DEBUG', None) is not None else lambda *args, **kwargs: None +debug('Trace: CONTROL') config = { # pose models 'OpenPose': {'class': OpenposeDetector, 'checkpoint': True, 'params': {'include_body': True, 'include_hand': False, 'include_face': False}}, @@ -115,9 +116,11 @@ def __init__(self, processor_id: str = None, resize = True, load_config = None): self.load_config = { 'cache_dir': cache_dir } from_config = config.get(processor_id, {}).get('load_config', None) if load_config is not None: - self.load_config.update(load_config) + for k, v in load_config.items(): + self.load_config[k] = v if from_config is not None: - self.load_config.update(from_config) + for k, v in from_config.items(): + self.load_config[k] = v if processor_id is not None: self.load() @@ -137,9 +140,11 @@ def load(self, processor_id: str = None): return from_config = config.get(processor_id, {}).get('load_config', None) if from_config is not None: - self.load_config.update(from_config) + for k, v in from_config.items(): + self.load_config[k] = v cls = config[processor_id]['class'] log.debug(f'Control processor loading: id="{processor_id}" class={cls.__name__}') + debug(f'Control processor config={self.load_config}') if 'DWPose' in processor_id: det_ckpt = 'https://download.openmmlab.com/mmdetection/v2.0/yolox/yolox_l_8x8_300e_coco/yolox_l_8x8_300e_coco_20211126_140236-d3bd2b23.pth' if 'Tiny' == config['DWPose']['model']: diff --git a/modules/control/run.py b/modules/control/run.py index fe99413a2..3dbe7114f 100644 --- a/modules/control/run.py +++ b/modules/control/run.py @@ -16,7 +16,8 @@ from modules import devices, shared, errors, processing, images, sd_models, sd_samplers -debug = shared.log.debug if os.environ.get('SD_CONTROL_DEBUG', None) is not None else lambda *args, **kwargs: None +debug = shared.log.trace if os.environ.get('SD_CONTROL_DEBUG', None) is not None else lambda *args, **kwargs: None +debug('Trace: CONTROL') pipe = None original_pipeline = None @@ -192,7 +193,7 @@ def control_run(units: List[unit.Unit], inputs, unit_type: str, is_generator: bo pass shared.sd_model = sd_models.set_diffuser_pipe(shared.sd_model, sd_models.DiffusersTaskType.TEXT_2_IMAGE) # reset current pipeline - if not has_models and (unit_type == 'reference' or unit_type == 'controlnet' or unit_type == 'xs'): # run in img2img mode + if not has_models and (unit_type == 'reference' or unit_type == 'adapter' or unit_type == 'controlnet' or unit_type == 'xs'): # run in img2img mode if len(active_strength) > 0: p.strength = active_strength[0] pipe = diffusers.AutoPipelineForImage2Image.from_pipe(shared.sd_model) # use set_diffuser_pipe @@ -229,7 +230,7 @@ def control_run(units: List[unit.Unit], inputs, unit_type: str, is_generator: bo instance = reference.ReferencePipeline(shared.sd_model) pipe = instance.pipeline else: - shared.log.error('Control: unknown unit type') + shared.log.error(f'Control: unknown unit type: {unit_type}') pipe = None debug(f'Control pipeline: class={pipe.__class__} args={vars(p)}') t1, t2, t3 = time.time(), 0, 0 @@ -352,7 +353,7 @@ def control_run(units: List[unit.Unit], inputs, unit_type: str, is_generator: bo # pipeline output = None if pipe is not None: # run new pipeline - if not has_models and (unit_type == 'reference' or unit_type == 'controlnet'): # run in img2img mode + if not has_models and (unit_type == 'reference' or unit_type == 'controlnet' or unit_type == 'adapter' or unit_type == 'xs'): # run in img2img mode if p.image is None: if hasattr(p, 'init_images'): del p.init_images @@ -365,6 +366,7 @@ def control_run(units: List[unit.Unit], inputs, unit_type: str, is_generator: bo if hasattr(p, 'init_images'): del p.init_images shared.sd_model = sd_models.set_diffuser_pipe(shared.sd_model, sd_models.DiffusersTaskType.TEXT_2_IMAGE) # reset current pipeline + debug(f'Control exec pipeline: class={pipe.__class__} args={vars(p)}') processed: processing.Processed = processing.process_images(p) # run actual pipeline output = processed.images if processed is not None else None # output = pipe(**vars(p)).images # alternative direct pipe exec call @@ -375,22 +377,22 @@ def control_run(units: List[unit.Unit], inputs, unit_type: str, is_generator: bo # outputs if output is not None and len(output) > 0: output_image = output[0] + if output_image is not None: + # resize + if resize_mode != 0 and resize_time == 'After': + debug(f'Control resize: image={input_image} width={width} height={height} mode={resize_mode} name={resize_name} sequence={resize_time}') + output_image = images.resize_image(resize_mode, output_image, width, height, resize_name) + elif hasattr(p, 'width') and hasattr(p, 'height'): + output_image = output_image.resize((p.width, p.height), Image.Resampling.LANCZOS) - # resize - if resize_mode != 0 and resize_time == 'After': - debug(f'Control resize: image={input_image} width={width} height={height} mode={resize_mode} name={resize_name} sequence={resize_time}') - output_image = images.resize_image(resize_mode, output_image, width, height, resize_name) - elif hasattr(p, 'width') and hasattr(p, 'height'): - output_image = output_image.resize((p.width, p.height), Image.Resampling.LANCZOS) - - output_images.append(output_image) - if is_generator: - image_txt = f'{output_image.width}x{output_image.height}' if output_image is not None else 'None' - if video is not None: - msg = f'Control output | {index} of {frames} skip {video_skip_frames} | Frame {image_txt}' - else: - msg = f'Control output | {index} of {len(inputs)} | Image {image_txt}' - yield (output_image, processed_image, msg) # result is control_output, proces_output + output_images.append(output_image) + if is_generator: + image_txt = f'{output_image.width}x{output_image.height}' if output_image is not None else 'None' + if video is not None: + msg = f'Control output | {index} of {frames} skip {video_skip_frames} | Frame {image_txt}' + else: + msg = f'Control output | {index} of {len(inputs)} | Image {image_txt}' + yield (output_image, processed_image, msg) # result is control_output, proces_output if video is not None and frame is not None: status, frame = video.read() diff --git a/modules/control/test.py b/modules/control/test.py index 0c5b61053..1f233ce74 100644 --- a/modules/control/test.py +++ b/modules/control/test.py @@ -11,6 +11,8 @@ def test_processors(image): from PIL import ImageDraw, ImageFont images = [] for processor_id in processors.list_models(): + if shared.state.interrupted: + continue shared.log.info(f'Testing processor: {processor_id}') processor = processors.Processor(processor_id) if processor is None: diff --git a/modules/generation_parameters_copypaste.py b/modules/generation_parameters_copypaste.py index 63c370e10..1d8e285ec 100644 --- a/modules/generation_parameters_copypaste.py +++ b/modules/generation_parameters_copypaste.py @@ -17,6 +17,7 @@ paste_fields = {} registered_param_bindings = [] debug = shared.log.trace if os.environ.get('SD_PASTE_DEBUG', None) is not None else lambda *args, **kwargs: None +debug('Trace: PASTE') class ParamBinding: diff --git a/modules/img2img.py b/modules/img2img.py index 04e17fc4c..6c828309c 100644 --- a/modules/img2img.py +++ b/modules/img2img.py @@ -10,6 +10,7 @@ debug = shared.log.trace if os.environ.get('SD_PROCESS_DEBUG', None) is not None else lambda *args, **kwargs: None +debug('Trace: PROCESS') def process_batch(p, input_files, input_dir, output_dir, inpaint_mask_dir, args): diff --git a/modules/paths.py b/modules/paths.py index 1d2869a7d..9347c51a0 100644 --- a/modules/paths.py +++ b/modules/paths.py @@ -33,6 +33,7 @@ sd_model_file = cli.ckpt or os.path.join(script_path, 'model.ckpt') # not used default_sd_model_file = sd_model_file # not used debug = log.trace if os.environ.get('SD_PATH_DEBUG', None) is not None else lambda *args, **kwargs: None +debug('Trace: PATH') paths = {} if os.environ.get('SD_PATH_DEBUG', None) is not None: diff --git a/modules/processing.py b/modules/processing.py index 761aa7f3b..a2f680c3b 100644 --- a/modules/processing.py +++ b/modules/processing.py @@ -45,6 +45,7 @@ opt_C = 4 opt_f = 8 debug = shared.log.trace if os.environ.get('SD_PROCESS_DEBUG', None) is not None else lambda *args, **kwargs: None +debug('Trace: PROCESS') def setup_color_correction(image): diff --git a/modules/processing_correction.py b/modules/processing_correction.py index 8f285bcff..4d4bd8f60 100644 --- a/modules/processing_correction.py +++ b/modules/processing_correction.py @@ -9,6 +9,7 @@ debug = shared.log.trace if os.environ.get('SD_HDR_DEBUG', None) is not None else lambda *args, **kwargs: None +debug('Trace: HDR') def soft_clamp_tensor(tensor, threshold=0.8, boundary=4): diff --git a/modules/prompt_parser.py b/modules/prompt_parser.py index 0e36707b1..f3c6b30c6 100644 --- a/modules/prompt_parser.py +++ b/modules/prompt_parser.py @@ -71,6 +71,7 @@ debug_output = os.environ.get('SD_PROMPT_DEBUG', None) debug = log.trace if debug_output is not None else lambda *args, **kwargs: None +debug('Trace: PROMPT') def get_learned_conditioning_prompt_schedules(prompts, steps): diff --git a/modules/prompt_parser_diffusers.py b/modules/prompt_parser_diffusers.py index 72d8ae3cc..6ec9df66c 100644 --- a/modules/prompt_parser_diffusers.py +++ b/modules/prompt_parser_diffusers.py @@ -7,7 +7,7 @@ from modules import shared, prompt_parser, devices debug = shared.log.trace if os.environ.get('SD_PROMPT_DEBUG', None) is not None else lambda *args, **kwargs: None - +debug('Trace: PROMPT') CLIP_SKIP_MAPPING = { None: ReturnedEmbeddingsType.LAST_HIDDEN_STATES_NORMALIZED, 1: ReturnedEmbeddingsType.LAST_HIDDEN_STATES_NORMALIZED, diff --git a/modules/sd_samplers.py b/modules/sd_samplers.py index c4c40c30a..5b710f449 100644 --- a/modules/sd_samplers.py +++ b/modules/sd_samplers.py @@ -4,6 +4,7 @@ debug = shared.log.trace if os.environ.get('SD_SAMPLER_DEBUG', None) is not None else lambda *args, **kwargs: None +debug('Trace: SAMPLER') all_samplers = [] all_samplers = [] all_samplers_map = {} diff --git a/modules/sd_samplers_diffusers.py b/modules/sd_samplers_diffusers.py index 024e88524..f61d7ddde 100644 --- a/modules/sd_samplers_diffusers.py +++ b/modules/sd_samplers_diffusers.py @@ -5,6 +5,7 @@ debug = shared.log.trace if os.environ.get('SD_SAMPLER_DEBUG', None) is not None else lambda *args, **kwargs: None +debug('Trace: SAMPLER') try: from diffusers import ( diff --git a/modules/txt2img.py b/modules/txt2img.py index cbe55a4ea..deb87caba 100644 --- a/modules/txt2img.py +++ b/modules/txt2img.py @@ -6,6 +6,7 @@ debug = shared.log.trace if os.environ.get('SD_PROCESS_DEBUG', None) is not None else lambda *args, **kwargs: None +debug('Trace: PROCESS') def txt2img(id_task, diff --git a/modules/ui_control.py b/modules/ui_control.py index 8a7bf8a99..c176836d3 100644 --- a/modules/ui_control.py +++ b/modules/ui_control.py @@ -15,6 +15,8 @@ units: list[unit.Unit] = [] # main state variable input_source = None debug = os.environ.get('SD_CONTROL_DEBUG', None) is not None +if debug: + shared.log.trace('Control debug enabled') def initialize(): diff --git a/modules/ui_extra_networks.py b/modules/ui_extra_networks.py index 9a8e6d308..6bf5644a1 100644 --- a/modules/ui_extra_networks.py +++ b/modules/ui_extra_networks.py @@ -25,6 +25,7 @@ refresh_time = 0 extra_pages = shared.extra_networks debug = shared.log.trace if os.environ.get('SD_EN_DEBUG', None) is not None else lambda *args, **kwargs: None +debug('Trace: EN') card_full = '''
diff --git a/webui.py b/webui.py index 6dcd965dd..76c1cb0a7 100644 --- a/webui.py +++ b/webui.py @@ -317,6 +317,7 @@ def webui(restart=False): shared.log.debug(f'Registered callbacks: {k}={len(v)} {[c.script for c in v]}') log.info(f"Startup time: {timer.startup.summary()}") debug = log.trace if os.environ.get('SD_SCRIPT_DEBUG', None) is not None else lambda *args, **kwargs: None + debug('Trace: SCRIPTS') debug('Loaded scripts:') for m in modules.scripts.scripts_data: debug(f' {m}') From 10c3671276713c79f22eb5123dd8766014b58a48 Mon Sep 17 00:00:00 2001 From: Vladimir Mandic Date: Wed, 20 Dec 2023 17:37:31 -0500 Subject: [PATCH 095/143] add themes --- html/emerald-paradise.jpg | Bin 0 -> 185142 bytes html/orchid-dreams.jpg | Bin 0 -> 161037 bytes html/timeless-beige.jpg | Bin 0 -> 163654 bytes 3 files changed, 0 insertions(+), 0 deletions(-) create mode 100644 html/emerald-paradise.jpg create mode 100644 html/orchid-dreams.jpg create mode 100644 html/timeless-beige.jpg diff --git a/html/emerald-paradise.jpg b/html/emerald-paradise.jpg new file mode 100644 index 0000000000000000000000000000000000000000..73a4b7a5f4cec41c6403a7896b079be302bcd47c GIT binary patch literal 185142 zcmeEuXINCrvgjIe&Yi2{l! z2nb4${MO*sz4tlihVS0*y+7}?uI}op>h9|5>R#QwYB-%dT?9BZ16`c}z|auj2LONo zfIz4KY!HHg|IWhcIs@9o1?7wKaY1>qNeGJqvTC}9I5=lSawdokfX&FQ&4dL2;QyaA z?lT(2(8xh(Mc*?*Tu=Xpk8rz;&dSN3(LRDP1^E1IgV@vC73pj2=j-X`3nFnbEG9m_ z<_;*7#~Ft%(iaX#`XMj`42((DJ-jh_^0Tt$u7Ma1HCJCu84USnop5g>C<1PwdD#L) z{cP-K0TXW&%E=t=>*s0g;06PUB!CIv4WPh(P5>KV4!{9lzz^`mR0R-u{^kaAXVs{A z_#*$1h{VCq)x*~nc~&JzCp_a;H?-FOsSO286<9)G^k!N_wS6Evyb8LjJh{c{m%^2_s@)hgNM&=ijlLg|L=?@!b9VC2Grx9Rja|= zo&Qk8Pb~pv+G^^cG#FC=%pBR+98rD_DyRia_AF?7BmW7f=JC&bYTk|(myo_%oMs-F zvHcy?JRI5n72eFp!}knt66m2~_M4r^9}e?Hd22v@p_m?=32EYN;`7TYFffK0Ofg~u z25_gJ)%-)V7N8CPt7gq%9zXFW-Y^wwObus&0_KhKv~q!ig9}=yE7JLw`p7T@Gf*Zq zl&>!e;ekRr|51eG7X>52U+|>A@Mf;gE`KEu|0000{pQEa73}q&G5|VYikXjpq-XPp z=Zr@7N26e(eB+PVM|y_C%ukjxPWr#+vA>_?3D06Mf}C0D1&nQimj2VaG3YZ1cFwRm zfQXY5Nbvw;yfX~s4@}|RoZlF6Oew$yjDPV+onbJR{|Dx*&L3bNz|5@Ee;KepW61xK zbo#%co#}!AQ}?fVh@}F~OYr$aZyFfrjD}guRQ^U|0Vx1*w&tDH!+^Cr3LpVTz!iXj5CU9L!~s$8kpP4MF)$T6%d>&7*Jhbs*3W9ealnU;{hwS#*`1JP9AgMXIM)%Z(W_|D!aKhM9R z@KD~a&aS^#TupOK?Z#&~untnFpD#)qj)Z$dec_HE0XEreT zzukXqKW~q}y>`x&|96a`kMrMOO?XfbUkj-7-?+$Na1Rd)c%ZMYkB)_*!LP-S;1~LD z%!Do|?_gCASLa`vBLCTWonL6M=lG6rC#c`qVoc}{_xAl4?3TaKe`hCla8^fopuGR0 zT*{yIt7-oxfJFc!6cVFrLSK|8_)_-c(1N^GGA%Jzd0g#>L07sAqq|X3R00{vB0U-eiAt4DRF%dB(EjbAZIqi9BYFcXQ z^OPiK;g95J<{x7SDKRlA87T!B83hd)85s>GBBS|Pgz~>r!Rd2=k^mIG02{&qU{OM_ zDIuqG;9Mm-?S?3WQsP3+0w^#hU;_{=99%qn0zx8U@YV5mA_Ty~{zaq!AUF_gEF3Ic zd^`eNY!V5ONQsTZP9=t`Y6_+1@VY6EN0V0gM2(Y+)~wS(Lee{8R2`o?-F$O#3+5w5 zS5zCxqjB59QS(Kyuk<H{QS*|tFZ}&)5&mNdF8nFN=`=useby!= zpbWeh2&3!GWnsKVv3$V(UYq3RQ>SkVz`uxOvU!@Ykq%%9qF5^ zT^DZdw>5^lIyE#9HPYcLv01k~ANlIBBh68N)y-z&L(_g0vvcmieL#Vy0$!hPv%+Q{Hj!nCTi`gVd?Z#{RVO-=oa^SozvG_J|q=!z%GhQ~C0CPHCScEa( z%iBIzw61Q`qMrkAL)7DyCy+gIMd8^V0gU;lK&gcX!UzzHC{jwpX$>E#n@&%weerVl z37Q~QI_PNesO-eyK3=_&w3D~0m&4}dNsh1is`tsiv4K^d`a zsEP?!@ox-D$Nfnw`<9k7=lkd`>KoVV>`z9*zD9-L?%PRh`4Vcj*>?(v&F|w9uOB>c zskh|aAw_NPJ{fCsoobpo{KB+7`*`{B#QF$MeTd%-W5y8Fu61~49hJ=gU zE){&LzfwJ_sLY~*Qe0j=@(8`r+x(4vh|1)UB50Ap^~@vt@?0%Bgd9)mDD9Jm=GdF3 z0I6+cXovQ@I(_Ff-s=AThxvL{4<8a{NqubSRQft9vzN~Mx{2I$plzq>QSKDS7%8xp{2M~9Ks>f&Sr|)l9k+H`f z3?7gzQsuVZN>L(-^40X&xTqjdPsQ-H z1UnA@;6-SEVH*3Jn8r9ipH-WAliR1j`LWgqyWyAOWo%|~aW_!)lekg!rvP8gG0WF4 zygj$v3!25IdCC#YN@Ob(7iknLXyCjoYo)146ku;U#prEH*)|K5@#Fe0-rh2TFM(=-8Ky#-q2M zWsQ6Z^N!HvDz83xG#YG=((<>o7Z7>UWHYxhr(j#>9{P~=Y5mBXX9;$k<>SN+0E+YD zfrfI$Bf-yOmE_Ns`w)uatuXg14L8)|ac<5A@*b~pU0R(^;S$%PdN0--S$_2CUcj4^ zo2#kvq0nN(+VA)spQ7BA9v#~WRo1N3$`&CvgY$V$Z5WEgCYBE3zLupWCPe z;^>ZclpPP{eF_WaJ1E`Jtbfx~@_H|4^3(0rP?632#c)42*oR=1hBAv|@d@d`TS`k? zyl?$E6Qw2!a*lHRkM$2SuNc*D=lE~9+i-s;bHDm%C0q)gQq^Jc>`n7_j?z#o=XSn! z-~N1AlW^T616O0<1=Rx9y*F3g34E;E+>^gez2*sd?0i_}YTCNF<C*85NfUPKh-6Xz$DR1cNVk4@Jo1kF@D( zH{O;-u7#hX7r#_Kymo=V_0tjKUVqA}6^vqb(hKYP0tu3dReEFkWAT-C{s;8C$ytXi zHuzPm2%IjX&v%fs4?cF_zQrF)UgZ-)*Psn6Q zUbq*qd-8SEG$ssudC8mt;<^Iss03Tv_c`>3eMj?0FL|B6vVIT$&XrL#5!3H`H|~V* z_Wkzw3yZAXWxB?w9qwdiCdG&8-olI*tP@TF$&+uZl{q%kGv@j^Y_SA)snqRSj31mI zY+Bu_8<(0KOe|tz6iV3B`Z{cTUNL~VDP|sCmK9gCcbKRhI$`lPIKTgxnpQA9E#rB7 zhN{f#t?)TUluxMljX3zmSuZxIVrY;+e@V2-0AvaElBPz z9HyC*^%SqAu-K|uJs^~IB;dPSU<>`?Dd5s4J$EH}hHfM9?JISr<&#t3_2rbxq{3YZ zfj7UcoAgY$fLF9Y`rd%IoP^ec}VnYRU}4u@=-8(>gq zYhv}oPw9?kj?&I^`VW4WFu0K)Sw*y<2l~QaFAn z3lBz5*?@gwXVpindxdnYcLXz8Chs+D zKPg8plb4``vl&hzx*D65_gR}GAum+j{o@sqjBkhEdGuP7Se;5KdpukFQOG2E_PRG? zNvf*mS4Iw{g~Zx+X!%KLsq z&1ti_FcIu4eeG)r^K9rjaHn@7rhe_auF;;FKXI`Am-AuhnLPD9jrd3I{_4+CcIm!O z2isQpAYtvUxOsU=oE??TNV^yj(>A)8QF8(IM}E;8_&$lnpicbfl4jN>$4w``McOIy zC(f=9J&x6f)oC3kqmEz}65Wo-;p3^t-z8pfQxXj7?>$3=ioZP=4JLAFc5FRjoYBC- zz5<1pu+DCu0^jr|$S0g(at+I{Cib=2nL7SDzmZ$+d#!GD?kR0P$BK!0ZC{iwjR)hC zQ${E@iZ$Zb=MTe<9hzEE#*ZY59F`gG&3F{e2ufL-+qOiX-1$8JtXEmL-c z)^RGfT$)zawY^bTO~$gUFniQ=3e40$Nl45e<2@+Va`)nE;$L;I9WkN$G`ZU_f%MU@ z*|u)m?^r$s-jCKj3BGiR?#-$#Jiy$%ST9nj@j?47m+7ystzlKir@$JIfll&y1MTgQ z<8$x3g+CR%`_Lt_|J2m?Al!UK7BzIJ3?FZT?pO$@Dk-GDVk$zK;iEFB8ho2SW8@4H{p8|IuWKB(Yx_S-v zJ-uX&TQafRST&IexUnc{mtW<+61wjq0XMRt2i?L(}T9#GRnb9+CO>@ zI{ic{8{WVE6t`0a8<SZiu*0H z%g;5lw{sGcN1IW8Ndcd`Y}efz1v6nhdmrTeLrBxDqwq%&Es2N9Ad3WlHG#DMv!*{OihMSL$Iae|McpF~6 zc5t<|8g}w_+jcj+;lOl#yWOx2IoeOjC(hsXjKH&BUT#z9BlNq;WyLFlQ%$Jjik)NTXZb5{InrHc5gk`Qe&-G6 z57Addsc&~Qv>Og>mxU_V_tgf3>>U@somx4$bJAS*t;1!lYJJ{yyX3&Wqq6o|xYU=B z@Z95(P4`?~mSQ;h`|bL${z;`byZPo<-X#w`J%qhE1tL#@vZ|TSUjvHU#^w$b=k+~S zo+-999&<;f2TYe2^&RJv9a`0hO9z{FEBmjX0{1)6Q>TEPY5yVJ*6vj+r}=;y#wOhZ zrTiwRxvG3nKjf!VKW}W<&n_&iz!-wLq9F%V!HZKAc!dfF*ual6E+Fg+K0apO z{1+U5Ubcc&{%qstEiFEHTMJ%>K!DRl(lQrcUr%`v5u}eW6m#<<3_~GA0->HFV#1;# zz(wUiPbkbC?#t!?2k#vfIli`aaIm>LDsos!8HyTus>5Aeb%MR&=D|i5uwZwXoFj*_ z65GW<`9Opx0`3cC3q*JzedGfbInFAV2Vo3agoEvj#n)Ytn>xxH&L$@; zCIk}|6=Rc;6NbtV^u6TCq^>wqIn1nvPxfNVbC;E4Th%JbJp$=^l(CfslG*@*sX034rx z&HiiXF^ph`c3=yri6*teC8%gb-B1QBFuoLQ+OZRtoAM zBnO3x%gRERTbBikkFEnRF_dzmDG}z zl#<|JbA-t|p}Y}La6nuUP-nP^n1&Pky%TgL3i>fO^AKok9EfHIYqz zStFacq_7Mduazs(5f$LW_p5H03l!-L2Nw_#4w3(HXa0L8^Ha+Ihn*3@EE_)uN#q~L z?~L}BB?j#K846stFyQYMQ|Z^j_V4uH8u+&c{;h$3YvA7+__qfBKhnUTXD&Dr+$RNq zC#uu;B$sv7)vubE8EfepXr3JqNpuWckti(Ctpx#kuz_xBY?rNV*zkuzw-!S1d4;P7>$8zz%F>kQY!#c?O;NTCnlL_}IWRqb!>aJir6)>nmge z9)zLZj^HsB;R!_s0l?2bV@d&37~QggN|qLrla>||7smJx{R;mn@fXv7kz&U7C(C!! zvoV9v4gQJyv+$odR1xUU1^TUFr~Zj^xC;PH(ExCM`cE8J0eIIC1ptjBf9Ve$#$Nt% zhIWO+gfR;J9sWy(-<8IU;Fi1a({}y4TfLSab*E4r7 zK;6uO4d&1LOlGKqc@Hs0EsUr$8sr3k(3Sfj7VmumF4nz5tuRE^r8X zALnh*ns8N?cL4dM(zLi`}tA(4f4RU~mjYWb*gT;)+gC&9`i=~RCk7a>%1T&+D|R3D z81@488umU89u74Q8;%f;0*(&OB^(%z7fu*X5>5_I1x_PQH_jN&BF-kx2`(uv6RrTR zJgzS8Wn5?60NfbdEZj2O2HYOpH@F{hck%G>=8bWS>w6kh2SOO-N$=`_Z)8& z?*rZrK0ZD@z5u==zA-)&-xogy|1N$teg{4p|0Dhn0%8JI0&xN@0viG(K?Fe-!2^P4 z1ZaX!1c!tagxrMkgvNx9gh7NUge8Qngd>C>2@i=Vi7pT+5?K(r5#1!pCVEKJLo`eD zotT)IgIJE(l-QN{25~m=W8!||MdE!DDiVGYbrL%gKaymUa+2pHQzYL>NlAG~RY+|} zeMyr^D@nUZ-;w?xqb3t3(AXOY*DkC3lX5K{0^ zs8c{G!YFbnS}5L9>`+ouicuOz|UaH z5WtYf(8I9CNW-YW2xClOtYw^L!eZiQGGhv1DrOpF+F@p4)@Jr(zRTRjyv9PuqQc_F zlELzfWrdZRRf*M=HG{Q-b(M{lO_j}qEt{>IZIhjeU5DL|y^wvF{gC4V$0d#kjz=7G zoJ5?moKBn>oG&;xxmdXjxvq0naZPg*a?5hNaNptXgyU+KUAB$g#--SPye?$OFKw7|6;J(19Ag-XCAX2bM z@U0MukgAZsP?gZ4FrBc0aD;G^@P-JNh>b{^NT0}wsFbLOXo={I7?qfwScF)s*f(*0 zahQ0n__zeAgqB3OM6<+}q<|z`GEZ_+idxE0Dpu-+)UmXTw72v_=@l6+nQJolWZufs z$ePF|$@a_P%BjhP%RQCbmzS3Jm9LfmsvxA`uJAzN^Ti7n;TKCTeo*94gen#)E-0}n zT~jJhT2N+JhAI~+zgOW@aa1W&`J~FH>ZV$)x}hej=B?JCwx=$y9;*Ia16xB|BSB+W zlS=cFW{&1NEe|XAE z>S5*a2tk0jhG;=jA>EPPC>B&8YSdG}GsbhlOWrHfYtvibyUYjTbH%6Gmj>KS4f^r= zMf)xKEBWX8{|K-Qs0*YFL>v-0l{-SNA#kZq9Ne3$EP>fNIpmz+1bYPpZ@G2M&5w|C$1 zJ~~e&?_oZ3eq#PXfos8Jp>|N8Fw&Dy%BMs+DTH>en?IHO&u&9u_{LdzAPXc_TQ`rk=(W6PRcLK!6K*Si%KkLBowhyY8PT(-XQv&(9XroG zpRaW~cYb(r^~Frr<*xB=)9&FO{ht0_t=_Ia)xM5?#r~%+2U)6PG3?-`c%hm~@={JmoR9 zHSIrrICEnbcQ#>;VlL|))4QU1zWGNB(hKd28jFMP&EL;_fPMI~0A~IqMVa zr^?UbpP#O1t-M~fUH$mQ^UL8{%sS=zy$#-t`mZWq2RAQoe%$ijI{lXPo&J0Iw&eDU z9h04RyNKPxy@Vh1KPvWR_xld44n7?Q9N`~jAM+izp6H*KlXL3uAtBk9ehoOUrup1y#yk5aa z(i5Lz(wls_s?_kM9z0*s>7WWE!mfc#5!fc^BO06RtO*fiXv+rber`UT^pG0`@~Nt9 z5d;gW3##ilXR;DiLf_odHOB7Gqm{%*3GTK1=;6iB3Yj}{$iN;@+(@}x3Va(0DUK~M zyAkBvKHtlyVrD}(g|1(b0=}qtsbEBdj01Vs4+RU+V@Kf&J zT7h)3Vd(;7j<0F14~B1%o&p}>S4jN&a#);{f5e;ujmi%r?ScxIVW&VG>JWDDS?b@U z|3n)_k3lWtuxOa{pT6G5dWaocR42vqY*s;_JU1nvGp0#Q_M(Qhk8MyK`dVkkuH3$< z?Ja5LG;3)8=<6n$Or`p^YqbkzYMO3_CAy@2+cV`x1a5cI(!WPg`pIT26_X?z>STu%@B2?T<&Y*ZVqvGnN!C4JYRdJqdD$bT8RmqOODTumSDdZV|Qb~MZUJ7c*scqnXW>HN0HpHr5(%cue?D@%1r&9KtMXF>Vkj&g1IgG``y6DKPRSk^P%F)Jts zEq$o*%kUHCm^&OAcWfOb{nIU)nEg~q3kjL@GD-pbP>903qUgM${JS8Qe53)#60+LX zEAKWr@gqU>bk=LX%-S^zUfFcAbd`mIlJB&H<5_VTf<|T`Y3j05@rnE%*Mn;l6Mn=E zpzRHO@$(5@C(2e)7=HC~->k&54MZ^YzMMO(g2SgpJ~sMmSt z@*#RG0|@{F4=lq3K8)6b;8!zGCeQKnowCu z3ooEtU^1!6<*<)U7w3vuffW^7Um|^P>LpM{Vsm6IU3_Y{ zJQedPQf{XwNT|>M;JQp!2~K0Xon%F17+pKWwA#sfcTs_rI6ZyX(9NCZyJ(4QJd)?5 zPEv`q`YRn>E@@plnI#vp8kqM;tig3@Jss!>mZ}p?>_wS~`mB4er_hfBY4Y8UgjD?e z$uy%Ho3?x=)oW=+TNKZxbxri}Uj+)7)X;*METMj)_RePLx-BNLo}BWqAImsGH-Sw9b)X2P56ZjKo){ z=-FiK@KT-T)-+FH+3`q`rroU1b8>eYH|A%)e+&9CT4r!N1`%DRZ}4H#E?&>86eyva zQI)rV@Vo>nZd{@fbxRSVgbjO>G_vWNrBSnO2iPR9&OED^{)VO~<>)h0ZGO^BS0HSf zFxWhUY>If*I(I zzhH*=su#(f5)HVktE!m}gV|d}J6bnsUiGY*!5WYLmzItDETHPYeF?fM1zpq#q{*R zZ^c~Y=`**lwU(&d#SZLo6YySI_OG3Pr2-|(rongs+qTQKAR#*c$jaq zt9Il&+302Yo>-AF*W-t?r;}=>ZC0Mx`y@Ky@M zNO{>CGN9MJ#Gxyo?jW!^?~49#gRim6UB%Q4jm)^|N5=8~MoHWlGt#|U>~x@jsyOll;~p%qDW-A^y! zuGxJ@wi=Jb3Q0POT1Hc%cF@2m9KLhk&QdvIg5nV#q9Q(}C-)aH2UV0k>{c94eP zl)&vu>`uKD!0K8EFCkY=T3+IlO+_`5p!SaZ{LH?B9@kHHG1aQql-AH$10Ica?D90F zUQgO2wtNOfPjia1-7~8I!z{{8HyLO(^8KS!p zSSp(%_=NZ|WHnUD*`b-}$HOB(jsk>ki(SgU&B{UVCai5&oYwQw&xg)dT^Bsf>9{=D zwf1wp!srl}pw{DYJx>{@It+_CHwQTqB0r2f*tRaAAYEAnKmSpvZ)_pPq2wupfZm+R z{sJRCB8jltK7V@{sry-08s_{#j#!z~`>8~Nep|2yiAg4%q(z~t1Os{WNk-xG1zoMe zlG|hkIUH>vFo*Ru+xH}P(l9T0FaqJRtD?n zQ3TRnW^dbbU%MzNr!y_8Bw7Z08NixvugjZ!A|#|Gw3K>Vs7IMkL`eMwX=60EV_nTUUg*7x|rA{bu5vErW&PR6EnlsDXHSBQpi>CpECdEF~I8e5&gl7Qzdr zjX8P^yHQt90qpG(c&*srg=d_%)M$q13|?34pC@|BS85pbwY#BmTphTN;^x-wC8e2@ zqV=WGoWOVY5bDUDYiJL@&I?CM-CpFEFe@{1qdc{@aMu5Jh#9;+1 zyVYn&Lb6_5H!)s`xVBX*AJyV_xZ-gRwS?(ST^0<*>4K(lJ6pqE7r;^=!P zTROUz8=BMlqMcOlyC)i^b-QBKX>sebtU_ROD#1mTvs2b#w>o1ZZ#7Y-7nOCBAvLN9>)2yR|gZ4wmH?us9SldThjW8FF1s4PLk!l&usd*{q+Lr`ExwF zk&TT<;jvGblDGoXtQci0Mf;L9nk?Vb#lI54rz#T6EirKKj%PtU*{F^aErWoVsq^a*ZJeS=ind{D+~I^H;#piv#b}e@dgGTc36o1PDIeeU1Zg$M zXNmz0(?Co9yHG8J+wMAy`Zvdp<;>DosJnPl6FE`w+?4X2X* z()cv=K$Ty8X~q{KUE&JEaq%wP`qnEV)J8e_j38#?K3T8wUX z<2)XV=C<>jdO>AwZcRcIx3bY?OQ6&LKtD#FX~sBd&?#m;@WZ{-ZC{SQMRAC8ns@^IQ3WP49*9BNDJeQv>bBB_@yVJg#e zaufmns*ZfOZ-cd~&1dZ$>#>r^=<8sij_tye$>T1Jc)4AtKzYxBJh^7mNycyS0>ig| zbZD!7Zaf9hv4^ld^2NE9T@?9A{<+Ve73t;=Ou(a7*y52wlhP3QJCCvV$ISReGvH6E zSl3f#m4AtP_5TA=XZ}g6eM!%@a&7UO0;KVfRM?k0o3APDsxkS-Ol}poX5drY?BrIp zf6XK;V$Hd%EWdrIaAl~)CLyIe5g|xxUTU^z^FI7^6mv(Qzx1Jy8^U5;oBM^WAksogU<4_VMMN922G>CuK`T zC-7h|cJSnjWC6=R6h23PpTE{ORhXayt_iu;PaKp{VaMl+bG__|V=gQ1&7qC!)z|4} zd)u>z4AciA<8L_}o?Iz?_MTpq)20hi5p`kuQgx~^5n8p~YA?v0YWZCk>R}y1_#SF* zjJat`fm4c_zs%Zp5jSq1X-4b~7URGc6-|0+OorMOGt%eOC8Hs+s89aM3q$LMBez0` zn|Ej5KADi)Bvl(*jWmowHTyf9Q^jm8KXtRFKmF5LkEHGs za9ze4eNwxL>q;^A;6oWabgtgjS6Z%az4R=6z~k{pqKh!A`U-b5x7B&JX%TGngHLNK_j>bllSmR=c#ZSUyn65VR@ zxCB6&GQ?@y1iwvscV8^Wfmie0oXM=5H2ibUY;_b%UeC*x>~4o|@CcKccVT+ZwW_jg z3}2X+#XG16QvR3=dydoRHQCldQpqguUS2C2=qg5;^r9x)Nu*EI#kMAm8+~724;^j2 z!r9x3cJsY$rk#E`vOb$fq&0iOH2avAlU{%z-~&~WfD%IhVR8>Ae`zK+f8Mu(v7_4HxzuclJ_C`z559YsXG4p9d@x}KdnYa+mCm%y~9YS=|lq|0zv}Rt{rjJKt z2)kWk>QAjDXzkF_YcE0@72j{#tAgR)ntFV1rjF(vC*r;I#`~BqF*_T__pm|e)7=Z4 z+&j(P<1KFrE8d@RU}a#{55jA*>X#K3Dy4oscmk?hmvKd$MwVRUhB(` zd!9x*>E~!T=WJ1B(9V@o`aa6lrRCZ|2BShhT0Dim_7I@_ucu>@>okwu0yhG zo>yNy=Njy)pUzmla<^!Sk8kvSME9qlY#ot<4DHrpip9H1JrmyZTE-w(#ol4gmu z$Wo7zndJ$$wh;Hn+Uj*7q%-g$)Ko5MrB&SQ`m(Fc$fEFL-}W7k+BpOG_A(xGd|Gz5 z6nnpM7fLp&aWC%9ocS~8R!Q~6CW|{(zT+#Lrj-X(&x-w6{WWH{7WE_Q5j7n#FX@Nb zSE$Msx2ijDi}jfpbuc5`aWx)JyzMRJrx@}wQ&m`bC~z)kZmYn_K}MN%$yvu+pj4H} zRxp#TN-f=koYB7UDQV-o?AiO&V}!aF5+0QDKUOSyJ6DDi+R+?fzqoRu>!6z!VCNyL$&u}%NRxqkK-U<$_%=j>lTwb5ns|>?W3CP}JQLc|vb z^2rTjnIF=#V|qC4RS#d}7Z@gZsP47Xc%y`muf?oqEKH^*dVP>acvyg&=Z>GT1tS0H zCnHaV=#cyCH}e$YcPAMWyIJ@qB*BKJ`Q|0(4Pkjh^g^ z)T&VU_bW-Em6UcpfN1mlvC7(0T9Bc@dRUpKI{npTy|PF5eZpGvkRX>qp_6 zl~(a4@T69J*ZCnm3HVupYp3vYy4BOhNYAaiNj|CHuw2y**n6`Pm63;fKb0et*nFjHmU~;wayM%l%koQW zVoqnZowa(n+)IVJ8p1w|Ryag`Op`V&dcMxG%)%^--5;;qtd*`NZRaF{bEAYlH_M$bw~K3{DQQH{ zU6;+MSbZ&6^&sQ96}5}{;cU?LG9~p*8I60Aif?WV$wgagW=cM_WOWrlDkhOu+Oyor zXFtXXgkufh`Z4H^!$sS}CR?u5iIgk2yE-*~y%&Akh*LsYtpL@@G;?;oR4>(~LOM{$3|TBh!!lAkFevWG&k5Dc^IgazowYh#M--|yKfkuJ{QCueTzqQre(Bw zL547U%JSajAVaZ&J9yDwbkV8leiqH+QJ)R!I)byry=ABhq2E%)Y!eBa9iH59Z5~Y) z;mqmk>5)F4H(vU5iRWqi0G~P^n!;>8fe2i2G~uDEVrj|8canOM<4XbGoR!H3jguIs zy&V@=yQF*PZS{!MA>P8Jt_XW<1RGn8`~z^EZ(3Eh8-3PDCgAjdsknD}IR4X6H- zfy#+AuK~Rj`Aq&LUJs9VopT}XNG7d`Ij>sN+;Bh5S=rCE#f_Dwp5Jqm4pVJ~;&E}? z`BQH!=iyk}=u}(W9?UU`Rv1NWjJ3qw`XF|@#DPmcQJjk&(Ql64THCKoE6(6lf7s_f zK8{WevIT^S&wIJARTFM* zyce|+ni;{mXc zte43=RyJx+6Ny=?e*1qmQeR+78c*4SC6~M=3k*J6-{z7`^ecS%o0x z1EK)rF|dHoU_vg~x7t^Yz5(uot$u*JUw^TF*f$Cnd;yFeU7U{TzlU<9fHyOd4-_*=%Ka`o74BlMtWaZ)g{;yt?4V4g?}w8_m!RkGTUFGJ?1{>t}9}os>_)u%X`S zY%65)j6|QV8CG~W!`0JeV-nDYspjr!6(!6v9aOxkvt3?d6MIt9wRBM#6*xGv4;J#Y zqagznSG>jx$FovZxvkFa=k(mX7-(~&$(CF?s;2jqSrzdtBb6|ui4%Fzh3HizACM^? znmc=iYmQ^d=IHqcHz3=%La+kw1x0Ckkz5IuS>UF$Vm-dG09&a5=^Q^!^sMWy=FrfZ zTmn|rYdiJf#<^L2Rc<$Kw!|B{97oanubG+RzR?}1kP*DyqKP1f3{LMA$ZA;GA;_fD z3kvk&Ys~=5Je<6XhLG2yTf(>KHc9iwRnVwwgct5MKwc*O@MvF5f6b84i390sKG1~B zzQ_>1i0g)fb<^P?&)bImtI?my2I5MBBr~a6G*wFFFS!dC*Q$Q_QXorFNO+}T8b`yJl>MOinUfHXh@x*KvFGq16 z46WZeY+id>w(8p77AX+@!q3>>VXK|lXzmsZlJnID3FFPI&@p@w{F=cEBZ;*aMAL{ zW&o{jeqtpr1U5c3h@EHdc=l-rGI^PKgpSVESz}P$6@6m^mwvhE6RcN1!B3pYBmoB@ zpK7tQU2nfJ5cO0V#aZXS{DZ!xE%m0b_kv+3Y~JpAQg#?M z;%P=@{nMz%)rT|+{EnN?qn=#2td#g(zOEsdSt{mU4^$ih=YY$^yzLePUfs!@bLoK* zJ$ZoWyECM#uA|FGD4#VQIPO-eTWV-H!IFzmOe2Q&^%>Z|1OKcJ!SLOdZ!HZMo8^-7 z6oizp6Ji`h_tg`=74Q-9shO9ZyeslHDv?r$E9^n1$X@;sH2nG$&u{%Dv6E!elT>qB zx$7|#Cp_xig)ctcalv|ZH8ex)eQp^d=Tz0X&wa98++Kir;lbq2xC%O)!gZw=8NZiX>5v_})_i6$0Uhr3V^rL`Z{0tp z73<-DxPnFzg;OqY=|P?7Nrv#WYu}AaJvB)?G-NJaaE+vp1-&fWUN=7Qn=ff8P zps)eI#>56T_RJeLWA=`+tJL7969^e>QB}`f8r&b;$0pm9{6Pz5_=DzGa6le5lo@ly5MilJs|LG*j3ELr;XTLy&Bb`*N=;GN`{8SlkbrYp<~ObDejmzr0WB|I zP$4GPvv$NJ1NA~Q1`D?~8y-nzgC2*s|9nOO6cyoYfnxhLC{}-*UBuI2aWSsFdb{$* zXhQUa>)g=bd8CW&(Ov!@v{kY3^5`ILfIk!Kk9t8wsY(n9*-Lx%)N=*H`V3>J3qS#! z4g*jM$Jwi_5By%wWOhGJ_A75$ZFfn&7+PCvwoJr%dTpx1C>FvYdTUG_7Sc2y*9tb(3!R_4_s{6y~W@Bp~B0WuHi_9q7kISV->LDRyQ|#=^|#*A5&SNu-6>Zlq8y zF{$5v7`b{2Fo>*p;b}o=h^ulL)hdw?-mh^GB*Laz$v^eP`+W%2o+R9+2v*7#Fd77= z(y>Ew7#8b8a-wxt03SO0H-QI9)pPOv4dJnOQCRf5b}`ZCG)n7^I}@0oJoqW2(@3HdY?4MdjVrP~rn zF|e@lj1jf973uWi=gyy^y350>UM=?9w$1}ebRWj{nI)bpgRpv?PraBJ$!RJ=&_y2A zO*>wR&}gpal;0YiN=-%gj;${r#EUSM&+xvqXSi^5mQ15_;%(htiX;AHdM9LFsf~ut z5+17JPr`?fCbye(FLv2mb27gC~hmEM6lz7j?p3lJkM`}fiP*)bNU}~R%e1fc>VKJ`jmUU%CLry@dUk&>WIW>k0y>6W>_ya#}9nu<* zqP&)(Flc=*!xNVo1^t62sl`m@(!LDQUB9|PC(R3 zryxvl^ghD|e!iTLq`A{xzEe?zc5p3k8mo%?Hbv3y#fPKiDEQukchvsoBi-1MIe{5# zL|{yHw3x-nFW2pQE!jfkON~f6pYQ}&(NsSe{4qi400{4X>9klOHkMCX;TK;;o53a9 ziu-mzTk&Y~dSyLwG|SivWV(_8S$@j$%~;1Y7V|+kOVQOH5xn55Q^)V@DC5`abc!oZ z{V>D}s$iZITl$q|n+^f)tW6CSE;A&y`WJvH1uf3Zu`oVshFK9G)v{RiR>FGRa?aDw zIk5pj8ZOL$#p${H%$ShntUl3q5?4xa^|RU1ud303O;5g-a+scUceouaW>x*Vt^N`& zS>U?i8BoAm&)foZXQqrAy`wfm<(g^ua7#K6tqV81A5|_ECl~F<4)y&F*N4|iN+9Fv zkBXzWDd93@27_m(6QVKYk&U%_0i{T%2kh9Ug612^%79Vkl&I{SGZfXBev`(>y}8H! zRi2rfnKZFozGq)HLkkbhSx^oJ^CiXy3S~UnIzMPy`>mOp5gUh0}qEgdG^on++%GUgM zA}C4Ujfa;MG$>U(9jmEuYZE6%OvT2r2*0%xrlyaCwVD&!85C)8AHqMO*zP~E^~_Q0 zpxl{JlJD%K#;6jN(r84}va2XLx1#LLK)ofDzCk) zH!KkXPk}EC@=w?n){!ee%{6af8vq+#*0k!-`xt>tv~rlx0c6vhR$Nsyb0(y3WpT9p z`)gSp`p{U1lBgZ>?MX+!Aa$p|s!fxS=^%>K0pt&IBn!yIt{bG>BS=MEdRV#nx>B|vP0IcJ%ir@ zq!eX(Qz;(LV-RIFEO5=^x#b8QsxzVjr!Ku#Vg=Ka)I=G;Um=#6N?rwSH#2AzzxNOi z?VDJ6HD&wr* z>u$8Q3v@sTE=E45HG5^#+Mkbc()z*At9nV+qQ9i4LVjJa*HMf~^yJ$1b1L+B!(FH~ z9YPN*Cju%1#UGZ~BF5{@mjtSAlDy6u{&ztN>i&4?PKoMm6zuW2Ma{8?YY zjp_cGsS#*TI&4wdEb(qLU+4SZqHfGHwd*c7kLgeim&>FcY@5gH1;d+r<2i9@K;*%Y zDARRpCtK3jUI|i|%6UaBIe!=DLIYg->5%q8)YJ^i`fQv4$pl7yq-nC)*>|wda>(eFN=B-sN6{&a^*h zt@lmLK9DklO|7E5y`(;|eW^-$ntD2>s>Yv{SHN7B^V$2PbKZtBXH9J7T7=~ZUrm9- zP|-X^1MBgNifY-Cv#Xp#U9h#j1dZs)XmEnN3tmA)&x7y#6tBuY;PmHTn5)1)XjiI# z(Ao;*;lD7bG6wlY8CoftRp?dpnDQVrUese*DRWGB&XZFDCdmmR9jNIdAGWPpY2;eD zq#hi>KU-Xq*j)Ox#&#F}2TlFmaK^|%#+D03?h49p;aLHjeV2gZdnQdJkHD0?%cpbP z?`raDICZr0o5qs`w*fr+y|HQOPD+_<-%O|Tjr!idT{y*`Y4^~4z>L;izc$+4^D@CQ zkL}gyJL~OZX-1}7e0&5+1sx;tn_CWd<6e4$Td2WYlqS#tM6cinm$KVVx0ZS;S`D#e z8&Hr^3rjUfiThO!Y}kdv$+9=rAH9$V=}bSg4aN8U*;HAR9G~+}{pl{}wD?}dw@-Qk zlipw8gQK?RBn$(>S);)9Fq^Sr^K7~l)UZZ!Xc9I{`XeMJ5xL!2RSM!&R}_vWz%lkx zB8cK5ZlJv~l6)X5+ejjy)v$uQ1JNhOGVDw*c6(Kg?%tfKL;JAR{sb_3+^N$<1bOfb zKMNi;acb9oynJ!t?L9cYPL0fQBAc3#YB=8B9Y}jb-MdB`a6NYy;&RmEAqUI&G>fTXQt*c(ZJfQoRNF+$!Q7^OI3 zJ~TYkx*1X8^zg0rW<|{N$0-~>H$x=F9O60*yXk`B9yFGUm zxR2CBC2YgX6;v_lgJrF*Vli4XYA;U_EY%m_RM5;|ZFvfPn4F;&XrpYF+WpFzlS33> zFjG9=6;j(3fj%XUgM*(F+T6ySc8s?Y!FDY*j;=jURDr~2ZR}&!WF(PqZ-TR|=by=s>is6kQ&N3@>LZxAHnysYxrJ$*TK6ksXdNHzhch+X7LPTCKR$wMyBX!vgz~PS;5iWs9rbvD`Hq`K-?{Nv>mF`@8 zbz@c=v(uRnH$9IpvkQr?+&)%Ov&c4T^eY~WITgZ^B^$HA+BT7F#D-e>Do>KFhbs`q z9IbXw<#?&qUHU;nDkfZ{soI|Qf8H&w<434u(`8Sa!S<*@imt8Utt8Ep>10S;OaO=x z4Id?EnA0kF%gu%dvlA3#J07or(zU(u>9J7|%sns7Fx|nc+yb>7QP2Y9OgMuyJ?XMK zb9X0_BZS!*QM^-XMtx)h&HEFg3qhSrwHieWiZkxVGMp<7Q{UOOY_hJJo<6XK^L6JEA6aSRKH8JnSb>u=5x-=! z7xcoj_X65Mn`7unUJGGON6C4x)@_DVF4BK|K+J`7rMC<4sznJ>CG{OG)+)x>`_4pr zB%ugdSRLUD6PPF#Jf|*{oNdAQ{%&1L46g@tw{NUtPf%yQ05EUgWtT)@=Hh|;zKBDe ziz|iYfka<2eCaXa$e+C8pBwMpv^zs>sbU@(CW6s3`R$!n94oVC^$Rf%vO+NxFOTNJ z<{$LXmd6N90%m3)=cOilubWEY@tr>NqI!WC`{I)ofSf5fUF3CUnH?KwXgs+Z;-K#1 z$>3wywBj?3tXMGJnWVUup2adaQyAV5%9jld=_y4Zs-?(6akWbgA7|HPX-2m08@Z+9 z1@Rj@l8K4igJx|iQicvG?_(XGN3rwe(DBbzHb5e}iOND9t@VqW0M4S2VNRJeldxC} z_K5L4-+3#VR*@5k7O7-Zpa9&&F>dmbplln0AH9Y1m`LFbK!-6!0i>~p@?l`i8XsK~ z%%mslw7YxFmO>XjRl2FabzY;I*b8j@l3_Lhz)?^DBZ>=)ojD>*8j64KysQy*+^Icd z+ofIguc)(`So6mj1Do420MC)#=tVd!W_qbT`WY*M18=Y6UyK`3R; z;CDP;LcSf2g)ag?K;c%AVrpB;cEOZ!bJU9Ikt#_sxp$6ksj^AZ%Bsd8!DU}d$Z9~n zrTe47RN^x?VXCYIcIwf(P1Z8o`e~x9W0@@?L`X83pDrhE=GSjn`XtTflJjOK!Wf%w zHA;Svrxlhou4PiE)Dm}xB$3I8+XA9;Fj(#RRWZ3FdsUthLFc!QU+jFto?g?7Z?i6+ z1^SDc1X;h5$}o5OOuR78jae8@#@>tF#1Ww`Dp5Y`L|SCGcXI1*9x1$d`Y}Dtnfqd< z4TPO3+*f8sP;U0QF-n-&8&R&33MRC>_tk`2-?TK-^83(c%RdRvvHQK%UU|p1x90Ap=D>e$>}BJ2B}yz{qcCV=-uG#+&rSt@P{Te? z*J;2TNI6XiQ$cWge-7OEnVpi|!U9|E<%JF`-E@*YHRpbnZE}9br$NEm`E!w=Q_U<6 zOb3}>G;KWJ*5uG1*q0dZfC|>1=a_o07KJQGN}I7`pto7VcDGB11U)f-gn8^Y_^s^3*C+y#;sm&@7?^Xo?^N`|hr;-y?naEk zhEddj$E=RirLj8t&8yP3YaMB*oR)VfYza*TYxFFyfxy z-NcUpik65R6Acco1ae%rMK=*9%DJb;tvBytJ3e){T`LvCjQ74oZU`L<{Ra$kX z9D=UIIW)h%`m4lwM?hS&>+MS#QGwCA2JqQp^6b2&ht#&G?J#WK0_4N=0*D0;fw(%( z$#XbVt(qj}I@^B70MHr7@I!42ocNP3-Q;`*H<8+`$=eA7KD*UrxsV)rnGrOwoGxyb znza1-v%amIu(sX$0TKdL%DBiBv{(c^Amc8KlFZbY{Zh*{<8CZ|97&T5AsTZz@hZNO))_$`;!mvt$c0ddju zm=QTz=i9Q-TgVOXP0q$2q)2UIx>f3|e>h@|wBYLKN9zCkL*n00>3>GT5)g7?NRsbw zzZ1%;r;B3~S4=?;!ZoF4qPNnUx3D2(sXSz<*iw}Ocvrg(YN7W)s?HKWl#5yFIY$mF zKfPHvCE1b3eq?<;x-JByyXEF22TvY>UYv+rJMcb`yN*b)Sq+1OY@^-t!>hkng|8>P zY2v`h*60`?vpyP7QKTYoJ^@8RKWuyEI+~QTFsU=mF?xVAaMU)i$qE5DZI7FuX(`wk ztAi7>wiJtMdos5G$syHDP289WGmufE^35b=;(iP58)5-b9%qFYYL^})Ct$NTF^uVc z$IjkgeRuMl>fZ+m6#`i&hU{25gH)LWnUhTvGE*~|zU-un!XAIvLhMdimmY2|${D1j zkBtqn^NR|n|`J+hfB!#Kv!QLRVh}i`a%=D#zuLLEm8Bc9(%6U^XG`Ns=EJ|aq z1u=JNGHpaCkr8Qwde-bSa{89GVw)qVv1@~VhmTqtkbRKuQ1}HY$m2Fd9POl1hvLG) z&c2vbuU^ohA;DpEo9aQ9De_c#psM;i9Vc3>`VcJi-UxKhH5Z9775=0B`YPMvR?DbF zH*bV0bk4aWK_K%HjqYqEBE&EM68tDfT%!*v)Y_JdFc?(V95f?k5i(QqA?FJx6B9xr zNzBwXLP44_g#u!$b8T?5G{R2iJ*fIhD^AM(mggcnWiq^Ft4eIx=q-;6n2d+$y~7mv zQ7>10z6MlR zueO&6%@8-J$B(K3Z-BydIB?c<)rL>NB$_KQ$8WZp$Jgi84%5(?BhcI@(Y4#dURwS^ zTyjsEFE4j(KyOl41facd)7e~o`~ZY5dg{6!MK}-yQecsj-Kbt{R*)u8)ZH1Z6RHW# zr`k<&>E$+oV+1%b?Ofgt^RlzP{k&z>FPj?3oz6Z849ZpmW~}O{8!|avzxQ-k#Oo4S z*~D{lif2J8Tax!Qz55*YvB~mcwFa#8rR8xpNpcJfiykO}=QT?SXex4qP-N<(19X@} znyLVb!ZTGI2Rb0@bVL3-UWu}{l6ukR3x(biPcSbVc(;Wqh%~WiUyX-MtnuNVb3~<- zC7k$U*&Nf=YJ>ibGoqYY7aHLdg;n4L%3E$Lsl8IWRZ@*%*M1v0YwQS}Ej$F%!nnh$ zag2x}9wqxNT?u6LjFpyd8`k@z8+p|bwg_}4Ty`NVcnrVhFgf|7aLnRhN)o+W>YTUT z$zSobU&thq@kePcE1oT@Kj}(v3gW_GPG8r%=r9?t|4E;WN>T`BP0M5@1ga3mmQ7Ep z15p4je{lVL7xSWKnxbZYHqFa>f#PB(7Ss*ObnEbI23Ts<1YQz7HleDkjV{!4>r@oPqQEQjXb8rY1s8;FdILRa#7Z(0iUjF~Mc+pl;=b9Cbq&F$^UsZT!bGviDXO#)_i?(E|9M{ta5&L8Qqfzt{1a6Qb@t7=x9ESr`-3JCK6e?!?eg+YalOfEU&r7d zU;g7U_=EN@!*lqM4Ao0iCe=S}%~6@~&)hB6Sa^s&(gQ5VQjkm_oLzCe*>z8My2yL! zsH__K>$ zy2;K+cfK;B&REtmW(WdZ59VhL<>=d-BM(#wHl`&m4IlS}{ zvbvlknee$j1g#6FXyyS1hAj~#Y^DCj6aC=~Nxngx{SC?2)qT0r|En4F}r zb)>L`Sk8IAhS5;9wLB4_mg4Ym=o~X_JCGB_p??#AyO6kx5|iAhj318YDO-%#S&wEw+>#wG58MU4n$*sp$Y%MbYBC3OObp#z*Y> zQOQa&=nl<%lGZByjloRuMd&o@HV;>`%#9>TewB%GbxkA9dJbi%#N2X{XM28MiS`9EI zqe#a?%B?W;%K84))kA#*B7%dZ>OhX|i*)aYkmJ8iC9ylK-MW_d>FDEVg%as3001Yt z`Uk{%Op7F;GWsIGEW*jDoE})N_ofj51)LftyGpZ#r&B?137**U9hD=RjPFKyhkTx@ST928BuwIa$iBr%9fdQJb1c{5KFZpX z({9w4Q=jNUMq7KUg;Zq0-~~MIq1F(`ajfxrD5&IGFZxNJ%?2kjc79ysBIaQ58;Z`N?v4 zyC96s2LuEz~BDdlnY%jGqZC<#^+fvyv#pQm%lBt~+4m&`Vf*@Joj}cA^d+CTWraHvIyvlz|LBvNbdv_gLI!r6RRv|2xAbb zoE~I485cot*4NB(Ode1i%jzy`^La|$=u%(M@0rdTgNjvRf z4X=z^S|Sg|cHt(EPos7=41l|pLMM#MUJMDQkZl(q#<-%!3XV6($D}%z&8tKGpbs)j zcCkbf&{~0uAH?pq(DNFb7u$QA9y6=KBzq1LM*yCVmg2#TU+;_Buwep|`o$NI^cO|o zWGLKOmNmjCs1>i<>v5}j@7iw`+(RFiJkG?968m~u*GzF4)CF0dWU%nRq$owfXSqv$ z?|weO5kaXuq}NHG&f+a^xYJ}+XxsXI`j7)Kr{ND8S@2I~mumrzgA{|Iq5VFq>VXO zme-DiW58TB>)Ix!SCgB~?d!6EWuDo;3rE{D(o5rKvKnP^rCu&)98ZQ=%wCwe#Kmsf z&9(_C<5UI3AJYb1B{kc)ic4<0kFA)!BiLR!92#=npy|URkfGA|LDtrXWa?Yu@?xST zs+8!J*+e$1^+eaD#%B^_;bzpTmzz1=k9nyznY^}Wn ztaHNHJO@spd<>59u6`FUmiS%V>)Sj?e5cK6pC)!EIMWb-48wmes z3e(3kMA>fYUM3`q1167SWA>@QxxkLVdWvD`h3MA8i*60ZG_grP)X-vOLIDHFd8em?(TW>v%CsS^mYmNPElV% zoA0SIq~5z8=jR>1EGyoAo-0t|osP`N0QGD%Y*0;L#3%Z09|dOo9$de;#^ z-Xsy%-h7W=&b(P9O|{}`WX=o7K=#=fg>6+X8N0Sa$o!HqZ{~GW8yOv4hdZR|0)TId%$v}qoqPOg*m z4N`op`k(I1hYzXg9rEkjiKs`L;XIPAYR}Wq8IxPo5zGm&*?4KCwc~wWaXWsRHhA@$ zP&4^n%TngCHfN7%;MFi^00J%Rp?udsA6ny05U;e$y-!i*@(019ym)3u% z{`y9>Gi;JZxS0^2S8QA2BohCDx3smUaA?J)fV=uG>a7#+pt(owv~B0~h(03W#V?S~ zuC9@l6Wig68HU~Nk1r5W0^1iO8PlRNq6`c$*g~2WGZq(p=75bLKa#&>jOV!dc8-db zR@aBr7$Ct7Di)3ub@T2@+N`J-k<2cenbG)gJ8EY&0#0IPdj5Q;nAS6ra+5CsSfuq8 zQ`u-*uIk50TI7i%u z7~YBVxv=GI<@*JW7(>OJXddc zCWA&$&1u=eY*Zfs5XSkEMB?Z&@&3pnrlI-An~+}U-H}P_z?tN#?#@$R8+CC^O zSLEbvw4lUa=&XtN3vm%+#DyD@D)9e88Rt33EGI`B zcDTOGCfrMff|+@C#=C(Ba8o$vMSQu58G2($pGqjGt;DB>>Dh7R%yC%$r;V04NlnMJ zl*Om^%-;J9=T9r$K`PJ{yKEreY(`moBLeZm~%del6; z;IFYl6;R;Ol)6GC?#|Ea#7;e(LPB#~yB`__)-T5i*7adwl9?1Hamz|gsVep!rHv=; z_#pB~_3SDr7glx6?h$3qZ_sKrtdbht*@J?P*7NPQ-_x%y}itbHr-U{PAXSEYY8bTl<2~6-S`pE zVe4|-_`-JMd5^~J2T!FIhvD=mLYWpi{>u2QTyS|Dx!ube7qPi_^;DIW%*1j~52{5o zGubz*L`ipR%R~Lc_2P#YHORT=t0Q2r_NdA`(22h5YDIil7vmSK&5L@)8vM_*VXlf^ zO5y!q4tm!YPOF9ee?6^mxqF$mY9=e(UY|{Q&UIL`JTktTO<4*YC=s~7iJmmmXf0j_ zK-0~UXe@|BgU{5NVLv??lkvl%14x;nyVqn1VJ!x8?94VrTr58l+K3Z-wDgHsMjpbU z#!QJ80t;!3Vvg|MfIENTFiZ0s=mU2%0|Q3OaF*Z#CSt9kBHI{zsH`S&{NX3M1VgF_ zh2NG1t$L{oI> z)^EU0F6- z)VVos9P=VBbXZvvkKzlIW=aCi7FqWd(W#h&^)Ac&{!Q3A$@Ro)j__GBMDP+D`oKD{$KvSm z;#^@t{DxsdBN!3)z!i$c{`SN!o?qxpE-M!r(HFu zo4(l$xs(%0B4UA)OJtMrwOOEMUJIJN7nCgXS7AHOd$*^&TAqHBNI}ye!#DQqvrr~f zmakA~EA>YU(pgQ<_wn@mEU5~=bRupX78XymoQ2o9mDbUPWLQNKv@fNG0>a!jUYu0v z2lgTc>QD8Wogt_NkQ%s`BR>>kgl_$rcs8QNCeK=_T+>>DikU-%r_Y9h`K8@?YKw}f zr&dH`TIBL2#TCjd_3=JwD^lTsXXoRNJ>(a<<*)!6tz1RrkS>Wyhbn3OM<2b1oXpsp z%uUj(X=x6u_~N{x(Y@N~teF>t1G$H#J)Qz`X5$^+EDP`Tpu*Jok0Q+fplQ8wi2S_6 z$ZNA_siTc)T zr$;UhFE6Umusfd`vNxn;vMc4TL@s1v0zWjdk3OzBUZEtZVYTx>Y0gHb?3+F)e_V`d zzl_?XXIR2a z#IwLRl!BtZu&&SN+)o#^CZ4^%G`4F`csFh4K`xX0{u$87J4!)E;VoNn^>3MaMJ6B`+!dLj`hBPC^jzG|i^_FBD-b}&BP+gmy@PexJ;M~f+}c{5x1eK+09Dsm|p8n9R*2TzCiMfcI(CTT4vr5o}EWsCfvUek*wF)UtnhBh=ZUyddu43OHFB~|Blueu7^Nh@s znGTfb|)96^wR$vZ(o`5D27HtTZ_t55O48O}->fzl2L_(a;S&ewM_J6WJ@8 zYigwwxOK@ZnU=^RVyf$;{6LvxN&`t_3l$5UZDH^bZGl5{Gd{O#Y$9ZZ=)t1i; zs`QBiC8GCWuTnX@s_Db3veRMJBF}#8a$D zZeDX;ZJB()$oiQr8Z#+I5n!cb5>&TRadJzQ;ZEypIyuWjKyBd0lnT8>ASTq<5HVk9xS*|>%cWo`sqzw3G>JH_+b8~a#fd8-3 zJZZcZcezAcV95Ls8pSk?8*3+)4wRrZHP?YmYhG1^`QRn0gogE>`YZW{*2~|@%SP>P z%gb*)+`2hSzk@U}m~6|d{F}#$pOc713FY^*8p_lSghrt|fmnIS(k4cjqh4Z+PRT2V zmZI<*TW}ZcIQT{o`jI{N9tt>2qQ|v`MXTDBWE4A@v>s$A9^f2Gu4FH{81&fe4Q&O7 zCkwZj1xX$uxr^SXxo=!QM!s7amHj9zk#NzRRey`9*Ka}@K@fhape@j;&_9p!lg5DF zFY<~7#Eh#vPSUa9D%BxXYFz#;!ucH2`C0#f&f8bwIyyU2rOA4L*$UB(LxL&>yzeh@ zA2QAQzvAHVAwc7%V>JDUNpE>7W0|N==79O4xh!l%9c&EOh)7N%!o&RfAy4tcm%<|9 z(Nh$abZ1^Q9#;7BGgX$s?9_xP=-wCpn`nq_W^Uk*Aw^|pQ!{skN3#YlOOu%-YA+M( z_v4`wR&7R!7ABsD8zt`zYaWnf*Rv^#g&vNuZYt$6`KJPgU>aOeCLFfmZde57KXpg` z@82NiN3Hi%rl%fz4OPTL5tdu4d9!3}iSBJUOP3;LqL^Wyltu;mUf*kB5aKi}CT{qN zTqemJ5@phX&Ao-f{bDhU(4Av#A!<&3l`1F#-d;IamlnX(j5awZ&F&9cy_kNAiU-}a zTjqRPX(6Ju$eZ3#L{{{7zCM|4-!-kPOk?BsPJ5EAFqamfBNn*&fQ(F>5WsPk#@bg; zW=N&}4jb2KLPt&H@pTTouu#1pPqBK)Osf$DonL{F#l_L#f-pson=q;7cpf926u-fE z$TA^OWXbjU08X?7C128!(D?oSYXyvN_z?h*omgQA^7R&RAY%@Eb09QC^2L_oH=JPY zEY;|0o`c&Dj9r;-<3@!gV&^1&nZopQ03-HY_Jsdv%*5sk<_j`Sx;owm{q<5MDbXKO zYNc}PRvgsr39Zdb&yqRd8k;JZ`@10lV3LSdBpTvw@=;8!Hnh!GN9P1Z0Mr6{F_^BR zNo^H)psd*r@N^C4N-w&3MsNiyd`A?fFH*O`xK6WG5rv7$xK9pR3MX}w$il@IQa50m z4msyW$WH-_HrG1cG}O4X`~%f33gAsbqGya3F{08B0&fX+^>e5=tf z^RFCUTkARB3~utYI94v(+6oKLwNg`9KSpqwPSP5tbI#8`sG*dPX=6INY25R-0lY0! zO*SuT0~{W;X^ao`^wwTTZJ#gexl(>%y2YKGG##N)L11!JY95lXL;-ez%5EU|Trvr= zbt|J7aRvtIvb@SRpJZ&NTjA@@nX?}s+! zRixyX4wzG@6RN~929uBI5M&Pjq)-3N!l;PhX~Z?ny5umLfVDg@pQZzJu;%1dya^6O z)xUw8qJ)wC7*(DYpFX_5ZkyEcE7wY*3!<+-jo9K+jrMOVyPggFX7ykld>gDqR-7r75+^x{@z^t zzx>Ao`IUOyYEBp)>CiSio0Ju?TVy(2RAAGx?(DX+5shaTRLLMOOLMM->8@vuq08)N zu67ZUIH{~<^qYh17~STtxo%Y7PikF4P4~?C!AmT^pzrR7aG6Mi;*8nu_qw}J|5}OY zYW_CRb^A7~Q}$KMcd-IRFw;Tx#m_|@ADUNaXk#H*jL-Xja{kMM7+&4~WH5-k(l2J7 zUfw-r7z_ON>uaDP?-V5ZmD%gA?c1cT;6TE6f2*PHp4l19|Kt5>(Rbr+<%h?@E`B8K z$$bBPB~wZH6{RE%{9t*0H@MdQA!^R%_d}pCvSoBlHtW}yACo_;tcsCDSIxHpgO~ra zN&1X+UcY%5w!@8hYgyKI$_{S*xV=nw-Mf^iAJSgP1+YIN!;|JvMk6%GLNem?kJe~!G4|c!n57p z?OEp05Dl2IGgG`L<1&pL!(3$otrO9#; z^2de|=!Wswttc=z&Xy*V^~@jHg}0{4aY)H;C=2zya(UggqvfvKg0SK$7tps8Gux)- z#bf(}hN^(8r8hO-1dIg#tHsmTCH%j@!u^wd|G&;g`aiIe=q1GAim-1@7YV1OvDs|( zBrv`oZHzQiSb;k#B%4FxLRiSrFh7%;k%)X!nR_3-Rl4lNZowHNvZP<>qdyQwaxknZ zP)!Ikm*fI~Luq4}aHng@(oM9(ppUSnOQKOl^zVQ9{y|&%g9e}Z-2Y27?B@eb>>fb= z@DEyx`=|TO{jJ|QpMG2XLAxX?{2sjAvhfGac|C?_6 zvtXm(yM7L7tLx(%*%hffs|G*W%Ivn%jEV@7{k%_Kz|9$2|MT+Wd#s z_=l$bhjsc7?PjdoSmL$|OpIH@fWsJMoe4RV=78kuVC_CZ@a6BDn@(X`0wjJ(jpbc^ z@!s@3Y`GVeOvw(5>h_KAYMeh9sE303C9bljAve|pLfg^CiKZSRwUwNAa?caU=bxOc z23j0UXa~cjwxfORCr8~-ftrURvP4~-V3QR2j9@GKdcxCy$X+d$HXUV;o-w~B{d z=NE&Jwm+;i>nBA|ELIO@0cn)7F4cZhRAY}<5_SEbj_&hrt(lRM6iYn{gQD0D@DudF ze5=~Bc2oj(o6Jm3V5GJpj#n1w<-+4Ps{zyU5^SFh8>SPIh)Y!h$l5*&2zBxAug-5p z2@^%I;N{B^kXP!76TXCRo011-&=CGW`~9$$DbUnNGI*JFZ|o|&f^mI%A1KU!SaZoR ziXZhWB*{%X@fP}TkG0~jSb;)_wCi};Jc2oU~UnH6ZBsXyAYYTS>KKPlXfF} ze}9yHaAWSz=+VDB<-e<42yVqXUcdY4Y&&PM=fYF&%1yD}H!!}jWKIKe<8D>%7hCEr zGBwPzLnP}BCM0NBsGC{zm9taX_B9^(4)&ZFFKX1l@G{0kXDEaoS8JA5l$d7J=a7z} z@#?WnWt`kqHx|SGuGx8sZ0(1JWdFWruPQqSFM6%-HyB!iFAq@v9UVoi9oYBzl+$QY z>CQGLXD%h5nG(M8;fPC8D=P!9uI>dt#6_DY-KP)!LEG{QzTvNQ7Fqd&wypRZ@L1tK3qUcyHd$bBMZ}Sa=Re~wl?O=EBR{RTl9l3#J^DXo+Y|hu@f<;hM zeOin~#&e`%2N}WI7?y3_E{SF8c6?E3djwhMh)?TGhEZ)}c|rnbRDX=A$VwiQ8&bOu ziW7b`&w{$19YxuNqc}XcG(U+Lbu5FEPmrG_o)P>(Q^ietQS|)3BS+r9L#6-Mq4Pv! z*v~iOh~;cQkS0EO&SMie@-$vE)7=x3O-Gi=5dZBrCq+HM)*}_+tlF{`Vr<0x=77+0 zZ~h~wmLU!4%GPIVGjCGcfC>?ur1*W2 zi#`Vjnc_gcRuqmbl2h%N@ev2KB<=l-%=Yud)bG->n((f;G*kWyx)_$#5RFJ1oD-o* zp4IQz?+EXsd5-fyt%WywyH`JLbOyaUDsH_Jo^_?C=6AM3D)5^&zA?emh3N(GigdpZ zfc$0nR7xL}uIO;-y~bVO$v8E?SH*fHBU~|c*`_8IdC8Sd{y&JQrVmO*W@nT5Bgd_)S&I1gWXhdou zS#flyk(iGjN4bGpiI&1J41Ci5pp{_Vvy=X$t=i6Zfj;CBSs0~{3BKBMe}MFAKhVjO z<=dN`4s;|88j}q-%R>FD6C^7HFORnPYA?Yb*#usU<}nRBMeX}=sA^@rJ-cx+*$Cj& zn3Za;jBvCVI^fmY?0&>u9fNQ-)Y8)_R@DwY>>NEG8+iM&$Hx-K+&lBlOSwth_b;3= znuI^LLy}#K>YveNV8XQ|Y4@jb)Is%Hn5- z-1G9r2Esl4h5?h8gd(nG^Z7l}s#%)~)J{_w29FsYf~Ed~7)l z5hwX(2dagl)6O8ZwDe#$?C8hT^Vm>`dp5Ztw51>y#n)+^&2hdx5}%1n@l>Y(-_Z+H zhbdb37~aJ3qI!x`${?ur-hBY}w2ay`iSDaQ^rVSlWD(|L5q;%ZgNoGm{SfkYTgzaR z;P~;n{S2tUGXHvk;Pbue9XY_?nG_ z;wS<5t*E#pf89`1C8$uQ-WHR!{if5FCRESW-AP~lsb5Cz&t(A~MMurFxa>T`@f9z@ zDORYb@_~D?AyX&V7C2iQUMQ6Dy|9P^?Z#R{`H++Rr|>{xx53k#bwApw+{(pav4hdf z^nUTA&zrhwQ_=q4x9IxHY<1IU#VC^ZV@bB-uqBcc07huv@!S8PMe1{(S-=vSn?Deu zM=sPc4fRPjS^2|;)jbM=T{Q#^7zbhDt(HUF_t`G~P!W2%w4PAWtJ2hw0;4rsQ7j{i zaTo&D>fxEC!q_{!RoBC&iDP}_tL3?$A%KtlN&5!u-)DlSU=9MoCDJ^f8@;P=dK(6M z*Zx3OIu-T@jWp&rfgyaXss0bz;t@3IA-=&&^q=D!VzmW{V= zXZ-o=<5h?Ejq+|Ln3;ESbF1E$y$x`7Ra^HHC%*XggT*(1 zK>3g*i-B%1riQVY%$#e%XuI*N+os!Jc@D6zv2mtL)0D>ZODH3QeV*nf*14|8;FSfu zByL_Chpl!*Et9fty}sDihOm~NmZtORpKE3< zKJaqp?i}2>&iE~g-PJsSj5)#uSUc^8RC%fz;10FV&dsss%Me6|kd5gkc2mE1iYp>B z4^U)M#P>rMDY86N_!0^H+0!)f*`0MNAz3New^BjGN_`2X3S7?sHK0-LttS8n@u{qI*<8v8@~qg&LZ9&I7H)EMmg(zixg1DMaSK zh)()X;Y{ z9ozF>uYZTczs4&-CT-`XUpG+xa{q}^k-_}@KiGTAu(+ZvTNDXF65N8j6&Bpxi(p0J z7Ti)e0fGj1*8o8aC@g{C1a~L6yE_E8ygJtBbiclT?)%aAe((INT~&K8oolT<=a^$m z^qr3mKIla~)jxp?sE~H|&2M)team~E7oSTt;y$}xR{f_(_O&plz3o7i*t!_}Kg zlg-x*pV+2-wO0)M*+XN3ySB&%Tes@A6%)u)cqd3PzO#Nu0Rpy!v^C^fpc(S|g(Z5I znI3E36Sy0qho{U58G)8PsaIXBKTI*C)KQg)8rp=F_*E~4izXvs4HB{ZIt#-l$887Y zHiQ2En|1RiKl)ai-nQsJ4dgoG@Jp45{rLBXLf`y%{gb@1+RTue<$03j{kbNEXu+w% zuHFoUGH?lsW^Wmr82dBxzno>IK(qfN&WitOwj$GS0I!E@=#7+zx_}CWRrDCWat;3H z(C3DSLfvRyC*DeBsCnQZUbeucNsRrB7Hq7O7A|O_Iy9uOUj>IJVMf$;sJR1E^!Ql$!fon86n(oJYC;J3 z@ht*55CJ=Oe32>Q*u;;raUhoR$Sm@AZ{@aHF=CQBS{c9esQUf&m!teI59lSgq9*6q z_X#Q%;Detca`bN>LKg9Y8}JJg=8w*ai>mGPJNp=wjYURbHO$F%$p~P#-;9q!XE2vs znAcybOm#(DphrlB8ud)803(AtrIYs-z18SlOv1EZ_Y7KrT=?Zlx zBwn6&Jtw_6OM210u4d-aT=Txop^M`@IO)Bng+xncKD`zww`vSn44vohq&NwnfxOV3 zr)mDBTVMY4mf4?3pXxK(%|0$E-r7{Q1(6&{UnqGy7k5A5nA!*jeR8j|PAc9Rw-q(} zEf;}j&N7n&R#=XOy{1qR)YH%8;4kRT(|1T(whO}R_5XvAwCjJ;_DDaYG0Zmf<4)p) z?hnFyFmgpQ5J}yL!Ilz3HHCA5SQ?C2r8DxfxBG$*;a=fK6KkDt*`w$6$YMHX&gAi8 zJ-6pmKCIb&kF4y2<@s-?9bE1_;6voxKUavw8Iz?oLSB{DaVX z`n+t(*3l|{BI1bNe7{)zac8qjc1ck0*vc>?te-};+a0d?VNqQ?9>t;vnMqtnUf%N3 zrY|394%_=Wd`L-$-*_EeRriI=bPnX2u1FJ8pSDcqjq|h8d2c+2(l&h`@}aICsB7OX zJf8GT@(aFf$3y8x{+7VIcVCZ}a1DA-AB(!?Ka>$oS)Q=qa-Hq8pF!I^tFqD?{YbU< z&p7bn*E9o@NfiY&g|Wl-zfKw}u(o7d`xH=w3^7>%O$#=v($g445V6sZOhtPb|7GyT= zc7?v-NNW0_=rxbF;>D}@$;HJ{NmX0d*>#+^$Sjp&$^`vdrtq*HQycn*9(GW7>S&`n zui}W#wsFyTBS2|Qy>zG8Kbm@k8>{KE4P7gqJHtbOco1JY!3r1)(Ihab8Mrs0EJQ%* zb_A?;b`5zc%lNc6-Cn7yGNa;1NCv`zavk(J9qrm0viaK?Rg{od++?=;AadI-GlF<@ti2D3xel&?s=+UTU7N=!>r4HNu7dDo=Mq3wc zeGZ0=dCkg_eGUc=-2QR~6G3F)TV?92AfL_a=e@}nOM z^VyrX;kI`1@I1r#A7-9W6Vc#VsasnF^bZIUi_kZ4YR-7W-|T|w9I2VwQ0D7b{*#)} zz&Mj$S)qk*erGtGE^E`Uh59kKnly0yjM-WNhM^(pVxtJ zEizXPUmu)I3N7yp_br(u7uah`KRMI3hxBA#LN72~4JpS>*Dlvonyq&h0(EPr?Z z$xKY{+rC1te}j*)#Jr-3ZpP(h_opb6iMVLEqxANte|3Co70>>D_!Utbl#1ke{hu|P z#D%Mf{kvlS?y-N*v430Tzn6=DFR=gKlK%5vxml3GXbgeSImm_=qXZYNCPD@J(t#@= zwe^?j>7!STa!?4>~YZ+wI_@cVnfrpV7tne;i zvDCIEgOV;1NKnSumA%g-T`j+pIiB4Tuv-4X<4tC>3t7Ft@}dEjNYZr7J0BwUWRc_{ zH5U<87Q5)pRz!4km3hyv>!HQcHlnKOG8P2@>=MQ zEtp)KmhOj(5Q~ab_a+_Vi3H76Xoh7WmYx0-9;z*00r%^bqxpw1Qw}okRfmy<(tZ*o zq+(A$vcd54I0nq7iKPz}X&?h<(pnlr0u*F0-7kDj8iqM1(vLn3MKN!uL52C{S5wT}pukAtVfiTVIY+poHL7HRx;1k4a~{UDxzb_bQ| z1i_de;Tc~C!cUbA1tT!n`(m@z#1E!Gp_!6*L(WQy>#p7EB$qY8CwYeoo_$zX^SiC5* z;@Zg-SclRf6E&GZ7w{P11q@5dYdWf-L(0~-Kx0Dgp^SM4mu=D8z~#iN^8K~LYIZ~Q z+qJfM58@d6Uf;JH)Iy9>l2t4aCQx|eC0JC-T7V+0cjGNt7#;epAjQm5SY4~-87>v= zl7bKVtLSRq+{xC?<`nf;N=RVdE*Eo$F1Ucl=%%4B0K#o{021wDkVGY$3SrWB$PDCk zvUz3BRTEFhfDTXxN~sfT5F4Bk`)`{S9Psheojg$U7)5(Wl_tyJ>hRuNyzZ5V%~qcVgE;7L!BJWiz6%ADRpv% zx_3Q3BGXZEeol7E(ESqpAsOAV&lmAM)RcoCluLja-V!5Ezh=qk=$AW|5Ryj6C_B;| ztxffcD+257Z=BTPr4tr;U!41yrjp8V$FH%&fK1^;n-??OFhKZ{ zk@(Q&^{K|af5XM85E*X0hc+EcYW(1@p8==eq?%3-&wp4WO}Esq*^Q! zV(SKN!|2ce+bwTTjRa-yzhpv*?O0!0PCC<-zY(d2aI_u>pEQibDMla^4?h=Wd4s2FCnpC;>Vo>{*}8g(vi|1UJVR^eNEJ5)=3c6#}d* zxP`o2Trxbm%ZsEsFYnMHMTy0PW6B?(n1zV#FA5O=uiqIf|3Mg-_=E6h@;nENH6>fk zYms+^8vO8ZnE3&!JMq57cQXH|YDEU{?RA~VuwUtac*!`OZk$SIu#0*5s0gR0%g^D66AM zV$*32dy)F(WG&sM7l0fkca;2vTzw zE8#R^TH)kFDHEu4yiR^F4;(cEU*dB9vc!#qP~4tw)Lj!9>^bmmDQc>fM9HORFm<>a zAAqq!;EAt}^U^=S$%PG?3ndn_WCZm;Kwks7t6u`mT+pQF#POA=@>vXGsSBBx4kfkv zYhUB1;8naeeE!-+N-d`?te+8tKK#k4NcLT$I`_La^KL+Otq}%2L9!lQ48Rh0(9<-_ zLBR7tuFssw)ac?&gCQ#}DOr(4?ib?R~E>pcWlogbb=E1}(E_ zB|@p0ldthX^L3jAz#4gwrl;VBB$w&FKNBfi{Y3s+gCOR20X#56!zxShzOjy z+j?qPGopQg%MajA|LG%}l^V{jpzo#>vfcnTVsX|{tVYwTq952#XB0XGW z7obr+wkG?raN&QRKoe;Y(ibdM^&(!uln{@B7@7MiEP|q#_)gVGsjV;oqJN1&{%AUO z7UK``pH)E$+Qjk?wtujHZnB~Mm(K)LZ(JYgOcqS8CZ%}U8B{;%N`vtxA=ye0;=ARo zuk<%ysFd`+P}q^EJgK{HrI8ZTgd~59d@{4^d6+~e-&5@$1S4KtslQ!7|8jwW9WC^X z`|bGK{eG^x&p#^=Qd1sOory_6Q}hj{C^wofWSBI+3@VVe(Q)Ls$4M3(a!f`*js##( zI}ulHUQF3YhdKW9L6QH9hyJ}K%J5bZpmHC#q>sRv78!u`YmB{LaCs%*fR%rk>ChsU zsJMrOiiOlv9^*>?wVxxF2?JuHkdU92`otUCVw}_*_aXzhT5ghCUZ3PtE?P^(ogK4j zo_3KS4ehle@ou|wi&FYTCT^i_6s|*uBnC(kPSO52p#VT?qH=z%6p41m=;>$q0=cYH zyUpxf_|5I4-e}kX#|HC;6Y0#0eRa1RHUIwLZ z_k>`3C5o2(jUZY{X6B#;g@MNA+fA71D?LcPj#=$zr$9WxlFz;09fHSO29H!WL%ngj zpPJv2Kd7_?x(5?4rcaI#j~8h#cQ7Pu*xq$e{zA|M5mBhEZfroMfxCh_-qAk6K2T_)1Q!;+y$K!oEY1^OETd;Lae=uzR5BBAM8978tfg}@_5{m+!A2+0RMGoSSX!{J3bApiev*I>n zDPU)6nlhp>k7}WfGc*REhS5=)q)&z}rr><^a{rv`*LlX{S5~UvuCBhk?mnA7PW}^> zvK%Zx68d(xvJI2E*6oWa8}ziK`{kw!?7sMN&Ga65UsX@ftio{iiXa*n4GLQz5m?L^T|yv8V$M2k||N{+r>&d7GdJM$n-Wi z2I*jI1D0EIG4@>k3{VGqS^q)M_9tWph{8!S%OoO`2`TmdAWY}<05RhJ_Gc)HLaP6j z^*Jc8U`XQr$1O}sV>{or~ZKRP?rf|d4P`WglC`zPONzrxGB<1V^lvR=Pm-tHUAT@EPHAP6m8 zXWzW8x=hLDdQ&`6R5pOP#98faHm2{25`aDt%ACTS)Saj@Kz~j@71-%GYQH5hsXic= zZT7m78fhzDt^-0HbO9fP>j#pR;wy!d7F6qFmHN=toDQM1_g5HLA_QAcOT8>?G=e@s z74+ZwOfe6`>FWlwZRq*I@nzYv8^3T@b$zY}9-7<^E!^?wqxsL0TDV1`-X9&sGY^{9 zHJ{;hr%^_?O~{(Lji=NMb^;>_*5qP_BFMQU;4xV;k|}|5&H$}&tq=04Mqa66zYq9Q z6mx&GdGAiyYDn$q-i(z@KKC&IX3-(a zyDL(#`d{zoE&2ED|L(f~zfYSG&(ZczhWu7z9u$cCm~?armT}xio(H2lQGwxz{tdgAmceVYM?Wc3neWW+aKOueRcr91OD(O5E=?!28&A%CSE|!BF5? zrX-|hl{5qA+UP8;boPawOdS-**sHf?8ax$=EjZg$3O7Oc6(~Pwl<%YzZlBKI+`({j zjPWjo8*i5VvKHJ{H;oTq8dAHYH(GE*{%>w#r}BjJPw@svA$U#kE6L{)kJIP#lyV^F zb?*bnA?8kY^ra-UpBs$2hwT}{MpQeu=9H94L_dVB4@hhM4m61^6hu5mG_=QL3rzkw zmkm&rp-D_h?jP2P;GyB_vl#GwuS-nn1=JsD2AA5thX=jkih6R09W>M_I0(e_7f1n{ zAm0RM_s3tWi=BTbhX1Qm4!JI5jsl}<>x0e9kcY@C(l3a4ejD(Y2xRG}o=-8%fBRA$ z^VJRE0dKDG)ZflXf(;ESy#+3)w)MiuU;cS)qNw3$4`vSPpBw+{|M__T+lP=6pRi8Z zO1hUI1+<2|8aV`|8bDsWilzVR9-WQoe-`R*ybI$a?T6X&P;F;o<#pB|A$114FiBVk zK|${z7XNAg;9Y#;KVUrWdo-~-%4*%{@f=mSjV6+b@f+t!2Tg7V?>(^ zLR#EVi&_Lyy5QN&%eJQhQm`zAFca%t48YYu_a{CUqv!!>A*=Py6Wl7<{t3%AaOaFp z!>#`Vx4(FekFF~ltELyDmYP)4T9It>Ws)mEI09&bt67-P9+zXl&g8EB=7!X9DJC>! z7FuiQvO2XIB#OT{z8r+B&elsL$a?yfTI zI-n)|N?#K8NRJW%yOb3#c#ql9Kw)Yl~-J z>%V|5?XDgi4`dG?lMZSAAUv2f_?7336~m#VdX#OBPl_q1GEL)NZe`9ne-J*Z?N0fS zTv*&cwO!GAs7ZfXsAWLg7vBcWNUJB12+&_pt!ONKSsS=&{kB2tHj_}Qxf2gbBfFQU_T7h6BmqCAV*`(Bfh_oUZzp zDEgJQKd8GH-WG=A$I2z@8!vb(E%#6SQe{?j^+qWAWbVi1`hYhicXUj@5B>tN?Z2v% z&}XH~eR6cNQ&6f0bgZHMr5={(3VbpQO>khiFo(fIi~I2uOYgl-B8(+oxA6Bx)QSe-P3_Lu@EBn^>3|N`J6=JJjAAtH4Cf= z@UL5AjxVn!xzVT+&4mWKQ3u0}Ckq1Lu+Nf(6kwx&GwO<(W6k8Fc^HF2JR;sC#; z|HbDUcqZQQkk6A+PH_pWE+qG%#CI=+#R>n$cV#eZ!CeSP+EG^x+4bK3jq)8PPmbKm=O#XTwR{^si;Q)5mDEgVdMC>9#=rrN6g^Jw`9&1 zIH=LVVE2ICmrsh9>Q3fi7Ekh_$uV0d{es=8?GHk19(w6y_#cEp;uZK+J^Sw~qSi0L z$$t!=P@mHrNaNv0A z-F=jain_Jbe<>Ex;dGhj^j%jpd~e1!oE`BPsv!b?Y8>4KLo z_WO#_?`!TC?8jW+BZ9vpwu<~`K-ZobeEycF@c!&|pezOWhBmSB8l z63z+ZkQEy#EZvG)@?SPM6-rf(?74j9HKr_!3ZCLWO7ubk?`>)@H|&&HI~;M$%BY6Q z{6S!*^2UB%s_&bczyH)*w9FQ}^Lz}4&l**4S$R;&30UFo=cYq|kqgRsqUfq=T7yu= zVKQYNPSlf}oBTmpbDEfk)>FN4OD02Gh>X4Tq_>fPCz;bpN&ld@B^b_=v5kCc`nO?7 zl28LCL9PRY6_Sx)7#UEPr8WvIUneF^Hrr0*%I@z% z7sjh=-OKsZmkHVNJ853-%~mu}Mf)(Xq~1Vx`IKM911v4!JPEt0fM7U}H5c6iaX4zd z7-IrvX(2yv+jjVmmFCb;9#Y(eRvn7pxg=qsP6c(sBd)=4U! zTD-27L`AxKAooD!cG^Pz?V*18)LUZlsKYUB{bQB0Jr9H65EE$K-%YK>UeO@dcUhp5 z48oww)@j#{MSu3`Dk4(>^XV6yLybQy`nQMtB+^kFrb~ExJOj|dCiM-_B^jl zMZ3~ad1BwY;do|yA+dUyV1L^&#W%Wf^l8@bV$tMP>QZJ}9$6Pk{dX>koXnATkrU?K zQ4}bBIqphcvNY%@Kx&ur15jRm={wbb$r$lC%R2k%i`p?=(0w#L+ShzGP6ZEEB7Cn7 zc{!Rgx2Lgzc+^ln!t?_ky_y%vD}l&#$n>ck911=iR};-vM_XY#)>GZalF>d78asn3 zu@5BSDayuOywQ|QrjW;(tNoo*b8MDj#+{+#oRDM8=IGc|v)WYWI~9PpJr*k6tqMbh z)u7wKR9;c9`}WUi@O$=6<*=r zU0tXwcl~f%FqxIK8nKeQupegC~1CiCl6qQi3{nKDedbwT<_iKsmPz-$`RQV+*$>kooYTfA|VMSh4Rb8CJo zB}fUFxDc+sgGPW_s=q(;Ysg`Fxqd?_EH<0Y;=NHwtT*YQ^Evl!G?2W%NW()q?cTlQ zfp*)%P`gF->m+R-#jkQAsijsjl7_FEf$~CGL7LQgN5Om%lX{xYtL%NMjVisAq-3a| z>2*QXcxClG5UT3OC^B)DFEJySG0!Jc9|2Z!p;?t&cXxVoe-N-Wq(S=XHtgC!a4Y)y z=$CAzyz=|F+A1=wm<(bHJUb?dnDiBY=BsGiAJkhr($2jf5}+ozqzV3(!VY|fS@2el zz#I8@w-A=?ox1*21))Gc$ z5@X6ua#dtxn4%zEq8qMUyL}@Bs7Zb z3C9Q1KCer@i_$Rkh(toBGb_4lpz-Y8b6*LQUAg36*5UoD_Y4~zW^j|mpTP9^{!Bj@ z;3wJReyw)rH{;Y4ZU3ny{7M+9)!J0$)@jSJxAyc8!j*(+&O4LmZeQpEb>hS~r^?Zj zKL|a>+L{Go6%F8)z{VI^2Xc4a)_TM#L2SDNiO41+JRs10o%55RKkuD`{^VW7WJhvx zcdCW0hV;CC{6-EsX-npVaO3B`@XF~veGWg=)S7HcT~5baybmX9{SG8=v-%Z(D~EQH zM2GnFPArF`qlOKwYS>K#ZrLvl>gap@!eJTDcF;Uf2S~%UWYtyU9hBBk5ic-CJ@Di_ z=aPJE^wLWwz#N{2*ZQ?$odWkle44?nB=Uh+g(fPuE4!a7&Q*bB5ynTnz_`zy)(v4u zi3lKXZ$!SVa#Q)=eD)g+cyC-Oz6A4rJBdIsP86|E^0!4(8ck2DSWVu9ZYk>b4hj}? zm**+PF6N61p*wbKp1_Qk12!#USDp#;NsW3SJLkSR8@fOf>PNmMCYNsjB6E{4x$JiiwFd^d z3j0$M<>`rX(~JB;D5<7c%3!mS79lywJVD!!!@O;03+?3Gtll*b*TKW4B%G)+Rb4jQ zr+2<-hRN?_lrE-CqRx4RdVB694>?nCC!?(T>L}7*prbZT+pR2@>$PVky6Il0%?V-f zXj8bs*!$d-8rw$Hko6t()%$7|EgN+jGu%}Z@RWJ*@0ko8H5mZcfhs-6^PeiWinaPX z?CTkrLR<>?S7uf{eY1u?WHD`vq$UsIC&kuH6m@!*w!@ij>xZ-)MMn4tnz42!PFAML zlD?6Ps(k)@_&7XjMa1#)<-1*%S3kJo@wAo51IWW5r(Jc8XZ6jYJXt0@6x_&~H18l$T=M_Xl{w5c1-x}>14Ao)Q2oP>BDN>Avl(W_6AYYuAoT`2}x zHYHwKEI-bmrq9?Ty7?zUnd8b zb*P^GPc+(70K(-G3#Np>7HTRuZL9Cg$KFfHNBap0`&q~%Qa!Wtjc!2nXzM9u;_?dv z$s$G+5}I5Sg4*+@c$duQYTvTuidmJ9E=J=EHTw>x?l@O^d80=~l6oz+)=&_QP$ez9 zI5)5NrOrlDC*c+$R)fP(vF#^RMhs(;kG~o}-)rBbjFdAwYKJ@5&0uK3sF&$+T}QiJ zH^IiQuhiX@QqV;0UdPxrQ%yGW8wgaVp7CFL28Pc%Z^X1G?3KR?6HDRcs!)|{Od5(W zv7ysHeu&L?xE>qfGSH>S0)H8G?2oQ^W>j@?6{mPyh*amN5)d}=;>GIC{(X%n*i>ro=*yGaO*Tu6K%fqK3O_9qp+Tb{bd{_U3OmL-Wa zG4A7gGp{y@h_Q7hq4Q);a4n0pi=rks`gJWs_nE|`ZmnjH3x3dV=gp4L;(kFHLMd?# zyvUjTyA?pvd-ec8oDf?tr$zNs(iv@t!3P<`7?G$|j0wf1 z*Me#DmY3d2UNsGgBFTsqm>T1I^7{EXJi1o`tQkR3zwoG4nH%QPgs|p&2%RvKW0fRg z>+}PY#_&R)w>HVYpT6~_AM1CYzn!J89)9tyCnnY{MO?crOJ4`k{S{_*%J5$YR?;X}_9bKSuHR#40G5z}(7|S?Zy z1`F?pM;D@xv34fig%zqYP8`Ua^T5yK>T08x`lU;c%1FoG+L!eh7T$?}JOPXaCjP2c zYzZyse2(k|$@j}ma??#?PF9_JzP_GbUL2zMoYqiKS~Z4+I@nJkx6EdZ{}VsmOC*PEji$UB zvO)NE-1J3KhkmA{Qk(eVY+9qsr&{gABAX{6^X;eP>xubCzB8zP&so{@QGq+S|5`z} zX&xW0BnyAHDSyZK(x!7=*fhyl=3_riU3P!->kxSvqUpW`;cxve8?24hEB6r%B~A1{ zmw%S{Ip1Y&D_x2>dsNCTWkHqgiE^uxyv##Ucbr_P7~HYn{4K!Fu?EHP)gMm@uGt6# zb8w&{JIW!`m#=TPMW>|7z8qGJgaE`5nTt&I6M3(|+Kt8mhhoz6G*iaAPJWHTm%lsPv zilF~GVS~ji`44?lZ9HQHOrI8{qH^-Pw3a^z-4dlK?;FiI(;|l3@HlSm!S8dQ#r!JG&JLb46eUf?6rei{KTnApZNF) zg!}uq7{xCee-x@_Fz9cMByZ&SPA@EExpzX#=Ug>-OIW=AS|p1$27L;iQlsdIq+l-9 zy?($+agKHih*HlTu|6_OkM)CozaGfXLwRqvNZTdkL;_mx+`j^^gsN^ zNZ;en5bU@22>IUc4!4{>(>#I8P^LtCO-TPBeCgS?GP4X$Mvtc#B0R>p8#vbk5t}>E zUh|d+^CS<-vw#h=8X_U96t#W6y~%0i3ekxn^kHm@Gh$_UoV~de(Cv$P)SsiuEIcf% ziVB%o4~bvLbH3!@ZnJAs1YQ&Jq10!1uRcnryw6wC0BAJD@u*`lzrLn>2y43u`h#Hg zJ>&IN<={h)DSzF@zLg{AME^q*r%D~^wRLKOn6qWW9e8qgZOD>1HVv0gMR;PO#ik`v zt1z0`vgBfCGj-}}%bF)TD9Fqrhe#a%&BB)uObngx-4^W1>e8r)FxjsbOYmgh?y#9o zxqsnZK)j+maY$X=%Zz^6mm!h^FmUNk#17z%`uJE|C&1+Ru57!vWEr!iq6fA(Y4A-o ztxpsr#<#6Qs%&~D#VkH>dBGQZ=tY*AErQA4BscPPn>8{KYTFwfUo$=FAiad*R2X}t zr@}3o{sD&|4f(T7U5DYRk#U@5Eq`Od=;MeN%i_0vzuy?oT5Z2zHSwR_v$Y$K9tjtX zz36W#tu&qE(juah*g@K8k{{x%fkK}IwNfB*<_RhTW8^uegN9y+>`m1*IW|G7a{%bp zvY~HC_>hc7(lg4W8t(h=DY4y%4l4`4*y~Cztd{HUwU;d;eO`;~P3_arQ>VO0nm5-e z_cE7J-G+^pF=}26uDPA-wylXaX!{eCm)Gq}-_z~KZrTt_%XP*%;`Y{z&iw4H5G1oE zn|f!Ajn`1czfpZB%fQ?xT(8U==OA2Lc-Sa<`MY#{9;B+L!;?pun+KsUdxuN8WRS$w zWKGBV)qv3@K8b~m9$LtNypxOPtup4-V*UouWSPhb8+3CWL!Gl{TEc3o7rRg^pS9we zNEE84u$Z5&n=Rc;RKwRDRF0@Vb=gc$xWvR{{fYKf5GU>P~Kl;1+5ql#^eVjYn6L2t>- zSx_{AfvBJHGCW2&)$z4|xr?}8hW zcT#~p=vWs#cP!`xJ2VbBB7DEb_wzzODQSp%Q-ikrl>p{@TYc};d@-%k?ZI`K zs!9GPoo=tz7qNC-VmjH>%)Bpn2~u1D1}s33n&p^nWk@uH-ukD$gI$04$t;y4{R6t~ zUPUVsRMuk$6&ue${IpOSl=)bLAGw{qlou_#}RzTkanNP4ZP#-6~exvRrvlRz2-Tb$W5Hhy26MgSCt=Qw=6 zYs9#puuZ@xP;yz>l{GxlMwqI{hKAhE0%$E5F7c_~5MfP#gOlej8++!WLD091c_@Y= zhjOYhm@=(lVXgNch3tKhZ;gcz!YeRPXjCi?A&{v%_#I9aqo;C0^}PyDYtj_psDI(+B@>XdB_jkeU3&-+L@B_3OgEfovi?s0E#RC z!D|Mu?f(45A@JKAoW1kGNMB*dr%sLE-k5`0^>N%X^*2On4ugh2ML!eCz|MxY(LpuF zTbPn_RC4y&P`2R;lkDOx>)cQFtWFN(&GXqqyHJJD)rj!q12+ZilDTG0D8(|2PWVKwBXlwSyQCQ- zKll8phJlaN>7|ac{fh!)YiwVy#{I1i@65RI`OCoy1D+fuY348hKTy-()WTT_^-RmY zNvrSk_~Kzoh`en~U&`>d;V*GeAvDQ?S5Uz1tU+;2t*E|}E0!7h@p#~;k?I4Z1)H`z zEJL>}anf89@+y!*CCX|^#QV^AbBvVHvNkni23Qogf{1^Udefgw5OD|NEHvIi3C=Vb z2S9TGdGkwSMpR~E8K0N+USknE7CXtC>bfHFXcQ`_V^K5I1P2B>e6!9jBX-ns+acOq zhn93(z3&+nZhTWQeO^BU%m}0Rl&dLuoT!B{6K%I^XbTlEedJ`)G_*R$=i<(zwY7ca ztDhJwNKV6FJNgyDk95^RX(MuWm3v3ivygW#@+d+L4>@Ft0S*1QHbU!EuV`>$@>s~O zUo{|xc?)%*8@oe~N3YRE$dkJ+xxqG#;tnl1xDHQJU43#u?7H-mEg2KC(U!fCsh2jzs|O_n||IK?~_YN+L@WvaRJ zO{dP$WVxR&;Xqs5?@*Nm+8xyNUIB7@5Lf;!A~GNsoj#yuS+s1_xat!}J(the)E@+Z zPsha)yNIv2MpZ@8I^*Ot(9Z)niBTMpyS>R1-FX|{y`>QcP)7UGFk1B1U3O2(t&kqC zp#SRvOa$aPiQM8^cFew%7}MVfs$gL^?wV-xG%h-hjelD8*j-+Qm`$el56lY@35R5H z*q&9-zkvbJSQ?yFQoP@v0i`uZuO@X;B07ih+&x==Z;+jYH6K10t?IY2b46RSIu~z5 ziw$OZ{#cXTVy*Nk-}x)ukFg{Rb{Qx!TSYKA1qY>njl+hdYTZ#G4-$4=%B4a6nJtWQ zm4@bZzb~R`@oHq6jIut`of9xz38X}rT4*MXE^QnbctKEDa}Tbt71jFfAoZQGY6D1L zC7|tsMRn^(Uji{DKOcTH4pr97if7*#d6D=#1_2o$s$ai%1dJFg2_zG5my^`hj}JMZ zk9F8>8v@h@kmzkMWEC;xA7uKy+TiBQjXhX6b{h`v@i?x$`?TEZbt z$8%NiK>I~KPimjDe}ckQU2R0ATueGboxtut4)}lZv5J599o!VWk$K}B7@B47VFj*- z+$-D0If#Z+OoOg&I|jKcxxmFd)0e6V4NKCa8$*$k&eK2lU}@r;)7*2uvq4)lkN~_e z1u>>{37Sgx^qz8qZ&RO6%9@vstwj>5(4nOEkLPN_<(C_V{NuLNI1VUn*pLqiYwt_m z^N4pjKkG=g4H=yT0azZe*?)tb>mK^c`kkQp8ZMgV4%)ScdE|^^5+B*+dDl$ZP0_rH zwdE5XEh`oH(f$-~#XN>`*n!WxjxIa=Ol&47?+o>HsBt!%79f(G3m9Up z$@i>S*`Gi^h?yeWD}L9j)Ycq$m|AhXbxW+&Eq9x}n5FG`fhi~ZAnLfpX~eE2ntVJ| zkMMN)Hva=gIO;U$J1o0*LS7Ru1wC3gn}}QjC=KGk=A%T8gSg&Df~uHX4S9x7d}@XC zRu>wBfV#yg%(LHnl{HmcH!^J$Kwz1QRS@v=PLOG5`BCANmXU4umOB$!8kB*8yAYx7~2HFX5gvQ+2RgZt>8P>LiSYQnW22|+iC668O2i$u+>y(VT>`?)SVg z_saKLVl_^rtA_z$(ccytQ`Ko;(nh(Xz{-zh2b)C z>9J5EG9>|k4r^rRfwLAc&j94sUx7QuaX9E)T_c+P0&9K#LJRWT=^ZY!KJ+gCApM8; zy3#CZMxi438( z?o6SSd12#;Z;3dJ<4b7b+S*c|m{hoS>m0WlksVXRVM@iS#$yV}x;g8<;U0`+EDR$t z^wn1lQ%y%xTRQ3iL`iCl9V}Qt!jlYu1WgFwgeXq-GIF~UR@2@9bct& zXX?bp;Tuof6q}NmTcL{YrXV_C-TYR9&~3U!&sm5&X~Uul+PBMEYj3*7IkfW(m~RTK zw_k|9cK4tVl5qA5PX#KU1mk^Bq%I!kTr{lpH~bR#VV>DkPxRe}fiu@c2ax=1|85KhyBD1 z`ZCSS)xPnTQg3<|Ks>ZsK=*THNj)!EfSXj`5FJ%tJD5oc<2x~`%B@Rf(af+VF{a*| z(;Llbb7uw7Nnw@IEpEsUBKwF>JXjSPkx9pO#CM4Pf`wR;vr@XzTnZVDjg8(zOyI9C zg}6?cI+P<=K$o}m*B?~A`5lCw!JzZcR>^0TRy|!O@@FWxTsR8`mXb=Td(Gd-=HmSs z#5zk>>b6Sf_dfaV&8J4$er#o%UVNZAhsLLVL)1{dnqz7JgRT?9T#=Go)6&piGbO)N0# z{sGzDHHFq4ejrJOK;$EVfznyD2h|Grd!;%X8547>*)TT1Cw-(G!#8895U#?Cno?zt z@6vJBq}z=Jvj)*MMKg>=2i!n`VwD*|d=_Cf>zn=OFY{LJurg;<-J8t*IEYc;4)lk4 z6pLt@jA|-QN-Iu&C((x#j+K6<@Az^ITI!qh**v1nc*eHJy%Cr{i8D3J&hg!HChVvK z{A^F|SgY7ED8jPpP6m%PfBP1G5x)qj7uY#T_+lpQ^Uj9~myOOiF`myPrkt~VR%ETQ zFk$8EzP?tXm4N#xWtOAWMy(JC=SaG+JH7@6l(dEjTFE8e?T)Ny{j` ztFU#Bk5N#-OTkjqaW2u(>`Z?OTGt$2)-)8#j6C3@8f@ z^7f{lu^YCGXcak(kBmI0(aWmI+M4?|ViMUR0M( zx^Kih_%7DfoLQl=aB~|$dY3Osk@lJ*Q^q12un6@xlu;wB1PH2h-;|spQ;U?Bmz?Vz zi#^&K7Qy-lmkiv;etxzpAhhC%q{eHs2OXOsvZ%3Yd5)O5?-EUUCp7kIvt@Wfw=%)$ zIP0_`T!HQ;GVig7se}xb%DAF#{R~IlhZ1gEeL!41+GFAI9TbJNzQ$Bkmcnh!iy5#e z$Q#v&$+(7E>cq?kS>34fD{x{q5o3*lrh4r6;vxq*(-Otb#)Om<_mNk4c&jnR3pD7` z1}_rrGdc1L8gE5;XIdI>)vxQs)l*Ia?#m6co;kvM02O=_s{-uud)>fp%L18fO2;of z!wM0UZvGHf1stNNg|&pyZ@r&V%#%b@j%$t#Kb{nmD7^e?Gg1}7_&O`nP{}ssW&2}U zS;GIr*;z-m`NmnE3Z+1S777%13s&5rrC5Ro_u@qo2u^W|6o-&tL5jN*f|eF{_uyKz z#c6^5mYJQ|vokxpXU_hgJm-C%oFvbCzxRGFT46WtnI$pnmCNX9B8|diB1Z@u}ap3F@~rcg1C%*{Ie+Bs6Ms}t(?^gSN`83puVFxs{1SQm>S zLgS)FF|6G6&7G)dhQ?4BYiZ$c zf3#6q&htpX`EGaohkupp!lM6m82>MsA)Mw9QMRx4;2U49=s3D?+*w&etKk>PB857| zy~vwth8vMPHY<%VS>fvC`*HX}^&Y1%(@A0ALEqnzAmH0vk~r=wbGb{WKOmx~{tgx` zC-<~zi%G2y?>0eqa|w4Df8n3x8K@18+ky_~&Jo=o-fcKHKC1i%>A9bK+MFVau$V^$ z4+%Q(*LAeN{M-BMEGBjTJBv|2zn<*y+`QNKItspwy9t)~M`ZT(+FLwsr{yI89YnTuEG(JTTWK2ll<9lmoYYT4q92>PCEh=WJ^jsP&&mo6e_TWSwvTw$Z|Z|$swxv@I%3yfPm`#YQ%k9Q}LjF zoKN3?61FSrh`DXmM5q1m<=h8ZZEd8!QX%T|thQCrelo~%T3+mJE5hM6Tnd;?;F#B5 z^lYVc?t`>7W;K;@;1+Fc1O7VN*jshS4;sq9fiV_(X%Rfx*s^H&c(16q!E4BzUZHI7?lb$*g!$xjjaG54e@R*Q%i7m8I*aT}sgDcoXiO2cc_}Aae-#M~ ztT@^5hLTlO_T(}h&=I-gxL#JfN>%EoT$$>3a9;)ph|^?;ctgC^t9RIw)!(-bHq8NG z%#*qpn>LL4w7&f3pR`2f)z6qqd}E@T8&10GS@-Gh$&u&NqQ>aQ*W{5eLwzm670lUM zQ&PhAx_7!w#(nkDgivm;Qb$;;Ra9D{FvNdMTXHEm-~t$jkv&y1ULs@-d>w~Kf#fY+ zCU;CrOZAYNdtpF$iBT5Pd`dc%Udpb*X8MElRy1 zW3a;^JBw2Qm!#wd!QpBV0U*8*isaJ9BO9ySIvgr;Vle3JVe%%p%SGXj4 zQoCHReq?^GG%1Wa4&0<(oDM10o=kRKxNxoN#8AoT_NDiDv(~WBPq8qDiBqI}QecDR z42f(=eqC9aX=-U#B_3-#N6{RgI}Hg5kZV)<6q%SagV%`5HWxG@2|=z^(jRp*J$rW$ zp3YDfN}v*&eWG?eYk=L39&EEo{XsaK^G0!q?%jm-Xv?x(Ngwnt-JFrVZdvS~l!=vw ztTG+UBgwRp0;J@r&I%$+IdX%1!LJn~cVPl?PeuCJjY&7A-1|@uGt83Ckd+`W!@JVMp){J9l4 za{Tzr(bo=tbc9~@H&ddY!HlzGzcjpFxcn|HQoMD<++YUV>|V8+>AR-q?rU4}6N2<- z_w{%`IyGn7NUNcT8S||^-1si-6Z60nH7JtTf9^+Uz=~diw#9nug2RrhGC33Bc3&91 z7tY-(oY&bs_yGmby4A+7v?4SF^UKf9m`qwd`mn==#63dki15bKdEW<+CBty`Kas>r z6_CSQW#jSPpJLIYFU$PXcLXGo5obwEuSSHwG;UJ9>~O#f0gogMnc15OLyNQ2x%NR> z;k|Nzbeaxc#BeEF>1&juRWvjX7qF(Y$OFiaNY{3itj%uKOBK?yW7}Nr@8Doa@FM2d zLphUfw~I+vF9(vhn!UCcdU60_7Q89EdbyK`aC>v~XzCv1Yt@{wgY~h|w{5pb{&qj% zck@t+HW0j&Ct!MH1^p@aU5`(0=Z5go`X8Kwj*C~*O$`qd^KE1JmfJ6g41~;#+0$gE zSjayOu9<(L_kWgt^4tusJNEj4EH{X*@lm*?nj;hg>m}yZlPhVo9N%nwTtR{Co5C7b zi~W?OApo0qNj(y#aqjU*EHD*S*^(+7DbIMHYB{{5tT!M#tEYC3w#|L3+(iFw3VcjG zEsi{m?dR(`p{ZN>AdZNa#$O}S%i5MifBV{4A5LbpVW#xqZwMbJ6JH_%6;CPJjG-sb z)j;P$jA2F9HSu=2g_-f%Jd)`|B8&yVH4>%A~|G$B7Y1*uX%6LX<^ zSR%q6a*rEf%9f=_C|la|VN0uRQ7%XGm2sGrB~W*3njKFBO)6a~>BKvYl5j~-2n7&R zRrV93>g2THFpVU6d)gzdsqmO^H{UdaLUYfr0uN&w>FFK8Ee5R-y6p8GLj}+iAV3Qt{h#7vgX6s=V+;Yvxf?Ab7xx^bkQF#2fcHl zK6}QS;?i_7@eTH&rh{rZs56>KZh&gF_~+)f=uDka0JAPaGV9H<18*p9bM(nT^Ju+` zh(R{PT41)HpGiP+#C<8fHuMEh(aEv4?6z9pKm8I9=dY{=3pvO00;$h|Gz8sEiy%_# z5A-ka8DAWRbbgJZl$Nb_ch_I7QD-fjkfHe6M4oNhsf7v?cGk60qd8bjqb>q+gAJQ2q0^S|TCKb>0bErVd99h5d9EnkO!8PbiV1p+%7J79;E_p|aM2OG<00cyA81C3q87zpT1 zKDMXP^L0ZVd$+0Yr95TxzrSwsd9OTITLqtt$2G3oi>{beAjYP|r)n(-KntPfjfh;a zX%OP=8n3C7nfYP03FcKvv53Kl7+j{eYn!+5FS2EIxn=37^P^=b453e&Ly@zstK7-d zZ%`v^^B;H$LI2ENZ_Dk9oHG33X19Li)3xXI$o2fED<7f3{(wYS@=}( zSj430O+UlVi-f{MrDOU%+6zR#pmH3Mj{`bd<2TIt$#T2{Ae=PrZZe-@(DilMnPHkq zO{OW`?33R<7o+e$l9l>uu(IK>`Gd^ZKFTmInGX&pqV?0yF@1~P%olnK_t7_K^s!sy#_t^yZb&dZ&5 ze`(ru#Miev+3ix{X9#k7-VG%sKe4jeV6JJ_w(L_?;q6z^FMbLg;-FB68ibLaBAEkizt;V974ErU`kzBpOpSRo-yt`Gy9IS}`;A)GtixA0omSPdPx=QzvE-?;Ry#6iW0R1EcYF z@tyTH-wYVYrVw=IuvXT!RTfZ)Vw)>$#PTUaAmZ;b4E~6KzeHVZ+O0|Y&8pwkUX~bQ zTiu*h=0gvWs(^R%*L5#DUax^tkWKpt_!k~Hp6zqKK2XM|+Io{N7L+yfxeq9aijrc( zEEc)1g1hi&Hz_D15ibA$zW7+h$)@I$;aJe(nYdvD(I_EJStMX|7p1H@AE)nH&GDpd zT7ZIT`)-g0g{Pjb=F5T|k z{GU(J%ddE&l7}Gkr~@AxhR9m4j*Nglp%?irDXB>gm-9M2y@ueF#T@XooBI67MFZu74(?7U(}Pmei^jDqfxaX z>f-0iY}8jPsZ@U5f6D1F>G{EebWgOg>QR#}yAHbb8Y5h&c##q8t~n@IRL}xyRFZSp z#x&0N{9Nw5_t6>A=4)Tc?#9o>ZoMjJrIdFrCo%>XI_L+3&db$5hq+(^52bcpw{2;O z7o%!^m3mP~;j^%1>if}Upd_NmtW|pLn3Qw?5$AUDZQdYbh?o5A%Sx_j_}e-z5Z&1Aj6F9DV<)(#>oaT})jIutB{`~x4U_WN8%-B%+glm4Pz zdt?iH=_OPvUp6H(kB676NdWIAU?a7Wf-9jLbPh#Jo}_Q@|E_?Ny1`X1OZe;Lywvv| zaiw(EHjDoC(wH9>3t9O@Y(K2e?*7ZPzfbkE+M8mn19e_m7H@f4plbA+y67)$$Sd%^ z@Uu2G;&)<=R@-hg@k)!qNdmihWx94)pVXp;&a~L#3>crX?M3cf-xW}sk^Jo!F$(gL z=B$A=b2wW^jL++v6=Piyr%8ALpY&{6;^epnlC5`AKIE`gkO)Ot$WvSAPSeGPvSw`h zMVk~C9s}d6NyV+)RH2+!Zb>@Lh(?$nwMOIX(6gr>mhw~e1~og~O&Dcl+k*Z*B=Yar z8riRRJ%;b@a4)CIuoz)p?4TTA!;|FhYuDZ?#}YzRJg_++vNokR&X|-SktZzsk%2wK zUKw5~WKg`V(+III{Ob+#KIS=(_}O74vaG9F_mu8k(W-%I<%omtTq?i)L$)TEhHQ!z zrcjgXo{Q!4{EK3hpHuM!)SqzDg}q)nazsW}2TiD*pXK@wju_t3J0)fv7m6*xcHUB^ z?@&|q>p%SF>Ckl%!-lc!q2gr>FD3KF#$>*(!|Bg0(AuW{B2Zqppz$GGm;W1~E8Wb% zJ|$#8A1Lbg6Jb4V>8-S~l}k&Tvu#@AW6+c&_KL0bF*!CJht#FtWUE^0Q;M$gBH!_` z;bOCd6kJv8lgmQV(^2c9>j47$9yZV~_&6(((jOmU_SPG}OUoxm>nr~Jr4Y&ZGv8M( ztjWn9$+rSvpBsuN@2D_!WNStObgF^WkWx8uxF^T~JHcgm&*)8(pkmE@I zl5ST-RrlA)MZweIe(|e$WB;;>Ek9Ns2hBSEzTl&Ey(`zo6W8&x@ePnqE=kS&901kIqAJ_(8jwao@B>c*wr$DZ^Fyy!kR{j4TnyFlfK_PFmA=@A8G5zB zNr&|e31{(e_ci{muQPGv&ur5@OMkv78ZT#ZqSUn9#Oh+SG+nwL+P-~ zKlI3WHvR9wM>U3j4ulOIV;;#wy*>M+vZ1y1s0~4*Upe&G0`1q2ed}+0Dd#Sk_5u9B z*yF|O=!F}lv5yea9a>PUc#A7LgVC;{+s#p>b?jvPP4w*d`owy8UoTqSl>Sanc*vC&LR#Kc(nt}Pk6YL*ozqF6@b zJpo&E@JaScKOq3);Oeh0n=K5c)ekH288ZLAwwHg2SoTJ@?&p-mI&S@ln-i^r>-I#@ ze3bgE4N6>N0Jv0#~mB-j}33rCRRd=gmoS7vm@)> ze*nnpJ5}n9G3`>RbR8But70L1eBb$~6zl*lK1{z><$*$RA`$yC7{K&PkCcssy?aq|>Qvrc%xN$Q1wsJtehx8|F_XT6SsqdeGy}+J{e22Fy zHz^8oyj$3QauWQW$ItUi+*{Pa&5qJPyeoe4iy+mMwd;h;hR#P#PQXK@zLh-T7JAt( zA=}m;D{W;oiZ$F$i=bXtv|};M&tij8Ay)*?t0X&{usM$m=n6qa4hGp}!cIqf!PuY8 z67xMJzn|_A%l5D78gwTDr32_Lta=v8IFg2!AxEN1AJPkSv0BT%eDo~HLuLNGa-o*I zT8R^k0cZ-nrCij!kq>C}WOKAFC{FOaDJu|TAR7U><_{k%FSd#{?1PWu6$FMqjr4vb zyl2`5<5T-QBs!MWF|3@XZI~nfR&^<+hY>BM>^n9}vti@{f*_vrY>dB}w?`|S@*tT# zMCyw6#@?9z?4uGVNGTVr7_6eLen>5PsiIZ8!Qa`mtPGNnd@gExQOM`>pqsYCH~DCf?32K$kHu}Rc_L=T?zu;12UR4`DBIfH zslTj&A7-g&6KJtSl70Jx)mxvgECfml$H(t(tIw8UP&9Ifxnhvl3Rm`-8{u-IT6N8<9%3Y_!t&HY6WPH(pL6vV0S>^LJqLW@ygs*Al!vk9d&Q+t#%e*P{-e?E4l?*$k3P z`yEk9t=B;P8tr^#2}Rnd{#XdhuHPnol`Z z;^Jw=kZMS;F>A`u{?kin&LFzH(-;fd?s;76KQ`yLnpSZn%J1$4gGcvjIl?{qK-HA+ zHHg>8=hY9=;BEtcOiVvkG@O81E}fp^*kMFyIbxqeX1ArfJwB>L+QfJ16_5r=7vx+M*)63Q23=9{~)WEJrK zq~zk&kc-1H3)Nq0#~JF0HgdrpKN1wGa=H5wD)0R`)pUpnQAn1l-yuYc68-W^t`GvG z1XV$0K_^uQ*}QWNcPMYdNQ*WjXbm~jvf5~hOmP<|9TO$RA!KU@HxIbw1_-2=Wi+nn^ z5C40DO>n*V2PgMVeW}S`R_;aco*kYDz+YH=(af{-T@SI~_y&@xfP^gg@qTSH8pbQt z4mxBjR1z)9k@oeWX>6UV5C5@wJ1;J~nlJl|SiW7LQ{vw<6#rtex-Tlb{>h7MAuS+M z6vLJMWF+*o-s&j6##7E#EM8+|;N4bhJAknw2U$qtXQJauDcZbiq;Cmk zJWYcg3iPOk@mBDHAVS?~Abb*NYfGlzwmVS-lOv2B4)48(p8x=&Ueg$wYo`sths#X6h*+qixo zD|jXWpY=CKq3Wxt-ZIT>{2Xb4(kcn+U>fJrv=^u-DmLD*;(6Nw_RD7U7`2uj#j6F0 zSmD_^!*oG``h~y8x}HDuS`%nEKR#tf2*6{J^qlp&z5p=7STZHAke+rL8bno^i`3SJ z(Gg)RE9L)8lK8fBDi!KLHY5*bWjYw3LcvQ>o$Y9*Z@reMHEZ(Fpso{W<+ZB?a-EVs z9vjbPPCbGQa?*M@QIL@z`Ehe*yZJ@0d+kZz%N4qchm6*Yib~vT#47F-ZEnUkPY)7y zR#d<~mBd_x)5+>AU|uWF zb-`cW(tRN$a3EJIRuj_-UNmIFrXvx$-pNn-!eZS?7LOY2iY6OtY;KJZKb zFlDO5EIu~aR-sCJGba&H8vM2pe>$pHr+is+^qV&&YOr_6dUD^qOEEb+AGQif0Bi`Ge*>c$oZLCtYip%OAq-Z)a_9Ly;(3{ zyBJ9}iH#a;NDmRv*t2L5z*gX>Am@E-Hq+s)21}Qx{&8c9Qk(gDv1! zI*3LwaZwSTC=L!S=}|zYhfw>AgBjuo0TcFYL)~S}ZlsTGXj(AQc!(_EVnmcY_kH2070&9c zrfK*G9X{|=t$}WIfBS;kcfh4UErHt9KQo+)dT3ZSyz%*lt@Pq}B5487mzW11azzv! zmYw#`ubY41xtBeWbB9!zj}C3tPB6<`8q#uqzxAL%SvEu@cJ|>i4{^2V=HcVM)%9L# zhwQKX?6s@;eS1JEfJF`?;(CXjonJ-gaJ=3!a1$(O>QXUD7&K*nxf!8O&BqpAe)1^k%c)4jVi{EdxOi$eK25vOlrQ!2*b}=B_&tFcZU7fk4IPAbh zw{iGV<-+w9}inS^YC zNIrSpt@P?vcAcUwlWLx(jS6wXQibVpJ<6xZbeC6`^VBrS#JDc5tbggf+K3Kb0m6B)qp*>l<6#x8i>8ft7jWlVa;717;YusZRGeo5K~z%lI@%|{)|>eSO9mo#luT+-5Et1^{HMk# z0o9pGWQi+w6tpb?6=iic6LK9rJ9#EUBqM<4kEWGIvD!=E81zylwQIaBeKZR_WTt@N*shqfVAdh+_to9+eG; z{BzW*%EODK%B&y%!P0wg4*s)!#W&oE4f4X&r@xfkGQ>{tO(uTBwP~^@3|84T)hK48 znyY2d`prCCee{*qj0TY{gOGV!i%D)BSnftEp8i_@3Kn0 zxmouzK<7ha5=J>jqx~*oybq^niB{yk<$D>r_`^8w+uAbJDDAfGaV4Q|(O|*j{rhBC z`Pat9WDH+d$aJ7ZeN*gC-qPZC4n^Y~E6YuL>C|MV+9RVtL1YQs$8Ej0v6*%H%ih&1 zqrKF>bVGiHR9q7WWtHF})0x+HhSsruRo}Uc9)5GHLAQJ6!^BW_$5G8_N^a4$NedJK zFdvf&l-7dlLi1$SvAWaCn#%mb9&OsrJF?u-kQ#Yfymv*LN~KAmx)^=%REy)HqdU+Ma+m zpp3M91}o8C@ldkle5=LuR?!9pyF~Ui*0(li9ohn>>LR%dJWR}lbRmkB8feJCR0Myd zrCtpN{?Ozp<{{#gKE$z*qvS%nSrI8g-ivR8{}c-6(^8boR2|dL+OS%lylT>6$0RtOWSmg?1?MGn-3NHnO|y0oSphw1 z&X>!Af~{5rL_7|t`=-$mI%ITYVU_N^ZWDHeF2r(!qF4)Fo;|8-@iX)lp3rmK@VVCu zynjm7xqYm!-ab^A@Pp-I2J?UW=6B-ZXMC zb(QU633ks4+4}RVHb>qL)O)>9&UlybNz%ti`z1ikP8yy4ck=EU5!N zkDBFOhHOU6QiNP9e}SFPQNmhbZt=p^Wb@~vh-!a{KF#jDGEIVt_J*+rq-ZB~EMH># zV{TB>{8a+{qQBL5V1oX@eiE`+e>-1v#{7HgGZ^}Z)Z9tympsV-UEs`WeNJAB30u>k zIER{(u^$QBEZfwDKd@axt&YL{j~H2_f7=c49Fic>NKC?#5`0v zrk_cD2l zQ1PjR`L}{4;CdrAdR0fx^}k2n9zQ~(cOcj?i}VwAB3_8Vdcj}#t?9Wp0h(K~D6>wL zZz_pyt_#CP%f^voY087Rc|+=3;(DuxX077}ZKl{;HI1l3Cg;-cYXdHUQ|?RZpc#;U z*pK*1pdJgUDa1t*oP$qb9Q|UntHxr&giVq~fyE${FBu}r5=DDcR*rIRgc}Oc6k5JX zd_yatPiimG%&zKn?5HHF%Dk9C(9#E~;_q!RHI6UVTm=bEo=RR&@=Qlz$)`gf8yQUc zed^b9#1IIL``fZYZjUcw!3~|3wr8Ha+rc0b+elfNJESAUUb^x26;D#(DHTpBp?{$U zTbC6dkoBzn#)+=?x1lP**UmoBkK|Pib)X`TS-@Zc4c}f8Qz#b7lIF^361=5uY3*@E z?vbb92~DPp_+0AfR;g_pXQD18J;2DwREW#zI@7!42dMh16H5_`m6UlnlGS~_GN9C9 zWGO{MHUG0s+Q_+*rBjuQz}R4P?%oq~ zlYpHzJCpHMLI~#xs)_B}OFUo3%jom^dcxX*y2Buc(;J$YR@E}CqQVjV1dbdASlwkj zRCkV&O0oKJBOT09rI|JUjq4`XwDNMy1ry&QYn0u!{kM7=gZcUl3g8!l=toI^Xax>` z9#$iq>(V;JFfDSA0rbm7qAsZ3f~Z-`GV)o-tb~X0LrsxB09=seO>Bq5eoBv-?#)sxZnlaQ0xH_7LrqVLp3U(nC9xP;=Kc8K_c73$9&7L=>-yb^ zBm*Dz$mN*(*T&8)FMm_Yddg;e<8%rsvW9e#j(LdDbiw?hva{Lbj_dueo@~r^pWNrA z8S2Th8MbWmPH@Q&1yAANN=r5egNJ!lff?&c$IX>S0g>ZXN6Y1;`YT+b&2xhIyvd6*44#j5cKHBfLIJft0W}_GW>%QtZ83XEe{edALEc z(-44|#M$g!8%R)pP65rCNSdMFxfjcSHT!wB( z`Dy3pBJ>9j9M_IJE;Vx0NbIM|_YS{~HNb0WQ_{>)Yz{!pb@FlSk_MLntLv4QO%Ya| zeJJ5^_(MK;HsdF!Kl~=Pd{c_8fwo|(wtVQ5+I0L@3pz569R1)a(v{PGSPhS-WTGxe zc?8Hve`1P7r9xCe6|I41lMt`hOQ6M8F+!i2!uvzV7)m;+8Dn?p(9pGNA~1{-!@M{A z=7dJ;O|6bGnhTw)zabyms!V;>T6y?WxT;dvU-oN|9@T|;XW_IFu72!R!g-t^2=2%i@m$fuylQ)%c2Npsyas?|OI zxMj6~~)IvlPZ4{e1sJ`}@Hd*?>72p2y@HE?AinJe$k<{F- zqFSCbVcB?f&9jou9J*C(8YCg-0b^rxqS1mTTtns}F}iL?-&?NW=GnU+-N&7Gej}OP z=`;shsfA_Qrd9S7Y@h3HmXoxY;)C=GCBp{}$-PDooTl>SWSeF2-TqklRHN=E5Y^m# zuIJl4DLAXfaObf}& zm#biMF_)3=fA#)Wxy-xfPBYcl?j5ZlAfikAh-1w=VnpcfPwAnnl$&-J ze`2z7-#w{Ra)H;4YH@wd1)VVMG-?;m2zd?7+s{x3PT?xu z1I?yFrkl^iG;=`hw#G2T#yV}mK_M7wRNZzxC_!+(JM?cx>3@yQagy8<$p6cT+W%R@ z@Soyo|K$t*FkG1@CwUhTuVFpA;s>F$@g=j<3%;`Wpx{HjK2q_y^aIi$y22DhfcM{y zua!redjH^TWlNW9aMVeX5(JNnmUyon zD{S>Wc7DY43cSeiWO^}arp@-oYc;}`@c`sBm^O%>=LDG{|^tcj) zZZ^IxgErO=A00xK92{JDN+YAlZMVZ|?h&i>50qwQBEubHvs6uDxK11{$(~v4naHPv z6yikm?ndqwP$U(Udj@pqH^hTc{Od?p=>1mw?3$_-f<-ip678WTt30R zW$wGpK;Ekb8eo#`mkz%5s%meo!7PoXeOU73h9{qaUi94w_!A{zUY&_|viCrY^$IZ* zA)d_NZVYyNo23RyG?IdhezuSo2lhaFM6&>sJro4kUsD3T1-YCKr+UxfZ)?PupVZ4b%jZMP3gSJQa7V3q2e8Y;T&)+?QrIdvRrG!T1??&urH$KoK@T|nFD z<(Mr|_*mpK1A<+RSv&uS#)dfiETO&($^*umpY)`p8nzul5))Hxp~F}si3Fr!Sgvu( zX2bx_$|8^!f;CTeX+cBftYqId{kR#KUe+la8N$6tfy305x#a0iOdpOKy0*6hE-JfH z93vS#8^TkI1gEd3_$`fD2z7Gtf#6_C7r>HE2t^BWh0WKh+9ZD~pU{l{q1m}%#>}iTO$ObNDyL-pqF#HH5^G)5qYe{8 zyH^bTE^p6jpLS{tm|)j-u1Q*Xt0H|+upP2vNvj(56l}(Lc`8yRs7~gFo%V#tE{`#C zQ_=9gEmAryWq6tPg-@E|=`GG_8HS3c{cmDCkU=uS>W zIssefScE-x!k0``ys$W#6Y?)+5OnEdnf1UoZuyb(Ce#ZVVrhCSwkp(G_|00Fw4H$w^P3h9#hngDQ>#*Vd zyUANOzEz!~XWIZi2mGh32qX>atcq{rFWR)k?-)5{7PhhIXryg$z~iI#_OXL(Zs3YA z7J9B~K+Nj75`RU}Q08b@-MaXGDU_1$oz-Y#%_6yVmo_W${nu<)Ukv+MV{m-`;HZDY zzj+EezpQTdDO#%t5RD#?6{qqKGx)Nxr24F2=$*bi`03{88u7|vc|BI%9dxX$xkF#< zHk116bx>+u_8)3?H7Ip{E*#%YLeAU5PU2{|Vu6sdD?&-n1zYLL`42V?gJY6?>cxje!Fn>)c8NP0PhUs?N}kykO0S9hr4 zaB_CeKcoBlM;v&LwaLkypf=LJ zCr!Tqy$(#%@V9K#(}}p(uIP@v(xv5atYNY4kJuI$)n$nH;9xT))O7}?bD}88IkAZ< zvxyc~lqspC@A=LxO+x=s6QYkb#aMC~3}3rSW*JyFt*RTlTHs3`D2F&6SV`_QKKOO< z97VGld{n^}Fsgp`I=+)a*f<6%kk==@Z;QYV5wT&O)=($veS6VAQ<<_cQ_>Oj>Ev7a z8HxJxF<;+WSKsH47?6;4^mK z*GPJ}_lvwU3Hg;Sj_7@6Nil93?fpRao~tn*leq}0Zl8;ESe&14ZP7wMtpr+~#u`Y- z5@He_jUHe0eHhhg>Z=hvP4k{vukQUdN$dPe5@q&nebwA90E`W^IltVw%aqnscXB{C zOh)Wjya@Ef%a!djgLsWcsCRF+L{6uiq;bI`2GZAT-L&YGLSEJl8u`}X;gY6|hoQ3Z zw#!8~`Dz1~KM%BO8y9qFK55+!H9PFx+3$OYHEZ8S{%!}To>}YuE~K?(n@^C}SW(T% z)|gw|gA{E9XGWO$gNKM%@n|e*t5A=_>8A!OezwFGxB5xp`v$%2-~k&JG@(Pc%{S9) zkV7d``FguQFSCPIev58z7u`dz%i~=uExUMbG|SWOUbAeX@93 z418@d6ozuqxfQow!S-JmC`gXgOY%R|f2hyEmF~ip1iRf?kKT|s9|td4O^am7<-d`P zG8<{n_z7ugk1P0!ON$J8>7a~N8zY(fs9dxp?oDqys6O|}FwQI$03dNJrs)*tgx(J=bkmU&pYUTm2fQ!ars7Xcw{XY$}O z(u#zDcBmGX!gh(E;}m7X1}81|=fL49s!?TeqQ5EPyB#7brrtlij~BI$*wl#iZZ`9% zL|79XD;cNgzn~2c2Fffsm4o0)Jy|4KV==8coBoM54OlZY(Zr>5<3Wp$`}!yb z1VyYFoQwWtZdNSI@O^Jf+WC+a3n_d`OFB=B_eGyHPS4M2(X?@z?u9KcK8QH%z<5A? zY%8QUFX^W!HW@M(66weY4sA#zg(B%q<;tQ(>;|Tu3=bQwJ5BG>pzeiDb+tmHs!7v5 zY8LkM`z7940>r)|-8L8WnLwKtwCziLAbN!y$)wn#S)Ee;?~+RlD`+Rv`yV(nKN=)C z&?lR_+=IaP@W%SG@0bVnDs?{bM>3syVm3+1;!4i-EWmiLY^C%Ww#_=^!L+Vxs+dNFS3e$YV1NLGe4m43ChR1qUYD19ud2q{k{{=X>ht!0y5=t0 znk0)wC@qiPDE$bG$i=;d=U;e<DM$v*|_qu?a6dPPz< zDLZgrsfc-3t!y%;719js>EtaQIqe>_qYd;Z8WMZQQ_x(V)+~oy#3x^B^;8l+kJsbY0D@6M4UV0W2p8YL!Ku7R3gM1`lJK81@@#W_JRR3`jef!tI`PcXX2d;zxK!TkcBU{+RE zTgeH)gwdNrWEx`y9!CvJXfD$}=?~ZI7cVCo*YObhCSVINx}l+@AnZ&s0WcTwD+DdeRLj87V`>GS`#PhBfFXbEjA)jW~Ddh^@rc*vt_bmuuVn*XHw%*)Fi@P+Sj zQ)zqpN1(H~PdHy@WB$p=y|+q3+(O+fOV~h|ZNX)f*tJy>vM>ZuJW|YV|^J7^t<9JevM% zvAn!81j9HI?HpWp#B7-MMN@%=D=k5fD-|x!m{%tZm-i`l>39JF0wRZho?P~fMWZDQ zXdPQyV*)l?85U{uZf^{?ZNez2RL2L;AB*mvm-cWz`sR8uN54JycrTt~MpEp)86&YB zI=r1bAnjlws8{&(a(X{ye7Ra6HtbKHo!c`N#9v8}a|9u+{(?s$Tt487@@xLi6fpvI zHk9U`j%@vD%FkkK)=&~S%=TqLhlrb_M3rX4LYj2EkgFAH(9#~A68!tVH71M763B*r zS@QCB?JZtf<#=l;7l%%G`}~-T-?~{$*xZxSZ0`AC?B_(%BGb3(9V(5%lZCELl1h%(?!}XP2p(WyJNu#l{4^u{SRnoHjV z0yb9WEjoC;?aI3(*#-iBqpBP-1L+E;rD{{1zxKcx`RKl*wzomc`=7Wvh*4IYzuaN94|IQYl#9OR-1&_Ji9e z+_11p+xNi-#=1bFsy<*926RssrIFB>0=vb;Xr%8_Sq|!6zHF)1z_eqEToGJ5;C#nS zt6zo#2BbF*n-M}pO=qOzbch4RyaG-mwi^A0Pd~Ib=bduKc?T(6%yTxOX}hE5ys45< zRO(vJI+V*hC?rp?T0&BuE(W-NjPJ7CAQ72YWiy^#v*-sAYdJ%BQb)W8LsMF99*O^x0?eC zz8e1(pst`}8N3yNtusD});TOlOzu(IX}uE)AR4{bf_qcuQcPRrqIy@I?Op7am&_b< z9LM%$-`=~Dnz!OV{BLvO2lqV~5*Qdv$oEi#-pfjMH4>gk6(qtFJcLt#*avW_aBDpU zf)kWd8#5+L*zLKUS-?=|!)RX{q(yV>*2KKnc0oIP{)p7ZDYTKTh{HET^~t?Rk3 zrlFxxMGVE_bmfM04@XExGNh(~|4iPk!pa1T=>4a=_&>y?w~n3mtnbwifi~#yNFQDhx62ai0 zr#Q)2!ALS9FDQtBM!NTYZwpS~A}V4ca7$r2(-c=#-5FyaXO6QXB~E;C12JT0=- z@DGRR*fD=dUl5YZU36xL5XpgzAX)`Te+O3(gvto6P9ew#etSOF-JugRr0<|U*v*!U z;NB#c`xpROcqj~gyA#0CiF@O@DrB)#1GZ?C1UpJ!QvdTq}WlXH(#*&Ul){F{gfrgUHyzza-jFWUt*)Ct4hl zfadAi=coyB!PC4$#kLtbHb?vU?eW`E4!FwW)UQ$`xc*g{1AxCi|%i?rwAO3(;Dc0QP^E{r6?R9s{F_urc zO3W!;G!(e#Ay+Ibre0*(>GPr)9Q3u!D1m%B@?S9r^yN@k=aGBIIBTy{UF?)YoH_On zJmHtF5!t?+*Zq_91#)Pqv-m99u5!`tY31FPs&Sb5k8~l`$hnhrJ|INM;8lyl%(w?o zu%n?Mg8O~;XEUyQhNxhb&&?l0bF0Ct`m?Y-GlYYIx;SWZ4{%NW{Xm9;K<_qqiN;yB$(%up18K(qBR)7H*7ht zB`cZB0wDSMqeUl~>WysC6oMZrg!N{q*YU2BM+b6FMzFn#JSpFA$PozGl5Sqt-O8d> zsFWFa1pcKSxsGLf883KAO8SW`q_fH^?T1{SkO||u;N}Jc>5bfB94ct(JyG3Ohx(R~ zPt>J{P$wSl(+$TIdiPFj$J3fBvbD2&;JY3lM&Sg~&F}Yg)}+}zg6Ivqka}N*=ev|R zzr>Rj0Kr0HQsjD*&OrHWF1=>&!r^rfi3tGc_+3UUJ@sJIUab&y*1-nabuq{gf%;h0(rVgi!9+xZwqXr*0hl*Ahtj!*@ zQR^g=X0GHwP*YDNPxluC%xZs3A0o@KjG~fZDGVU&QIjMFG zb(!bw3Xk**$u9=T{-VcYwVqr#qxTxV&GH&{Yr80hQX_|&=LolF1p`?u>&6z)hteJr zTO-#WP0I}0TG~TFNHm?m%y_`OUA=F#KB$~#;;8>P8TBNkG8yQzYGy{P4g(Xo^5bdn zJ`ZJvFgQNgL~=g$)>t_uyIHXIfVdq~$~|4HSpNd<4IrUzR=UB8;(eVYn*3qvV@V2HPqhVcpWtvRUY_kiL_RH0YmC{F@A z3$sFN&9%|Yo={^C-ibDg&a7wY7;C`}DSJg=iuN?@s6X6y=bitx&~K0wt0*L9Zz&

(#1;dVtsWcbBs$&)<|9|GC|s~jOcLyz*k@BG7|5}qUU816y$5wxc$J?Q zJ*ipbGwe7LX;CuHJ}kICKFQR0ri$_6+VZ-=rBZ2y6HBxp9lVO-NAs`#WPN^dzYVZ`$JH zsvDf=M3yo+J^$|FF#Xt?d^z&{~}8tJ!c`+ugDkSOSb;$p8F~u zki1$^fkEMfM1Seg-{T3Q%jAdhCrypWazT0|-n!}hfSe5y3b#DnTk7?M`x79s=lR0j^%w2e zGB<_ul8VOJT{rFW+#B08XPd8eS7-me4`jJ1pwGxI(O$V}n@@5*j=XZasi=nsR4T^Z z6w+%BPVgLz1n2#~UP3GGt?35=eu~?fxYv5~U;j5Joc;!vY2VQtIZ-|@S1sM_sOwHE zBo41s?aP`@!MdK6z{U8lSJgGm@+lp78K9Ph(V2y%$tD!AN+rFfl*ko1bvnE(Iz58D zvk(^XLV8jZsPgVx_2iMjo{>FM#mHG1-QvjeDqHgSsy%a|s$?|>$h2y0nVd=lS<-^> zbb7d)HrR)Pem?6s62E``GcBKX){ZT%&jt&?jZJfLe7gA~(icA>Fx7DkXjziiW+Fs^ z@3!iBX2gs326XngWydCrQygrnFo=hyGb0ZbU!OaaZ0x5O_YKT1X#rNvR&5#h5XYh% zfLnZ&d7noeo!1<}$6N?7<4Jeb@j&+tq_Ih_Qx+(M?>7g)!$XJ%yAaQ+A zFa|8b0drguGN}ORrTHM+JA8V!QpIOMHpaJ{?DrX4i};(oCU?Xv zU63$*L9m%TCY&+BR5@?vfX3J2q{Y@WL=P%Xr(L&F4nQIk(vHlw@qb z^_8|{-DYF7_Ps5$$+@(TR}v-3+Obgyi(Gs@Cq`5<_m-t56-?!Odc=0T42Di{{4%YU z>lePg;Ztush<7x(C7~07K>Y6opHV>b1{izF&^Z%a3YJcL3Q)=@dYN=%vk;D=2sYGK zc(N`p?o{BmO(+wNs+%VOoS z?7SUJKhd~hE`juw_&SSnyl{JO@{Bk0+lB-y)XtFwy8}6Cp#?R zapzIJ0##?;=X!Byh(RKys93_Qqr(Ik)1P#>bfTnHE4039))zWs;JV9BV`J*I@a3`4 zPblJai_A0}`fVB8Aq^}X!OoZ{-G~Y~9~spTX0&HH1 z*Gk5woVe8H6-AB;Ua8IN=BCZSoQA$W+;zc}F^+ab3sH$Z$^HGq zEUlW2Vl$IFW$xQ(dwA!}iyvJSPdA3b1dK?%nLM&J9pbhS27SL~f7J?!bAiKoC_gZt z?c;BV2Ez?W!^4-=O!T&DMMFjV%Kapy`i?lzt_0g>!$(QcPFb`|^KN^P*dDRX&d=YQ z1oNz1vff+>n2(KoZ?@&hQh7T;s{GkJD!_TC1<|ZTJp=CTF-1i@X@OME7ISToUB8#4q`)Pi>R8EVm(y+#{-Zy zYw!GVwYXl{iv5kvIvG^A2$4=eJg!0E92OJzG7K}~3V+>jt#W;?ds3LKAyb`ai2=_p z?zj>HjHO)wrt_wuH52)9y+`T}>-l{@RWpQd$*30vC?#N;TECdoS36d!anAn$tJ~Il zHkI-VTkX^?i0$o~#(XGK;n!UpF)!YGCY==le_-n19f_#!QwV?})J&-M`isLQzG9g4cNoy`ny1ATWWp2u9 z+e)a*MzD*xY;4%7rzk$#!UHC0H^LMANjOQQZ-G5zC1oM&2kvD&ano?WXo}kYPj#^p zj|>v3q5=FmH_S?NJV$3)X9x9oy0C2e9AA*h)1z>RKS-Ct9EHjvV_jJ={YHhtn$5is zQ};No!$!U3`@IA!o84I%Mx;K$lHqx{Z%ECx@^=M-v}LzbWGDD$h!a|g<*fc;o-i#? zJ;K{xz95^h?bW}=#==8ckE~kRZ5>eitb;FRJskO1`-6fP_8rYS?arsQhZWbR*Iu$F7prEERtz!p7p%STaMJ_%rEab7t| z_wnrYl+0)ebG*l8#B**$N2_6h{?8nHPxT%CipqjF-9 zj=wMC(`TDqP16VJSKW=4vk_(eGG$e0P%LONI43p zuBzIPrK5q+jtQ9+)uUBO3m68=If|)T>i*X0SHoPqr5gEP;M>^wqps8bz-H6O*o-!- zqllMdni1nHE1$Z%q_xr|DbFrNU?sf+>?f;g$l1HpZfC<&F$855&N@^f&*v2(11tU% z+3XzRHaV(KrPZ3#iCKY>*#r)Ms+kkMa8UWc4kJk~J8(8aM=yJk`2cHkt1!r!Uz}a} zp<?}Mb84Wm$K1S!_I(ZsrmRm0zPI&pkCo`Y{IK&F zFf@GVykgOAyYvs5`}PGWm<=W@x@-`}cV9WSR6uDc7tI+=PRAEQ^Xk9bCI1I;{Gj&k zozyvoCPN}|hz>ny&zZ~0_B4VK>cT1ggOc3|P}_>~q69sWlOA4tPT$!~ z6J2VPH{#m#Y!>ZG(LiXyC=M%?%HMlF+IY4;&j@OIEUJo&$TY6?=Xi_l9}1 zrg)!=d`X_&n0d%v8^tA&$;S(c635AhBI=rpkvHyo(~bA5nLS|M<*Jc6foW~h&14Y- z8)(WqHzmOoi5CdZp);fst}-jv?dQ zjK_V(GYv1gs8kguE;{#w3~eyUflJvny`iB&0jdr8y5lp3$kD{tk(G97z_=>#r()a}KK(M4aZL<0kRa7dP<8i6YFXM@T$ZJ2iBbJ!<9*os7@A+& zpKFMba`$!#?zVNaQUtnK1~_#Q!0&~P7`0bPXx!04P_tpo2A_5N%sRSuTGBpRW9kkq zhSQjs(rvoCe9*oEGHzJ37a5xIi)TJ7e$r?(l65=s=$dhX($&q3f~mhPxafzgan;G& z&}Uc@dg4j?e*Y)=S?QTFz0{UG-VTY&^MvVrdyr`u%@-gfNZCwDp8htvI6e^SquIM` z*d)!&vY|cKUH^9PVYq==%yZ7Yw=OT9y-apS7fP6Z45q$SPXT;V$E>-XA)Ap364CdI zle0LK1UEY=9!O9v&*_|u8SgbEiGHlkXXeb{`<~%o!hNE~g5R=lF#KBA;=QnN{1nD+ z6zwhKH=W|j#qfyg&0Vu7hKF7-9!QTGvy!TUyKL+j!1N5+Tm3SyLu;n4$^k>+vEHhM zifosf{?wZ8Ad5Sh69Dg_LZ*8&y-R=V8I>s+Cc*f1m$IG65R$T)&6^HR`voBb&7b=^ z4IZpZb5ni7u=5X*G{RY-M#qzgV$b5)$XN`->*Znrpb8E?Ls#j1o>y z_E9j^gX@pZd*0OlAU=ae0Y_nS(|?&FGXJuy7s~f{@DsGSXWbo={ra=xY$uj$*mNPY zw>s`HB?c(RyZZ}ZS`TNsRwUL05&b;K-||8uqJ|R%(ZX&7W^`djLhdbmsg<+5{J6FA zINIpQj#ypd_+1hStz~uvDt$kBy;EPI@XcL?&jQ(Y;IAs|S{DPTKg2oQ==eyJ8#yyd zLGl=4rsXJ5{0Ch_z;7^Sb*nGk_?3(SE$|5t?{p?GW9|y0)1js<<-MgGob)pY-Xk_v z@kTjAlq(5-OY+&Kwten#rKy*~KhdS28enw;qlspZe@5L}7 zi^u@)Bd@EF+Q0MZlTt+n)o?f4f98gYHz}U1`imRFi|ZB+n4}@5Zqd1udlD-Uyg{V{ zjD8z9hTQJqGx^JR`|~s6bpxnebgecwD|NX_#)iGku$omzAGaF(`dljR0`$m4PV;fI z$C|h*sY318Cnl9KEGwsnA39$%_b&=(gp`#??%vmC%D4$B2<+CKvK5;dKc*kRlA8>b z-ee_&ntrREC^AIjL|3Jd(R7czXJ!@e*dNf5hw@O-iC|xAq1c6_RBPNYiCrnS)&`TJ zD@9;<*bEJp{|-T%Lo3G-Ay+@)gXT>HIC$p?jSA?!x^<5+ycn6+6+xv`3ouxWr5shP z%VCdXWe2%k$8{yHZI;*?Y%0U*JNT0aQ`7-CTXJP#l2FW_F|P|>fxCiol)43-K%=Kk z*STLJd8;x&by@E`47N<@0YvYinm_!`WaUw}>cDklJ*C!(R3l-K{@jcrx!><{?{q0B zq;JeR>T7~MzR0%pWcc+xm^E~Ij?blse$mi;qKEw9@>;XxwTX->O=~ND_oOPiHS(WSa?;^pp$+#6pT$a*xqJ-rRH2%94|XIPk>Kmt#z*b z5Tpxe{dORw(qqIG=C!dS{?=c%K(H0^vjf(}04b2k-(iC&+wuvCWsPByz-4ABZIC%{ zUoFGs&aGoK1LsZkyg}~wo3WGnoQ8RMcsm~@wn-#^bvE4oS_Vtys z_rDtZR^%-0*q{l$8}F>F)w0p4&sLU_`sO-#oylR99gart)JjhYBDGhu5A1L9xa1Z_ zBH~WFx9;k{B=4cDPI~&`T^?l|0L7KMxX*u|j{@O07D!TdXUbwuefa~-dF{yl`EhLL z(u9bQpZt1rP<8dxm*D($#K`_;jC<9e9JR3}3GJF-FP^meqEWnn1(dt_OPzn0e(n*& z!0JX_DW1+?3vC(58HxImG)?}}MejQe^L|>!Qi6_#%|GG6QYLS=uEd9lKrNvS2H$!% zt#c)iFPHFO5y*UVntJ801)9~XuY`6(c;IgON9)p zi{?I>q+IaTAScRE^$7QAaQ6#}yR!NZgH-Bf67wV*X>Yg7N4pWy8SnK`x32;TWLqsF z5O|MHM7q?shhmMsgLfx<#-LYN+DQt2p@I1k%9aBJ#_h7O%2uHOQ(4Qcz~&lPpbvZA zmbk+*UY;*bIsO@goGA3>`@LsLgfk;M9}4H+?Ps^tlVj-rPZ;IaO20LyIP)^aF-PJYFx;@YuG5^^jBFQt z6Zc0J{9S~Vy-k(8V$vO#nOK62ZDrpIHu?EBFLHXH`j2MQ$CcnCBG^QN09yr}hsKP3m-9Kfcr6kPqScP?pJh`@O5H z{vSxQFd%BygXs+pKIoqRyegUDL$}wR5_Vn^XA&xMH4=(`{zt0+5y}4_PLn&gT9N2j z{wK^2m;PCdSCf1FRme^wEip})1DV0Wh5RW~aLB+1FFmJbF&aQjn(^z6eH1cqdS#;c zx2-#2%SvNU zHO7o)d+u6?KyNNZY)Rhe5kdRtEW^ZX#5H6qUHcmudBSK_x;akM&`=?KW}4 zf3}iK9W`n}1(?9^^@~;^5^XC|MCKXO1ctVVl4DHGku3^x4(=e~n#S5qCiy-CfbQ!C zt?YDx2!D-r4vHJGKc)wwcdJqUl!Z__oP{$A7KEJJ4GcP$n|?w5m6P-KcnVlS_gjfR z5*1<7?ch_OO)Vlsl%1~38c4HV5a5t(sD9a8BOXzIcdh&IBw1HdP)_zRE1_*bK=AA3 z#{%uD;pJc~>$qiDv3qkvhKqgjCexYQWlDL!v}{$8`q6b!lAi*E6%j#4M@3k2b{EQT zi+@Hd^l%ZgQ#hRUyWqIkbaf3TuCaZ(+8e3Uea8@gM#X&7i9}#>TPxg|p3TkTf1D&% zNLo(Ot?Y5$v({T%TvzD%^f!TGu4&WEq(Y?5-WGTAl+S9NgbN)kT)2;SCCG03+GKPn z@E&IDmEV6HvydgaTq&4#r~Ynd12T-ZA<;3z`B?ztrqT&tQ*;l=xx@nM?+5%xVxn#8AK?zXQUe>R?U!>~x`V7{ZK;>LW^n zH}h%xqU@cWIuc^8CAJdjUY~1nU!V)3J|7WNi)3Rn#>!MC(=X##f7=569KA~*J7$fV zh>U@9VWf`_#OU*)vTmxRQRvXl?a{u8ndMcl{_BH1Cu!_div1|{x0f{a8#2sMv$ab8 z$;pQKpYGNmUzLiEg2eR$daax5uA+cK?r-MiN^}Jn;*OJ0|`ssV{u@TdlQY$vBKrc7SlpQVS$b&D7!jol_846UwoLNKM64rM}Ts!pDN>shtu5=av+nX=}id&3o$l4acR2$Kkxb2{`i zsy#m0VEi$jdHEMK_HNePwzsL?eNdbMc+)NTmkq&@NU(*q?l!+1Ow^k{wya%|Kz&P1 zl=~9##D+^o+2LfQjLb51qn*iuuxrHxPV&ASE`_?Sh?ovyE_qmS3Y@~OKByD z=Vpu~z+hI?-j#2TxN;)1GaHZsD)TD6;!|@wAW(6}ZLK+7pouiPK;9~AkA*hL(-x-xAG5XLPi`xs_YFXt9P$ZJAEHU-q>pH8-8OCy*BTnk`GBMb(woD zBh8<(>vlO3hyQ2ATzq#`vcLv%a|}=g>U&x7Yz+ zju@JDi|t(3 z22~MQ`0Z}31e#sWlg9(&4SetG7_9@q1<*JUDFyuXUG$l;p+hudvZ+Rsa@1m`5&3GF z-}yxQK^d7;E;8%&IF@IIjHY)#YEod6JJHa$u7sXTV%7u#hxPNHKZKS9a7!h_ITwFf z7v!~qs1t97oUb|7qZf1whfVai>EZPf^Y=G{ViCN&A8Y+{g?!IgxB5GbNdMx16GdS< zsITU4R}r@_wiv&+9*sR)Jyc~MYpOsq6=sBHAdYH23?3UI65r{BW28~~gX;Fall zTlR20aY+P@r|Lw`J6;7wwA3qgR>M2N;C;ucWY=YFWU<)86AC7lH}vFgrH`4t`J+?(bW=zgR-%{-cryPIo50djp+-W8N9T_m+C9 z!wOe(E6pPy{87ee%%0+soSi@JcbUN?J<->9YQJZN>r=0-`mR2g1mv66Maf<kg$&P4`1tdQE{+2(o3fVWA zV_bYT`v&IYS8og7x)+Qbd90MkvXk#WuZp+Q{<_chJmTfF1MdA5jGz)BG+cci-nrl| zSsjWgD`8Lhqt}|fWf@&JQ7;{@C_ZvoXqr-aPYdRj39*fSt9xLvcF9O?$bT$ipyU02zDAfN zXK6bvl>BaHBUD%Ef1k7^y{wYjIK%=R6&O9KG}Yi6YMi3vco)x_vv2Jl9+X%79@S&c z_AP?*Alg4WRS>wYE}4Yvu44w%?K!g+K{o3^ZL8_%vvLEp4O*Z}do)t~yRA`-)fm?; zpx;-q0tE*;UmEy4UAgOBe(|)?%|;xIgS+m};`z|@(Bb23^;f(Khohb>ww$$hS#1v2 zAh7Q8j-ag__%)}VlNl~rz4uBs25ze^+~KYH+=7$qL*^ae9sV(@6t&7aZ4Pr;%9~HP zv_Ev7K<5FFg^F{Y-INH`eu<&ylC{w2d>EYelJ(vz?QrZfS_}y)+JyZXq{eiMJ_9II z6|bHRs?UF{i=NTvp7Z}Xqq|)DsTYv*;A;ZOt6MkTM3&&n`i7Bg#4jFoXCK4+!EdT0 zCgR!2b+b}vr4mMzB38t|&mKqPmi}oRo&4LiYk^YYev~pOZJ{Gd5Fg98_R>@t7VY;qd<4P^*sLJ0&NCQLUJQ z@0f*h$QIv>EeP6@bo0QcGg}@U`VepR*J}EO&q&daJ9uu|(FIDizz`$*1G(DM(glKa z8|X((rkCprY34WQG3pU?V-?$rVK_gkkRMiL9*KxwWno!gzd-rXDShfoQr+^Jdd1JV zV>6$V$+bc{u_@*wdm=yE0Ut<=YbbwP6Cr!%nyTtI=B64PVRjc~cVi*%#%t#*>QZ#K z_y5piN#zuaAoY_p0KWq`)5-!Mv|rXh%aPc=S-eoEX~*Cyn}>3L9iA^RPk+_iwkFzi zxaVIIfQ6@+N>jzfdvii#0|u9F{7n(@+`-!0?kW6zjg2+txmd?lS7N|D=aa`j8F4JL z!WhiaV94Sn+tk}bV#|86W8{SFnI04Ak;vvF!)*7;ClEO_S6!<9IbRUtx@jmA0Bci0lkjw5{``p7dnN6xO>0 zEYjahBREzyH`J99qg~v9LQb1qK~Fo}RbkpeOoB(J2QivG!gOq%w^ZA8?oTYTiQlCB zCiNT-hhI}Lu7%44EKsocPyTEq`r@W)P_uw5BMqrxtl63+HAP%)efR*p&tj`Ba@X2M zA?9TT^3j%IPYt%fotnZxYWs$E*E_ie>BZF9YWWwG5&k7HQ7-&gON=c!s_zu?$pkZA z>F#P%^7uV59Tgy3SANM5A)5AJFsXGZZ;q0{{Oe@aWLYn`%sx^)zw=NB>`>)2l>(Lh zeK9fD+y|R3!rzCK0hiYVU3UHIINtYf1&l@3Lx_nJZHakaQ3LCdT*(HZNX8EZQ4`>I z+Cn`obql+*==`MME6r70Z_S@eMWpAXhu$1eoJTu?N~rum9ht8_WB=Z6|6H;afeguM z59@*s+ID=skEHgRGD?}3>RCAhUP+?;oL6QMgueo=)tx&Zs$lPvcKsOeh|y(kF+07* zF&*(udXkyd^rOGRg~s8rH8d{yV#YifE-z}9mvgI6lh1d%9#TH_8qsG}-Yxmzegx2< zE2wl-lQC~LjlZPa&zt3|k43|^z6hMUNdMWSFYB(yjJg-x z6>?d3D$C&Ug}+U81=Nc^K8SMY%c07_QHdgf2=>?NhFB)YML=7u`-P3up6z;u?(dGW zj*~3OabtI1d2KK30|f$)Vb;v*(odQU zLSJm1aoyKlB)@_Cd4g&7BYV%5Yh=0&rR?gy{+G8FLG@tlYci%UiSTK^W@Sge-gH(Ft#rdZ!Mo(q8K_?y>R_9nx+IEY~;7xvR8aCE2hMO2TxjJcy86(Es~Qqn{ukW>)t4G<;du|p|$Ldf6uQa$^H*`Xz(*+ zv>T|GPNj!#5aM8C zRU;F_+Z}_9LppriDR-9S@(8O=@op~AsVjFshAbm%U}o7ew%VShv=$8tB zGG${1b6TU81Nm7qI%OrRH)2Ar!>`fCE_Vs>{xE_A(OuX0&n1TJgOFB!R< z)()IQ-PIGAsP9Za;|-SGyG2l#Su?4c_tMW|U=`XL+7x7Hf)V zBx9c}`l8glZl`1+mMH`)fGt|l5mA>B<{O#{6#N82r#^ztnjf@>5=4VW` z<_;e78PNs-_+3t~%*L3YJ5HM3>R&Sc`Tn++{vl~_mUXra7XK@Gcf5Zq`(F~E&u_!C z?!t4UiJrFmaT+0=-ntx3%!f^flKqsvs~5u`QatYbs)exQOU%f8^1l^+x)B zUkuQP>@*_&C7GkMpi1))UF9E#TUi#2*;f1FQimq?!bid%wTSU|)Kq}1w;Wo}Y_LJ?hFC6GDv(K&c;Vk@!?@Q@}r*c;F`E-TOw|cLukae=49SlBvqNZMYhmQ0lti)$H7f_}R$Q{aa+pNH!2pGV14GkLI_?onF~zD&<4 z9~}yEXrpP{J5~w#f(rq0`pRWL^>xLxY&|5wL6NGbOPfK#na7h4n4EgQ9Z4iuheeTs;g;pBQ#^KJdo*~nyw(aF22uy zq-R*mm5iS{ApQC5fMpr4DfpRqHH;7a;$#-b{2r@pt`%43a>X;{!t^D^^EJhjbMJV~ z8%8JsGvl;l**Uf%v~Kz=F!16Hs?sG_`=zh3=j@n6J{Qg${QcYe=W772_Gv~h({+r+ ztlD)@XHkFcuL?`8JF;_c0d=N%e%`_b9PgJ-cS`G7X`XgwXw=kgq5+mNN@Daxm^n__ z>dhx9%XpcV+J&ADS~?NfH__HekDH{DVfrl>PbP;*&Wh1aZG3O`b7;txeWJC1TvP#&;ma#n$tBG!TSySW9Ln$el1*scYIH8}=&A(Ml z_pR=$WZ(RdkUR%#{~AkftmL){ZoIebFFue0Zj{C$%&3lAl0AB8W5GuLA_5#G^(dM4 z$j40C2~lIUd1#Z`!I+#JEiU=kAULz;O$8#K2i^@$vop9S*}LXT2@M&^h|YI+9N@cnZge)ct~$2g#}Ul94q8GnD~)zNvSpA@5~B0$M= zg^9w4Z+!$0T>~u{_2hFXo`#Dg1^t9oq@>xNPR|nGtFd8Ulia~`v3xlKC`3m^G{a-x zSxozdZX8(Ex8h_ypWN-y*)Q4`6Ft3l5vv7%>a}_P{s4(-CZr)3O%E~>{}A;$M#_J+ zu#SS)yeB0pzn@CT&_$XCvd(fzck;vd-$GoV&d*))2mGn&S0}-&`qyH~$C*NEEAF^w zQ^k?XDvnz9nZ~eE?m{F~*9y5d6-hPymfxV(W{>V?_sZ9jx`BBs$m*cqrAo7;jLxgZ zXpWTo6Wc(E;>6yYRG9Zf0s$n7%qN7tt3SO)!LJ5Q8ltJJI$H=iJ0ChlA4rUl;jHk_ zcj?EpgHbbWpCp+1=lc8GfBkCSh}#V0+aX9?^Vj`;KiV0EJ1;&jp6a&XC^5!0I!dPf z{D~?F=KM6zwZ^5~_28@5W2FE`BOrKu-jM-L)N0XQ)M-3KU;3sR$1;Do+CCaz5&ol3 z@$_%fxZD72G|E5q@bn*$tA^Ez*hIK-e1{&J_p3`Flp_Kvw@GLqj&?LDM%tHLm0LS} z6EuxYRjHTQnyrelCh{CJC7mre1k!ZP^xHBerl8O$i8<6K-L5vsk6JtxkPa~)S1R6q zsQ-(Izj@cb?xaXtijZ+J8*nw|Q7v9M(1qJ>29MlY^OM#d;b2ZP%C7byZIbZ(WZm|km z^MU2#MhE8*#PY9At*aui(P{6;`=nYGIo&*~MIw!oa{F>?dpOVKtx}q$>7N~S2`QAQ z(t9$ok)&O$NW0R^{oE$di7=earkSlqHM36 z@3gc^QNf>A{qKf>Ju}b(LE8Zq^K;FCn%!W%;yV`0C(NhFe5K)cYI?7q;Lpd4jr838 z9wJg7Yz2Wc!x}p9XLV{J{MCL|BtKdW;uXUL566u{_ebI6Hx+Dz{08dU zTr{f%>z&lA?QbFKJ=)c^chtrPaOOLIT2_9@Yw{cBPLEM%?Y$N zW$bcFrPcFpBgm7M-PPH~G=0FY}*wA-#kfkid zi9xH=O;Wo?L!5yDK{wP-m24{3;Cs6^tJBd%L~kn zBg~jD|?*##*TGcu}JgAuJuo#LAs8C*Mn=E*w?~+hX1G22LJzk znzzTzEk;o!SEa%3fri8mDsLy|anZYf{=XzAH-etKqk#?TLOi)c+(CBe23JIKF)Qci zEwz^3X|qeu?WTIi8`0d(sI0t9wvm+M^%!S4MM4k?T#ISn9&Qr#E0jO1SCHeCp%mol*2TKwo~9B z!&52o&YKrr2PR@=)#YB#qhx)~!gCjK+Om@tOoBkS=_WCQv1Ake*h?JED*PiW(3RIB zyFCfxm;&YMSjkGQ`N<{1fo0cBdZz}gAf4_0U20^-+f(J^tymxmfWiCyOag`O|B-7B zo$LUZ(S_j)g1|q<5<3?FALF)&PH5S!>$cIh_4|(vz7(sZFZb~KQw6=(GmK6sd*&lR z{-Kc(XqA}b-k*OhG_Xt?Q-96I`r!Luh)DeT=N{wRk@wcKfh@TdhUQK4%xv>sd*#ED z{@n2E85fUa@o1l%>oUY{`%=kI4Y$${6W0|HkM4w({%N5!nJ7$>F;wu%#zxh%czUlr zxwb#Sn7!>+WdB*Y0P^EyL@o;lKIRO;Wc@IZqz~Ylnn0CF+0~^>7;Q{S>c*i8i-pEd zT;8_5e4!347#ELG-vBYX;2w287RtZTL`zO$ zSi|{;tLB3;_vN}2P^)*h5OWP#=_18UZ^eCn^Uq{b30?w(y2+b8MSrA}WRcv2!RqME zls2=wWBp*cDxgh#Rj?gx(PpkPab)UIaq#Ercq)qtZ*L zA#{+Uba2_7-EY77PImTWXZ9>J?@SKfljptf=l8oVGkEYruS%#7M^qSzG=Q?Enk~p} zf|XP8htPLD$N}mxhU?Uz7+W@iu3uPOG>?sb>VC&mNxfOz``pq|(-gC3rl9fIhAcU1 z;#^~Bd}z+=`mw9&+fbvG+cZ=`3r~A4u8HOoF#^ME=S) zPVG`oFvtUj5~p}OdE-77?pWL8zt6Ob=h!jzrP3o&`s%er0@7*=qN}7?6@vKFFB!qH z&?+sNJzCzXlZ$Q_HVeRv*wV)LWJWzLmepm=+lCZyaSO|=0$&TW%2jGu zHau=R30yYbZq_nic9ux@I`ZaxG=Y)Xu7pN4qqH7arF?~vp*|8TA=U1J`|j2!3AE4$ zr;K%nj@9~hcbuA!E}q;{o&C%pPrTU>`5MPS%hQF>?i3rtA3Xru$UxBT>sdTZjvvjKh_>xt{?J?KgmuF&KaA$ZRPx(E-&1{gE9O=Y zR3Q1@&^zApRBFnBvDBa)M}OC|D?FED3#qCzjU$bdS(1>Y?hB2`hKCjo#Zy@G%!gs9 z_07N3LA+Vyk@Mq@h7NoCPLtPt_kQ*M3}95$mM*&RPr(RW(z+H1pT zgG+SlSH#Zwed8roq2=GW0>rXW5l8n7>TTvVSFrO_ym!|oV1I>3+TJS{8Tv20omrqC z{#)iu27l2F?AkqUSm>=B6X)`^Du(o`shW8rhj6(C#_EZC*~es6?qe8a>A1!j<@*;QTEq^UQ`J>syhUa?Q-;NoOYasUY*$-(5pTb@Gt1bQ!Oc-h46pK_Z z@_H8>=^fLJTXpxlIk*80>_D|oD3hw(XqvLD4l+YHc#5@fdV>wATG`EvWP@#XsnplL zB=x%2lVh&_ajTZ+H>lx*Oc6h)%Z68nYEnpCW{{?gw>sZoLhI$TAP|>o^$pFJbEo&v zGlF}2m%A0>POHVeYc1}-_oNnO%RGa%7Y8IiXr}Y$tu;xl|Ahto5Wr6!bG75D7y60x zP~6A`U3K@G!>$hA#*(I~9MIg-=SGA*{%sxj?pj57>EywmPD*Zxds>`5)E-28eTgp7l=9&QFze+nn$|md01HzlIqhka@;64u zvZC8h-x_7K3vw)!*Hykv&6V4;q~#VC7izKSeKfR@Em6KLD^-+;7YgW{Xs$=0Ge%cX z;f1vb*1P=nd#5=}(tLk^qJXZCz1mYI0%IY7-6&CgbOvveYsHHU$ISM$t9Z+Xh50(b z5J?3SHaNV^>RrAuHv}C87D<7guBke&pA}>ggO`y!UtNoH9 z#CSc`IHsT~gy3|(@tuaPbM_K;y8@sy#rq@8HYOt>JLVcd>cq}qJ^MpNhg7kj`=J-* zvMys~w`>C1OPCs{y3r&)Pl-VOS05L3aAfa`4?H3^0>6_=P*jTKhdrCGW+ zE-axB4|=7n@vGrRRDpFETdP`zCqro`wJ4<|#+OBO0q-n)B`9|EZ*u*2J@06ZWEgRS z3F8x~EcbljeEY>zwYN4>gQeDd{l_|Xx^~tw86DntC7HD&&3szMeM!NWKV5|yU%z1U z1U|TW%}UrEtIi7;exi^A#`zTu$#WJsyI8hSMDnXvXsqDRIcYF5Hj(W`2Qf6#td{J_ z@x*5cMGiiAT|W{L>ar#7NQD%R=oJj8oAlQ47Mexm*73T(!I5as*4ZVX0I|wL<$na@OIod{viWfulEhAxx_<<3^eR<(6S(a4pi-4Uol>qslimS&PnkFU$!f5ak)5KGAAdPI^-fr zESg;SjQe3Sz2NUCqWZ8zhff8dzt0bum6@YR0b~Yda8k@9xZwt zm9DBE`f{APXF`|am_JS0;|9acQ`b~=K0XYJ$YO;&JK^h-iYxIDbDtQOHw23si{1nko`EL^}E2v?|${ z-Mp~-(>3iEgbnbFg{7ec$_*GSr?n(G<9vCXKQ`35YwN>~q{*Pj^eAA=XM+A`Qm2t! zzfX2MgmIXad3+9Fmq%@xvfN*bF=_|I5%Oa7G1 z)$IsP8{8cv5+zZi;+;1tV~&@U8~=+s;nxKxX;Kbf#nmX=Zx| zkJVh;nqe-Ux4vR&;5(V%o7$gwNj5>^A{>-k>P7DwZ_lxT5byg^5Uin!t!=?b-#(=H zS|Ypeou6Ys=36xwkAY6Zf1eP=Tc-E=fgNviT{XWBg`0ZcEM59UUW{Pnmqm*F6*yDP z-pNY`KQeOSmUNcPax)7<*-iC$6te^D_&qAr_*j`d`PHRTBiX!eN*Fa^Db4XopT@?r zB6<%>jG1jj=lp_#A0B0T_)wr`vxHi?qKjm&=`TqT@;-d2>6y2Q5a`Ch#e9U_>pD1czbRkW% zV93UG<#6o4d>9%3^~Is{2M(w*Rez`GmnY?*m>Ih6TeBF7i@~t`A%pNe&65%WXgAnq=}-_7gZ`gO0RN+6 zA$7{c+Wi``O}^){^45W2A%QY8@gKqP1k<1Fp6~MLm%A6=6lO1+EM%fo&Ci=XWgPZi zwzfYhusGGm8Q1QJPDOJ3pDl*};~%fc&gi0Pms|Sd*`J@A4ofJH(?B0M(KxP9Fj=&# zoy6^TmjUN7`TE7);u|Hw(Cnz0=y$)pA^62XTdvXY+KDYs4Y^0v#A|bmE>0wE4pkmo z5S(N0xr~mEwLu4+M-h0gracy83|=Mf9lj9|;}VT;o%HNr`m_4HM7@~*bYGh`D?Y7J zG)ve_+iZ!l>cV(xxx2{aprsKiQxq0ZnVF@lUY*_j9z3(CsyKgN@ze9rO2QSxmhC`` z(Yq)a#~*G6H~4UxQi&qXta?r}!J_qG^U8A!~B=-|7p8+Rh1I zA5_M+B{jiO<{BC1705C_KWktIh5<3!j|}MI&hONK(*{j^CzPJ5(vsO4-yJxEE$^FW zIl{(MCZ61UaZ?jDXlS9?f=w192o!@%PZ_&5)tin@{C}kVwMau_+}E628+xmT=V!JL zL{&+`4QPu@&QW9C+pJMJ=i4(fls}u}uQi6_U{iWXil{3F6-vZ0l`YVZ#7($KcV)n2 z;BbK1!;Fd0zwGV+NkIk0y~yk9a9DD}TC}w0^fv!hZN|aZ^P0F+ZCu(WqIHw>X)#6( zQixKqNCi@$A=kHsWWv|`ZM@ZCseKDBAhRQjCx~|GtW& zKgJ-#6s5Q1q%iIPjV7E{;C3^h7y3brAmbyEa~AkU))iV{($83nz?H0747)H9%Ek*( zWr9OIeZ7XG6~26v(=f>1AJU6wE99=o(K3yWB8SEL3~tBA-`6;RTqVSA&c%m%y_-YC z6QgUJrXF(O{(RY9fs9Z&K}yhHP^)Lh3h3jr!7D?@1w%Mi8GVf@D*IE|UZgbAlKvu5 zNJrH7?dE^ct^<8^RR#1Q!Vtx##+F!qEf)oG#5OXpf6C8E^A#1oBg`w^&g>fFo3dt> z^W^KTte6QH6&R&K zX$%0W2M1M;1-G?h(0ZUOqPaWppUJfVFDp-uvW;o_7f-o6{QW#b4ri|mFZ(-hq-UM4 zfUp_LWIR;Q1xjn&Aub*1y#J1Tgepw;nm;S~d#i!QNVqX}Ov8eI(Wi~uV8fqKMS!>3 zV8KEkxbEh?NJ8JJ1qN>)Z)>GU?ysr2P9$e%iZ(g3#biIy8&s1}3M>U^eCn`j^JtvN zEYc*EIlOdLu$#VB-@f5={(Zm;kny!)7FP>3ZgvM8n1>*b$JZZk`$>GpedD@%4to$c z%qsq?&0btL`dVr&pziMT3hkj;~=T z5<`DCdKh~jPEHyM&gJ3%I%?LGxA@RsVzQc6#`)dmGfvAT$8O6&Hbxo#mnvXO3AqpmS^#^km3Dd^$!U$Sy?}VVIM<(X zkGZS7Qe0;1?+Ig5Bc2ZbnnoIXZKd$4G=9mCqF!`&d+36xnNfyG#@`Q{kX(S=eFM4- zKbsl(gR^+0uI3WfAYOZ!%walKqAF5>e}D)TB)FeSOKS6&-qfK6&xXj0ds{%x`E}FCBB~y4e2gFNDGPRgtYy%^D0tkD{``GS zm^Kw7flVL2ea}q-q;|uP%Olv{JU5H#o1PW}qu-ZreE@uRv!^ zR2Jv+PZ!M83xlK(WXpDcN|K-Rk#YmxGrng2B)(i%S0{yg$UUEQ$(g25h$w^NA{vS; zTRO#*xi`pN%773*2Zv^L37V+ zS9!yvdZ;W+s!n>sOz8=f$KCgtQ~I^nye8EHTOz@$uajmv3PT=VmWdbqC@?dfeCq0+ zJcaoVY^fu>vTJ<20q8c~g4yurg&fEs&I&%PKvZ=L*&q6u1Xhs(n?(9yO7GjwLxSNA zL;I7Z70q59d2)gK*bTUf(yy6<(^?eROjh1r@byFUvW5HR2o%E6tsVq~EBt@R~_)_;v{QsnN?o#n>tL zg4F0X!(>PY$9I_5si6zYAf<-{bS-b%lJEI2}l*Mbtt!SvI(AxIWVb_MXq(wuWDuS8!3HX z$M$0q#Xb~BO%}d%>G}mr`(GB*WeiO1*OEl@I7NNj zgo_3Y78o(jJ8_z&1!m}qR5f^^CQ?KtiGfjiQ;X)W8?tL>a8*)^01BsbT`pheWU4qY zE3RaC5K1BXZ#BKd2V@9_ks?=J^%DY()cWT zW|Ed=lHu^L8@djL&ji$su77~N?(~4pa(MQw;<2x zggS@T+AiY~{v4Gn1Lk>q?f9*ZPszj=mxc;(sA-3sriGbp`BD^5c^-Ysp83!><#cu1SEbT?hguKwX z$VIVBTQaht;rTZW`kyEnpc$V@V!6MGUcu6kWXwI~*o9^sJLKIWrg&eL_}Pq!6E}cZ zk0^&I%u0)bQ(0YysW&6cp-dKubpn($SPJ;VL$ zgO}gwe%4_|oD!G%Hjj-q^ImW&2MNy1nW_`XSq;%FL;_SI_qSQcsvPH=CJ0Qm2G(EH zu@2a|f!r{}TnyA3?HG!x_r#h-EFi>d!ta)!@E}sPxT9Uv>!#nV&3dH&Kl+I;HqEN) zy7}`kv$sf5`!PR%TZC(|ZC+Om;r~2jUap=!8|#et#TkRFdQj!DVWzZuyZ@%A{~`YY z-ctUe8$b8k#V;D7ugueQ;iMstik3}P8kQ62#m7^u`MIe#^d^Lm&6s#j@u?gl{iQzK7?nA$PU9FAVwA;1dZv z3)pPu_`K0P=s=3&OR8n~(EFeyCmmv@tvryJ8fFld9Nq8kawaN(1Ezm%^KVv;%`_|cjA~l&=IkI*KT1v%D)=DQSm~XnCN}0vKVNZ+^hka>H%QL`leSr zTYF8{`hUzz8{Dwa1#q7l`35L%T-7n0IN2EbwnXa%;yem1d)N>`JuBdWw;$0%MNoiC zu_9(o`@N1`h4!2^D`AoxqVEM5vmno#W90-Dt6;~5Oyz$fIzqh;z#fI-6a1&;cc!C2 zpe!$+CT$ff;AZhlGnbjLi%vLB4f%yCA#t^wDabTHWT%S7-drVrRPnEQ8KipNSobSQ5GWkQi+RlQH|Wd)ZSR(u1qw0}e07|}r&1(c zP@D{dRFyE|#6J(?vH~gd!ng>HJ#9i0Uhyp?mNniw*T^~|K)#ivzh5pb$oeQLjMG(g zXMXCQy$E7lRR9%nf*;Z|Bc!KWGG;IZI2G(8-n$ekC-blm+7 z9%XAE>{{T7bV@qedMeovjXHo3YMEH&_*zmp*%V{Ozu4l{z$;U4+#m_f)*C-HoUbw8 zM7k|9q)%(U2}?Ci$`bH}%FLS*ff^i*TsC$jt2w3?elc*ZEXm!>vH`c3PxTi}AP3HX zcR5H4N5L2H{FlhBxw$<}-RBGY=e6H*RkI5F)toLWcjzAoT;45@KTK0>D{Yy}ZnS4~ zsG2nAE#p&==Ci9S1Bi$$J-d>hRBdl0{*d!dnV`75agWk{Ice-Zk>`ALQ`m7?-Y(~) zTcql_>upb^VzBTS14y%o(~D}mE~TR-tOzhNQsCZ&PP$q=f$sx$TSBd(+Vc7NF=(P1 zn!5Avt1F5kPw6<}{hU-B+ebkHhf=_29lwyTsJRkDo z8mHX+!KNOeRdRLskP|58r0@*(py8dcWl@Ttla}w7#Aej1VDYTLfgb{(Yn7>Q-Ltgr zHvV3deN;Yfa>{>Oy>103r!cVIH|^J@%4?%gy+}>|4|W(9I}(qK?WF zv`Q&%^e$#VaRf^FZRx_EnuLy@lPZx~2vCkjP^++B#n%y<2zTCxDgbvD)PT2}F0x+B z8}Oml@m~Lr`gU^zDqp)pUMDn8&W-23X?lM7xbnl!ksA!d80hfRo9!>{k583*Xp()9 zpMCuf4i7Iq09j0kMMR_z54zegc&Ya(<^(E5g;nZl^xx>424iaBWu50N6bJ>q<^9T` zzr)!%O5>9mCI!cLF}!p7IHjRFk|%>Y*m(N?!ZJM~sI(*QJB~LrQcxxMSSIf#iqm0r zHOhaWAne=b!!Yomj8u8vvI9OajF;fkQk7l!q_DwSWdrF6@9`UPIzt8;t1?w%btus!@ zVp!j_jN=_zyhP4F7SBfTWtGK1_@(azgX%Ga7CBFdML6rx26=Cq4US6twzZB~bdL}1 z`~_uN_b#Xodh>>qr}jcoxD7n&yFEYW0G~fIB4OL{ZgtZ4omHIr9Ku&_xpPI4yN_le z^k(fFYQj$0cv&KYQI&vm9$4u7kAo*I$B#WTb-ff9?-V^wDjGOKCWRK()pN8buFw6%CS=p@ zB<>bxN&Pm3msYEYlZ@U9>O+;X?hfe%TZ}{!lV~Eyxn}R5C>vrR*wN`6E%129iNS*a`dCet*Z>MA~J)r!-YCO47%+Qu4uy%C6(Ws2?1KxfTWXiT&` z05SyGkl0ZkyP23o1+=J36;Q28Ja|yLlC+>BOUAQp@=P9UC!`5rGNepe@(-+6$1?R9 z%!Qi^RmVi5e$ZMuRzpUWKK-sF--Sv1*@s|;W%>|B^B(2DD>{7(z?!A@^l8gcKVtUC zx+OFfUQi0KuqlEX?}6F>us-aGcf^IPn#;%cWaVruxWWShkMIM_P8)!|%uRbBT+iva z<1cT9an@-jc8Q4d5XVa!>n%8yotUBIWKD*Eix-E{?BJpEb)-;n1ic-oFZ1KM7Xp7G zd;)tVKRvOsThuL#3;vU2r!wc$8oFY09%&u9FIS zv?!wWhXy@uf0ldC*526(8r-K~yJ(`BjMq>4Sih_JdhQ&(39JmRvY9h1{w%bA85e-lgGA|Ph(Akm+9=mE!gVsc4wR$RgR;FaJ zIJQW!D$Zrt?N24EOCCF~9MvpLAxGXnXGeyhNfS$e>xku8NcL!#))T|vNe}4t8L7dl zn{a~>|9H+FT+oZBd)6nFO#butfH!&b9-GG+K9#D^6Fs;;bjr6<5e*vwiqVWV3uzlN z@uN()`7sBoN?&Y@x@VC=6CWz*_`r=lUfP0G;~x3z)dVDCpd)F2Zh@4%j8UX@O^NDr zF3Si7eNz~^v~-ZQ&UZrLMNIO4KkC1|V$GBD;2147etQo}tG4>%9h$!2Plf{8Uc$q) zOLXuVWxQFQ_PioO<2&6d)ccxNqI_)EPapi}G`ZP(^uGpojZNhrY~@W%fei#{2C#w`3fRsXk)hSK0!xz+2)`vc*RJ`%eSCva zY>VqkoM+2m=`WjD@SV%NweG;7Hm*4(RJ3MN_a5R^0Hj|-_>yP-a5zBT4jy}`q(I&av8ngnSw7Ur3IA==Pq_D6f&ArV>~ zLdZ4xBKmxq!0%{VbzZ86gmx1KSC|Hk*avqHzJ&%{t@ir(_jRPtx2GhEj{i{o$b zh;>aF-wAli-M zO%^P?*7NWUo9y-2k<3)ex_P*L6jpgAJJ`8g{O28+n>xh6!i9& znv0jPSPUHITWY5XKK$4ecUx5S?q>?q>&Mf%52v9dRX;+Q`9=0`pFoBIGUtM@{j0G| z)@6s9#d(aQSf+l{PH?1Ih9@lLaX&Mi2gkyx4DQde&&%90$#5i88550VLrmYqE}E7> zJJKi{E=2sj3oVwg(P+m-jH+Xo;mhN=yB(64f{)=(t!(C>{}Uyj?cc=z-YnA;lr4T3 z;Ym<^kW0rhN!Z=|$;exqlzivjoj>dU>qqzgXL=ezu0m?yxJvXd2PSCm>{XKB9r8H& z5%K*gzp2*RBJ^ZkbcNJ5F2XwRFE?JE_gZa*m$`(P`#-LSt!J-(9x_KbD(`-F8d&CO zL+#4W6k=_c?}!bj!4E@{`|OVvB1d{Ks(MD9Jq=+YY^s5_6xpb;9KFHUzHn%2GCikc5k5AyXA!wQR6N6anlEOp(ix>L()^^ z_~1if34d*kyBj=ZR}twhVeHZRuMcC<**?~zfs0?N2=BQmzT(<8iN6ug|2SnLnUBm> z85~X&olZ!FWHhaPl9?Fb9oG}E!3aS;!NM~UurJ=io>7C8-Vz5A)Wbny$DCApE*^*n ztP%i@Y1-Y>W$}aGwr($~3P0=$LiD12Y_Aje%w$DvQ^@-~7YU%W~IDWCjECchP%#70vknIxk z!9bi}HGPo|g}kY(L3L8G?RThL1Bkd~m7}*RrRTcHb6u z>svdtWs{xo{%^|=euAL~c?!LE_@mlkHBPM1{705-cxiq{z;WJ}XP0*&mJjAVZ7{bL z<>n%(PG(W{>I?h|zF=YkA@vlJ3gdoFpb^4xx~g{dZ1Ho9X|!Xqd8_|WBbuB{T1BLg zhm%PBMF9n%j6uk@eR#CA-x%|N&nS+lw(xY?-^WG>rf0ONIbkO3lnj2SRu`zWqsjBY ze)ad0Xh#wgJ9ayAcE0swQ!_ zB2?z?C1NH&3zMq4lOaTeIcBcfp#a>oc1&>%sG!W@ile4U$kf^6TaJ<&rE1&5mb_oKDN1x5de-dr@)4Zn)#3~#QFJ@5l zeUny&>67DSm4WAo$}#i4=F!IIy{=<+u-x1N=og zt(fLCnY`WB`A@qcOfs4Hl0jUjw{UmGc#ui<^xef?W~DqRshh2c+L!|>G|4VJYE@ol z7_~ND{dgU8Pp+(>LZg-Xl(Wj~bOH$vZnyyOW_@)c`k!6K{1cUoo6|E?HRouIbOkYQ%D(h{t zcZ}o!(hsD5>;(5ILqRXIC&~%lE2Lu!@v&O*i%4NiNBLOe{L_H9{Xm1t2Zhtx2l{vD zTmGo{7e~dEToDAXTk^&ko->6CsubuO8!SjD9oezruK(N#0MKLbSSMnQI{BBP415!Xp(DP8%xcD9AQUY$uk~D>wRwy>MoK@i zB;C~A-o8(uzjIhp^h+2IFayjFg!&W+el9*sG|!;JaU1h{Cm)zQ#cB!Kp#>X+ZKyUn zT>qwrk9(jXPItP9zyjBchRL`otF>h5e*}bu?SdgdKif?MBm-rJNrwsp0+GveeQ*C2 zK-`{xu_3x_W9T>|NdyPkFm)Kw@@RC165j(HU^XL-=B6C}3 zpF}*(g)3GCN~|7F&q|>+bgKMh{3b!>sc@pn>gAlnA)AL=bN<>L+2%ZFZWLigoslZT(W zyGRu#%Np7QP$q{lWg8D`f$O~Kl|Mjwa5LyaUfHA!^0%o}ZE(AfAVR8~kNNQ5{&71e z$mVi9@6o(YX@!)-z6?`)+S`1{PHEK>H@TDMYKnc&T&Q}Iusyd-m1xA59kzt|*BxI^ zQhp}*J~*jTn?OHZ;yt9+woEHbx|15x&ySh!D&!D%NOGtDUI(5+4fA{=wH+LI4g|~M z2vE4zs!*(ow#%j6e=8zfCyW{`z3)zJ+MCk9d?@L|oEeIItn+ta;1#me3>b{pxy%r~ z9uXbC>Dth8QSZ%(`(V=#+kodaJdAwO910?}b-M#*R#b2QjKv6HJ%$a)nq60pN9z3j zd0*YVA9X_tM_(()(Wo6JyRUN(*I=glVvBC%!ThgYcjI4wD(q@#e@(zeSwQ|Es-Y#! z^jE1Nqs84ZBXw)BB$%r4GM}H1_j9lTv%fS$AYL+{2*3k<6;S5ZNfy!0i(XHpY*e{d zDhs@ez_1{Vu6Z&f(ddT^3+Iqk)he;T-!h3_?ud+;vPNki!9a}GH!$E8$rv@iFs zVv}Cbow|wdxB3&ZxGAq3WslscJ=-me;7w}_g=*^#(i$89 zZc;vsdyx`BxC<4XIID3P>rsk3Nf_4`q9&lCq7s&p^4B6zA=xr*K*t2s9I;4x*R>JJ zw^7A;>cM|&u~knPk2G=$(DOtl*bb%F=R`a*SJJU5TQVV3HOJiR${PZGK2K{cfy<5I z%7HhYQ!~P)W1$aWx9{t;Yq+u9x4rrC(O-^$WVM__`o&P3Kf#e(M_tQVsY_kdCK|p>tUZr^#RA zlmcgsLr}dKp~NGT%xa=uBjs_b%781PKL&vki5-rmwQfdk`Xl)#{T7P{+?d(@2?KWP zG5nCe#>77YJ-cLjMmDjst)k8D^n3re#Nz(H$qOQ^H2n2@W>wwIRA-Yoi+U0t>Dm8wq1|AH zNXmtyxJcl-T0GkS!>;lFA`tn1NjccwwQtWo!1>uQ%rL9QZLAIDIXO!FeDwxp(_Yf; z`50HMw7Tn@n;z6soG|$@7mu>XX2@M_fsx z0d4*D{&cL>O%BtTB}v#{JIc>Srw%8eACPQeJ1T@ zkdE=`L=v^|+i{&dj?do;=FebCpE{mD^w&x*-(STTy0noz>&3;K>$@q(bBQ|dhJc-P zjcvL6RsqT=xul{VOpOGZ>vX2_BBug!FzY3BH<#<``}XRlfb-XSn|u8=5^m+$jw{|n z#EgQ5?7W{7O2bw1+WqUW*f}$AXEil=UuH|}1g7lhgGI?XjNbOUEuiI*IpjG_A(kl6 zumywF)EHVX$fy+BxO2d!cXQlp^{#X$BECp`-k;5J4_A8xdaoE?TOYC`iMQxu5wmlf zWO7r*g^2|SEO~p;ed|D2L-mq6E0_9gdH>2ntQwaVu;Y{0ST7jZ4*kJcv%WgZ1FFV6MHgklK7h$(4zVL0+70~^sjYaG~0>$K%~{>T%+WeDR;;_7wLND zDBRb+b;1UnAu|81Ppj4BLDlZ~iEy=tv zZbxE+@>ryf-a~0gbO}64{VY&x>wPouZb^}0#0+$TZ5?xoN)*BWVGM6zFV=v4U68Ssf;{M4R8U_{A2Ac^XAxk|)cqpa?j_5nz}995);Rdy2E8Cu`ZawSEy2dQmj64AuTp)4!dzgEcg$&A5E0lbvnF5Jb)kw0Cg-%`Xdtb$ZP~ z!ae!d385OFDqmeIEfo9)#vaCr^!}nl`3@i6*e4DJuK49lV7peJv5E0-ZDn#(FInQk z3aEP{A5VrIt_Rb4?FXP6u!@b9%~xzuqqLks>vlj6$?2dvkED_-MW*HM3$EV-qaS+} z>cf+WOhRB3a&XX9zyMv_RhhTyHq(CA=0+6S%x4A@WZ;rDB_8TmyPCK@0tL!aH4J_o zuHgTkLcIp2U(5ZZJnHS;;)@OAl%&JJ_8e^Ri>EVom?=yR)1f0GIiO=BmQ~2jN6dMx zwh3*dlAOIp=|(gBAH1J>t_0ZTD|^Fnz39My)|~TUA>ihxm*sU}1JliaI%l;yxHsVo zRqG6*MsfFEC#(Pw)xsd{1d6{i9H&$Bm~XzQK{my0c|WGbOtiPvo{6#aFMYvfY>Wga ztZ#6@6aYKTLe`pwPWUlFh}W|WdB4(gO^^2VdF7W;uyO~5okVEvnC57?SfEV|q`>iv zXYMbIFA|@Q!-UEOTZ1VF>@i(NTqu zi&`e9kN{C9Bw23t!Aa9|(trkfyyO=fi!&uX6CVugmxYWf$4=Pu14!Te>>CiSzKc{d z+VYL!WQ$v+_)WtImZNO&u7(;UnLC}{ININc-jI}~d%Ja*(~tXrS>@%F?@Na`H4JF6 zTDg~^g@z_{e9!|?hSXwgfzHVTx~f>CZh$1Q5-35x*vp;p@0W73qLpOX&|f17i@j1T zU_n>>6?@4fhN+CqQ%%Z*R3@w5lrrm8zY?u6kd^ltN%27Aeb#`IaNr4En~i{^D%%{>gE+(Mz5BmR(PhFZM-OyL!6 zilf15SIn)v<9irFpvx0M7}!YLD{~&CU4CXRJ~o{J8lRb2?*ATb$VseUPF7itoEhzM zkPy?@FE`uP?EQS7i*&r9J{N8#csTKa*pS`Aavb_C$bb_v+6$1#;#9e13PNEA~3y?j0Xr|#0P5kKs9D!m}x)C+MG)#OQ z11<{Oy-kZtz)_{o9avCnrq<)h>;|NGDB&4hTUpIN0$N`g>E2{~Gg>{Jch>v_(X)1( zS|YvuQ&e4Hsa(o1LDGyJ7uER zon9txR@9TFRUn`itwY{a0WH514MSam9I`xxP>d=mAtpNTo_snkOdLBUM+siYkBZ1K zo`5KD*8&&p;PPcj4yk~BY>*kVAb1xF$?R9QP-03=kn8B47l51rn;!k)jQZi`6(zn)i%IbAKCd_6D+{y!Y*!70v9WKt6QM z>+hQ)GuOfMXW}XwE6%aWskh`gNuDur`Z-bqRxPo5t|`?1bEnIS<10j88{A~FyUc;u z50fk`0rTxe%+VQYccZrkF7BD`zPxCyw*(d|*kiXhEg`%YwRLV5Ze7HSSfK zc(j6?LYa3b`?74RFFI!+BU>OJ8l0l8JkH9=V_e?tYLdgvBLf+lSY-R3BRMn&{|o<+ zC5&DHn|&@_go0FgJslE>?hjN+sp12 z6&davYBL&mss3f`D@>5WWWU`&XAw2aeZ^l-gT8_{fDNn)*|ctpqo~>9lBo{1`)`YU zWyZ7ms>asW?bxh0@!!r&oI0UW59r|85|S$}OJpXdA7{f1S2r9NEt5|(Ji)rNibaBz z*lFKbt~k~VgV~oyc0MdGsCjLpbzny^Qjq7|Uxm#3>HH1WzBeQC-Lca5OnKd~Vx3t| zF!xSdPbVuE=Ummu#sWa?J@VylqQmmfdIz2wt?r4i{kv~U`ibqxOa~NG$1E@8od2gP zp_}mQKdhIy;c`W)YtMfKy*aPv%`S|41I*iIhoTP$TYZi=f!+8+Dy$7pcPj7Sr}I3| z&oxmJ{Cqwvq}8wBE{1e#vZ?ywB7Zjb%_x+Q8-KWCke*{x7zMVaA1B3U5 zgTBz$EfV+%pKiq*KR@eqr805b=&p&z6u0ia*y8@_^xE&m7^k-xVEuM|pPA~Gn6pd} z6LrEa3{EvaxefYxHy+y~r%rs%3;@li?mQb~qfUpr79#?qcL~aeljrC7J(hFaMdLDj zjLs4nvG$NpkT=vh$@)gzy+|Jl**s67s6T!sJ~rB(*z)CN6<+JXcwbJD*v4q!k`_`53m zG!g&T%y)AKS*ZIy8|XM$XWU8rIw#~S=$H5f18+_GTg3A|yT19_X{sDr#p_!a#;6Qh zRFN+DS<`}6pn7Cv7QK^$Wn_+J#ODO8%4B48Z89qC!Q2?JPP2~@^?-J-Nm71cV7R$I zHLRvlO!}f7O0GqUc@9Zw{Ct$7`NqwS5}PnSp41N)q%KG{qwGq4aW10f(N|yO`}LL) zo9HdU>A3kpbAjOwrUe5{Yor*Ps;2IkDB&Bn`Cs#Qkb7~QeT(mQ)N>5ig6*-KG}dAS zokz&+oI8U#QZY&-YU;zWF|hYRrX~?*)&O!=g`xMn5sVC)!_g>(JWlq^%y+pRfD+Nv zA<>PRcp17cgYm`reYQE-p#_P>u1zn+xA#PojGeg2^`3-27;sBBb?Nt>SAED^eHv$V z$_W4Pr#Zh9*;sJI&JUp9v~m^zZ#|jDE?j8LHMwS(KAv!~mweKu;MXFgE13UwZm7SzJ6&|{Nq52$Wxnc>5lu0%_;e)4Mc)Au?nQrR@1*ZsIggU#W6`QCQX*@mG7 zCe61#bJ$R_VKoGew&H6UctS>n;VCrxQljjQV!!BJ;CS&ZkvI9`;EL(cb4-joC*^FH zBIq7>?b`pw-dP2;0f1YYQUwYWZHu)Of=h9SLW>1=hvE$qNN}&G;BVfzagZN-ezops&_7XPlENw9s!Re|$J2KB;v zUd%+1C&eRWL9FWSppDq>i$I#nmz*j(dK(KsJ@4;5^2fSKfM$4m`e)I^g?IFSvNHCq zPWd1ebcE`ParCUc9&FQHU4(>8M?|v<)RpEpe1R`0j%-HakiNMB6l7&K)4;?y+g}a8 z5U7v`F>hu7%2kr6f(yb;aG*)kMg^gef7UC^?G~`-I%Q3vG-e>m&)LO0TNsl=Usg)t zwagZbxnP$Hn~))fvJ#=iwAamuD%Cc=k2Iwbe)ro10(`k1s~ zb|n%GsI+1lmbaN)Q*_-1W?G>n=B7|Ng_*k!MFc)a7W+M98 z-x~1@wa^P^QE%@~Z;E%<}PPEj)`b znt9|>iI!R+N^9D>|Mvbb->i(u=--Ly%5ChGgV!js7T=a&q#YxorI30se9Ob|p+2^o z9vwv3W;}}|q3<2bkk$*+kA{Pa&D~EW(=AHeOkYLBoH(eE-^S~ox3Q*itd-twBGu0d z|9EkDF@zPKHJeFT6T>re2@NvSPn9o_IufL4Q1FU20GVYS@-Z2a6B|Q?}+)r?ZOH-O;5)cjqwr?6vf- z{OY+%Z*C}-1!Vyr7#^B7Bh=C*k1xDyJKPWdE|nf6dTG}(xC~#P6{R}bF_G2q&tC?5 zc#p?Grp;0|vN$1xG%YG7VH67-7-V%U!PHSZ-%X>vrH?Us>2l7e3JBXUoaXNXOgeXT zIeLgzQnB3~fAV3V!Mn+aatHn$04LShr)H1t>3<+)4DDUb8_AHis!{0^WJn9|WF2jr zv^g>v(x{pUTF|2z2+Z(L6fVq3x8m<@H$}5P~}#Hk>+P9rR_z z^jJHIvt`?d2-}zI-Gvz%MRo)yLj<1RQ8jVuRnnxpOSyV{l$*=l z>)aC~%_bMYVMIs3`gucNxRuY@23sn8>LPuxsyEkHe@(0n`ILwlXfJtZrq;xT2qbw_ zj%``FtlD38Lhm0QFt0g~qh`3}#RMBye!B6wbOj1TI&Zqq-7kN$_IU$hOjkOflQ+b5 zDd5tv085PR`@Re29A)I`_litXey*Nv>KKCB zQoZWZ)2%mPz&tfqko>|`;XBJOr)N)8P?o()u%TCus>^g$J;~wo-EZ45F`FLHSP@q! z-=4fO)z|%09q~qov}I2o5T?CQS4^6*{lIhP%Go*qz?C9(8yY~gseI-V0iB{~W!sL< zS+8*B;Y|jD<&z_HQ~1@mFET;=Jjs_KAj7G|V)Ki~5oH&CGBGdMw6w0tZQGODoa!e< zXrr!^IV0C!{e?rsSC?K|Wh~p8;LPFfT-5;O2OJA^iC4^CzMSRYVd3*6eU;t)^Bk$2 z00mrY$RY+~raZV-PF`SzlGh0~SX4e9Eh}1Z6~ADm&R~rLt>}Vvq#uz?G!!SZcaMUx z!)O2SLOyi&+R;f0+Pt;t{Um&rs`L=tQpHmtS*hC~A%@mY9uQ$~_>I*ycv5NE9@LEV z)rvvEUlddsjvTw76DTrJH9T^Tv^?5NpXciIQNW zJ$9(+~4o^9#;tR1Hh!k zsL+?Z+giP?j7v6)*`RWsE;=Fg&8B`}xK&iQt|-dc%{_!}NSZu|c=_)eW||91v7a>z zQ>W>N>3TmJOhSD2>p|4I70x3cvm-y0LUMiA*J1h0FU{6wxxU)SO8CD6evPl>yhlP7 z=xYX1zkf1q0-5h|nAKYZHxezB6zxbZWz9((ykPSJ*;3G%fyGlO_Xn7B4i39w+{?h{ z2Np2~$!Zj}UeOj`BD<~1R~3`#$M}zbS)*Xo84LsjZ^ljGs$n0G6~ktGc6Pn7EwSyHEr{Aep;dJ3HObAOei8{Z}} z*M|45aWN0Zum%pj287&X2cU9T=Eg{bl!m_TB`W|p`-Mp1X!uO_ZB*K}f+3u#ki z*(dzez8qFkO^fsVarjlQ2=kdd1??-k*|wmk9Xd^ynu;V+ivwOP;R8=!e4jV0%7HS8 zMeNu08-e5rO*FpJu`&Cx{28sT-=P&No`*((#O2#W*XWviZqH|S3nl_84<*Ob7l?;P z#mf;?4R2v)1ikN7<6f5E-(pkJVief~J0*;^eVJ^ELfZQK=&Ky->Fiv%iC*wI%UgEQ z+y3obQ@0}ZBZS;U#ux>&9=qi zPkA22urn`FnM%4zaU=C!20nPQha`Z|K2hiEp@lzL7G(c`1KJ;(0m=1HXqd|}Z@sIy z{)D%7(z25>YTO1m+qz%uK=shUg%m}&+A zHLd|40U~FRax_u18rT5SB8k`+R2x%!0~+a%o)LM03FrlIUlkjqM5dN42)Z_>OqXGf z@mmW=dwXwKC5-x5U~9v{zR>E$!%V5rRHl&qX@ez z!rP0=K2ZAv{z3WtZtzPycQdSCu**S&ZHm`%q4$PUf7e4)iZ7OKbXH^DI8%3n+i-8l5gud{vLo&54 zU=?(`GA-5q1$1UDb2Sx=Pv@aBPE{jj_R-AqK%=63|6F}H&%#H4|98i+!r@|L)?t#m zf+x@^z`vJQ(8Bmwz+UA6m&XM%VXco5;(9__i?yv-p66tLN z2$e?h{{Hyj;MA@{DyijJT#C$Ic5baE`k=7LSsMmB-L^;=u*(r+ndO(6GF`-G)L$S- ztUCs3V_?Ie{Q_bKU;s=v8+7F;on?2!3bshD4`XUQeLhN0RJJcd<_V|Wp%`arrz{EO z^Y_zQ;W)SNQvct3uzJQd?uv$jR_y=bMMtKsyLl~r@yh;Q?$EN!w+tWffD1jchGu#7gzs`_>le|wbT_$&P^Jrb;F=zdJ^I4%<^JJm ziut#m0Y-%@Y*Br9$mfo zMk<%Y@d0bpb*Izs`Aj=)1`2jD)JyJ0#%>83E?f3YeHwiZm2$z5@hE#y7TrkEsK>ymS#{7CNH^DXH`%N(Gftk+?@^}z{x>RmYf@gP-s zpOYoL(i5odKJc+Q0e{cny7cawwxi^;KZ{8a%$&*P4yzkRs0bAGWm z%6<58C?;89z+9wiqwN2^tdHO| zlTuSS5?{mCCBSSxnu*Go%W-(KNzFY6D!I!y7Yk!Eu4qLz~%Kdmy1}r52%leYX=Qw+g8f2YsP*=xZ`$l@vbu_!$}O%xILskQ9J>9`iJ9s=OZN;m+e!fl45 ztfoPoSk(EJHy%(F9JH7&v34FRMr04*4P0$FE!HodWI|n!MR|t2 zjhDG6T;^q_V`^&MkU4t=i~Kv^v>0A`^P#y~%B->BZ3$&+-SP=E^RzMTIY{|&wW$f~U z!zh#M9=E!={YFTD{Kck$j!j7ce}?zZxNlpN|A?7o!S&3DbYN?G3FUEeBGvDBz@nd= zUK>-4wsR{>4r*`4X|)=q>`b`Gmx>?do^j?#xPJ=TL1Pkc%?-s*9SX}aeoPV#S;tV< zlu%uFF($dM)EaLxzF^J#MAo3}$LoGL1xtOlq(@<@WgzhRW$Mymxb>*;4`b!~CrN53 zX1&bcaq1P5A`#zGrhJ%%7@zove|h5dm4_7HrSnf>$Oq)xXxa>2(`}7P{PC>c2iWZ4 z76F7cV+?Dj_6u2OfE*^1dIRmy&XDD~_ZArU^!V=a$Q|e^e`CKVVqZiAtSos)=rUg} zbU~Zp81VaKUoDEldu485?Um+-vNHXamO@!!q9<~Zboc8FX(pw^MBfh{t8mQjN>fd7 zyDvo)*>a}K@ODRPz|><4WQqfvh7Mc0u{dan<+-kMZtl9a%3)O^94wCbx)Js)mS7KI zBAq>*3{B36(e~$r?TIzR>FcoVJ#0-Pod|KiIIXij(8nsQl&N!=kQiI@=ayY`q?@g@ z?F4LqM=+iwrDr0^(`)w89U9WuT6!{G_+7sh6|foRht|y%}hM`E)IYCs&Nfl*c9+*so*TLRNRIoua!I~ zP=qU)Af&T`9qGRDi=(>Q62C~EMP`T`Ncv3Q==ZU7%;<+sKq{9IwG|9hF za=sSeUR!)g=dYZms6g4-uIBf=Y!gy%#y!0EqtI(ez%k%#P zJ)!CAA?%}zy8%W5ZpD&zG01ZhAdGNYHK2X8=if! z-YxgMfJPUrwlfe1Aet}fN4AEV%{DTqE2f8B*apeF^sHh2G%#hAxq)wnFU2G`#pymR zawUtr(P54yaOO%bA5r|Q{u%5@I}z>NhzupTI8s0~XLxRVx+{x&mPoQMnGzYiQs$*J zYeX1HvtJ;-el~3u}*yLZb*WxE)lxp{`M$iD6Os{!iD+&&}lyN|GpA zJ(F9W+eUG2-O;Sf9V0Y&uLVoH=}S%`;b0FZ?uv7Ho{?E_Y0cmFjSNJZRIv^iYLF>Rt9gL>@hVV#I$t2ROj6?g;9EX8Dl%NBw=rdC$emJ zsafH3b-ka2lCf8U6yY71ECO7+{XOYl@Al}X0;s09TZInoW1ktdS9>OTHTUo5hw}R{ zb+&GSW8jjk=81m3q*FXzgw!~b0W01kX~zF(>M50AdO9?C(k z36kLtbXYDu?#;}h_{P1tv+ghnbD>gY^O^OKXubej&R?xd5qLd`BbX8$_AW2_YGj$b zFrc(DxK%gnWli5UIuqr6>NBVjgMYA*`%tv$J*buk)BW0f^y`}+ z?ZigEdJqLr4RSq*n()mC`^ksP1Z&Zi$j@2$gIecj*>7$-Buht7 z@GS?J&uuSxWW|f~jQdv3``E00|NG#!bvyn6E}JC%q`s%Pg{eE6`QAa)wAmkQYh49a z58~}sIhoeb&4PVicXl9hFiS>83k7dz*ND5|E*$i4-Wq&-sA$$I)So(>2Bs*x241yQ5b<;o%W?~YioqUgxUaZk9af#V&n2iI>E4 zw$oIGhbiV>Np?0y;|pl!0I&LP+q;7V z=Knj?Sfc%=t^Ei5G2DOcZfR9Mki81^5AP~5VXgZg-f1%KdtTz8zD+!L@MUTCsPp5d+Lg}wn6f$3Q_GP-lUOEywzf_ z{v_X0@zMPM&-j1)_pqv&IA2dOR42X1N%EJDMP-7u^dp4EiJV+Bi>hEh-9Yx<3P z+u#fFM85xJFTeLL;LCM*B}nnNY+_1}fB^4}NxD+}8as649__m+*^S;dms6Yc#fge^ zroNV*#bA0s+WrNbZF_c+!UPr9MPzhs z&&hc)^nYud>PXJ+x7K60yARPNpU@BDihZtimX5SqDzpgY)gHMHLCZWBo_@mM1veR( zk$i!k9v=Sm`~1zQ$EI%5m$|wU<@#o&GDgm~zR3j{Ym|`}4PFbK&KC_1T(PA7`yuQa zjj-Se;^6+8Oi1?eUxk@>H?Q?}{mNU1IS>P-GG*&#wShA{C2ribI}E3Nl({C0?jJtil~erh2h}4I=e@F#d}-kqwCYJOLb~; z1u+1{aTfx$GAdDEZ21QwPrYU-d_wb2pL9E6rv_R2Pim!xGs(55c5%L{6w%sfp6fYn z<~)752U!s*6LKe_Gu^{}tvQ8}%K#C{J!0#o?dE<(Rhvr1r0j@_ZJXQ8q37ftH@xXR zepYu~wrgbR1I+8^>ekPj?!PWk`5L-5q~Watl%rETGa&iXZ+v1Ayt1Ue*_egVfEPzg<}@DX!+XEd98;X?7Cl7y*H@}95Dkg0q0ecjF4)I=Rk0}c zc}GCjVBy-Jc70!T=vRfrEAfALY>Nm`*hbHDMzKJDf+5Q)QIVBzeM)7Mro>Z<@<|1z zuj3RsfS_k-WLQHszskq)9X9v-tsI^>s1RyIcKz@pK?Xio-jnPj9K_;MoB%*N@16+K z(_qbavLOGg$W7WATs+Yr>}gKHw&yehVS&FrdvZqCo_2h=%j>pgjJ^d%-T8j z`xun|1_$j$3FkldolNxEhQ&Ou3sFp9;!dzlVAl{mjUZMCP*!Ta`@|faVD_TKB!;;} zos(k7V`NW!r1#t}6(WN(0**1S%_W|UwQ(&HZQl(U(++@JfAdXHVza)5hpvaMj~!N5elVa@=E5k0b85u)Jw5 zx%G-JFX1ynw2v9I6cYL1VSTHAs*05SVzgxB?gNpWT2rtXE z=yG%Ts5p01E^cdz+@KZ{UPBig~eZ>x~LGE|` zIKT?tAC{%T|0lmFz-lK66=R*dc8oTl^jFnqbVGyR$Fcco0~{VW-k%4`);Q+YF3nYC zH|_W|>GAYjBtU>>*sUyR*5jSgh3GqN^@aH2U$CN_L~0%KI!3$_bxOEF75-*txrSdo zhMU}@;zeQ1t&CUW_CaB^J2@LuD>f9V%q9AtLIMqO;q9o+a?ryXwY} zVQT*MF(PtF@4Tl^1`(+kNtU}<&tz4pr~ zug35FzuAR5dBvp*=WKn{jz)foHgXVP*T;3qHQ@3ImiYLveo77TYyZ4eehs<@8cdc} zobjz3tc}jCpC1{FVP_X*z7!WmvpDYXY=k`G5yKqFVg$bxTYoF43ogb-{8(JYCQrt8 zVu$le{S(-Qr-4zK!_zDZbwFSkb&~fp#ZT1rpIWO8taFhx5Jl^}KWXkb{12H)$S8?` zak7M%^pq97!XjtO_erK?Q@B$Tb$k%kfM&~VyBQ}j?l!;VJ>`(0Slqj_y>f$a95i@w zi)0|B9B(V)=G27`bR1S*cuM#vk3N2<{n9@$hT|qL3AD0ZUBPl;+v1|v&vP~@opyp8 zZhh>ORbH{VnHrmskn(D~GpZqUbROOO`0L0|ECt_0&D>jbHFWq%kLpn936Q}F%hK%z z3<+n-f>iErL3tKB&9((*|Ae@d?9v?D(;PwR#^(rA zyJ?1095HoZ)4P@_<>!EEDqrlA7lx(y%=Vf{cq8~;UtQjiapRmH`#R^s0va0$+!P2H z@RJ}d*uS9RzA-t0`ZOd?G{0$tuJ*f+%#sDiv69&I$&X}#R131v7>Y|ozIBnhv%Xg3 zq3ZG~KCyD3Nmy^k9j z=^9&ZGvlKDr%zoBc1k)Aij8gwgkIfbLyH`9<)QjFkF6HAZ97jMJEsU1lXg2+I}nOI zr<^9uQk$i=r3;ve6=P@Q>9nkxlaw=XOh(sT=u_`=E}#_DX6vB}E{}5F1l2)(l=Oq> z6O-c->DNp8@+1qB-iJkqE71`{dyiMRet7cXY~V1>U_TuUu0v^R&LMutLL$X z-W>!7((Woqft9B0=Snh3VzcB+N-@{IyYQAqKlS?TawrMxl(&c?=uoDmckt)F-HCUw zO80HH{hcNl1e>nw1=84=~OY_Yv=~{_vV++2= z)7+?^o7jh+c~a92+3;sk+UgkwIjFo8r2|=7@XS}TkJXi zgh;bI!>n!9-F<>V)$g?Ah4UTONRpnPvWYRUl3fsQGc0@|=>*gCRV77vN|5%M zo3kJ_}_FUFgCkFl&dBx{;rsL z0>tMlQpdt9eJPUphEu(xsyUl}QnLw`8hvz*F0N*hsC4Z|$6JrZ5WIX8{dp=hr6vQQ z%>3!iIdkj+?tjmtDKQO<#%)6E?wn>WKHOyo5-#tPjFs1DK2DBkt*NR$-M<5T{D5uR zu^!>gjo|8kXOeJ0u=>@#^t?(Ek!p!R)=8G=jPC_1wOW^Q7hVN%_i9Hk-yj7~e0ol! zx@#>of1Ohs7YU~blqtG(%JIp34!4YSr zcQ4ORd<5hNrck)uN=ffVo*vlrg@gGVla71|a+kiX5B_)tP?egeh|M_A?XVV$O!||b zn+nzJM19`#9L!XF<-^`yI9&+|c{R?oC37w_hfBz5Gfk0gNNUxmZx*tAD zPZEO6O%>|Xn}mNf#6Fsl`TA!zF*?6QYubK((Gqlpn|`9K{xga6->)n7$gYjrTIpB8 zz3s_4E238s^o%M`YlOsCE8YR0iI#krBOI+0qCQ<7=}}9F9(?ad3kz>2(IsHxZdoWJ zPD+NXK6!n@6u$D#T@^$We5dx5+iOQAZmcDMQiHgB_1mV#JfmA9d5R5o+q@ zu48Vkg6P>eOt=Z_sb;OFY*rZ#~`czIcr36NNm0+qMX?j z=S(kabiTNR=i#oGZUX6|cbZ?+vzB**%HQryNTOBQ0O}x-`&ti@mePG_cKw?h(fb#K z2KjesfvIB}%?a;I5VPE%Z-=J0MVY6k;@=whz|LoOW~oRRNKug zER%gq3_mEKq9kZHsXy>vjpV&@Q^|s}0V!Lz_<3(fQk+YB3ymMCs}c~a^$vSJoq57F z6kCIiU}}_`*4JMV?^Kp{OCq(8r#EXs*0i)P>m zLMY*LPkz9PfF^W2QzJagN;37HMqoUcdz-31BN?e&m;-_U`Fq5_oBhn{`dD2Ht<7PN zyO%7()Il?y%M=3>%a$ww^S1(?tginK&s=jBK2aQ7tUcUg_=h)W#s5N*G8_uA?6tFH z4t7X=ftI5T{^rcGPVVPyK9uj?+^l9n?Eo9({I;c&+PKuaQ=ro^3C#$5{o`%u>Vu-Q z)CY+eIr%ZdGhyd3)(BZ(!Iss*mM2)(p6W|t8w9mU$q?R<#ofdF)dKicKfd6LWtkzV znPu~W-0GwrSbKl0Cd>jdD}qLFj0~M*q3-l=4<$vdeDfUw*3V-P`mK*px6L_X{*hf; zxd0rCLWAEF*a3XEsljL((5ITcK(fAGSHa4n>B8g;CY~9cv!r0P1hkTMh&lk}ay61&vv({JsRjQstmEVtsVM(kJcfaF_ zQOAf$WA^fb9G68d^N>DOe}YE~J2I+Bt_Wbq;S)Ak#}OtDCL8v;0E!HkQ@SD*NBcB8 z#E3r^>}lsb=6^Z*!zrt7!EE@Di%YQSN76k2PjO=Zr%&Y;k$EE}7QW6s?Uuo3UDV@I zK|#wKa=K+;d$NR21;-9198*u0x!@K@WG6HH=usY6bZS+G7vE17pA4=8Zk=p%iQW)K zml80$de>2;2H6kcq@;{MWp(1!w1 zYL$op2kAKu!q*qql9h&_sE!NGnti*HJ;tk327dBJ^7*exyljK>0OQeOl2?hW>m2^Dm$7eSYg;&+a>@>5<;DVaj1}TF@Vq_K!xuDTlVE!N z_4NzRACt;^n2)=-yc2qCs*l!r=rW__fleS39iw+p?1l>d+PYBv{$YJ7U!qyEi-N83=AP)QRYD@lMl zGj6Rwmvkx*PF6~atp}R#X5IH2KLlY-T&FM1$<@=wD@-Fv%IXdpl5@G|ST#uQ*Obj? zIJ5xYdc-h%$RMr1pmt~c9X~S+exRPP#}xNTL*VkE7WPkT#bU{jZX<6y)9e{94q#F0 z;Ry#XM!B4hJ>0X1G=DVw)7r{uoN>Tt7`pgfyPiYUlODn-DOjfuj87bnf^3;vcuj#1 z7a-r1q2*F7q}A?0uqKzGV<;AEwZ3g;Y@NET)~GY5Op(*QZkT>%l=D9{TK4&lO(XVh6?}5Bc3)qV389S*AqvtLh1KKzM zHBj$Xz1`|f7S5Ve%S(E*B92Y};dP&8S>JVR-wJqagLj;H1@kvG{R?U<#ckxEI6K(c zSw1O>`Y`%_NDkG{l~NplK>I5DKxhu_j*ds(;Gm2rh85mWX|q$AQLmN8O7$hvtP+lG zwF&aomLVR_cn)8v3I&wNryO7#mg;^J_F2-=31t65)*y?NGiY z*PBC%8fbHhgYm+pQCczue2lF#W$CV_V?u_YJg(ILMGzlOl8o5QZ^)Y6&=awHbuczX zz|O1OB|^1DRicu?SU?u|k(tVP`X|<7H&I!3f|EI_dRt&*vOAIQ!g=x1L*@Hr=$_o* z*RV7bxZTCx4C9R3F^c=@`m=N5ytj*02E_y1~;mjY`;%6 zjC@>U!;hveTaw#h)7c4KPwZxh9QsMmK4?r0IEse94iT11awmSB%xyCVc#W?t)zy&a zMeZ$0$S#t$)0T2O0i9QMavHrImOwHM-%18FQq@(sf=p!t9L|lI1C<`WN5()da`+1t zYALs`vdu^B_4}0E^ibRLeU}Wy=q&PW{W&&s8BlKyHzH_kec<~+F8Z&A4dj4o6L&{& z^E+0XtR;m^)mBwIvK1{w)M*ZT$B<8|%39{`Qv#kgBwXU*H!i(IMnEGx$>>tf=Cx7^ zS-@PrGdz^o8$Yr+r({ATcxqcWt#ISoBmYydt$4!Zi>Fc?3bF|=gOeq%T`|A#uE;sz zmZphXdM+&`63t-2BeCwS29~4XM%o|_Oz56ag>-=^_KK?E2QcUfVwTBwk-XIISxiI| z+=4>yU^I23CejDvr3qIJN8YcnKEdBFC`cyqrl!`goix5<0+kfZy70E($bHipI?;RY zSIlB{qRL+(>AWkLl}B?VzcUTlL9XbM+^NLBs~5n5%1!PuAC6Z#&QB`b(7#g<>UBPl z{FQ}(_b#VNE^@-i1lMc^Uu~Lp10fyrb&F%j)2%t61~Q}vT7S^p2-?O z)s(yUqTV}HMm$forQ6WwGG6p$N+x?S>YRad(@!F{$ak^NrE5$~rt2YW8r`EPY+bnb zTTCd7|7qHo!0nLy2mYwjtjm?Gw<$K6-5@!LJ18E`GD`oxuk?tHBOS!iz*1f31vnnq#xZg=5ch zpD42RC?1_px4VK%3({+7u0W!agaiN$K4MldU_f5 zhh!6FfsU5;zENL~y2HJb*Wt=eafD9X_hh~3BtOJ1E-J=&=Ojy8wbo*Ia!~59l;ly& zRS>*=nU0}g_Oil$XyI?#r_JF1G~B=XH-6}QKPFPlckFYXL ziv~RH_3B_Z)?T6JAf(>%9RwB@fVtYvw%zB*S^MH*M^pzI)K!SGK%1!yj3G4A0t}NQ zA0T}cQ93H`jxa2MmC42LJgp-Vk zvaW+rGqls)q}x`!MQ-(Z8BMx)*n8E7%*R9z7|@ZJk3y`A4At-c<5B56>}UY{g0IER z$E>y9e?aVJ^dVq4gRjdG6`2$-hY@zsY)dLG9c)M>eH}fkBVz1Tc$}K^;j-51#aq71 zYO)Beg%~3Gmt)U9F4DARWuDFK?uYzLJrS;OuZG&8Tx2_NYDVaet zl2>nCT3Nft4I3Tc(B)KtcZb&DAXh!rJ!i1VzT zk8j8H#TshIKo7-bq=II(f5#i-WucA4>e~P2)$CowzcYi>G;hm_7&j8-+)8k^LT_-m z#GaNTY0Dx7BEUX({NA(QN`?U+ah)x#a`mD^fDqLcf*}&pZEe_V9-AL@%^zV%=eaNa z_m}4Nkk>ii990Fpy*zVN|KXM286szW^N=3;J;@c0e_W$wPErDr1y1<3O^9nc$-mA4 zKQn#a>I!p4L?FTZrGIsAg-#XU)y_1K+H=SWMa58)ji-EHZzPKt?hoJj?)m^BKT&WD zg!xtW&jz_jIns>{{=*Z$LCB(_}eBP+0Po^e*ocf@-1c!gC&Wq~X+uFC=O`I|ty;0_a zj=gaLIaBMgv7V6L@9<+rH%xccPV51Jn!YyG`F68`XUVLIg4vGLE3n;PtS^7W*Yy!e zzj5yEFz+@fKrb1)V?(p43hk1r_$L4o_^Qer-)I5R1N;#&@4qiqc@T->|I)5|mTO%w zO`lgrr<-Z}Xjp3~DB{8)wQ^84_7x2i(tJ=bjDp_ar&JcI4tR-e^$Z!#?J zOeEUW{eE;o{kZ85(q#&Z=3h8qUH4I5U7b-rRiLOtluADux9r`l(?o%*iZtKHv(y)S zS)i0EVDpu&hZKP|J=DY@8o!4DG|R)fSJz(lmg_?a8*jgdmuMH15U6lxeCxowXB_vD z&XG;wVAZ`B>MvkL7EP_II1q6eAra+fll*e_$0~Wf*y@jjkM$)=a*6@r?XHywg)=8( zV;Ho*L=54WAw@qP8@Vdd-p$|qC6%VbN;iC8>}e=ND$7DjGRnmUF5HNJAzWdgy*s%8 zr*)q?J}|?=9X+MFZCSPtr7UB%6@f=3vR|sJlpry zaavS6ugD;1K!jn;R;PUuUcz!Sh2X{f!>i|3gt(~{?(3+EDi0XE3`#yS%GahnD(m>jV#SWH~Kb5XLxX8QV9md5#dfif*9qLCsaj=N?fp%%<6~~Fg3O3O@0`YA~ zP;wiIBxgtK5=FIoSD)yvnnsjR58x#=HDw)MMnOYv$6wiHM}{niGq4C~8`cXOI2UL< z9X%~n0f7$oZ%hp6DVJxOz4g>FHSV{(k3fytL@G(c1b`D7@mJ0qPMB{UvBzkKKQp$R z)#d$KC+>lZn}I${CiV3g(Y?dw_z`su{UtX;ZIzg}Zy*~Q9%_!T>sY=$e*L_f;Nr!O z?APW^93#c7N(J!jm+2xBXml3G-iFY_jVVmsDcmQ~ZlS*Ay$6YiaQ&@FscUVRX171bXd zaNRUFEPyZEvhOasYMmXtkrHud??uc5g2Ol))!)6L6IPIU_LJd}R2z`#1jCe8A-rUt zyOTIA(sv+{^VWay;+Kh8fi%g?OWBWGXmNY&s@1CaxK7+W&_q%DS0Y?qDn;_=mIG3V zx8o%BOW*db#zc`X_bxJWuY$_7ME~Pb{5gwB8LIXTMK^ac*Wx}RQmOqD9cf{cYh)|E zZUO3C5W;~qNlXz#>R+4q`=s?}Nns_v269N>cUs?+$cAvODM#1S7us^B~x{+Lo85(Kpxv1s6+MG=x2#_F-sBa2sy08wk zBEf{k_5qAdhm}ntof762)audWosDMi3!TqNQh6Fx&I%Cw>xT7HMP6*<6!#K`__KIy zw2=l^3Ie!(5u4btr0EAM06iL>Qz+JS(}x#)@Nxv=oIH)j?BtWgt!V&NP-Ze?d zmZsn6@T#<;qIbGcQI(!%dEr=f3+yp!oPH!afBR@aB{Je=r{|D5a{~QYNLwD<{AW_m z$DkHR`tyBU6hb48t0afwg;8m~uF`Cl>@^B()&#ISnpbL0eKeSIgW6~AOX+&Qpv6_O z0@45}-%u^h|H<%jMu1Co3&lhhKQ-2YW`zy})n9}}sAr2~Hs-kd^ei9+ZyH}79OxBf zKl#gg5wuPD^n<9x5>)=fNEi3LJvzk+BG;FiO=c6`&yz$kH{(Mw-qO`mfI#)(w_m++ zl-1kC(~`*s2X=LzmS&lV?G0s9Bq0r_?nkHk`$fjh3ip-`PY9PKEvfTv}ai?OA}#&kux;3~NW56QOpeQ6}?c zu&F`s@E@0ujK{U_g#XO?_I6H(Y?#kxh^2Z_G-hEj%17`e}GrDpB{~iMBN}v$Cgu9`#D>qFC9pNEc*1p6;@e#aGMpYphyioq>z+&uAWQ zntHFqf(xdcXB%HperwK3>t0a6an|aq=k~SVgGiIkQu5j^FsqPg-`qynz!Scm6yKkD z=b(lpca`!FqR}*_M5OTD6lwLU5=`8qPTwb3%Pb4l{>1sMf&Xp- z5#RM^JP_bij&>cb;1bU5k+Ld?Se+rW%6k`^fC!q|x2tKDP@Sv&qlH3k;k0KOmtM+HV~-9CfxqA8(?|pu){N zA6@=WT8{sU>Hf!8|6jbkw@Dfq*PNkyr%@K@dm?db(P<(<=}xz^ZL>9LCqE=1z%Q`C zrPX8Sgl?47LgkKn4?;X%unr|8%afArNvk$qZZ;d$*rZjD2Pr&yTe+y~xcQl?))OJQ zHlhzml;TBxu}a;O1oZltIA@^DTzKB(0sXR#bQUtpr#3=xNKLD$qvx#A){Ok${w4S| zkrJUla1-u1h{7hl8g)qfHV#WLo~<;u)Mg!!$y@5x7}tGTNDL>u9-_YqYE-MsM@xui z#z=j;aJ%I$8n(8n49^J`Q`zrs2_DyqN9T6v#$EH@>%9Lj!P~iKBrSY+l>Yc38PV_6 z=FZZbLu%bgoDQok*gCA{q|Oy+wV>(J-35EkBIh1hB0Q6jao0szvOzY3puBvKupc%e zsUT1+r;{f4ZR%aVZ6r?7;@MUoAt%v4H)$eRU9P3r(=|6187FJfL5s(P`g*GBktIMt zY2x&vR@T1CRkmv4k}cyv4%ZprhyP&%A$B_#j5sWgIyi{@J?qAn^I&-v$W^1;qiQSt zV{GJ6QqG9Mn-gZ=H8VGRcH#eIR)6K}P{RICdI55Ic2*jI8y0SIEv$V%3Kt2*=#J)ru z*Dt}*siNl@>~3f;gCjH&*rqIG9Jt|Jw9P8A)B4b}-GkuWVcKgpGe3O%6zW6#VHD+X zOHGT3Hse6>hF_?g(^=}+3=A;TDLdEmK1_S7yFRXK$F3%#$r(ljIgXnfT&%utK`|HE zEVSmPmIfW1a&}`5_B{L0)T=BI=BOv3;%IjEaMJOqTG*f&P4~lYxJ7Mh)U^7eL6W$1 z;a#*lE|Cu@35)j`ys@}b3M9dcUb(VN$S;Qnm)~=s>EG9Tn z5gp7P`)K&YFTYXmu8J`Gb|x{3)C@LV&_r^+`P4t*mQv@l{b;RMpn+?;Xn)`DDw$I0 zS;yk-j-y(gWVfQH3vLg4a9&kx7$ThvnYyU%fWqZ}l5`=qzZ>_3Ri1Y>rc)}IapGDy zWa|YnaQFNR8ot?xjjZ?gLN@X>*nVcJ<&#Sdf{P za>LaFuEOlk=L=(5FG%Agi@&S?usI$t;ci4URH&&L;q^S7a@7ss5h#Zt01{|eu|K~*yO}$9l^7K4>VfFm_BWVY@R zf1|Us-X79mjUE)7_6s(V4!RQ&IxZK2JfJL)8nCDdEzM79Sjj%wP^(1HFgQ_^SmvAU9h4QgD}ORTKA@pzu` zd;oR7tc#O*kBrurZ|+fIoBt9tg2?%RYQh9MY$u^OL`1q4S7y^tofvH35No!DwEs{_Kl;>B?oR0e zM;Gb#p7SqF%ZXjR9`2KQmk(6btkwq$kLT+H?rrVOHV^!;`uPNEt_Y>QXYD&2i!9$}4xIeanqq!#vdQS)C70c8{0L+qydgqu-gy z`H#Lb2^2;PlLrKBP^m+lx_%2ej;42u*5DxNgQQ$x!MgjoUfgNZ5^BBqppXyK6%^`s zMu78_t|lPyJDjDc{wIqGlqrzt@o|PWTlx7R@NS2hWN`iyKbRx!LM_4>^FWrww!MV+u1E()~dt$!Lrokm(8N z%Xgg@Lrv^z_~s@l`rOSUXEDPM_Tm##yc3C_7AqF0+*uIzdtj;SMpxI<{4Kj4P~xL2 zWPM9EwkN<1kauprHR(p*@1R?4y}jy`he)3gc=Umo01N<6%<8j~qlk>n zW1Ki`zuM@5A@*`eYYSl;S+j0!h-Ea-3E<0${4-4y?m+qcm^gzVF!$Tav~AYDTbw(W zm(H})nq-slQdvc;qLtdaM&RV4?#~o^HAVjL&oV(c%vS<+^7Idv*xz1;JAET=8&?sK zJ>K?&Myzj@?Vl^$!fW%K`6HZ~lC-OcE2+WLOU<5Dim9kZiiGJUP%*6!!_JnnbuK1H zWsiS)^8gwp^@`S(Yyz6N>g-PY%alQHvE!ICkoc@P%#Yq_*SYv$i1*CY8D+oSGN1yF z>9CkDRd8Hfg{=^R^fmDT*0l@QF48+;U$Klo9LG0so~XX)R6p1vYFqePvNbL-k)<`9 z6Eojt!az%+3sb3r6&}s|5xNMw?urA|tA`3kYFfnX>>vxY6M3R};W_rx=czd6hYX*U zWd#J(4aPu53Lzd$oWe}JxT)H2Z)(J?#Z-vgs9X=$Z`6wKW+}>Lk0QA$HEsK34@JvD zst4pR3VSsaPWzYNZsL{=3ohRr$p53e)D-?Cx+_&r*?m*sR0{hG2K}w%Q*`QU>nqYg zb5>!x2dj7VThN7<4Wb#^A--`o7XR)AtmaBi9!~j5TH45k zL*Gs^F5^Ll#1_I=IJdktljox4_MaX2zXS|%O?ej-)pZz}+8_^&7b-5d(7n*nDC8H} zQBxyw+35^?TE9jsEU1qVujx3gQnfDRl?~x(#-@lz0+mb@ZwiJNAV|KWMTffw_Ag8S zZHB;HSfh=)>cci4KJ*1KFeq>QsU;?k?Xi)O0h!v8gXldt^{fmag{-dVb6TTHZxZ>L zsUr+s3CC6Kn=bloOB-b!I!Be1SVyO{_SUYB09e*9RgBSBo(43tP5=r76sGh?iahrY zH{|eC7+hlC`Sv(gilblZ>b;Q+o#`|UCgf?!8_giYF?zSjgBQ8$@)PO^_J0&_<+6JM zN{HVd%mOcXR=Pyd_4`!_TS_hgu!jZeNtCuK1;I#RbnlprMEiYp`+5Y^czJ|fi42`5 zFyj_x*Kmz|?A)(!_HNaPBoFeCKwYqg?{R%y3D!Ni$_VT>7Q-jLPT1{3P`CGsn*R8o zrQhGdZhwiRF7LtSKK{S^)+53H(xt01w5=;o<2`u&BTBgmZuOZxLI?BKSGaUx_fQ|R z&&r!s@h6gbDUCYNZ}1mOAc+djUEa+kPqw&M`vNM8&`^&Simof$UwJ%|-}@n;9Op}8SB~yjqjr^3^$9xUeXB2WLy_5!)9PuAc`gk`BHmsMvv?t1-@U)fhV;rSu3{3)=l=Eb} zzjKadTsfgIj;HT?Pg)XdsXb4kav-v(Dam(E``^%Y9R+oNr*rPh?eX#EF0nup%p^0FxG+IF6a{6Kph zXS`Z!4scWs4exHMF|VQH0;vWvh&A5auRhaM4>`?bobSY^mcHcUFOOJ$x7+E;Y!k>O ztaci+wVzkB#E0afR_Ikg0{{MU*EMJ;(MLNb`()}}WR|q;sn3D&C$udODSa&%Lpi^Q z$)@Z?4KXJcj5x=r`L~zui#NI?o7^T;6sYR}5-Sem zHfkT93D{BAUQM&0TEq+ru48sBcQ;BG;v*u{+AQ=P1sJUBNt*Wcg&uyAw-2cd856wZ z?sidlR_{22BT=AH7xY*%>KqWt=apjdl8DG(NMwjnZGV~zpaHy1h#Gxly;C|gBwSOT z-PvTYBg8$T@J|=ti(cW%?QVoc>!qc>K0@I;tkPfp;cRN(?v4USQK_GmHIaIv{9KD0 z58K=fqLFFoi^aN^=^M6pPk;=x!@Pq}^nYBm(w!d(b#J|Iv>7dSK@>RNGD$~VKFB`! zbhxP!!t<NZBl#PS03I;aWh5PMSuN9K5|fEBn>!#@bNSvYsN7p(B6b&8C)$JR?>mmLFAFt8omU?Jz6=b$KOm{91NO8JOlqRrd}>!p zAB?kgf1>xw(pDYrT*ZEhr~v=Whus-x z^(j(&bBux#**c=pr`xOKOPG%8SjV}L#4sS8JD6rk#m~1tJ5Un-L{N_H>w>BFez&4x zkRh`sM1L$!^y>4E(m>Sm-WS_Qnx*TfP{}XCyVEY5?DC8vr`k3`LV4=QJIkiOrgj-Z ziCl^5y(aHyVqZeAYMw~^6Rhbj_becxWRmdEz&w~!^GC3iM>)*(^j zqJNvC!-LMTZv+^a{asj$Dl%60HqAlf5#?+?9Gk@Sf{2*j#RjLy^u{G1*#bc7TQk$k zWdhZNFX7v@i?s2J`#a2F}!I)F6Y1nYMp)W!;DEyp9zHn*7Hugdw+ z*A#SG%P@nfY4mFEy9n?3Xc}|vSkV-)8qLj{U!t+CnRVsW{1e{Gomo*7DtBWQ5oo$^2bLov zPK8mDiObxn823aD*QbI+(mf9jYVAoeYI$PwvX5(gpkiw^KJhoxa`y`AwBMsYlW6f` z>9{MeMZ#fA`o|j2uu6W-?{wu~>gkE{$+kbF_%2}ciBmdFRh|nZQV5>-T;M`K+2Vxifs#1WOxqD8m#x6OK_74etqY4E$TB-)w|;)BP~ePjYY7SRZa0;9UwV z-N}O6y+9+&wx~-NB)DtA^FC$UskDGTF(umF!BfIbdpg9}C*;9%9C&a5g&Bz7yR>v( z07#hbv&bK;d^*ofn>GYCd=B&KdQcF|`0Zd}s;g;r3)$#)OVX%>-rnEdu^RJa?0GwL zeDZr{H^&mEr3Xr{f~?(YX&XZZ)ZbSe@`47rh`({TOmewbW03c+I@3Q+GEh;rco0>O zg3_!tat_($r=Zvg$EDPOq*wqY$7=q#rxABmC?u-K?wT26m=YVF=F6S>)%0NdTrJIU zPI3aws{BnV9k7RjZJd?%YS-PRziuCuv>X{LZfq(n%Jr~FeY9@0g0_pK7-I2uNG^PH?!yfJi#W*?aN?1kLi0_) zUVdY}9(=hbjGdoS%#r@(z~*CpV>Ph}s?VTPdf=j}eBCmM&5WI3E7+!(MC}soO7q+B zqUH{@i}zF->?&<&1klq)RAF`&hTkZ~;ym0Tzj~sBJ?t{bbSoBJr{uoZpG-)pM`+Q& zMaK*Nj-Qonn`U;QVXj3w-CQa6(K;zX1^e&w=Plk6Bn8A(T58pY)i8M%TL$y<$SUYBgwR8 zufz7(uR%ocVV-rE!KHzt8?)s`gJhfO+4O=|wiQMj=qfSu>*2L+iG)1%tFvS7tP}&MMf(9yU9`E!coZ~Om#LcfKcDqo^pQ@u-)@grDOCUN4!H_FA^QK{ zsW}+qPlW*@jN|Jq^MbT;lv}O)M`*?&%T0hj`uq?>94G~FVpx45r)eytsTHY~ zU+X8w^xivT@Zj}@SCRG{3}nD9e=&Pck4w5lgax6KGwD6-_Ai0a!X}ut)9r|7Dl9%1 zu5oZmgY6MnRZuX~?ca#dX-|v^oA@&M2s5{=!D*lrR!PAN(pwP&o>vz#JB1;EgmNi-q*!Z6( zj$&gF=fHdIm1kw*EwkXMJ>J=3IMTy{!FctHR92&x88GHnKlpVFXf1DU(N3hRNwgUwx*W6Zu7)L{CWF~ z%Z34NcYzZi9)HNksUDRUr_dQ2mA7-@6Ro+Lz_jw8Q@sL{WlV zgz*%DH2$uAr$^lo?o-RPE2)!v(Dc>_qq^og^*u=7N`+W#PoUO+G*{sJlhN2LnHVo$ z6(wD?>2ciHc*ZW>&`DGQOja#5V!1NXTF()iRQ>nD>gaE#ORxKKr$ZsKlQ-KvV>D}E zq5x;-dKgK8*po7xxOKlsDp0CL2NpVwp!{0MnfWCXG;HNkAAed|U}2TMjdwESHWX-s zJ7Z59G8ha0C7AZ{a#2u?u)c(7A z0M3SqzAv7PclxT3^dux!kA$A-hR_C3Q8(tJm19vAc%_z>$8PjW<4+saB34n^i>wGm%dsxdrV)g^|r3cT{gDOWt`=dfo{%DX! z4+$%CsCuF@!qXnC5T7GIMa;Wuo#qC1O>`?tHp6)wx(|8Xc;Bgzl$av(Vq+UlRXn_yB8iy7wm#I3?gnVkvGkQlY6q zC48v)V6aj0CbE%My9U6(^d+IeE3bSC1U}}PxUj|AiKXrEQj^z{VeaT@{hW1NSZ19) z(ju1+IkEG_zB^5>$L#~gKgnDc!C-ap{hy8o`XE&_DPW$=!T>#biktfIgZ}uT9yO~zw#RjUr!=@UFchppwQ|b*Wn)wxEv_Qm1rnXDrt`K|(Y3XWSDnyfy1baW3Sm}rdV6K}|b_ZjQohxfK7nyC<>llD+ z<10h;R1GtA25KN(o&m!>zT`L|h<7nhK(}@Hq1*;xU_HVh(u|?lRQol;@N_pOxC;;13WEl*RT~J0qq1aS4SZW760}XQ^uE zs{VQmByQQ=nCw&MyCs=LO$Ly2UET{vPy(O55!Yqj26j9$iI-zTlTC#O(8{)(0oX09 zwKC}5zZ!IM=AC9WdQ<;nJAPKluBjq|icX1g5yp{dCdpKja21Q=V+y!liJjyYnTlA= zVV9Zpj(`H&NP${;NdlC#-fx%3zxIANO#+jkx+$JL)0+PEi&qCQL#4e9+O|Tnf~l;; zA>)+&VfY+SgQSs@6@7a5QmgGtw(6F*L1o)QC^X4e zhJTj6qh{n*8mZIDY#d5?xrB_D_5cu1G|t)}+uG^Ufkjlg-h~L?~DA=^Fb!DUe?W(C0oS}xjXJzAZZG^^W+Pjv`yJrp9xaXpwUy6(A6JqBS( z71MI{c`c~X{*7LnCL}8NWmd-t@lWr%{ypJY^~t@NX9M8xn(al))L%bIV77f#jE9Py3HnoeY!UrWX4|WDRi-?Dqxb`W3;qzSQe~CwDm&n0v`W+Im z+7@n>%Li)CmiCkt04XbKaqdSHTNST!R2x8=C%@eSK5(_wdkrImy=+G+ml281)^~p; z#k;(^>xl63gfV@E43e|w1n~2fU>XD4#V@T?n4uG6q|0QXB-vAvaWQb_KjWH@%h->1 z-Jx$53Z`!lsIO%_$;6g-_U3%Qbyi=LNL5a>pX+M5vFn^X9|?{a`|#W&q{2K;fioav zw{+eC$R$52u;Q4(Qel*lB{a5do;kLAY1${9{4eSgJG_gnpLjqi zvXH<>Q!WqoL9L5&e$T;@L!HxbZKI_07 zJFug;m3%IG7XA!RTDh&`B2Zr89LRo0>ZL0(F(ucs)(Kq}E6|yBrpMiN+DTP4Z6bkc z%efU=GNshoPD;eeq14Q#lGXlwJ3R+VcYPDW&(ozOIXQRBwdym~wv$37C#BAt!pPl- zpxN2d$}_m^R8RhX7hLA{or?#^36QBjA><<;2*C$#c{^}@6q@m;2zx| zH5@f2+3xCDin%CuSId|l-kW#h zL~Gw_ByWm~P997Sn!kSqpzZt0+@{JAQW$yugU>+2h_se3n4A+C_GZ||*)5wk7&7lx zQWh?d*8Yz2L%uG)IE5}k1UaPGHQXn#c;CM6^HafG!FiIb&P~02`HJ3Uj%brj{(nfVe>k2QzwUpj6CN5q zd6md-Fi_cL@6dwKXmpo`_PtaHg4xBT!(g=*wiV>3caMs&wW|{VXpqJ95$j` zsV4pFKPp{B_c!ck#69f1b!6yqh;$GB_sotfhAz)(x$Fej&7}R&WVS!*(C)^`V#-&r z-{)68f~sMi2U=xM!@Pn?G+mH91AuTxYd3p~f%T$>WR3Q|o{ti7)rHeb<+6&%=Wz9AC+M;yXW!_;og1xA@TX4w1V_2wz!|J(y3d<14TlyGTI z?L;GtpD&z>uba{3sJ+u9KD-MSrkns4l*_ag@@~e|S(;la4geTFC2lx=W-X@yTqooS zbJB_E&enoA;z#3j^FWl9R*Ij)q!{*A)KyOFFKyGfuYeGCS zh)xP)ea5F!LwHWkuexJY8<0H#Xgz0Cfd392pGQs3v}qUz!QynNJw09chVOG&S-h#K z%#*6Q8!7%0nN(`*Y4CeenAl(w;KF)BZxmnAQiDPbZix9(6)v&vbO~%*K?>>=^v*Tt zuv7}fq(ZSF<hL6eQ^!mQ>8hiCBLbU`Hr$yx zNDjG{V@QtZPy&9d$qpW?>8<#zLMjujWa2oEbAl8Eidsff|6Ui)%?Q}(<%4oH34NI# z1u*EiE}R~Jpt9XE>l(|70*xAuX&mt(9z2Mm-Yx@FPl=<7VHld#+oGdW2GDrh>7lbf z>g^Z?GriGO)^WtJFvl*4k6^>%-d04K_r(>MW~m?YXJ3XWY&FCIsafe%e2PbRPNQZW z9c?F*93_W_vryGJbu+UG$L0tORKypaX=?t0q}EB#t)qV3msGH5Se!W`X_RbGUTf@L z?fbq4ZP%#fVxHeMu5R?oZ zw{1$uk?YW$9{%$4T|Pjw3i)g)m?1*LVX1I_Qtg<#sy5J_7k#yFZw4{r9Bs+-wJ+>b zhRctW(>p5cN^M@t^RCUC;r=FEp(5kBXXM@7)!b-xm`bcVm^c}myGB6w4b!%MoNU)m(Xgdul?i&Bg2}|D-K<`Jx@`JBvP%Yj3v#pe7Zgufj;eX1|=Y9bfol z_}RpL=cLYb^06_~`o%B3==+ZX@Ke0a^)Rmjs=c4_9m+P|LT*U3@uy=>sov83H8WzI zo2kMhdY(^pY9%8l0K&8S^;t_gw?olaIH*}ldpoz>>X*Nd$ig3W zMA-jEACtObdl%p1Qu)?8R+bXe+uJ@z^{VPna6XdXAN$$K#Ws(f$?x|;iL`bpu@6(N?kk=fatT+(sy)*Vba_+zSB(1?))%oQs1IM)_ zd~IPgy@zw!z2U;EQU2Ur>&0Z{=#`R0dyhw8q`b4<$GMu5zD7u!W?YV5&Qmorap)@G zoiaC>e-AXGWI`2hXZJ91F|O3a=C5Q-#^D#kjk8<2lTB5%yUKf#bFqqvF;ePV*{^(ckW2lh*@q{X2q)(8J*UHyzpGK4+fdvOs)_@= zl8aJ5Ezn|ZZLuD15yyFG>mpqzZpuW)qW0p$VfgFn}ts1Iz6ja*kRd0Zjwk-Z*_i5A(Fp(;)vmsnI$6${Ni;Ym$?hU@FJJt8dU;;Al>MBQ?Lelu#@m+WO*SEPBL1vuVu(jZ~{)m9NP>Euz z{=*jHI=@Z(L2z*^KhzR+I*OeJmI=aCqN^_sYHOvJK67~7>#=sJV2$@1`R60q>nLrB zt?9W`brnvhwaLDswm9obV|0Pk>imG|N*r3+xiQ+C<$=5M^@#6i43xw=>T0nOHjgW_ zOiKzaa4}nGtv+zRWg9VlAGI$H>TE_3hpi87zo~E*9nWH05B-gq+JSQJN+)Fm*N);? z=ePYS3x7iHy^i|8Sq2yvE2%j}PyfsiTv5g#D<{2yqv~pJX@_k@Yb&Lh^&Y3xfMsj) zKk9^#)X`f%NXSgA@opK*SO5<3kw|?oTh!ca1ehu)sH=os$rr(@z7xHj#q7D;+!mc2 zwAk{3n%4;1oKcKF`d-37PpE|#b~N$zk4uysBJ%fz(|mI*-kzuV(vt8_!m?_!rno9I z)YvvhA2V^*&l9>w|4Wcq;d{L|6IOzA)&kynexPfH&6aqGK|F{H?p}LF)9-0z27QXd zRGin8tBy|7L=y&5zQ~fol$IPAM3IA{`TlU1p*O@0b4qx-btH^~^4Gt$jP33k@fIFl zs^?Nk^Yu1(z&&%ld#mYUNd%Ht zkC7YPX2f_F3-!NQ)}tBPS-(hrQ0VTs_-q^ag$$bKNEK5d`{UDjh;*#ce8_%6vvtIM*$(c zpGkkQ4+`aXay@`C!WC|aZOU|;il4N>Y+4Z41t4s1QC^rwrc^MKQTfmR&ZmuEyn6P1 zqaKYh*+LbL_G91nwf7ou=*IkGOeD|r)#0Abn!OV6kp>l~`nZ{(_NH@PWxxz@o=2%J zisCgNj};#{3KI~Mtr>ub5&YTVR7*sz^PihK6#t#a`p?Jid;bHtzMp#k#cThg0NU`Q zYszp|3Y-605J#n>E!p8z&C@nuB4RNHpy1o-0v-*&;rn$ny%UVm3L9?4y9c2^cK>Kol+cJOzhSA zO{>G%?I!HpAoWr*%H2QH`FZE*l0m2>4t#++BM}Qcck|l&aVee$s~zboC|d;Mb9m{U zF`rf#qo?S;6N9YRxyxin{9Laa%ND(N?yfo1x+$f<-k4G5rPRbkW0Uz2*Yll9qh>4@qT2uap#PoK!U@{PC6sD%BtfA$+ zrlX;ufcvWkKN5cdTU-u?mn`J=Dwv2x9=~ojzta%B@LYqQj@}mqLyf( z8EwdzvhSQ@`(x+QPi@wPm}_MVhE^Gr_}W-BmabgsS+buHSo%UE=R8!SB0XWL*n>F^ z?7-r^J4>17dQ=0~$X<1;K|nHg`KavMG(hm6q=H(IyO&}^LvT_*7r4Z8>JMGK6!98G zI@!Zg$$*C91b;bts%B>%+)t3^l}fDl%n|>!)PkLvjSeehLA)hM>v|-t6%%M9y<(aa z(vuR3zW{(?Mz2*O@_0up9Qg<4mXY<1H5h$^-K?aK7sW*<-TQX>KfU!K9Q(zZSvJ?b zSs?wT(ynFd&^xCOSw6bkjpO!Znf0cd@d429X`eKsqrl^{)HEqghp z0fYJuSKACWUiBvzP&5|rNx2;cqf;^=N5dRMm_to$9(Wm^le< z%B8y80X!n$XCyL6ApIGo&G?=Vlusjpxi7X@%3|ycK|Jx{Mx1ndg*!E~Zsz3>!Z|d# z=b+rnB9ypJ(}Nt#B#Y-S(<)o1?8Uq6TqY82MGF=-k$OdNMx~ym9~!>iI5(HS)i+z| zg0X(Ob`DlAcIB{^{QceLw!awPu)YW4zwQc;P0-TR*nK_4hd9QtN1fAI9npT0daJHa zV^KpTYBR3h{Q96YW45!Ox>?4#Pt#V)&pONK=MXaW)NdwQl9i9!YImDzO%EsqJc|-j z&cHn?@%Iv)(71(l|4RU==fj3wRtQUT83z`ed?*=31m}w{edC0zokFGmc_y!zAhh~Y zct@cGo{#k$*1*jg=@lLJ-dI8f^zuI?>e_8Mz5~S{ohNWGY|%0r>bb-nR#9s3b1v70 zSo4%*(jlu~bZSdz`K_1oA_OuHmHm=8XH!=T27yPdNVw540oCk>gEWQ8%Y?NC%?Lw?uuqMk5X{$r3N-4 zd?&I6?X+F@n=^)dnT5p-*@-NdbWz{aoBrgnD>rKpHNDlxGxu^f@n*{*B&^9z?e#p) zewxSJDKzf8cp3!K@c5W$c^GwEJQRh`o6go9ah=Z1MfO%XnnqIrUzv$ee%U09pW%AaTAjJd zEvSqepza*y?K~Ih&arDYS_`j&f&czXK;fXi+Nd@fr?apc<;{o@{MNit=^2^SQ^mk6 z?aIUYB+L}a^R|b1niv$qtaR%}J5gP6Drmhi&vHA87_ zN|o;ypt|uMT`q65U960xn1B8t`SCA-yS~pOdCB}nDlv&O7;3tU@kaeBXi6Xn&Q&#q50|_JaR=<72>g_bf zk&z6p<=Ye$g)^&3e3y84x0bL6iNQjwpC2@SpzAZBBGM-gpXc4-HIFy)^^tSp)L8^I zubgjhV_vcu%Wuq%d2cB*g@j*c5{i*aGBF1-jn%3RLCzLsYJ`o{6zYb*x^@8wDIFGM z@5B#*9Wm`DA2ZCfO6B?uR_fE&R^6`@d3Ut(zJvxiO?Na4cO!c=|~P>#@z(Kp9rvgWP;m{9>s`>?c+pFw(yOHn1cJh`xP;bxbe zlL_&P2t*_r;nDImY21;|SxE9nM&n7sY zZPvg@tU?RTRK^&#_?YW*_)7;%F$ceEYoJNM>b#Jy+z|t+Wxr1t zG=29gEl62H`os44JlDchF8G#)mCL&ZO`d~6hY9+bZ}K*tXa*l$)2 zt~Db=fosys!Y@3kBjwWOM6V-iSOjv-QbSO79hR5CJ9LH$rB)$ifdbQ&w`K6H)k(M% zgC7CVN>K;g>CfZ^au{Bpp{k}c4yM39t25Isx;d|=FtJ{By73X|3B#4%mQ0-knN+*tPF-IyGjj~MzFdpcM59FHS6=nn3#8<)@kW8#SDQgTLFquTl0moF&c|OH zCK~}2EU-_d%b}IRL617I z(qp~wZy0#!oByINPh33GOO<0d`uO3=$zQTIALsk~nT7<{>=XarJ@CKv$|apfo<^}f zY<=j}N=DZ)JQCm40Vt@0mGWKErp#h&ACH*raqQ}3PC|q%VJf+(dL@DI$=5}j!aHaS z`n>ZZLEM^jm16d)d>#?l_5MzHv2lpVk74+hxo}Z+Lg7^e_?v!be`pbAxt$kmo4geY z731HlYrJ#40}&rxD|m0fZ5v;@L8R~j;31sUc3t#Uv6y&Bc!lJLGh@NXq9 zxBBiP(Krgu&o=Prqa2;^%n_bU$B}dbbB}V|&tJW1lg+evINlsg`@+c#tdL=ohu6o= zM)1k1_kwsA+KO7#?nbEb4!jne31|Yl>Y2yPF$DWmrAozse+eFV$Aa$w8!wK39~FD2 zjOdgoQ}3y~ENlto>yz8Z?O@+F=g6dam?xkL}eWf3;4DZ%y zYcUz#n9(Rap7KQHuGnOzzLM&7@Ez05;uk=3Nfa#2#e)2Ui?C$*2Bj|7lnIbdcVW4hg-t0Me1-=HA`ey?bYN z|H)(~^UZwUB=hE+_dUPoIbpi8cS6Q>)K|yU$!+Td82eqM+oPFcMXvd@l778adqX5@ zaSPadAy9haBex%%M5ySv`;Wl!N==J=BL9gPK|WbtCKr?ybFIvkJOhZ-QY8!h&$m@lYf1t!rh7CyQcT(x(l?J!Ut`nDQ$6|PL`Hmkhn zc|CvksQlzK;b)G;!REG0b#E~nLPi7X3`^dNFX4N4??^XO4Vsa-KH7m9tHC>VdHoxc ze#sSmU8{NKu9d9S$FF<6r&xass4USpA9rQq%}EVX8Rf`SiLmgu^xnCC99F2qrs4bb zTSWg<9_NgqI}Ms-5=-PlU1Dx)Y!I$K;gDY75u`n*n*|k5cU}HTA^#N92Yqu^dUQ?T zR#G*C4T)hNip;o~`@ApqvI#3>tD=u~7bxuXUCgd6`HO3>39wnqG*&w*C9l$Ythrwo zZdK*X8uLLw{D5qdyZs+QXtB{}laitJb+tA-tYSq3c(|tyJT(Z6wEO@fi9wKNaaT$! z=Tv^jM*SX7$cT<=GX(vH)#8J+1SJ@)zD2#Uxwm5Md7qY!Vua@7O8?NPMc+I17tCzQI^SV)`;^bvwxwPyf}xv# z*N?jXgD*@8=#rNBl<>1B9QL_4b_Z+a<77Gxz)qXvAJO5&rvFNz{FdxOQ!PS zX6xouOlD@(OrF>Eq&xExpugh4ZTnQKf3wZ&eMro~r>%8+xCT#Z;jSCy^2z`;2*mCg z4Fq@b96&*MGZY4>aXIJJ46>I{AGLEZ}lo9VirG^6a}4$e|lj_bdAOJ$_<6VMdBa z@c@q2zeZ7@slEViO6SSq9IWMpgGcTO(maTaI8SpF)a^)|(=U_TUrdIIW%~HF0Ov2! zWP*lVKF9lf#@lBQ86Crmlo!{idCcK->YRk3xVR&0jyVYU{4JV-yfm~iq?%M|ARpT( zxBj-@6k+V^2Iy1d?$=Wj?wx;gwYeu#d-dBpm|N-Q`{b{TP)qAjoEyc;n5m~fD8anM z;Rw9D;8bQ}F0juzzvErj@Yc~j(I+a6G|X#|=m&;n zB-JH2F0;8g_;4XYlKeb$4ok-zHhVdJD_C8fq{Hwi9+=%D#bznXf=7Ta??`KxGvpL` zF@(8v8(3fDw9d60doOyfOxS)4(RqC}bL!|`t+rio*QttG!6eRQa~2ej;73^GL?HkT z6j5An7+F91=`-;`QdZFG(fUuXY|mIMbQ<%w%=Qis&3=P;DR}P&d~vIA;CMG|#sE}T z;VpYWO+Z(~s83J2)3lP&6N%&i8GRiZ__hAtwn}$C^omu6hUJAV+N831?5A~GEK5Q$ zT@-ns&DtI9g=9^RK`t{=klr#G|vv@*LYi^xaU6cb|eVF(bE)2A}D6TgJ6k z2XqDQ7GKxw>8y7JZJjjA{-j$lxM<$2Smmnc`*~6P4yAubVGqIo(`*Vv#1z|v z(TOFs^4uB`KBX%k_ePywXg(LZWpUx-+oy(oL;^i$nlRu?SqhEtR_L$GNWSW4io(yu*S43J~zkyuIQ{JLXz-77l$f+6C)rwnd}cK z>Sb<#OnW2NBDG>6G|5rW4|-175V5^i-apwZD!!3L2tT)2R4bazDQdKS5{1zaFXN04 zORuiYT5q(ucf{UsCTobNuhnyKyYC|>Y9hzSauo^atpr9_; ztGIX*c4zBu12t+2ZwA&2@cmu_N6fjDRlk78)fZM{UssQfGqA`nGj}ml)0gGOLCW+Y zu2yi}KZ4PaQqN-ey+S!}&7Ur1ixI!8KN?#`Dr?GGsyQg!`4Yt;+h4p8&31jG{BkHl40Nf~VpBE^5f zuGgBv@)~wiRZSK~MBP@~C)Xs!@Gy;5_w~+wpr_F&x8zbC3!)B-38OGKe!3?x=TQ*; zqcqDyE)Bew`njwN_;Ex2Y0}7;Q?p0K0qdc5Tvcm;M3qk93!$Eyet(5Hv`PRy}psqUkw2IOw7p6wLXZ!J*2Yh`rfeQ2 z-sUL9ztK@W3m~;e&(Bw$&PBTyh*Pe+lj(()>`7nM-)?29Vx9=5V?1*4f0|ovow3qH zMLN=kwOB}nVptslr(+l7INSGP9@nKl zCG;WV$n-Ie`s|btEIyi%fq*MM`Y+q)a(MGf?)YOe%pCv02W)yHV0ZyV@{%6(nEpE= zVWlf6?wLAdYcKFlss$3#D~s-Ebu#ayxGdyyIvyd+Cv)&h~Zs$o@Ul$hYFQm2WV2Zy#vQErYI!Ywv17T3xn=D4D3@&y zC8(A3j&X%!KYyuojF!>!Z~tZg|NnkVv@yFKqAixiJhwlIeX{Wq{io7q<&GE%2n@}ict)2TQ}W;g z@$m@46jsK?G6}ZOuFGCl42f*(eg4>bNpjGu?&xzg6%IpH8g5%Y39E z1`>M$p_#&_vvDmYn<{g&-U}WLAos@)I;+4GcNHsiQm$)zFuH78_ zbEkGonBe|}>b=xLR2^t;c&$VN;1re#wQW<|)~bC9V>N`&-4ah5x|OL>)YDoTrxm*Y z5}xg;twh#Qn(JJ{up1x6O{dP$Aw+Y&y8Pn?#4yZ96M&&C?S$v&OIG*Q?V?j+L*CJH zU8r$xh7{saOpx7B;ikB+$-1cBP#4rT(rRH{tGeZ(k`Tp`{4Zii=MKbS<e$`(-{}sd~fp7NH6Y`-K0FVoary@pk#w^BTp&v=-cj`+}KkS!4w| zH%TyeULULryQb8TZw5=BLl52LCATwz=f3xh0sF|{m5;Y}N+T$0+(-$?|6Q+WRmmbi zraM_2q`LYUY_?0d5x6YnwuF9SQS;(^_P7`3aI>faA1J3ztO5FM*qjh4BZ+~pP^i*+tj}McQ&c#odyW#Sf z?0q)EBcyqIi+#=ZIZmG1mR3^};dAD#OEI;q*sMnBS11Jw z5QJqWr|vcK4ZUqVlqaZx#GQlqEOcfetEd_}o$FSUm7CJdzR5IwV|TTm1>yaTS|^j~ zeM*w)q(#N4`<82fP#0?0A8m~TS4GE&6(P_3$JRkA{i}kdDiDYG9Z{#wQbsyUKGT9VGL>ZS(u@nT*oDwk7drIAmgg+T7D3_;{Z{K-ZbCW4%4GT_SU;0! zieiKn@z4df24BGK3*f1x zl}&@q*)277y>2Os8J=2R>Y>4oYA#%s@6MtOgoB(M^vd4?u1Em<8n@0*oSj3OzGxo~ zK163cUy#SB<*^H|^n6491aPcVbvFM7pcfPB&9 zhul5)p&R8?F*{|x7I*F;*T(<4tS5>mG^}wT3*4Wr=e**~&3ad}48^ZTqt{XOS0rC4f z)29}Re{C;JTX`Ix<+`ZWt!i~nFJBFE3mHx&V!!#B^Za1lR=u2BBPbcGl0>#$(A8qw zn!S2X^D5&Wz09}lqT|tCd6N6JWD2lgAx4q6_-dCgqj$qSop;y@$TryDygJO9Y}Q3U zsVpOg4#Go;vDjZq^H%sWw`JV56NW7=gPx^*k$yl|xul=q{%fh~$YK*54joJELE2uE z>Q;|_3_91-!z3E3T=u@|Ih>m5mY7(4#0s=rEtHCK5!N?FFax7Q<>e__?k5J(JEhkz zV38aWYc)9jla|zw;sX{Gws;s^AY#akTC@_#v9F4xN$Ix}{<8 z?MnnWy!iIO74@B=uU$yhfHIC{+#9`rz>M^Y&KG@HFFAT)1?iKZIZa7r>!H^M<+UvH zm_+H+8&9|ZBUe;$6?E=L#ovAfy_Pe=0pigQo|jUn`tXv1VO|<*dD87s{0LQJGp*NJ zWO#zEsC%bVdr8|h?)X3}WuR=Rv|v>fca6kHAZA+_VK=JAssyUH#>PCVSy_o~Fm#C&1e435VCM zM9Tf8-7f=7C({EZ1_t{`C}xNiNKu{%?|+E9o#;1+Mh&9!sud^l=p&;2b^r2{z~oq2 z-6vgqq`yEMVs3;SpATd|xK<;ZDq2nD4{0mdQ~9I2r0lXQ{X3x(Z(R$(r(-;h&3rxl ze4k4=9R0GMu{MPe=@)`+$()g$ac^}>h_w^*sOc9T7UZ>3+wskY|FnCyd_T)(?yqCs znOl7ZKxr*E>6H;f^S5Ds-RjkLlYV;rHSdma<&QdscYx@E;)M&i2DfC!f#>Z*HI2P( z-5wd0tIAUr$+hJ7!s4cRY>g{OygrB=O9G3cM92=|hMPIGE?ezw1tN(Q$I|>#n|}Wd z8UkyG_~i=MqW=+S6$pnfVttsy@T^}A*etIEMnqNPcNISB^c&k)y+eKhH|o~t<3&gG+^}dX~*03Z5`&9rdSk;)xmbhN+gsr zTs)irM424_M!WhWOJyu!OF@|4?poUKl;&2V`!La>?faNDj(GNV7?o79u#@q3s7u|- z&=j&1@$Nj)MK%9{rgW|x&VT!@&N{yLbD9Pc$8nBanKx+b`6-29PVvUYe&O5cau}8P zYfM#@woDj&EVMwfinTtvpJBTEcBstS#f$5qfvJ9Cc#&fRNTnX}AylUdAbhp2v@I_T zJ-g=1MBDBME+}4%49WtZmDaAjh@-NE!9yz1yds%IjAg{@8A7TUiGcyxxbCo*K(? zzvz1*zM}_7Wfw^Zm%HxTs(5KMl^f^K6_=4n{4rnqXZv5}$p~!XZl=&&uM35=YS?O4 zqv|34j1f?$i61cq6m;$84aA!)7Brgka2KNT?`knjyK`hOM2DDz7>bH(K=J zfrZF3;5Q+on5}GWAv$Y`Ec3Ymh5k3mp;2h>j6Vnb-p-qhUa;a2b(rMPRE+*df4buCIqcsv$(xsUUs%bbI-t%CZh%R4{dR8#*cJaL+fSl1zjqw=!3n3^( z9zPU+e8eo9GEWXlM%o^ZdH@A=$?Jmu$O!h^S_yva0bNb`& z8$710W!2;R)Y>8I18cR!;~Ko+=h1~G4b}i^@~=+@yKMwL-1biuUS~E9w-hRd$Jp_r zYzUM)n!wCcC-pXU^nd1LIgy-QIvLsLAGcxG3$`RdbyD^(yA>m9e81nHxk46!?CJs3 zvW$xBKP1BP!k-t5Xc_t}%q%{i4|N+~NP@R_`17wtKBFzwd=U1t zXt3+TnFA>~P@={y1WKj+Wr3PXL=yMCq7w;MjhwUzD~i2;SCL2tfezlshWPA1225bC zbj7}p*z41FE4i}n+$)y9n+4UReE1vtW(WFi8zQZzOgxQ<8Q!07JVMeDZ6~RXNPpNi zXgfaFKl5g>-skjnmeh+F^ywVkl)eH6OBb>r)Kv%`pvq|On=)E|DqY#JTYrHG@9ohn z1B2bojU0Y8sc+0~$!^UVGN!sD+rGE1%mgivd&Ayw@kEZ+S_~HynRG~=Mmq;yCZ(Ko?ODYSS;@4A44dFiwo#d_ND;yDPQri z?83|y)~8BRb8L3DY?O(*7M&PCMKr5w*~P5MRwyl!NDtD^fd#G;IFlB?^)CPJMNX0b zOTTmYa!b$z&i*u_dMo-!AC)N|g1b95);n61NxZyQ-2YA3l4E~y5+=IIzBKxbdM>C2 zV0xyHC~@K6)mEpWi(=sRI~5IR8h>9Vok*==jd-SDvF7j5S`|N<9Aax9ORQeo(QO}0 z$HuE}GEy#WE0oM)^rS8{<(Ec(d0f^bMDeQ#F?Z*i`5KFGIv-qg)=UWNc-$&opRz8a zZ9(d?_$uoml+*2ljw-;Q+IM1t9P?i(-keWNJ$Z;H%3!76utM}NRAYYr;D7c*tV{WD zS}LF^xjsxQP` zcsd+39d2Hgb+`HGXAbx48*3CS@;`*9OmLRy5AI~>I;ff<%b4})fp6D4Dkl|1CAa$| zebO8d&MW$w$xq+|HHYx2s}wML?=K5A)-OI#C632a)gW>OkWH@6YZ&fMEOsejpyppl z`rU2ayu)jRmr5h-nAuuK>#j!64}-Mo4A*B0jo<9;pieX?5>vii=Q2;bh^N4QM9svu z|MbvjtGj@>O>E;thXwxNyOtFtcE#lIht71D-Y^;g#$4~I?EaU>S_PA1Rl!mzKcyEy zE3Qa?vA`O@MsH`^!tlMQES8Q(2I;zpKMdctG&)?FS>1G&l4A+i%L&1ArrX=QF}5AF zT>MVx4ACB|O&^U9_37%vW4%ixE`31OHeU5hMZ=7>s1&0Q-JYD&1^)R*FsWUlFUGx^mzT6LNrSAY{bIM*Xb@TG|GBct z?|Z=bpQo9TS<#|w=%>aeTUx^o6Wj~G=|KUzlb$h?LrpNdUC0#(r?p^}lJ(&jK0X-e zF4)2a371UGfBm*K`XlHLW32Z<`4Ohx!UtiXAsCp{b#c6A=)PpR{JN=h^aUxR;3H2k za1W^bQ#PxA#HY^0#D776wdH$^up@Wg3OnOtR!3WSMa+T`H@MOF%z*rqXWCrojF()= zhi-7FITRWJ_dCz|DZos|{NuNbO7Chu9)upr~C(=ONIH;*i1ya7; z;noVtDF+@oIFtmc7fdl34OG$wvT_0CpIzRa)ADPDI7O^z`*K%Xd-Z&>!Loee+6Fr*}T}f$z08aYRIxTV!c9M@ab))egZ0-L0q2t{vnpu%cmXZDPOBCjN-)s zbSB`}oIMiUHWP{t8;Qr-zfEHH*x0zZCluT6|5@#v+G?L{Kz{a1WnGs(dIxuMp1VEi zSH@yJTziMt*NXQ#-&1iB+?vF?L+&AgfMUAFGw%hqX0tw$ra9>aNDdwPt|x2Eaoc$Uz;(tXSvl?D;>*8|=jx5jw}G#A;fmzt9; zna?YPJ8^Xl)ccc=N$X19`a6|@M0ITxpoHGKp^5Ql7ru;D(?W!pF9tygRK(TYffhDzj|C2{2Vf3`vJggO4{RFAz4HwmH25Fy?J{d{ zsnC#AlHf5kWhD-^7|E-c;WRb+4-9r~FRy_63_o-Vsz)NbA~ub?mJD0|u0BBZYJF2G z)TsEuel6;4j-EySVNK-T_d%V-d!&eHj30zpPBeMSO*@wSi`leL4j5 z%GD^G&s4>5yjNh6?yel?DOoDoK#W^3I+G3r>JiBlI-?opR%@f_38k@0+rk6e!_APG zlr;VY%O}N($0`4r-nj#dc+$kxd@m`@L5a=ydLGp&h+}m;e>d zwI3}ss<`6AR_g#e{grL@>cA@rd<^^2wJ5M;VsGD`!FyTMB)&XT;9*|P=iJpV4ni)w zz~PF~3RU12y2DoDKhL6~95#Y8(FG2o-<-%Mf2C1cnz{mnxS+2je5FC<+}tdflRN&n zawJuaKCO!$wBmjplSpbWn&tT3j^KA|Cs3wvQnK_wVoxMXq8==jl1TL>B2c5TB>7Rl zd)r?yqRYN*`uKiCUX!h|3Agbt<*$jnQiKJo$pM+=Q-Y}6$93m_5!Qiv=YCn82I_E)7A=v~VXV4l7vJQPp_$<$(x%7db5pJC>R>Hx4%gM~R zYd_oZMwF*Y&lIZ%J+lUdpepOGDZOL|?hr%tW1mntN&{g5s`QN|LGxV$2WCA69hJvd z<_W0nLla1;h~ktM{2(xZ=$Pmw73CLJ8P})T-$kKgJZ9X^QLBkd5SOgglry7r4ephD zL#Uqt0ZuD^-XFPBHV11rM=-tu*$pK~yM=ja7a6S^!K?Lsu>~y_YPmRey_B3ckqFm5 zueV$canC&F#P1BF4fBgw10xou&d&ZK?Z`Zm+6BR`7alhFk(^94Y*6^i*GRczwZmLZ z=p8=3;m)w6ia1MACK`I*te&Ef=^zE`s+66zA9GZ;_pym_G^UvnbS^9(93CpPy}G6V zK)JQxua1#mtUtgY&)yF71f`SU{6!=`Wh+8Oox zi>yMN?Q1{v_~BI>Hq$rcY_)kPFY~`(CpW5n?`&RUZJoKgIVUb9IWnhz1e{I4ng!Kc zdYT}E&!SPn^K&E~FZaAzG*WhwnZH6FT8A^*k`{)(-L!0PzHduft7ih?cd6*y92Ro- z6vS50?f`;!ygFeItKy1C%+DCksY_+N>P4Tg>Ox9D8e*STF$+fjt^XhUzt8{yQTYsH z+vnP-FY;zbN2x964_Eqad?FZx%z(mIYIJE%O!1CSRU3tVh^$!Dyyxm}QiOtdXt?!Y z#R^qS-li$b^P0FvCW~}2c%;YIVOzJOm;LSlv3qreejfxPdyObEZON+9#`1TVZI~TF zlw74T_&oG{fu=p_w*~Tsgl{q9*O=vZJ^S0glON|e55h{V;K|hLs3vMdQ_%)=6WFX; z1du=f%4>zw4`u45kPdhjD{>sq+Dg^4TG46S^?D_PU!?M39%sR}fuo9OLZqv_BEb*# z%NTTR)s+~;h1VXmyw5O@Y4WxI~mJ;L~?{sr<&$UW_EaB08*jnHlbvfk=kK5C>S~5x)5m^w(x}b z1^6(f#jy1z3dZ&&(8_ZNp}O<=YMzsd5rn!pJuC)#in**`jSUU2nz&NcjMumi7ILF) ztk^28G(wN@nPh&suPS?16DL*TnVlP6xj=ZZuo9=?=Q6|kMiu`qpx@J3|s+~TW ze9iiZ=bpcK4i9Ydd_Q3=Vq*ZS8mv3a^SryB3#rzqZdvyGwn~_VYM%_o<8nm-36@7^ zGy{~Va0eo_uC^+VOX{j-H88Uz9ihX`RvPwsS?<%5De2OJVay;zN!WQUkg%HkMJKT9 z;RXU~<|Y&l)Psq%V{h2TR#HLPEuLj zwP;Z^d*Jh`(!Vc^jqJ3QWAfz+1n%nM?+;Hxs0^=5`)UHEKt75Q2j&0#5foOY0;EvU z_{98@u}wl>iO~4&WjZNfOuJ;~{z6AfbAS{oAn0wCN<%3b-)~s4ExuOvF)v(S zvstmCYqw?USh`2!f)>INHaxDuJ0--2!D2_1tc?d~gT@ZU%j=+2gpbR;2 zVzXwY^miMjMO+FSiA<@Skv6RzdbYNn9V}(ZGzWMAZFcZ5RxszU(kXI~9%|p{UZS`g z-Ug8F+H>}c`qta|pJrOJlKc5@JYIg1*YaO!IUTeF*m)FQK#o(pa5DYC>Y1r3e&069 zl+F~R#3x_J-KAN-B!&|#skD+TlJaIwVZ>ZaVXka1`WilDg3TOKBiCgS`|9jFy(^5^ zM00nM$BrxFSEI@F`CZaz0tF{AW-jd$eH}faaXN1ZHX7+LX`qyv0dB8bbKzxLNMZ{g z>2Y%|5;}XxLxh6Td05h9KzjLcT;v<81%dc7--rH~uW4nhvG8PznsJ4=SQ%o4n;r#yG6Y+vDsNHgx3v=S3y}aXPPfxY|+ijoe9Ykk(?*h$EHE0B33Dljw=ZC{^!1 zT$z1_I#)v3bqBfsJXs}7k4p_y4|vsXYgfH*qrf}lFCwaP82^L1wld67T9g4O!Ad6* z4@^D=fnB_55Q0*^E&<<8X*2FD*YER8IJ}V-lj-u0!4oVu>ne2c1k1T|svQwL?J{en zSqV?D^q4a!8vFP^ufY>6Mh!V@oudCTCUM@Q7_3-W8oKZAhvz` z5d&iY={!HjoY80C#X5QEY*gnYz`}ej-!DHdaAJIOx6oG6!aDQ-+LqETQ zwIfzCC%>*RF`w(*F>99Cf#^wEQ5S1b;@`#?GG6S(_ZH0Ohx8#U^cG(`thI$^(O=8C z4TcXy*U|krY@#E7%+b;}IWiA;lKK#f8_q4BY`u#@OF(?6*}u4{Y_2`Q*P9NEFnIMW zdm%0ILGNo}auR7BZexo#fA}sr#nz;>AU*&o>2kAtUjgJGLzbX>MvqN!(74o%o=K z`t-&SFE^Q$mY_*q7u1w}&fNDY65wPul|nXY^T=0N&h~J&PRqIM{kW{~F}WZyF|K7LCC0Y?kHhd^Faw+>oHXlmlNH zcvLska)@ufP=3MRqu$(oQM}PO-aA|6pEM>Oa&A%!`Qv19?Y-anxA1n#6Xl~1(i8OX|2$T&$`w$A2X`OQ40K4#IY9NDA!t-~ z%G~rV?{+^mzfw$Y_l@oCgUAdgK{Pt+9v2NC^cAb>p9S8!PsezE$qbX0u*KUglL0i} zhk7CC{I#K^}*z?qr8 z+Q~TF#94?`MBB>RuXHgsHa?iLd_FJ7x0K2OD2+PiW^-jzZqwFxxw^uBR@3Yz8Tl*R>hr0jMor8>OAf(xRgTq_gDz+ zp!+HRoUFT7xpyr>fGQ=EDv{|ysoudTYFSeLAHf^Go{t9=2KaR0az&h-TcRGMltFyD z_BSe(UrVvBURI6zjoG&=P3j0?u2=G^#P}#-7q7$pEXJqweBF(&F$-DTBBn!Row*z@ z_=JoKKbvi}+XBRN3d@Ax+ub|r+90gvW04y%qw1iFhtoPsJP35np|$#sQFtNMV8r^L zS-#Q}_H$DrxEWqb+~z7WP)V}o99E`5#N}$J`jSDq*kwGlvxdaOu=WHEJgg9a%57&Y zhEK@+q9Y-6Q}}%60rAjgzZq2^3#+S#KxSZ3Grb0^0XZTv`_&pb2NktF) zsi1JNTr-jOLZ>~hH3EWuo7)!&-ht=ZGKEZrkvl49APM3>@Rsn2^Jy>BD77zads5V5 zCQgERHqqDndCPPJ%tor3++DoL=zd3OEzdVnJdn6VY_Okh7L$_0+^MbWBNN!7-8+D* zfuIH_mGx#<1cO-QV5=FCC zjorB;C5WS2!-9a0inyexH)kCic z7s!P&OSwZT!TX?AkDp?5tCXK-t7=<}-ee+^uD@<{{NCVV2(dqFRn8e0%3^P6S3}y{ z&+g3WT&(8&N-9oLKbtb!878EdsjieMAp)!RkSK`ZoUIx7<9Uf>8K6$c=AZcy4zn{+lUZrUUVA^ zIH01q3|{Xt(r_&B{ICpZv8x|*t$4RiN9J{5z*?B)P@A~#gt0ccD)V~KV_m(ljO9mc zvQ&eZ5~W2|-x>q^;`&}1z4M+>Sl=xn6kQte@wpwE85l&Nxh$ri)QyhiuAH5%^+6u* zA7BrUeB%oly-g?DZ2)(q^Di_V&r}EscJ}Z<-x?2>-RGKKH-fITYyS9#PY{R0B+Y^5 za>hmxntBeZYQ$iW(Xl$bc|3aDu+QU&-2M0TGSippZkAU&i)_p)%yzF;h)*Z!Pz~Se zsYpvPo+XYjbBWyH{a|R-3ngk=XmM%&x@riS4K4e?94NlF(A9PZ=xpk(B;^0h1B(EW1N5)BotR)9Bc0DDYbUM>@3&!8G*EMPz4>=pF(Z%pTRyfiQfRIcLyDZ&H z)Si^D@-jGg+y_l_n_h4^)-vZN_2;tn2Nqo{)o5&Re{mv_WegyDX7Y0uPU3k#?y8z5 zyqEnpQH=q)=WpIlM-ps@91z=At63Nc4O~AqJ4h1~{RXXqARqk++!Q#LD;9sgmxAqI zc`1`Juv~3uYAT;CmeRYrt`yy!^E8|4+*sj51B^nH6Z#T->dmibIFKA%(~s98_|Y!% zurMWCieK|?c+5JG8B;zYrpT)U5l=7I4(G#PQaFp>(f*##tCLyOu=MRW3U%Nw?3P@6 z?1m*)NfpwUG;t2+`9_hSQkvkx(o(BQsyf8M@r&2ulLO903DPgA|9oJ8%CvX^4B%(V za|cGHvsYK-SH?i(yGHbrdA?38$Gs=5Gc@4F;Z9mlaqF6{`!WWqTh06snT^{CgYD0d zE1OAa(w?v38Lo_HQVdA+!JZ3e^LJ;Hpv%kzpe615t@ zITcVU3vIU{t|x-^e>j1BzBwjr$U8&asyfYAPPtB&?p$m{zCtkv=>Q)y^ObqIX?iIS zA!tK5YIc(DBKz?fx7oZMZ=^b}h)OkFo4d6pnDJY?zK>4-vqL*A#7Wtl=VG(%va*p< zTvrlbO_RURmu{PssjW7pqy$=p;9!Z-;D>D$A6J}tz}SEiB&@8zbvp!@fjals9F-r+ z974MAH3X_O7|`;%De9wJ`lfn4{FkCs^pSZsEPrt4H`HZMZ0kBM-3$L3P1H~Q_2zSd zfQy60Z`RuzQfo$8ANoX)a)BqAk0m8okMD3;5O;52-Kx=YXEk&($^{Ok=lX7afhyzJ z$Q?$wYfJKSW!l8hPS&86(|Vu#*oMnfklxk}E!f8|&!gBKv!7V_EBne~E>L0PHs7h} zRAP882~GKvCY&~oY61Hb(eP!;EIDhaFSh%-O(LROx?rh#0VoI=lBPUu-^=GbIsr!o z$=-0|hVhRGJvm6kon$4~M9*IVPrHoJ;*$nP_MxT*LLOs7*s*A!PMxrsI);fdNUZy9 zfan8uVf-)|(haddV(?PplL_PSiC{7Ub*Y%8aNBm-Ej8;;nz!o^EWr`BxE*yv5(jYA z+7wW6ram_SIK@86c<4fgPw_TLJC?{~9Uc8H(-hqI%@&SLv%)yn1eDIRdcsdF?6af^ zyO(bGOtGF0vwHe@&C%)R?!MLE{F}$_x?WGsE`AXs+;+sy%v;<__zIgirN`TsWHJy< z_@iqG-Gim@pEaGKr-&z7F})q>p3{bsZ&pGH4FI5LnEcK6cm5G{7av&H`(8_gAk!7g z?GtVfvjZ8L!<3Y5PYT`M_S;43Ij5Xho@GY8U}Mn~H-EO@aCNC~5p;^^S(CgliRDw;0l_r-IMPM;CqR0Jth!#d z)z$cH<)6`!!Y<|0(FA!vWX-6tC3$x5%JZHFe2SYE4o?uuaU`X57NdG%)TviRpW^7vJJ$@7cvR!9GFRD zRhv1ok~J}BzI5VObth+V2iq)61vG=S=2Oj%0-=YUNFQ{$CUOud}BODgIli7oSg< zp|dEK8n^q^vFd%@>-?^5-QBO$%0|IaK0#O@M*o!+EdLJ(Os>eKx|AGX9i#AWBEvfP zh(W(=wbHO44|4e%)xu{8srw#FEuG&Uw$CSS|GUp<9C4vaDv5l+W%^5%9mkpR{q0q+ zmj%sTSvm&c2hUuTBf4Khn7`WY>85S%!j^iPY2~x{yP>)a{31xo>mgbZ_R)M&_0fnf zAeX!`5T=(B|49Ch*w{x^W|nxGB_0;?|4krDkbm&MkwX^km*x0zG`XDWx2b5EQ&y6! zzjr+F;<|vX2O~iQ^?0Y9%*owNywmRdH|4~*nq&$_zuZ9k{;eboL&*H-?yr}3dhckH zc^1wy7T=5ePZ@=-wlv@$fqu634~emCPyb?q|9CATD|W~a+!(8X-2-LbnRwl{Hemga zKu>Qc@K>*GybJ9c4zx;9*;}l9(C7{xwW+JYje*OT^>07Se@~WWF1JH>+k0CK{+unq zbV~YxH(af|2qAf;z2NOMeBxKB;CUnrfm7CBM&p&mCAgu4oFC(;xow z&Gvn5QtntOJQ+6BdsLsczJcY|24xsk_bcvG!reqLr_as5o19fN%}>edr_7pr=f}9I zv1Vg?$7Ss1jEbbxn53NTHfquCr07g9wl1!eblP~_ucb`+j`aGY^RKX7pF z_;vmG**x*u`I8Y2Qd!rIl4I|3({FHhb~yo*piy*onp#Yc>xxl?DHru&FLd*1$ zP`-|~CEm_8JX}pb*~Kqmt73HS49PF=OMeL@wcDNCgTWawwFgc}>J%Qi!e>#f+XoJH zZs0M@xGAQ;j2Gm}4Sym!5gCbX$O!5_&XBH&1Wg}%6><58mzHri+oLqsYR~W%By;aF zX)b;xBs^*Eg<-sr?#OWT0#a;+iU(sfp(X3B0huH*H`DVAAoV^A$m}YEeue^Dl?ztOqDk*m}f zla^rWYZIr;C%>6E0~=j$`4myfz1@jU?~tn9J|JH;;8C~qDuQ<;?#4MSdc>??E!#?} z;&ShqZTClJ+muz-r&_NdNB7KbW8AfDX_vE`vn4P4@u@DSX%@Y_m91k#UYRc#UF4GO zcNa%`5KV#?m%6)B!Nd+~fw776xhHiFW+e~s#+=uBom~+Vq z2@=iJe!V-@H*+cftlWZDyzI@Scob zX3x0NTf5VKdJ$e`k!L3rm;dgM7coa%c`3H3tcOM~(PG69kG!5w3eznw=n16jq2v0N7JxmuLX=KQ|2v_k#8(V^O+QQhBjr|@_e$o?@0dsP3Y!u@*QfqKh zHv;Q@GxcoV3nd0TkI>mGQ7KtezFh;MzD&s@}Zb+rr- zLArRoc|fXwYpT|Q#S0YW_)zQW>GyJ^BLF>Z`FvX^nPNE7CTS(H_l9ksel4{rg-bV3 zytE^-jW?WcT`2MANF+^ZKDQcu9ewJH1!Jocb)$+5AI=rihiYGKs}*9Lz#}zo64%I~ z(|C{dSlQ=6_CPas?QmL1KX+WH7oId#5CL@Yf6S`%K|MQqL{W|8aUSB%88zui%o}lv zI~=ICX#t-Qmjgf3)!+qgL`5Y1ytu0Ud;*ZDolDXou%&$$diszS8%5sN_G$~bpwjb(Su z-pAVTE9mB~eB-Sym`H*}mmG_&_fDBf;H{>K#=bW&iL0B|g2uxIs)0*zk+UB+)f3}e4CPVR(|o8+fF*df{V-o zXsq%f4_lka7u>&&r`Jj~q#`l+ z8isiHC8>z6Ol&3b!6#hVRWe;UYJod){EPkJ6122_R?YZ-wD*>AZGPLjD72+5P@uS5 z(LiyRLXjfDgL}~u+#T9d+}))V0t6{eg11<3_XLW&)8eHk|8>?n>#Tj&xqI&q_kOzH z-uyCuc{9fxbI6=yJkJ;3^3y|M?*J&i6|#pf%A@N&)o^TXQ(}=1ozX|-ZO;!Gk#|A@ zYEZGa&A%mLgts z{7rjt6(!5_OKEvMf+JcJXI?{DEB}+9$*6p!g`6n?yK|;A;WMbxE`cclt9pj%_C=bt zT>`Ttn(j2=AX+z$a3emDwG-_*OG*S4lf@|}5=0N_f6M&VnI0~SU&fVeV>a*xlT`3c z&?)U%DM+q2z5n&(7pQVDa(%Gx4+j3+1u@zgKK%RhU*9BcTD^~OxWz`h&P2&d^OKSH z7Sc1o8p-1S^{5Mco=B9+0-#C8z|bM`fJp89(!>K?Dwcm(DSQ$otFc0Y!H5y^JW-L! z{$cxn3<&-uR={*&yA8W5y0QC?X0{SQ_y77l%J_14XA;{0ujJ54F9Nr!vdD0F^>Qf# zK_s8KHTXHj$EU4+c+2Y-D<-d6%IiX#xd6&7WGRr>iApQJu0ldD!Y(-Q&BcU7QF7cG z`S9aGdA;exFT;ANxBFDlZxR%TA&pI7 zB&6?uAjz!c#$iMSYTsmG{)hr-$<6}}0T12`hD~D9DEdMSB(9Ew8+FPG-9v#!vTg2( zUteG9tjQp|((ajV>}9@N0zW=}^LDJ zcVs`I>Th1*U{_N?K^ljQXMgx*=lqi3gw^+PaV^Vn|aT8Rg#5xr$?R| zg@wRBvqL3G@KT?MvzbH{e&T$+)ewWHu;G4O_|WKaqF$nY?~C<>f!C4>R{`KP&v>Q+ zFU-;6c>9M1b_d(9O|90H2geZWd{utK;YKQ&=u-CLuvd(m;YcSId0ePGriZM$s>t|V z=4k2nVCo7>An6Fc@Ru<3#$`vBEGtR)iM;xGFP=@PwSl^vy4sZi-774O$oKV)sPw5u zg=Rxo$(*jLMX)znH^Midn2-YN6P$xreW|3J)!-xB`GG+XmrbL%(31`^7T;?nXM#*6 zZ7C;vyT_99WdJEgweOEyUwjFj?z#T^ThJvB>5IU@kPqAT(ZrzT&y*gj(7;Ez31MzGAkt5 zPt-nk((OI`u!fDnC89qdFc3)L#HgrWWhxy zf}D}JKL|d)RUz+H)qlk78w1D2?x;v{rg12k(J`C#M@E-$akO2#eKoMOL}jdwg$v${ z)krXv8GG{w%82yJn|LIbKFI}Z{s424?!~T)JT;mHs=Q0AT{U}WRflrg27p)7g^tp{ z5~`e{xu5gYx3OVtd0>r}s*{UsCMYt4b6Rin+v1J~Y0TnCpuX1z&h+AnA}-L_urd0z zm$I_f*X2gge&`Ze1}{9w0BahQAgXG&T2cs`Vc=onoZq!!^zMu zWZP%+m8@MTN){`=-gi_N`vMD@<6b48#U8|XGA>6VXx+JDf$8Jgo5Wf+F?A`=wnadx zNO;%U&klTHxT_r1&3$9pFANUKqP(YLv} z)Nyk<5n7+N{y5*AJ`9~y0`Lkfg8MrvN~UwyE%#JZizlbytb2DO_cX)eFzrUT{nnBx zOyOs{4x}N~f})Q^H;xV(?4QqmyI^y2{d?fU(CXRKAfmuM8>`eXmaC(tk|}5}eJ&YS zaXby4_YGD~`u&SsyA#AGgAMVft={VHne`x1tuw zbXX0l$0~V8RyK;MZ`}7}MWej(SCr_EL&Hycma@(`a#fSyslD_$)C7!9db9!YiH)Wt zg?IkcLrJq%fAs02Q&MB_G)Ym+mmB@v{v)s81ZVQpjS80Njm2{14lefrTi`iXY$(O` z(}9Z|1G%<0sSu1b`fr@DnkBGH8i#Lnmbg(n#XvBZvPF)@Gf|6g`!P$Zib0N&6LL=h zDi#kz1W($3OYX(w%K4RfyKS!^l$e2|#|o*V{v?$fK(I6SRCb21k$7 zU=>lH&k8mK2dHk=BR@*fJafl^P;{He6VxpLUEI}r5tySDi|}LwaL$;~H=hHIUV7JHU^Pi`} zG+#N3TZVZ*m8&-fHq#z<4modpU7Eo*ZQ3p2(W?>#YWaazK~l$>O*Cy4CsQp&kQ1b{ zxEZd^7c%_R3^I%v3wMcfSY>EFQTZ#=f(+P6-B(V$N>?41XG`SxlZ@>GS5>w2d|8R} zK>;abZwIq~gttB|fW#K>;XJzaRz7$%zubVzM)xl*bfJFP*=aQcIQ2F;xUpAF06IvW ziVJ$`n~m#~XACMBdA9`^)o2Nz#jMK@yTiYLG;8Ny36+)|QQ32bORxABq}?#n zEeB++LwCkb=6-!O&*mwxC~>2GGf=_48>H;5!hIF3q_vi2ZeGXzT7%A{Vr2C8561lH zrEa71(xxRxP3n6tdMYk+U1c)|zR)pewPKW?ib>g#pASF2lD3^+j4Sx{UGL{SB`q4? zPX@-#QiVZaGh>HS{SkGygKAjKb%Itb-5KBbD)e+?p(_k55b#8pSY*^Kv-pwIr&w3#!2It5t8at!!6(p}pe*4x!- zRCwV)o*6ystS-cF(VQ?I`o-FhQA7B?CL=p>o=o;N$g@J9PIt4YbEd?|6RNe2>|RRx z8Fz6{U{eLmU}B;Cey_{ZHRI-zU4j$4FmKW);ct-@Iqxm}CNm}zg$>qrVP#GrqtaP> zGj&K(q4A~lw@8_TXFO3U9jf9`k+b}#M54V2fE_TUV`|0rhBf5?r2E2<873}p#a8%* z41_U5=`${;#6S{eoSx#gTPX}XIuzkL@v7!t7CSo-&8cb-In`sKQkDed)3gFia16aO zJ&03KzS}&)&zO7CV@}U@Cle^*jVj|A^pLGXu*y-u%EE5tPqk`|cKaJ$*BC@-#gRkS z+4^y5##IA;da~@W{@D)qMPji61O0dH@x8{c6w-cj+X1z0l>@=d8wGTPerG)=aXtx; z|DE&vR}2pMj&5bJ1YW{>+=wBorlf>O6mIWTjg_;IwW;w~dsT#~hYlzI zP=)N|JgmNAkzyiQtjmH6D_=j2xPI;Gtxo1COlRUvw%HQ(Ajlm zp<@ars{#T;Dqkd%Ds=2|zqhubLk>Hs>M>TDyi82O+h{m<7KmZD0Q8Mn5Ehv>O@|S` zqjODe6|-S20fcLGMz$F8utyY(_4bNy8IdV6Y&cr#4LzTa5|oZaD4#v$u$d$2B3d7I zc+Ff^*+gO)o1K(eB2GEotqs=}GFAhDjIydpSsJdpfqOd8s~QWM#A1QYSc=UU(` z9ka)wMhtyQ3TASuFP!>bW616&U)YFMG7-sC&=(|~k(+zV(Y@xzS3jP5%d^rQw;^2Q zsu^)QVXVTz)z)nGB2~9gg%jAONob!f$YHt$V^d_Z*3b8e9?Ukkvl^nCxb&p3Q)C7a@uVcm zcZ9zy2Yrl)sscK(pJI?1uruVEy(swoeUvi=F-ABa=Hgb_Y|BLmvmVA}D<}4pvWQ?)H1p90P{3CT`MZ{sTzW1PMcO~gs6`}_`?wGcl1DE;x2M>^2dp8`44`HK{o>>`#~KIw z9!E?Y>(1_|Hg%n=;n)-DX1@AjHaBT{BG}^KcmIH~7x-eKf+1a>qUAFlJTKs#=dzcf zA_K?bP^YkkASFCp>YKBBZ7$P|u@c0hp1baZy^!~So4bQmoDpvkekj+caxvgWlIO$@ z59KR<9YgZdB<17{CFP!Nkr%h8b1l< zo+kRa<5BjDvH@FIhH^%x4?PYIwlTk@4|!i;2ZKTyn~4FmC)`v!nDa>=xL2e&0T0L$ zNGhKRrQRdkMf%Z}Qe%gYSe;?N3s4=lsD&R`p6-4M^?s=O#%7Z&8`ld*S@}_wY|p-? zF4{0{*vHvP%1&7gMOMx5Ql%WT%>p$4ZVrBSs)^5pFWyRk4}%b?!;+Pcw@^f%t3t_< z-B$GhZYUtjcV#zeh^2P#j9?sxW=lvTp@C2oV0?N>1vS4k(_EM! zy~B+`D%;@t7Pe{E(PJA}v)9vzRfDv<(L5kuOO`iirqaRMB8NepZd1#FFq?xz6igSu z(b9Bk-v9yAxO(<<_Ie&elHCbM4;F|l*K7NY1H2+VwiN9t!BiW9-Ks6mAAHl)$IWML zao(%{)aWN>?q`{BrrBU&f_*|I#O5pV$jQfz-z^Z{M)wXBa zzTdWl&BPiS9RQKRn2ziiScpj9q~Mz|@H+<^32#x63bNCS%p>7|3N+<=c$$}exC*}L z%K{6m+kC6qYk;YuDk{`&u(vxZMNdk*1IGrDuy!>dmqDcy_xci<<`!((aj3AU_Ye;V;((>$sT4p@cD5Rit?&=;Y_<-0fK*}1C;t?4GbM3_ub-?f@TmwPd}iNP5mP%GbR$_FN6tA6?4F1YOKsbpz+RI;5ZzFUL^T zFYcmu>D<8rj0B@fsgqiT1Wo;D3Nb|yYTXaCe=xWLZ(gbEHL?$xwm`5|M%X=Kn-L=M zEF&eWN5xgB@(LBYUvU9=JRKtI%{vr#({m_yZan_N=H&Q->om;j!BR$Yy+jzzBv04f zN7j}cQ&>-O(OSMJGkhYiYI(mr5MJ$>WgMEfk?pA+>_{BOS6LKdBEXa2mxZd4bgbfB`6(7UtP`?C-Yp8R~(Uv+tAgS&CEIedYSQ*zjz~> zP`yO7WuWC{wwdmNP9PfIeZp+-A@?`Xumvs|6(^aIXFfyxsTrc^d$$A0d1d>y$QFUp zW&KZgk9wtDmnT=c+9p)K^tw0KiSDd2c>_2)kG&73ubm*6L zElFK4K9i>jcVM5lf01>zEWjnWpFfp7Rnu9^MsdCcY%;tIsj+xfw>=R$w?kO;FhJ|I#FEsIKof>C63rA8!sclgC zOAs49f+y~ik-M65t|AQ%rO4LKTFItUoS?zhb8997n;*A@^W?k>Y1@7dPU)xvxCHw+ zOw34n*R{7hU(;yQnk~AIFPc8rmeP)U$q7W-hM?B|3)UkRRqQ^Nmc6NEJ%U0HHiq^^rqGGd6W2F>bYJQPiL+AA*fs}Bk zn!2r))1JdMjifBkzK9Bz9w=466XdeB0DvF9!;SvQ#98lZl(+5jrl(UDLY|)brEmvD zpv~%tMk)z_L=o~2#rFtj2g`vpk+DrAB%cRPacI1N?Y&cjzl4v~2lbs^>7AZyRXT(R zwh9cd1=f?dv>fae-<9)@>6+5gX*q}wNye?kHi-mZ!>?xQM)m2QPw#>iim&w6Z(egj z%#&T-qZ=?>3fnZ*wAy|byjygHwW#lsxtt6Fs+P1qea+$VCvs_+9g_0wNT1in_L!L6>0Z+F-mP&tPE3?TkWRypQ8* z9kKC%bl8y17okKuJGqfUc+X3wOmux2WHQ%!QxFPeJCge|9eXkN zw(P%`RnGo}gpckH`#UtIo5j-Wml9=I5ndQIN1w@Pr=JKuQ6zaD_Oe1rsU^cn?_t)I zTYyL7)i_hiNp9u9|FE z>QS6}uPq_0)LALQRb4^Pb_oF^-Bc_fz>!o$xhrcDPVW)5KLzPpzO2i8J#A0Ia&4C5 z6zF`kN44?vK=wUm>lu>VPK0!u$|6>6-clCg`&qbY2{;}4{Ody`Lm~%Bd1B#KC1i6@x9WiWo*m7T15 zT}OuVrY}!e9B#on0U*;9mv2iKAxK6z@0_h4X@^@mJYf(YnqslFQ4SB!m+_Jbt=rH| ztAy$+Q^`n;2ocLg<*}*I1@ZdyNQUmz6f1{?A!;J()m!nlvy;Byx=+g*J&RCQiT3E$ z)DJr-!=7bV_DKMqCf8FVUEiLct{k?1T{B&ByF=F(AQ&mq*@$VXCZXAb(LA0QMed$% zWgt8vdjAFfzo^)Q z?v@V*KAJS+NgTvwt}s@eeq1VqzgaCao`C9oJ8BAce=ux!BO( zebFl=^MgE9o7NGH(G5Z-H*$%ch?~-{+zhGT0vEk9+!6b)8f$Nmt6K#+1MYPAh44yM z3p7(jO~dtvr{rWBbfg_6cM;@AYU4z`GZD&Oc01KN{KYil?0h#Qm&Md3x?c zPp-j=S&enCbbGD&;JIY7%nDnFs<~pBrrpdOg72t|ehJc~a#Im777L zrM+kyOhxqbg!d_J&Rg9eas+#rDguPY<)o#0+b43ndFsF;QQ{&6G zc}B?zdrqK+V7;22W#F>!@0qZa7U3z7QNMx{QQ)VY3&;)!mZM155g6ErJaZ_-+i+$B zuu<}G2C@v%mtY-h;eA~l;W7d_XF{N^CbYB?{KMBSmkUF^!_I0DHhPvQ(FH)3@bmb( zhQ=+>cdsuRFCvH(aSlEEC$&8x;5FBlOHe>g^rF4iCr^jey}59J(T zpKNc|yzC=~ioJqGe|MIAy!ma5Pc*bW%vhDygVqL;>U+i%BIyl`1nN5!KMw{AZ997^ z{qP7~_2y5BZ5vH8LeoBVzJ%({30|c%EsahKy&{PNm3W$fdtIRo3@(zI3po`&HS1Sk z4Tw^YoKp`=*>U-I$3lDYkVM~7&6Vt(rTOJJh&{hf9H+Py`&Yr=8Zm>-wkEaGk$Hn! zBa`ULnweoSKl8PG`xZ>GgY6H}2Z?=R5drp78SQI?A}2IuSXwCdC0#Q|^+^lT{aguaj*uf<|^!r$-!G{}OO3LLl?x zhw7#R8o2?m6E#ZA)J@yZ7)#MJRTxA`7K@xkd-p8J0AImUXX)@vmAfRr3t_+CCHE3) z?el~9AoxC|UN+79XW5301vd$tzhHJnmcK5g8?#2*n^O`-Ec09>7{>?~Y?b1#jB?3w zn$7vTTf_W2@&eWlt=LFu=x|wpmH1fKqwA@qYyk15Wv7mJ&`E)F|4(7zmG}-% zi|eIv2F{<=l*A{m_Zu=qU|ge)2ipR@Z`r>alxMqO+W;)gN$=U}cIjD6mk+F3W1$ig z(M>tumDMWH5lqS|$N9{>inwA{j~5SKc6ZaAUDmUJPr1douyrebg`I zV_zBy@Z)ILGOPCXxEuAb;uN*MdM0un2^PVoA>cf%fC)=D7%SRq;LOHV_h!fWde_gI zUa$MThjMMUx`xhlhVL43=zUewAKs7F;C@o;`bv5csqN?(*LCmIZ*0X$W>VGLh)O@# zI&YGlo$!|UWa#Fg63k?WUDKk0vuWP$y@^ae=p85}rPR_B)30TT&z^10H}gT}G<%!J z=#<8sizGC5u(qOJ$$#5`boJR7`BQCMEya)%l^Dm7!n6-r@EAGSPvl5V*IKQkBGNeDA(Eo^6)kqai_BUgAueWU(@=4MvpqTBb<>Q$ z@am86Z?A3jPzNq9EajBs>c1o6c;Q(FO;a)kLEF(8(aQ90EX9rfHXu z7@5D^H{o3~{ey9%Ho9sMs>4u;SZ&cBVKW-bae2mZH`K5TcMo8CypR&Yv(>krMY_=M z7Lu{j>otBJDlW;^GaW`^byfx$yR;ivCXmP{H_p{&2e+xYuB z09i*B2Wk^__lMHHlFI^~Zsl26F3$i~H7EO`r>)Lb9Thdz^p{)){=Mi7VUR#wL%NXkah>LcYJJ_U;T{H6I zTZ&(!<*M?=G*WK7qSLJ}0|a7=Y7KCdmzP(TC;f=7P|2sMKYW$YCj1!tKt6YCuz(vM z^nh%Gt7b@jFnKL`FR(>_bv{hOb@S?@v;17j?vmviiZdV-LMdr`SHUBk-@i`d@eaQK z2ZMWXc2Z+iVP@!gBe^o`c7^^quV;D(jpsy4Rb%VP?>9O17qbFj)$V?zo9x~Bz^Sbb z3o|Als%jS#C#WYe3}A*J`s$T`Zt<$BdAZe1oj6TLe>J3Hiv8k^G{50S0_D_A-bf|H zd1#7_oa_&VKm{mhv{omOK=Yk>PUDH#^*r=ojg^U`hdeX2m>+}zG>J_M;A&}Vq=pyd zCfMlBR^Z7M@p-k!VCBz|%`#M4v#ui0zB6fK z%7pekc{mn@VJkd6Q+KfXWSbqGLfB^(h)Z2b1e+1kilrs?qsgpBssXJ26V*Q*l)+7t zTc01-Ey_mHd?ei$_dEo4;>`-`9O(M`<|1#6bNt{aHK`w^Oub%HLjQLp_kWgKXr}z` zy4U(G`1a4=7_9yoxBnyPr+H3-qw^rS(1!VpVVp?cC;d2Rho260P0nn&Y^xwF<2_`v zbKs|E3jFY>>e+oPJ8E~;Kl+6|D52$2UcW>+4$j(iFDpUFRte>Pk{O*Cd02(dqAW%9 z`l;}A(zz1kIT6fkClA|TAt%J)YDR%~E`bwhXyWDT<76ibB7cuf4*FVc^WuW!3n^!N zyH9H50=v72p4wG&8WdH}{i*_CG#UC5DsN9UIr*SV2?v|%VFi*aO3u)iO+GMZR`jW3?y_*^f15>&Lty|0Wb9yjfxC6Dvc z1#gK;C57q8F@`@sIV+(f!`UP;{Fx4Xg#lvCtD>jLQ1lT!eGaAo7vW6~^z4;X-R0^~ zzU^gz%Xvamqr}gevh(Po#ZvVT#3fy+vL$Ss8AI<%+tWs^H@5hbL5tSbHq||TU@&G= zS8BlX(9n1adpua;m8ktBy0J0q2Rijxjme`VUPsrzE#Ii@3@m z>RxuUQe%6_>O$E$2=W7??pr3B!iM}Ct8{&>lDE?X!9CEK135SLSLIj!4&6E5S?h~k zD|)@B7vzrhClT&Q2N71f4cE6bq{QB>nL934EPX4es~g-)Y_EzPxVX+MA)~%gz1*FY z=_MBZid=_9__^5G?pbzxNe!?(U;n1fm(XeCGIe)!ZRiL_)91`=3E9klZ`tp}9>+sS ziXbnvaBN3kLw$KpHBA5G`jWWVH|3?v4;9;fw8jaZ-T0nu zW_@p`Y-pHTbdfYSzHy1~|31?FZPWF<$C`&JE_#Z)SN)Cn_dHbB3$M9kbX;`$9E9^#M>;xvKfUaJ$9)|= zHJ%^5sCQ$)9|p&?xWe-Ly{*1iwNvZQF{uU~42sdEx)saYPeC?7Y4krr#N%e~vyPya z-c$;E?sC?U(6KNg0WQS{hrCm6%fjqN=~ePcmKfw!0o(~~Fj`{0$&KPAzotbrfR=~T z3=_=1h-ySHxkATR^p)5viiUi=Huo3C8OMaVOUR%qXny~2N)w+W!s;Q6siwy_?gcNl zi_!CHhu7p(QR^PVbLt2MnEiSy1wNsi;j#C{s zH7nAkrKI@f+LVW`SXR~+d_>;Jmwi`Xk(rCn8kL^N#Z{I1CS&6=+^u=Jeodcptv1q1 zwb`yHp7Jd5FK^ylNZQ;ehQzzjNT}UT-W|Me-Ea1z^rL8>$Qxn#7-q)gQB(HJv|x&l zv55p!!5*h@ckJTdHfM;`@FS_36|BNuVFjNCoLMCq&}aH7A}TPHiC@s>;drW)o$ zV5cO-Q{`25a18=y!#ui#3E|tgU;Fwl#&H!wed~3OM&#cw2iXBbcz)`AyUmE^i@7=?4 z&sUuqEr%6}@O<&lQ*nA=juw`#=5@JE0;&`VWpxf5rm%ZJPqt;WO5>v?D7Yr69)9TPUSannhxdQfAL+Gu@5sHDGPcaq|Czj zhM|0^&7puRZFc|DMgVTR6_~cVq>-|$am!OTrOKS%(r^4cT(LTjy#SD*n}Cwx@wMx3 z-87h{mhC(kH)qIk?_|;wR`F;I%}7_%MMT8Yy*vx@9{F16&fkE+QOJB0b?ZqfTqor}3B_igpiU%Ky%ZcT%~VwG&(&UqfQT^jyCcV1t;JQDK@ zEKbi^xQu%=!gF!k0V9`bB)A&}FIWHXM z#rRWKQJ4BW>_%$v5Ilj`HX9yf@qWpW?0$-A4=}b@tix7>CD(B>f?7rTurIq0bRx)?B&8k$mqlBwouzYgBoTGLQzG|{fo zqwLFd$n#MAy!XmhqCGDX?Be!>O(NlZri##SMuAgO@SdWa)+T71Z{!{C?JZ^EX!A6w znwgxtsy`7?kCruEEbzcUCAzO~io{MhxH9lN(C+)74Xf%dJP zTpX&Qw1K+N-^JOS#Xe(b&dC@!g+{8)+AdG~N^$Ctv?KmR4|#9|G>#lnP1aCc2MRf; z4CwBH*5-)c1g+dG!EV~4=M!scOZx&Glm|M+w6mP5g8g1T+BKpvG{h9woc{efZZK}BXs=r1UtSj#_VJt{bL%6}vv)e!U+Lb`s*#Iw_Od?`)vY zQB&2~tF(QE&0!uH8gH-t0rLt7hDPUUCG?5vP=jW*ENe1knhMf;E*g=yQ*s`0xF0o4gI9OvoB7MykCgNcq8qBvv)3 z`1_~Wf#ma-pF=ZJF|-b727c)|ji*STlnFl=GPzKH{USs#L+U&WulIEV^lgNOBqo1B;3bFufSsw8oee&q>1B$wTB_QGd(8Y z)!;$+ur8SJ&N}3-JXtY;cT`5977m=hrbgd{^`Ga9uw5G(O8r@BdVb*S&UP_M10F{}Byx3#;hC%<=Ts;2Lh9>FLCtLqvXz;&P$kP99 z1E~M|AtiL4GK4FkWmPaDvc&`mBALfaJ$m5jbV2*hGU?bxR=I?BK z%TgVy+OK$OY498*$n=r<32SyH^v8etFaLAfVLaH!i}EphTg7Q1KAo2Mx%fw1Tv#L& zL@NDf;xA95%Jc{0N9+A*C0@mgH0;0M!v5nl=bt(M|5MuyX26qeG!f)|_5j7$3#aHm z7|rjmHveGAI$&?v+sf8HKr^2|^z?kCMVP;&U^_f~l-dq6jiZ-_xjDXbZ`Iu;G5}M; z;ZfxB-}20Gqne5#BX$7I#}J|fgJY=-ConuNWy?VuL*EuNIe|R5dTKg#MEa0zteYl){Wt&6=cM3Y}*bUO$`@L|J!GksXc0q=*it9H#EG zRm=z+tS;qgW_dg|P6^b9-lXT6852{cr;!C3CLcstb6LX+iB5;qn~DjYS><#jk(#gn zx}iTv$oqp)Ui~`-^zw;H1KR&QE%E$ynE35MX6!ckC=ZjSq%)3<8HQ=^{qU~7EO{LGP znlJ=qhzcJ0n1IG{Ma{FvAZi~w>(fTdxhtl5?Q&{_PSX2}<5U(inJNo@wVpLsZqd!( z-QsHV#OvQ8e*2ydP3|%<@H&m!eS@TJmP#_*{pEM%e(_i%dN+S#I-UrK1CV$P2j|7Y zed5muG#M+M09K<%;t7SGIE0lOWChAZ`2~h!&oWzG+#5+cRH|q@fF_5mhRF;WQ;Xq+ znrZ}8UectEuB~&7@e8i?nImnPnU~qUN213H{cn=*^iZi2&PoJVGBZSj`Iub`>b1el zmgfvYf9Z&;AARzN*p1HP^Y@pfH&{-FL{ZuWugtmZ`4oX@Bo532shGop=EvH$mNs6< zGUN|umpSu9{`pGgcXQZy5lZoz>zc&`U^0$1-%FijXiwjo_qKxK59!iYbBF-_@)BK2 z=Skvf>^inrmTJq*qs$et_X1Rx*v3&Ru1aB4s>7p40gnzCX`50h;*nU~3{A3KQzbXpgcTTMzQ2{yR1!(A6 zuGFm5jF3K?x9hbF{d}u+?gUkH#pV2y)jJ*7QH^) zjM%9@J-=0Xd|}+*P|A385X_alKVhg63W$2tdPwV_1oCtvWfqLoSe7m|<}5XSh#9)j zs_R@tIIT8>qwp(e+q_=@?=F~$@d26I>?D;I-XK@w8W=8PYz3NApekGk_Mnk*AJV3y zEqx>Uq=Ehxr*b_Lu=)q%Xj1xe7u}X5m}#q5gSPv0H9O%;w^^Is?hy2G;m3CGjtfd` zJJF?9(rwG@N5~;jvS~5tVtwj(X%=OUT=nKE*m&k2i~y+|r;ezNl4IvowWU@)$?)?y zmOeU@D6JH3P6t;kB?}p|1utyz>81lvqLPIPcKoS^f{z(}#Tn~c8dOd}VXXi^i?e|$ zZ3XtPjY}(oVMo9-86|HOip+i248cMbxDy*t2FmZr%y)lC%bn74>5R<6b#>a2&~}&U z^SqV1(B;(Ep$Nd{VcD#;>APzsP%-gNAF6F+Y+63l2g4n0mf`w8qX)%4{aN@QKIfm5 literal 0 HcmV?d00001 diff --git a/html/orchid-dreams.jpg b/html/orchid-dreams.jpg new file mode 100644 index 0000000000000000000000000000000000000000..8a62eeb0a564991859e095202093b0e238399333 GIT binary patch literal 161037 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 literal 0 HcmV?d00001 diff --git a/html/timeless-beige.jpg b/html/timeless-beige.jpg new file mode 100644 index 0000000000000000000000000000000000000000..e8c59137911b5fe110a6a7d78fa5dbef39b841e3 GIT binary patch literal 163654 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%0AcZ$Av$4AqTe?UtxkVO-aK@MHr3q^;Py27DXk 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
Pdy})!fnU=gcL{R4K?SX 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 Date: Thu, 21 Dec 2023 08:09:12 -0500 Subject: [PATCH 096/143] runtime eval pipeline type --- installer.py | 2 +- modules/processing_diffusers.py | 56 +++++++++++++++++++-------------- 2 files changed, 33 insertions(+), 25 deletions(-) diff --git a/installer.py b/installer.py index 5ba92cee5..6c2798c99 100644 --- a/installer.py +++ b/installer.py @@ -936,7 +936,7 @@ def add_args(parser): group.add_argument('--reset', default = os.environ.get("SD_RESET",False), action='store_true', help = "Reset main repository to latest version, default: %(default)s") group.add_argument('--upgrade', default = os.environ.get("SD_UPGRADE",False), action='store_true', help = "Upgrade main repository to latest version, default: %(default)s") group.add_argument('--requirements', default = os.environ.get("SD_REQUIREMENTS",False), action='store_true', help = "Force re-check of requirements, default: %(default)s") - group.add_argument('--quick', default = os.environ.get("SD_QUICK",False), action='store_true', help = "Run with startup sequence only, default: %(default)s") + group.add_argument('--quick', default = os.environ.get("SD_QUICK",False), action='store_true', help = "Bypass version checks, default: %(default)s") group.add_argument('--use-directml', default = os.environ.get("SD_USEDIRECTML",False), action='store_true', help = "Use DirectML if no compatible GPU is detected, default: %(default)s") group.add_argument("--use-openvino", default = os.environ.get("SD_USEOPENVINO",False), action='store_true', help="Use Intel OpenVINO backend, default: %(default)s") group.add_argument("--use-ipex", default = os.environ.get("SD_USEIPEX",False), action='store_true', help="Force use Intel OneAPI XPU backend, default: %(default)s") diff --git a/modules/processing_diffusers.py b/modules/processing_diffusers.py index 674401e16..9dfba3b71 100644 --- a/modules/processing_diffusers.py +++ b/modules/processing_diffusers.py @@ -19,9 +19,20 @@ from modules.processing_correction import correction_callback +debug = shared.log.trace if os.environ.get('SD_DIFFUSERS_DEBUG', None) is not None else lambda *args, **kwargs: None +debug('Trace: DIFFUSERS') +debug_steps = shared.log.trace if os.environ.get('SD_STEPS_DEBUG', None) is not None else lambda *args, **kwargs: None +debug_steps('Trace: STEPS') + + def process_diffusers(p: StableDiffusionProcessing, seeds, prompts, negative_prompts): results = [] - is_refiner_enabled = p.enable_hr and p.refiner_steps > 0 and p.refiner_start > 0 and p.refiner_start < 1 and shared.sd_refiner is not None + + def is_txt2img(): + return sd_models.get_diffusers_task(shared.sd_model) == sd_models.DiffusersTaskType.TEXT_2_IMAGE + + def is_refiner_enabled(): + return p.enable_hr and p.refiner_steps > 0 and p.refiner_start > 0 and p.refiner_start < 1 and shared.sd_refiner is not None if getattr(p, 'init_images', None) is not None and len(p.init_images) > 0: tgt_width, tgt_height = 8 * math.ceil(p.init_images[0].width / 8), 8 * math.ceil(p.init_images[0].height / 8) @@ -293,6 +304,7 @@ def task_specific_kwargs(model): 'width': p.width if hasattr(p, 'width') else None, 'height': p.height if hasattr(p, 'height') else None, } + debug(f'Diffusers task args: {task_args}') return task_args def set_pipeline_args(model, prompts: list, negative_prompts: list, prompts_2: typing.Optional[list]=None, negative_prompts_2: typing.Optional[list]=None, desc:str='', **kwargs): @@ -302,6 +314,7 @@ def set_pipeline_args(model, prompts: list, negative_prompts: list, prompts_2: t args = {} signature = inspect.signature(type(model).__call__) possible = signature.parameters.keys() + debug(f'Diffusers pipeline possible: {possible}') generator_device = devices.cpu if shared.opts.diffusers_generator_device == "cpu" else shared.device generator = [torch.Generator(generator_device).manual_seed(s) for s in seeds] prompts, negative_prompts, prompts_2, negative_prompts_2 = fix_prompts(prompts, negative_prompts, prompts_2, negative_prompts_2) @@ -407,6 +420,7 @@ def set_pipeline_args(model, prompts: list, negative_prompts: list, prompts_2: t if shared.cmd_opts.profile: t1 = time.time() shared.log.debug(f'Profile: pipeline args: {t1-t0:.2f}') + debug(f'Diffusers pipeline args: {args}') return args def recompile_model(hires=False): @@ -423,7 +437,7 @@ def recompile_model(hires=False): shared.log.info("OpenVINO: Recompiling base model") sd_models.unload_model_weights(op='model') sd_models.reload_model_weights(op='model') - if is_refiner_enabled: + if is_refiner_enabled(): shared.log.info("OpenVINO: Recompiling refiner") sd_models.unload_model_weights(op='refiner') sd_models.reload_model_weights(op='refiner') @@ -457,12 +471,19 @@ def update_sampler(sd_model, second_pass=False): if shared.opts.diffusers_move_base and not getattr(shared.sd_model, 'has_accelerate', False): shared.sd_model.to(devices.device) - is_img2img = bool(sd_models.get_diffusers_task(shared.sd_model) == sd_models.DiffusersTaskType.IMAGE_2_IMAGE or sd_models.get_diffusers_task(shared.sd_model) == sd_models.DiffusersTaskType.INPAINTING) - use_refiner_start = bool(is_refiner_enabled and not p.is_hr_pass and not is_img2img and p.refiner_start > 0 and p.refiner_start < 1) - use_denoise_start = bool(is_img2img and p.refiner_start > 0 and p.refiner_start < 1) + # pipeline type is set earlier in processing, but check for sanity + if sd_models.get_diffusers_task(shared.sd_model) != sd_models.DiffusersTaskType.TEXT_2_IMAGE and len(getattr(p, 'init_images' ,[])) == 0: + shared.sd_model = sd_models.set_diffuser_pipe(shared.sd_model, sd_models.DiffusersTaskType.TEXT_2_IMAGE) # reset pipeline + if hasattr(shared.sd_model, 'unet') and hasattr(shared.sd_model.unet, 'config') and hasattr(shared.sd_model.unet.config, 'in_channels') and shared.sd_model.unet.config.in_channels == 9: + shared.sd_model = sd_models.set_diffuser_pipe(shared.sd_model, sd_models.DiffusersTaskType.INPAINTING) # force pipeline + if len(getattr(p, 'init_images' ,[])) == 0: + p.init_images = [TF.to_pil_image(torch.rand((3, getattr(p, 'height', 512), getattr(p, 'width', 512))))] + + use_refiner_start = is_txt2img() and is_refiner_enabled() and not p.is_hr_pass and p.refiner_start > 0 and p.refiner_start < 1 + use_denoise_start = not is_txt2img() and p.refiner_start > 0 and p.refiner_start < 1 def calculate_base_steps(): - if is_img2img: + if not is_txt2img(): if use_denoise_start and shared.sd_model_type == 'sdxl': steps = p.steps // (1 - p.refiner_start) elif p.denoising_strength > 0: @@ -473,9 +494,7 @@ def calculate_base_steps(): steps = (p.steps // p.refiner_start) + 1 else: steps = p.steps - - if os.environ.get('SD_STEPS_DEBUG', None) is not None: - shared.log.debug(f'Steps: type=base input={p.steps} output={steps} refiner={use_refiner_start}') + debug_steps(f'Steps: type=base input={p.steps} output={steps} task={sd_models.get_diffusers_task(shared.sd_model)} refiner={use_refiner_start} denoise={p.denoising_strength} model={shared.sd_model_type}') return max(2, int(steps)) def calculate_hires_steps(): @@ -485,9 +504,7 @@ def calculate_hires_steps(): steps = (p.steps // p.denoising_strength) + 1 else: steps = 0 - - if os.environ.get('SD_STEPS_DEBUG', None) is not None: - shared.log.debug(f'Steps: type=hires input={p.hr_second_pass_steps} output={steps} denoise={p.denoising_strength}') + debug_steps(f'Steps: type=hires input={p.hr_second_pass_steps} output={steps} denoise={p.denoising_strength} model={shared.sd_model_type}') return max(2, int(steps)) def calculate_refiner_steps(): @@ -502,18 +519,9 @@ def calculate_refiner_steps(): else: #steps = p.refiner_steps # SD 1.5 with denoise strenght steps = (p.refiner_steps * 1.25) + 1 - - if os.environ.get('SD_STEPS_DEBUG', None) is not None: - shared.log.debug(f'Steps: type=refiner input={p.refiner_steps} output={steps} start={p.refiner_start} denoise={p.denoising_strength}') + debug_steps(f'Steps: type=refiner input={p.refiner_steps} output={steps} start={p.refiner_start} denoise={p.denoising_strength}') return max(2, int(steps)) - # pipeline type is set earlier in processing, but check for sanity - if sd_models.get_diffusers_task(shared.sd_model) != sd_models.DiffusersTaskType.TEXT_2_IMAGE and len(getattr(p, 'init_images' ,[])) == 0: - shared.sd_model = sd_models.set_diffuser_pipe(shared.sd_model, sd_models.DiffusersTaskType.TEXT_2_IMAGE) # reset pipeline - if hasattr(shared.sd_model, 'unet') and hasattr(shared.sd_model.unet, 'config') and hasattr(shared.sd_model.unet.config, 'in_channels') and shared.sd_model.unet.config.in_channels == 9: - shared.sd_model = sd_models.set_diffuser_pipe(shared.sd_model, sd_models.DiffusersTaskType.INPAINTING) # force pipeline - if len(getattr(p, 'init_images' ,[])) == 0: - p.init_images = [TF.to_pil_image(torch.rand((3, getattr(p, 'height', 512), getattr(p, 'width', 512))))] base_args = set_pipeline_args( model=shared.sd_model, prompts=prompts, @@ -610,7 +618,7 @@ def calculate_refiner_steps(): p.is_hr_pass = False # optional refiner pass or decode - if is_refiner_enabled: + if is_refiner_enabled(): prev_job = shared.state.job shared.state.job = 'refine' shared.state.job_count +=1 @@ -678,7 +686,7 @@ def calculate_refiner_steps(): p.is_refiner_pass = False # final decode since there is no refiner - if not is_refiner_enabled: + if not is_refiner_enabled(): if output is not None: if not hasattr(output, 'images') and hasattr(output, 'frames'): shared.log.debug(f'Generated: frames={len(output.frames[0])}') From aeccc28d6029156fb2d91c7916ee99c352646dd0 Mon Sep 17 00:00:00 2001 From: Vladimir Mandic Date: Thu, 21 Dec 2023 10:57:11 -0500 Subject: [PATCH 097/143] control set task args --- modules/control/adapters.py | 2 +- modules/control/reference.py | 2 +- modules/control/run.py | 39 ++++++++--------- modules/lora | 2 +- modules/processing_diffusers.py | 5 ++- t2i.py | 77 +++++++++++++++++++++++++++++++++ wiki | 2 +- 7 files changed, 104 insertions(+), 25 deletions(-) create mode 100644 t2i.py diff --git a/modules/control/adapters.py b/modules/control/adapters.py index 38269f779..263dcef5a 100644 --- a/modules/control/adapters.py +++ b/modules/control/adapters.py @@ -1,6 +1,6 @@ import os import time -from diffusers import StableDiffusionPipeline, StableDiffusionXLPipeline, T2IAdapter, MultiAdapter, StableDiffusionAdapterPipeline, StableDiffusionXLAdapterPipeline +from diffusers import StableDiffusionPipeline, StableDiffusionXLPipeline, T2IAdapter, MultiAdapter, StableDiffusionAdapterPipeline, StableDiffusionXLAdapterPipeline # pylint: disable=unused-import from modules.shared import log from modules import errors diff --git a/modules/control/reference.py b/modules/control/reference.py index 4d84aeca5..8116274a7 100644 --- a/modules/control/reference.py +++ b/modules/control/reference.py @@ -5,7 +5,7 @@ from modules.control.proc.reference_sdxl import StableDiffusionXLReferencePipeline -what = 'ControlNet-XS' +what = 'Reference' def list_models(): diff --git a/modules/control/run.py b/modules/control/run.py index 3dbe7114f..b28963d16 100644 --- a/modules/control/run.py +++ b/modules/control/run.py @@ -193,22 +193,20 @@ def control_run(units: List[unit.Unit], inputs, unit_type: str, is_generator: bo pass shared.sd_model = sd_models.set_diffuser_pipe(shared.sd_model, sd_models.DiffusersTaskType.TEXT_2_IMAGE) # reset current pipeline - if not has_models and (unit_type == 'reference' or unit_type == 'adapter' or unit_type == 'controlnet' or unit_type == 'xs'): # run in img2img mode - if len(active_strength) > 0: - p.strength = active_strength[0] - pipe = diffusers.AutoPipelineForImage2Image.from_pipe(shared.sd_model) # use set_diffuser_pipe - elif unit_type == 'adapter' and has_models: + debug(f'Control: run type={unit_type} models={has_models}') + if unit_type == 'adapter' and has_models: p.extra_generation_params["Control mode"] = 'Adapter' p.extra_generation_params["Control conditioning"] = use_conditioning - p.adapter_conditioning_scale = use_conditioning + p.task_args['adapter_conditioning_scale'] = use_conditioning instance = adapters.AdapterPipeline(selected_models, shared.sd_model) pipe = instance.pipeline elif unit_type == 'controlnet' and has_models: p.extra_generation_params["Control mode"] = 'ControlNet' p.extra_generation_params["Control conditioning"] = use_conditioning - p.controlnet_conditioning_scale = use_conditioning - p.control_guidance_start = active_start[0] if len(active_start) == 1 else list(active_start) - p.control_guidance_end = active_end[0] if len(active_end) == 1 else list(active_end) + p.task_args['controlnet_conditioning_scale'] = use_conditioning + p.task_args['control_guidance_start'] = active_start[0] if len(active_start) == 1 else list(active_start) + p.task_args['control_guidance_end'] = active_end[0] if len(active_end) == 1 else list(active_end) + p.task_args['guess_mode'] = p.guess_mode instance = controlnets.ControlNetPipeline(selected_models, shared.sd_model) pipe = instance.pipeline elif unit_type == 'xs' and has_models: @@ -222,16 +220,17 @@ def control_run(units: List[unit.Unit], inputs, unit_type: str, is_generator: bo elif unit_type == 'reference': p.extra_generation_params["Control mode"] = 'Reference' p.extra_generation_params["Control attention"] = p.attention - p.reference_attn = 'Attention' in p.attention - p.reference_adain = 'Adain' in p.attention - p.attention_auto_machine_weight = p.query_weight - p.gn_auto_machine_weight = p.adain_weight - p.style_fidelity = p.fidelity + p.task_args['reference_attn'] = 'Attention' in p.attention + p.task_args['reference_adain'] = 'Adain' in p.attention + p.task_args['attention_auto_machine_weight'] = p.query_weight + p.task_args['gn_auto_machine_weight'] = p.adain_weight + p.task_args['style_fidelity'] = p.fidelity instance = reference.ReferencePipeline(shared.sd_model) pipe = instance.pipeline - else: - shared.log.error(f'Control: unknown unit type: {unit_type}') - pipe = None + else: # run in img2img mode + if len(active_strength) > 0: + p.strength = active_strength[0] + pipe = diffusers.AutoPipelineForImage2Image.from_pipe(shared.sd_model) # use set_diffuser_pipe debug(f'Control pipeline: class={pipe.__class__} args={vars(p)}') t1, t2, t3 = time.time(), 0, 0 status = True @@ -353,20 +352,20 @@ def control_run(units: List[unit.Unit], inputs, unit_type: str, is_generator: bo # pipeline output = None if pipe is not None: # run new pipeline - if not has_models and (unit_type == 'reference' or unit_type == 'controlnet' or unit_type == 'adapter' or unit_type == 'xs'): # run in img2img mode + if not has_models and (unit_type == 'controlnet' or unit_type == 'adapter' or unit_type == 'xs'): # run in img2img mode if p.image is None: if hasattr(p, 'init_images'): del p.init_images shared.sd_model = sd_models.set_diffuser_pipe(shared.sd_model, sd_models.DiffusersTaskType.TEXT_2_IMAGE) # reset current pipeline else: p.init_images = [processed_image] # pylint: disable=attribute-defined-outside-init - processed_image.save('/tmp/test.png') shared.sd_model = sd_models.set_diffuser_pipe(shared.sd_model, sd_models.DiffusersTaskType.IMAGE_2_IMAGE) # reset current pipeline else: if hasattr(p, 'init_images'): del p.init_images shared.sd_model = sd_models.set_diffuser_pipe(shared.sd_model, sd_models.DiffusersTaskType.TEXT_2_IMAGE) # reset current pipeline - debug(f'Control exec pipeline: class={pipe.__class__} args={vars(p)}') + debug(f'Control exec pipeline: class={pipe.__class__} p={vars(p)}') + debug(f'Control exec pipeline: class={pipe.__class__} args={p.task_args}') processed: processing.Processed = processing.process_images(p) # run actual pipeline output = processed.images if processed is not None else None # output = pipe(**vars(p)).images # alternative direct pipe exec call diff --git a/modules/lora b/modules/lora index 0908c5414..0a52b83c6 160000 --- a/modules/lora +++ b/modules/lora @@ -1 +1 @@ -Subproject commit 0908c5414dadfe2185669e2d657d542161647b79 +Subproject commit 0a52b83c6a4c87d6375dc6d4d55ad78c39f9f666 diff --git a/modules/processing_diffusers.py b/modules/processing_diffusers.py index 9dfba3b71..a9796e1ea 100644 --- a/modules/processing_diffusers.py +++ b/modules/processing_diffusers.py @@ -304,7 +304,7 @@ def task_specific_kwargs(model): 'width': p.width if hasattr(p, 'width') else None, 'height': p.height if hasattr(p, 'height') else None, } - debug(f'Diffusers task args: {task_args}') + debug(f'Diffusers task specific args: {task_args}') return task_args def set_pipeline_args(model, prompts: list, negative_prompts: list, prompts_2: typing.Optional[list]=None, negative_prompts_2: typing.Optional[list]=None, desc:str='', **kwargs): @@ -374,9 +374,12 @@ def set_pipeline_args(model, prompts: list, negative_prompts: list, prompts_2: t if arg in possible: args[arg] = task_kwargs[arg] task_args = getattr(p, 'task_args', {}) + debug(f'Diffusers task args: {task_args}') for k, v in task_args.items(): if k in possible: args[k] = v + else: + debug(f'Diffusers unknown task args: {k}={v}') hypertile_set(p, hr=len(getattr(p, 'init_images', [])) > 0) clean = args.copy() diff --git a/t2i.py b/t2i.py new file mode 100644 index 000000000..0c596169c --- /dev/null +++ b/t2i.py @@ -0,0 +1,77 @@ +import torch +import diffusers +from PIL import Image +from rich import print + +model_id = "runwayml/stable-diffusion-v1-5" +print(f'torch=={torch.__version__} diffusers=={diffusers.__version__}') + +adapters = [ + 'TencentARC/t2iadapter_canny_sd15v2', + # 'TencentARC/t2iadapter_depth_sd15v2', + # 'TencentARC/t2iadapter_zoedepth_sd15v1', + # 'TencentARC/t2iadapter_openpose_sd14v1', + # 'TencentARC/t2iadapter_sketch_sd15v2', +] +seeds = [42] + +print(f'loading: {model_id}') +base = diffusers.StableDiffusionPipeline.from_pretrained(model_id, variant="fp16", cache_dir='/mnt/d/Models/Diffusers').to('cuda') +image = Image.new('RGB', (512,512), 0) # input is irrelevant, so just creating blank image +print('loaded') + +def callback(step: int, timestep: int, latents: torch.FloatTensor): + print(f'callback: step={step} timestep={timestep} latents={latents.shape}') + +for adapter_id in adapters: + print(f'loading: {adapter_id}') + adapter = diffusers.T2IAdapter.from_pretrained('TencentARC/t2iadapter_depth_sd15v2', cache_dir='/mnt/d/Models/Diffusers') + pipe = diffusers.StableDiffusionAdapterPipeline( + vae=base.vae, + text_encoder=base.text_encoder, + tokenizer=base.tokenizer, + unet=base.unet, + scheduler=base.scheduler, + requires_safety_checker=False, + safety_checker=None, + feature_extractor=None, + adapter=adapter, + ).to('cuda') + output = pipe(prompt=['test'], negative_prompt=['test'], num_inference_steps=20, image=image) # ok + print(f'adapter: {adapter_id} {output}') + pipe.scheduler = diffusers.EulerAncestralDiscreteScheduler.from_config(pipe.scheduler.config) + pipe.scheduler.config['num_train_timesteps'] = 1000 + pipe.scheduler.config['beta_start'] = 0.00085 + pipe.scheduler.config['beta_end'] = 0.012 + pipe.scheduler.config['beta_schedule'] = 'scaled_linear' + pipe.scheduler.config['prediction_type'] = 'epsilon' + pipe.scheduler.config['rescale_betas_zero_snr'] = False + output = pipe( + prompt=['test'], + negative_prompt=['test'], + num_inference_steps=20, + image=image, + callback=callback, + callback_steps=1, + output_type='latent', + eta=1.0, + clip_skip=1, + guidance_scale=6, + generator=[torch.Generator('cpu').manual_seed(seed) for seed in seeds], + ) + print(f'adapter: {adapter_id} {output}') + +""" +'callback_steps': 1, +'callback': .diffusers_callback_legacy at 0x7f4569259a80>, + +'guidance_scale': 6, +'generator': [], +'num_inference_steps': 20, + +'eta': 1.0, +'clip_skip': 1, +'image': } + +Given groups=1, weight of size [320, 64, 3, 3], expected input[1, 192, 64, 64] to have 64 channels, but got 192 channels instead +""" diff --git a/wiki b/wiki index 5ce9b8d0b..840269c12 160000 --- a/wiki +++ b/wiki @@ -1 +1 @@ -Subproject commit 5ce9b8d0b798194e108094701bc55589fd01cbd1 +Subproject commit 840269c12e0faae36d8b013070360fcd37ee24fc From 1b0e9b0f149fe080ab0ccf9f33630b5261f8d644 Mon Sep 17 00:00:00 2001 From: Vladimir Mandic Date: Thu, 21 Dec 2023 11:56:29 -0500 Subject: [PATCH 098/143] update control adapters --- modules/control/adapters.py | 25 +++++++----- modules/control/run.py | 9 +---- t2i.py | 77 ------------------------------------- 3 files changed, 17 insertions(+), 94 deletions(-) delete mode 100644 t2i.py diff --git a/modules/control/adapters.py b/modules/control/adapters.py index 263dcef5a..9e8360567 100644 --- a/modules/control/adapters.py +++ b/modules/control/adapters.py @@ -9,19 +9,25 @@ debug = log.trace if os.environ.get('SD_CONTROL_DEBUG', None) is not None else lambda *args, **kwargs: None debug('Trace: CONTROL') predefined_sd15 = { - 'Canny': 'TencentARC/t2iadapter_canny_sd15v2', - 'Depth': 'TencentARC/t2iadapter_depth_sd15v2', - 'Depth Zoe': 'TencentARC/t2iadapter_zoedepth_sd15v1', + 'Segment': 'TencentARC/t2iadapter_seg_sd14v1', + 'Zoe Depth': 'TencentARC/t2iadapter_zoedepth_sd15v1', 'OpenPose': 'TencentARC/t2iadapter_openpose_sd14v1', - 'Sketch': 'TencentARC/t2iadapter_sketch_sd15v2', + 'KeyPose': 'TencentARC/t2iadapter_keypose_sd14v1', + 'Color': 'TencentARC/t2iadapter_color_sd14v1', + 'Depth v1': 'TencentARC/t2iadapter_depth_sd14v1', + 'Depth v2': 'TencentARC/t2iadapter_depth_sd15v2', + 'Canny v1': 'TencentARC/t2iadapter_canny_sd14v1', + 'Canny v2': 'TencentARC/t2iadapter_canny_sd15v2', + 'Sketch v1': 'TencentARC/t2iadapter_sketch_sd14v1', + 'Sketch v2': 'TencentARC/t2iadapter_sketch_sd15v2', } predefined_sdxl = { 'Canny XL': 'TencentARC/t2i-adapter-canny-sdxl-1.0', - 'Depth Zoe XL': 'TencentARC/t2i-adapter-depth-zoe-sdxl-1.0', - 'Depth Midas XL': 'TencentARC/t2i-adapter-depth-midas-sdxl-1.0', 'LineArt XL': 'TencentARC/t2i-adapter-lineart-sdxl-1.0', - 'OpenPose XL': 'TencentARC/t2i-adapter-openpose-sdxl-1.0', 'Sketch XL': 'TencentARC/t2i-adapter-sketch-sdxl-1.0', + 'Zoe Depth XL': 'TencentARC/t2i-adapter-depth-zoe-sdxl-1.0', + 'OpenPose XL': 'TencentARC/t2i-adapter-openpose-sdxl-1.0', + 'Midas Depth XL': 'TencentARC/t2i-adapter-depth-midas-sdxl-1.0', } models = {} all_models = {} @@ -104,8 +110,9 @@ def __init__(self, adapter: T2IAdapter | list[T2IAdapter], pipeline: StableDiffu if pipeline is None: log.error(f'Control {what} pipeline: model not loaded') return - # if isinstance(adapter, list) and len(adapter) > 1: # TODO use MultiAdapter - # adapter = MultiAdapter(adapter) + if isinstance(adapter, list) and len(adapter) > 1: # TODO use MultiAdapter + adapter = MultiAdapter(adapter) + adapter.to(device=pipeline.device, dtype=pipeline.dtype) if isinstance(pipeline, StableDiffusionXLPipeline): self.pipeline = StableDiffusionXLAdapterPipeline( vae=pipeline.vae, diff --git a/modules/control/run.py b/modules/control/run.py index b28963d16..bf3e9fd7f 100644 --- a/modules/control/run.py +++ b/modules/control/run.py @@ -166,13 +166,6 @@ def control_run(units: List[unit.Unit], inputs, unit_type: str, is_generator: bo active_process.append(u.process) # active_model.append(model) active_strength.append(u.strength) - """ - if (len(active_process) == 0) and (unit_type != 'reference'): - msg = 'Control: no active units' - shared.log.warning(msg) - restore_pipeline() - return msg - """ p.ops.append('control') has_models = False @@ -426,8 +419,8 @@ def control_run(units: List[unit.Unit], inputs, unit_type: str, is_generator: bo image_txt = f'| Frames {len(output_images)} | Size {output_images[0].width}x{output_images[0].height}' image_txt += f' | {util.dict2str(p.extra_generation_params)}' - debug(f'Control ready: {image_txt}') restore_pipeline() + debug(f'Control ready: {image_txt}') if is_generator: yield (output_images, processed_image, f'Control ready {image_txt}', output_filename) else: diff --git a/t2i.py b/t2i.py deleted file mode 100644 index 0c596169c..000000000 --- a/t2i.py +++ /dev/null @@ -1,77 +0,0 @@ -import torch -import diffusers -from PIL import Image -from rich import print - -model_id = "runwayml/stable-diffusion-v1-5" -print(f'torch=={torch.__version__} diffusers=={diffusers.__version__}') - -adapters = [ - 'TencentARC/t2iadapter_canny_sd15v2', - # 'TencentARC/t2iadapter_depth_sd15v2', - # 'TencentARC/t2iadapter_zoedepth_sd15v1', - # 'TencentARC/t2iadapter_openpose_sd14v1', - # 'TencentARC/t2iadapter_sketch_sd15v2', -] -seeds = [42] - -print(f'loading: {model_id}') -base = diffusers.StableDiffusionPipeline.from_pretrained(model_id, variant="fp16", cache_dir='/mnt/d/Models/Diffusers').to('cuda') -image = Image.new('RGB', (512,512), 0) # input is irrelevant, so just creating blank image -print('loaded') - -def callback(step: int, timestep: int, latents: torch.FloatTensor): - print(f'callback: step={step} timestep={timestep} latents={latents.shape}') - -for adapter_id in adapters: - print(f'loading: {adapter_id}') - adapter = diffusers.T2IAdapter.from_pretrained('TencentARC/t2iadapter_depth_sd15v2', cache_dir='/mnt/d/Models/Diffusers') - pipe = diffusers.StableDiffusionAdapterPipeline( - vae=base.vae, - text_encoder=base.text_encoder, - tokenizer=base.tokenizer, - unet=base.unet, - scheduler=base.scheduler, - requires_safety_checker=False, - safety_checker=None, - feature_extractor=None, - adapter=adapter, - ).to('cuda') - output = pipe(prompt=['test'], negative_prompt=['test'], num_inference_steps=20, image=image) # ok - print(f'adapter: {adapter_id} {output}') - pipe.scheduler = diffusers.EulerAncestralDiscreteScheduler.from_config(pipe.scheduler.config) - pipe.scheduler.config['num_train_timesteps'] = 1000 - pipe.scheduler.config['beta_start'] = 0.00085 - pipe.scheduler.config['beta_end'] = 0.012 - pipe.scheduler.config['beta_schedule'] = 'scaled_linear' - pipe.scheduler.config['prediction_type'] = 'epsilon' - pipe.scheduler.config['rescale_betas_zero_snr'] = False - output = pipe( - prompt=['test'], - negative_prompt=['test'], - num_inference_steps=20, - image=image, - callback=callback, - callback_steps=1, - output_type='latent', - eta=1.0, - clip_skip=1, - guidance_scale=6, - generator=[torch.Generator('cpu').manual_seed(seed) for seed in seeds], - ) - print(f'adapter: {adapter_id} {output}') - -""" -'callback_steps': 1, -'callback': .diffusers_callback_legacy at 0x7f4569259a80>, - -'guidance_scale': 6, -'generator': [], -'num_inference_steps': 20, - -'eta': 1.0, -'clip_skip': 1, -'image': } - -Given groups=1, weight of size [320, 64, 3, 3], expected input[1, 192, 64, 64] to have 64 channels, but got 192 channels instead -""" From 01fb0faa22b93e37c38acf46fbea8c5c08c6dc48 Mon Sep 17 00:00:00 2001 From: Vladimir Mandic Date: Thu, 21 Dec 2023 12:10:42 -0500 Subject: [PATCH 099/143] update requirements --- modules/loader.py | 1 + requirements.txt | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/modules/loader.py b/modules/loader.py index bb6b120a1..31cadcd56 100644 --- a/modules/loader.py +++ b/modules/loader.py @@ -7,6 +7,7 @@ from modules import timer, errors initialized = False +errors.install() logging.getLogger("DeepSpeed").disabled = True # os.environ.setdefault('OMP_NUM_THREADS', 1) # os.environ.setdefault('MKL_NUM_THREADS', 1) diff --git a/requirements.txt b/requirements.txt index 1aee9cccd..be2442bb9 100644 --- a/requirements.txt +++ b/requirements.txt @@ -63,7 +63,7 @@ pandas==1.5.3 protobuf==3.20.3 pytorch_lightning==1.9.4 tokenizers==0.15.0 -transformers==4.36.0 +transformers==4.36.2 tomesd==0.1.3 urllib3==1.26.15 Pillow==10.1.0 From 9b40bdcd9192b12c934ca0f428330fdc2b433aaa Mon Sep 17 00:00:00 2001 From: Vladimir Mandic Date: Thu, 21 Dec 2023 12:21:44 -0500 Subject: [PATCH 100/143] update changelog --- CHANGELOG.md | 4 ++-- wiki | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index efaaa1de4..f0b4a6158 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,6 @@ # Change Log for SD.Next -## Update for 2023-12-20 +## Update for 2023-12-21 *Note*: based on `diffusers==0.25.0.dev0` @@ -9,7 +9,7 @@ - top-level **Control** next to **Text** and **Image** generate - supports all variations of **SD15** and **SD-XL** models - supports *Text*, *Image*, *Batch* and *Video* processing - - for details see Wiki documentation: + - for details and list of supported models and workflows, see Wiki documentation: - **Diffusers** - **AnimateDiff** diff --git a/wiki b/wiki index 840269c12..fded47794 160000 --- a/wiki +++ b/wiki @@ -1 +1 @@ -Subproject commit 840269c12e0faae36d8b013070360fcd37ee24fc +Subproject commit fded477941498c1a1fd40f466181235502a6870f From d7b0d5914342866a87019e0ff313663b67237dc3 Mon Sep 17 00:00:00 2001 From: Vladimir Mandic Date: Thu, 21 Dec 2023 13:36:42 -0500 Subject: [PATCH 101/143] add control tests --- modules/control/test.py | 121 ++++++++++++++++++++++++++++++++++++++-- modules/ui_control.py | 8 ++- wiki | 2 +- 3 files changed, 122 insertions(+), 9 deletions(-) diff --git a/modules/control/test.py b/modules/control/test.py index 1f233ce74..29296f669 100644 --- a/modules/control/test.py +++ b/modules/control/test.py @@ -1,10 +1,10 @@ import math -from PIL import Image -from modules.control import processors # patrickvonplaten controlnet_aux -from modules import shared +from PIL import Image, ImageChops +from modules import shared, errors def test_processors(image): + from modules.control import processors if image is None: shared.log.error('Image not loaded') return None, None, None @@ -15,11 +15,16 @@ def test_processors(image): continue shared.log.info(f'Testing processor: {processor_id}') processor = processors.Processor(processor_id) + output = image if processor is None: shared.log.error(f'Processor load failed: id="{processor_id}"') - continue - output = processor(image) - processor.reset() + processor_id = f'{processor_id} error' + else: + output = processor(image) + processor.reset() + diff = ImageChops.difference(image, output) + if not diff.getbbox(): + processor_id = f'{processor_id} null' draw = ImageDraw.Draw(output) font = ImageFont.truetype('DejaVuSansMono', 48) draw.text((10, 10), processor_id, (0,0,0), font=font) @@ -40,3 +45,107 @@ def test_processors(image): grid.paste(thumb, box=(x, y)) yield None, grid, None, images return None, grid, None, images # preview_process, output_image, output_video, output_gallery + + +def test_controlnets(prompt, negative, image): + from modules import devices, sd_models + from modules.control import controlnets + if image is None: + shared.log.error('Image not loaded') + return None, None, None + from PIL import ImageDraw, ImageFont + images = [] + for model_id in controlnets.list_models(): + if model_id is None: + model_id = 'None' + if shared.state.interrupted: + continue + output = image + if model_id != 'None': + controlnet = controlnets.ControlNet(model_id=model_id, device=devices.device, dtype=devices.dtype) + if controlnet is None: + shared.log.error(f'ControlNet load failed: id="{model_id}"') + continue + shared.log.info(f'Testing ControlNet: {model_id}') + pipe = controlnets.ControlNetPipeline(controlnet=controlnet.model, pipeline=shared.sd_model) + pipe.pipeline.to(device=devices.device, dtype=devices.dtype) + sd_models.set_diffuser_options(pipe) + try: + res = pipe.pipeline(prompt=prompt, negative_prompt=negative, image=image, num_inference_steps=10, output_type='pil') + output = res.images[0] + except Exception as e: + errors.display(e, f'ControlNet {model_id} inference') + model_id = f'{model_id} error' + pipe.restore() + draw = ImageDraw.Draw(output) + font = ImageFont.truetype('DejaVuSansMono', 48) + draw.text((10, 10), model_id, (0,0,0), font=font) + draw.text((8, 8), model_id, (255,255,255), font=font) + images.append(output) + yield output, None, None, images + rows = round(math.sqrt(len(images))) + cols = math.ceil(len(images) / rows) + w, h = 256, 256 + size = (cols * w + cols, rows * h + rows) + grid = Image.new('RGB', size=size, color='black') + shared.log.info(f'Test ControlNets: images={len(images)} grid={grid}') + for i, image in enumerate(images): + x = (i % cols * w) + (i % cols) + y = (i // cols * h) + (i // cols) + thumb = image.copy().convert('RGB') + thumb.thumbnail((w, h), Image.Resampling.HAMMING) + grid.paste(thumb, box=(x, y)) + yield None, grid, None, images + return None, grid, None, images # preview_process, output_image, output_video, output_gallery + + +def test_adapters(prompt, negative, image): + from modules import devices, sd_models + from modules.control import adapters + if image is None: + shared.log.error('Image not loaded') + return None, None, None + from PIL import ImageDraw, ImageFont + images = [] + for model_id in adapters.list_models(): + if model_id is None: + model_id = 'None' + if shared.state.interrupted: + continue + output = image + if model_id != 'None': + adapter = adapters.Adapter(model_id=model_id, device=devices.device, dtype=devices.dtype) + if adapter is None: + shared.log.error(f'Adapter load failed: id="{model_id}"') + continue + shared.log.info(f'Testing Adapter: {model_id}') + pipe = adapters.AdapterPipeline(adapter=adapter.model, pipeline=shared.sd_model) + pipe.pipeline.to(device=devices.device, dtype=devices.dtype) + sd_models.set_diffuser_options(pipe) + try: + res = pipe.pipeline(prompt=prompt, negative_prompt=negative, image=image, num_inference_steps=10, output_type='pil') + output = res.images[0] + except Exception as e: + errors.display(e, f'Adapter {model_id} inference') + model_id = f'{model_id} error' + pipe.restore() + draw = ImageDraw.Draw(output) + font = ImageFont.truetype('DejaVuSansMono', 48) + draw.text((10, 10), model_id, (0,0,0), font=font) + draw.text((8, 8), model_id, (255,255,255), font=font) + images.append(output) + yield output, None, None, images + rows = round(math.sqrt(len(images))) + cols = math.ceil(len(images) / rows) + w, h = 256, 256 + size = (cols * w + cols, rows * h + rows) + grid = Image.new('RGB', size=size, color='black') + shared.log.info(f'Test Adapters: images={len(images)} grid={grid}') + for i, image in enumerate(images): + x = (i % cols * w) + (i % cols) + y = (i // cols * h) + (i // cols) + thumb = image.copy().convert('RGB') + thumb.thumbnail((w, h), Image.Resampling.HAMMING) + grid.paste(thumb, box=(x, y)) + yield None, grid, None, images + return None, grid, None, images # preview_process, output_image, output_video, output_gallery diff --git a/modules/ui_control.py b/modules/ui_control.py index c176836d3..5559db6ae 100644 --- a/modules/ui_control.py +++ b/modules/ui_control.py @@ -477,10 +477,14 @@ def create_ui(_blocks: gr.Blocks=None): generation_parameters_copypaste.register_paste_params_button(bindings) if debug: - from modules.control.test import test_processors + from modules.control.test import test_processors, test_controlnets, test_adapters gr.HTML('

Debug


') with gr.Row(): - run_test_processors_btn = gr.Button(value="Test all processors", variant='primary', elem_classes=['control-button']) + run_test_processors_btn = gr.Button(value="Test:Processors", variant='primary', elem_classes=['control-button']) + run_test_controlnets_btn = gr.Button(value="Test:ControlNets", variant='primary', elem_classes=['control-button']) + run_test_adapters_btn = gr.Button(value="Test:Adapters", variant='primary', elem_classes=['control-button']) run_test_processors_btn.click(fn=test_processors, inputs=[input_image], outputs=[preview_process, output_image, output_video, output_gallery]) + run_test_controlnets_btn.click(fn=test_controlnets, inputs=[prompt, negative, input_image], outputs=[preview_process, output_image, output_video, output_gallery]) + run_test_adapters_btn.click(fn=test_adapters, inputs=[prompt, negative, input_image], outputs=[preview_process, output_image, output_video, output_gallery]) return [(control_ui, 'Control', 'control')] diff --git a/wiki b/wiki index fded47794..f75201d5b 160000 --- a/wiki +++ b/wiki @@ -1 +1 @@ -Subproject commit fded477941498c1a1fd40f466181235502a6870f +Subproject commit f75201d5bc7a12c176e7243845c8d96835ef726a From 0471f549f081d0b71e50e5b177ef1029cfaacd39 Mon Sep 17 00:00:00 2001 From: Vladimir Mandic Date: Thu, 21 Dec 2023 14:34:38 -0500 Subject: [PATCH 102/143] add more control tests --- CHANGELOG.md | 3 +- modules/control/controlnetsxs.py | 2 +- modules/control/run.py | 2 +- modules/control/test.py | 52 ++++++++++++++++++++++++++++++++ modules/ui_control.py | 4 ++- 5 files changed, 59 insertions(+), 4 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index f0b4a6158..71af9e0c3 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -103,7 +103,8 @@ - updated `cli/simple-txt2img.py` and `cli/simple-img2img.py` scripts - save `params.txt` regardless of image save status - update built-in log monitor in ui, thanks @midcoastal - - major CHANGELOG cleanup, thanks @JetVarimax + - major CHANGELOG doc cleanup, thanks @JetVarimax + - major INSTALL doc cleanup, thanks JetVarimax ## Update for 2023-12-04 diff --git a/modules/control/controlnetsxs.py b/modules/control/controlnetsxs.py index 1cd54cbe5..78890c840 100644 --- a/modules/control/controlnetsxs.py +++ b/modules/control/controlnetsxs.py @@ -112,7 +112,7 @@ def load(self, model_id: str = None, time_embedding_mix: float = 0.0): errors.display(e, f'Control {what} load') -class ControlNetPipeline(): +class ControlNetXSPipeline(): def __init__(self, controlnet: ControlNetXSModel | list[ControlNetXSModel], pipeline: StableDiffusionXLPipeline | StableDiffusionPipeline, dtype = None): t0 = time.time() self.orig_pipeline = pipeline diff --git a/modules/control/run.py b/modules/control/run.py index bf3e9fd7f..94a5b788f 100644 --- a/modules/control/run.py +++ b/modules/control/run.py @@ -208,7 +208,7 @@ def control_run(units: List[unit.Unit], inputs, unit_type: str, is_generator: bo p.controlnet_conditioning_scale = use_conditioning p.control_guidance_start = active_start[0] if len(active_start) == 1 else list(active_start) p.control_guidance_end = active_end[0] if len(active_end) == 1 else list(active_end) - instance = controlnetsxs.ControlNetPipeline(selected_models, shared.sd_model) + instance = controlnetsxs.ControlNetXSPipeline(selected_models, shared.sd_model) pipe = instance.pipeline elif unit_type == 'reference': p.extra_generation_params["Control mode"] = 'Reference' diff --git a/modules/control/test.py b/modules/control/test.py index 29296f669..42c76fcd7 100644 --- a/modules/control/test.py +++ b/modules/control/test.py @@ -149,3 +149,55 @@ def test_adapters(prompt, negative, image): grid.paste(thumb, box=(x, y)) yield None, grid, None, images return None, grid, None, images # preview_process, output_image, output_video, output_gallery + + +def test_xs(prompt, negative, image): + from modules import devices, sd_models + from modules.control import controlnetsxs + if image is None: + shared.log.error('Image not loaded') + return None, None, None + from PIL import ImageDraw, ImageFont + images = [] + for model_id in controlnetsxs.list_models(): + if model_id is None: + model_id = 'None' + if shared.state.interrupted: + continue + output = image + if model_id != 'None': + xs = controlnetsxs.ControlNetXS(model_id=model_id, device=devices.device, dtype=devices.dtype) + if xs is None: + shared.log.error(f'ControlNet-XS load failed: id="{model_id}"') + continue + shared.log.info(f'Testing ControlNet-XS: {model_id}') + pipe = controlnetsxs.ControlNetXSPipeline(controlnet=xs.model, pipeline=shared.sd_model) + pipe.pipeline.to(device=devices.device, dtype=devices.dtype) + sd_models.set_diffuser_options(pipe) + try: + res = pipe.pipeline(prompt=prompt, negative_prompt=negative, image=image, num_inference_steps=10, output_type='pil') + output = res.images[0] + except Exception as e: + errors.display(e, f'ControlNet-XS {model_id} inference') + model_id = f'{model_id} error' + pipe.restore() + draw = ImageDraw.Draw(output) + font = ImageFont.truetype('DejaVuSansMono', 48) + draw.text((10, 10), model_id, (0,0,0), font=font) + draw.text((8, 8), model_id, (255,255,255), font=font) + images.append(output) + yield output, None, None, images + rows = round(math.sqrt(len(images))) + cols = math.ceil(len(images) / rows) + w, h = 256, 256 + size = (cols * w + cols, rows * h + rows) + grid = Image.new('RGB', size=size, color='black') + shared.log.info(f'Test ControlNet-XS: images={len(images)} grid={grid}') + for i, image in enumerate(images): + x = (i % cols * w) + (i % cols) + y = (i // cols * h) + (i // cols) + thumb = image.copy().convert('RGB') + thumb.thumbnail((w, h), Image.Resampling.HAMMING) + grid.paste(thumb, box=(x, y)) + yield None, grid, None, images + return None, grid, None, images # preview_process, output_image, output_video, output_gallery diff --git a/modules/ui_control.py b/modules/ui_control.py index 5559db6ae..1fc1deea6 100644 --- a/modules/ui_control.py +++ b/modules/ui_control.py @@ -477,14 +477,16 @@ def create_ui(_blocks: gr.Blocks=None): generation_parameters_copypaste.register_paste_params_button(bindings) if debug: - from modules.control.test import test_processors, test_controlnets, test_adapters + from modules.control.test import test_processors, test_controlnets, test_adapters, test_xs gr.HTML('

Debug


') with gr.Row(): run_test_processors_btn = gr.Button(value="Test:Processors", variant='primary', elem_classes=['control-button']) run_test_controlnets_btn = gr.Button(value="Test:ControlNets", variant='primary', elem_classes=['control-button']) + run_test_xs_btn = gr.Button(value="Test:ControlNets-XS", variant='primary', elem_classes=['control-button']) run_test_adapters_btn = gr.Button(value="Test:Adapters", variant='primary', elem_classes=['control-button']) run_test_processors_btn.click(fn=test_processors, inputs=[input_image], outputs=[preview_process, output_image, output_video, output_gallery]) run_test_controlnets_btn.click(fn=test_controlnets, inputs=[prompt, negative, input_image], outputs=[preview_process, output_image, output_video, output_gallery]) + run_test_xs_btn.click(fn=test_xs, inputs=[prompt, negative, input_image], outputs=[preview_process, output_image, output_video, output_gallery]) run_test_adapters_btn.click(fn=test_adapters, inputs=[prompt, negative, input_image], outputs=[preview_process, output_image, output_video, output_gallery]) return [(control_ui, 'Control', 'control')] From ec0a08c4db3791ca909eeb16fcb1447a411259f2 Mon Sep 17 00:00:00 2001 From: Vladimir Mandic Date: Fri, 22 Dec 2023 07:34:14 -0500 Subject: [PATCH 103/143] fix inpaint overlay --- modules/img2img.py | 2 +- modules/processing.py | 42 ++++++++++++++++++++++++++++++------------ modules/shared.py | 2 +- 3 files changed, 32 insertions(+), 14 deletions(-) diff --git a/modules/img2img.py b/modules/img2img.py index 6c828309c..4c0ce910b 100644 --- a/modules/img2img.py +++ b/modules/img2img.py @@ -235,7 +235,7 @@ def img2img(id_task: str, mode: int, diffusers_guidance_rescale=diffusers_guidance_rescale, refiner_steps=refiner_steps, refiner_start=refiner_start, - inpaint_full_res=inpaint_full_res, + inpaint_full_res=inpaint_full_res != 0, inpaint_full_res_padding=inpaint_full_res_padding, inpainting_mask_invert=inpainting_mask_invert, hdr_clamp=hdr_clamp, hdr_boundary=hdr_boundary, hdr_threshold=hdr_threshold, diff --git a/modules/processing.py b/modules/processing.py index a2f680c3b..41857ea3c 100644 --- a/modules/processing.py +++ b/modules/processing.py @@ -62,8 +62,6 @@ def apply_color_correction(correction, original_image): def apply_overlay(image: Image, paste_loc, index, overlays): - if not shared.opts.img2img_apply_overlay: - return image if overlays is None or index >= len(overlays): return image overlay = overlays[index] @@ -1222,6 +1220,8 @@ def __init__(self, init_images: list = None, resize_mode: int = 0, resize_name: self.latent_mask = None self.mask_for_overlay = None self.mask_blur = mask_blur + self.mask_blur_x: int = 4 + self.mask_blur_y: int = 4 self.inpainting_fill = inpainting_fill self.inpaint_full_res = inpaint_full_res self.inpaint_full_res_padding = inpaint_full_res_padding @@ -1241,6 +1241,18 @@ def __init__(self, init_images: list = None, resize_mode: int = 0, resize_name: self.scripts = None self.script_args = [] + @property + def mask_blur(self): + if self.mask_blur_x == self.mask_blur_y: + return self.mask_blur_x + return None + + @mask_blur.setter + def mask_blur(self, value): + if isinstance(value, int): + self.mask_blur_x = value + self.mask_blur_y = value + def init(self, all_prompts, all_seeds, all_subseeds): if shared.backend == shared.Backend.DIFFUSERS and self.image_mask is not None: shared.sd_model = modules.sd_models.set_diffuser_pipe(self.sd_model, modules.sd_models.DiffusersTaskType.INPAINTING) @@ -1263,28 +1275,36 @@ def init(self, all_prompts, all_seeds, all_subseeds): if image_mask is not None: if type(image_mask) == list: image_mask = image_mask[0] - image_mask = image_mask.convert('L') + image_mask = create_binary_mask(image_mask) if self.inpainting_mask_invert: image_mask = ImageOps.invert(image_mask) - if self.mask_blur > 0: - image_mask = image_mask.filter(ImageFilter.GaussianBlur(self.mask_blur)) + if self.mask_blur_x > 0: + np_mask = np.array(image_mask) + kernel_size = 2 * int(2.5 * self.mask_blur_x + 0.5) + 1 + np_mask = cv2.GaussianBlur(np_mask, (kernel_size, 1), self.mask_blur_x) + image_mask = Image.fromarray(np_mask) + if self.mask_blur_y > 0: + np_mask = np.array(image_mask) + kernel_size = 2 * int(2.5 * self.mask_blur_y + 0.5) + 1 + np_mask = cv2.GaussianBlur(np_mask, (1, kernel_size), self.mask_blur_y) + image_mask = Image.fromarray(np_mask) if self.inpaint_full_res: self.mask_for_overlay = image_mask mask = image_mask.convert('L') crop_region = modules.masking.get_crop_region(np.array(mask), self.inpaint_full_res_padding) crop_region = modules.masking.expand_crop_region(crop_region, self.width, self.height, mask.width, mask.height) x1, y1, x2, y2 = crop_region + mask = mask.crop(crop_region) - if mask.width != self.width or mask.height != self.height: - image_mask = images.resize_image(3, mask, self.width, self.height, self.resize_name) + image_mask = images.resize_image(2, mask, self.width, self.height) self.paste_to = (x1, y1, x2-x1, y2-y1) else: - if image_mask.width != self.width or image_mask.height != self.height: - image_mask = images.resize_image(self.resize_mode, image_mask, self.width, self.height, self.resize_name) + image_mask = images.resize_image(self.resize_mode, image_mask, self.width, self.height) np_mask = np.array(image_mask) np_mask = np.clip((np_mask.astype(np.float32)) * 2, 0, 255).astype(np.uint8) self.mask_for_overlay = Image.fromarray(np_mask) self.overlay_images = [] + latent_mask = self.latent_mask if self.latent_mask is not None else image_mask add_color_corrections = shared.opts.img2img_color_correction and self.color_corrections is None @@ -1321,9 +1341,7 @@ def init(self, all_prompts, all_seeds, all_subseeds): self.overlay_images.append(image_masked.convert('RGBA')) except Exception as e: shared.log.error(f"Failed to apply mask to image: {e}") - self.mask = image_mask # assign early for diffusers - # crop_region is not None if we are doing inpaint full res - if crop_region is not None: + if crop_region is not None: # crop_region is not None if we are doing inpaint full res image = image.crop(crop_region) if image.width != self.width or image.height != self.height: image = images.resize_image(3, image, self.width, self.height, self.resize_name) diff --git a/modules/shared.py b/modules/shared.py index 8d887e650..b8068cbff 100644 --- a/modules/shared.py +++ b/modules/shared.py @@ -554,7 +554,7 @@ def default(obj): "postprocessing_sep_img2img": OptionInfo("

Img2Img & Inpainting

", "", gr.HTML), "img2img_color_correction": OptionInfo(False, "Apply color correction"), - "img2img_apply_overlay": OptionInfo(False, "Apply result as overlay"), + # "img2img_apply_overlay": OptionInfo(False, "Apply result as overlay"), "img2img_fix_steps": OptionInfo(False, "For image processing do exact number of steps as specified", gr.Checkbox, { "visible": False }), "img2img_background_color": OptionInfo("#ffffff", "Image transparent color fill", ui_components.FormColorPicker, {}), "inpainting_mask_weight": OptionInfo(1.0, "Inpainting conditioning mask strength", gr.Slider, {"minimum": 0.0, "maximum": 1.0, "step": 0.01}), From 4b74d3ebf9fb6505ae4ba3cc8eaa284c8f48201b Mon Sep 17 00:00:00 2001 From: Vladimir Mandic Date: Fri, 22 Dec 2023 08:07:34 -0500 Subject: [PATCH 104/143] modularize ui imports --- launch.py | 10 ++ modules/hypernetworks/ui.py | 4 +- modules/processing.py | 2 +- modules/ui.py | 226 ++++++++++++++++++------------------ 4 files changed, 127 insertions(+), 115 deletions(-) diff --git a/launch.py b/launch.py index a8c156b8c..00adf4475 100755 --- a/launch.py +++ b/launch.py @@ -47,6 +47,16 @@ def get_custom_args(): if current != default: custom[arg] = getattr(args, arg) installer.log.info(f'Command line args: {sys.argv[1:]} {installer.print_dict(custom)}') + if os.environ.get('SD_ENV_DEBUG', None) is not None: + env = os.environ.copy() + if 'PATH' in env: + del env['PATH'] + if 'PS1' in env: + del env['PS1'] + installer.log.trace(f'Environment: {installer.print_dict(env)}') + else: + env = [f'{k}={v}' for k, v in os.environ.items() if k.startswith('SD_')] + installer.log.debug(f'Env flags: {env}') @lru_cache() diff --git a/modules/hypernetworks/ui.py b/modules/hypernetworks/ui.py index 2d6acbafb..c8b6cd5c3 100644 --- a/modules/hypernetworks/ui.py +++ b/modules/hypernetworks/ui.py @@ -23,8 +23,8 @@ def train_hypernetwork(*args): Hypernetwork saved to {html.escape(filename)} """ return res, "" - except Exception: - raise + except Exception as e: + raise RuntimeError("Hypernetwork error") from e finally: shared.sd_model.cond_stage_model.to(devices.device) shared.sd_model.first_stage_model.to(devices.device) diff --git a/modules/processing.py b/modules/processing.py index 41857ea3c..7710117fe 100644 --- a/modules/processing.py +++ b/modules/processing.py @@ -11,7 +11,7 @@ import torch import numpy as np import cv2 -from PIL import Image, ImageFilter, ImageOps +from PIL import Image, ImageOps from skimage import exposure from ldm.data.util import AddMiDaS from ldm.models.diffusion.ddpm import LatentDepth2ImageDiffusion diff --git a/modules/ui.py b/modules/ui.py index ac328469f..f8de15f4e 100644 --- a/modules/ui.py +++ b/modules/ui.py @@ -9,41 +9,32 @@ import numpy as np from PIL import Image from modules.call_queue import wrap_gradio_gpu_call, wrap_queued_call, wrap_gradio_call - -from modules import sd_hijack, sd_models, script_callbacks, ui_extensions, deepbooru, extra_networks, ui_common, ui_postprocessing, ui_loadsave, ui_train, ui_models, ui_control, ui_interrogate, modelloader +from modules import timer, shared, theme, sd_models, script_callbacks, modelloader, prompt_parser, ui_common, ui_loadsave, ui_symbols, generation_parameters_copypaste from modules.ui_components import FormRow, FormGroup, ToolButton, FormHTML from modules.paths import script_path, data_path -from modules.shared import opts, cmd_opts from modules.dml import directml_override_opts -from modules import prompt_parser -from modules import timer -import modules.ui_symbols as symbols -import modules.generation_parameters_copypaste as parameters_copypaste -import modules.hypernetworks.ui import modules.scripts -import modules.shared -import modules.errors -import modules.styles -import modules.extras -import modules.theme import modules.textual_inversion.ui -import modules.sd_samplers +import modules.hypernetworks.ui +import modules.errors modules.errors.install() mimetypes.init() mimetypes.add_type('application/javascript', '.js') -log = modules.shared.log +log = shared.log +opts = shared.opts +cmd_opts = shared.cmd_opts ui_system_tabs = None -switch_values_symbol = symbols.switch -detect_image_size_symbol = symbols.detect -paste_symbol = symbols.paste -clear_prompt_symbol = symbols.clear -restore_progress_symbol = symbols.apply -folder_symbol = symbols.folder -extra_networks_symbol = symbols.networks -apply_style_symbol = symbols.apply -save_style_symbol = symbols.save +switch_values_symbol = ui_symbols.switch +detect_image_size_symbol = ui_symbols.detect +paste_symbol = ui_symbols.paste +clear_prompt_symbol = ui_symbols.clear +restore_progress_symbol = ui_symbols.apply +folder_symbol = ui_symbols.folder +extra_networks_symbol = ui_symbols.networks +apply_style_symbol = ui_symbols.apply +save_style_symbol = ui_symbols.save txt2img_paste_fields = [] img2img_paste_fields = [] txt2img_args = [] @@ -77,16 +68,17 @@ def infotext_to_html(text): # may be referenced by extensions def send_gradio_gallery_to_image(x): if len(x) == 0: return None - return parameters_copypaste.image_from_url_text(x[0]) + return generation_parameters_copypaste.image_from_url_text(x[0]) def add_style(name: str, prompt: str, negative_prompt: str): + from modules import styles if name is None: return [gr_show() for x in range(4)] - style = modules.styles.Style(name, prompt, negative_prompt) - modules.shared.prompt_styles.styles[style.name] = style - modules.shared.prompt_styles.save_styles(modules.shared.opts.styles_dir) - return [gr.Dropdown.update(visible=True, choices=list(modules.shared.prompt_styles.styles)) for _ in range(2)] + style = styles.Style(name, prompt, negative_prompt) + shared.prompt_styles.styles[style.name] = style + shared.prompt_styles.save_styles(shared.opts.styles_dir) + return [gr.Dropdown.update(visible=True, choices=list(shared.prompt_styles.styles)) for _ in range(2)] def calc_resolution_hires(width, height, hr_scale, hr_resize_x, hr_resize_y, hr_upscaler): @@ -109,8 +101,8 @@ def resize_from_to_html(width, height, scale_by): def apply_styles(prompt, prompt_neg, styles): - prompt = modules.shared.prompt_styles.apply_styles_to_prompt(prompt, styles) - prompt_neg = modules.shared.prompt_styles.apply_negative_styles_to_prompt(prompt_neg, styles) + prompt = shared.prompt_styles.apply_styles_to_prompt(prompt, styles) + prompt_neg = shared.prompt_styles.apply_negative_styles_to_prompt(prompt_neg, styles) return [gr.Textbox.update(value=prompt), gr.Textbox.update(value=prompt_neg), gr.Dropdown.update(value=[])] @@ -130,7 +122,7 @@ def process_interrogate(interrogation_function, mode, ii_input_files, ii_input_d if not os.path.isdir(ii_input_dir): log.error(f"Interrogate: Input directory not found: {ii_input_dir}") return [gr.update(), None] - images = modules.shared.listfiles(ii_input_dir) + images = shared.listfiles(ii_input_dir) if ii_output_dir != "": os.makedirs(ii_output_dir, exist_ok=True) else: @@ -147,11 +139,12 @@ def interrogate(image): if image is None: log.error("Interrogate: no image selected") return gr.update() - prompt = modules.shared.interrogator.interrogate(image.convert("RGB")) + prompt = shared.interrogator.interrogate(image.convert("RGB")) return gr.update() if prompt is None else prompt def interrogate_deepbooru(image): + from modules import deepbooru prompt = deepbooru.model.tag(image) return gr.update() if prompt is None else prompt @@ -161,7 +154,7 @@ def create_batch_inputs(tab): with FormRow(elem_id=f"{tab}_row_batch"): batch_count = gr.Slider(minimum=1, step=1, label='Batch count', value=1, elem_id=f"{tab}_batch_count") batch_size = gr.Slider(minimum=1, maximum=32, step=1, label='Batch size', value=1, elem_id=f"{tab}_batch_size") - batch_switch_btn = ToolButton(value=symbols.switch, elem_id=f"{tab}_batch_switch_btn", label="Switch dims") + batch_switch_btn = ToolButton(value=ui_symbols.switch, elem_id=f"{tab}_batch_switch_btn", label="Switch dims") batch_switch_btn.click(lambda w, h: (h, w), inputs=[batch_count, batch_size], outputs=[batch_count, batch_size], show_progress=False) return batch_count, batch_size @@ -170,12 +163,12 @@ def create_seed_inputs(tab, reuse_visible=True): with gr.Accordion(open=False, label="Seed", elem_id=f"{tab}_seed_group", elem_classes=["small-accordion"]): with FormRow(elem_id=f"{tab}_seed_row", variant="compact"): seed = gr.Number(label='Initial seed', value=-1, elem_id=f"{tab}_seed", container=True) - random_seed = ToolButton(symbols.random, elem_id=f"{tab}_random_seed", label='Random seed') - reuse_seed = ToolButton(symbols.reuse, elem_id=f"{tab}_reuse_seed", label='Reuse seed', visible=reuse_visible) + random_seed = ToolButton(ui_symbols.random, elem_id=f"{tab}_random_seed", label='Random seed') + reuse_seed = ToolButton(ui_symbols.reuse, elem_id=f"{tab}_reuse_seed", label='Reuse seed', visible=reuse_visible) with FormRow(visible=True, elem_id=f"{tab}_subseed_row", variant="compact"): subseed = gr.Number(label='Variation', value=-1, elem_id=f"{tab}_subseed", container=True) - random_subseed = ToolButton(symbols.random, elem_id=f"{tab}_random_subseed") - reuse_subseed = ToolButton(symbols.reuse, elem_id=f"{tab}_reuse_subseed", visible=reuse_visible) + random_subseed = ToolButton(ui_symbols.random, elem_id=f"{tab}_random_subseed") + reuse_subseed = ToolButton(ui_symbols.reuse, elem_id=f"{tab}_reuse_subseed", visible=reuse_visible) subseed_strength = gr.Slider(label='Variation strength', value=0.0, minimum=0, maximum=1, step=0.01, elem_id=f"{tab}_subseed_strength") with FormRow(visible=False): seed_resize_from_w = gr.Slider(minimum=0, maximum=4096, step=8, label="Resize seed from width", value=0, elem_id=f"{tab}_seed_resize_from_w") @@ -193,13 +186,13 @@ def create_advanced_inputs(tab): clip_skip = gr.Slider(label='CLIP skip', value=1, minimum=1, maximum=14, step=1, elem_id=f"{tab}_clip_skip", interactive=True) with FormRow(): image_cfg_scale = gr.Slider(minimum=0.0, maximum=30.0, step=0.1, label='Secondary CFG scale', value=6.0, elem_id=f"{tab}_image_cfg_scale") - diffusers_guidance_rescale = gr.Slider(minimum=0.0, maximum=1.0, step=0.05, label='Guidance rescale', value=0.7, elem_id=f"{tab}_image_cfg_rescale", visible=modules.shared.backend == modules.shared.Backend.DIFFUSERS) + diffusers_guidance_rescale = gr.Slider(minimum=0.0, maximum=1.0, step=0.05, label='Guidance rescale', value=0.7, elem_id=f"{tab}_image_cfg_rescale", visible=shared.backend == shared.Backend.DIFFUSERS) with gr.Group(): with FormRow(): full_quality = gr.Checkbox(label='Full quality', value=True, elem_id=f"{tab}_full_quality") - restore_faces = gr.Checkbox(label='Face restore', value=False, visible=len(modules.shared.face_restorers) > 1, elem_id=f"{tab}_restore_faces") - tiling = gr.Checkbox(label='Tiling', value=False, elem_id=f"{tab}_tiling", visible=modules.shared.backend == modules.shared.Backend.ORIGINAL) - with gr.Group(visible=modules.shared.backend == modules.shared.Backend.DIFFUSERS): + restore_faces = gr.Checkbox(label='Face restore', value=False, visible=len(shared.face_restorers) > 1, elem_id=f"{tab}_restore_faces") + tiling = gr.Checkbox(label='Tiling', value=False, elem_id=f"{tab}_tiling", visible=shared.backend == shared.Backend.ORIGINAL) + with gr.Group(visible=shared.backend == shared.Backend.DIFFUSERS): with FormRow(): hdr_clamp = gr.Checkbox(label='HDR clamp', value=False, elem_id=f"{tab}_hdr_clamp") hdr_boundary = gr.Slider(minimum=0.0, maximum=10.0, step=0.1, value=4.0, label='Range', elem_id=f"{tab}_hdr_boundary") @@ -219,10 +212,10 @@ def create_resize_inputs(tab, images, time_selector=False, scale_visible=True): dummy_component = gr.Number(visible=False, value=0) with gr.Accordion(open=False, label="Resize", elem_classes=["small-accordion"], elem_id=f"{tab}_resize_group"): with gr.Row(): - resize_mode = gr.Radio(label="Resize mode", elem_id=f"{tab}_resize_mode", choices=modules.shared.resize_modes, type="index", value="None") + resize_mode = gr.Radio(label="Resize mode", elem_id=f"{tab}_resize_mode", choices=shared.resize_modes, type="index", value="None") resize_time = gr.Radio(label="Resize order", elem_id=f"{tab}_resize_order", choices=['Before', 'After'], value="Before", visible=time_selector) with gr.Row(): - resize_name = gr.Dropdown(label="Resize method", elem_id=f"{tab}_resize_name", choices=[x.name for x in modules.shared.sd_upscalers], value=opts.upscaler_for_img2img) + resize_name = gr.Dropdown(label="Resize method", elem_id=f"{tab}_resize_name", choices=[x.name for x in shared.sd_upscalers], value=opts.upscaler_for_img2img) create_refresh_button(resize_name, modelloader.load_upscalers, lambda: {"choices": modelloader.load_upscalers()}, 'refresh_upscalers') with FormRow(visible=True) as _resize_group: @@ -235,9 +228,9 @@ def create_resize_inputs(tab, images, time_selector=False, scale_visible=True): with FormRow(): width = gr.Slider(minimum=64, maximum=8192, step=8, label="Width", value=512, elem_id=f"{tab}_width") height = gr.Slider(minimum=64, maximum=8192, step=8, label="Height", value=512, elem_id=f"{tab}_height") - res_switch_btn = ToolButton(value=symbols.switch, elem_id=f"{tab}_res_switch_btn") + res_switch_btn = ToolButton(value=ui_symbols.switch, elem_id=f"{tab}_res_switch_btn") res_switch_btn.click(lambda w, h: (h, w), inputs=[width, height], outputs=[width, height], show_progress=False) - detect_image_size_btn = ToolButton(value=symbols.detect, elem_id=f"{tab}_detect_image_size_btn") + detect_image_size_btn = ToolButton(value=ui_symbols.detect, elem_id=f"{tab}_detect_image_size_btn") detect_image_size_btn.click(fn=lambda w, h, _: (w or gr.update(), h or gr.update()), _js="currentImg2imgSourceResolution", inputs=[dummy_component, dummy_component, dummy_component], outputs=[width, height], show_progress=False) with gr.Tab(label="Resize by") as tab_scale_by: @@ -293,28 +286,28 @@ def copy_seed(gen_info_string: str, index: int): def update_token_counter(text, steps): + from modules import extra_networks, sd_hijack try: text, _ = extra_networks.parse_prompt(text) _, prompt_flat_list, _ = prompt_parser.get_multicond_prompt_list([text]) prompt_schedules = prompt_parser.get_learned_conditioning_prompt_schedules(prompt_flat_list, steps) except Exception: - # a parsing error can happen here during typing, and we don't want to bother the user with - # messages related to it in console prompt_schedules = [[[steps, text]]] + flat_prompts = reduce(lambda list1, list2: list1+list2, prompt_schedules) prompts = [prompt_text for step, prompt_text in flat_prompts] - if modules.shared.backend == modules.shared.Backend.ORIGINAL: + if shared.backend == shared.Backend.ORIGINAL: token_count, max_length = max([sd_hijack.model_hijack.get_prompt_lengths(prompt) for prompt in prompts], key=lambda args: args[0]) - elif modules.shared.backend == modules.shared.Backend.DIFFUSERS: - if modules.shared.sd_model is not None and hasattr(modules.shared.sd_model, 'tokenizer'): - tokenizer = modules.shared.sd_model.tokenizer + elif shared.backend == shared.Backend.DIFFUSERS: + if shared.sd_model is not None and hasattr(shared.sd_model, 'tokenizer'): + tokenizer = shared.sd_model.tokenizer if tokenizer is None: token_count = 0 max_length = 75 else: has_bos_token = tokenizer.bos_token_id is not None has_eos_token = tokenizer.eos_token_id is not None - ids = [modules.shared.sd_model.tokenizer(prompt) for prompt in prompts] + ids = [shared.sd_model.tokenizer(prompt) for prompt in prompts] if len(ids) > 0 and hasattr(ids[0], 'input_ids'): ids = [x.input_ids for x in ids] token_count = max([len(x) for x in ids]) - int(has_bos_token) - int(has_eos_token) @@ -349,11 +342,11 @@ def create_toprow(is_img2img: bool = False, id_part: str = None): submit = gr.Button('Generate', elem_id=f"{id_part}_generate", variant='primary') with gr.Row(elem_id=f"{id_part}_generate_line2"): interrupt = gr.Button('Stop', elem_id=f"{id_part}_interrupt") - interrupt.click(fn=lambda: modules.shared.state.interrupt(), _js="requestInterrupt", inputs=[], outputs=[]) + interrupt.click(fn=lambda: shared.state.interrupt(), _js="requestInterrupt", inputs=[], outputs=[]) skip = gr.Button('Skip', elem_id=f"{id_part}_skip") - skip.click(fn=lambda: modules.shared.state.skip(), inputs=[], outputs=[]) + skip.click(fn=lambda: shared.state.skip(), inputs=[], outputs=[]) pause = gr.Button('Pause', elem_id=f"{id_part}_pause") - pause.click(fn=lambda: modules.shared.state.pause(), _js='checkPaused', inputs=[], outputs=[]) + pause.click(fn=lambda: shared.state.pause(), _js='checkPaused', inputs=[], outputs=[]) with gr.Row(elem_id=f"{id_part}_tools"): button_paste = gr.Button(value='Restore', variant='secondary', elem_id=f"{id_part}_paste") # symbols.paste button_clear = gr.Button(value='Clear', variant='secondary', elem_id=f"{id_part}_clear_prompt_btn") # symbols.clear @@ -365,13 +358,13 @@ def create_toprow(is_img2img: bool = False, id_part: str = None): negative_token_counter = gr.HTML(value="0/75", elem_id=f"{id_part}_negative_token_counter", elem_classes=["token-counter"]) negative_token_button = gr.Button(visible=False, elem_id=f"{id_part}_negative_token_button") with gr.Row(elem_id=f"{id_part}_styles_row"): - styles = gr.Dropdown(label="Styles", elem_id=f"{id_part}_styles", choices=[style.name for style in modules.shared.prompt_styles.styles.values()], value=[], multiselect=True) - _styles_btn_refresh = create_refresh_button(styles, modules.shared.prompt_styles.reload, lambda: {"choices": list(modules.shared.prompt_styles.styles)}, f"{id_part}_styles_refresh") + styles = gr.Dropdown(label="Styles", elem_id=f"{id_part}_styles", choices=[style.name for style in shared.prompt_styles.styles.values()], value=[], multiselect=True) + _styles_btn_refresh = create_refresh_button(styles, shared.prompt_styles.reload, lambda: {"choices": list(shared.prompt_styles.styles)}, f"{id_part}_styles_refresh") # styles_btn_refresh = ToolButton(symbols.refresh, elem_id=f"{id_part}_styles_refresh", visible=True) - # styles_btn_refresh.click(fn=lambda: gr.update(choices=[style.name for style in modules.shared.prompt_styles.styles.values()]), inputs=[], outputs=[styles]) + # styles_btn_refresh.click(fn=lambda: gr.update(choices=[style.name for style in shared.prompt_styles.styles.values()]), inputs=[], outputs=[styles]) styles_btn_select = gr.Button('Select', elem_id=f"{id_part}_styles_select", visible=False) styles_btn_select.click(_js="applyStyles", fn=parse_style, inputs=[styles], outputs=[styles]) - styles_btn_apply = ToolButton(symbols.apply, elem_id=f"{id_part}_extra_apply", visible=False) + styles_btn_apply = ToolButton(ui_symbols.apply, elem_id=f"{id_part}_extra_apply", visible=False) styles_btn_apply.click(fn=apply_styles, inputs=[prompt, negative_prompt, styles], outputs=[prompt, negative_prompt, styles]) return prompt, styles, negative_prompt, submit, button_interrogate, button_deepbooru, button_paste, button_extra, token_counter, token_button, negative_token_counter, negative_token_button @@ -383,7 +376,7 @@ def setup_progressbar(*args, **kwargs): # pylint: disable=unused-argument def apply_setting(key, value): if value is None: return gr.update() - if modules.shared.cmd_opts.freeze: + if shared.cmd_opts.freeze: return gr.update() # dont allow model to be swapped when model hash exists in prompt if key == "sd_model_checkpoint" and opts.disable_weights_auto_swap: @@ -402,7 +395,7 @@ def apply_setting(key, value): opts.data[key] = valtype(value) if valtype != type(None) else value if oldval != value and opts.data_labels[key].onchange is not None: opts.data_labels[key].onchange() - opts.save(modules.shared.config_filename) + opts.save(shared.config_filename) return getattr(opts, key) @@ -415,19 +408,19 @@ def set_sampler_original_options(sampler_options, sampler_algo): opts.data['schedulers_brownian_noise'] = 'brownian noise' in sampler_options opts.data['schedulers_discard_penultimate'] = 'discard penultimate sigma' in sampler_options opts.data['schedulers_sigma'] = sampler_algo - opts.save(modules.shared.config_filename, silent=True) + opts.save(shared.config_filename, silent=True) def set_sampler_diffuser_options(sampler_options): opts.data['schedulers_use_karras'] = 'karras' in sampler_options opts.data['schedulers_use_thresholding'] = 'dynamic thresholding' in sampler_options opts.data['schedulers_use_loworder'] = 'low order' in sampler_options opts.data['schedulers_rescale_betas'] = 'rescale beta' in sampler_options - opts.save(modules.shared.config_filename, silent=True) + opts.save(shared.config_filename, silent=True) with FormRow(elem_classes=['flex-break']): sampler_index = gr.Dropdown(label='Sampling method', elem_id=f"{tabname}_sampling", choices=[x.name for x in choices], value='Default', type="index") steps = gr.Slider(minimum=1, maximum=99, step=1, label="Sampling steps", elem_id=f"{tabname}_steps", value=20) - if modules.shared.backend == modules.shared.Backend.ORIGINAL: + if shared.backend == shared.Backend.ORIGINAL: with FormRow(elem_classes=['flex-break']): choices = ['brownian noise', 'discard penultimate sigma'] values = [] @@ -453,10 +446,11 @@ def set_sampler_diffuser_options(sampler_options): def create_sampler_inputs(tab): + from modules import sd_samplers with gr.Accordion(open=False, label="Sampler", elem_id=f"{tab}_sampler", elem_classes=["small-accordion"]): with FormRow(elem_id=f"{tab}_row_sampler"): - modules.sd_samplers.set_samplers() - steps, sampler_index = create_sampler_and_steps_selection(modules.sd_samplers.samplers, tab) + sd_samplers.set_samplers() + steps, sampler_index = create_sampler_and_steps_selection(sd_samplers.samplers, tab) return steps, sampler_index @@ -471,7 +465,7 @@ def create_hires_inputs(tab): with FormRow(elem_id=f"{tab}_hires_finalres", variant="compact"): hr_final_resolution = FormHTML(value="", elem_id=f"{tab}_hr_finalres", label="Upscaled resolution", interactive=False) with FormRow(elem_id=f"{tab}_hires_fix_row1", variant="compact"): - hr_upscaler = gr.Dropdown(label="Upscaler", elem_id=f"{tab}_hr_upscaler", choices=[*modules.shared.latent_upscale_modes, *[x.name for x in modules.shared.sd_upscalers]], value=modules.shared.latent_upscale_default_mode) + hr_upscaler = gr.Dropdown(label="Upscaler", elem_id=f"{tab}_hr_upscaler", choices=[*shared.latent_upscale_modes, *[x.name for x in shared.sd_upscalers]], value=shared.latent_upscale_default_mode) hr_force = gr.Checkbox(label='Force Hires', value=False, elem_id=f"{tab}_hr_force") with FormRow(elem_id=f"{tab}_hires_fix_row2", variant="compact"): hr_second_pass_steps = gr.Slider(minimum=0, maximum=99, step=1, label='Hires steps', elem_id=f"{tab}_steps_alt", value=20) @@ -479,7 +473,7 @@ def create_hires_inputs(tab): with FormRow(elem_id=f"{tab}_hires_fix_row3", variant="compact"): hr_resize_x = gr.Slider(minimum=0, maximum=4096, step=8, label="Resize width to", value=0, elem_id=f"{tab}_hr_resize_x") hr_resize_y = gr.Slider(minimum=0, maximum=4096, step=8, label="Resize height to", value=0, elem_id=f"{tab}_hr_resize_y") - with FormGroup(visible=modules.shared.backend == modules.shared.Backend.DIFFUSERS): + with FormGroup(visible=shared.backend == shared.Backend.DIFFUSERS): with FormRow(elem_id=f"{tab}_refiner_row1", variant="compact"): refiner_start = gr.Slider(minimum=0.0, maximum=1.0, step=0.05, label='Refiner start', value=0.8, elem_id=f"{tab}_refiner_start") refiner_steps = gr.Slider(minimum=0, maximum=99, step=1, label="Refiner steps", elem_id=f"{tab}_refiner_steps", value=5) @@ -513,7 +507,7 @@ def create_ui(startup_timer = None): if startup_timer is None: timer.startup = timer.Timer() reload_javascript() - parameters_copypaste.reset() + generation_parameters_copypaste.reset() import modules.txt2img # pylint: disable=redefined-outer-name modules.scripts.scripts_current = modules.scripts.scripts_txt2img @@ -535,7 +529,7 @@ def create_ui(startup_timer = None): with FormRow(): width = gr.Slider(minimum=64, maximum=4096, step=8, label="Width", value=512, elem_id="txt2img_width") height = gr.Slider(minimum=64, maximum=4096, step=8, label="Height", value=512, elem_id="txt2img_height") - res_switch_btn = ToolButton(value=symbols.switch, elem_id="txt2img_res_switch_btn", label="Switch dims") + res_switch_btn = ToolButton(value=ui_symbols.switch, elem_id="txt2img_res_switch_btn", label="Switch dims") res_switch_btn.click(lambda w, h: (h, w), inputs=[width, height], outputs=[width, height], show_progress=False) with FormGroup(elem_classes="settings-accordion"): @@ -642,9 +636,9 @@ def create_ui(startup_timer = None): (seed_resize_from_h, "Seed resize from-2"), *modules.scripts.scripts_txt2img.infotext_fields ] - parameters_copypaste.add_paste_fields("txt2img", None, txt2img_paste_fields, override_settings) - txt2img_bindings = parameters_copypaste.ParamBinding(paste_button=txt2img_paste, tabname="txt2img", source_text_component=txt2img_prompt, source_image_component=None) - parameters_copypaste.register_paste_params_button(txt2img_bindings) + generation_parameters_copypaste.add_paste_fields("txt2img", None, txt2img_paste_fields, override_settings) + txt2img_bindings = generation_parameters_copypaste.ParamBinding(paste_button=txt2img_paste, tabname="txt2img", source_text_component=txt2img_prompt, source_image_component=None) + generation_parameters_copypaste.register_paste_params_button(txt2img_bindings) txt2img_token_button.click(fn=wrap_queued_call(update_token_counter), inputs=[txt2img_prompt, steps], outputs=[txt2img_token_counter]) txt2img_negative_token_button.click(fn=wrap_queued_call(update_token_counter), inputs=[txt2img_negative_prompt, steps], outputs=[txt2img_negative_token_counter]) @@ -716,16 +710,16 @@ def update_orig(image, state): init_mask_inpaint = gr.Image(label="Mask", source="upload", interactive=True, type="pil", elem_id="img_inpaint_mask") with gr.TabItem('Batch', id='batch', elem_id="img2img_batch_tab") as tab_batch: - hidden = '
Disabled when launched with --hide-ui-dir-config.' if modules.shared.cmd_opts.hide_ui_dir_config else '' + hidden = '
Disabled when launched with --hide-ui-dir-config.' if shared.cmd_opts.hide_ui_dir_config else '' gr.HTML( "

Upload images or process images in a directory" + "
Add inpaint batch mask directory to enable inpaint batch processing" f"{hidden}

" ) img2img_batch_files = gr.Files(label="Batch Process", interactive=True, elem_id="img2img_image_batch") - img2img_batch_input_dir = gr.Textbox(label="Inpaint batch input directory", **modules.shared.hide_dirs, elem_id="img2img_batch_input_dir") - img2img_batch_output_dir = gr.Textbox(label="Inpaint batch output directory", **modules.shared.hide_dirs, elem_id="img2img_batch_output_dir") - img2img_batch_inpaint_mask_dir = gr.Textbox(label="Inpaint batch mask directory", **modules.shared.hide_dirs, elem_id="img2img_batch_inpaint_mask_dir") + img2img_batch_input_dir = gr.Textbox(label="Inpaint batch input directory", **shared.hide_dirs, elem_id="img2img_batch_input_dir") + img2img_batch_output_dir = gr.Textbox(label="Inpaint batch output directory", **shared.hide_dirs, elem_id="img2img_batch_output_dir") + img2img_batch_inpaint_mask_dir = gr.Textbox(label="Inpaint batch mask directory", **shared.hide_dirs, elem_id="img2img_batch_inpaint_mask_dir") img2img_tabs = [tab_img2img, tab_sketch, tab_inpaint, tab_inpaint_color, tab_inpaint_upload, tab_batch] for i, tab in enumerate(img2img_tabs): @@ -897,32 +891,38 @@ def select_img2img_tab(tab): (seed_resize_from_h, "Seed resize from-2"), *modules.scripts.scripts_img2img.infotext_fields ] - parameters_copypaste.add_paste_fields("img2img", init_img, img2img_paste_fields, override_settings) - parameters_copypaste.add_paste_fields("inpaint", init_img_with_mask, img2img_paste_fields, override_settings) - img2img_bindings = parameters_copypaste.ParamBinding(paste_button=img2img_paste, tabname="img2img", source_text_component=img2img_prompt, source_image_component=None) - parameters_copypaste.register_paste_params_button(img2img_bindings) + generation_parameters_copypaste.add_paste_fields("img2img", init_img, img2img_paste_fields, override_settings) + generation_parameters_copypaste.add_paste_fields("inpaint", init_img_with_mask, img2img_paste_fields, override_settings) + img2img_bindings = generation_parameters_copypaste.ParamBinding(paste_button=img2img_paste, tabname="img2img", source_text_component=img2img_prompt, source_image_component=None) + generation_parameters_copypaste.register_paste_params_button(img2img_bindings) timer.startup.record("ui-img2img") modules.scripts.scripts_current = None - with gr.Blocks(analytics_enabled=False) as control_interface: - ui_control.create_ui() - timer.startup.record("ui-control") + if shared.backend == shared.Backend.DIFFUSERS: + with gr.Blocks(analytics_enabled=False) as control_interface: + from modules import ui_control + ui_control.create_ui() + timer.startup.record("ui-control") with gr.Blocks(analytics_enabled=False) as extras_interface: + from modules import ui_postprocessing ui_postprocessing.create_ui() timer.startup.record("ui-extras") with gr.Blocks(analytics_enabled=False) as train_interface: + from modules import ui_train ui_train.create_ui([txt2img_prompt, txt2img_negative_prompt, steps, sampler_index, cfg_scale, seed, width, height]) timer.startup.record("ui-train") with gr.Blocks(analytics_enabled=False) as models_interface: + from modules import ui_models ui_models.create_ui() timer.startup.record("ui-models") with gr.Blocks(analytics_enabled=False) as interrogate_interface: + from modules import ui_interrogate ui_interrogate.create_ui() timer.startup.record("ui-interrogate") @@ -989,7 +989,7 @@ def get_opt_values(): loadsave = ui_loadsave.UiLoadsave(cmd_opts.ui_config) components = [] component_dict = {} - modules.shared.settings_components = component_dict + shared.settings_components = component_dict dummy_component1 = gr.Label(visible=False) script_callbacks.ui_settings_callback() @@ -1008,17 +1008,17 @@ def run_settings(*args): if cmd_opts.use_directml: directml_override_opts() if cmd_opts.use_openvino: - if not modules.shared.opts.cuda_compile: - modules.shared.log.warning("OpenVINO: Enabling Torch Compile") - modules.shared.opts.cuda_compile = True - if modules.shared.opts.cuda_compile_backend != "openvino_fx": - modules.shared.log.warning("OpenVINO: Setting Torch Compiler backend to OpenVINO FX") - modules.shared.opts.cuda_compile_backend = "openvino_fx" - if modules.shared.opts.sd_backend != "diffusers": - modules.shared.log.warning("OpenVINO: Setting backend to Diffusers") - modules.shared.opts.sd_backend = "diffusers" + if not shared.opts.cuda_compile: + shared.log.warning("OpenVINO: Enabling Torch Compile") + shared.opts.cuda_compile = True + if shared.opts.cuda_compile_backend != "openvino_fx": + shared.log.warning("OpenVINO: Setting Torch Compiler backend to OpenVINO FX") + shared.opts.cuda_compile_backend = "openvino_fx" + if shared.opts.sd_backend != "diffusers": + shared.log.warning("OpenVINO: Setting backend to Diffusers") + shared.opts.sd_backend = "diffusers" try: - opts.save(modules.shared.config_filename) + opts.save(shared.config_filename) if len(changed) > 0: log.info(f'Settings: changed={len(changed)} {changed}') except RuntimeError: @@ -1033,7 +1033,7 @@ def run_settings_single(value, key): return gr.update(value=getattr(opts, key)), opts.dumpjson() if cmd_opts.use_directml: directml_override_opts() - opts.save(modules.shared.config_filename) + opts.save(shared.config_filename) log.debug(f'Setting changed: key={key}, value={value}') return get_value_for_setting(key), opts.dumpjson() @@ -1079,7 +1079,7 @@ def run_settings_single(value, key): current_row = gr.Column(variant='compact') current_row.__enter__() previous_section = item.section - if k in quicksettings_names and not modules.shared.cmd_opts.freeze: + if k in quicksettings_names and not shared.cmd_opts.freeze: quicksettings_list.append((i, k, item)) components.append(dummy_component) elif section_must_be_skipped: @@ -1109,7 +1109,7 @@ def run_settings_single(value, key): gr.Markdown(md) with gr.TabItem("Licenses", id="system_licenses", elem_id="system_tab_licenses"): - gr.HTML(modules.shared.html("licenses.html"), elem_id="licenses", elem_classes="licenses") + gr.HTML(shared.html("licenses.html"), elem_id="licenses", elem_classes="licenses") create_dirty_indicator("tab_licenses", [], interactive=False) def unload_sd_weights(): @@ -1137,27 +1137,29 @@ def reload_sd_weights(): ] interfaces += script_callbacks.ui_tabs_callback() interfaces += [(settings_interface, "System", "system")] + + from modules import ui_extensions extensions_interface = ui_extensions.create_ui() interfaces += [(extensions_interface, "Extensions", "extensions")] timer.startup.record("ui-extensions") - modules.shared.tab_names = [] + shared.tab_names = [] for _interface, label, _ifid in interfaces: - modules.shared.tab_names.append(label) + shared.tab_names.append(label) - with gr.Blocks(theme=modules.theme.gradio_theme, analytics_enabled=False, title="SD.Next") as demo: + with gr.Blocks(theme=theme.gradio_theme, analytics_enabled=False, title="SD.Next") as demo: with gr.Row(elem_id="quicksettings", variant="compact"): for _i, k, _item in sorted(quicksettings_list, key=lambda x: quicksettings_names.get(x[1], x[0])): component = create_setting_component(k, is_quicksettings=True) component_dict[k] = component - parameters_copypaste.connect_paste_params_buttons() + generation_parameters_copypaste.connect_paste_params_buttons() with gr.Tabs(elem_id="tabs") as tabs: for interface, label, ifid in interfaces: if interface is None: continue - # if label in modules.shared.opts.hidden_tabs or label == '': + # if label in shared.opts.hidden_tabs or label == '': # continue with gr.TabItem(label, id=ifid, elem_id=f"tab_{ifid}"): # log.debug(f'UI render: id={ifid}') @@ -1180,9 +1182,9 @@ def reload_sd_weights(): inputs=components, outputs=[text_settings, result], ) - defaults_submit.click(fn=lambda: modules.shared.restore_defaults(restart=True), _js="restartReload") - restart_submit.click(fn=lambda: modules.shared.restart_server(restart=True), _js="restartReload") - shutdown_submit.click(fn=lambda: modules.shared.restart_server(restart=False), _js="restartReload") + defaults_submit.click(fn=lambda: shared.restore_defaults(restart=True), _js="restartReload") + restart_submit.click(fn=lambda: shared.restart_server(restart=True), _js="restartReload") + shutdown_submit.click(fn=lambda: shared.restart_server(restart=False), _js="restartReload") for _i, k, _item in quicksettings_list: component = component_dict[k] @@ -1298,7 +1300,7 @@ def stylesheet(fn): if not os.path.isfile(cssfile): continue head += stylesheet(cssfile) - if opts.gradio_theme in modules.theme.list_builtin_themes(): + if opts.gradio_theme in theme.list_builtin_themes(): head += stylesheet(os.path.join(script_path, "javascript", f"{opts.gradio_theme}.css")) if os.path.exists(os.path.join(data_path, "user.css")): head += stylesheet(os.path.join(data_path, "user.css")) @@ -1308,13 +1310,13 @@ def stylesheet(fn): def reload_javascript(): - is_builtin = modules.theme.reload_gradio_theme() + is_builtin = theme.reload_gradio_theme() head = html_head() css = html_css(is_builtin) body = html_body() def template_response(*args, **kwargs): - res = modules.shared.GradioTemplateResponseOriginal(*args, **kwargs) + res = shared.GradioTemplateResponseOriginal(*args, **kwargs) res.body = res.body.replace(b'', f'{head}'.encode("utf8")) res.body = res.body.replace(b'', f'{css}{body}'.encode("utf8")) res.init_headers() @@ -1338,5 +1340,5 @@ def quicksettings_hint(): app.add_api_route("/internal/ping", lambda: {}, methods=["GET"]) -if not hasattr(modules.shared, 'GradioTemplateResponseOriginal'): - modules.shared.GradioTemplateResponseOriginal = gradio.routes.templates.TemplateResponse +if not hasattr(shared, 'GradioTemplateResponseOriginal'): + shared.GradioTemplateResponseOriginal = gradio.routes.templates.TemplateResponse From 93a3b532d8d0216d1eab4dc992fa78401a71ca89 Mon Sep 17 00:00:00 2001 From: Vladimir Mandic Date: Fri, 22 Dec 2023 14:15:08 -0500 Subject: [PATCH 105/143] control input control --- CHANGELOG.md | 2 +- javascript/sdnext.css | 2 + modules/control/adapters.py | 4 +- modules/control/controlnets.py | 4 +- modules/control/controlnetsxs.py | 4 +- modules/control/processors.py | 10 ++- modules/control/run.py | 123 ++++++++++++++++++-------- modules/control/unit.py | 9 +- modules/ui.py | 7 +- modules/ui_control.py | 144 ++++++++++++++++++++++--------- wiki | 2 +- 11 files changed, 216 insertions(+), 95 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 71af9e0c3..8a2d44611 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,6 @@ # Change Log for SD.Next -## Update for 2023-12-21 +## Update for 2023-12-22 *Note*: based on `diffusers==0.25.0.dev0` diff --git a/javascript/sdnext.css b/javascript/sdnext.css index 846c4c5f7..b4d7fce93 100644 --- a/javascript/sdnext.css +++ b/javascript/sdnext.css @@ -256,6 +256,8 @@ table.settings-value-table td { padding: 0.4em; border: 1px solid #ccc; max-widt font-size: 0.7em; z-index: 50; font-family: monospace; display: none; } /* control */ +#control_input_type { max-width: 18em } +#control_settings .small-accordion .form { min-width: 350px !important } .control-button { min-height: 42px; max-height: 42px; line-height: 1em; } .control-tabs>.tab-nav { margin-bottom: 0; margin-top: 0; } .processor-settings { padding: 0 !important; max-width: 300px; } diff --git a/modules/control/adapters.py b/modules/control/adapters.py index 9e8360567..12ed28907 100644 --- a/modules/control/adapters.py +++ b/modules/control/adapters.py @@ -77,7 +77,7 @@ def reset(self): self.model = None self.model_id = None - def load(self, model_id: str = None): + def load(self, model_id: str = None) -> str: try: t0 = time.time() model_id = model_id or self.model_id @@ -97,9 +97,11 @@ def load(self, model_id: str = None): t1 = time.time() self.model_id = model_id log.debug(f'Control {what} loaded: id="{model_id}" path="{model_path}" time={t1-t0:.2f}') + return f'{what} loaded model: {model_id}' except Exception as e: log.error(f'Control {what} model load failed: id="{model_id}" error={e}') errors.display(e, f'Control {what} load') + return f'{what} failed to load model: {model_id}' class AdapterPipeline(): diff --git a/modules/control/controlnets.py b/modules/control/controlnets.py index 14a73c2cf..0230fe773 100644 --- a/modules/control/controlnets.py +++ b/modules/control/controlnets.py @@ -90,7 +90,7 @@ def reset(self): self.model = None self.model_id = None - def load(self, model_id: str = None): + def load(self, model_id: str = None) -> str: try: t0 = time.time() model_id = model_id or self.model_id @@ -115,9 +115,11 @@ def load(self, model_id: str = None): t1 = time.time() self.model_id = model_id log.debug(f'Control {what} model loaded: id="{model_id}" path="{model_path}" time={t1-t0:.2f}') + return f'{what} loaded model: {model_id}' except Exception as e: log.error(f'Control {what} model load failed: id="{model_id}" error={e}') errors.display(e, f'Control {what} load') + return f'{what} failed to load model: {model_id}' class ControlNetPipeline(): diff --git a/modules/control/controlnetsxs.py b/modules/control/controlnetsxs.py index 78890c840..7096902a7 100644 --- a/modules/control/controlnetsxs.py +++ b/modules/control/controlnetsxs.py @@ -81,7 +81,7 @@ def reset(self): self.model = None self.model_id = None - def load(self, model_id: str = None, time_embedding_mix: float = 0.0): + def load(self, model_id: str = None, time_embedding_mix: float = 0.0) -> str: try: t0 = time.time() model_id = model_id or self.model_id @@ -107,9 +107,11 @@ def load(self, model_id: str = None, time_embedding_mix: float = 0.0): t1 = time.time() self.model_id = model_id log.debug(f'Control {what} model loaded: id="{model_id}" path="{model_path}" time={t1-t0:.2f}') + return f'{what} loaded model: {model_id}' except Exception as e: log.error(f'Control {what} model load failed: id="{model_id}" error={e}') errors.display(e, f'Control {what} load') + return f'{what} failed to load model: {model_id}' class ControlNetXSPipeline(): diff --git a/modules/control/processors.py b/modules/control/processors.py index c8cc9438e..ab6196d88 100644 --- a/modules/control/processors.py +++ b/modules/control/processors.py @@ -131,13 +131,13 @@ def reset(self): self.processor_id = None self.override = None - def load(self, processor_id: str = None): + def load(self, processor_id: str = None) -> str: try: t0 = time.time() processor_id = processor_id or self.processor_id if processor_id is None or processor_id == 'None': self.reset() - return + return '' from_config = config.get(processor_id, {}).get('load_config', None) if from_config is not None: for k, v in from_config.items(): @@ -158,7 +158,7 @@ def load(self, processor_id: str = None): pose_ckpt = 'https://huggingface.co/yzd-v/DWPose/resolve/main/dw-ll_ucoco_384.pth' else: log.error(f'Control processor load failed: id="{processor_id}" error=unknown model type') - return + return f'Processor failed to load: {processor_id}' self.model = cls(det_ckpt=det_ckpt, pose_config=pose_config, pose_ckpt=pose_ckpt, device="cpu") elif 'SegmentAnything' in processor_id: if 'Base' == config['SegmentAnything']['model']: @@ -167,7 +167,7 @@ def load(self, processor_id: str = None): self.model = cls.from_pretrained(model_path = 'segments-arnaud/sam_vit_l', filename='sam_vit_l_0b3195.pth', model_type='vit_l', **self.load_config) else: log.error(f'Control processor load failed: id="{processor_id}" error=unknown model type') - return + return f'Processor failed to load: {processor_id}' elif config[processor_id].get('load_config', None) is not None: self.model = cls.from_pretrained(**self.load_config) elif config[processor_id]['checkpoint']: @@ -177,9 +177,11 @@ def load(self, processor_id: str = None): t1 = time.time() self.processor_id = processor_id log.debug(f'Control processor loaded: id="{processor_id}" class={self.model.__class__.__name__} time={t1-t0:.2f}') + return f'Processor loaded: {processor_id}' except Exception as e: log.error(f'Control processor load failed: id="{processor_id}" error={e}') display(e, 'Control processor load') + return f'Processor load filed: {processor_id}' def __call__(self, image_input: Image): if self.override is not None: diff --git a/modules/control/run.py b/modules/control/run.py index 94a5b788f..40868d9a4 100644 --- a/modules/control/run.py +++ b/modules/control/run.py @@ -59,7 +59,7 @@ def restore_pipeline(): devices.torch_gc() -def control_run(units: List[unit.Unit], inputs, unit_type: str, is_generator: bool, +def control_run(units: List[unit.Unit], inputs, inits, unit_type: str, is_generator: bool, input_type: int, prompt, negative, styles, steps, sampler_index, seed, subseed, subseed_strength, seed_resize_from_h, seed_resize_from_w, cfg_scale, clip_skip, image_cfg_scale, diffusers_guidance_rescale, full_quality, restore_faces, tiling, @@ -69,7 +69,7 @@ def control_run(units: List[unit.Unit], inputs, unit_type: str, is_generator: bo video_skip_frames, video_type, video_duration, video_loop, video_pad, video_interpolate, ): global pipe, original_pipeline # pylint: disable=global-statement - debug(f'Control {unit_type}: input={inputs}') + debug(f'Control {unit_type}: input={inputs} init={inits} type={input_type}') if inputs is None or (type(inputs) is list and len(inputs) == 0): inputs = [None] output_images: List[Image.Image] = [] # output images @@ -110,7 +110,7 @@ def control_run(units: List[unit.Unit], inputs, unit_type: str, is_generator: bo hdr_maximize = hdr_maximize, hdr_max_center = hdr_max_center, hdr_max_boundry = hdr_max_boundry, - resize_mode = resize_mode, + resize_mode = resize_mode if resize_name != 'None' else 0, resize_name = resize_name, scale_by = scale_by, selected_scale_tab = selected_scale_tab, @@ -136,36 +136,36 @@ def control_run(units: List[unit.Unit], inputs, unit_type: str, is_generator: bo if unit_type == 'adapter' and u.adapter.model is not None: active_process.append(u.process) active_model.append(u.adapter) - active_strength.append(u.strength) + active_strength.append(float(u.strength)) p.adapter_conditioning_factor = u.factor shared.log.debug(f'Control T2I-Adapter unit: process={u.process.processor_id} model={u.adapter.model_id} strength={u.strength} factor={u.factor}') elif unit_type == 'controlnet' and u.controlnet.model is not None: active_process.append(u.process) active_model.append(u.controlnet) - active_strength.append(u.strength) - active_start.append(u.start) - active_end.append(u.end) + active_strength.append(float(u.strength)) + active_start.append(float(u.start)) + active_end.append(float(u.end)) p.guess_mode = u.guess shared.log.debug(f'Control ControlNet unit: process={u.process.processor_id} model={u.controlnet.model_id} strength={u.strength} guess={u.guess} start={u.start} end={u.end}') elif unit_type == 'xs' and u.controlnet.model is not None: active_process.append(u.process) active_model.append(u.controlnet) - active_strength.append(u.strength) - active_start.append(u.start) - active_end.append(u.end) + active_strength.append(float(u.strength)) + active_start.append(float(u.start)) + active_end.append(float(u.end)) p.guess_mode = u.guess shared.log.debug(f'Control ControlNet-XS unit: process={u.process.processor_id} model={u.controlnet.model_id} strength={u.strength} guess={u.guess} start={u.start} end={u.end}') elif unit_type == 'reference': p.override = u.override p.attention = u.attention - p.query_weight = u.query_weight - p.adain_weight = u.adain_weight + p.query_weight = float(u.query_weight) + p.adain_weight = float(u.adain_weight) p.fidelity = u.fidelity shared.log.debug('Control Reference unit') else: active_process.append(u.process) # active_model.append(model) - active_strength.append(u.strength) + active_strength.append(float(u.strength)) p.ops.append('control') has_models = False @@ -184,7 +184,6 @@ def control_run(units: List[unit.Unit], inputs, unit_type: str, is_generator: bo use_conditioning = active_strength[0] if len(active_strength) == 1 else list(active_strength) # strength or list[strength] else: pass - shared.sd_model = sd_models.set_diffuser_pipe(shared.sd_model, sd_models.DiffusersTaskType.TEXT_2_IMAGE) # reset current pipeline debug(f'Control: run type={unit_type} models={has_models}') if unit_type == 'adapter' and has_models: @@ -193,6 +192,8 @@ def control_run(units: List[unit.Unit], inputs, unit_type: str, is_generator: bo p.task_args['adapter_conditioning_scale'] = use_conditioning instance = adapters.AdapterPipeline(selected_models, shared.sd_model) pipe = instance.pipeline + if inits is not None: + shared.log.warning('Control: T2I-Adapter does not support separate init image') elif unit_type == 'controlnet' and has_models: p.extra_generation_params["Control mode"] = 'ControlNet' p.extra_generation_params["Control conditioning"] = use_conditioning @@ -210,6 +211,8 @@ def control_run(units: List[unit.Unit], inputs, unit_type: str, is_generator: bo p.control_guidance_end = active_end[0] if len(active_end) == 1 else list(active_end) instance = controlnetsxs.ControlNetXSPipeline(selected_models, shared.sd_model) pipe = instance.pipeline + if inits is not None: + shared.log.warning('Control: ControlNet-XS does not support separate init image') elif unit_type == 'reference': p.extra_generation_params["Control mode"] = 'Reference' p.extra_generation_params["Control attention"] = p.attention @@ -220,6 +223,8 @@ def control_run(units: List[unit.Unit], inputs, unit_type: str, is_generator: bo p.task_args['style_fidelity'] = p.fidelity instance = reference.ReferencePipeline(shared.sd_model) pipe = instance.pipeline + if inits is not None: + shared.log.warning('Control: ControlNet-XS does not support separate init image') else: # run in img2img mode if len(active_strength) > 0: p.strength = active_strength[0] @@ -244,6 +249,10 @@ def control_run(units: List[unit.Unit], inputs, unit_type: str, is_generator: bo try: with devices.inference_context(): if isinstance(inputs, str): # only video, the rest is a list + if input_type == 2: # separate init image + if isinstance(inits, str) and inits != inputs: + shared.log.warning('Control: separate init video not support for video input') + input_type = 1 try: video = cv2.VideoCapture(inputs) if not video.isOpened(): @@ -268,6 +277,7 @@ def control_run(units: List[unit.Unit], inputs, unit_type: str, is_generator: bo if frame is not None: inputs = [Image.fromarray(frame)] # cv2 to pil for i, input_image in enumerate(inputs): + debug(f'Control Control image: {i + 1} of {len(inputs)}') if shared.state.skipped: shared.state.skipped = False continue @@ -278,20 +288,37 @@ def control_run(units: List[unit.Unit], inputs, unit_type: str, is_generator: bo # get input if isinstance(input_image, str): try: - input_image = Image.open(input_image) + input_image = Image.open(inputs[i]) except Exception as e: - shared.log.error(f'Control: image open failed: path={input_image} error={e}') + shared.log.error(f'Control: image open failed: path={inputs[i]} type=control error={e}') continue + # match init input + if input_type == 1: + debug('Control Init image: same as control') + init_image = input_image + elif inits is None: + debug('Control Init image: none') + init_image = None + elif isinstance(inits[i], str): + debug(f'Control: init image: {inits[i]}') + try: + init_image = Image.open(inits[i]) + except Exception as e: + shared.log.error(f'Control: image open failed: path={inits[i]} type=init error={e}') + continue + else: + debug(f'Control Init image: {i % len(inits) + 1} of {len(inits)}') + init_image = inits[i % len(inits)] index += 1 if video is not None and index % (video_skip_frames + 1) != 0: continue # resize - if resize_mode != 0 and input_image is not None: + if p.resize_mode != 0 and input_image is not None: p.extra_generation_params["Control resize"] = f'{resize_time}: {resize_name}' - if resize_mode != 0 and input_image is not None and resize_time == 'Before': - debug(f'Control resize: image={input_image} width={width} height={height} mode={resize_mode} name={resize_name} sequence={resize_time}') - input_image = images.resize_image(resize_mode, input_image, width, height, resize_name) + if p.resize_mode != 0 and input_image is not None and resize_time == 'Before': + debug(f'Control resize: image={input_image} width={width} height={height} mode={p.resize_mode} name={resize_name} sequence={resize_time}') + input_image = images.resize_image(p.resize_mode, input_image, width, height, resize_name) # process if input_image is None: @@ -335,6 +362,20 @@ def control_run(units: List[unit.Unit], inputs, unit_type: str, is_generator: bo processed_image = [np.array(i) for i in p.image] processed_image = util.blend(processed_image) # blend all processed images into one processed_image = Image.fromarray(processed_image) + + if unit_type == 'controlnet' and input_type == 1: # Init image same as control + p.task_args['control_image'] = p.image + p.task_args['strength'] = p.denoising_strength + p.task_args['image'] = input_image + elif unit_type == 'controlnet' and input_type == 2: # Separate init image + p.task_args['control_image'] = p.image + p.task_args['strength'] = p.denoising_strength + if init_image is None: + shared.log.warning('Control: separate init image not provided') + p.task_args['image'] = input_image + else: + p.task_args['image'] = init_image + if is_generator: image_txt = f'{processed_image.width}x{processed_image.height}' if processed_image is not None else 'None' msg = f'process | {index} of {frames if video is not None else len(inputs)} | {"Image" if video is None else "Frame"} {image_txt}' @@ -342,23 +383,31 @@ def control_run(units: List[unit.Unit], inputs, unit_type: str, is_generator: bo yield (None, processed_image, f'Control {msg}') t2 += time.time() - t2 + # prepare pipeline + if hasattr(p, 'init_images'): + del p.init_images # control never uses init_image as-is + if pipe is not None: + if not has_models and (unit_type == 'controlnet' or unit_type == 'adapter' or unit_type == 'xs'): # run in txt2img or img2img mode + if processed_image is not None: + p.init_images = [processed_image] + pipe = sd_models.set_diffuser_pipe(shared.sd_model, sd_models.DiffusersTaskType.IMAGE_2_IMAGE) + else: + pipe = sd_models.set_diffuser_pipe(shared.sd_model, sd_models.DiffusersTaskType.TEXT_2_IMAGE) + elif unit_type == 'reference': + pipe = sd_models.set_diffuser_pipe(shared.sd_model, sd_models.DiffusersTaskType.TEXT_2_IMAGE) + else: # actual control + if 'control_image' in p.task_args: + pipe = sd_models.set_diffuser_pipe(shared.sd_model, sd_models.DiffusersTaskType.IMAGE_2_IMAGE) # only controlnet supports img2img + else: + pipe = sd_models.set_diffuser_pipe(shared.sd_model, sd_models.DiffusersTaskType.TEXT_2_IMAGE) + # pipeline output = None if pipe is not None: # run new pipeline - if not has_models and (unit_type == 'controlnet' or unit_type == 'adapter' or unit_type == 'xs'): # run in img2img mode - if p.image is None: - if hasattr(p, 'init_images'): - del p.init_images - shared.sd_model = sd_models.set_diffuser_pipe(shared.sd_model, sd_models.DiffusersTaskType.TEXT_2_IMAGE) # reset current pipeline - else: - p.init_images = [processed_image] # pylint: disable=attribute-defined-outside-init - shared.sd_model = sd_models.set_diffuser_pipe(shared.sd_model, sd_models.DiffusersTaskType.IMAGE_2_IMAGE) # reset current pipeline - else: - if hasattr(p, 'init_images'): - del p.init_images - shared.sd_model = sd_models.set_diffuser_pipe(shared.sd_model, sd_models.DiffusersTaskType.TEXT_2_IMAGE) # reset current pipeline - debug(f'Control exec pipeline: class={pipe.__class__} p={vars(p)}') - debug(f'Control exec pipeline: class={pipe.__class__} args={p.task_args}') + debug(f'Control exec pipeline: class={pipe.__class__}') + debug(f'Control exec pipeline: task={sd_models.get_diffusers_task(pipe)}') + debug(f'Control exec pipeline: p={vars(p)}') + debug(f'Control exec pipeline: args={p.task_args}') processed: processing.Processed = processing.process_images(p) # run actual pipeline output = processed.images if processed is not None else None # output = pipe(**vars(p)).images # alternative direct pipe exec call @@ -371,9 +420,9 @@ def control_run(units: List[unit.Unit], inputs, unit_type: str, is_generator: bo output_image = output[0] if output_image is not None: # resize - if resize_mode != 0 and resize_time == 'After': - debug(f'Control resize: image={input_image} width={width} height={height} mode={resize_mode} name={resize_name} sequence={resize_time}') - output_image = images.resize_image(resize_mode, output_image, width, height, resize_name) + if p.resize_mode != 0 and resize_time == 'After': + debug(f'Control resize: image={input_image} width={width} height={height} mode={p.resize_mode} name={resize_name} sequence={resize_time}') + output_image = images.resize_image(p.resize_mode, output_image, width, height, resize_name) elif hasattr(p, 'width') and hasattr(p, 'height'): output_image = output_image.resize((p.width, p.height), Image.Resampling.LANCZOS) diff --git a/modules/control/unit.py b/modules/control/unit.py index 3d52be522..ced17acfd 100644 --- a/modules/control/unit.py +++ b/modules/control/unit.py @@ -32,6 +32,7 @@ def __init__(self, image_upload = None, control_start = None, control_end = None, + result_txt = None, extra_controls: list = [], # noqa B006 ): self.enabled = enabled or False @@ -118,17 +119,17 @@ def upload_image(image_file): # bind ui controls to properties if present if self.type == 'adapter': if model_id is not None: - model_id.change(fn=self.adapter.load, inputs=[model_id], show_progress=True) + model_id.change(fn=self.adapter.load, inputs=[model_id], outputs=[result_txt], show_progress=True) if extra_controls is not None and len(extra_controls) > 0: extra_controls[0].change(fn=adapter_extra, inputs=extra_controls) elif self.type == 'controlnet': if model_id is not None: - model_id.change(fn=self.controlnet.load, inputs=[model_id], show_progress=True) + model_id.change(fn=self.controlnet.load, inputs=[model_id], outputs=[result_txt], show_progress=True) if extra_controls is not None and len(extra_controls) > 0: extra_controls[0].change(fn=controlnet_extra, inputs=extra_controls) elif self.type == 'xs': if model_id is not None: - model_id.change(fn=self.controlnet.load, inputs=[model_id, extra_controls[0]], show_progress=True) + model_id.change(fn=self.controlnet.load, inputs=[model_id, extra_controls[0]], outputs=[result_txt], show_progress=True) if extra_controls is not None and len(extra_controls) > 0: extra_controls[0].change(fn=controlnetxs_extra, inputs=extra_controls) elif self.type == 'reference': @@ -142,7 +143,7 @@ def upload_image(image_file): if model_strength is not None: model_strength.change(fn=strength_change, inputs=[model_strength]) if process_id is not None: - process_id.change(fn=self.process.load, inputs=[process_id], show_progress=True) + process_id.change(fn=self.process.load, inputs=[process_id], outputs=[result_txt], show_progress=True) if reset_btn is not None: reset_btn.click(fn=reset, inputs=[], outputs=[enabled_cb, model_id, process_id, model_strength]) if preview_btn is not None: diff --git a/modules/ui.py b/modules/ui.py index f8de15f4e..a47556115 100644 --- a/modules/ui.py +++ b/modules/ui.py @@ -208,11 +208,14 @@ def create_advanced_inputs(tab): return cfg_scale, clip_skip, image_cfg_scale, diffusers_guidance_rescale, full_quality, restore_faces, tiling, hdr_clamp, hdr_boundary, hdr_threshold, hdr_center, hdr_channel_shift, hdr_full_shift, hdr_maximize, hdr_max_center, hdr_max_boundry -def create_resize_inputs(tab, images, time_selector=False, scale_visible=True): +def create_resize_inputs(tab, images, time_selector=False, scale_visible=True, mode=None): dummy_component = gr.Number(visible=False, value=0) with gr.Accordion(open=False, label="Resize", elem_classes=["small-accordion"], elem_id=f"{tab}_resize_group"): with gr.Row(): - resize_mode = gr.Radio(label="Resize mode", elem_id=f"{tab}_resize_mode", choices=shared.resize_modes, type="index", value="None") + if mode is not None: + resize_mode = gr.Radio(label="Resize mode", elem_id=f"{tab}_resize_mode", choices=shared.resize_modes, type="index", value=mode, visible=False) + else: + resize_mode = gr.Radio(label="Resize mode", elem_id=f"{tab}_resize_mode", choices=shared.resize_modes, type="index", value='None') resize_time = gr.Radio(label="Resize order", elem_id=f"{tab}_resize_order", choices=['Before', 'After'], value="Before", visible=time_selector) with gr.Row(): resize_name = gr.Dropdown(label="Resize method", elem_id=f"{tab}_resize_name", choices=[x.name for x in shared.sd_upscalers], value=opts.upscaler_for_img2img) diff --git a/modules/ui_control.py b/modules/ui_control.py index 1fc1deea6..7665843e0 100644 --- a/modules/ui_control.py +++ b/modules/ui_control.py @@ -14,14 +14,14 @@ max_units = 10 units: list[unit.Unit] = [] # main state variable input_source = None -debug = os.environ.get('SD_CONTROL_DEBUG', None) is not None -if debug: - shared.log.trace('Control debug enabled') +input_init = None +debug = shared.log.trace if os.environ.get('SD_CONTROL_DEBUG', None) is not None else lambda *args, **kwargs: None +debug('Trace: CONTROL') def initialize(): from modules import devices - shared.log.debug(f'Control initialize: models={shared.opts.control_dir} debug={debug}') + shared.log.debug(f'Control initialize: models={shared.opts.control_dir}') controlnets.cache_dir = os.path.join(shared.opts.control_dir, 'controlnets') controlnetsxs.cache_dir = os.path.join(shared.opts.control_dir, 'controlnetsxs') adapters.cache_dir = os.path.join(shared.opts.control_dir, 'adapters') @@ -37,8 +37,7 @@ def initialize(): def return_controls(res): # return preview, image, video, gallery, text - if debug: - shared.log.debug(f'Control received: type={type(res)} {res}') + debug(f'Control received: type={type(res)} {res}') if isinstance(res, str): # error response return [None, None, None, None, res] elif isinstance(res, tuple): # standard response received as tuple via control_run->yield(output_images, process_image, result_txt) @@ -67,7 +66,7 @@ def generate_click(job_id: str, active_tab: str, *args): shared.mem_mon.reset() progress.start_task(job_id) try: - for results in control_run(units, input_source, active_tab, True, *args): + for results in control_run(units, input_source, input_init, active_tab, True, *args): progress.record_results(job_id, results) yield return_controls(results) except Exception as e: @@ -106,27 +105,30 @@ def get_video(filepath: str): return msg -def select_input(selected_input): - global input_source # pylint: disable=global-statement +def select_input(selected_input, selected_init, init_type): + debug(f'Control select input: source={selected_input} init={selected_init}, type={init_type}') + global input_source, input_init # pylint: disable=global-statement input_type = type(selected_input) status = 'Control input | Unknown' + res = [gr.Tabs.update(selected='out-gallery'), status] + # control inputs if hasattr(selected_input, 'size'): # image via upload -> image input_source = [selected_input] input_type = 'PIL.Image' shared.log.debug(f'Control input: type={input_type} input={input_source}') status = f'Control input | Image | Size {selected_input.width}x{selected_input.height} | Mode {selected_input.mode}' - return [gr.Tabs.update(selected='out-gallery'), status] + res = [gr.Tabs.update(selected='out-gallery'), status] elif isinstance(selected_input, gr.components.image.Image): # not likely input_source = [selected_input.value] input_type = 'gr.Image' shared.log.debug(f'Control input: type={input_type} input={input_source}') - return [gr.Tabs.update(selected='out-gallery'), status] + res = [gr.Tabs.update(selected='out-gallery'), status] elif isinstance(selected_input, str): # video via upload > tmp filepath to video input_source = selected_input input_type = 'gr.Video' shared.log.debug(f'Control input: type={input_type} input={input_source}') status = get_video(input_source) - return [gr.Tabs.update(selected='out-video'), status] + res = [gr.Tabs.update(selected='out-video'), status] elif isinstance(selected_input, list): # batch or folder via upload -> list of tmp filepaths if hasattr(selected_input[0], 'name'): input_type = 'tempfiles' @@ -136,10 +138,46 @@ def select_input(selected_input): input_source = selected_input status = f'Control input | Images | Files {len(input_source)}' shared.log.debug(f'Control input: type={input_type} input={input_source}') - return [gr.Tabs.update(selected='out-gallery'), status] + res = [gr.Tabs.update(selected='out-gallery'), status] else: # unknown input_source = None - return [gr.Tabs.update(selected='out-gallery'), status] + # init inputs: optional + if init_type == 0: # Control only + input_init = None + elif init_type == 1: # Init image same as control assigned during runtime + input_init = None + elif init_type == 2: # Separate init image + if hasattr(selected_init, 'size'): # image via upload -> image + input_init = [selected_init] + input_type = 'PIL.Image' + shared.log.debug(f'Control input: type={input_type} input={input_init}') + status = f'Control input | Image | Size {selected_init.width}x{selected_init.height} | Mode {selected_init.mode}' + res = [gr.Tabs.update(selected='out-gallery'), status] + elif isinstance(selected_init, gr.components.image.Image): # not likely + input_init = [selected_init.value] + input_type = 'gr.Image' + shared.log.debug(f'Control input: type={input_type} input={input_init}') + res = [gr.Tabs.update(selected='out-gallery'), status] + elif isinstance(selected_init, str): # video via upload > tmp filepath to video + input_init = selected_init + input_type = 'gr.Video' + shared.log.debug(f'Control input: type={input_type} input={input_init}') + status = get_video(input_init) + res = [gr.Tabs.update(selected='out-video'), status] + elif isinstance(selected_init, list): # batch or folder via upload -> list of tmp filepaths + if hasattr(selected_init[0], 'name'): + input_type = 'tempfiles' + input_init = [f.name for f in selected_init] # tempfile + else: + input_type = 'files' + input_init = selected_init + status = f'Control input | Images | Files {len(input_init)}' + shared.log.debug(f'Control input: type={input_type} input={input_init}') + res = [gr.Tabs.update(selected='out-gallery'), status] + else: # unknown + input_init = None + debug(f'Control select input: source={input_source} init={input_init}') + return res def video_type_change(video_type): @@ -162,7 +200,13 @@ def create_ui(_blocks: gr.Blocks=None): prompt, styles, negative, btn_generate, _btn_interrogate, _btn_deepbooru, btn_paste, btn_extra, prompt_counter, btn_prompt_counter, negative_counter, btn_negative_counter = ui.create_toprow(is_img2img=False, id_part='control') with FormGroup(elem_id="control_interface", equal_height=False): with gr.Row(elem_id='control_settings'): - resize_mode, resize_name, width, height, scale_by, selected_scale_tab, resize_time = ui.create_resize_inputs('control', [], time_selector=True, scale_visible=False) + + with gr.Accordion(open=False, label="Input", elem_id="control_input", elem_classes=["small-accordion"]): + input_type = gr.Radio(label="Input type", choices=['Control only', 'Init image same as control', 'Separate init image'], value='Control only', type='index', elem_id='control_input_type') + denoising_strength = gr.Slider(minimum=0.01, maximum=0.99, step=0.01, label='Denoising strength', value=0.50, elem_id="control_denoising_strength") + show_preview = gr.Checkbox(label="Show preview", value=True, elem_id="control_show_preview") + + resize_mode, resize_name, width, height, scale_by, selected_scale_tab, resize_time = ui.create_resize_inputs('control', [], time_selector=True, scale_visible=False, mode='Fixed') with gr.Accordion(open=False, label="Sampler", elem_id="control_sampler", elem_classes=["small-accordion"]): sd_samplers.set_samplers() @@ -172,9 +216,6 @@ def create_ui(_blocks: gr.Blocks=None): seed, _reuse_seed, subseed, _reuse_subseed, subseed_strength, seed_resize_from_h, seed_resize_from_w = ui.create_seed_inputs('control', reuse_visible=False) cfg_scale, clip_skip, image_cfg_scale, diffusers_guidance_rescale, full_quality, restore_faces, tiling, hdr_clamp, hdr_boundary, hdr_threshold, hdr_center, hdr_channel_shift, hdr_full_shift, hdr_maximize, hdr_max_center, hdr_max_boundry = ui.create_advanced_inputs('control') - with gr.Accordion(open=False, label="Denoise", elem_id="control_denoise", elem_classes=["small-accordion"]): - denoising_strength = gr.Slider(minimum=0.0, maximum=0.99, step=0.01, label='Denoising strength', value=0.50, elem_id="control_denoising_strength") - with gr.Accordion(open=False, label="Video", elem_id="control_video", elem_classes=["small-accordion"]): with gr.Row(): video_skip_frames = gr.Slider(minimum=0, maximum=100, step=1, label='Skip input frames', value=0, elem_id="control_video_skip_frames") @@ -198,8 +239,8 @@ def create_ui(_blocks: gr.Blocks=None): result_txt = gr.HTML(elem_classes=['control-result'], elem_id='control-result') with gr.Row(elem_id='control-inputs'): - with gr.Column(scale=9, elem_id='control-input-column'): - gr.HTML('Input

') + with gr.Column(scale=9, elem_id='control-input-column', visible=True) as _column_input: + gr.HTML('Control input

') with gr.Tabs(elem_classes=['control-tabs'], elem_id='control-tab-input'): with gr.Tab('Image', id='in-image') as tab_image: input_image = gr.Image(label="Input", show_label=False, type="pil", source="upload", interactive=True, tool="editor", height=gr_height) @@ -209,7 +250,18 @@ def create_ui(_blocks: gr.Blocks=None): input_batch = gr.File(label="Input", show_label=False, file_count='multiple', file_types=['image'], type='file', interactive=True, height=gr_height) with gr.Tab('Folder', id='in-folder') as tab_folder: input_folder = gr.File(label="Input", show_label=False, file_count='directory', file_types=['image'], type='file', interactive=True, height=gr_height) - with gr.Column(scale=9, elem_id='control-output-column'): + with gr.Column(scale=9, elem_id='control-init-column', visible=False) as column_init: + gr.HTML('Init input

') + with gr.Tabs(elem_classes=['control-tabs'], elem_id='control-tab-init'): + with gr.Tab('Image', id='init-image') as tab_image_init: + init_image = gr.Image(label="Input", show_label=False, type="pil", source="upload", interactive=True, tool="editor", height=gr_height) + with gr.Tab('Video', id='init-video') as tab_video_init: + init_video = gr.Video(label="Input", show_label=False, interactive=True, height=gr_height) + with gr.Tab('Batch', id='init-batch') as tab_batch_init: + init_batch = gr.File(label="Input", show_label=False, file_count='multiple', file_types=['image'], type='file', interactive=True, height=gr_height) + with gr.Tab('Folder', id='init-folder') as tab_folder_init: + init_folder = gr.File(label="Input", show_label=False, file_count='directory', file_types=['image'], type='file', interactive=True, height=gr_height) + with gr.Column(scale=9, elem_id='control-output-column', visible=True) as _column_output: gr.HTML('Output

') with gr.Tabs(elem_classes=['control-tabs'], elem_id='control-tab-output') as output_tabs: with gr.Tab('Gallery', id='out-gallery'): @@ -218,20 +270,21 @@ def create_ui(_blocks: gr.Blocks=None): output_image = gr.Image(label="Input", show_label=False, type="pil", interactive=False, tool="editor", height=gr_height) with gr.Tab('Video', id='out-video'): output_video = gr.Video(label="Input", show_label=False, height=gr_height) - with gr.Column(scale=9, elem_id='control-preview-column'): + with gr.Column(scale=9, elem_id='control-preview-column', visible=True) as column_preview: gr.HTML('Preview

') with gr.Tabs(elem_classes=['control-tabs'], elem_id='control-tab-preview'): with gr.Tab('Preview', id='preview-image') as tab_image: preview_process = gr.Image(label="Input", show_label=False, type="pil", source="upload", interactive=False, height=gr_height, visible=True) - input_image.change(fn=select_input, inputs=[input_image], outputs=[output_tabs, result_txt]) - input_video.change(fn=select_input, inputs=[input_video], outputs=[output_tabs, result_txt]) - input_batch.change(fn=select_input, inputs=[input_batch], outputs=[output_tabs, result_txt]) - input_folder.change(fn=select_input, inputs=[input_folder], outputs=[output_tabs, result_txt]) - tab_image.select(fn=select_input, inputs=[input_image], outputs=[output_tabs, result_txt]) - tab_video.select(fn=select_input, inputs=[input_video], outputs=[output_tabs, result_txt]) - tab_batch.select(fn=select_input, inputs=[input_batch], outputs=[output_tabs, result_txt]) - tab_folder.select(fn=select_input, inputs=[input_folder], outputs=[output_tabs, result_txt]) + for ctrl in [input_image, input_video, input_batch, input_folder, init_image, init_video, init_batch, init_folder, tab_image, tab_video, tab_batch, tab_folder, tab_image_init, tab_video_init, tab_batch_init, tab_folder_init]: + inputs = [input_image, init_image, input_type] + outputs = [output_tabs, result_txt] + if hasattr(ctrl, 'change'): + ctrl.change(fn=select_input, inputs=inputs, outputs=outputs) + if hasattr(ctrl, 'select'): + ctrl.select(fn=select_input, inputs=inputs, outputs=outputs) + show_preview.change(fn=lambda x: gr.update(visible=x), inputs=[show_preview], outputs=[column_preview]) + input_type.change(fn=lambda x: gr.update(visible=x == 2), inputs=[input_type], outputs=[column_init]) with gr.Tabs(elem_id='control-tabs') as _tabs_control_type: @@ -252,15 +305,16 @@ def create_ui(_blocks: gr.Blocks=None): process_id = gr.Dropdown(label="Processor", choices=processors.list_models(), value='None') model_id = gr.Dropdown(label="ControlNet", choices=controlnets.list_models(), value='None') ui_common.create_refresh_button(model_id, controlnets.list_models, lambda: {"choices": controlnets.list_models(refresh=True)}, 'refresh_control_models') - model_strength = gr.Slider(label="Strength", minimum=0.0, maximum=1.0, step=0.1, value=1.0-i/10) - control_start = gr.Slider(label="Start", minimum=0.0, maximum=1.0, step=0.1, value=0) - control_end = gr.Slider(label="End", minimum=0.0, maximum=1.0, step=0.1, value=1.0) + model_strength = gr.Slider(label="Strength", minimum=0.01, maximum=1.0, step=0.01, value=1.0-i/10) + control_start = gr.Slider(label="Start", minimum=0.0, maximum=1.0, step=0.05, value=0) + control_end = gr.Slider(label="End", minimum=0.0, maximum=1.0, step=0.05, value=1.0) reset_btn = ui_components.ToolButton(value=ui_symbols.reset) image_upload = gr.UploadButton(label=ui_symbols.upload, file_types=['image'], elem_classes=['form', 'gradio-button', 'tool']) process_btn= ui_components.ToolButton(value=ui_symbols.preview) controlnet_ui_units.append(unit_ui) units.append(unit.Unit( unit_type = 'controlnet', + result_txt = result_txt, image_input = input_image, enabled_cb = enabled_cb, reset_btn = reset_btn, @@ -296,15 +350,16 @@ def create_ui(_blocks: gr.Blocks=None): process_id = gr.Dropdown(label="Processor", choices=processors.list_models(), value='None') model_id = gr.Dropdown(label="ControlNet-XS", choices=controlnetsxs.list_models(), value='None') ui_common.create_refresh_button(model_id, controlnetsxs.list_models, lambda: {"choices": controlnetsxs.list_models(refresh=True)}, 'refresh_control_models') - model_strength = gr.Slider(label="Strength", minimum=0.0, maximum=1.0, step=0.1, value=1.0-i/10) - control_start = gr.Slider(label="Start", minimum=0.0, maximum=1.0, step=0.1, value=0) - control_end = gr.Slider(label="End", minimum=0.0, maximum=1.0, step=0.1, value=1.0) + model_strength = gr.Slider(label="Strength", minimum=0.01, maximum=1.0, step=0.01, value=1.0-i/10) + control_start = gr.Slider(label="Start", minimum=0.0, maximum=1.0, step=0.05, value=0) + control_end = gr.Slider(label="End", minimum=0.0, maximum=1.0, step=0.05, value=1.0) reset_btn = ui_components.ToolButton(value=ui_symbols.reset) image_upload = gr.UploadButton(label=ui_symbols.upload, file_types=['image'], elem_classes=['form', 'gradio-button', 'tool']) process_btn= ui_components.ToolButton(value=ui_symbols.preview) controlnetxs_ui_units.append(unit_ui) units.append(unit.Unit( unit_type = 'xs', + result_txt = result_txt, image_input = input_image, enabled_cb = enabled_cb, reset_btn = reset_btn, @@ -327,7 +382,7 @@ def create_ui(_blocks: gr.Blocks=None): gr.HTML('
T2I-Adapter') with gr.Row(): extra_controls = [ - gr.Slider(label="Control factor", minimum=0.0, maximum=1.0, step=0.1, value=1.0, scale=3), + gr.Slider(label="Control factor", minimum=0.0, maximum=1.0, step=0.05, value=1.0, scale=3), ] num_adaptor_units = gr.Slider(label="Units", minimum=1, maximum=max_units, step=1, value=1, scale=1) adaptor_ui_units = [] # list of hidable accordions @@ -340,13 +395,14 @@ def create_ui(_blocks: gr.Blocks=None): process_id = gr.Dropdown(label="Processor", choices=processors.list_models(), value='None') model_id = gr.Dropdown(label="Adapter", choices=adapters.list_models(), value='None') ui_common.create_refresh_button(model_id, adapters.list_models, lambda: {"choices": adapters.list_models(refresh=True)}, 'refresh_adapter_models') - model_strength = gr.Slider(label="Strength", minimum=0.0, maximum=1.0, step=0.1, value=1.0-i/10) + model_strength = gr.Slider(label="Strength", minimum=0.01, maximum=1.0, step=0.01, value=1.0-i/10) reset_btn = ui_components.ToolButton(value=ui_symbols.reset) image_upload = gr.UploadButton(label=ui_symbols.upload, file_types=['image'], elem_classes=['form', 'gradio-button', 'tool']) process_btn= ui_components.ToolButton(value=ui_symbols.preview) adaptor_ui_units.append(unit_ui) units.append(unit.Unit( unit_type = 'adapter', + result_txt = result_txt, image_input = input_image, enabled_cb = enabled_cb, reset_btn = reset_btn, @@ -368,9 +424,9 @@ def create_ui(_blocks: gr.Blocks=None): with gr.Row(): extra_controls = [ gr.Radio(label="Reference context", choices=['Attention', 'Adain', 'Attention Adain'], value='Attention', interactive=True), - gr.Slider(label="Style fidelity", minimum=0.0, maximum=1.0, step=0.1, value=0.5, interactive=True), # prompt vs control importance - gr.Slider(label="Reference query weight", minimum=0.0, maximum=1.0, step=0.1, value=1.0, interactive=True), - gr.Slider(label="Reference adain weight", minimum=0.0, maximum=2.0, step=0.1, value=1.0, interactive=True), + gr.Slider(label="Style fidelity", minimum=0.0, maximum=1.0, step=0.05, value=0.5, interactive=True), # prompt vs control importance + gr.Slider(label="Reference query weight", minimum=0.0, maximum=1.0, step=0.05, value=1.0, interactive=True), + gr.Slider(label="Reference adain weight", minimum=0.0, maximum=2.0, step=0.05, value=1.0, interactive=True), ] for i in range(1): # can only have one reference unit with gr.Accordion(f'Reference unit {i+1}', visible=True) as unit_ui: @@ -379,12 +435,13 @@ def create_ui(_blocks: gr.Blocks=None): with gr.Row(): enabled_cb = gr.Checkbox(value= i == 0, label="Enabled", visible=False) model_id = gr.Dropdown(label="Reference", choices=reference.list_models(), value='Reference', visible=False) - model_strength = gr.Slider(label="Strength", minimum=0.0, maximum=1.0, step=0.1, value=1.0, visible=False) + model_strength = gr.Slider(label="Strength", minimum=0.01, maximum=1.0, step=0.01, value=1.0, visible=False) reset_btn = ui_components.ToolButton(value=ui_symbols.reset) image_upload = gr.UploadButton(label=ui_symbols.upload, file_types=['image'], elem_classes=['form', 'gradio-button', 'tool']) process_btn= ui_components.ToolButton(value=ui_symbols.preview) units.append(unit.Unit( unit_type = 'reference', + result_txt = result_txt, image_input = input_image, enabled_cb = enabled_cb, reset_btn = reset_btn, @@ -443,6 +500,7 @@ def create_ui(_blocks: gr.Blocks=None): tabs_state = gr.Text(value='none', visible=False) input_fields = [ + input_type, prompt, negative, styles, steps, sampler_index, seed, subseed, subseed_strength, seed_resize_from_h, seed_resize_from_w, @@ -476,7 +534,7 @@ def create_ui(_blocks: gr.Blocks=None): bindings = generation_parameters_copypaste.ParamBinding(paste_button=btn_paste, tabname="control", source_text_component=prompt, source_image_component=output_gallery) generation_parameters_copypaste.register_paste_params_button(bindings) - if debug: + if os.environ.get('SD_CONTROL_DEBUG', None) is not None: # debug only from modules.control.test import test_processors, test_controlnets, test_adapters, test_xs gr.HTML('

Debug


') with gr.Row(): diff --git a/wiki b/wiki index f75201d5b..9994450f3 160000 --- a/wiki +++ b/wiki @@ -1 +1 @@ -Subproject commit f75201d5bc7a12c176e7243845c8d96835ef726a +Subproject commit 9994450f36413a1a72500c397ff39e024aab13a1 From 843340d3b3cf51ef5c0b9876fcb6598d811e54b0 Mon Sep 17 00:00:00 2001 From: Disty0 Date: Sat, 23 Dec 2023 15:30:33 +0300 Subject: [PATCH 106/143] OpenVINO 8 bit support for CPUs --- CHANGELOG.md | 1 + installer.py | 1 + modules/intel/openvino/__init__.py | 32 ++++++++++++++++++++---------- modules/shared.py | 1 + 4 files changed, 24 insertions(+), 11 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 8a2d44611..cdac8d8e4 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -75,6 +75,7 @@ - add `DISABLE_IPEXRUN` and `DISABLE_IPEX_1024_WA` environment variables - compatibility improvements - **OpenVINO**, thanks @disty0 + - **8 bit support for CPUs** - add *Directory for OpenVINO cache* option to *System Paths* - remove Intel ARC specific 1024x1024 workaround - **HDR controls** diff --git a/installer.py b/installer.py index 6c2798c99..29d65bb5d 100644 --- a/installer.py +++ b/installer.py @@ -523,6 +523,7 @@ def check_torch(): install('hidet', 'hidet') if args.use_openvino or opts.get('cuda_compile_backend', '') == 'openvino_fx': install(os.environ.get('OPENVINO_PACKAGE', 'openvino==2023.2.0'), 'openvino') + install('nncf==2.7.0', 'nncf') install('onnxruntime-openvino', 'onnxruntime-openvino', ignore=True) # TODO openvino: numpy version conflicts with tensorflow and doesn't support Python 3.11 os.environ.setdefault('PYTORCH_TRACING_MODE', 'TORCHFX') os.environ.setdefault('NEOReadDebugKeys', '1') diff --git a/modules/intel/openvino/__init__.py b/modules/intel/openvino/__init__.py index 6bd090819..d9650ed20 100644 --- a/modules/intel/openvino/__init__.py +++ b/modules/intel/openvino/__init__.py @@ -1,20 +1,35 @@ import os import sys import torch +import nncf + from openvino.frontend import FrontEndManager from openvino.frontend.pytorch.fx_decoder import TorchFXPythonDecoder from openvino.frontend.pytorch.torchdynamo.partition import Partitioner from openvino.runtime import Core, Type, PartialShape, serialize + from torch._dynamo.backends.common import fake_tensor_unsupported from torch._dynamo.backends.registry import register_backend from torch.fx.experimental.proxy_tensor import make_fx from torch.fx import GraphModule from torch.utils._pytree import tree_flatten + from types import MappingProxyType from hashlib import sha256 import functools + from modules import shared, devices +NNCFNodeName = str +def get_node_by_name(self, name: NNCFNodeName) -> nncf.common.graph.NNCFNode: + node_ids = self._node_name_to_node_id_map.get(name, None) + if node_ids is None: + raise RuntimeError("Could not find a node {} in NNCFGraph!".format(name)) + + node_key = f"{node_ids[0]} {name}" + return self._nodes[node_key] +nncf.common.graph.NNCFGraph.get_node_by_name = get_node_by_name + def BUILD_MAP_UNPACK(self, inst): items = self.popn(inst.argval) # ensure everything is a dict @@ -50,18 +65,9 @@ def __init__(self, gm, partition_id, use_python_fusion_cache, model_hash_str: st self.executor_parameters = {"use_python_fusion_cache": use_python_fusion_cache, "model_hash_str": model_hash_str} self.file_name = file_name - self.perm_fallback = False def __call__(self, *args): - #if self.perm_fallback: - # return self.gm(*args) - - #try: result = openvino_execute(self.gm, *args, executor_parameters=self.executor_parameters, partition_id=self.partition_id, file_name=self.file_name) - #except Exception: - # self.perm_fallback = True - # return self.gm(*args) - return result def get_device(): @@ -223,12 +229,14 @@ def openvino_compile(gm: GraphModule, *args, model_hash_str: str = None, file_na om.inputs[idx].get_node().set_element_type(dtype_mapping[input_data.dtype]) om.inputs[idx].get_node().set_partial_shape(PartialShape(list(input_data.shape))) om.validate_nodes_and_infer_types() + if shared.opts.openvino_compress_weights: + om = nncf.compress_weights(om) if model_hash_str is not None: core.set_property({'CACHE_DIR': cache_root + '/blob'}) - compiled = core.compile_model(om, device) - return compiled + compiled_model = core.compile_model(om, device) + return compiled_model def openvino_compile_cached_model(cached_model_path, *example_inputs): core = Core() @@ -249,6 +257,8 @@ def openvino_compile_cached_model(cached_model_path, *example_inputs): om.inputs[idx].get_node().set_element_type(dtype_mapping[input_data.dtype]) om.inputs[idx].get_node().set_partial_shape(PartialShape(list(input_data.shape))) om.validate_nodes_and_infer_types() + if shared.opts.openvino_compress_weights: + om = nncf.compress_weights(om) core.set_property({'CACHE_DIR': shared.opts.openvino_cache_path + '/blob'}) diff --git a/modules/shared.py b/modules/shared.py index b8068cbff..05cdcc9fb 100644 --- a/modules/shared.py +++ b/modules/shared.py @@ -329,6 +329,7 @@ def default(obj): "openvino_hetero_gpu": OptionInfo(False, "OpenVINO use Hetero Device for single inference with multiple devices"), "openvino_remove_cpu_from_hetero": OptionInfo(False, "OpenVINO remove CPU from Hetero Device"), "openvino_remove_igpu_from_hetero": OptionInfo(False, "OpenVINO remove iGPU from Hetero Device"), + "openvino_compress_weights": OptionInfo(False, "OpenVINO compress weights to 8 bit (CPU Only)"), })) options_templates.update(options_section(('advanced', "Inference Settings"), { From 9dcc76af023526cc602f2906b5cf600e91fa416d Mon Sep 17 00:00:00 2001 From: Disty0 Date: Sat, 23 Dec 2023 16:42:24 +0300 Subject: [PATCH 107/143] OpenVINO reduce System RAM usage --- CHANGELOG.md | 1 + modules/intel/openvino/__init__.py | 2 ++ 2 files changed, 3 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index cdac8d8e4..f5a2485a6 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -76,6 +76,7 @@ - compatibility improvements - **OpenVINO**, thanks @disty0 - **8 bit support for CPUs** + - Reduce System RAM usage when compiling - add *Directory for OpenVINO cache* option to *System Paths* - remove Intel ARC specific 1024x1024 workaround - **HDR controls** diff --git a/modules/intel/openvino/__init__.py b/modules/intel/openvino/__init__.py index d9650ed20..40e989df2 100644 --- a/modules/intel/openvino/__init__.py +++ b/modules/intel/openvino/__init__.py @@ -397,6 +397,8 @@ def _call(*args): if inputs_reversed: example_inputs.reverse() model = make_fx(subgraph)(*example_inputs) + # Deleting unused subgraphs doesn't do anything, so we cast it down to fp8 + subgraph = subgraph.to(dtype=torch.float8_e4m3fn) for node in model.graph.nodes: if node.target == torch.ops.aten.mul_.Tensor: node.target = torch.ops.aten.mul.Tensor From d25b3d1f9fa5b221ae0a2de6b5921eca75e03609 Mon Sep 17 00:00:00 2001 From: Disty0 Date: Sat, 23 Dec 2023 17:22:04 +0300 Subject: [PATCH 108/143] OpenVINO fix compile when cache doesn't exist --- modules/intel/openvino/__init__.py | 5 +++-- modules/processing_diffusers.py | 25 ++++++++++++++++++++++++- modules/sd_models_compile.py | 2 ++ 3 files changed, 29 insertions(+), 3 deletions(-) diff --git a/modules/intel/openvino/__init__.py b/modules/intel/openvino/__init__.py index 40e989df2..70d3abaa6 100644 --- a/modules/intel/openvino/__init__.py +++ b/modules/intel/openvino/__init__.py @@ -370,6 +370,9 @@ def openvino_fx(subgraph, example_inputs): example_inputs_reordered.append(example_inputs[idx1]) example_inputs = example_inputs_reordered + # Deleting unused subgraphs doesn't do anything, so we cast it down to fp8 + subgraph = subgraph.to(dtype=torch.float8_e4m3fn) + # Model is fully supported and already cached. Run the cached OV model directly. compiled_model = openvino_compile_cached_model(maybe_fs_cached_name, *example_inputs) @@ -397,8 +400,6 @@ def _call(*args): if inputs_reversed: example_inputs.reverse() model = make_fx(subgraph)(*example_inputs) - # Deleting unused subgraphs doesn't do anything, so we cast it down to fp8 - subgraph = subgraph.to(dtype=torch.float8_e4m3fn) for node in model.graph.nodes: if node.target == torch.ops.aten.mul_.Tensor: node.target = torch.ops.aten.mul.Tensor diff --git a/modules/processing_diffusers.py b/modules/processing_diffusers.py index a9796e1ea..2415d2cde 100644 --- a/modules/processing_diffusers.py +++ b/modules/processing_diffusers.py @@ -132,6 +132,12 @@ def full_vae_decode(latents, model): latents = latents.to(next(iter(model.vae.post_quant_conv.parameters())).dtype) decoded = model.vae.decode(latents / model.vae.config.scaling_factor, return_dict=False)[0] + # Downcast VAE after OpenVINO compile + if shared.opts.cuda_compile and shared.opts.cuda_compile_backend == "openvino_fx" and shared.compiled_model_state.first_pass_vae: + shared.compiled_model_state.first_pass_vae = False + if hasattr(shared.sd_model, "vae"): + shared.sd_model.vae.to(dtype=torch.float8_e4m3fn) + devices.torch_gc(force=True) if shared.opts.diffusers_move_unet and not getattr(model, 'has_accelerate', False) and hasattr(model, 'unet'): model.unet.to(unet_device) t1 = time.time() @@ -447,7 +453,6 @@ def recompile_model(hires=False): shared.compiled_model_state.height = compile_height shared.compiled_model_state.width = compile_width shared.compiled_model_state.batch_size = p.batch_size - shared.compiled_model_state.first_pass = False else: pass #Can be implemented for TensorRT or Olive else: @@ -550,6 +555,12 @@ def calculate_refiner_steps(): try: t0 = time.time() output = shared.sd_model(**base_args) # pylint: disable=not-callable + # Downcast UNET after OpenVINO compile + if shared.opts.cuda_compile and shared.opts.cuda_compile_backend == "openvino_fx" and shared.compiled_model_state.first_pass: + shared.compiled_model_state.first_pass = False + if hasattr(shared.sd_model, "unet"): + shared.sd_model.unet.to(dtype=torch.float8_e4m3fn) + devices.torch_gc(force=True) if shared.cmd_opts.profile: t1 = time.time() shared.log.debug(f'Profile: pipeline call: {t1-t0:.2f}') @@ -613,6 +624,12 @@ def calculate_refiner_steps(): shared.state.sampling_steps = hires_args['num_inference_steps'] try: output = shared.sd_model(**hires_args) # pylint: disable=not-callable + # Downcast UNET after OpenVINO compile + if shared.opts.cuda_compile and shared.opts.cuda_compile_backend == "openvino_fx" and shared.compiled_model_state.first_pass: + shared.compiled_model_state.first_pass = False + if hasattr(shared.sd_model, "unet"): + shared.sd_model.unet.to(dtype=torch.float8_e4m3fn) + devices.torch_gc(force=True) except AssertionError as e: shared.log.info(e) p.init_images = [] @@ -672,6 +689,12 @@ def calculate_refiner_steps(): try: shared.sd_refiner.register_to_config(requires_aesthetics_score=shared.opts.diffusers_aesthetics_score) refiner_output = shared.sd_refiner(**refiner_args) # pylint: disable=not-callable + # Downcast UNET after OpenVINO compile + if shared.opts.cuda_compile and shared.opts.cuda_compile_backend == "openvino_fx" and shared.compiled_model_state.first_pass_refiner: + shared.compiled_model_state.first_pass_refiner = False + if hasattr(shared.sd_refiner, "unet"): + shared.sd_refiner.unet.to(dtype=torch.float8_e4m3fn) + devices.torch_gc(force=True) except AssertionError as e: shared.log.info(e) diff --git a/modules/sd_models_compile.py b/modules/sd_models_compile.py index b68f29b6b..e8dcdcdf9 100644 --- a/modules/sd_models_compile.py +++ b/modules/sd_models_compile.py @@ -9,6 +9,8 @@ class CompiledModelState: def __init__(self): self.first_pass = True + self.first_pass_refiner = True + self.first_pass_vae = True self.height = 512 self.width = 512 self.batch_size = 1 From 79e6c51c068745d5f69bc74fd7c1c084632760e9 Mon Sep 17 00:00:00 2001 From: Vladimir Mandic Date: Sat, 23 Dec 2023 10:02:34 -0500 Subject: [PATCH 109/143] fix control img2img --- modules/control/run.py | 7 +- modules/processing.py | 39 +++++---- modules/processing_diffusers.py | 113 +----------------------- modules/processing_vae.py | 147 ++++++++++++++++++++++++++++++++ modules/ui.py | 19 +++-- wiki | 2 +- 6 files changed, 189 insertions(+), 138 deletions(-) create mode 100644 modules/processing_vae.py diff --git a/modules/control/run.py b/modules/control/run.py index 40868d9a4..e2dad2ea6 100644 --- a/modules/control/run.py +++ b/modules/control/run.py @@ -118,6 +118,8 @@ def control_run(units: List[unit.Unit], inputs, inits, unit_type: str, is_genera n_iter = batch_count, batch_size = batch_size, ) + processing.process_init(p) + if resize_mode != 0 or inputs is None or inputs == [None]: p.width = width # pylint: disable=attribute-defined-outside-init p.height = height # pylint: disable=attribute-defined-outside-init @@ -128,7 +130,6 @@ def control_run(units: List[unit.Unit], inputs, inits, unit_type: str, is_genera del p.width del p.height - t0 = time.time() for u in units: if not u.enabled or u.type != unit_type: @@ -364,9 +365,9 @@ def control_run(units: List[unit.Unit], inputs, inits, unit_type: str, is_genera processed_image = Image.fromarray(processed_image) if unit_type == 'controlnet' and input_type == 1: # Init image same as control + p.task_args['image'] = input_image p.task_args['control_image'] = p.image p.task_args['strength'] = p.denoising_strength - p.task_args['image'] = input_image elif unit_type == 'controlnet' and input_type == 2: # Separate init image p.task_args['control_image'] = p.image p.task_args['strength'] = p.denoising_strength @@ -394,8 +395,10 @@ def control_run(units: List[unit.Unit], inputs, inits, unit_type: str, is_genera else: pipe = sd_models.set_diffuser_pipe(shared.sd_model, sd_models.DiffusersTaskType.TEXT_2_IMAGE) elif unit_type == 'reference': + p.is_control = True pipe = sd_models.set_diffuser_pipe(shared.sd_model, sd_models.DiffusersTaskType.TEXT_2_IMAGE) else: # actual control + p.is_control = True if 'control_image' in p.task_args: pipe = sd_models.set_diffuser_pipe(shared.sd_model, sd_models.DiffusersTaskType.IMAGE_2_IMAGE) # only controlnet supports img2img else: diff --git a/modules/processing.py b/modules/processing.py index 7710117fe..b2ea8c175 100644 --- a/modules/processing.py +++ b/modules/processing.py @@ -179,6 +179,7 @@ def __init__(self, sd_model=None, outpath_samples=None, outpath_grids=None, prom self.all_subseeds = None self.clip_skip = clip_skip self.iteration = 0 + self.is_control = False self.is_hr_pass = False self.is_refiner_pass = False self.hr_force = False @@ -797,20 +798,9 @@ def validate_sample(tensor): return cast -def process_images_inner(p: StableDiffusionProcessing) -> Processed: - """this is the main loop that both txt2img and img2img use; it calls func_init once inside all the scopes and func_sample once per batch""" - - if type(p.prompt) == list: - assert len(p.prompt) > 0 - else: - assert p.prompt is not None - +def process_init(p: StableDiffusionProcessing): seed = get_fixed_seed(p.seed) subseed = get_fixed_seed(p.subseed) - if shared.backend == shared.Backend.ORIGINAL: - modules.sd_hijack.model_hijack.apply_circular(p.tiling) - modules.sd_hijack.model_hijack.clear_comments() - comments = {} if type(p.prompt) == list: p.all_prompts = [shared.prompt_styles.apply_styles_to_prompt(x, p.styles) for x in p.prompt] else: @@ -827,15 +817,32 @@ def process_images_inner(p: StableDiffusionProcessing) -> Processed: p.all_subseeds = subseed else: p.all_subseeds = [int(subseed) + x for x in range(len(p.all_prompts))] - if os.path.exists(shared.opts.embeddings_dir) and not p.do_not_reload_embeddings and shared.backend == shared.Backend.ORIGINAL: - modules.sd_hijack.model_hijack.embedding_db.load_textual_inversion_embeddings(force_reload=False) - if p.scripts is not None and isinstance(p.scripts, modules.scripts.ScriptRunner): - p.scripts.process(p) + + +def process_images_inner(p: StableDiffusionProcessing) -> Processed: + """this is the main loop that both txt2img and img2img use; it calls func_init once inside all the scopes and func_sample once per batch""" + + if type(p.prompt) == list: + assert len(p.prompt) > 0 + else: + assert p.prompt is not None + + if shared.backend == shared.Backend.ORIGINAL: + modules.sd_hijack.model_hijack.apply_circular(p.tiling) + modules.sd_hijack.model_hijack.clear_comments() + comments = {} infotexts = [] output_images = [] cached_uc = [None, None] cached_c = [None, None] + process_init(p) + if os.path.exists(shared.opts.embeddings_dir) and not p.do_not_reload_embeddings and shared.backend == shared.Backend.ORIGINAL: + modules.sd_hijack.model_hijack.embedding_db.load_textual_inversion_embeddings(force_reload=False) + if p.scripts is not None and isinstance(p.scripts, modules.scripts.ScriptRunner): + p.scripts.process(p) + + def get_conds_with_caching(function, required_prompts, steps, cache): if cache[0] is not None and (required_prompts, steps) == cache[0]: return cache[1] diff --git a/modules/processing_diffusers.py b/modules/processing_diffusers.py index 2415d2cde..c97c81c8b 100644 --- a/modules/processing_diffusers.py +++ b/modules/processing_diffusers.py @@ -9,14 +9,13 @@ import modules.shared as shared import modules.sd_samplers as sd_samplers import modules.sd_models as sd_models -import modules.sd_vae as sd_vae -import modules.taesd.sd_vae_taesd as sd_vae_taesd import modules.images as images import modules.errors as errors from modules.processing import StableDiffusionProcessing, create_random_tensors import modules.prompt_parser_diffusers as prompt_parser_diffusers from modules.sd_hijack_hypertile import hypertile_set from modules.processing_correction import correction_callback +from modules.processing_vae import vae_encode, vae_decode debug = shared.log.trace if os.environ.get('SD_DIFFUSERS_DEBUG', None) is not None else lambda *args, **kwargs: None @@ -115,113 +114,6 @@ def diffusers_callback(_pipe, step: int, timestep: int, kwargs: dict): shared.profiler.step() return kwargs - def full_vae_decode(latents, model): - t0 = time.time() - if shared.opts.diffusers_move_unet and not getattr(model, 'has_accelerate', False) and hasattr(model, 'unet'): - shared.log.debug('Moving to CPU: model=UNet') - unet_device = model.unet.device - model.unet.to(devices.cpu) - devices.torch_gc() - if not shared.cmd_opts.lowvram and not shared.opts.diffusers_seq_cpu_offload and hasattr(model, 'vae'): - model.vae.to(devices.device) - latents.to(model.vae.device) - - upcast = (model.vae.dtype == torch.float16) and getattr(model.vae.config, 'force_upcast', False) and hasattr(model, 'upcast_vae') - if upcast: # this is done by diffusers automatically if output_type != 'latent' - model.upcast_vae() - latents = latents.to(next(iter(model.vae.post_quant_conv.parameters())).dtype) - - decoded = model.vae.decode(latents / model.vae.config.scaling_factor, return_dict=False)[0] - # Downcast VAE after OpenVINO compile - if shared.opts.cuda_compile and shared.opts.cuda_compile_backend == "openvino_fx" and shared.compiled_model_state.first_pass_vae: - shared.compiled_model_state.first_pass_vae = False - if hasattr(shared.sd_model, "vae"): - shared.sd_model.vae.to(dtype=torch.float8_e4m3fn) - devices.torch_gc(force=True) - if shared.opts.diffusers_move_unet and not getattr(model, 'has_accelerate', False) and hasattr(model, 'unet'): - model.unet.to(unet_device) - t1 = time.time() - shared.log.debug(f'VAE decode: name={sd_vae.loaded_vae_file if sd_vae.loaded_vae_file is not None else "baked"} dtype={model.vae.dtype} upcast={upcast} images={latents.shape[0]} latents={latents.shape} time={round(t1-t0, 3)}') - return decoded - - def full_vae_encode(image, model): - shared.log.debug(f'VAE encode: name={sd_vae.loaded_vae_file if sd_vae.loaded_vae_file is not None else "baked"} dtype={model.vae.dtype} upcast={model.vae.config.get("force_upcast", None)}') - if shared.opts.diffusers_move_unet and not getattr(model, 'has_accelerate', False) and hasattr(model, 'unet'): - shared.log.debug('Moving to CPU: model=UNet') - unet_device = model.unet.device - model.unet.to(devices.cpu) - devices.torch_gc() - if not shared.cmd_opts.lowvram and not shared.opts.diffusers_seq_cpu_offload and hasattr(model, 'vae'): - model.vae.to(devices.device) - encoded = model.vae.encode(image.to(model.vae.device, model.vae.dtype)).latent_dist.sample() - if shared.opts.diffusers_move_unet and not getattr(model, 'has_accelerate', False) and hasattr(model, 'unet'): - model.unet.to(unet_device) - return encoded - - def taesd_vae_decode(latents): - shared.log.debug(f'VAE decode: name=TAESD images={len(latents)} latents={latents.shape}') - if len(latents) == 0: - return [] - decoded = torch.zeros((len(latents), 3, latents.shape[2] * 8, latents.shape[3] * 8), dtype=devices.dtype_vae, device=devices.device) - for i in range(latents.shape[0]): - decoded[i] = sd_vae_taesd.decode(latents[i]) - return decoded - - def taesd_vae_encode(image): - shared.log.debug(f'VAE encode: name=TAESD image={image.shape}') - encoded = sd_vae_taesd.encode(image) - return encoded - - def vae_decode(latents, model, output_type='np', full_quality=True): - t0 = time.time() - prev_job = shared.state.job - shared.state.job = 'vae' - if not torch.is_tensor(latents): # already decoded - return latents - if latents.shape[0] == 0: - shared.log.error(f'VAE nothing to decode: {latents.shape}') - return [] - if shared.state.interrupted or shared.state.skipped: - return [] - if not hasattr(model, 'vae'): - shared.log.error('VAE not found in model') - return [] - if latents.shape[0] == 4 and latents.shape[1] != 4: # likely animatediff latent - latents = latents.permute(1, 0, 2, 3) - if len(latents.shape) == 3: # lost a batch dim in hires - latents = latents.unsqueeze(0) - if full_quality: - decoded = full_vae_decode(latents=latents, model=shared.sd_model) - else: - decoded = taesd_vae_decode(latents=latents) - # TODO validate decoded sample diffusers - # decoded = validate_sample(decoded) - if hasattr(model, 'image_processor'): - imgs = model.image_processor.postprocess(decoded, output_type=output_type) - else: - import diffusers - image_processor = diffusers.image_processor.VaeImageProcessor() - imgs = image_processor.postprocess(decoded, output_type=output_type) - shared.state.job = prev_job - if shared.cmd_opts.profile: - t1 = time.time() - shared.log.debug(f'Profile: VAE decode: {t1-t0:.2f}') - return imgs - - def vae_encode(image, model, full_quality=True): # pylint: disable=unused-variable - if shared.state.interrupted or shared.state.skipped: - return [] - if not hasattr(model, 'vae'): - shared.log.error('VAE not found in model') - return [] - tensor = TF.to_tensor(image.convert("RGB")).unsqueeze(0).to(devices.device, devices.dtype_vae) - if full_quality: - tensor = tensor * 2 - 1 - latents = full_vae_encode(image=tensor, model=shared.sd_model) - else: - latents = taesd_vae_encode(image=tensor) - return latents - def fix_prompts(prompts, negative_prompts, prompts_2, negative_prompts_2): if type(prompts) is str: prompts = [prompts] @@ -480,7 +372,8 @@ def update_sampler(sd_model, second_pass=False): shared.sd_model.to(devices.device) # pipeline type is set earlier in processing, but check for sanity - if sd_models.get_diffusers_task(shared.sd_model) != sd_models.DiffusersTaskType.TEXT_2_IMAGE and len(getattr(p, 'init_images' ,[])) == 0: + has_images = len(getattr(p, 'init_images' ,[])) > 0 or getattr(p, 'is_control', False) is True + if sd_models.get_diffusers_task(shared.sd_model) != sd_models.DiffusersTaskType.TEXT_2_IMAGE and not has_images: shared.sd_model = sd_models.set_diffuser_pipe(shared.sd_model, sd_models.DiffusersTaskType.TEXT_2_IMAGE) # reset pipeline if hasattr(shared.sd_model, 'unet') and hasattr(shared.sd_model.unet, 'config') and hasattr(shared.sd_model.unet.config, 'in_channels') and shared.sd_model.unet.config.in_channels == 9: shared.sd_model = sd_models.set_diffuser_pipe(shared.sd_model, sd_models.DiffusersTaskType.INPAINTING) # force pipeline diff --git a/modules/processing_vae.py b/modules/processing_vae.py new file mode 100644 index 000000000..5da449469 --- /dev/null +++ b/modules/processing_vae.py @@ -0,0 +1,147 @@ +import os +import time +import torch +import torchvision.transforms.functional as TF +from modules import shared, devices, sd_vae +import modules.taesd.sd_vae_taesd as sd_vae_taesd + + +debug = shared.log.trace if os.environ.get('SD_VAE_DEBUG', None) is not None else lambda *args, **kwargs: None +debug('Trace: VAE') + + +def create_latents(image, p, dtype=None, device=None): + from modules.processing import create_random_tensors + from PIL import Image + if image is None: + return image + elif isinstance(image, Image.Image): + latents = vae_encode(image, model=shared.sd_model, full_quality=p.full_quality) + elif isinstance(image, list): + latents = [vae_encode(i, model=shared.sd_model, full_quality=p.full_quality).squeeze(dim=0) for i in image] + latents = torch.stack(latents, dim=0).to(shared.device) + else: + shared.log.warning(f'Latents: input type: {type(image)} {image}') + return image + noise = p.denoising_strength * create_random_tensors(latents.shape[1:], seeds=p.all_seeds, subseeds=p.all_subseeds, subseed_strength=p.subseed_strength, p=p) + latents = (1 - p.denoising_strength) * latents + noise + if dtype is not None: + latents = latents.to(dtype=dtype) + if device is not None: + latents = latents.to(device=device) + return latents + + +def full_vae_decode(latents, model): + t0 = time.time() + if shared.opts.diffusers_move_unet and not getattr(model, 'has_accelerate', False) and hasattr(model, 'unet'): + shared.log.debug('Moving to CPU: model=UNet') + unet_device = model.unet.device + model.unet.to(devices.cpu) + devices.torch_gc() + if not shared.cmd_opts.lowvram and not shared.opts.diffusers_seq_cpu_offload and hasattr(model, 'vae'): + model.vae.to(devices.device) + latents.to(model.vae.device) + + upcast = (model.vae.dtype == torch.float16) and getattr(model.vae.config, 'force_upcast', False) and hasattr(model, 'upcast_vae') + if upcast: # this is done by diffusers automatically if output_type != 'latent' + model.upcast_vae() + latents = latents.to(next(iter(model.vae.post_quant_conv.parameters())).dtype) + + decoded = model.vae.decode(latents / model.vae.config.scaling_factor, return_dict=False)[0] + + # Downcast VAE after OpenVINO compile + if shared.opts.cuda_compile and shared.opts.cuda_compile_backend == "openvino_fx" and shared.compiled_model_state.first_pass_vae: + shared.compiled_model_state.first_pass_vae = False + if hasattr(shared.sd_model, "vae"): + shared.sd_model.vae.to(dtype=torch.float8_e4m3fn) + devices.torch_gc(force=True) + + if shared.opts.diffusers_move_unet and not getattr(model, 'has_accelerate', False) and hasattr(model, 'unet'): + model.unet.to(unet_device) + t1 = time.time() + debug(f'VAE decode: name={sd_vae.loaded_vae_file if sd_vae.loaded_vae_file is not None else "baked"} dtype={model.vae.dtype} upcast={upcast} images={latents.shape[0]} latents={latents.shape} time={round(t1-t0, 3)}') + return decoded + + +def full_vae_encode(image, model): + debug(f'VAE encode: name={sd_vae.loaded_vae_file if sd_vae.loaded_vae_file is not None else "baked"} dtype={model.vae.dtype} upcast={model.vae.config.get("force_upcast", None)}') + if shared.opts.diffusers_move_unet and not getattr(model, 'has_accelerate', False) and hasattr(model, 'unet'): + debug('Moving to CPU: model=UNet') + unet_device = model.unet.device + model.unet.to(devices.cpu) + devices.torch_gc() + if not shared.cmd_opts.lowvram and not shared.opts.diffusers_seq_cpu_offload and hasattr(model, 'vae'): + model.vae.to(devices.device) + encoded = model.vae.encode(image.to(model.vae.device, model.vae.dtype)).latent_dist.sample() + if shared.opts.diffusers_move_unet and not getattr(model, 'has_accelerate', False) and hasattr(model, 'unet'): + model.unet.to(unet_device) + return encoded + + +def taesd_vae_decode(latents): + debug(f'VAE decode: name=TAESD images={len(latents)} latents={latents.shape}') + if len(latents) == 0: + return [] + decoded = torch.zeros((len(latents), 3, latents.shape[2] * 8, latents.shape[3] * 8), dtype=devices.dtype_vae, device=devices.device) + for i in range(latents.shape[0]): + decoded[i] = sd_vae_taesd.decode(latents[i]) + return decoded + + +def taesd_vae_encode(image): + debug(f'VAE encode: name=TAESD image={image.shape}') + encoded = sd_vae_taesd.encode(image) + return encoded + + +def vae_decode(latents, model, output_type='np', full_quality=True): + t0 = time.time() + prev_job = shared.state.job + shared.state.job = 'vae' + if not torch.is_tensor(latents): # already decoded + return latents + if latents.shape[0] == 0: + shared.log.error(f'VAE nothing to decode: {latents.shape}') + return [] + if shared.state.interrupted or shared.state.skipped: + return [] + if not hasattr(model, 'vae'): + shared.log.error('VAE not found in model') + return [] + if latents.shape[0] == 4 and latents.shape[1] != 4: # likely animatediff latent + latents = latents.permute(1, 0, 2, 3) + if len(latents.shape) == 3: # lost a batch dim in hires + latents = latents.unsqueeze(0) + if full_quality: + decoded = full_vae_decode(latents=latents, model=shared.sd_model) + else: + decoded = taesd_vae_decode(latents=latents) + # TODO validate decoded sample diffusers + # decoded = validate_sample(decoded) + if hasattr(model, 'image_processor'): + imgs = model.image_processor.postprocess(decoded, output_type=output_type) + else: + import diffusers + image_processor = diffusers.image_processor.VaeImageProcessor() + imgs = image_processor.postprocess(decoded, output_type=output_type) + shared.state.job = prev_job + if shared.cmd_opts.profile: + t1 = time.time() + shared.log.debug(f'Profile: VAE decode: {t1-t0:.2f}') + return imgs + + +def vae_encode(image, model, full_quality=True): # pylint: disable=unused-variable + if shared.state.interrupted or shared.state.skipped: + return [] + if not hasattr(model, 'vae'): + shared.log.error('VAE not found in model') + return [] + tensor = TF.to_tensor(image.convert("RGB")).unsqueeze(0).to(devices.device, devices.dtype_vae) + if full_quality: + tensor = tensor * 2 - 1 + latents = full_vae_encode(image=tensor, model=shared.sd_model) + else: + latents = taesd_vae_encode(image=tensor) + return latents diff --git a/modules/ui.py b/modules/ui.py index a47556115..87b1e0553 100644 --- a/modules/ui.py +++ b/modules/ui.py @@ -908,6 +908,8 @@ def select_img2img_tab(tab): from modules import ui_control ui_control.create_ui() timer.startup.record("ui-control") + else: + control_interface = None with gr.Blocks(analytics_enabled=False) as extras_interface: from modules import ui_postprocessing @@ -1129,15 +1131,14 @@ def reload_sd_weights(): timer.startup.record("ui-settings") - interfaces = [ - (txt2img_interface, "Text", "txt2img"), - (img2img_interface, "Image", "img2img"), - (control_interface, "Control", "control"), - (extras_interface, "Process", "process"), - (train_interface, "Train", "train"), - (models_interface, "Models", "models"), - (interrogate_interface, "Interrogate", "interrogate"), - ] + interfaces = [] + interfaces += [(txt2img_interface, "Text", "txt2img")] + interfaces += [(img2img_interface, "Image", "img2img")] + interfaces += [(control_interface, "Control", "control")] if control_interface is not None else [] + interfaces += [(extras_interface, "Process", "process")] + interfaces += [(train_interface, "Train", "train")] + interfaces += [(models_interface, "Models", "models")] + interfaces += [(interrogate_interface, "Interrogate", "interrogate")] interfaces += script_callbacks.ui_tabs_callback() interfaces += [(settings_interface, "System", "system")] diff --git a/wiki b/wiki index 9994450f3..62c496b6c 160000 --- a/wiki +++ b/wiki @@ -1 +1 @@ -Subproject commit 9994450f36413a1a72500c397ff39e024aab13a1 +Subproject commit 62c496b6cf936eec933d58d912c47372212c1281 From 1bbdab74cfa23c44dc689f39c34d95aa4d3b2a2c Mon Sep 17 00:00:00 2001 From: Disty0 Date: Sat, 23 Dec 2023 19:22:57 +0300 Subject: [PATCH 110/143] OpenVINO fix cache loading --- modules/intel/openvino/__init__.py | 70 +++++++++++++++--------------- modules/processing_diffusers.py | 39 ++++++++--------- modules/processing_vae.py | 2 +- 3 files changed, 53 insertions(+), 58 deletions(-) diff --git a/modules/intel/openvino/__init__.py b/modules/intel/openvino/__init__.py index 70d3abaa6..84e4edf43 100644 --- a/modules/intel/openvino/__init__.py +++ b/modules/intel/openvino/__init__.py @@ -358,41 +358,41 @@ def openvino_fx(subgraph, example_inputs): maybe_fs_cached_name = cached_model_name(model_hash_str + "_fs", get_device(), example_inputs, shared.opts.openvino_cache_path) if os.path.isfile(maybe_fs_cached_name + ".xml") and os.path.isfile(maybe_fs_cached_name + ".bin"): - if (shared.compiled_model_state.cn_model != [] and str(shared.compiled_model_state.cn_model) in maybe_fs_cached_name): - example_inputs_reordered = [] - if (os.path.isfile(maybe_fs_cached_name + ".txt")): - f = open(maybe_fs_cached_name + ".txt", "r") - for input_data in example_inputs: - shape = f.readline() - if (str(input_data.size()) != shape): - for idx1, input_data1 in enumerate(example_inputs): - if (str(input_data1.size()).strip() == str(shape).strip()): - example_inputs_reordered.append(example_inputs[idx1]) - example_inputs = example_inputs_reordered - - # Deleting unused subgraphs doesn't do anything, so we cast it down to fp8 - subgraph = subgraph.to(dtype=torch.float8_e4m3fn) - - # Model is fully supported and already cached. Run the cached OV model directly. - compiled_model = openvino_compile_cached_model(maybe_fs_cached_name, *example_inputs) - - def _call(*args): - if (shared.compiled_model_state.cn_model != [] and str(shared.compiled_model_state.cn_model) in maybe_fs_cached_name): - args_reordered = [] - if (os.path.isfile(maybe_fs_cached_name + ".txt")): - f = open(maybe_fs_cached_name + ".txt", "r") - for input_data in args: - shape = f.readline() - if (str(input_data.size()) != shape): - for idx1, input_data1 in enumerate(args): - if (str(input_data1.size()).strip() == str(shape).strip()): - args_reordered.append(args[idx1]) - args = args_reordered - - res = execute_cached(compiled_model, *args) - shared.compiled_model_state.partition_id = shared.compiled_model_state.partition_id + 1 - return res - return _call + example_inputs_reordered = [] + if (os.path.isfile(maybe_fs_cached_name + ".txt")): + f = open(maybe_fs_cached_name + ".txt", "r") + for input_data in example_inputs: + shape = f.readline() + if (str(input_data.size()) != shape): + for idx1, input_data1 in enumerate(example_inputs): + if (str(input_data1.size()).strip() == str(shape).strip()): + example_inputs_reordered.append(example_inputs[idx1]) + example_inputs = example_inputs_reordered + + # Deleting unused subgraphs doesn't do anything, so we cast it down to fp8 + subgraph = subgraph.to(dtype=torch.float8_e4m3fn) + devices.torch_gc(force=True) + + # Model is fully supported and already cached. Run the cached OV model directly. + compiled_model = openvino_compile_cached_model(maybe_fs_cached_name, *example_inputs) + + def _call(*args): + if (shared.compiled_model_state.cn_model != [] and str(shared.compiled_model_state.cn_model) in maybe_fs_cached_name): + args_reordered = [] + if (os.path.isfile(maybe_fs_cached_name + ".txt")): + f = open(maybe_fs_cached_name + ".txt", "r") + for input_data in args: + shape = f.readline() + if (str(input_data.size()) != shape): + for idx1, input_data1 in enumerate(args): + if (str(input_data1.size()).strip() == str(shape).strip()): + args_reordered.append(args[idx1]) + args = args_reordered + + res = execute_cached(compiled_model, *args) + shared.compiled_model_state.partition_id = shared.compiled_model_state.partition_id + 1 + return res + return _call else: os.environ.setdefault('OPENVINO_TORCH_MODEL_CACHING', "0") maybe_fs_cached_name = None diff --git a/modules/processing_diffusers.py b/modules/processing_diffusers.py index c97c81c8b..702a8e730 100644 --- a/modules/processing_diffusers.py +++ b/modules/processing_diffusers.py @@ -345,10 +345,20 @@ def recompile_model(hires=False): shared.compiled_model_state.height = compile_height shared.compiled_model_state.width = compile_width shared.compiled_model_state.batch_size = p.batch_size - else: - pass #Can be implemented for TensorRT or Olive - else: - pass #Do nothing if compile is disabled + + # Downcast UNET after OpenVINO compile + def downcast_openvino(op="base"): + if shared.opts.cuda_compile and shared.opts.cuda_compile_backend == "openvino_fx": + if shared.compiled_model_state.first_pass and op == "base": + shared.compiled_model_state.first_pass = False + if hasattr(shared.sd_model, "unet"): + shared.sd_model.unet.to(dtype=torch.float8_e4m3fn) + devices.torch_gc(force=True) + if shared.compiled_model_state.first_pass_refiner and op == "refiner": + shared.compiled_model_state.first_pass_refiner = False + if hasattr(shared.sd_refiner, "unet"): + shared.sd_refiner.unet.to(dtype=torch.float8_e4m3fn) + devices.torch_gc(force=True) def update_sampler(sd_model, second_pass=False): sampler_selection = p.latent_sampler if second_pass else p.sampler_name @@ -448,12 +458,7 @@ def calculate_refiner_steps(): try: t0 = time.time() output = shared.sd_model(**base_args) # pylint: disable=not-callable - # Downcast UNET after OpenVINO compile - if shared.opts.cuda_compile and shared.opts.cuda_compile_backend == "openvino_fx" and shared.compiled_model_state.first_pass: - shared.compiled_model_state.first_pass = False - if hasattr(shared.sd_model, "unet"): - shared.sd_model.unet.to(dtype=torch.float8_e4m3fn) - devices.torch_gc(force=True) + downcast_openvino(op="base") if shared.cmd_opts.profile: t1 = time.time() shared.log.debug(f'Profile: pipeline call: {t1-t0:.2f}') @@ -517,12 +522,7 @@ def calculate_refiner_steps(): shared.state.sampling_steps = hires_args['num_inference_steps'] try: output = shared.sd_model(**hires_args) # pylint: disable=not-callable - # Downcast UNET after OpenVINO compile - if shared.opts.cuda_compile and shared.opts.cuda_compile_backend == "openvino_fx" and shared.compiled_model_state.first_pass: - shared.compiled_model_state.first_pass = False - if hasattr(shared.sd_model, "unet"): - shared.sd_model.unet.to(dtype=torch.float8_e4m3fn) - devices.torch_gc(force=True) + downcast_openvino(op="base") except AssertionError as e: shared.log.info(e) p.init_images = [] @@ -582,12 +582,7 @@ def calculate_refiner_steps(): try: shared.sd_refiner.register_to_config(requires_aesthetics_score=shared.opts.diffusers_aesthetics_score) refiner_output = shared.sd_refiner(**refiner_args) # pylint: disable=not-callable - # Downcast UNET after OpenVINO compile - if shared.opts.cuda_compile and shared.opts.cuda_compile_backend == "openvino_fx" and shared.compiled_model_state.first_pass_refiner: - shared.compiled_model_state.first_pass_refiner = False - if hasattr(shared.sd_refiner, "unet"): - shared.sd_refiner.unet.to(dtype=torch.float8_e4m3fn) - devices.torch_gc(force=True) + downcast_openvino(op="refiner") except AssertionError as e: shared.log.info(e) diff --git a/modules/processing_vae.py b/modules/processing_vae.py index 5da449469..bd9425de7 100644 --- a/modules/processing_vae.py +++ b/modules/processing_vae.py @@ -54,7 +54,7 @@ def full_vae_decode(latents, model): if shared.opts.cuda_compile and shared.opts.cuda_compile_backend == "openvino_fx" and shared.compiled_model_state.first_pass_vae: shared.compiled_model_state.first_pass_vae = False if hasattr(shared.sd_model, "vae"): - shared.sd_model.vae.to(dtype=torch.float8_e4m3fn) + model.vae.to(dtype=torch.float8_e4m3fn) devices.torch_gc(force=True) if shared.opts.diffusers_move_unet and not getattr(model, 'has_accelerate', False) and hasattr(model, 'unet'): From 1d383588999562caa71ce35269fabd98362e0267 Mon Sep 17 00:00:00 2001 From: Vladimir Mandic Date: Sat, 23 Dec 2023 12:50:30 -0500 Subject: [PATCH 111/143] control add ip-adapter --- modules/control/adapters.py | 5 +- modules/control/controlnets.py | 5 +- modules/control/controlnetsxs.py | 5 +- modules/control/ipadapter.py | 90 ++++++++++++++++++++++++++++++++ modules/control/reference.py | 5 +- modules/control/run.py | 18 +++++-- modules/processing.py | 4 +- modules/sd_models.py | 4 ++ modules/ui_control.py | 28 ++++++++-- wiki | 2 +- 10 files changed, 150 insertions(+), 16 deletions(-) create mode 100644 modules/control/ipadapter.py diff --git a/modules/control/adapters.py b/modules/control/adapters.py index 12ed28907..19030d2cb 100644 --- a/modules/control/adapters.py +++ b/modules/control/adapters.py @@ -124,6 +124,8 @@ def __init__(self, adapter: T2IAdapter | list[T2IAdapter], pipeline: StableDiffu tokenizer_2=pipeline.tokenizer_2, unet=pipeline.unet, scheduler=pipeline.scheduler, + image_encoder=getattr(pipeline, 'image_encoder', None), + feature_extractor=getattr(pipeline, 'feature_extractor', None), adapter=adapter, ).to(pipeline.device) elif isinstance(pipeline, StableDiffusionPipeline): @@ -133,9 +135,10 @@ def __init__(self, adapter: T2IAdapter | list[T2IAdapter], pipeline: StableDiffu tokenizer=pipeline.tokenizer, unet=pipeline.unet, scheduler=pipeline.scheduler, + image_encoder=getattr(pipeline, 'image_encoder', None), + feature_extractor=getattr(pipeline, 'feature_extractor', None), requires_safety_checker=False, safety_checker=None, - feature_extractor=None, adapter=adapter, ).to(pipeline.device) else: diff --git a/modules/control/controlnets.py b/modules/control/controlnets.py index 0230fe773..0e9816082 100644 --- a/modules/control/controlnets.py +++ b/modules/control/controlnets.py @@ -139,6 +139,8 @@ def __init__(self, controlnet: ControlNetModel | list[ControlNetModel], pipeline tokenizer_2=pipeline.tokenizer_2, unet=pipeline.unet, scheduler=pipeline.scheduler, + image_encoder=getattr(pipeline, 'image_encoder', None), + feature_extractor=getattr(pipeline, 'feature_extractor', None), controlnet=controlnet, # can be a list ).to(pipeline.device) elif isinstance(pipeline, StableDiffusionPipeline): @@ -148,9 +150,10 @@ def __init__(self, controlnet: ControlNetModel | list[ControlNetModel], pipeline tokenizer=pipeline.tokenizer, unet=pipeline.unet, scheduler=pipeline.scheduler, + image_encoder=getattr(pipeline, 'image_encoder', None), + feature_extractor=getattr(pipeline, 'feature_extractor', None), requires_safety_checker=False, safety_checker=None, - feature_extractor=None, controlnet=controlnet, # can be a list ).to(pipeline.device) else: diff --git a/modules/control/controlnetsxs.py b/modules/control/controlnetsxs.py index 7096902a7..630f06df3 100644 --- a/modules/control/controlnetsxs.py +++ b/modules/control/controlnetsxs.py @@ -131,6 +131,8 @@ def __init__(self, controlnet: ControlNetXSModel | list[ControlNetXSModel], pipe tokenizer_2=pipeline.tokenizer_2, unet=pipeline.unet, scheduler=pipeline.scheduler, + image_encoder=getattr(pipeline, 'image_encoder', None), + feature_extractor=getattr(pipeline, 'feature_extractor', None), controlnet=controlnet, # can be a list ).to(pipeline.device) elif isinstance(pipeline, StableDiffusionPipeline): @@ -140,9 +142,10 @@ def __init__(self, controlnet: ControlNetXSModel | list[ControlNetXSModel], pipe tokenizer=pipeline.tokenizer, unet=pipeline.unet, scheduler=pipeline.scheduler, + image_encoder=getattr(pipeline, 'image_encoder', None), + feature_extractor=getattr(pipeline, 'feature_extractor', None), requires_safety_checker=False, safety_checker=None, - feature_extractor=None, controlnet=controlnet, # can be a list ).to(pipeline.device) else: diff --git a/modules/control/ipadapter.py b/modules/control/ipadapter.py new file mode 100644 index 000000000..ed12ca63c --- /dev/null +++ b/modules/control/ipadapter.py @@ -0,0 +1,90 @@ +import time +from PIL import Image +from modules import shared, processing, devices + + +image_encoder = None +image_encoder_type = None +loaded = None +ADAPTERS = [ + 'none', + 'ip-adapter_sd15', + 'ip-adapter_sd15_light', + 'ip-adapter-plus_sd15', + 'ip-adapter-plus-face_sd15', + 'ip-adapter-full-face_sd15', + # 'models/ip-adapter_sd15_vit-G', # RuntimeError: mat1 and mat2 shapes cannot be multiplied (2x1024 and 1280x3072) + 'ip-adapter_sdxl', + # 'sdxl_models/ip-adapter_sdxl_vit-h', + # 'sdxl_models/ip-adapter-plus_sdxl_vit-h', + # 'sdxl_models/ip-adapter-plus-face_sdxl_vit-h', +] + + +def apply_ip_adapter(pipe, p: processing.StableDiffusionProcessing, adapter, scale, image, reset=False): # pylint: disable=arguments-differ + from transformers import CLIPVisionModelWithProjection + # overrides + if hasattr(p, 'ip_adapter_name'): + adapter = p.ip_adapter_name + if hasattr(p, 'ip_adapter_scale'): + scale = p.ip_adapter_scale + if hasattr(p, 'ip_adapter_image'): + image = p.ip_adapter_image + # init code + global loaded, image_encoder, image_encoder_type # pylint: disable=global-statement + if pipe is None: + return + if shared.backend != shared.Backend.DIFFUSERS: + shared.log.warning('IP adapter: not in diffusers mode') + return False + if adapter == 'none': + if hasattr(pipe, 'set_ip_adapter_scale'): + pipe.set_ip_adapter_scale(0) + if loaded is not None: + shared.log.debug('IP adapter: unload attention processor') + pipe.unet.set_default_attn_processor() + pipe.unet.config.encoder_hid_dim_type = None + loaded = None + return False + if image is None: + image = Image.new('RGB', (512, 512), (0, 0, 0)) + if not hasattr(pipe, 'load_ip_adapter'): + shared.log.error(f'IP adapter: pipeline not supported: {pipe.__class__.__name__}') + return False + if getattr(pipe, 'image_encoder', None) is None or getattr(pipe, 'image_encoder', None) == (None, None): + if shared.sd_model_type == 'sd': + subfolder = 'models/image_encoder' + elif shared.sd_model_type == 'sdxl': + subfolder = 'sdxl_models/image_encoder' + else: + shared.log.error(f'IP adapter: unsupported model type: {shared.sd_model_type}') + return False + if image_encoder is None or image_encoder_type != shared.sd_model_type: + try: + image_encoder = CLIPVisionModelWithProjection.from_pretrained("h94/IP-Adapter", subfolder=subfolder, torch_dtype=devices.dtype, cache_dir=shared.opts.diffusers_dir, use_safetensors=True).to(devices.device) + image_encoder_type = shared.sd_model_type + except Exception as e: + shared.log.error(f'IP adapter: failed to load image encoder: {e}') + return False + pipe.image_encoder = image_encoder + + # main code + subfolder = 'models' if 'sd15' in adapter else 'sdxl_models' + if adapter != loaded or getattr(pipe.unet.config, 'encoder_hid_dim_type', None) is None or reset: + t0 = time.time() + if loaded is not None: + # shared.log.debug('IP adapter: reset attention processor') + pipe.unet.set_default_attn_processor() + loaded = None + else: + shared.log.debug('IP adapter: load attention processor') + pipe.load_ip_adapter("h94/IP-Adapter", subfolder=subfolder, weight_name=f'{adapter}.safetensors') + t1 = time.time() + shared.log.info(f'IP adapter load: adapter="{adapter}" scale={scale} image={image} time={t1-t0:.2f}') + loaded = adapter + else: + shared.log.debug(f'IP adapter cache: adapter="{adapter}" scale={scale} image={image}') + pipe.set_ip_adapter_scale(scale) + p.task_args['ip_adapter_image'] = p.batch_size * [image] + p.extra_generation_params["IP Adapter"] = f'{adapter}:{scale}' + return True diff --git a/modules/control/reference.py b/modules/control/reference.py index 8116274a7..32d8dcdd2 100644 --- a/modules/control/reference.py +++ b/modules/control/reference.py @@ -29,6 +29,8 @@ def __init__(self, pipeline: StableDiffusionXLPipeline | StableDiffusionPipeline tokenizer_2=pipeline.tokenizer_2, unet=pipeline.unet, scheduler=pipeline.scheduler, + image_encoder=getattr(pipeline, 'image_encoder', None), + feature_extractor=getattr(pipeline, 'feature_extractor', None), ).to(pipeline.device) elif isinstance(pipeline, StableDiffusionPipeline): self.pipeline = StableDiffusionReferencePipeline( @@ -37,9 +39,10 @@ def __init__(self, pipeline: StableDiffusionXLPipeline | StableDiffusionPipeline tokenizer=pipeline.tokenizer, unet=pipeline.unet, scheduler=pipeline.scheduler, + image_encoder=getattr(pipeline, 'image_encoder', None), + feature_extractor=getattr(pipeline, 'feature_extractor', None), requires_safety_checker=False, safety_checker=None, - feature_extractor=None, ).to(pipeline.device) else: log.error(f'Control {what} pipeline: class={pipeline.__class__.__name__} unsupported model type') diff --git a/modules/control/run.py b/modules/control/run.py index e2dad2ea6..a80829e28 100644 --- a/modules/control/run.py +++ b/modules/control/run.py @@ -13,6 +13,7 @@ from modules.control import controlnetsxs # VisLearn ControlNet-XS from modules.control import adapters # TencentARC T2I-Adapter from modules.control import reference # ControlNet-Reference +from modules.control import ipadapter # IP-Adapter from modules import devices, shared, errors, processing, images, sd_models, sd_samplers @@ -67,6 +68,7 @@ def control_run(units: List[unit.Unit], inputs, inits, unit_type: str, is_genera resize_mode, resize_name, width, height, scale_by, selected_scale_tab, resize_time, denoising_strength, batch_count, batch_size, video_skip_frames, video_type, video_duration, video_loop, video_pad, video_interpolate, + ip_adapter, ip_scale, ip_image, ip_type, ): global pipe, original_pipeline # pylint: disable=global-statement debug(f'Control {unit_type}: input={inputs} init={inits} type={input_type}') @@ -246,6 +248,9 @@ def control_run(units: List[unit.Unit], inputs, inits, unit_type: str, is_genera shared.sd_model.to(shared.device) sd_models.copy_diffuser_options(shared.sd_model, original_pipeline) # copy options from original pipeline sd_models.set_diffuser_options(shared.sd_model) + if ipadapter.apply_ip_adapter(shared.sd_model, p, ip_adapter, ip_scale, ip_image, reset=True): + original_pipeline.feature_extractor = shared.sd_model.feature_extractor + original_pipeline.image_encoder = shared.sd_model.image_encoder try: with devices.inference_context(): @@ -377,6 +382,9 @@ def control_run(units: List[unit.Unit], inputs, inits, unit_type: str, is_genera else: p.task_args['image'] = init_image + if ip_type == 1 and ip_adapter != 'none': + p.task_args['ip_adapter_image'] = input_image + if is_generator: image_txt = f'{processed_image.width}x{processed_image.height}' if processed_image is not None else 'None' msg = f'process | {index} of {frames if video is not None else len(inputs)} | {"Image" if video is None else "Frame"} {image_txt}' @@ -391,18 +399,18 @@ def control_run(units: List[unit.Unit], inputs, inits, unit_type: str, is_genera if not has_models and (unit_type == 'controlnet' or unit_type == 'adapter' or unit_type == 'xs'): # run in txt2img or img2img mode if processed_image is not None: p.init_images = [processed_image] - pipe = sd_models.set_diffuser_pipe(shared.sd_model, sd_models.DiffusersTaskType.IMAGE_2_IMAGE) + shared.sd_model = sd_models.set_diffuser_pipe(shared.sd_model, sd_models.DiffusersTaskType.IMAGE_2_IMAGE) else: - pipe = sd_models.set_diffuser_pipe(shared.sd_model, sd_models.DiffusersTaskType.TEXT_2_IMAGE) + shared.sd_model = sd_models.set_diffuser_pipe(shared.sd_model, sd_models.DiffusersTaskType.TEXT_2_IMAGE) elif unit_type == 'reference': p.is_control = True - pipe = sd_models.set_diffuser_pipe(shared.sd_model, sd_models.DiffusersTaskType.TEXT_2_IMAGE) + shared.sd_model = sd_models.set_diffuser_pipe(shared.sd_model, sd_models.DiffusersTaskType.TEXT_2_IMAGE) else: # actual control p.is_control = True if 'control_image' in p.task_args: - pipe = sd_models.set_diffuser_pipe(shared.sd_model, sd_models.DiffusersTaskType.IMAGE_2_IMAGE) # only controlnet supports img2img + shared.sd_model = sd_models.set_diffuser_pipe(shared.sd_model, sd_models.DiffusersTaskType.IMAGE_2_IMAGE) # only controlnet supports img2img else: - pipe = sd_models.set_diffuser_pipe(shared.sd_model, sd_models.DiffusersTaskType.TEXT_2_IMAGE) + shared.sd_model = sd_models.set_diffuser_pipe(shared.sd_model, sd_models.DiffusersTaskType.TEXT_2_IMAGE) # pipeline output = None diff --git a/modules/processing.py b/modules/processing.py index b2ea8c175..e2283b8ae 100644 --- a/modules/processing.py +++ b/modules/processing.py @@ -1261,9 +1261,9 @@ def mask_blur(self, value): self.mask_blur_y = value def init(self, all_prompts, all_seeds, all_subseeds): - if shared.backend == shared.Backend.DIFFUSERS and self.image_mask is not None: + if shared.backend == shared.Backend.DIFFUSERS and self.image_mask is not None and not self.is_control: shared.sd_model = modules.sd_models.set_diffuser_pipe(self.sd_model, modules.sd_models.DiffusersTaskType.INPAINTING) - elif shared.backend == shared.Backend.DIFFUSERS and self.image_mask is None: + elif shared.backend == shared.Backend.DIFFUSERS and self.image_mask is None and not self.is_control: shared.sd_model = modules.sd_models.set_diffuser_pipe(self.sd_model, modules.sd_models.DiffusersTaskType.IMAGE_2_IMAGE) if self.sampler_name == "PLMS": diff --git a/modules/sd_models.py b/modules/sd_models.py index 6fe672b4f..5d0dce124 100644 --- a/modules/sd_models.py +++ b/modules/sd_models.py @@ -1027,6 +1027,8 @@ def set_diffuser_pipe(pipe, new_pipe_type): sd_model_hash = getattr(pipe, "sd_model_hash", None) has_accelerate = getattr(pipe, "has_accelerate", None) embedding_db = getattr(pipe, "embedding_db", None) + image_encoder = getattr(pipe, "image_encoder", None) + feature_extractor = getattr(pipe, "feature_extractor", None) # TODO implement alternative diffusion pipelines """ @@ -1065,6 +1067,8 @@ def set_diffuser_pipe(pipe, new_pipe_type): new_pipe.sd_model_hash = sd_model_hash new_pipe.has_accelerate = has_accelerate new_pipe.embedding_db = embedding_db + new_pipe.image_encoder = image_encoder + new_pipe.feature_extractor = feature_extractor new_pipe.is_sdxl = True # pylint: disable=attribute-defined-outside-init # a1111 compatibility item new_pipe.is_sd2 = False # pylint: disable=attribute-defined-outside-init new_pipe.is_sd1 = False # pylint: disable=attribute-defined-outside-init diff --git a/modules/ui_control.py b/modules/ui_control.py index 7665843e0..607a78f7e 100644 --- a/modules/ui_control.py +++ b/modules/ui_control.py @@ -6,6 +6,7 @@ from modules.control import adapters # TencentARC T2I-Adapter from modules.control import processors # patrickvonplaten controlnet_aux from modules.control import reference # reference pipeline +from modules.control import ipadapter # reference pipeline from modules import errors, shared, progress, sd_samplers, ui, ui_components, ui_symbols, ui_common, generation_parameters_copypaste, call_queue from modules.ui_components import FormRow, FormGroup @@ -202,9 +203,14 @@ def create_ui(_blocks: gr.Blocks=None): with gr.Row(elem_id='control_settings'): with gr.Accordion(open=False, label="Input", elem_id="control_input", elem_classes=["small-accordion"]): - input_type = gr.Radio(label="Input type", choices=['Control only', 'Init image same as control', 'Separate init image'], value='Control only', type='index', elem_id='control_input_type') - denoising_strength = gr.Slider(minimum=0.01, maximum=0.99, step=0.01, label='Denoising strength', value=0.50, elem_id="control_denoising_strength") - show_preview = gr.Checkbox(label="Show preview", value=True, elem_id="control_show_preview") + with gr.Row(): + input_type = gr.Radio(label="Input type", choices=['Control only', 'Init image same as control', 'Separate init image'], value='Control only', type='index', elem_id='control_input_type') + with gr.Row(): + denoising_strength = gr.Slider(minimum=0.01, maximum=0.99, step=0.01, label='Denoising strength', value=0.50, elem_id="control_denoising_strength") + with gr.Row(): + show_ip = gr.Checkbox(label="Enable IP adapter", value=False, elem_id="control_show_ip") + with gr.Row(): + show_preview = gr.Checkbox(label="Show preview", value=False, elem_id="control_show_preview") resize_mode, resize_name, width, height, scale_by, selected_scale_tab, resize_time = ui.create_resize_inputs('control', [], time_selector=True, scale_visible=False, mode='Fixed') @@ -261,6 +267,17 @@ def create_ui(_blocks: gr.Blocks=None): init_batch = gr.File(label="Input", show_label=False, file_count='multiple', file_types=['image'], type='file', interactive=True, height=gr_height) with gr.Tab('Folder', id='init-folder') as tab_folder_init: init_folder = gr.File(label="Input", show_label=False, file_count='directory', file_types=['image'], type='file', interactive=True, height=gr_height) + with gr.Column(scale=9, elem_id='control-init-column', visible=False) as column_ip: + gr.HTML('IP Adapter

') + with gr.Tabs(elem_classes=['control-tabs'], elem_id='control-tab-ip'): + with gr.Tab('Image', id='init-image') as tab_image_init: + ip_image = gr.Image(label="Input", show_label=False, type="pil", source="upload", interactive=True, tool="editor", height=gr_height) + with gr.Row(): + ip_adapter = gr.Dropdown(label='Adapter', choices=ipadapter.ADAPTERS, value='none') + ip_scale = gr.Slider(label='Scale', minimum=0.0, maximum=1.0, step=0.01, value=0.5) + with gr.Row(): + ip_type = gr.Radio(label="Input type", choices=['Init image same as control', 'Separate init image'], value='Init image same as control', type='index', elem_id='control_ip_type') + ip_image.change(fn=lambda x: gr.update(value='Init image same as control' if x is None else 'Separate init image'), inputs=[ip_image], outputs=[ip_type]) with gr.Column(scale=9, elem_id='control-output-column', visible=True) as _column_output: gr.HTML('Output

') with gr.Tabs(elem_classes=['control-tabs'], elem_id='control-tab-output') as output_tabs: @@ -270,7 +287,7 @@ def create_ui(_blocks: gr.Blocks=None): output_image = gr.Image(label="Input", show_label=False, type="pil", interactive=False, tool="editor", height=gr_height) with gr.Tab('Video', id='out-video'): output_video = gr.Video(label="Input", show_label=False, height=gr_height) - with gr.Column(scale=9, elem_id='control-preview-column', visible=True) as column_preview: + with gr.Column(scale=9, elem_id='control-preview-column', visible=False) as column_preview: gr.HTML('Preview

') with gr.Tabs(elem_classes=['control-tabs'], elem_id='control-tab-preview'): with gr.Tab('Preview', id='preview-image') as tab_image: @@ -284,6 +301,7 @@ def create_ui(_blocks: gr.Blocks=None): if hasattr(ctrl, 'select'): ctrl.select(fn=select_input, inputs=inputs, outputs=outputs) show_preview.change(fn=lambda x: gr.update(visible=x), inputs=[show_preview], outputs=[column_preview]) + show_ip.change(fn=lambda x: gr.update(visible=x), inputs=[show_ip], outputs=[column_ip]) input_type.change(fn=lambda x: gr.update(visible=x == 2), inputs=[input_type], outputs=[column_init]) with gr.Tabs(elem_id='control-tabs') as _tabs_control_type: @@ -508,6 +526,7 @@ def create_ui(_blocks: gr.Blocks=None): resize_mode, resize_name, width, height, scale_by, selected_scale_tab, resize_time, denoising_strength, batch_count, batch_size, video_skip_frames, video_type, video_duration, video_loop, video_pad, video_interpolate, + ip_adapter, ip_scale, ip_image, ip_type, ] output_fields = [ preview_process, @@ -517,6 +536,7 @@ def create_ui(_blocks: gr.Blocks=None): result_txt, ] paste_fields = [] # TODO paste fields + control_dict = dict( fn=generate_click, _js="submit_control", diff --git a/wiki b/wiki index 62c496b6c..2b232b2ca 160000 --- a/wiki +++ b/wiki @@ -1 +1 @@ -Subproject commit 62c496b6cf936eec933d58d912c47372212c1281 +Subproject commit 2b232b2cab058b969acdea7c682252dbe4ca6cf1 From 7ab9ba07817310f1ad00593825407b73f12a4f59 Mon Sep 17 00:00:00 2001 From: Vladimir Mandic Date: Sat, 23 Dec 2023 12:57:12 -0500 Subject: [PATCH 112/143] hide variation seed in diffusers --- CHANGELOG.md | 4 ++-- modules/ui.py | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index f5a2485a6..57c09eda4 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,11 +1,11 @@ # Change Log for SD.Next -## Update for 2023-12-22 +## Update for 2023-12-23 *Note*: based on `diffusers==0.25.0.dev0` - **Control** - - native implementation of **ControlNet**, **ControlNet XS** and **T2I Adapters** + - native implementation of **ControlNet**, **ControlNet XS**, **T2I Adapters** and **IP Adapters** - top-level **Control** next to **Text** and **Image** generate - supports all variations of **SD15** and **SD-XL** models - supports *Text*, *Image*, *Batch* and *Video* processing diff --git a/modules/ui.py b/modules/ui.py index 87b1e0553..eddf1393d 100644 --- a/modules/ui.py +++ b/modules/ui.py @@ -165,7 +165,7 @@ def create_seed_inputs(tab, reuse_visible=True): seed = gr.Number(label='Initial seed', value=-1, elem_id=f"{tab}_seed", container=True) random_seed = ToolButton(ui_symbols.random, elem_id=f"{tab}_random_seed", label='Random seed') reuse_seed = ToolButton(ui_symbols.reuse, elem_id=f"{tab}_reuse_seed", label='Reuse seed', visible=reuse_visible) - with FormRow(visible=True, elem_id=f"{tab}_subseed_row", variant="compact"): + with FormRow(elem_id=f"{tab}_subseed_row", variant="compact", visible=shared.backend==shared.Backend.ORIGINAL): subseed = gr.Number(label='Variation', value=-1, elem_id=f"{tab}_subseed", container=True) random_subseed = ToolButton(ui_symbols.random, elem_id=f"{tab}_random_subseed") reuse_subseed = ToolButton(ui_symbols.reuse, elem_id=f"{tab}_reuse_subseed", visible=reuse_visible) From c3984b5550276bb2cfc9040d2d8945607b3c755e Mon Sep 17 00:00:00 2001 From: Vladimir Mandic Date: Sat, 23 Dec 2023 14:25:17 -0500 Subject: [PATCH 113/143] lora and vae loading error reporting --- extensions-builtin/Lora/networks.py | 4 ++-- modules/sd_vae.py | 34 ++++++++++++++++------------- 2 files changed, 21 insertions(+), 17 deletions(-) diff --git a/extensions-builtin/Lora/networks.py b/extensions-builtin/Lora/networks.py index 006eaf5bb..fb8b9e138 100644 --- a/extensions-builtin/Lora/networks.py +++ b/extensions-builtin/Lora/networks.py @@ -129,9 +129,9 @@ def load_network(name, network_on_disk): raise AssertionError(f"Could not find a module type (out of {', '.join([x.__class__.__name__ for x in module_types])}) that would accept those keys: {', '.join(weights.w)}") net.modules[key] = net_module if keys_failed_to_match: - shared.log.warning(f"LoRA unmatched keys: file={network_on_disk.filename} keys={len(keys_failed_to_match)}") + shared.log.warning(f"LoRA file={network_on_disk.filename} unmatched={len(keys_failed_to_match)} matched={len(matched_networks)}") if debug: - shared.log.debug(f"LoRA unmatched keys: file={network_on_disk.filename} keys={keys_failed_to_match}") + shared.log.debug(f"LoRA file={network_on_disk.filename} unmatched={keys_failed_to_match}") lora_cache[name] = net t1 = time.time() timer['load'] += t1 - t0 diff --git a/modules/sd_vae.py b/modules/sd_vae.py index 9e28e4c73..46a1e5a45 100644 --- a/modules/sd_vae.py +++ b/modules/sd_vae.py @@ -147,21 +147,25 @@ def load_vae(model, vae_file=None, vae_source="unknown-source"): global loaded_vae_file # pylint: disable=global-statement cache_enabled = shared.opts.sd_vae_checkpoint_cache > 0 if vae_file: - if cache_enabled and vae_file in checkpoints_loaded: - # use vae checkpoint cache - shared.log.info(f"Loading VAE: model={get_filename(vae_file)} source={vae_source} cached=True") - store_base_vae(model) - _load_vae_dict(model, checkpoints_loaded[vae_file]) - else: - if not os.path.isfile(vae_file): - shared.log.error(f"VAE not found: model={vae_file} source={vae_source}") - return - store_base_vae(model) - vae_dict_1 = load_vae_dict(vae_file) - _load_vae_dict(model, vae_dict_1) - if cache_enabled: - # cache newly loaded vae - checkpoints_loaded[vae_file] = vae_dict_1.copy() + try: + if cache_enabled and vae_file in checkpoints_loaded: + # use vae checkpoint cache + shared.log.info(f"Loading VAE: model={get_filename(vae_file)} source={vae_source} cached=True") + store_base_vae(model) + _load_vae_dict(model, checkpoints_loaded[vae_file]) + else: + if not os.path.isfile(vae_file): + shared.log.error(f"VAE not found: model={vae_file} source={vae_source}") + return + store_base_vae(model) + vae_dict_1 = load_vae_dict(vae_file) + _load_vae_dict(model, vae_dict_1) + if cache_enabled: + # cache newly loaded vae + checkpoints_loaded[vae_file] = vae_dict_1.copy() + except Exception as e: + shared.log.error(f"Loading VAE failed: model={vae_file} source={vae_source} {e}") + restore_base_vae(model) # clean up cache if limit is reached if cache_enabled: while len(checkpoints_loaded) > shared.opts.sd_vae_checkpoint_cache + 1: # we need to count the current model From 6bb73e1fd8b1b7bff59ba3a297dfb0d3e60dbdf1 Mon Sep 17 00:00:00 2001 From: Vladimir Mandic Date: Sun, 24 Dec 2023 11:30:04 -0500 Subject: [PATCH 114/143] add support for lora oft, lyco oft, peft --- CHANGELOG.md | 14 ++- .../Lora/extra_networks_lora.py | 2 +- extensions-builtin/Lora/lora_convert.py | 9 +- extensions-builtin/Lora/lyco_helpers.py | 47 ++++++++ extensions-builtin/Lora/network_full.py | 8 +- extensions-builtin/Lora/network_glora.py | 14 +-- extensions-builtin/Lora/network_hada.py | 16 +-- extensions-builtin/Lora/network_ia3.py | 10 +- extensions-builtin/Lora/network_lokr.py | 26 ++--- extensions-builtin/Lora/network_lora.py | 10 +- extensions-builtin/Lora/network_norm.py | 8 +- extensions-builtin/Lora/network_oft.py | 108 ++++++++++++------ extensions-builtin/Lora/networks.py | 49 +++++--- .../Lora/ui_extra_networks_lora.py | 1 - extensions-builtin/sd-webui-controlnet | 2 +- installer.py | 31 +---- launch.py | 1 - modules/lora | 2 +- requirements.txt | 2 +- 19 files changed, 218 insertions(+), 142 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 57c09eda4..62f49b783 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,17 +1,17 @@ # Change Log for SD.Next -## Update for 2023-12-23 +## Update for 2023-12-24 *Note*: based on `diffusers==0.25.0.dev0` -- **Control** +- **Control** - native implementation of **ControlNet**, **ControlNet XS**, **T2I Adapters** and **IP Adapters** - - top-level **Control** next to **Text** and **Image** generate - - supports all variations of **SD15** and **SD-XL** models + - top-level **Control** next to **Text** and **Image** generate + - supports all variations of **SD15** and **SD-XL** models - supports *Text*, *Image*, *Batch* and *Video* processing - for details and list of supported models and workflows, see Wiki documentation: - -- **Diffusers** + +- **Diffusers** - **AnimateDiff** - can now be used with *second pass* - enhance, upscale and hires your videos! - **IP Adapter** @@ -58,6 +58,8 @@ - add support for block weights, thanks @AI-Casanova example `` - add support for LyCORIS GLora networks + - add support for LoRA PEFT (*Diffusers*) networks + - add support for Lora-OFT (*Kohya*) and Lyco-OFT (*Kohaku*) networks - reintroduce alternative loading method in settings: `lora_force_diffusers` - add support for `lora_fuse_diffusers` if using alternative method use if you have multiple complex loras that may be causing performance degradation diff --git a/extensions-builtin/Lora/extra_networks_lora.py b/extensions-builtin/Lora/extra_networks_lora.py index 5ef27e0fc..6cdfd03a8 100644 --- a/extensions-builtin/Lora/extra_networks_lora.py +++ b/extensions-builtin/Lora/extra_networks_lora.py @@ -62,7 +62,7 @@ def activate(self, p, params_list): if network_hashes: p.extra_generation_params["Lora hashes"] = ", ".join(network_hashes) if len(names) > 0: - shared.log.info(f'Applying LoRA: {names} patch={t1-t0:.2f} load={t2-t1:.2f}') + shared.log.info(f'LoRA apply: {names} patch={t1-t0:.2f} load={t2-t1:.2f}') elif self.active: self.active = False diff --git a/extensions-builtin/Lora/lora_convert.py b/extensions-builtin/Lora/lora_convert.py index fb314f258..65f6a0adb 100644 --- a/extensions-builtin/Lora/lora_convert.py +++ b/extensions-builtin/Lora/lora_convert.py @@ -1,9 +1,11 @@ -from typing import Dict +import os import re import bisect +from typing import Dict from modules import shared +debug = os.environ.get('SD_LORA_DEBUG', None) is not None suffix_conversion = { "attentions": {}, "resnets": { @@ -144,12 +146,13 @@ def diffusers(self, key): map_keys = list(self.UNET_CONVERSION_MAP.keys()) # prefix of U-Net modules map_keys.sort() search_key = key.replace(self.LORA_PREFIX_UNET, "").replace(self.OFT_PREFIX_UNET, "").replace(self.LORA_PREFIX_TEXT_ENCODER1, "").replace(self.LORA_PREFIX_TEXT_ENCODER2, "") - position = bisect.bisect_right(map_keys, search_key) map_key = map_keys[position - 1] if search_key.startswith(map_key): - key = key.replace(map_key, self.UNET_CONVERSION_MAP[map_key]).replace("oft","lora") # pylint: disable=unsubscriptable-object + key = key.replace(map_key, self.UNET_CONVERSION_MAP[map_key]).replace("oft", "lora") # pylint: disable=unsubscriptable-object sd_module = shared.sd_model.network_layer_mapping.get(key, None) + if debug and sd_module is None: + raise RuntimeError(f"LoRA key not found in network_layer_mapping: key={key} mapping={shared.sd_model.network_layer_mapping.keys()}") return key, sd_module def __call__(self, key): diff --git a/extensions-builtin/Lora/lyco_helpers.py b/extensions-builtin/Lora/lyco_helpers.py index 279b34bc9..1679a0ce6 100644 --- a/extensions-builtin/Lora/lyco_helpers.py +++ b/extensions-builtin/Lora/lyco_helpers.py @@ -19,3 +19,50 @@ def rebuild_cp_decomposition(up, down, mid): up = up.reshape(up.size(0), -1) down = down.reshape(down.size(0), -1) return torch.einsum('n m k l, i n, m j -> i j k l', mid, up, down) + + +# copied from https://github.com/KohakuBlueleaf/LyCORIS/blob/dev/lycoris/modules/lokr.py +def factorization(dimension: int, factor:int=-1) -> tuple[int, int]: + ''' + return a tuple of two value of input dimension decomposed by the number closest to factor + second value is higher or equal than first value. + + In LoRA with Kroneckor Product, first value is a value for weight scale. + secon value is a value for weight. + + Becuase of non-commutative property, A⊗B ≠ B⊗A. Meaning of two matrices is slightly different. + + examples) + factor + -1 2 4 8 16 ... + 127 -> 1, 127 127 -> 1, 127 127 -> 1, 127 127 -> 1, 127 127 -> 1, 127 + 128 -> 8, 16 128 -> 2, 64 128 -> 4, 32 128 -> 8, 16 128 -> 8, 16 + 250 -> 10, 25 250 -> 2, 125 250 -> 2, 125 250 -> 5, 50 250 -> 10, 25 + 360 -> 8, 45 360 -> 2, 180 360 -> 4, 90 360 -> 8, 45 360 -> 12, 30 + 512 -> 16, 32 512 -> 2, 256 512 -> 4, 128 512 -> 8, 64 512 -> 16, 32 + 1024 -> 32, 32 1024 -> 2, 512 1024 -> 4, 256 1024 -> 8, 128 1024 -> 16, 64 + ''' + + if factor > 0 and (dimension % factor) == 0: + m = factor + n = dimension // factor + if m > n: + n, m = m, n + return m, n + if factor < 0: + factor = dimension + m, n = 1, dimension + length = m + n + while m length or new_m>factor: + break + else: + m, n = new_m, new_n + if m > n: + n, m = m, n + return m, n + diff --git a/extensions-builtin/Lora/network_full.py b/extensions-builtin/Lora/network_full.py index bf6930e96..233791712 100644 --- a/extensions-builtin/Lora/network_full.py +++ b/extensions-builtin/Lora/network_full.py @@ -16,12 +16,12 @@ def __init__(self, net: network.Network, weights: network.NetworkWeights): self.weight = weights.w.get("diff") self.ex_bias = weights.w.get("diff_b") - def calc_updown(self, orig_weight): + def calc_updown(self, target): output_shape = self.weight.shape - updown = self.weight.to(orig_weight.device, dtype=orig_weight.dtype) + updown = self.weight.to(target.device, dtype=target.dtype) if self.ex_bias is not None: - ex_bias = self.ex_bias.to(orig_weight.device, dtype=orig_weight.dtype) + ex_bias = self.ex_bias.to(target.device, dtype=target.dtype) else: ex_bias = None - return self.finalize_updown(updown, orig_weight, output_shape, ex_bias) + return self.finalize_updown(updown, target, output_shape, ex_bias) diff --git a/extensions-builtin/Lora/network_glora.py b/extensions-builtin/Lora/network_glora.py index 3c54a14cc..ce6ceaa1b 100644 --- a/extensions-builtin/Lora/network_glora.py +++ b/extensions-builtin/Lora/network_glora.py @@ -20,11 +20,11 @@ def __init__(self, net: network.Network, weights: network.NetworkWeights): self.w2a = weights.w["a2.weight"] self.w2b = weights.w["b2.weight"] - def calc_updown(self, orig_weight): # pylint: disable=arguments-differ - w1a = self.w1a.to(orig_weight.device, dtype=orig_weight.dtype) - w1b = self.w1b.to(orig_weight.device, dtype=orig_weight.dtype) - w2a = self.w2a.to(orig_weight.device, dtype=orig_weight.dtype) - w2b = self.w2b.to(orig_weight.device, dtype=orig_weight.dtype) + def calc_updown(self, target): # pylint: disable=arguments-differ + w1a = self.w1a.to(target.device, dtype=target.dtype) + w1b = self.w1b.to(target.device, dtype=target.dtype) + w2a = self.w2a.to(target.device, dtype=target.dtype) + w2b = self.w2b.to(target.device, dtype=target.dtype) output_shape = [w1a.size(0), w1b.size(1)] - updown = (w2b @ w1b) + ((orig_weight @ w2a) @ w1a) - return self.finalize_updown(updown, orig_weight, output_shape) + updown = (w2b @ w1b) + ((target @ w2a) @ w1a) + return self.finalize_updown(updown, target, output_shape) diff --git a/extensions-builtin/Lora/network_hada.py b/extensions-builtin/Lora/network_hada.py index 78e8c1569..0feda761e 100644 --- a/extensions-builtin/Lora/network_hada.py +++ b/extensions-builtin/Lora/network_hada.py @@ -22,15 +22,15 @@ def __init__(self, net: network.Network, weights: network.NetworkWeights): self.t1 = weights.w.get("hada_t1") self.t2 = weights.w.get("hada_t2") - def calc_updown(self, orig_weight): - w1a = self.w1a.to(orig_weight.device, dtype=orig_weight.dtype) - w1b = self.w1b.to(orig_weight.device, dtype=orig_weight.dtype) - w2a = self.w2a.to(orig_weight.device, dtype=orig_weight.dtype) - w2b = self.w2b.to(orig_weight.device, dtype=orig_weight.dtype) + def calc_updown(self, target): + w1a = self.w1a.to(target.device, dtype=target.dtype) + w1b = self.w1b.to(target.device, dtype=target.dtype) + w2a = self.w2a.to(target.device, dtype=target.dtype) + w2b = self.w2b.to(target.device, dtype=target.dtype) output_shape = [w1a.size(0), w1b.size(1)] if self.t1 is not None: output_shape = [w1a.size(1), w1b.size(1)] - t1 = self.t1.to(orig_weight.device, dtype=orig_weight.dtype) + t1 = self.t1.to(target.device, dtype=target.dtype) updown1 = lyco_helpers.make_weight_cp(t1, w1a, w1b) output_shape += t1.shape[2:] else: @@ -38,9 +38,9 @@ def calc_updown(self, orig_weight): output_shape += w1b.shape[2:] updown1 = lyco_helpers.rebuild_conventional(w1a, w1b, output_shape) if self.t2 is not None: - t2 = self.t2.to(orig_weight.device, dtype=orig_weight.dtype) + t2 = self.t2.to(target.device, dtype=target.dtype) updown2 = lyco_helpers.make_weight_cp(t2, w2a, w2b) else: updown2 = lyco_helpers.rebuild_conventional(w2a, w2b, output_shape) updown = updown1 * updown2 - return self.finalize_updown(updown, orig_weight, output_shape) + return self.finalize_updown(updown, target, output_shape) diff --git a/extensions-builtin/Lora/network_ia3.py b/extensions-builtin/Lora/network_ia3.py index f8d86926f..cb39df228 100644 --- a/extensions-builtin/Lora/network_ia3.py +++ b/extensions-builtin/Lora/network_ia3.py @@ -15,12 +15,12 @@ def __init__(self, net: network.Network, weights: network.NetworkWeights): self.w = weights.w["weight"] self.on_input = weights.w["on_input"].item() - def calc_updown(self, orig_weight): - w = self.w.to(orig_weight.device, dtype=orig_weight.dtype) - output_shape = [w.size(0), orig_weight.size(1)] + def calc_updown(self, target): + w = self.w.to(target.device, dtype=target.dtype) + output_shape = [w.size(0), target.size(1)] if self.on_input: output_shape.reverse() else: w = w.reshape(-1, 1) - updown = orig_weight * w - return self.finalize_updown(updown, orig_weight, output_shape) + updown = target * w + return self.finalize_updown(updown, target, output_shape) diff --git a/extensions-builtin/Lora/network_lokr.py b/extensions-builtin/Lora/network_lokr.py index 426d64308..20387efee 100644 --- a/extensions-builtin/Lora/network_lokr.py +++ b/extensions-builtin/Lora/network_lokr.py @@ -32,26 +32,26 @@ def __init__(self, net: network.Network, weights: network.NetworkWeights): self.dim = self.w2b.shape[0] if self.w2b is not None else self.dim self.t2 = weights.w.get("lokr_t2") - def calc_updown(self, orig_weight): + def calc_updown(self, target): if self.w1 is not None: - w1 = self.w1.to(orig_weight.device, dtype=orig_weight.dtype) + w1 = self.w1.to(target.device, dtype=target.dtype) else: - w1a = self.w1a.to(orig_weight.device, dtype=orig_weight.dtype) - w1b = self.w1b.to(orig_weight.device, dtype=orig_weight.dtype) + w1a = self.w1a.to(target.device, dtype=target.dtype) + w1b = self.w1b.to(target.device, dtype=target.dtype) w1 = w1a @ w1b if self.w2 is not None: - w2 = self.w2.to(orig_weight.device, dtype=orig_weight.dtype) + w2 = self.w2.to(target.device, dtype=target.dtype) elif self.t2 is None: - w2a = self.w2a.to(orig_weight.device, dtype=orig_weight.dtype) - w2b = self.w2b.to(orig_weight.device, dtype=orig_weight.dtype) + w2a = self.w2a.to(target.device, dtype=target.dtype) + w2b = self.w2b.to(target.device, dtype=target.dtype) w2 = w2a @ w2b else: - t2 = self.t2.to(orig_weight.device, dtype=orig_weight.dtype) - w2a = self.w2a.to(orig_weight.device, dtype=orig_weight.dtype) - w2b = self.w2b.to(orig_weight.device, dtype=orig_weight.dtype) + t2 = self.t2.to(target.device, dtype=target.dtype) + w2a = self.w2a.to(target.device, dtype=target.dtype) + w2b = self.w2b.to(target.device, dtype=target.dtype) w2 = lyco_helpers.make_weight_cp(t2, w2a, w2b) output_shape = [w1.size(0) * w2.size(0), w1.size(1) * w2.size(1)] - if len(orig_weight.shape) == 4: - output_shape = orig_weight.shape + if len(target.shape) == 4: + output_shape = target.shape updown = make_kron(output_shape, w1, w2) - return self.finalize_updown(updown, orig_weight, output_shape) + return self.finalize_updown(updown, target, output_shape) diff --git a/extensions-builtin/Lora/network_lora.py b/extensions-builtin/Lora/network_lora.py index 5dcb05322..8c2c4c8a5 100644 --- a/extensions-builtin/Lora/network_lora.py +++ b/extensions-builtin/Lora/network_lora.py @@ -51,20 +51,20 @@ def create_module(self, weights, key, none_ok=False): module.weight.requires_grad_(False) return module - def calc_updown(self, orig_weight): # pylint: disable=W0237 - up = self.up_model.weight.to(orig_weight.device, dtype=orig_weight.dtype) - down = self.down_model.weight.to(orig_weight.device, dtype=orig_weight.dtype) + def calc_updown(self, target): # pylint: disable=W0237 + up = self.up_model.weight.to(target.device, dtype=target.dtype) + down = self.down_model.weight.to(target.device, dtype=target.dtype) output_shape = [up.size(0), down.size(1)] if self.mid_model is not None: # cp-decomposition - mid = self.mid_model.weight.to(orig_weight.device, dtype=orig_weight.dtype) + mid = self.mid_model.weight.to(target.device, dtype=target.dtype) updown = lyco_helpers.rebuild_cp_decomposition(up, down, mid) output_shape += mid.shape[2:] else: if len(down.shape) == 4: output_shape += down.shape[2:] updown = lyco_helpers.rebuild_conventional(up, down, output_shape, self.network.dyn_dim) - return self.finalize_updown(updown, orig_weight, output_shape) + return self.finalize_updown(updown, target, output_shape) def forward(self, x, y): self.up_model.to(device=devices.device) diff --git a/extensions-builtin/Lora/network_norm.py b/extensions-builtin/Lora/network_norm.py index a291fbad3..f327b9754 100644 --- a/extensions-builtin/Lora/network_norm.py +++ b/extensions-builtin/Lora/network_norm.py @@ -14,11 +14,11 @@ def __init__(self, net: network.Network, weights: network.NetworkWeights): self.w_norm = weights.w.get("w_norm") self.b_norm = weights.w.get("b_norm") - def calc_updown(self, orig_weight): + def calc_updown(self, target): output_shape = self.w_norm.shape - updown = self.w_norm.to(orig_weight.device, dtype=orig_weight.dtype) + updown = self.w_norm.to(target.device, dtype=target.dtype) if self.b_norm is not None: - ex_bias = self.b_norm.to(orig_weight.device, dtype=orig_weight.dtype) + ex_bias = self.b_norm.to(target.device, dtype=target.dtype) else: ex_bias = None - return self.finalize_updown(updown, orig_weight, output_shape, ex_bias) + return self.finalize_updown(updown, target, output_shape, ex_bias) diff --git a/extensions-builtin/Lora/network_oft.py b/extensions-builtin/Lora/network_oft.py index 6d350671a..6cadc36d0 100644 --- a/extensions-builtin/Lora/network_oft.py +++ b/extensions-builtin/Lora/network_oft.py @@ -1,49 +1,85 @@ import torch -import diffusers.models.lora as diffusers_lora import network -from modules import devices +from lyco_helpers import factorization +from einops import rearrange + class ModuleTypeOFT(network.ModuleType): def create_module(self, net: network.Network, weights: network.NetworkWeights): - """ - weights.w.items() + if all(x in weights.w for x in ["oft_blocks"]) or all(x in weights.w for x in ["oft_diag"]): + return NetworkModuleOFT(net, weights) + + return None + +# Supports both kohya-ss' implementation of COFT https://github.com/kohya-ss/sd-scripts/blob/main/networks/oft.py +# and KohakuBlueleaf's implementation of OFT/COFT https://github.com/KohakuBlueleaf/LyCORIS/blob/dev/lycoris/modules/diag_oft.py +class NetworkModuleOFT(network.NetworkModule): + def __init__(self, net: network.Network, weights: network.NetworkWeights): + + super().__init__(net, weights) + + self.lin_module = None + self.org_module: list[torch.Module] = [self.sd_module] - alpha : tensor(0.0010, dtype=torch.bfloat16) - oft_blocks : tensor([[[ 0.0000e+00, 1.4400e-04, 1.7319e-03, ..., -8.8882e-04, - 5.7373e-03, -4.4250e-03], - [-1.4400e-04, 0.0000e+00, 8.6594e-04, ..., 1.5945e-03, - -8.5449e-04, 1.9684e-03], ...etc... - , dtype=torch.bfloat16)""" + self.scale = 1.0 + # kohya-ss if "oft_blocks" in weights.w.keys(): - module = NetworkModuleOFT(net, weights) - return module - else: - return None + self.is_kohya = True + self.oft_blocks = weights.w["oft_blocks"] # (num_blocks, block_size, block_size) + self.alpha = weights.w["alpha"] # alpha is constraint + self.dim = self.oft_blocks.shape[0] # lora dim + # LyCORIS + elif "oft_diag" in weights.w.keys(): + self.is_kohya = False + self.oft_blocks = weights.w["oft_diag"] + # self.alpha is unused + self.dim = self.oft_blocks.shape[1] # (num_blocks, block_size, block_size) + is_linear = type(self.sd_module) in [torch.nn.Linear, torch.nn.modules.linear.NonDynamicallyQuantizableLinear] + is_conv = type(self.sd_module) in [torch.nn.Conv2d] + is_other_linear = type(self.sd_module) in [torch.nn.MultiheadAttention] # unsupported -class NetworkModuleOFT(network.NetworkModule): - def __init__(self, net: network.Network, weights: network.NetworkWeights): - super().__init__(net, weights) + if is_linear: + self.out_dim = self.sd_module.out_features + elif is_conv: + self.out_dim = self.sd_module.out_channels + elif is_other_linear: + self.out_dim = self.sd_module.embed_dim - self.weights = weights.w.get("oft_blocks").to(device=devices.device) - self.dim = self.weights.shape[0] # num blocks - self.alpha = self.multiplier() - self.block_size = self.weights.shape[-1] - - def get_weight(self): - block_Q = self.weights - self.weights.transpose(1, 2) - I = torch.eye(self.block_size, device=devices.device).unsqueeze(0).repeat(self.dim, 1, 1) - block_R = torch.matmul(I + block_Q, (I - block_Q).inverse()) - block_R_weighted = self.alpha * block_R + (1 - self.alpha) * I - R = torch.block_diag(*block_R_weighted) - return R - - def calc_updown(self, orig_weight): - R = self.get_weight().to(device=devices.device, dtype=orig_weight.dtype) - if orig_weight.dim() == 4: - updown = torch.einsum("oihw, op -> pihw", orig_weight, R) * self.calc_scale() + if self.is_kohya: + self.constraint = self.alpha * self.out_dim + self.num_blocks = self.dim + self.block_size = self.out_dim // self.dim else: - updown = torch.einsum("oi, op -> pi", orig_weight, R) * self.calc_scale() + self.constraint = None + self.block_size, self.num_blocks = factorization(self.out_dim, self.dim) + + def calc_updown(self, target): + oft_blocks = self.oft_blocks.to(target.device, dtype=target.dtype) + eye = torch.eye(self.block_size, device=target.device) + constraint = self.constraint.to(target.device) + + if self.is_kohya: + block_Q = oft_blocks - oft_blocks.transpose(1, 2) # ensure skew-symmetric orthogonal matrix + norm_Q = torch.norm(block_Q.flatten()).to(target.device) + new_norm_Q = torch.clamp(norm_Q, max=constraint) + block_Q = block_Q * ((new_norm_Q + 1e-8) / (norm_Q + 1e-8)) + mat1 = eye + block_Q + mat2 = (eye - block_Q).float().inverse() + oft_blocks = torch.matmul(mat1, mat2) + + R = oft_blocks.to(target.device, dtype=target.dtype) + + # This errors out for MultiheadAttention, might need to be handled up-stream + merged_weight = rearrange(target, '(k n) ... -> k n ...', k=self.num_blocks, n=self.block_size) + merged_weight = torch.einsum( + 'k n m, k n ... -> k m ...', + R, + merged_weight + ) + merged_weight = rearrange(merged_weight, 'k m ... -> (k m) ...') - return self.finalize_updown(updown, orig_weight, orig_weight.shape) + updown = merged_weight.to(target.device, dtype=target.dtype) - target + output_shape = target.shape + return self.finalize_updown(updown, target, output_shape) diff --git a/extensions-builtin/Lora/networks.py b/extensions-builtin/Lora/networks.py index fb8b9e138..4ad17df1b 100644 --- a/extensions-builtin/Lora/networks.py +++ b/extensions-builtin/Lora/networks.py @@ -1,4 +1,4 @@ -from typing import Union +from typing import Union, List import os import re import time @@ -18,12 +18,12 @@ from modules import shared, devices, sd_models, sd_models_compile, errors, scripts, sd_hijack -debug = os.environ.get('SD_LORA_DEBUG', None) +debug = os.environ.get('SD_LORA_DEBUG', None) is not None originals: lora_patches.LoraPatches = None extra_network_lora = None available_networks = {} available_network_aliases = {} -loaded_networks = [] +loaded_networks: List[network.Network] = [] timer = { 'load': 0, 'apply': 0, 'restore': 0 } # networks_in_memory = {} lora_cache = {} @@ -76,7 +76,7 @@ def assign_network_names_to_compvis_modules(sd_model): sd_model.network_layer_mapping = network_layer_mapping -def load_diffusers(name, network_on_disk, lora_scale=1.0): +def load_diffusers(name, network_on_disk, lora_scale=1.0) -> network.Network: t0 = time.time() cached = lora_cache.get(name, None) # if debug: @@ -96,11 +96,11 @@ def load_diffusers(name, network_on_disk, lora_scale=1.0): return net -def load_network(name, network_on_disk): +def load_network(name, network_on_disk) -> network.Network: t0 = time.time() cached = lora_cache.get(name, None) if debug: - shared.log.debug(f'LoRA load: name="{name}" file="{network_on_disk.filename}" {"cached" if cached else ""}') + shared.log.debug(f'LoRA load: name="{name}" file="{network_on_disk.filename}" type=lora {"cached" if cached else ""}') if cached is not None: return cached net = network.Network(name, network_on_disk) @@ -111,7 +111,16 @@ def load_network(name, network_on_disk): matched_networks = {} convert = lora_convert.KeyConvert() for key_network, weight in sd.items(): - key_network_without_network_parts, network_part = key_network.split(".", 1) + parts = key_network.split('.') + if len(parts) > 5: # messy handler for diffusers peft lora + key_network_without_network_parts = '_'.join(parts[:-2]) + if not key_network_without_network_parts.startswith('lora_'): + key_network_without_network_parts = 'lora_' + key_network_without_network_parts + network_part = '.'.join(parts[-2:]).replace('lora_A', 'lora_down').replace('lora_B', 'lora_up') + else: + key_network_without_network_parts, network_part = key_network.split(".", 1) + if debug: + shared.log.debug(f'LoRA load: name="{name}" full={key_network} network={network_part} key={key_network_without_network_parts}') key, sd_module = convert(key_network_without_network_parts) if sd_module is None: keys_failed_to_match[key_network] = key @@ -126,12 +135,15 @@ def load_network(name, network_on_disk): if net_module is not None: break if net_module is None: - raise AssertionError(f"Could not find a module type (out of {', '.join([x.__class__.__name__ for x in module_types])}) that would accept those keys: {', '.join(weights.w)}") - net.modules[key] = net_module - if keys_failed_to_match: + shared.log.error(f'LoRA unhandled: name={name} key={key} weights={weights.w.keys()}') + else: + net.modules[key] = net_module + if len(keys_failed_to_match) > 0: shared.log.warning(f"LoRA file={network_on_disk.filename} unmatched={len(keys_failed_to_match)} matched={len(matched_networks)}") if debug: shared.log.debug(f"LoRA file={network_on_disk.filename} unmatched={keys_failed_to_match}") + elif debug: + shared.log.debug(f"LoRA file={network_on_disk.filename} unmatched={len(keys_failed_to_match)} matched={len(matched_networks)}") lora_cache[name] = net t1 = time.time() timer['load'] += t1 - t0 @@ -169,6 +181,8 @@ def load_networks(names, te_multipliers=None, unet_multipliers=None, dyn_dims=No for i, (network_on_disk, name) in enumerate(zip(networks_on_disk, names)): net = None if network_on_disk is not None: + if debug: + shared.log.debug(f'LoRA load start: name="{name}" file="{network_on_disk.filename}"') try: if recompile_model: shared.compiled_model_state.lora_model.append(f"{name}:{te_multipliers[i] if te_multipliers else 1.0}") @@ -188,7 +202,7 @@ def load_networks(names, te_multipliers=None, unet_multipliers=None, dyn_dims=No network_on_disk.read_hash() if net is None: failed_to_load_networks.append(name) - shared.log.error(f"LoRA unknown: network={name}") + shared.log.error(f"LoRA unknown type: network={name}") continue net.te_multiplier = te_multipliers[i] if te_multipliers else 1.0 net.unet_multiplier = unet_multipliers[i] if unet_multipliers else 1.0 @@ -271,10 +285,11 @@ def network_apply_weights(self: Union[torch.nn.Conv2d, torch.nn.Linear, torch.nn if current_names != wanted_names: network_restore_weights_from_backup(self) for net in loaded_networks: + # default workflow where module is known and has weights module = net.modules.get(network_layer_name, None) if module is not None and hasattr(self, 'weight'): try: - with torch.no_grad(): + with devices.inference_context(): updown, ex_bias = module.calc_updown(self.weight) if len(self.weight.shape) == 4 and self.weight.shape[1] == 9: # inpainting model. zero pad updown to make channel[1] 4 to 9 @@ -286,17 +301,21 @@ def network_apply_weights(self: Union[torch.nn.Conv2d, torch.nn.Linear, torch.nn else: self.bias += ex_bias except RuntimeError as e: - if debug: - shared.log.debug(f"LoRA apply weight network={net.name} layer={network_layer_name} {e}") extra_network_lora.errors[net.name] = extra_network_lora.errors.get(net.name, 0) + 1 + if debug: + module_name = net.modules.get(network_layer_name, None) + shared.log.error(f"LoRA apply weight name={net.name} module={module_name} layer={network_layer_name} {e}") + errors.display(e, 'LoRA apply weight') + raise RuntimeError('LoRA apply weight') from e continue + # alternative workflow looking at _*_proj layers module_q = net.modules.get(network_layer_name + "_q_proj", None) module_k = net.modules.get(network_layer_name + "_k_proj", None) module_v = net.modules.get(network_layer_name + "_v_proj", None) module_out = net.modules.get(network_layer_name + "_out_proj", None) if isinstance(self, torch.nn.MultiheadAttention) and module_q and module_k and module_v and module_out: try: - with torch.no_grad(): + with devices.inference_context(): updown_q, _ = module_q.calc_updown(self.in_proj_weight) updown_k, _ = module_k.calc_updown(self.in_proj_weight) updown_v, _ = module_v.calc_updown(self.in_proj_weight) diff --git a/extensions-builtin/Lora/ui_extra_networks_lora.py b/extensions-builtin/Lora/ui_extra_networks_lora.py index bd408b327..cee5ad29e 100644 --- a/extensions-builtin/Lora/ui_extra_networks_lora.py +++ b/extensions-builtin/Lora/ui_extra_networks_lora.py @@ -19,7 +19,6 @@ def create_item(self, name): try: path, _ext = os.path.splitext(l.filename) name = os.path.splitext(os.path.relpath(l.filename, shared.cmd_opts.lora_dir))[0] - if shared.backend == shared.Backend.ORIGINAL: if l.sd_version == network.SdVersion.SDXL: return None diff --git a/extensions-builtin/sd-webui-controlnet b/extensions-builtin/sd-webui-controlnet index 01e4574d8..4ceb6e8a4 160000 --- a/extensions-builtin/sd-webui-controlnet +++ b/extensions-builtin/sd-webui-controlnet @@ -1 +1 @@ -Subproject commit 01e4574d8e01fa00628fdae0c8215283a1c36a8d +Subproject commit 4ceb6e8a4b86605cdeb6cd7087d1d7ef34452d85 diff --git a/installer.py b/installer.py index 29d65bb5d..1db105dca 100644 --- a/installer.py +++ b/installer.py @@ -574,36 +574,6 @@ def install_packages(): print_profile(pr, 'Packages') -# clone required repositories -def install_repositories(): - """ - if args.profile: - pr = cProfile.Profile() - pr.enable() - def d(name): - return os.path.join(os.path.dirname(__file__), 'repositories', name) - log.info('Verifying repositories') - os.makedirs(os.path.join(os.path.dirname(__file__), 'repositories'), exist_ok=True) - stable_diffusion_repo = os.environ.get('STABLE_DIFFUSION_REPO', "https://github.com/Stability-AI/stablediffusion.git") - stable_diffusion_commit = os.environ.get('STABLE_DIFFUSION_COMMIT_HASH', None) - clone(stable_diffusion_repo, d('stable-diffusion-stability-ai'), stable_diffusion_commit) - taming_transformers_repo = os.environ.get('TAMING_TRANSFORMERS_REPO', "https://github.com/CompVis/taming-transformers.git") - taming_transformers_commit = os.environ.get('TAMING_TRANSFORMERS_COMMIT_HASH', None) - clone(taming_transformers_repo, d('taming-transformers'), taming_transformers_commit) - k_diffusion_repo = os.environ.get('K_DIFFUSION_REPO', 'https://github.com/crowsonkb/k-diffusion.git') - k_diffusion_commit = os.environ.get('K_DIFFUSION_COMMIT_HASH', '0455157') - clone(k_diffusion_repo, d('k-diffusion'), k_diffusion_commit) - codeformer_repo = os.environ.get('CODEFORMER_REPO', 'https://github.com/sczhou/CodeFormer.git') - codeformer_commit = os.environ.get('CODEFORMER_COMMIT_HASH', "7a584fd") - clone(codeformer_repo, d('CodeFormer'), codeformer_commit) - blip_repo = os.environ.get('BLIP_REPO', 'https://github.com/salesforce/BLIP.git') - blip_commit = os.environ.get('BLIP_COMMIT_HASH', None) - clone(blip_repo, d('BLIP'), blip_commit) - if args.profile: - print_profile(pr, 'Repositories') - """ - - # run extension installer def run_extension_installer(folder): path_installer = os.path.realpath(os.path.join(folder, "install.py")) @@ -776,6 +746,7 @@ def set_environment(): os.environ.setdefault('USE_TORCH', '1') os.environ.setdefault('UVICORN_TIMEOUT_KEEP_ALIVE', '60') os.environ.setdefault('KINETO_LOG_LEVEL', '3') + os.environ.setdefault('DO_NOT_TRACK', '1') os.environ.setdefault('HF_HUB_CACHE', opts.get('hfcache_dir', os.path.join(os.path.expanduser('~'), '.cache', 'huggingface', 'hub'))) log.debug(f'Cache folder: {os.environ.get("HF_HUB_CACHE")}') if sys.platform == 'darwin': diff --git a/launch.py b/launch.py index 00adf4475..4617e5f60 100755 --- a/launch.py +++ b/launch.py @@ -215,7 +215,6 @@ def start_server(immediate=True, server=None): installer.log.info('Startup: standard') installer.install_requirements() installer.install_packages() - installer.install_repositories() installer.install_submodules() init_paths() installer.install_extensions() diff --git a/modules/lora b/modules/lora index 0a52b83c6..1a36f9dc6 160000 --- a/modules/lora +++ b/modules/lora @@ -1 +1 @@ -Subproject commit 0a52b83c6a4c87d6375dc6d4d55ad78c39f9f666 +Subproject commit 1a36f9dc65fb3be8baa9dcbde832048cc5644efa diff --git a/requirements.txt b/requirements.txt index be2442bb9..5e3c49937 100644 --- a/requirements.txt +++ b/requirements.txt @@ -55,7 +55,7 @@ opencv-python-headless==4.7.0.72 diffusers==0.24.0 einops==0.4.1 gradio==3.43.2 -huggingface_hub==0.19.4 +huggingface_hub==0.20.1 numexpr==2.8.4 numpy==1.24.4 numba==0.57.1 From 36016432ab9e64c7c8cf03e05822c8663c3a52ae Mon Sep 17 00:00:00 2001 From: Vladimir Mandic Date: Sun, 24 Dec 2023 11:30:33 -0500 Subject: [PATCH 115/143] cleanup --- extensions-builtin/Lora/networks.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/extensions-builtin/Lora/networks.py b/extensions-builtin/Lora/networks.py index 4ad17df1b..b0128eb43 100644 --- a/extensions-builtin/Lora/networks.py +++ b/extensions-builtin/Lora/networks.py @@ -119,8 +119,8 @@ def load_network(name, network_on_disk) -> network.Network: network_part = '.'.join(parts[-2:]).replace('lora_A', 'lora_down').replace('lora_B', 'lora_up') else: key_network_without_network_parts, network_part = key_network.split(".", 1) - if debug: - shared.log.debug(f'LoRA load: name="{name}" full={key_network} network={network_part} key={key_network_without_network_parts}') + # if debug: + # shared.log.debug(f'LoRA load: name="{name}" full={key_network} network={network_part} key={key_network_without_network_parts}') key, sd_module = convert(key_network_without_network_parts) if sd_module is None: keys_failed_to_match[key_network] = key From 542686eef8c1dab0ac47a5f74473e566f3172364 Mon Sep 17 00:00:00 2001 From: Vladimir Mandic Date: Mon, 25 Dec 2023 07:58:43 -0500 Subject: [PATCH 116/143] detect sd2 --- extensions-builtin/sd-webui-controlnet | 2 +- modules/sd_models.py | 18 +++++++++--------- scripts/animatediff.py | 7 +------ 3 files changed, 11 insertions(+), 16 deletions(-) diff --git a/extensions-builtin/sd-webui-controlnet b/extensions-builtin/sd-webui-controlnet index 4ceb6e8a4..b7a9bea23 160000 --- a/extensions-builtin/sd-webui-controlnet +++ b/extensions-builtin/sd-webui-controlnet @@ -1 +1 @@ -Subproject commit 4ceb6e8a4b86605cdeb6cd7087d1d7ef34452d85 +Subproject commit b7a9bea231c1eecdb2bf25ff7554e8b11d727fb6 diff --git a/modules/sd_models.py b/modules/sd_models.py index 5d0dce124..aa97d922b 100644 --- a/modules/sd_models.py +++ b/modules/sd_models.py @@ -474,10 +474,10 @@ def load_model_weights(model: torch.nn.Module, checkpoint_info: CheckpointInfo, model.sd_model_checkpoint = checkpoint_info.filename model.sd_checkpoint_info = checkpoint_info model.is_sdxl = False # a1111 compatibility item - model.is_sd2 = False # a1111 compatibility item - model.is_sd1 = True # a1111 compatibility item - shared.opts.data["sd_checkpoint_hash"] = checkpoint_info.sha256 + model.is_sd2 = hasattr(model.cond_stage_model, 'model') # a1111 compatibility item + model.is_sd1 = not hasattr(model.cond_stage_model, 'model') # a1111 compatibility item model.logvar = model.logvar.to(devices.device) # fix for training + shared.opts.data["sd_checkpoint_hash"] = checkpoint_info.sha256 sd_vae.delete_base_vae() sd_vae.clear_loaded_vae() vae_file, vae_source = sd_vae.resolve_vae(checkpoint_info.filename) @@ -686,9 +686,9 @@ def copy_diffuser_options(new_pipe, orig_pipe): new_pipe.sd_model_hash = orig_pipe.sd_model_hash new_pipe.has_accelerate = orig_pipe.has_accelerate new_pipe.embedding_db = orig_pipe.embedding_db - new_pipe.is_sdxl = True # pylint: disable=attribute-defined-outside-init # a1111 compatibility item - new_pipe.is_sd2 = False # pylint: disable=attribute-defined-outside-init - new_pipe.is_sd1 = False # pylint: disable=attribute-defined-outside-init + new_pipe.is_sdxl = orig_pipe.is_sdxl # pylint: disable=attribute-defined-outside-init # a1111 compatibility item + new_pipe.is_sd2 = orig_pipe.is_sd2 # pylint: disable=attribute-defined-outside-init + new_pipe.is_sd1 = orig_pipe.is_sdv1 # pylint: disable=attribute-defined-outside-init @@ -1069,9 +1069,9 @@ def set_diffuser_pipe(pipe, new_pipe_type): new_pipe.embedding_db = embedding_db new_pipe.image_encoder = image_encoder new_pipe.feature_extractor = feature_extractor - new_pipe.is_sdxl = True # pylint: disable=attribute-defined-outside-init # a1111 compatibility item - new_pipe.is_sd2 = False # pylint: disable=attribute-defined-outside-init - new_pipe.is_sd1 = False # pylint: disable=attribute-defined-outside-init + new_pipe.is_sdxl = pipe.is_sdxl + new_pipe.is_sd2 = pipe.is_sd2 + new_pipe.is_sd1 = pipe.is_sd1 shared.log.debug(f"Pipeline class change: original={pipe.__class__.__name__} target={new_pipe.__class__.__name__}") pipe = new_pipe return pipe diff --git a/scripts/animatediff.py b/scripts/animatediff.py index 650f43d6b..067e1b82d 100644 --- a/scripts/animatediff.py +++ b/scripts/animatediff.py @@ -87,15 +87,10 @@ def set_adapter(adapter_name: str = 'None'): motion_adapter=motion_adapter, ) orig_pipe = shared.sd_model - new_pipe.sd_checkpoint_info = shared.sd_model.sd_checkpoint_info - new_pipe.sd_model_hash = shared.sd_model.sd_model_hash - new_pipe.sd_model_checkpoint = shared.sd_model.sd_checkpoint_info.filename - new_pipe.is_sdxl = False - new_pipe.is_sd2 = False - new_pipe.is_sd1 = True shared.sd_model = new_pipe if not ((shared.opts.diffusers_model_cpu_offload or shared.cmd_opts.medvram) or (shared.opts.diffusers_seq_cpu_offload or shared.cmd_opts.lowvram)): shared.sd_model.to(shared.device) + sd_models.copy_diffuser_options(new_pipe, orig_pipe) sd_models.set_diffuser_options(shared.sd_model, vae=None, op='model') shared.log.debug(f'AnimateDiff create pipeline: adapter="{loaded_adapter}"') except Exception as e: From 60e0e110dd761e48f85bce62e69f61b5d9f78a8f Mon Sep 17 00:00:00 2001 From: Vladimir Mandic Date: Mon, 25 Dec 2023 09:23:50 -0500 Subject: [PATCH 117/143] fix control pipeline --- modules/control/adapters.py | 2 -- modules/control/controlnets.py | 2 -- modules/control/controlnetsxs.py | 2 -- modules/control/reference.py | 2 -- modules/control/run.py | 1 + modules/sd_models.py | 6 +++--- wiki | 2 +- 7 files changed, 5 insertions(+), 12 deletions(-) diff --git a/modules/control/adapters.py b/modules/control/adapters.py index 19030d2cb..5ad4f859c 100644 --- a/modules/control/adapters.py +++ b/modules/control/adapters.py @@ -124,7 +124,6 @@ def __init__(self, adapter: T2IAdapter | list[T2IAdapter], pipeline: StableDiffu tokenizer_2=pipeline.tokenizer_2, unet=pipeline.unet, scheduler=pipeline.scheduler, - image_encoder=getattr(pipeline, 'image_encoder', None), feature_extractor=getattr(pipeline, 'feature_extractor', None), adapter=adapter, ).to(pipeline.device) @@ -135,7 +134,6 @@ def __init__(self, adapter: T2IAdapter | list[T2IAdapter], pipeline: StableDiffu tokenizer=pipeline.tokenizer, unet=pipeline.unet, scheduler=pipeline.scheduler, - image_encoder=getattr(pipeline, 'image_encoder', None), feature_extractor=getattr(pipeline, 'feature_extractor', None), requires_safety_checker=False, safety_checker=None, diff --git a/modules/control/controlnets.py b/modules/control/controlnets.py index 0e9816082..e183f99a6 100644 --- a/modules/control/controlnets.py +++ b/modules/control/controlnets.py @@ -139,7 +139,6 @@ def __init__(self, controlnet: ControlNetModel | list[ControlNetModel], pipeline tokenizer_2=pipeline.tokenizer_2, unet=pipeline.unet, scheduler=pipeline.scheduler, - image_encoder=getattr(pipeline, 'image_encoder', None), feature_extractor=getattr(pipeline, 'feature_extractor', None), controlnet=controlnet, # can be a list ).to(pipeline.device) @@ -150,7 +149,6 @@ def __init__(self, controlnet: ControlNetModel | list[ControlNetModel], pipeline tokenizer=pipeline.tokenizer, unet=pipeline.unet, scheduler=pipeline.scheduler, - image_encoder=getattr(pipeline, 'image_encoder', None), feature_extractor=getattr(pipeline, 'feature_extractor', None), requires_safety_checker=False, safety_checker=None, diff --git a/modules/control/controlnetsxs.py b/modules/control/controlnetsxs.py index 630f06df3..8a97eabbb 100644 --- a/modules/control/controlnetsxs.py +++ b/modules/control/controlnetsxs.py @@ -131,7 +131,6 @@ def __init__(self, controlnet: ControlNetXSModel | list[ControlNetXSModel], pipe tokenizer_2=pipeline.tokenizer_2, unet=pipeline.unet, scheduler=pipeline.scheduler, - image_encoder=getattr(pipeline, 'image_encoder', None), feature_extractor=getattr(pipeline, 'feature_extractor', None), controlnet=controlnet, # can be a list ).to(pipeline.device) @@ -142,7 +141,6 @@ def __init__(self, controlnet: ControlNetXSModel | list[ControlNetXSModel], pipe tokenizer=pipeline.tokenizer, unet=pipeline.unet, scheduler=pipeline.scheduler, - image_encoder=getattr(pipeline, 'image_encoder', None), feature_extractor=getattr(pipeline, 'feature_extractor', None), requires_safety_checker=False, safety_checker=None, diff --git a/modules/control/reference.py b/modules/control/reference.py index 32d8dcdd2..c5479698c 100644 --- a/modules/control/reference.py +++ b/modules/control/reference.py @@ -29,7 +29,6 @@ def __init__(self, pipeline: StableDiffusionXLPipeline | StableDiffusionPipeline tokenizer_2=pipeline.tokenizer_2, unet=pipeline.unet, scheduler=pipeline.scheduler, - image_encoder=getattr(pipeline, 'image_encoder', None), feature_extractor=getattr(pipeline, 'feature_extractor', None), ).to(pipeline.device) elif isinstance(pipeline, StableDiffusionPipeline): @@ -39,7 +38,6 @@ def __init__(self, pipeline: StableDiffusionXLPipeline | StableDiffusionPipeline tokenizer=pipeline.tokenizer, unet=pipeline.unet, scheduler=pipeline.scheduler, - image_encoder=getattr(pipeline, 'image_encoder', None), feature_extractor=getattr(pipeline, 'feature_extractor', None), requires_safety_checker=False, safety_checker=None, diff --git a/modules/control/run.py b/modules/control/run.py index a80829e28..dd4fe83bd 100644 --- a/modules/control/run.py +++ b/modules/control/run.py @@ -419,6 +419,7 @@ def control_run(units: List[unit.Unit], inputs, inits, unit_type: str, is_genera debug(f'Control exec pipeline: task={sd_models.get_diffusers_task(pipe)}') debug(f'Control exec pipeline: p={vars(p)}') debug(f'Control exec pipeline: args={p.task_args}') + debug(f'Control exec pipeline: image={p.task_args.get("image", None)}') processed: processing.Processed = processing.process_images(p) # run actual pipeline output = processed.images if processed is not None else None # output = pipe(**vars(p)).images # alternative direct pipe exec call diff --git a/modules/sd_models.py b/modules/sd_models.py index aa97d922b..46af45474 100644 --- a/modules/sd_models.py +++ b/modules/sd_models.py @@ -686,9 +686,9 @@ def copy_diffuser_options(new_pipe, orig_pipe): new_pipe.sd_model_hash = orig_pipe.sd_model_hash new_pipe.has_accelerate = orig_pipe.has_accelerate new_pipe.embedding_db = orig_pipe.embedding_db - new_pipe.is_sdxl = orig_pipe.is_sdxl # pylint: disable=attribute-defined-outside-init # a1111 compatibility item - new_pipe.is_sd2 = orig_pipe.is_sd2 # pylint: disable=attribute-defined-outside-init - new_pipe.is_sd1 = orig_pipe.is_sdv1 # pylint: disable=attribute-defined-outside-init + new_pipe.is_sdxl = getattr(orig_pipe, 'is_sdxl', False) # pylint: disable=attribute-defined-outside-init # a1111 compatibility item + new_pipe.is_sd2 = getattr(orig_pipe, 'is_sd2', False) # pylint: disable=attribute-defined-outside-init + new_pipe.is_sd1 = getattr(orig_pipe, 'is_sd1', True) # pylint: disable=attribute-defined-outside-init diff --git a/wiki b/wiki index 2b232b2ca..4d9737acc 160000 --- a/wiki +++ b/wiki @@ -1 +1 @@ -Subproject commit 2b232b2cab058b969acdea7c682252dbe4ca6cf1 +Subproject commit 4d9737acc348cfc0ae0b139ec5e39ae61967e7ac From 068809719a163119e4cc4b635fd84b1a526f44e2 Mon Sep 17 00:00:00 2001 From: Vladimir Mandic Date: Mon, 25 Dec 2023 14:45:01 -0500 Subject: [PATCH 118/143] update requirements and add control-lllite --- CHANGELOG.md | 6 +- javascript/ui.js | 21 ++- modules/control/adapters.py | 7 +- modules/control/controlnets.py | 13 +- modules/control/controlnetslite.py | 135 ++++++++++++++ modules/control/controlnetslite_model.py | 202 +++++++++++++++++++++ modules/control/controlnetsxs.py | 11 +- modules/control/proc/canny.py | 1 + modules/control/proc/edge.py | 1 + modules/control/reference.py | 2 +- modules/control/run.py | 25 ++- modules/control/test.py | 56 +++++- modules/control/unit.py | 8 + modules/generation_parameters_copypaste.py | 3 +- modules/modelloader.py | 6 +- modules/shared.py | 12 +- modules/ui_common.py | 13 +- modules/ui_control.py | 78 ++++++-- modules/ui_models.py | 3 +- pyproject.toml | 2 +- requirements.txt | 10 +- wiki | 2 +- 22 files changed, 555 insertions(+), 62 deletions(-) create mode 100644 modules/control/controlnetslite.py create mode 100644 modules/control/controlnetslite_model.py diff --git a/CHANGELOG.md b/CHANGELOG.md index 62f49b783..b676ddf98 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,11 +1,12 @@ # Change Log for SD.Next -## Update for 2023-12-24 +## Update for 2023-12-25 *Note*: based on `diffusers==0.25.0.dev0` - **Control** - - native implementation of **ControlNet**, **ControlNet XS**, **T2I Adapters** and **IP Adapters** + - native implementation of all image control methods: + **ControlNet**, **ControlNet XS**, **Control LLLite**, **T2I Adapters** and **IP Adapters** - top-level **Control** next to **Text** and **Image** generate - supports all variations of **SD15** and **SD-XL** models - supports *Text*, *Image*, *Batch* and *Video* processing @@ -104,6 +105,7 @@ - improve handling of long filenames and filenames during batch processing - do not set preview samples when using via api - avoid unnecessary resizes in img2img and inpaint + - safe handling of config updates avoid file corruption on I/O errors - updated `cli/simple-txt2img.py` and `cli/simple-img2img.py` scripts - save `params.txt` regardless of image save status - update built-in log monitor in ui, thanks @midcoastal diff --git a/javascript/ui.js b/javascript/ui.js index 845402f99..7902aaacf 100644 --- a/javascript/ui.js +++ b/javascript/ui.js @@ -66,45 +66,54 @@ function extract_image_from_gallery(gallery) { window.args_to_array = Array.from; // Compatibility with e.g. extensions that may expect this to be around +function switchToTab(tab) { + const tabs = Array.from(gradioApp().querySelectorAll('#tabs > .tab-nav > button')); + const btn = tabs?.find((t) => t.innerText === tab); + log('switchToTab', tab); + if (btn) btn.click(); +} + function switch_to_txt2img(...args) { - gradioApp().querySelector('#tabs').querySelectorAll('button')[0].click(); + switchToTab('Text'); return Array.from(arguments); } function switch_to_img2img_tab(no) { - gradioApp().querySelector('#tabs').querySelectorAll('button')[1].click(); + switchToTab('Image'); gradioApp().getElementById('mode_img2img').querySelectorAll('button')[no].click(); } function switch_to_img2img(...args) { + switchToTab('Image'); switch_to_img2img_tab(0); return Array.from(arguments); } function switch_to_sketch(...args) { + switchToTab('Image'); switch_to_img2img_tab(1); return Array.from(arguments); } function switch_to_inpaint(...args) { + switchToTab('Image'); switch_to_img2img_tab(2); return Array.from(arguments); } function switch_to_inpaint_sketch(...args) { + switchToTab('Image'); switch_to_img2img_tab(3); return Array.from(arguments); } function switch_to_extras(...args) { - gradioApp().querySelector('#tabs').querySelectorAll('button')[2].click(); + switchToTab('Process'); return Array.from(arguments); } function switch_to_control(...args) { - const tabs = Array.from(gradioApp().querySelector('#tabs').querySelectorAll('button')); - const btn = tabs.find((el) => el.innerText.toLowerCase() === 'control'); - btn.click(); + switchToTab('Control'); return Array.from(arguments); } diff --git a/modules/control/adapters.py b/modules/control/adapters.py index 5ad4f859c..39b05c9dd 100644 --- a/modules/control/adapters.py +++ b/modules/control/adapters.py @@ -1,5 +1,6 @@ import os import time +from typing import Union from diffusers import StableDiffusionPipeline, StableDiffusionXLPipeline, T2IAdapter, MultiAdapter, StableDiffusionAdapterPipeline, StableDiffusionXLAdapterPipeline # pylint: disable=unused-import from modules.shared import log from modules import errors @@ -33,7 +34,7 @@ all_models = {} all_models.update(predefined_sd15) all_models.update(predefined_sdxl) -cache_dir = 'models/control/adapters' +cache_dir = 'models/control/adapter' def list_models(refresh=False): @@ -105,10 +106,10 @@ def load(self, model_id: str = None) -> str: class AdapterPipeline(): - def __init__(self, adapter: T2IAdapter | list[T2IAdapter], pipeline: StableDiffusionXLPipeline | StableDiffusionPipeline, dtype = None): + def __init__(self, adapter: Union[T2IAdapter, list[T2IAdapter]], pipeline: Union[StableDiffusionXLPipeline, StableDiffusionPipeline], dtype = None): t0 = time.time() self.orig_pipeline = pipeline - self.pipeline = None + self.pipeline: Union[StableDiffusionXLPipeline, StableDiffusionPipeline] = None if pipeline is None: log.error(f'Control {what} pipeline: model not loaded') return diff --git a/modules/control/controlnets.py b/modules/control/controlnets.py index e183f99a6..4b1cf0869 100644 --- a/modules/control/controlnets.py +++ b/modules/control/controlnets.py @@ -1,8 +1,7 @@ import os import time -from diffusers import StableDiffusionPipeline -from diffusers import StableDiffusionXLPipeline -from diffusers import ControlNetModel, StableDiffusionControlNetPipeline, StableDiffusionXLControlNetPipeline +from typing import Union +from diffusers import StableDiffusionPipeline, StableDiffusionXLPipeline, ControlNetModel, StableDiffusionControlNetPipeline, StableDiffusionXLControlNetPipeline from modules.shared import log, opts from modules import errors @@ -38,11 +37,11 @@ all_models = {} all_models.update(predefined_sd15) all_models.update(predefined_sdxl) -cache_dir = 'models/control/controlnets' +cache_dir = 'models/control/controlnet' def find_models(): - path = os.path.join(opts.control_dir, 'controlnets') + path = os.path.join(opts.control_dir, 'controlnet') files = os.listdir(path) files = [f for f in files if f.endswith('.safetensors')] downloaded_models = {} @@ -123,14 +122,14 @@ def load(self, model_id: str = None) -> str: class ControlNetPipeline(): - def __init__(self, controlnet: ControlNetModel | list[ControlNetModel], pipeline: StableDiffusionXLPipeline | StableDiffusionPipeline, dtype = None): + def __init__(self, controlnet: Union[ControlNetModel, list[ControlNetModel]], pipeline: Union[StableDiffusionXLPipeline, StableDiffusionPipeline], dtype = None): t0 = time.time() self.orig_pipeline = pipeline self.pipeline = None if pipeline is None: log.error('Control model pipeline: model not loaded') return - if isinstance(pipeline, StableDiffusionXLPipeline): + elif isinstance(pipeline, StableDiffusionXLPipeline): self.pipeline = StableDiffusionXLControlNetPipeline( vae=pipeline.vae, text_encoder=pipeline.text_encoder, diff --git a/modules/control/controlnetslite.py b/modules/control/controlnetslite.py new file mode 100644 index 000000000..16a7505f2 --- /dev/null +++ b/modules/control/controlnetslite.py @@ -0,0 +1,135 @@ +import os +import time +from typing import Union +import numpy as np +from PIL import Image +from diffusers import StableDiffusionPipeline, StableDiffusionXLPipeline +from modules.shared import log, opts +from modules import errors +from modules.control.controlnetslite_model import ControlNetLLLite + + +what = 'ControlLLLite' +debug = log.trace if os.environ.get('SD_CONTROL_DEBUG', None) is not None else lambda *args, **kwargs: None +debug('Trace: CONTROL') +predefined_sd15 = { +} +predefined_sdxl = { + 'Canny XL': 'kohya-ss/controlnet-lllite/controllllite_v01032064e_sdxl_canny', + 'Canny anime XL': 'kohya-ss/controlnet-lllite/controllllite_v01032064e_sdxl_canny_anime', + 'Depth anime XL': 'kohya-ss/controlnet-lllite/controllllite_v01008016e_sdxl_depth_anime', + 'Blur anime XL': 'kohya-ss/controlnet-lllite/controllllite_v01016032e_sdxl_blur_anime_beta', + 'Pose anime XL': 'kohya-ss/controlnet-lllite/controllllite_v01032064e_sdxl_pose_anime', + 'Replicate anime XL': 'kohya-ss/controlnet-lllite/controllllite_v01032064e_sdxl_replicate_anime_v2', +} +models = {} +all_models = {} +all_models.update(predefined_sd15) +all_models.update(predefined_sdxl) +cache_dir = 'models/control/lite' + + +def find_models(): + path = os.path.join(opts.control_dir, 'lite') + files = os.listdir(path) + files = [f for f in files if f.endswith('.safetensors')] + downloaded_models = {} + for f in files: + basename = os.path.splitext(f)[0] + downloaded_models[basename] = os.path.join(path, f) + all_models.update(downloaded_models) + return downloaded_models + + +def list_models(refresh=False): + import modules.shared + global models # pylint: disable=global-statement + if not refresh and len(models) > 0: + return models + models = {} + if modules.shared.sd_model_type == 'none': + models = ['None'] + elif modules.shared.sd_model_type == 'sdxl': + models = ['None'] + sorted(predefined_sdxl) + sorted(find_models()) + elif modules.shared.sd_model_type == 'sd': + models = ['None'] + sorted(predefined_sd15) + sorted(find_models()) + else: + log.warning(f'Control {what} model list failed: unknown model type') + models = ['None'] + sorted(predefined_sd15) + sorted(predefined_sdxl) + sorted(find_models()) + debug(f'Control list {what}: path={cache_dir} models={models}') + return models + + +class ControlLLLite(): + def __init__(self, model_id: str = None, device = None, dtype = None, load_config = None): + self.model: ControlNetLLLite = None + self.model_id: str = model_id + self.device = device + self.dtype = dtype + self.load_config = { 'cache_dir': cache_dir } + if load_config is not None: + self.load_config.update(load_config) + if model_id is not None: + self.load() + + def reset(self): + if self.model is not None: + log.debug(f'Control {what} model unloaded') + self.model = None + self.model_id = None + + def load(self, model_id: str = None) -> str: + try: + t0 = time.time() + model_id = model_id or self.model_id + if model_id is None or model_id == 'None': + self.reset() + return + model_path = all_models[model_id] + if model_path == '': + return + if model_path is None: + log.error(f'Control {what} model load failed: id="{model_id}" error=unknown model id') + return + log.debug(f'Control {what} model loading: id="{model_id}" path="{model_path}" {self.load_config}') + if model_path.endswith('.safetensors'): + self.model = ControlNetLLLite(model_path) + else: + import huggingface_hub as hf + folder, filename = os.path.split(model_path) + model_path = hf.hf_hub_download(repo_id=folder, filename=f'{filename}.safetensors', cache_dir=cache_dir) + self.model = ControlNetLLLite(model_path) + if self.device is not None: + self.model.to(self.device) + if self.dtype is not None: + self.model.to(self.dtype) + t1 = time.time() + self.model_id = model_id + log.debug(f'Control {what} model loaded: id="{model_id}" path="{model_path}" time={t1-t0:.2f}') + return f'{what} loaded model: {model_id}' + except Exception as e: + log.error(f'Control {what} model load failed: id="{model_id}" error={e}') + errors.display(e, f'Control {what} load') + return f'{what} failed to load model: {model_id}' + + +class ControlLLitePipeline(): + def __init__(self, pipeline: Union[StableDiffusionXLPipeline, StableDiffusionPipeline]): + self.pipeline = pipeline + self.nets = [] + + def apply(self, controlnet: Union[ControlNetLLLite, list[ControlNetLLLite]], image, conditioning): + if image is None: + return + self.nets = [controlnet] if isinstance(controlnet, ControlNetLLLite) else controlnet + debug(f'Control {what} apply: models={len(self.nets)} image={image} conditioning={conditioning}') + weight = [conditioning] if isinstance(conditioning, float) else conditioning + images = [image] if isinstance(image, Image.Image) else image + images = [i.convert('RGB') for i in images] + for i, cn in enumerate(self.nets): + cn.apply(pipe=self.pipeline, cond=np.asarray(images[i % len(images)]), weight=weight[i % len(weight)]) + + def restore(self): + from modules.control.controlnetslite_model import clear_all_lllite + clear_all_lllite() + self.nets = [] diff --git a/modules/control/controlnetslite_model.py b/modules/control/controlnetslite_model.py new file mode 100644 index 000000000..991f3c31a --- /dev/null +++ b/modules/control/controlnetslite_model.py @@ -0,0 +1,202 @@ +# Credits: +# + +import re +import torch +from safetensors.torch import load_file + + +all_hack = {} + + +class LLLiteModule(torch.nn.Module): + def __init__( + self, + name: str, + is_conv2d: bool, + in_dim: int, + depth: int, + cond_emb_dim: int, + mlp_dim: int, + ): + super().__init__() + self.name = name + self.is_conv2d = is_conv2d + self.is_first = False + modules = [] + modules.append(torch.nn.Conv2d(3, cond_emb_dim // 2, kernel_size=4, stride=4, padding=0)) # to latent (from VAE) size*2 + if depth == 1: + modules.append(torch.nn.ReLU(inplace=True)) + modules.append(torch.nn.Conv2d(cond_emb_dim // 2, cond_emb_dim, kernel_size=2, stride=2, padding=0)) + elif depth == 2: + modules.append(torch.nn.ReLU(inplace=True)) + modules.append(torch.nn.Conv2d(cond_emb_dim // 2, cond_emb_dim, kernel_size=4, stride=4, padding=0)) + elif depth == 3: + # kernel size 8は大きすぎるので、4にする / kernel size 8 is too large, so set it to 4 + modules.append(torch.nn.ReLU(inplace=True)) + modules.append(torch.nn.Conv2d(cond_emb_dim // 2, cond_emb_dim // 2, kernel_size=4, stride=4, padding=0)) + modules.append(torch.nn.ReLU(inplace=True)) + modules.append(torch.nn.Conv2d(cond_emb_dim // 2, cond_emb_dim, kernel_size=2, stride=2, padding=0)) + self.conditioning1 = torch.nn.Sequential(*modules) + if self.is_conv2d: + self.down = torch.nn.Sequential( + torch.nn.Conv2d(in_dim, mlp_dim, kernel_size=1, stride=1, padding=0), + torch.nn.ReLU(inplace=True), + ) + self.mid = torch.nn.Sequential( + torch.nn.Conv2d(mlp_dim + cond_emb_dim, mlp_dim, kernel_size=1, stride=1, padding=0), + torch.nn.ReLU(inplace=True), + ) + self.up = torch.nn.Sequential( + torch.nn.Conv2d(mlp_dim, in_dim, kernel_size=1, stride=1, padding=0), + ) + else: + self.down = torch.nn.Sequential( + torch.nn.Linear(in_dim, mlp_dim), + torch.nn.ReLU(inplace=True), + ) + self.mid = torch.nn.Sequential( + torch.nn.Linear(mlp_dim + cond_emb_dim, mlp_dim), + torch.nn.ReLU(inplace=True), + ) + self.up = torch.nn.Sequential( + torch.nn.Linear(mlp_dim, in_dim), + ) + self.depth = depth + self.cond_image = None + self.cond_emb = None + + def set_cond_image(self, cond_image): + self.cond_image = cond_image + self.cond_emb = None + + def forward(self, x): + if self.cond_emb is None: + # print(f"cond_emb is None, {self.name}") + cx = self.conditioning1(self.cond_image.to(x.device, dtype=x.dtype)) + # if blk_shape is not None: + # b, c, h, w = blk_shape + # cx = torch.nn.functional.interpolate(cx, (h, w), mode="nearest-exact") + if not self.is_conv2d: + # reshape / b,c,h,w -> b,h*w,c + n, c, h, w = cx.shape + cx = cx.view(n, c, h * w).permute(0, 2, 1) + self.cond_emb = cx + cx = self.cond_emb + + # uncond/condでxはバッチサイズが2倍 + if x.shape[0] != cx.shape[0]: + if self.is_conv2d: + cx = cx.repeat(x.shape[0] // cx.shape[0], 1, 1, 1) + else: + # print("x.shape[0] != cx.shape[0]", x.shape[0], cx.shape[0]) + cx = cx.repeat(x.shape[0] // cx.shape[0], 1, 1) + + cx = torch.cat([cx, self.down(x)], dim=1 if self.is_conv2d else 2) + cx = self.mid(cx) + cx = self.up(cx) + return cx + + +def clear_all_lllite(): + global all_hack # pylint: disable=global-statement + for k, v in all_hack.items(): + k.forward = v + k.lllite_list = [] + all_hack = {} + return + + +class ControlNetLLLite(torch.nn.Module): # pylint: disable=abstract-method + def __init__(self, path: str): + super().__init__() + module_weights = {} + try: + state_dict = load_file(path) + except Exception as e: + raise RuntimeError(f"Failed to load {path}") from e + for key, value in state_dict.items(): + fragments = key.split(".") + module_name = fragments[0] + weight_name = ".".join(fragments[1:]) + if module_name not in module_weights: + module_weights[module_name] = {} + module_weights[module_name][weight_name] = value + modules = {} + for module_name, weights in module_weights.items(): + if "conditioning1.4.weight" in weights: + depth = 3 + elif weights["conditioning1.2.weight"].shape[-1] == 4: + depth = 2 + else: + depth = 1 + + module = LLLiteModule( + name=module_name, + is_conv2d=weights["down.0.weight"].ndim == 4, + in_dim=weights["down.0.weight"].shape[1], + depth=depth, + cond_emb_dim=weights["conditioning1.0.weight"].shape[0] * 2, + mlp_dim=weights["down.0.weight"].shape[0], + ) + # info = module.load_state_dict(weights) + modules[module_name] = module + setattr(self, module_name, module) + if len(modules) == 1: + module.is_first = True + + self.modules = modules + return + + @torch.no_grad() + def apply(self, pipe, cond, weight): # pylint: disable=arguments-differ + map_down_lllite_to_unet = {4: (1, 0), 5: (1, 1), 7: (2, 0), 8: (2, 1)} + model = pipe.unet + if type(cond) != torch.Tensor: + cond = torch.tensor(cond) + cond = cond/255 # 0-255 -> 0-1 + cond_image = cond.unsqueeze(dim=0).permute(0, 3, 1, 2) # h,w,c -> b,c,h,w + cond_image = cond_image * 2.0 - 1.0 # 0-1 -> -1-1 + + for module in self.modules.values(): + module.set_cond_image(cond_image) + for k, v in self.modules.items(): + k = k.replace('middle_block', 'middle_blocks_0') + match = re.match("lllite_unet_(.*)_blocks_(.*)_1_transformer_blocks_(.*)_(.*)_to_(.*)", k, re.M | re.I) + assert match, 'Failed to load ControlLLLite!' + root = match.group(1) + block = match.group(2) + block_number = match.group(3) + attn_name = match.group(4) + proj_name = match.group(5) + if root == 'input': + mapped_block, mapped_number = map_down_lllite_to_unet[int(block)] + b = model.down_blocks[mapped_block].attentions[int(mapped_number)].transformer_blocks[int(block_number)] + elif root == 'output': + # TODO: Map up unet blocks to lite blocks + print(f'Not implemented: {root}') + else: + b = model.mid_block.attentions[0].transformer_blocks[int(block_number)] + b = getattr(b, attn_name, None) + assert b is not None, 'Failed to load ControlLLLite!' + b = getattr(b, 'to_' + proj_name, None) + assert b is not None, 'Failed to load ControlLLLite!' + if not hasattr(b, 'lllite_list'): + b.lllite_list = [] + if len(b.lllite_list) == 0: + all_hack[b] = b.forward + b.forward = self.get_hacked_forward(original_forward=b.forward, model=model, blk=b) + b.lllite_list.append((weight, v)) + return + + def get_hacked_forward(self, original_forward, model, blk): + @torch.no_grad() + def forward(x, **kwargs): + hack = 0 + for weight, module in blk.lllite_list: + module.to(x.device) + module.to(x.dtype) + hack = hack + module(x) * weight + x = x + hack + return original_forward(x, **kwargs) + return forward diff --git a/modules/control/controlnetsxs.py b/modules/control/controlnetsxs.py index 8a97eabbb..1ddef9dc1 100644 --- a/modules/control/controlnetsxs.py +++ b/modules/control/controlnetsxs.py @@ -1,13 +1,12 @@ import os import time +from typing import Union from modules.shared import log, opts from modules import errors ok = True try: - from diffusers import StableDiffusionPipeline - from diffusers import StableDiffusionXLPipeline - from diffusers import ControlNetXSModel, StableDiffusionControlNetXSPipeline, StableDiffusionXLControlNetXSPipeline + from diffusers import StableDiffusionPipeline, StableDiffusionXLPipeline, ControlNetXSModel, StableDiffusionControlNetXSPipeline, StableDiffusionXLControlNetXSPipeline except Exception: from diffusers import ControlNetModel ControlNetXSModel = ControlNetModel # dummy @@ -27,11 +26,11 @@ all_models = {} all_models.update(predefined_sd15) all_models.update(predefined_sdxl) -cache_dir = 'models/control/controlnetsxs' +cache_dir = 'models/control/xs' def find_models(): - path = os.path.join(opts.control_dir, 'controlnetsxs') + path = os.path.join(opts.control_dir, 'xs') files = os.listdir(path) files = [f for f in files if f.endswith('.safetensors')] downloaded_models = {} @@ -115,7 +114,7 @@ def load(self, model_id: str = None, time_embedding_mix: float = 0.0) -> str: class ControlNetXSPipeline(): - def __init__(self, controlnet: ControlNetXSModel | list[ControlNetXSModel], pipeline: StableDiffusionXLPipeline | StableDiffusionPipeline, dtype = None): + def __init__(self, controlnet: Union[ControlNetXSModel, list[ControlNetXSModel]], pipeline: Union[StableDiffusionXLPipeline, StableDiffusionPipeline], dtype = None): t0 = time.time() self.orig_pipeline = pipeline self.pipeline = None diff --git a/modules/control/proc/canny.py b/modules/control/proc/canny.py index e68673d88..1f450b9f6 100644 --- a/modules/control/proc/canny.py +++ b/modules/control/proc/canny.py @@ -31,5 +31,6 @@ def __call__(self, input_image=None, low_threshold=100, high_threshold=200, dete if output_type == "pil": detected_map = Image.fromarray(detected_map) + detected_map = detected_map.convert('L') return detected_map diff --git a/modules/control/proc/edge.py b/modules/control/proc/edge.py index d068383c1..d2d8b2df2 100644 --- a/modules/control/proc/edge.py +++ b/modules/control/proc/edge.py @@ -58,6 +58,7 @@ def __call__(self, input_image=None, pf=True, mode='edge', detect_resolution=512 edge_map = cv2.resize(edge_map, (W, H), interpolation=cv2.INTER_LINEAR) if output_type == "pil": + edge_map = edge_map.convert('L') edge_map = Image.fromarray(edge_map) return edge_map diff --git a/modules/control/reference.py b/modules/control/reference.py index c5479698c..ccc3b2277 100644 --- a/modules/control/reference.py +++ b/modules/control/reference.py @@ -1,8 +1,8 @@ import time from diffusers import StableDiffusionPipeline, StableDiffusionXLPipeline -from modules.shared import log from modules.control.proc.reference_sd15 import StableDiffusionReferencePipeline from modules.control.proc.reference_sdxl import StableDiffusionXLReferencePipeline +from modules.shared import log what = 'Reference' diff --git a/modules/control/run.py b/modules/control/run.py index dd4fe83bd..c67a78120 100644 --- a/modules/control/run.py +++ b/modules/control/run.py @@ -11,6 +11,7 @@ from modules.control import processors from modules.control import controlnets # lllyasviel ControlNet from modules.control import controlnetsxs # VisLearn ControlNet-XS +from modules.control import controlnetslite # Kohya ControlLLLite from modules.control import adapters # TencentARC T2I-Adapter from modules.control import reference # ControlNet-Reference from modules.control import ipadapter # IP-Adapter @@ -156,7 +157,11 @@ def control_run(units: List[unit.Unit], inputs, inits, unit_type: str, is_genera active_strength.append(float(u.strength)) active_start.append(float(u.start)) active_end.append(float(u.end)) - p.guess_mode = u.guess + shared.log.debug(f'Control ControlNet-XS unit: process={u.process.processor_id} model={u.controlnet.model_id} strength={u.strength} guess={u.guess} start={u.start} end={u.end}') + elif unit_type == 'lite' and u.controlnet.model is not None: + active_process.append(u.process) + active_model.append(u.controlnet) + active_strength.append(float(u.strength)) shared.log.debug(f'Control ControlNet-XS unit: process={u.process.processor_id} model={u.controlnet.model_id} strength={u.strength} guess={u.guess} start={u.start} end={u.end}') elif unit_type == 'reference': p.override = u.override @@ -173,7 +178,7 @@ def control_run(units: List[unit.Unit], inputs, inits, unit_type: str, is_genera has_models = False selected_models: List[Union[controlnets.ControlNetModel, controlnetsxs.ControlNetXSModel, adapters.AdapterModel]] = None - if unit_type == 'adapter' or unit_type == 'controlnet' or unit_type == 'xs': + if unit_type == 'adapter' or unit_type == 'controlnet' or unit_type == 'xs' or unit_type == 'lite': if len(active_model) == 0: selected_models = None elif len(active_model) == 1: @@ -216,6 +221,14 @@ def control_run(units: List[unit.Unit], inputs, inits, unit_type: str, is_genera pipe = instance.pipeline if inits is not None: shared.log.warning('Control: ControlNet-XS does not support separate init image') + elif unit_type == 'lite' and has_models: + p.extra_generation_params["Control mode"] = 'ControlLLLite' + p.extra_generation_params["Control conditioning"] = use_conditioning + p.controlnet_conditioning_scale = use_conditioning + instance = controlnetslite.ControlLLitePipeline(shared.sd_model) + pipe = instance.pipeline + if inits is not None: + shared.log.warning('Control: ControlLLLite does not support separate init image') elif unit_type == 'reference': p.extra_generation_params["Control mode"] = 'Reference' p.extra_generation_params["Control attention"] = p.attention @@ -232,6 +245,8 @@ def control_run(units: List[unit.Unit], inputs, inits, unit_type: str, is_genera if len(active_strength) > 0: p.strength = active_strength[0] pipe = diffusers.AutoPipelineForImage2Image.from_pipe(shared.sd_model) # use set_diffuser_pipe + instance = None + debug(f'Control pipeline: class={pipe.__class__} args={vars(p)}') t1, t2, t3 = time.time(), 0, 0 status = True @@ -396,7 +411,7 @@ def control_run(units: List[unit.Unit], inputs, inits, unit_type: str, is_genera if hasattr(p, 'init_images'): del p.init_images # control never uses init_image as-is if pipe is not None: - if not has_models and (unit_type == 'controlnet' or unit_type == 'adapter' or unit_type == 'xs'): # run in txt2img or img2img mode + if not has_models and (unit_type == 'controlnet' or unit_type == 'adapter' or unit_type == 'xs' or unit_type == 'lite'): # run in txt2img or img2img mode if processed_image is not None: p.init_images = [processed_image] shared.sd_model = sd_models.set_diffuser_pipe(shared.sd_model, sd_models.DiffusersTaskType.IMAGE_2_IMAGE) @@ -411,6 +426,8 @@ def control_run(units: List[unit.Unit], inputs, inits, unit_type: str, is_genera shared.sd_model = sd_models.set_diffuser_pipe(shared.sd_model, sd_models.DiffusersTaskType.IMAGE_2_IMAGE) # only controlnet supports img2img else: shared.sd_model = sd_models.set_diffuser_pipe(shared.sd_model, sd_models.DiffusersTaskType.TEXT_2_IMAGE) + if unit_type == 'lite': + instance.apply(selected_models, p.image, use_conditioning) # pipeline output = None @@ -480,6 +497,8 @@ def control_run(units: List[unit.Unit], inputs, inits, unit_type: str, is_genera image_txt = f'| Frames {len(output_images)} | Size {output_images[0].width}x{output_images[0].height}' image_txt += f' | {util.dict2str(p.extra_generation_params)}' + if hasattr(instance, 'restore'): + instance.restore() restore_pipeline() debug(f'Control ready: {image_txt}') if is_generator: diff --git a/modules/control/test.py b/modules/control/test.py index 42c76fcd7..ba87420ea 100644 --- a/modules/control/test.py +++ b/modules/control/test.py @@ -112,7 +112,7 @@ def test_adapters(prompt, negative, image): model_id = 'None' if shared.state.interrupted: continue - output = image + output = image.copy() if model_id != 'None': adapter = adapters.Adapter(model_id=model_id, device=devices.device, dtype=devices.dtype) if adapter is None: @@ -122,6 +122,7 @@ def test_adapters(prompt, negative, image): pipe = adapters.AdapterPipeline(adapter=adapter.model, pipeline=shared.sd_model) pipe.pipeline.to(device=devices.device, dtype=devices.dtype) sd_models.set_diffuser_options(pipe) + image = image.convert('L') if 'Canny' in model_id or 'Sketch' in model_id else image.convert('RGB') try: res = pipe.pipeline(prompt=prompt, negative_prompt=negative, image=image, num_inference_steps=10, output_type='pil') output = res.images[0] @@ -201,3 +202,56 @@ def test_xs(prompt, negative, image): grid.paste(thumb, box=(x, y)) yield None, grid, None, images return None, grid, None, images # preview_process, output_image, output_video, output_gallery + + +def test_lite(prompt, negative, image): + from modules import devices, sd_models + from modules.control import controlnetslite + if image is None: + shared.log.error('Image not loaded') + return None, None, None + from PIL import ImageDraw, ImageFont + images = [] + for model_id in controlnetslite.list_models(): + if model_id is None: + model_id = 'None' + if shared.state.interrupted: + continue + output = image + if model_id != 'None': + lite = controlnetslite.ControlLLLite(model_id=model_id, device=devices.device, dtype=devices.dtype) + if lite is None: + shared.log.error(f'Control-LLite load failed: id="{model_id}"') + continue + shared.log.info(f'Testing ControlNet-XS: {model_id}') + pipe = controlnetslite.ControlLLitePipeline(pipeline=shared.sd_model) + pipe.apply(controlnet=lite.model, image=image, conditioning=1.0) + pipe.pipeline.to(device=devices.device, dtype=devices.dtype) + sd_models.set_diffuser_options(pipe) + try: + res = pipe.pipeline(prompt=prompt, negative_prompt=negative, image=image, num_inference_steps=10, output_type='pil') + output = res.images[0] + except Exception as e: + errors.display(e, f'ControlNet-XS {model_id} inference') + model_id = f'{model_id} error' + pipe.restore() + draw = ImageDraw.Draw(output) + font = ImageFont.truetype('DejaVuSansMono', 48) + draw.text((10, 10), model_id, (0,0,0), font=font) + draw.text((8, 8), model_id, (255,255,255), font=font) + images.append(output) + yield output, None, None, images + rows = round(math.sqrt(len(images))) + cols = math.ceil(len(images) / rows) + w, h = 256, 256 + size = (cols * w + cols, rows * h + rows) + grid = Image.new('RGB', size=size, color='black') + shared.log.info(f'Test ControlNet-XS: images={len(images)} grid={grid}') + for i, image in enumerate(images): + x = (i % cols * w) + (i % cols) + y = (i // cols * h) + (i // cols) + thumb = image.copy().convert('RGB') + thumb.thumbnail((w, h), Image.Resampling.HAMMING) + grid.paste(thumb, box=(x, y)) + yield None, grid, None, images + return None, grid, None, images # preview_process, output_image, output_video, output_gallery diff --git a/modules/control/unit.py b/modules/control/unit.py index ced17acfd..3e52531f8 100644 --- a/modules/control/unit.py +++ b/modules/control/unit.py @@ -4,6 +4,7 @@ from modules.control import processors from modules.control import controlnets from modules.control import controlnetsxs +from modules.control import controlnetslite from modules.control import adapters from modules.control import reference # pylint: disable=unused-import @@ -110,6 +111,8 @@ def upload_image(image_file): self.controlnet = controlnets.ControlNet(device=default_device, dtype=default_dtype) elif self.type == 'xs': self.controlnet = controlnetsxs.ControlNetXS(device=default_device, dtype=default_dtype) + elif self.type == 'lite': + self.controlnet = controlnetslite.ControlLLLite(device=default_device, dtype=default_dtype) elif self.type == 'reference': pass else: @@ -132,6 +135,11 @@ def upload_image(image_file): model_id.change(fn=self.controlnet.load, inputs=[model_id, extra_controls[0]], outputs=[result_txt], show_progress=True) if extra_controls is not None and len(extra_controls) > 0: extra_controls[0].change(fn=controlnetxs_extra, inputs=extra_controls) + elif self.type == 'lite': + if model_id is not None: + model_id.change(fn=self.controlnet.load, inputs=[model_id], outputs=[result_txt], show_progress=True) + if extra_controls is not None and len(extra_controls) > 0: + extra_controls[0].change(fn=controlnetxs_extra, inputs=extra_controls) elif self.type == 'reference': if extra_controls is not None and len(extra_controls) > 0: extra_controls[0].change(fn=reference_extra, inputs=extra_controls) diff --git a/modules/generation_parameters_copypaste.py b/modules/generation_parameters_copypaste.py index 1d8e285ec..f28dc233b 100644 --- a/modules/generation_parameters_copypaste.py +++ b/modules/generation_parameters_copypaste.py @@ -125,7 +125,8 @@ def bind_buttons(buttons, send_image, send_generate_info): for tabname, button in buttons.items(): source_text_component = send_generate_info if isinstance(send_generate_info, gr.components.Component) else None source_tabname = send_generate_info if isinstance(send_generate_info, str) else None - register_paste_params_button(ParamBinding(paste_button=button, tabname=tabname, source_text_component=source_text_component, source_image_component=send_image, source_tabname=source_tabname)) + bindings = ParamBinding(paste_button=button, tabname=tabname, source_text_component=source_text_component, source_image_component=send_image, source_tabname=source_tabname) + register_paste_params_button(bindings) def register_paste_params_button(binding: ParamBinding): diff --git a/modules/modelloader.py b/modules/modelloader.py index a8bf22339..05781f3a7 100644 --- a/modules/modelloader.py +++ b/modules/modelloader.py @@ -6,7 +6,7 @@ from urllib.parse import urlparse import PIL.Image as Image import rich.progress as p -from modules import shared +from modules import shared, errors from modules.upscaler import Upscaler, UpscalerLanczos, UpscalerNearest, UpscalerNone from modules.paths import script_path, models_path @@ -68,6 +68,7 @@ def download_civit_meta(model_path: str, model_id): return msg except Exception as e: msg = f'CivitAI download error: id={model_id} url={url} file={fn} {e}' + errors.display(e, 'CivitAI download error') shared.log.error(msg) return msg return f'CivitAI download error: id={model_id} url={url} code={r.status_code}' @@ -100,7 +101,8 @@ def download_civit_preview(model_path: str, preview_url: str): except Exception as e: os.remove(preview_file) res += f' error={e}' - shared.log.error(f'CivitAI download error: url={preview_url} file={preview_file} {e}') + shared.log.error(f'CivitAI download error: url={preview_url} file={preview_file} written={written} {e}') + errors.display(e, 'CivitAI download error') shared.state.end() if img is None: return res diff --git a/modules/shared.py b/modules/shared.py index 05cdcc9fb..f8c1e5fed 100644 --- a/modules/shared.py +++ b/modules/shared.py @@ -217,6 +217,8 @@ def readfile(filename, silent=False, lock=False): def writefile(data, filename, mode='w', silent=False): lock = None locked = False + import tempfile + def default(obj): log.error(f"Saving: {filename} not a valid object: {obj}") @@ -239,13 +241,19 @@ def default(obj): raise ValueError('not a valid object') lock = fasteners.InterProcessReaderWriterLock(f"{filename}.lock", logger=log) locked = lock.acquire_write_lock(blocking=True, timeout=3) - with open(filename, mode, encoding="utf8") as file: - file.write(output) + with tempfile.NamedTemporaryFile(mode=mode, encoding="utf8", delete=False, dir=os.path.dirname(filename)) as f: + f.write(output) + f.flush() + os.fsync(f.fileno()) + os.replace(f.name, filename) + # with open(filename, mode=mode, encoding="utf8") as file: + # file.write(output) t1 = time.time() if not silent: log.debug(f'Save: file="{filename}" json={len(data)} bytes={len(output)} time={t1-t0:.3f}') except Exception as e: log.error(f'Saving failed: {filename} {e}') + errors.display(e, 'Saving failed') finally: if lock is not None: lock.release_read_lock() diff --git a/modules/ui_common.py b/modules/ui_common.py index 368f61730..ca8efa096 100644 --- a/modules/ui_common.py +++ b/modules/ui_common.py @@ -13,6 +13,8 @@ folder_symbol = symbols.folder +debug = shared.log.trace if os.environ.get('SD_PASTE_DEBUG', None) is not None else lambda *args, **kwargs: None +debug('Trace: PASTE') def update_generation_info(generation_info, html_info, img_index): @@ -214,7 +216,10 @@ def create_output_panel(tabname, preview=True): clip_files.click(fn=None, _js='clip_gallery_urls', inputs=[result_gallery], outputs=[]) save = gr.Button('Save', elem_id=f'save_{tabname}') delete = gr.Button('Delete', elem_id=f'delete_{tabname}') - buttons = parameters_copypaste.create_buttons(["img2img", "inpaint", "extras", "control"]) + if shared.backend == shared.Backend.ORIGINAL: + buttons = parameters_copypaste.create_buttons(["img2img", "inpaint", "extras"]) + else: + buttons = parameters_copypaste.create_buttons(["img2img", "inpaint", "control", "extras"]) download_files = gr.File(None, file_count="multiple", interactive=False, show_label=False, visible=False, elem_id=f'download_files_{tabname}') with gr.Group(): @@ -245,9 +250,9 @@ def create_output_panel(tabname, preview=True): else: paste_field_names = [] for paste_tabname, paste_button in buttons.items(): - parameters_copypaste.register_paste_params_button(parameters_copypaste.ParamBinding( - paste_button=paste_button, tabname=paste_tabname, source_tabname=("txt2img" if tabname == "txt2img" else None), source_image_component=result_gallery, paste_field_names=paste_field_names - )) + debug(f'Create output panel: button={paste_button} tabname={paste_tabname}') + bindings = parameters_copypaste.ParamBinding(paste_button=paste_button, tabname=paste_tabname, source_tabname=("txt2img" if tabname == "txt2img" else None), source_image_component=result_gallery, paste_field_names=paste_field_names) + parameters_copypaste.register_paste_params_button(bindings) return result_gallery, generation_info, html_info, html_info_formatted, html_log diff --git a/modules/ui_control.py b/modules/ui_control.py index 607a78f7e..468aa3af7 100644 --- a/modules/ui_control.py +++ b/modules/ui_control.py @@ -3,6 +3,7 @@ from modules.control import unit from modules.control import controlnets # lllyasviel ControlNet from modules.control import controlnetsxs # vislearn ControlNet-XS +from modules.control import controlnetslite # vislearn ControlNet-XS from modules.control import adapters # TencentARC T2I-Adapter from modules.control import processors # patrickvonplaten controlnet_aux from modules.control import reference # reference pipeline @@ -12,7 +13,7 @@ gr_height = 512 -max_units = 10 +max_units = 5 units: list[unit.Unit] = [] # main state variable input_source = None input_init = None @@ -23,15 +24,17 @@ def initialize(): from modules import devices shared.log.debug(f'Control initialize: models={shared.opts.control_dir}') - controlnets.cache_dir = os.path.join(shared.opts.control_dir, 'controlnets') - controlnetsxs.cache_dir = os.path.join(shared.opts.control_dir, 'controlnetsxs') - adapters.cache_dir = os.path.join(shared.opts.control_dir, 'adapters') - processors.cache_dir = os.path.join(shared.opts.control_dir, 'processors') + controlnets.cache_dir = os.path.join(shared.opts.control_dir, 'controlnet') + controlnetsxs.cache_dir = os.path.join(shared.opts.control_dir, 'xs') + controlnetslite.cache_dir = os.path.join(shared.opts.control_dir, 'lite') + adapters.cache_dir = os.path.join(shared.opts.control_dir, 'adapter') + processors.cache_dir = os.path.join(shared.opts.control_dir, 'processor') unit.default_device = devices.device unit.default_dtype = devices.dtype os.makedirs(shared.opts.control_dir, exist_ok=True) os.makedirs(controlnets.cache_dir, exist_ok=True) os.makedirs(controlnetsxs.cache_dir, exist_ok=True) + os.makedirs(controlnetslite.cache_dir, exist_ok=True) os.makedirs(adapters.cache_dir, exist_ok=True) os.makedirs(processors.cache_dir, exist_ok=True) @@ -58,7 +61,7 @@ def return_controls(res): def generate_click(job_id: str, active_tab: str, *args): from modules.control.run import control_run shared.log.debug(f'Control: tab={active_tab} job={job_id} args={args}') - if active_tab not in ['controlnet', 'xs', 'adapter', 'reference']: + if active_tab not in ['controlnet', 'xs', 'adapter', 'reference', 'lite']: return None, None, None, None, f'Control: Unknown mode: {active_tab} args={args}' shared.state.begin('control') progress.add_task_to_queue(job_id) @@ -203,14 +206,14 @@ def create_ui(_blocks: gr.Blocks=None): with gr.Row(elem_id='control_settings'): with gr.Accordion(open=False, label="Input", elem_id="control_input", elem_classes=["small-accordion"]): - with gr.Row(): - input_type = gr.Radio(label="Input type", choices=['Control only', 'Init image same as control', 'Separate init image'], value='Control only', type='index', elem_id='control_input_type') - with gr.Row(): - denoising_strength = gr.Slider(minimum=0.01, maximum=0.99, step=0.01, label='Denoising strength', value=0.50, elem_id="control_denoising_strength") with gr.Row(): show_ip = gr.Checkbox(label="Enable IP adapter", value=False, elem_id="control_show_ip") with gr.Row(): show_preview = gr.Checkbox(label="Show preview", value=False, elem_id="control_show_preview") + with gr.Row(): + input_type = gr.Radio(label="Input type", choices=['Control only', 'Init image same as control', 'Separate init image'], value='Control only', type='index', elem_id='control_input_type') + with gr.Row(): + denoising_strength = gr.Slider(minimum=0.01, maximum=0.99, step=0.01, label='Denoising strength', value=0.50, elem_id="control_denoising_strength") resize_mode, resize_name, width, height, scale_by, selected_scale_tab, resize_time = ui.create_resize_inputs('control', [], time_selector=True, scale_visible=False, mode='Fixed') @@ -402,10 +405,10 @@ def create_ui(_blocks: gr.Blocks=None): extra_controls = [ gr.Slider(label="Control factor", minimum=0.0, maximum=1.0, step=0.05, value=1.0, scale=3), ] - num_adaptor_units = gr.Slider(label="Units", minimum=1, maximum=max_units, step=1, value=1, scale=1) - adaptor_ui_units = [] # list of hidable accordions + num_adapter_units = gr.Slider(label="Units", minimum=1, maximum=max_units, step=1, value=1, scale=1) + adapter_ui_units = [] # list of hidable accordions for i in range(max_units): - with gr.Accordion(f'Adapter unit {i+1}', visible= i < num_adaptor_units.value) as unit_ui: + with gr.Accordion(f'Adapter unit {i+1}', visible= i < num_adapter_units.value) as unit_ui: with gr.Row(): with gr.Column(): with gr.Row(): @@ -417,7 +420,7 @@ def create_ui(_blocks: gr.Blocks=None): reset_btn = ui_components.ToolButton(value=ui_symbols.reset) image_upload = gr.UploadButton(label=ui_symbols.upload, file_types=['image'], elem_classes=['form', 'gradio-button', 'tool']) process_btn= ui_components.ToolButton(value=ui_symbols.preview) - adaptor_ui_units.append(unit_ui) + adapter_ui_units.append(unit_ui) units.append(unit.Unit( unit_type = 'adapter', result_txt = result_txt, @@ -435,7 +438,47 @@ def create_ui(_blocks: gr.Blocks=None): ) if i == 0: units[-1].enabled = True # enable first unit in group - num_adaptor_units.change(fn=display_units, inputs=[num_adaptor_units], outputs=adaptor_ui_units) + num_adapter_units.change(fn=display_units, inputs=[num_adapter_units], outputs=adapter_ui_units) + + with gr.Tab('Lite') as _tab_lite: + gr.HTML('Control LLLite') + with gr.Row(): + extra_controls = [ + ] + num_lite_units = gr.Slider(label="Units", minimum=1, maximum=max_units, step=1, value=1, scale=1) + lite_ui_units = [] # list of hidable accordions + for i in range(max_units): + with gr.Accordion(f'Control unit {i+1}', visible= i < num_lite_units.value) as unit_ui: + with gr.Row(): + with gr.Column(): + with gr.Row(): + enabled_cb = gr.Checkbox(value= i == 0, label="Enabled") + process_id = gr.Dropdown(label="Processor", choices=processors.list_models(), value='None') + model_id = gr.Dropdown(label="Model", choices=controlnetslite.list_models(), value='None') + ui_common.create_refresh_button(model_id, controlnetslite.list_models, lambda: {"choices": controlnetslite.list_models(refresh=True)}, 'refresh_lite_models') + model_strength = gr.Slider(label="Strength", minimum=0.01, maximum=1.0, step=0.01, value=1.0-i/10) + reset_btn = ui_components.ToolButton(value=ui_symbols.reset) + image_upload = gr.UploadButton(label=ui_symbols.upload, file_types=['image'], elem_classes=['form', 'gradio-button', 'tool']) + process_btn= ui_components.ToolButton(value=ui_symbols.preview) + lite_ui_units.append(unit_ui) + units.append(unit.Unit( + unit_type = 'lite', + result_txt = result_txt, + image_input = input_image, + enabled_cb = enabled_cb, + reset_btn = reset_btn, + process_id = process_id, + model_id = model_id, + model_strength = model_strength, + preview_process = preview_process, + preview_btn = process_btn, + image_upload = image_upload, + extra_controls = extra_controls, + ) + ) + if i == 0: + units[-1].enabled = True # enable first unit in group + num_lite_units.change(fn=display_units, inputs=[num_lite_units], outputs=lite_ui_units) with gr.Tab('Reference') as _tab_reference: gr.HTML('ControlNet reference-only control') @@ -555,16 +598,19 @@ def create_ui(_blocks: gr.Blocks=None): generation_parameters_copypaste.register_paste_params_button(bindings) if os.environ.get('SD_CONTROL_DEBUG', None) is not None: # debug only - from modules.control.test import test_processors, test_controlnets, test_adapters, test_xs + from modules.control.test import test_processors, test_controlnets, test_adapters, test_xs, test_lite gr.HTML('

Debug


') with gr.Row(): run_test_processors_btn = gr.Button(value="Test:Processors", variant='primary', elem_classes=['control-button']) run_test_controlnets_btn = gr.Button(value="Test:ControlNets", variant='primary', elem_classes=['control-button']) run_test_xs_btn = gr.Button(value="Test:ControlNets-XS", variant='primary', elem_classes=['control-button']) run_test_adapters_btn = gr.Button(value="Test:Adapters", variant='primary', elem_classes=['control-button']) + run_test_lite_btn = gr.Button(value="Test:Control-LLLite", variant='primary', elem_classes=['control-button']) + run_test_processors_btn.click(fn=test_processors, inputs=[input_image], outputs=[preview_process, output_image, output_video, output_gallery]) run_test_controlnets_btn.click(fn=test_controlnets, inputs=[prompt, negative, input_image], outputs=[preview_process, output_image, output_video, output_gallery]) run_test_xs_btn.click(fn=test_xs, inputs=[prompt, negative, input_image], outputs=[preview_process, output_image, output_video, output_gallery]) run_test_adapters_btn.click(fn=test_adapters, inputs=[prompt, negative, input_image], outputs=[preview_process, output_image, output_video, output_gallery]) + run_test_lite_btn.click(fn=test_lite, inputs=[prompt, negative, input_image], outputs=[preview_process, output_image, output_video, output_gallery]) return [(control_ui, 'Control', 'control')] diff --git a/modules/ui_models.py b/modules/ui_models.py index 89af09737..ab9870c05 100644 --- a/modules/ui_models.py +++ b/modules/ui_models.py @@ -515,7 +515,8 @@ def civit_search_metadata(civit_previews_rehash, title): continue for item in page.list_items(): meta = os.path.splitext(item['filename'])[0] + '.json' - if ('card-no-preview.png' in item['preview'] or not os.path.isfile(meta)) and os.path.isfile(item['filename']): + has_meta = os.path.isfile(meta) and os.stat(meta).st_size > 0 + if ('card-no-preview.png' in item['preview'] or not has_meta) and os.path.isfile(item['filename']): sha = item.get('hash', None) found = False if sha is not None and len(sha) > 0: diff --git a/pyproject.toml b/pyproject.toml index afb5b4fd0..9df236ad9 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,5 +1,5 @@ [tool.ruff] -target-version = "py310" +target-version = "py39" select = [ "F", "E", diff --git a/requirements.txt b/requirements.txt index 5e3c49937..04634b171 100644 --- a/requirements.txt +++ b/requirements.txt @@ -50,8 +50,8 @@ clip-interrogator==0.6.0 antlr4-python3-runtime==4.9.3 requests==2.31.0 tqdm==4.66.1 -accelerate==0.24.1 -opencv-python-headless==4.7.0.72 +accelerate==0.25.0 +opencv-python-headless==4.8.1.78 diffusers==0.24.0 einops==0.4.1 gradio==3.43.2 @@ -65,9 +65,9 @@ pytorch_lightning==1.9.4 tokenizers==0.15.0 transformers==4.36.2 tomesd==0.1.3 -urllib3==1.26.15 +urllib3==1.26.18 Pillow==10.1.0 -timm==0.9.7 +timm==0.9.12 pydantic==1.10.13 -typing-extensions==4.8.0 +typing-extensions==4.9.0 peft diff --git a/wiki b/wiki index 4d9737acc..7dadbd912 160000 --- a/wiki +++ b/wiki @@ -1 +1 @@ -Subproject commit 4d9737acc348cfc0ae0b139ec5e39ae61967e7ac +Subproject commit 7dadbd912a1854bad2d7c6463eaecc458f2808c3 From 0c38745ffac169a5f6ff7af4dae4fb34940611b0 Mon Sep 17 00:00:00 2001 From: Vladimir Mandic Date: Mon, 25 Dec 2023 18:07:37 -0500 Subject: [PATCH 119/143] fix atomic write --- modules/shared.py | 18 ++++++++++-------- 1 file changed, 10 insertions(+), 8 deletions(-) diff --git a/modules/shared.py b/modules/shared.py index f8c1e5fed..cd70bf54d 100644 --- a/modules/shared.py +++ b/modules/shared.py @@ -214,7 +214,7 @@ def readfile(filename, silent=False, lock=False): return data -def writefile(data, filename, mode='w', silent=False): +def writefile(data, filename, mode='w', silent=False, atomic=False): lock = None locked = False import tempfile @@ -241,13 +241,15 @@ def default(obj): raise ValueError('not a valid object') lock = fasteners.InterProcessReaderWriterLock(f"{filename}.lock", logger=log) locked = lock.acquire_write_lock(blocking=True, timeout=3) - with tempfile.NamedTemporaryFile(mode=mode, encoding="utf8", delete=False, dir=os.path.dirname(filename)) as f: - f.write(output) - f.flush() - os.fsync(f.fileno()) - os.replace(f.name, filename) - # with open(filename, mode=mode, encoding="utf8") as file: - # file.write(output) + if atomic: + with tempfile.NamedTemporaryFile(mode=mode, encoding="utf8", delete=False, dir=os.path.dirname(filename)) as f: + f.write(output) + f.flush() + os.fsync(f.fileno()) + os.replace(f.name, filename) + else: + with open(filename, mode=mode, encoding="utf8") as file: + file.write(output) t1 = time.time() if not silent: log.debug(f'Save: file="{filename}" json={len(data)} bytes={len(output)} time={t1-t0:.3f}') From 03c59c72cf676300cec2239020f78378d56a9040 Mon Sep 17 00:00:00 2001 From: Vladimir Mandic Date: Tue, 26 Dec 2023 08:02:46 -0500 Subject: [PATCH 120/143] fix model detection compatibility item --- modules/lowvram.py | 4 ++-- modules/sd_models.py | 24 ++++++++++++++---------- 2 files changed, 16 insertions(+), 12 deletions(-) diff --git a/modules/lowvram.py b/modules/lowvram.py index cb684acd2..5afb7e7c8 100644 --- a/modules/lowvram.py +++ b/modules/lowvram.py @@ -52,7 +52,7 @@ def first_stage_model_decode_wrap(z): return first_stage_model_decode(z) # for SD1, cond_stage_model is CLIP and its NN is in the tranformer frield, but for SD2, it's open clip, and it's in model field - if hasattr(sd_model.cond_stage_model, 'model'): + if hasattr(sd_model, 'cond_stage_model') and hasattr(sd_model.cond_stage_model, 'model'): sd_model.cond_stage_model.transformer = sd_model.cond_stage_model.model # remove several big modules: cond, first_stage, depth/embedder (if applicable), and unet from the model and then @@ -73,7 +73,7 @@ def first_stage_model_decode_wrap(z): sd_model.embedder.register_forward_pre_hook(send_me_to_gpu) parents[sd_model.cond_stage_model.transformer] = sd_model.cond_stage_model - if hasattr(sd_model.cond_stage_model, 'model'): + if hasattr(sd_model, 'cond_stage_model') and hasattr(sd_model.cond_stage_model, 'model'): sd_model.cond_stage_model.model = sd_model.cond_stage_model.transformer del sd_model.cond_stage_model.transformer diff --git a/modules/sd_models.py b/modules/sd_models.py index 46af45474..33130a2fc 100644 --- a/modules/sd_models.py +++ b/modules/sd_models.py @@ -476,7 +476,7 @@ def load_model_weights(model: torch.nn.Module, checkpoint_info: CheckpointInfo, model.is_sdxl = False # a1111 compatibility item model.is_sd2 = hasattr(model.cond_stage_model, 'model') # a1111 compatibility item model.is_sd1 = not hasattr(model.cond_stage_model, 'model') # a1111 compatibility item - model.logvar = model.logvar.to(devices.device) # fix for training + model.logvar = model.logvar.to(devices.device) if hasattr(model, 'logvar') else None # fix for training shared.opts.data["sd_checkpoint_hash"] = checkpoint_info.sha256 sd_vae.delete_base_vae() sd_vae.clear_loaded_vae() @@ -683,12 +683,12 @@ def detect_pipeline(f: str, op: str = 'model', warning=True): def copy_diffuser_options(new_pipe, orig_pipe): new_pipe.sd_checkpoint_info = orig_pipe.sd_checkpoint_info new_pipe.sd_model_checkpoint = orig_pipe.sd_model_checkpoint - new_pipe.sd_model_hash = orig_pipe.sd_model_hash - new_pipe.has_accelerate = orig_pipe.has_accelerate - new_pipe.embedding_db = orig_pipe.embedding_db - new_pipe.is_sdxl = getattr(orig_pipe, 'is_sdxl', False) # pylint: disable=attribute-defined-outside-init # a1111 compatibility item - new_pipe.is_sd2 = getattr(orig_pipe, 'is_sd2', False) # pylint: disable=attribute-defined-outside-init - new_pipe.is_sd1 = getattr(orig_pipe, 'is_sd1', True) # pylint: disable=attribute-defined-outside-init + new_pipe.embedding_db = getattr(orig_pipe, 'embedding_db', None) + new_pipe.sd_model_hash = getattr(orig_pipe, 'sd_model_hash', None) + new_pipe.has_accelerate = getattr(orig_pipe, 'has_accelerate', False) + new_pipe.is_sdxl = getattr(orig_pipe, 'is_sdxl', False) # a1111 compatibility item + new_pipe.is_sd2 = getattr(orig_pipe, 'is_sd2', False) + new_pipe.is_sd1 = getattr(orig_pipe, 'is_sd1', True) @@ -970,6 +970,10 @@ def load_diffuser(checkpoint_info=None, already_loaded_state_dict=None, timer=No sd_model.sd_model_hash = checkpoint_info.calculate_shorthash() # pylint: disable=attribute-defined-outside-init sd_model.sd_checkpoint_info = checkpoint_info # pylint: disable=attribute-defined-outside-init sd_model.sd_model_checkpoint = checkpoint_info.filename # pylint: disable=attribute-defined-outside-init + sd_model.is_sdxl = False # a1111 compatibility item + sd_model.is_sd2 = hasattr(sd_model, 'cond_stage_model') and hasattr(sd_model.cond_stage_model, 'model') # a1111 compatibility item + sd_model.is_sd1 = not sd_model.is_sd2 # a1111 compatibility item + sd_model.logvar = sd_model.logvar.to(devices.device) if hasattr(sd_model, 'logvar') else None # fix for training shared.opts.data["sd_checkpoint_hash"] = checkpoint_info.sha256 if hasattr(sd_model, "set_progress_bar_config"): sd_model.set_progress_bar_config(bar_format='Progress {rate_fmt}{postfix} {bar} {percentage:3.0f}% {n_fmt}/{total_fmt} {elapsed} {remaining}', ncols=80, colour='#327fba') @@ -1069,9 +1073,9 @@ def set_diffuser_pipe(pipe, new_pipe_type): new_pipe.embedding_db = embedding_db new_pipe.image_encoder = image_encoder new_pipe.feature_extractor = feature_extractor - new_pipe.is_sdxl = pipe.is_sdxl - new_pipe.is_sd2 = pipe.is_sd2 - new_pipe.is_sd1 = pipe.is_sd1 + new_pipe.is_sdxl = getattr(pipe, 'is_sdxl', False) # a1111 compatibility item + new_pipe.is_sd2 = getattr(pipe, 'is_sd2', False) + new_pipe.is_sd1 = getattr(pipe, 'is_sd1', True) shared.log.debug(f"Pipeline class change: original={pipe.__class__.__name__} target={new_pipe.__class__.__name__}") pipe = new_pipe return pipe From 54deae774610474be52be63e787000aacb2f387e Mon Sep 17 00:00:00 2001 From: Vladimir Mandic Date: Tue, 26 Dec 2023 13:06:14 -0500 Subject: [PATCH 121/143] change onboarding and remove download default model --- CHANGELOG.md | 6 ++- SECURITY.md | 2 +- html/locale_en.json | 11 +++-- html/reference.json | 20 ++++++++ installer.py | 2 +- javascript/sdnext.css | 3 ++ javascript/settings.js | 39 +++++++++++++++- .../Reference/dreamshaperXL_turboDpmppSDE.jpg | Bin 0 -> 54282 bytes models/Reference/dreamshaper_8.jpg | Bin 0 -> 55225 bytes .../Reference/juggernautXL_v7Rundiffusion.jpg | Bin 0 -> 41737 bytes models/Reference/juggernaut_reborn.jpg | Bin 0 -> 40406 bytes modules/control/run.py | 24 +++++----- modules/control/test.py | 32 ++++++------- modules/control/unit.py | 22 ++++----- .../{controlnets.py => units/controlnet.py} | 0 modules/control/{ => units}/ipadapter.py | 0 .../{controlnetslite.py => units/lite.py} | 4 +- .../lite_model.py} | 0 modules/control/{ => units}/reference.py | 0 .../{adapters.py => units/t2iadapter.py} | 0 .../control/{controlnetsxs.py => units/xs.py} | 0 modules/modelloader.py | 28 ++++++++++- modules/processing.py | 8 ++-- modules/sd_models.py | 3 +- modules/sd_samplers_common.py | 2 + modules/ui.py | 18 ++++--- modules/ui_control.py | 44 +++++++++--------- scripts/ipadapter.py | 23 +++++---- 28 files changed, 195 insertions(+), 96 deletions(-) create mode 100644 models/Reference/dreamshaperXL_turboDpmppSDE.jpg create mode 100644 models/Reference/dreamshaper_8.jpg create mode 100644 models/Reference/juggernautXL_v7Rundiffusion.jpg create mode 100644 models/Reference/juggernaut_reborn.jpg rename modules/control/{controlnets.py => units/controlnet.py} (100%) rename modules/control/{ => units}/ipadapter.py (100%) rename modules/control/{controlnetslite.py => units/lite.py} (97%) rename modules/control/{controlnetslite_model.py => units/lite_model.py} (100%) rename modules/control/{ => units}/reference.py (100%) rename modules/control/{adapters.py => units/t2iadapter.py} (100%) rename modules/control/{controlnetsxs.py => units/xs.py} (100%) diff --git a/CHANGELOG.md b/CHANGELOG.md index b676ddf98..a792bff42 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,6 @@ # Change Log for SD.Next -## Update for 2023-12-25 +## Update for 2023-12-26 *Note*: based on `diffusers==0.25.0.dev0` @@ -52,6 +52,9 @@ - allow setting of resize method directly in image tab (previously via settings -> upscaler_for_img2img) - **General** + - new **onboarding** + if no models are found during startup, app will no longer ask to download default checkpoint + instead, it will show message in UI with options to change model path or download any of the reference checkpoints - support for **Torch 2.1.2** - **Process** create videos from batch or folder processing supports *GIF*, *PNG* and *MP4* with full interpolation, scene change detection, etc. @@ -98,6 +101,7 @@ - **chaiNNer** fix `NaN` issues due to autocast - **Upscale** increase limit from 4x to 8x given the quality of some upscalers - **Extra Networks** fix sort + - reduced default **CFG scale** from 6 to 4 to be more out-of-the-box compatibile with LCM/Turbo models - disable google fonts check on server startup - fix torchvision/basicsr compatibility - fix styles quick save diff --git a/SECURITY.md b/SECURITY.md index af9687f8c..9c1e11bb0 100644 --- a/SECURITY.md +++ b/SECURITY.md @@ -32,5 +32,5 @@ Any code commit is validated before merge - Download extensions and themes indexes from automatically updated indexes - Download required packages and repositories from GitHub during installation/upgrade - Download installed/enabled extensions -- Download default model from official repository +- Download models from CivitAI and/or Huggingface when instructed by user - Submit benchmark info upon user interaction diff --git a/html/locale_en.json b/html/locale_en.json index 8bce40518..91789b742 100644 --- a/html/locale_en.json +++ b/html/locale_en.json @@ -43,17 +43,18 @@ "tabs": [ {"id":"","label":"Text","localized":"","hint":"Create image from text"}, {"id":"","label":"Image","localized":"","hint":"Create image from image"}, + {"id":"","label":"Control","localized":"","hint":"Create image with additional control"}, {"id":"","label":"Process","localized":"","hint":"Process existing image"}, - {"id":"","label":"Train","localized":"","hint":"Run training or model merging"}, + {"id":"","label":"Interrogate","localized":"","hint":"Run interrogate to get description of your image"}, + {"id":"","label":"Train","localized":"","hint":"Run training"}, {"id":"","label":"Models","localized":"","hint":"Convert or merge your models"}, - {"id":"","label":"Interrogator","localized":"","hint":"Run interrogate to get description of your image"}, - {"id":"","label":"System Info","localized":"","hint":"System information"}, {"id":"","label":"Agent Scheduler","localized":"","hint":"Enqueue your generate requests and run them in the background"}, {"id":"","label":"Image Browser","localized":"","hint":"Browse through your generated image database"}, {"id":"","label":"System","localized":"","hint":"System settings and information"}, + {"id":"","label":"System Info","localized":"","hint":"System information"}, {"id":"","label":"Settings","localized":"","hint":"Application settings"}, - {"id":"","label":"Extensions","localized":"","hint":"Application extensions"}, - {"id":"","label":"Script","localized":"","hint":"Addtional scripts to be used"} + {"id":"","label":"Script","localized":"","hint":"Addtional scripts to be used"}, + {"id":"","label":"Extensions","localized":"","hint":"Application extensions"} ], "action panel": [ {"id":"","label":"Generate","localized":"","hint":"Start processing"}, diff --git a/html/reference.json b/html/reference.json index 5c8d26378..dc33c0481 100644 --- a/html/reference.json +++ b/html/reference.json @@ -1,4 +1,24 @@ { + "DreamShaper SD 1.5 v8": { + "path": "dreamshaper_8.safetensors@https://civitai.com/api/download/models/128713", + "desc": "Showcase finetuned model based on Stable diffusion 1.5", + "preview": "dreamshaper_8.jpg" + }, + "DreamShaper SD XL Turbo": { + "path": "dreamshaperXL_turboDpmppSDE.safetensors@https://civitai.com/api/download/models/251662", + "desc": "Showcase finetuned model based on Stable diffusion XL", + "preview": "dreamshaperXL_turboDpmppSDE.jpg" + }, + "Juggernaut Reborn": { + "path": "juggernaut_reborn.safetensors@https://civitai.com/api/download/models/274039", + "desc": "Showcase finetuned model based on Stable diffusion 1.5", + "preview": "juggernaut_reborn.jpg" + }, + "Juggernaut XL v7 RunDiffusion": { + "path": "juggernautXL_v7Rundiffusion.safetensors@https://civitai.com/api/download/models/240840", + "desc": "Showcase finetuned model based on Stable diffusion XL", + "preview": "juggernautXL_v7Rundiffusion.jpg" + }, "RunwayML SD 1.5": { "path": "runwayml/stable-diffusion-v1-5", "desc": "Stable Diffusion 1.5 is the base model all other 1.5 checkpoint were trained from. It's a latent text-to-image diffusion model capable of generating photo-realistic images given any text input. The Stable-Diffusion-v1-5 checkpoint was initialized with the weights of the Stable-Diffusion-v1-2 checkpoint and subsequently fine-tuned on 595k steps at resolution 512x512.", diff --git a/installer.py b/installer.py index 1db105dca..2a15bb4c4 100644 --- a/installer.py +++ b/installer.py @@ -363,7 +363,7 @@ def check_torch(): log.debug(f'Torch allowed: cuda={allow_cuda} rocm={allow_rocm} ipex={allow_ipex} diml={allow_directml} openvino={allow_openvino}') torch_command = os.environ.get('TORCH_COMMAND', '') xformers_package = os.environ.get('XFORMERS_PACKAGE', 'none') - install('onnxruntime', 'onnxruntime', ignore=True) + install('onnxruntime onnxruntimegpu', 'onnxruntime', ignore=True) if torch_command != '': pass elif allow_cuda and (shutil.which('nvidia-smi') is not None or args.use_xformers or os.path.exists(os.path.join(os.environ.get('SystemRoot') or r'C:\Windows', 'System32', 'nvidia-smi.exe'))): diff --git a/javascript/sdnext.css b/javascript/sdnext.css index b4d7fce93..86a6bcbdb 100644 --- a/javascript/sdnext.css +++ b/javascript/sdnext.css @@ -263,6 +263,9 @@ table.settings-value-table td { padding: 0.4em; border: 1px solid #ccc; max-widt .processor-settings { padding: 0 !important; max-width: 300px; } .processor-group>div { flex-flow: wrap;gap: 1em; } +/* main info */ +.main-info { font-weight: var(--section-header-text-weight); color: var(--body-text-color-subdued); padding: 1em !important; margin-top: 2em !important; line-height: var(--line-lg) !important; } + /* loader */ .splash { position: fixed; top: 0; left: 0; width: 100vw; height: 100vh; z-index: 1000; display: block; text-align: center; } .motd { margin-top: 2em; color: var(--body-text-color-subdued); font-family: monospace; font-variant: all-petite-caps; } diff --git a/javascript/settings.js b/javascript/settings.js index 93e4bebce..a787161dc 100644 --- a/javascript/settings.js +++ b/javascript/settings.js @@ -108,7 +108,7 @@ onAfterUiUpdate(async () => { const settingsSearch = gradioApp().querySelectorAll('#settings_search > label > textarea')[0]; settingsSearch.oninput = (e) => { setTimeout(() => { - log('settingsSearch', e.target.value) + log('settingsSearch', e.target.value); showAllSettings(); gradioApp().querySelectorAll('#tab_settings .tabitem').forEach((section) => { section.querySelectorAll('.dirtyable').forEach((setting) => { @@ -129,6 +129,40 @@ onOptionsChanged(() => { }); }); +async function initModels() { + const warn = () => ` +

No models available

+ - Select a model from reference list to download or
+ - Set model path to a folder containing your models
+ Current model path: ${opts.ckpt_dir}
+ `; + const el = gradioApp().getElementById('main_info'); + const en = gradioApp().getElementById('txt2img_extra_networks'); + if (!el || !en) return; + const req = await fetch('/sdapi/v1/sd-models'); + const res = req.ok ? await req.json() : []; + log('initModels', res.length); + const ready = () => ` +

Ready

+ ${res.length} models available
+ `; + el.innerHTML = res.length > 0 ? ready() : warn(); + el.style.display = 'block'; + setTimeout(() => el.style.display = 'none', res.length === 0 ? 30000 : 1500); + if (res.length === 0) { + if (en.classList.contains('hide')) gradioApp().getElementById('txt2img_extra_networks_btn').click(); + const repeat = setInterval(() => { + const buttons = Array.from(gradioApp().querySelectorAll('#txt2img_model_subdirs > button')) || []; + const reference = buttons.find((b) => b.innerText === 'Reference'); + if (reference) { + clearInterval(repeat); + reference.click(); + log('enReferenceSelect'); + } + }, 100); + } +} + function initSettings() { if (settingsInitialized) return; settingsInitialized = true; @@ -138,7 +172,7 @@ function initSettings() { const observer = new MutationObserver((mutations) => { const showAllPages = gradioApp().getElementById('settings_show_all_pages'); if (showAllPages.style.display === 'none') return; - const mutation = (mut) => mut.type === 'attributes' && mut.attributeName === 'style' + const mutation = (mut) => mut.type === 'attributes' && mut.attributeName === 'style'; if (mutations.some(mutation)) showAllSettings(); }); const tabContentWrapper = document.createElement('div'); @@ -155,3 +189,4 @@ function initSettings() { } onUiLoaded(initSettings); +onUiLoaded(initModels); diff --git a/models/Reference/dreamshaperXL_turboDpmppSDE.jpg b/models/Reference/dreamshaperXL_turboDpmppSDE.jpg new file mode 100644 index 0000000000000000000000000000000000000000..802d9259392d762df0d00347413fdaa3a5c14e33 GIT binary patch literal 54282 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%0D!+IJ9Nf+-KRCOm5>oxM^cW>bs6%{ASjVVK& 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%0D5s#orE#P@e6h2CbxEe&uuB=w{WCP1 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 XlpKztrix$Pjxqt@ik8`NOLPC(|KRlX literal 0 HcmV?d00001 diff --git a/models/Reference/juggernautXL_v7Rundiffusion.jpg b/models/Reference/juggernautXL_v7Rundiffusion.jpg new file mode 100644 index 0000000000000000000000000000000000000000..cbce7cb3240e08ecb6aab206bf55a83b781c77f4 GIT binary patch literal 41737 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

i55 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 Dt|+Hs literal 0 HcmV?d00001 diff --git a/modules/control/run.py b/modules/control/run.py index c67a78120..e4dc60dfe 100644 --- a/modules/control/run.py +++ b/modules/control/run.py @@ -9,12 +9,12 @@ from modules.control import util from modules.control import unit from modules.control import processors -from modules.control import controlnets # lllyasviel ControlNet -from modules.control import controlnetsxs # VisLearn ControlNet-XS -from modules.control import controlnetslite # Kohya ControlLLLite -from modules.control import adapters # TencentARC T2I-Adapter -from modules.control import reference # ControlNet-Reference -from modules.control import ipadapter # IP-Adapter +from modules.control.units import controlnet # lllyasviel ControlNet +from modules.control.units import xs # VisLearn ControlNet-XS +from modules.control.units import lite # Kohya ControlLLLite +from modules.control.units import t2iadapter # TencentARC T2I-Adapter +from modules.control.units import reference # ControlNet-Reference +from modules.control.units import ipadapter # IP-Adapter from modules import devices, shared, errors, processing, images, sd_models, sd_samplers @@ -77,7 +77,7 @@ def control_run(units: List[unit.Unit], inputs, inits, unit_type: str, is_genera inputs = [None] output_images: List[Image.Image] = [] # output images active_process: List[processors.Processor] = [] # all active preprocessors - active_model: List[Union[controlnets.ControlNet, controlnetsxs.ControlNetXS, adapters.Adapter]] = [] # all active models + active_model: List[Union[controlnet.ControlNet, xs.ControlNetXS, t2iadapter.Adapter]] = [] # all active models active_strength: List[float] = [] # strength factors for all active models active_start: List[float] = [] # start step for all active models active_end: List[float] = [] # end step for all active models @@ -177,7 +177,7 @@ def control_run(units: List[unit.Unit], inputs, inits, unit_type: str, is_genera p.ops.append('control') has_models = False - selected_models: List[Union[controlnets.ControlNetModel, controlnetsxs.ControlNetXSModel, adapters.AdapterModel]] = None + selected_models: List[Union[controlnet.ControlNetModel, xs.ControlNetXSModel, t2iadapter.AdapterModel]] = None if unit_type == 'adapter' or unit_type == 'controlnet' or unit_type == 'xs' or unit_type == 'lite': if len(active_model) == 0: selected_models = None @@ -198,7 +198,7 @@ def control_run(units: List[unit.Unit], inputs, inits, unit_type: str, is_genera p.extra_generation_params["Control mode"] = 'Adapter' p.extra_generation_params["Control conditioning"] = use_conditioning p.task_args['adapter_conditioning_scale'] = use_conditioning - instance = adapters.AdapterPipeline(selected_models, shared.sd_model) + instance = t2iadapter.AdapterPipeline(selected_models, shared.sd_model) pipe = instance.pipeline if inits is not None: shared.log.warning('Control: T2I-Adapter does not support separate init image') @@ -209,7 +209,7 @@ def control_run(units: List[unit.Unit], inputs, inits, unit_type: str, is_genera p.task_args['control_guidance_start'] = active_start[0] if len(active_start) == 1 else list(active_start) p.task_args['control_guidance_end'] = active_end[0] if len(active_end) == 1 else list(active_end) p.task_args['guess_mode'] = p.guess_mode - instance = controlnets.ControlNetPipeline(selected_models, shared.sd_model) + instance = controlnet.ControlNetPipeline(selected_models, shared.sd_model) pipe = instance.pipeline elif unit_type == 'xs' and has_models: p.extra_generation_params["Control mode"] = 'ControlNet-XS' @@ -217,7 +217,7 @@ def control_run(units: List[unit.Unit], inputs, inits, unit_type: str, is_genera p.controlnet_conditioning_scale = use_conditioning p.control_guidance_start = active_start[0] if len(active_start) == 1 else list(active_start) p.control_guidance_end = active_end[0] if len(active_end) == 1 else list(active_end) - instance = controlnetsxs.ControlNetXSPipeline(selected_models, shared.sd_model) + instance = xs.ControlNetXSPipeline(selected_models, shared.sd_model) pipe = instance.pipeline if inits is not None: shared.log.warning('Control: ControlNet-XS does not support separate init image') @@ -225,7 +225,7 @@ def control_run(units: List[unit.Unit], inputs, inits, unit_type: str, is_genera p.extra_generation_params["Control mode"] = 'ControlLLLite' p.extra_generation_params["Control conditioning"] = use_conditioning p.controlnet_conditioning_scale = use_conditioning - instance = controlnetslite.ControlLLitePipeline(shared.sd_model) + instance = lite.ControlLLitePipeline(shared.sd_model) pipe = instance.pipeline if inits is not None: shared.log.warning('Control: ControlLLLite does not support separate init image') diff --git a/modules/control/test.py b/modules/control/test.py index ba87420ea..235cee0b4 100644 --- a/modules/control/test.py +++ b/modules/control/test.py @@ -49,25 +49,25 @@ def test_processors(image): def test_controlnets(prompt, negative, image): from modules import devices, sd_models - from modules.control import controlnets + from modules.control.units import controlnet if image is None: shared.log.error('Image not loaded') return None, None, None from PIL import ImageDraw, ImageFont images = [] - for model_id in controlnets.list_models(): + for model_id in controlnet.list_models(): if model_id is None: model_id = 'None' if shared.state.interrupted: continue output = image if model_id != 'None': - controlnet = controlnets.ControlNet(model_id=model_id, device=devices.device, dtype=devices.dtype) + controlnet = controlnet.ControlNet(model_id=model_id, device=devices.device, dtype=devices.dtype) if controlnet is None: shared.log.error(f'ControlNet load failed: id="{model_id}"') continue shared.log.info(f'Testing ControlNet: {model_id}') - pipe = controlnets.ControlNetPipeline(controlnet=controlnet.model, pipeline=shared.sd_model) + pipe = controlnet.ControlNetPipeline(controlnet=controlnet.model, pipeline=shared.sd_model) pipe.pipeline.to(device=devices.device, dtype=devices.dtype) sd_models.set_diffuser_options(pipe) try: @@ -101,25 +101,25 @@ def test_controlnets(prompt, negative, image): def test_adapters(prompt, negative, image): from modules import devices, sd_models - from modules.control import adapters + from modules.control.units import t2iadapter if image is None: shared.log.error('Image not loaded') return None, None, None from PIL import ImageDraw, ImageFont images = [] - for model_id in adapters.list_models(): + for model_id in t2iadapter.list_models(): if model_id is None: model_id = 'None' if shared.state.interrupted: continue output = image.copy() if model_id != 'None': - adapter = adapters.Adapter(model_id=model_id, device=devices.device, dtype=devices.dtype) + adapter = t2iadapter.Adapter(model_id=model_id, device=devices.device, dtype=devices.dtype) if adapter is None: shared.log.error(f'Adapter load failed: id="{model_id}"') continue shared.log.info(f'Testing Adapter: {model_id}') - pipe = adapters.AdapterPipeline(adapter=adapter.model, pipeline=shared.sd_model) + pipe = t2iadapter.AdapterPipeline(adapter=adapter.model, pipeline=shared.sd_model) pipe.pipeline.to(device=devices.device, dtype=devices.dtype) sd_models.set_diffuser_options(pipe) image = image.convert('L') if 'Canny' in model_id or 'Sketch' in model_id else image.convert('RGB') @@ -154,25 +154,25 @@ def test_adapters(prompt, negative, image): def test_xs(prompt, negative, image): from modules import devices, sd_models - from modules.control import controlnetsxs + from modules.control.units import xs if image is None: shared.log.error('Image not loaded') return None, None, None from PIL import ImageDraw, ImageFont images = [] - for model_id in controlnetsxs.list_models(): + for model_id in xs.list_models(): if model_id is None: model_id = 'None' if shared.state.interrupted: continue output = image if model_id != 'None': - xs = controlnetsxs.ControlNetXS(model_id=model_id, device=devices.device, dtype=devices.dtype) + xs = xs.ControlNetXS(model_id=model_id, device=devices.device, dtype=devices.dtype) if xs is None: shared.log.error(f'ControlNet-XS load failed: id="{model_id}"') continue shared.log.info(f'Testing ControlNet-XS: {model_id}') - pipe = controlnetsxs.ControlNetXSPipeline(controlnet=xs.model, pipeline=shared.sd_model) + pipe = xs.ControlNetXSPipeline(controlnet=xs.model, pipeline=shared.sd_model) pipe.pipeline.to(device=devices.device, dtype=devices.dtype) sd_models.set_diffuser_options(pipe) try: @@ -206,25 +206,25 @@ def test_xs(prompt, negative, image): def test_lite(prompt, negative, image): from modules import devices, sd_models - from modules.control import controlnetslite + from modules.control.units import lite if image is None: shared.log.error('Image not loaded') return None, None, None from PIL import ImageDraw, ImageFont images = [] - for model_id in controlnetslite.list_models(): + for model_id in lite.list_models(): if model_id is None: model_id = 'None' if shared.state.interrupted: continue output = image if model_id != 'None': - lite = controlnetslite.ControlLLLite(model_id=model_id, device=devices.device, dtype=devices.dtype) + lite = lite.ControlLLLite(model_id=model_id, device=devices.device, dtype=devices.dtype) if lite is None: shared.log.error(f'Control-LLite load failed: id="{model_id}"') continue shared.log.info(f'Testing ControlNet-XS: {model_id}') - pipe = controlnetslite.ControlLLitePipeline(pipeline=shared.sd_model) + pipe = lite.ControlLLitePipeline(pipeline=shared.sd_model) pipe.apply(controlnet=lite.model, image=image, conditioning=1.0) pipe.pipeline.to(device=devices.device, dtype=devices.dtype) sd_models.set_diffuser_options(pipe) diff --git a/modules/control/unit.py b/modules/control/unit.py index 3e52531f8..1df54b81b 100644 --- a/modules/control/unit.py +++ b/modules/control/unit.py @@ -2,11 +2,11 @@ from PIL import Image from modules.shared import log from modules.control import processors -from modules.control import controlnets -from modules.control import controlnetsxs -from modules.control import controlnetslite -from modules.control import adapters -from modules.control import reference # pylint: disable=unused-import +from modules.control.units import controlnet +from modules.control.units import xs +from modules.control.units import lite +from modules.control.units import t2iadapter +from modules.control.units import reference # pylint: disable=unused-import default_device = None @@ -45,8 +45,8 @@ def __init__(self, self.end = max(self.start, self.end) # processor always exists, adapter and controlnet are optional self.process: processors.Processor = processors.Processor() - self.adapter: adapters.Adapter = None - self.controlnet: Union[controlnets.ControlNet, controlnetsxs.ControlNetXS] = None + self.adapter: t2iadapter.Adapter = None + self.controlnet: Union[controlnet.ControlNet, xs.ControlNetXS] = None # map to input image self.input: Image = image_input self.override: Image = None @@ -106,13 +106,13 @@ def upload_image(image_file): # actual init if self.type == 'adapter': - self.adapter = adapters.Adapter(device=default_device, dtype=default_dtype) + self.adapter = t2iadapter.Adapter(device=default_device, dtype=default_dtype) elif self.type == 'controlnet': - self.controlnet = controlnets.ControlNet(device=default_device, dtype=default_dtype) + self.controlnet = controlnet.ControlNet(device=default_device, dtype=default_dtype) elif self.type == 'xs': - self.controlnet = controlnetsxs.ControlNetXS(device=default_device, dtype=default_dtype) + self.controlnet = xs.ControlNetXS(device=default_device, dtype=default_dtype) elif self.type == 'lite': - self.controlnet = controlnetslite.ControlLLLite(device=default_device, dtype=default_dtype) + self.controlnet = lite.ControlLLLite(device=default_device, dtype=default_dtype) elif self.type == 'reference': pass else: diff --git a/modules/control/controlnets.py b/modules/control/units/controlnet.py similarity index 100% rename from modules/control/controlnets.py rename to modules/control/units/controlnet.py diff --git a/modules/control/ipadapter.py b/modules/control/units/ipadapter.py similarity index 100% rename from modules/control/ipadapter.py rename to modules/control/units/ipadapter.py diff --git a/modules/control/controlnetslite.py b/modules/control/units/lite.py similarity index 97% rename from modules/control/controlnetslite.py rename to modules/control/units/lite.py index 16a7505f2..9796f77f1 100644 --- a/modules/control/controlnetslite.py +++ b/modules/control/units/lite.py @@ -6,7 +6,7 @@ from diffusers import StableDiffusionPipeline, StableDiffusionXLPipeline from modules.shared import log, opts from modules import errors -from modules.control.controlnetslite_model import ControlNetLLLite +from modules.control.units.lite_model import ControlNetLLLite what = 'ControlLLLite' @@ -130,6 +130,6 @@ def apply(self, controlnet: Union[ControlNetLLLite, list[ControlNetLLLite]], ima cn.apply(pipe=self.pipeline, cond=np.asarray(images[i % len(images)]), weight=weight[i % len(weight)]) def restore(self): - from modules.control.controlnetslite_model import clear_all_lllite + from modules.control.units.lite_model import clear_all_lllite clear_all_lllite() self.nets = [] diff --git a/modules/control/controlnetslite_model.py b/modules/control/units/lite_model.py similarity index 100% rename from modules/control/controlnetslite_model.py rename to modules/control/units/lite_model.py diff --git a/modules/control/reference.py b/modules/control/units/reference.py similarity index 100% rename from modules/control/reference.py rename to modules/control/units/reference.py diff --git a/modules/control/adapters.py b/modules/control/units/t2iadapter.py similarity index 100% rename from modules/control/adapters.py rename to modules/control/units/t2iadapter.py diff --git a/modules/control/controlnetsxs.py b/modules/control/units/xs.py similarity index 100% rename from modules/control/controlnetsxs.py rename to modules/control/units/xs.py diff --git a/modules/modelloader.py b/modules/modelloader.py index 05781f3a7..09fdf326b 100644 --- a/modules/modelloader.py +++ b/modules/modelloader.py @@ -294,8 +294,9 @@ def load_diffusers_models(model_path: str, command_path: str = None, clear=True) if os.path.exists(os.path.join(folder, 'hidden')): continue output.append(name) - except Exception as e: - shared.log.error(f"Error analyzing diffusers model: {folder} {e}") + except Exception: + # shared.log.error(f"Error analyzing diffusers model: {folder} {e}") + pass except Exception as e: shared.log.error(f"Error listing diffusers: {place} {e}") shared.log.debug(f'Scanning diffusers cache: {model_path} {command_path} items={len(output)} time={time.time()-t0:.2f}') @@ -339,6 +340,29 @@ def load_reference(name: str): return True +def load_civitai(model: str, url: str): + from modules import sd_models + name, _ext = os.path.splitext(model) + info = sd_models.get_closet_checkpoint_match(name) + if info is not None: + shared.log.debug(f'Reference model: {name}') + return name # already downloaded + else: + shared.log.debug(f'Reference model: {name} download start') + download_civit_model_thread(model_name=model, model_url=url, model_path='', model_type='safetensors', preview=None, token=None) + shared.log.debug(f'Reference model: {name} download complete') + sd_models.list_models() + info = sd_models.get_closet_checkpoint_match(name) + print('HERE1', info) + print('HERE2', name) + if info is not None: + shared.log.debug(f'Reference model: {name}') + return name # already downloaded + else: + shared.log.debug(f'Reference model: {name} not found') + return None + + cache_folders = {} cache_last = 0 cache_time = 1 diff --git a/modules/processing.py b/modules/processing.py index e2283b8ae..b40973bbb 100644 --- a/modules/processing.py +++ b/modules/processing.py @@ -565,13 +565,13 @@ def create_infotext(p: StableDiffusionProcessing, all_prompts=None, all_seeds=No if index is None: index = position_in_batch + iteration * p.batch_size if all_prompts is None: - all_prompts = p.all_prompts + all_prompts = p.all_prompts or [p.prompt] if all_negative_prompts is None: - all_negative_prompts = p.all_negative_prompts + all_negative_prompts = p.all_negative_prompts or [p.negative_prompt] if all_seeds is None: - all_seeds = p.all_seeds + all_seeds = p.all_seeds or [p.seed] if all_subseeds is None: - all_subseeds = p.all_subseeds + all_subseeds = p.all_subseeds or [p.subseed] while len(all_prompts) <= index: all_prompts.append(all_prompts[-1]) while len(all_seeds) <= index: diff --git a/modules/sd_models.py b/modules/sd_models.py index 33130a2fc..85f8894d4 100644 --- a/modules/sd_models.py +++ b/modules/sd_models.py @@ -168,6 +168,7 @@ def list_models(): shared.log.info(f'Available models: path="{shared.opts.ckpt_dir}" items={len(checkpoints_list)} time={time.time()-t0:.2f}') checkpoints_list = dict(sorted(checkpoints_list.items(), key=lambda cp: cp[1].filename)) + """ if len(checkpoints_list) == 0: if not shared.cmd_opts.no_download: key = input('Download the default model? (y/N) ') @@ -185,7 +186,7 @@ def list_models(): checkpoint_info = CheckpointInfo(filename) if checkpoint_info.name is not None: checkpoint_info.register() - + """ def update_model_hashes(): txt = [] diff --git a/modules/sd_samplers_common.py b/modules/sd_samplers_common.py index dd24d42ae..88f511a5d 100644 --- a/modules/sd_samplers_common.py +++ b/modules/sd_samplers_common.py @@ -38,6 +38,8 @@ def single_sample_to_image(sample, approximation=None): warn_once('Unknown decode type, please reset preview method') approximation = 0 + if len(sample.shape) > 4: # likely unknown video latent (e.g. svd) + return Image.new(mode="RGB", size=(512, 512)) if len(sample.shape) == 4 and sample.shape[0]: # likely animatediff latent sample = sample.permute(1, 0, 2, 3)[0] if approximation == 0: # Simple diff --git a/modules/ui.py b/modules/ui.py index eddf1393d..77676790d 100644 --- a/modules/ui.py +++ b/modules/ui.py @@ -182,10 +182,10 @@ def create_advanced_inputs(tab): with gr.Accordion(open=False, label="Advanced", elem_id=f"{tab}_advanced", elem_classes=["small-accordion"]): with gr.Group(): with FormRow(): - cfg_scale = gr.Slider(minimum=0.0, maximum=30.0, step=0.1, label='CFG scale', value=6.0, elem_id=f"{tab}_cfg_scale") + cfg_scale = gr.Slider(minimum=0.0, maximum=30.0, step=0.1, label='CFG scale', value=4.0, elem_id=f"{tab}_cfg_scale") clip_skip = gr.Slider(label='CLIP skip', value=1, minimum=1, maximum=14, step=1, elem_id=f"{tab}_clip_skip", interactive=True) with FormRow(): - image_cfg_scale = gr.Slider(minimum=0.0, maximum=30.0, step=0.1, label='Secondary CFG scale', value=6.0, elem_id=f"{tab}_image_cfg_scale") + image_cfg_scale = gr.Slider(minimum=0.0, maximum=30.0, step=0.1, label='Secondary CFG scale', value=4.0, elem_id=f"{tab}_image_cfg_scale") diffusers_guidance_rescale = gr.Slider(minimum=0.0, maximum=1.0, step=0.05, label='Guidance rescale', value=0.7, elem_id=f"{tab}_image_cfg_rescale", visible=shared.backend == shared.Backend.DIFFUSERS) with gr.Group(): with FormRow(): @@ -648,6 +648,9 @@ def create_ui(startup_timer = None): ui_extra_networks.setup_ui(extra_networks_ui, txt2img_gallery) + with FormRow(): + gr.HTML(value="", elem_id="main_info", visible=False, elem_classes=["main-info"]) + timer.startup.record("ui-txt2img") import modules.img2img # pylint: disable=redefined-outer-name @@ -1136,9 +1139,9 @@ def reload_sd_weights(): interfaces += [(img2img_interface, "Image", "img2img")] interfaces += [(control_interface, "Control", "control")] if control_interface is not None else [] interfaces += [(extras_interface, "Process", "process")] + interfaces += [(interrogate_interface, "Interrogate", "interrogate")] interfaces += [(train_interface, "Train", "train")] interfaces += [(models_interface, "Models", "models")] - interfaces += [(interrogate_interface, "Interrogate", "interrogate")] interfaces += script_callbacks.ui_tabs_callback() interfaces += [(settings_interface, "System", "system")] @@ -1224,10 +1227,13 @@ def reload_sd_weights(): ) def reference_submit(model): - loaded = modelloader.load_reference(model) - if loaded: + if '@' not in model: # diffusers + loaded = modelloader.load_reference(model) return model if loaded else opts.sd_model_checkpoint - return loaded + else: # civitai + model, url = model.split('@') + loaded = modelloader.load_civitai(model, url) + return loaded if loaded is not None else opts.sd_model_checkpoint button_set_reference = gr.Button('Change reference', elem_id='change_reference', visible=False) button_set_reference.click( diff --git a/modules/ui_control.py b/modules/ui_control.py index 468aa3af7..9315426d4 100644 --- a/modules/ui_control.py +++ b/modules/ui_control.py @@ -1,13 +1,13 @@ import os import gradio as gr from modules.control import unit -from modules.control import controlnets # lllyasviel ControlNet -from modules.control import controlnetsxs # vislearn ControlNet-XS -from modules.control import controlnetslite # vislearn ControlNet-XS -from modules.control import adapters # TencentARC T2I-Adapter from modules.control import processors # patrickvonplaten controlnet_aux -from modules.control import reference # reference pipeline -from modules.control import ipadapter # reference pipeline +from modules.control.units import controlnet # lllyasviel ControlNet +from modules.control.units import xs # vislearn ControlNet-XS +from modules.control.units import lite # vislearn ControlNet-XS +from modules.control.units import t2iadapter # TencentARC T2I-Adapter +from modules.control.units import reference # reference pipeline +from modules.control.units import ipadapter # reference pipeline from modules import errors, shared, progress, sd_samplers, ui, ui_components, ui_symbols, ui_common, generation_parameters_copypaste, call_queue from modules.ui_components import FormRow, FormGroup @@ -24,18 +24,18 @@ def initialize(): from modules import devices shared.log.debug(f'Control initialize: models={shared.opts.control_dir}') - controlnets.cache_dir = os.path.join(shared.opts.control_dir, 'controlnet') - controlnetsxs.cache_dir = os.path.join(shared.opts.control_dir, 'xs') - controlnetslite.cache_dir = os.path.join(shared.opts.control_dir, 'lite') - adapters.cache_dir = os.path.join(shared.opts.control_dir, 'adapter') + controlnet.cache_dir = os.path.join(shared.opts.control_dir, 'controlnet') + xs.cache_dir = os.path.join(shared.opts.control_dir, 'xs') + lite.cache_dir = os.path.join(shared.opts.control_dir, 'lite') + t2iadapter.cache_dir = os.path.join(shared.opts.control_dir, 'adapter') processors.cache_dir = os.path.join(shared.opts.control_dir, 'processor') unit.default_device = devices.device unit.default_dtype = devices.dtype os.makedirs(shared.opts.control_dir, exist_ok=True) - os.makedirs(controlnets.cache_dir, exist_ok=True) - os.makedirs(controlnetsxs.cache_dir, exist_ok=True) - os.makedirs(controlnetslite.cache_dir, exist_ok=True) - os.makedirs(adapters.cache_dir, exist_ok=True) + os.makedirs(controlnet.cache_dir, exist_ok=True) + os.makedirs(xs.cache_dir, exist_ok=True) + os.makedirs(lite.cache_dir, exist_ok=True) + os.makedirs(t2iadapter.cache_dir, exist_ok=True) os.makedirs(processors.cache_dir, exist_ok=True) @@ -324,8 +324,8 @@ def create_ui(_blocks: gr.Blocks=None): with gr.Row(): enabled_cb = gr.Checkbox(value= i==0, label="") process_id = gr.Dropdown(label="Processor", choices=processors.list_models(), value='None') - model_id = gr.Dropdown(label="ControlNet", choices=controlnets.list_models(), value='None') - ui_common.create_refresh_button(model_id, controlnets.list_models, lambda: {"choices": controlnets.list_models(refresh=True)}, 'refresh_control_models') + model_id = gr.Dropdown(label="ControlNet", choices=controlnet.list_models(), value='None') + ui_common.create_refresh_button(model_id, controlnet.list_models, lambda: {"choices": controlnet.list_models(refresh=True)}, 'refresh_control_models') model_strength = gr.Slider(label="Strength", minimum=0.01, maximum=1.0, step=0.01, value=1.0-i/10) control_start = gr.Slider(label="Start", minimum=0.0, maximum=1.0, step=0.05, value=0) control_end = gr.Slider(label="End", minimum=0.0, maximum=1.0, step=0.05, value=1.0) @@ -369,8 +369,8 @@ def create_ui(_blocks: gr.Blocks=None): with gr.Row(): enabled_cb = gr.Checkbox(value= i==0, label="") process_id = gr.Dropdown(label="Processor", choices=processors.list_models(), value='None') - model_id = gr.Dropdown(label="ControlNet-XS", choices=controlnetsxs.list_models(), value='None') - ui_common.create_refresh_button(model_id, controlnetsxs.list_models, lambda: {"choices": controlnetsxs.list_models(refresh=True)}, 'refresh_control_models') + model_id = gr.Dropdown(label="ControlNet-XS", choices=xs.list_models(), value='None') + ui_common.create_refresh_button(model_id, xs.list_models, lambda: {"choices": xs.list_models(refresh=True)}, 'refresh_control_models') model_strength = gr.Slider(label="Strength", minimum=0.01, maximum=1.0, step=0.01, value=1.0-i/10) control_start = gr.Slider(label="Start", minimum=0.0, maximum=1.0, step=0.05, value=0) control_end = gr.Slider(label="End", minimum=0.0, maximum=1.0, step=0.05, value=1.0) @@ -414,8 +414,8 @@ def create_ui(_blocks: gr.Blocks=None): with gr.Row(): enabled_cb = gr.Checkbox(value= i == 0, label="Enabled") process_id = gr.Dropdown(label="Processor", choices=processors.list_models(), value='None') - model_id = gr.Dropdown(label="Adapter", choices=adapters.list_models(), value='None') - ui_common.create_refresh_button(model_id, adapters.list_models, lambda: {"choices": adapters.list_models(refresh=True)}, 'refresh_adapter_models') + model_id = gr.Dropdown(label="Adapter", choices=t2iadapter.list_models(), value='None') + ui_common.create_refresh_button(model_id, t2iadapter.list_models, lambda: {"choices": t2iadapter.list_models(refresh=True)}, 'refresh_adapter_models') model_strength = gr.Slider(label="Strength", minimum=0.01, maximum=1.0, step=0.01, value=1.0-i/10) reset_btn = ui_components.ToolButton(value=ui_symbols.reset) image_upload = gr.UploadButton(label=ui_symbols.upload, file_types=['image'], elem_classes=['form', 'gradio-button', 'tool']) @@ -454,8 +454,8 @@ def create_ui(_blocks: gr.Blocks=None): with gr.Row(): enabled_cb = gr.Checkbox(value= i == 0, label="Enabled") process_id = gr.Dropdown(label="Processor", choices=processors.list_models(), value='None') - model_id = gr.Dropdown(label="Model", choices=controlnetslite.list_models(), value='None') - ui_common.create_refresh_button(model_id, controlnetslite.list_models, lambda: {"choices": controlnetslite.list_models(refresh=True)}, 'refresh_lite_models') + model_id = gr.Dropdown(label="Model", choices=lite.list_models(), value='None') + ui_common.create_refresh_button(model_id, lite.list_models, lambda: {"choices": lite.list_models(refresh=True)}, 'refresh_lite_models') model_strength = gr.Slider(label="Strength", minimum=0.01, maximum=1.0, step=0.01, value=1.0-i/10) reset_btn = ui_components.ToolButton(value=ui_symbols.reset) image_upload = gr.UploadButton(label=ui_symbols.upload, file_types=['image'], elem_classes=['form', 'gradio-button', 'tool']) diff --git a/scripts/ipadapter.py b/scripts/ipadapter.py index 31ebd6d1f..7297099dc 100644 --- a/scripts/ipadapter.py +++ b/scripts/ipadapter.py @@ -16,19 +16,19 @@ image_encoder = None image_encoder_type = None loaded = None -ADAPTERS = [ - 'none', - 'ip-adapter_sd15', - 'ip-adapter_sd15_light', - 'ip-adapter-plus_sd15', - 'ip-adapter-plus-face_sd15', - 'ip-adapter-full-face_sd15', +ADAPTERS = { + 'None': 'none', + 'Base': 'ip-adapter_sd15', + 'Light': 'ip-adapter_sd15_light', + 'Plus': 'ip-adapter-plus_sd15', + 'Plus Face': 'ip-adapter-plus-face_sd15', + 'Full face': 'ip-adapter-full-face_sd15', + 'Base SXDL': 'ip-adapter_sdxl', # 'models/ip-adapter_sd15_vit-G', # RuntimeError: mat1 and mat2 shapes cannot be multiplied (2x1024 and 1280x3072) - 'ip-adapter_sdxl', # 'sdxl_models/ip-adapter_sdxl_vit-h', # 'sdxl_models/ip-adapter-plus_sdxl_vit-h', # 'sdxl_models/ip-adapter-plus-face_sdxl_vit-h', -] +} class Script(scripts.Script): @@ -41,7 +41,7 @@ def show(self, is_img2img): def ui(self, _is_img2img): with gr.Accordion('IP Adapter', open=False, elem_id='ipadapter'): with gr.Row(): - adapter = gr.Dropdown(label='Adapter', choices=ADAPTERS, value='none') + adapter = gr.Dropdown(label='Adapter', choices=list(ADAPTERS), value='none') scale = gr.Slider(label='Scale', minimum=0.0, maximum=1.0, step=0.01, value=0.5) with gr.Row(): image = gr.Image(image_mode='RGB', label='Image', source='upload', type='pil', width=512) @@ -50,6 +50,8 @@ def ui(self, _is_img2img): def process(self, p: processing.StableDiffusionProcessing, adapter, scale, image): # pylint: disable=arguments-differ from transformers import CLIPVisionModelWithProjection # overrides + adapter = ADAPTERS[adapter] + print('HERE', adapter) if hasattr(p, 'ip_adapter_name'): adapter = p.ip_adapter_name if hasattr(p, 'ip_adapter_scale'): @@ -96,6 +98,7 @@ def process(self, p: processing.StableDiffusionProcessing, adapter, scale, image # main code subfolder = 'models' if 'sd15' in adapter else 'sdxl_models' + print('HERE2', subfolder) if adapter != loaded or getattr(shared.sd_model.unet.config, 'encoder_hid_dim_type', None) is None: t0 = time.time() if loaded is not None: From 54acb47f418cb57cb21b16f4f48fd88836afbcb6 Mon Sep 17 00:00:00 2001 From: Vladimir Mandic Date: Tue, 26 Dec 2023 13:19:38 -0500 Subject: [PATCH 122/143] add faceid module --- CHANGELOG.md | 6 +++ installer.py | 3 +- scripts/faceid.py | 109 ++++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 117 insertions(+), 1 deletion(-) create mode 100644 scripts/faceid.py diff --git a/CHANGELOG.md b/CHANGELOG.md index a792bff42..9b8cd419d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -18,6 +18,12 @@ - **IP Adapter** - add support for `ip-adapter-plus_sd15`, `ip-adapter-plus-face_sd15` and `ip-adapter-full-face_sd15` - can now be used in *xyz-grid* + - **FaceID** + - also based on IP adapters, but with additional face detection and external embeddings calculation + - calculates face embeds based on input image and uses it to guide generation + - simply select from *scripts -> faceid* + - *experimental module*: requirements must be installed manually: + > pip install insightface ip_adapter - **Text-to-Video** - in text tab, select `text-to-video` script - supported models: ModelScope v1.7b, ZeroScope v1, ZeroScope v1.1, ZeroScope v2, ZeroScope v2 Dark, Potat v1 diff --git a/installer.py b/installer.py index 2a15bb4c4..432fda9be 100644 --- a/installer.py +++ b/installer.py @@ -363,7 +363,8 @@ def check_torch(): log.debug(f'Torch allowed: cuda={allow_cuda} rocm={allow_rocm} ipex={allow_ipex} diml={allow_directml} openvino={allow_openvino}') torch_command = os.environ.get('TORCH_COMMAND', '') xformers_package = os.environ.get('XFORMERS_PACKAGE', 'none') - install('onnxruntime onnxruntimegpu', 'onnxruntime', ignore=True) + if not installed('onnxruntime', quiet=True) and not installed('onnxruntime-gpu', quiet=True): # allow either + install('onnxruntime', 'onnxruntime', ignore=True) if torch_command != '': pass elif allow_cuda and (shutil.which('nvidia-smi') is not None or args.use_xformers or os.path.exists(os.path.join(os.environ.get('SystemRoot') or r'C:\Windows', 'System32', 'nvidia-smi.exe'))): diff --git a/scripts/faceid.py b/scripts/faceid.py new file mode 100644 index 000000000..4135ea952 --- /dev/null +++ b/scripts/faceid.py @@ -0,0 +1,109 @@ +import os +import cv2 +import torch +import numpy as np +import gradio as gr +import diffusers +import huggingface_hub as hf +from modules import scripts, processing, shared, devices + + +app = None +try: + import onnxruntime + from insightface.app import FaceAnalysis + from ip_adapter.ip_adapter_faceid import IPAdapterFaceID + ok = True +except Exception as e: + shared.log.error(f'FaceID: {e}') + ok = False + + +class Script(scripts.Script): + def title(self): + return 'FaceID' + + def show(self, is_img2img): + return ok if shared.backend == shared.Backend.DIFFUSERS else False + + # return signature is array of gradio components + def ui(self, _is_img2img): + with gr.Row(): + scale = gr.Slider(label='Scale', minimum=0.0, maximum=1.0, step=0.01, value=1.0) + with gr.Row(): + image = gr.Image(image_mode='RGB', label='Image', source='upload', type='pil', width=512) + return [scale, image] + + def run(self, p: processing.StableDiffusionProcessing, scale, image): # pylint: disable=arguments-differ, unused-argument + if not ok: + shared.log.error('FaceID: missing dependencies') + return None + if image is None: + shared.log.error('FaceID: no init_images') + return None + if shared.sd_model_type != 'sd': + shared.log.error('FaceID: base model not supported') + return None + + global app # pylint: disable=global-statement + if app is None: + shared.log.debug(f"ONNX: device={onnxruntime.get_device()} providers={onnxruntime.get_available_providers()}") + app = FaceAnalysis(name="buffalo_l", providers=['CUDAExecutionProvider', 'CPUExecutionProvider']) + onnxruntime.set_default_logger_severity(3) + app.prepare(ctx_id=0, det_thresh=0.5, det_size=(640, 640)) + + image = cv2.cvtColor(np.array(image), cv2.COLOR_RGB2BGR) + faces = app.get(image) + if len(faces) == 0: + shared.log.error('FaceID: no faces found') + return None + for face in faces: + shared.log.debug(f'FaceID face: score={face.det_score:.2f} gender={"female" if face.gender==0 else "male"} age={face.age} bbox={face.bbox}') + embeds = torch.from_numpy(faces[0].normed_embedding).unsqueeze(0) + + ip_ckpt = "h94/IP-Adapter-FaceID/ip-adapter-faceid_sd15.bin" + shared.log.debug(f'FaceID model load: {ip_ckpt}') + folder, filename = os.path.split(ip_ckpt) + model_path = hf.hf_hub_download(repo_id=folder, filename=filename, cache_dir=shared.opts.diffusers_dir) + if model_path is None: + shared.log.error(f'FaceID: model download failed: {ip_ckpt}') + return None + + processing.process_init(p) + shared.sd_model.scheduler = diffusers.DDIMScheduler( + num_train_timesteps=1000, + beta_start=0.00085, + beta_end=0.012, + beta_schedule="scaled_linear", + clip_sample=False, + set_alpha_to_one=False, + steps_offset=1, + ) + ip_model = IPAdapterFaceID(shared.sd_model, model_path, devices.device) + ip_model_dict = { + 'prompt': p.all_prompts[0], + 'negative_prompt': p.all_negative_prompts[0], + 'num_samples': p.batch_size, + 'width': p.width, + 'height': p.height, + 'num_inference_steps': p.steps, + 'scale': scale, + 'guidance_scale': p.cfg_scale, + 'seed': int(p.all_seeds[0]), + 'faceid_embeds': None, + } + shared.log.debug(f'FaceID args: {ip_model_dict}') + ip_model_dict['faceid_embeds'] = embeds + images = ip_model.generate(**ip_model_dict) + + processed = processing.Processed( + p, + images_list=images, + seed=p.seed, + subseed=p.subseed, + index_of_first_image=0, + ) + processed.infotexts = processed.infotext(p, 0) + ip_model = None + devices.torch_gc() + return processed From d1acc6e203fdf3d81af2b7de7371e0bf3495120a Mon Sep 17 00:00:00 2001 From: Vladimir Mandic Date: Tue, 26 Dec 2023 13:32:21 -0500 Subject: [PATCH 123/143] add faceid metadata --- scripts/faceid.py | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/scripts/faceid.py b/scripts/faceid.py index 4135ea952..9e609a527 100644 --- a/scripts/faceid.py +++ b/scripts/faceid.py @@ -64,6 +64,7 @@ def run(self, p: processing.StableDiffusionProcessing, scale, image): # pylint: ip_ckpt = "h94/IP-Adapter-FaceID/ip-adapter-faceid_sd15.bin" shared.log.debug(f'FaceID model load: {ip_ckpt}') folder, filename = os.path.split(ip_ckpt) + basename, _ext = os.path.splitext(filename) model_path = hf.hf_hub_download(repo_id=folder, filename=filename, cache_dir=shared.opts.diffusers_dir) if model_path is None: shared.log.error(f'FaceID: model download failed: {ip_ckpt}') @@ -96,6 +97,12 @@ def run(self, p: processing.StableDiffusionProcessing, scale, image): # pylint: ip_model_dict['faceid_embeds'] = embeds images = ip_model.generate(**ip_model_dict) + ip_model = None + p.extra_generation_params["IP Adapter"] = f'{basename}:{scale}' + for i, face in enumerate(faces): + p.extra_generation_params[f"FaceID {i} score"] = f'{face.det_score:.2f}' + p.extra_generation_params[f"FaceID {i} gender"] = "female" if face.gender==0 else "male" + p.extra_generation_params[f"FaceID {i} age"] = face.age processed = processing.Processed( p, images_list=images, @@ -103,7 +110,7 @@ def run(self, p: processing.StableDiffusionProcessing, scale, image): # pylint: subseed=p.subseed, index_of_first_image=0, ) - processed.infotexts = processed.infotext(p, 0) - ip_model = None + processed.info = processed.infotext(p, 0) + processed.infotexts = [processed.info] devices.torch_gc() return processed From 81952ce0999b7fb3ae8e4783aaad31f4226f0f8a Mon Sep 17 00:00:00 2001 From: Vladimir Mandic Date: Tue, 26 Dec 2023 13:41:21 -0500 Subject: [PATCH 124/143] cleanups --- modules/call_queue.py | 5 ++++- modules/modelloader.py | 2 -- scripts/ipadapter.py | 8 ++++---- 3 files changed, 8 insertions(+), 7 deletions(-) diff --git a/modules/call_queue.py b/modules/call_queue.py index 7da6dddc8..482a87e82 100644 --- a/modules/call_queue.py +++ b/modules/call_queue.py @@ -76,7 +76,10 @@ def f(*args, extra_outputs_array=extra_outputs, **kwargs): if not shared.mem_mon.disabled: vram = {k: -(v//-(1024*1024)) for k, v in shared.mem_mon.read().items()} if vram.get('active_peak', 0) > 0: - vram_html = f" |

GPU active {max(vram['active_peak'], vram['reserved_peak'])} MB reserved {vram['reserved']} | used {vram['used']} MB free {vram['free']} MB total {vram['total']} MB | retries {vram['retries']} oom {vram['oom']}

" + vram_html = " |

" + vram_html += f"GPU active {max(vram['active_peak'], vram['reserved_peak'])} MB reserved {vram['reserved']} | used {vram['used']} MB free {vram['free']} MB total {vram['total']} MB" + vram_html += f" | retries {vram['retries']} oom {vram['oom']}" if vram.get('retries', 0) > 0 or vram.get('oom', 0) > 0 else '' + vram_html += "

" if isinstance(res, list): res[-1] += f"

Time: {elapsed_text}

{vram_html}
" return tuple(res) diff --git a/modules/modelloader.py b/modules/modelloader.py index 09fdf326b..171d20a9e 100644 --- a/modules/modelloader.py +++ b/modules/modelloader.py @@ -353,8 +353,6 @@ def load_civitai(model: str, url: str): shared.log.debug(f'Reference model: {name} download complete') sd_models.list_models() info = sd_models.get_closet_checkpoint_match(name) - print('HERE1', info) - print('HERE2', name) if info is not None: shared.log.debug(f'Reference model: {name}') return name # already downloaded diff --git a/scripts/ipadapter.py b/scripts/ipadapter.py index 7297099dc..ab6b89f08 100644 --- a/scripts/ipadapter.py +++ b/scripts/ipadapter.py @@ -48,16 +48,16 @@ def ui(self, _is_img2img): return [adapter, scale, image] def process(self, p: processing.StableDiffusionProcessing, adapter, scale, image): # pylint: disable=arguments-differ - from transformers import CLIPVisionModelWithProjection # overrides - adapter = ADAPTERS[adapter] - print('HERE', adapter) + adapter = ADAPTERS.get(adapter, None) if hasattr(p, 'ip_adapter_name'): adapter = p.ip_adapter_name if hasattr(p, 'ip_adapter_scale'): scale = p.ip_adapter_scale if hasattr(p, 'ip_adapter_image'): image = p.ip_adapter_image + if adapter is None: + return # init code global loaded, image_encoder, image_encoder_type # pylint: disable=global-statement if shared.sd_model is None: @@ -90,6 +90,7 @@ def process(self, p: processing.StableDiffusionProcessing, adapter, scale, image return if image_encoder is None or image_encoder_type != shared.sd_model_type: try: + from transformers import CLIPVisionModelWithProjection image_encoder = CLIPVisionModelWithProjection.from_pretrained("h94/IP-Adapter", subfolder=subfolder, torch_dtype=devices.dtype, cache_dir=shared.opts.diffusers_dir, use_safetensors=True).to(devices.device) image_encoder_type = shared.sd_model_type except Exception as e: @@ -98,7 +99,6 @@ def process(self, p: processing.StableDiffusionProcessing, adapter, scale, image # main code subfolder = 'models' if 'sd15' in adapter else 'sdxl_models' - print('HERE2', subfolder) if adapter != loaded or getattr(shared.sd_model.unet.config, 'encoder_hid_dim_type', None) is None: t0 = time.time() if loaded is not None: From 198c8673e6c94984c7a2bc1246bfc179693cc955 Mon Sep 17 00:00:00 2001 From: Vladimir Mandic Date: Tue, 26 Dec 2023 13:57:06 -0500 Subject: [PATCH 125/143] update changelog --- CHANGELOG.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 9b8cd419d..d62d3dbb0 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -61,7 +61,7 @@ - new **onboarding** if no models are found during startup, app will no longer ask to download default checkpoint instead, it will show message in UI with options to change model path or download any of the reference checkpoints - - support for **Torch 2.1.2** + - support for **Torch 2.1.2** (release) and **Torch 2.3** (dev) - **Process** create videos from batch or folder processing supports *GIF*, *PNG* and *MP4* with full interpolation, scene change detection, etc. - **LoRA** From 1f8749adec80ae593ae915206785d5a8c073e2a3 Mon Sep 17 00:00:00 2001 From: Disty0 Date: Tue, 26 Dec 2023 22:09:36 +0300 Subject: [PATCH 126/143] OpenVINO update PyTorch to 2.1.2 --- CHANGELOG.md | 3 ++- installer.py | 2 +- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index d62d3dbb0..cf8f4d4ab 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -88,7 +88,8 @@ - compatibility improvements - **OpenVINO**, thanks @disty0 - **8 bit support for CPUs** - - Reduce System RAM usage when compiling + - reduce System RAM usage + - update to Torch 2.1.2 - add *Directory for OpenVINO cache* option to *System Paths* - remove Intel ARC specific 1024x1024 workaround - **HDR controls** diff --git a/installer.py b/installer.py index 432fda9be..8b571d02d 100644 --- a/installer.py +++ b/installer.py @@ -455,7 +455,7 @@ def check_torch(): install('onnxruntime-openvino', 'onnxruntime-openvino', ignore=True) elif allow_openvino and args.use_openvino: log.info('Using OpenVINO') - torch_command = os.environ.get('TORCH_COMMAND', 'torch==2.1.1 torchvision==0.16.1 --index-url https://download.pytorch.org/whl/cpu') + torch_command = os.environ.get('TORCH_COMMAND', 'torch==2.1.2 torchvision==0.16.2 --index-url https://download.pytorch.org/whl/cpu') else: machine = platform.machine() if sys.platform == 'darwin': From 0df94404c4c687df16efd4934c8351f6d466d454 Mon Sep 17 00:00:00 2001 From: Vladimir Mandic Date: Wed, 27 Dec 2023 08:51:55 -0500 Subject: [PATCH 127/143] add reference to original, reimplement control-xs --- CHANGELOG.md | 7 +- html/reference.json | 29 +- javascript/control.js | 1 + javascript/sdnext.css | 1 + modules/control/proc/edge.py | 2 +- modules/control/units/xs.py | 15 +- modules/control/units/xs_model.py | 1016 ++++++++++++ modules/control/units/xs_pipe.py | 1938 ++++++++++++++++++++++ modules/ui.py | 3 - modules/ui_common.py | 2 + modules/ui_extra_networks.py | 11 +- modules/ui_extra_networks_checkpoints.py | 17 +- pyproject.toml | 2 + requirements.txt | 3 +- 14 files changed, 3009 insertions(+), 38 deletions(-) create mode 100644 modules/control/units/xs_model.py create mode 100644 modules/control/units/xs_pipe.py diff --git a/CHANGELOG.md b/CHANGELOG.md index cf8f4d4ab..50d1ca0d8 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,6 @@ # Change Log for SD.Next -## Update for 2023-12-26 +## Update for 2023-12-27 *Note*: based on `diffusers==0.25.0.dev0` @@ -59,8 +59,9 @@ (previously via settings -> upscaler_for_img2img) - **General** - new **onboarding** - if no models are found during startup, app will no longer ask to download default checkpoint - instead, it will show message in UI with options to change model path or download any of the reference checkpoints + - if no models are found during startup, app will no longer ask to download default checkpoint + instead, it will show message in UI with options to change model path or download any of the reference checkpoints + - *extra networks -> models -> reference* section is now enabled for both original and diffusers backend - support for **Torch 2.1.2** (release) and **Torch 2.3** (dev) - **Process** create videos from batch or folder processing supports *GIF*, *PNG* and *MP4* with full interpolation, scene change detection, etc. diff --git a/html/reference.json b/html/reference.json index dc33c0481..6ad5f0700 100644 --- a/html/reference.json +++ b/html/reference.json @@ -2,7 +2,8 @@ "DreamShaper SD 1.5 v8": { "path": "dreamshaper_8.safetensors@https://civitai.com/api/download/models/128713", "desc": "Showcase finetuned model based on Stable diffusion 1.5", - "preview": "dreamshaper_8.jpg" + "preview": "dreamshaper_8.jpg", + "original": true }, "DreamShaper SD XL Turbo": { "path": "dreamshaperXL_turboDpmppSDE.safetensors@https://civitai.com/api/download/models/251662", @@ -12,7 +13,8 @@ "Juggernaut Reborn": { "path": "juggernaut_reborn.safetensors@https://civitai.com/api/download/models/274039", "desc": "Showcase finetuned model based on Stable diffusion 1.5", - "preview": "juggernaut_reborn.jpg" + "preview": "juggernaut_reborn.jpg", + "original": true }, "Juggernaut XL v7 RunDiffusion": { "path": "juggernautXL_v7Rundiffusion.safetensors@https://civitai.com/api/download/models/240840", @@ -21,13 +23,24 @@ }, "RunwayML SD 1.5": { "path": "runwayml/stable-diffusion-v1-5", + "alt": "v1-5-pruned-emaonly.safetensors@https://huggingface.co/runwayml/stable-diffusion-v1-5/resolve/main/v1-5-pruned-emaonly.safetensors?download=true", "desc": "Stable Diffusion 1.5 is the base model all other 1.5 checkpoint were trained from. It's a latent text-to-image diffusion model capable of generating photo-realistic images given any text input. The Stable-Diffusion-v1-5 checkpoint was initialized with the weights of the Stable-Diffusion-v1-2 checkpoint and subsequently fine-tuned on 595k steps at resolution 512x512.", - "preview": "runwayml--stable-diffusion-v1-5.jpg" + "preview": "runwayml--stable-diffusion-v1-5.jpg", + "original": true }, - "StabilityAI SD 2.1": { + "StabilityAI SD 2.1 EMA": { "path": "stabilityai/stable-diffusion-2-1-base", - "desc": "This stable-diffusion-2-1 model is fine-tuned from stable-diffusion-2 (768-v-ema.ckpt) with an additional 55k steps on the same dataset. Improvement over base 1.5 model, but never really took off.", - "preview": "stabilityai--stable-diffusion-2.1-base.jpg" + "alt": "v2-1_512-ema-pruned.safetensors@https://huggingface.co/stabilityai/stable-diffusion-2-1-base/resolve/main/v2-1_512-ema-pruned.safetensors?download=true", + "desc": "This stable-diffusion-2-1-base model fine-tunes stable-diffusion-2-base (512-base-ema.ckpt) with 220k extra steps taken", + "preview": "stabilityai--stable-diffusion-2.1-base.jpg", + "original": true + }, + "StabilityAI SD 2.1 V": { + "path": "stabilityai/stable-diffusion-2-1-base", + "alt": "v2-1_768-ema-pruned.safetensors@https://huggingface.co/stabilityai/stable-diffusion-2-1/resolve/main/v2-1_768-ema-pruned.safetensors?download=true", + "desc": "This stable-diffusion-2 model is resumed from stable-diffusion-2-base (512-base-ema.ckpt) and trained for 150k steps using a v-objective on the same dataset. Resumed for another 140k steps on 768x768 images", + "preview": "stabilityai--stable-diffusion-2.1-base.jpg", + "original": true }, "StabilityAI SD-XL 1.0 Base": { "path": "stabilityai/stable-diffusion-xl-base-1.0", @@ -36,8 +49,10 @@ }, "StabilityAI SD 2.1 Turbo": { "path": "stabilityai/sd-turbo", + "alt": "sd_turbo.safetensors@https://huggingface.co/stabilityai/sd-turbo/resolve/main/sd_turbo.safetensors?download=true", "desc": "SD-Turbo is a distilled version of Stable Diffusion 2.1, trained for real-time synthesis. SD-Turbo is based on a novel training method called Adversarial Diffusion Distillation (ADD) (see the technical report), which allows sampling large-scale foundational image diffusion models in 1 to 4 steps at high image quality. This approach uses score distillation to leverage large-scale off-the-shelf image diffusion models as a teacher signal and combines this with an adversarial loss to ensure high image fidelity even in the low-step regime of one or two sampling steps.", - "preview": "stabilityai--sd-turbo.jpg" + "preview": "stabilityai--sd-turbo.jpg", + "original": true }, "StabilityAI SD-XL Turbo": { "path": "stabilityai/sdxl-turbo", diff --git a/javascript/control.js b/javascript/control.js index 090afb11a..3690c491a 100644 --- a/javascript/control.js +++ b/javascript/control.js @@ -2,6 +2,7 @@ function setupControlUI() { const tabs = ['input', 'output', 'preview']; for (const tab of tabs) { const btn = gradioApp().getElementById(`control-${tab}-button`); + if (!btn) continue; // eslint-disable-line no-continue btn.style.cursor = 'pointer'; btn.onclick = () => { const t = gradioApp().getElementById(`control-tab-${tab}`); diff --git a/javascript/sdnext.css b/javascript/sdnext.css index 86a6bcbdb..8ebaf10c5 100644 --- a/javascript/sdnext.css +++ b/javascript/sdnext.css @@ -70,6 +70,7 @@ button.custom-button{ border-radius: var(--button-large-radius); padding: var(-- .performance { font-size: 0.85em; color: #444; } .performance p { display: inline-block; color: var(--body-text-color-subdued) !important } .performance .time { margin-right: 0; } +.thumbnails { background: var(--body-background-fill); } #control_gallery { height: 564px; } #control-result { padding: 0.5em; } #control-inputs { margin-top: 1em; } diff --git a/modules/control/proc/edge.py b/modules/control/proc/edge.py index d2d8b2df2..73481129f 100644 --- a/modules/control/proc/edge.py +++ b/modules/control/proc/edge.py @@ -58,7 +58,7 @@ def __call__(self, input_image=None, pf=True, mode='edge', detect_resolution=512 edge_map = cv2.resize(edge_map, (W, H), interpolation=cv2.INTER_LINEAR) if output_type == "pil": - edge_map = edge_map.convert('L') edge_map = Image.fromarray(edge_map) + edge_map = edge_map.convert('L') return edge_map diff --git a/modules/control/units/xs.py b/modules/control/units/xs.py index 1ddef9dc1..b0dc84659 100644 --- a/modules/control/units/xs.py +++ b/modules/control/units/xs.py @@ -1,16 +1,11 @@ import os import time from typing import Union +from diffusers import StableDiffusionPipeline, StableDiffusionXLPipeline from modules.shared import log, opts from modules import errors - -ok = True -try: - from diffusers import StableDiffusionPipeline, StableDiffusionXLPipeline, ControlNetXSModel, StableDiffusionControlNetXSPipeline, StableDiffusionXLControlNetXSPipeline -except Exception: - from diffusers import ControlNetModel - ControlNetXSModel = ControlNetModel # dummy - ok = False +from modules.control.units.xs_model import ControlNetXSModel +from modules.control.units.xs_pipe import StableDiffusionControlNetXSPipeline, StableDiffusionXLControlNetXSPipeline what = 'ControlNet-XS' @@ -43,8 +38,6 @@ def find_models(): def list_models(refresh=False): global models # pylint: disable=global-statement - if not ok: - return models import modules.shared if not refresh and len(models) > 0: return models @@ -130,7 +123,7 @@ def __init__(self, controlnet: Union[ControlNetXSModel, list[ControlNetXSModel]] tokenizer_2=pipeline.tokenizer_2, unet=pipeline.unet, scheduler=pipeline.scheduler, - feature_extractor=getattr(pipeline, 'feature_extractor', None), + # feature_extractor=getattr(pipeline, 'feature_extractor', None), controlnet=controlnet, # can be a list ).to(pipeline.device) elif isinstance(pipeline, StableDiffusionPipeline): diff --git a/modules/control/units/xs_model.py b/modules/control/units/xs_model.py new file mode 100644 index 000000000..c6419b44d --- /dev/null +++ b/modules/control/units/xs_model.py @@ -0,0 +1,1016 @@ +# Copyright 2023 The HuggingFace Team. All rights reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +import math +from dataclasses import dataclass +from typing import Any, Dict, List, Optional, Tuple, Union + +import torch +import torch.utils.checkpoint +from torch import nn +from torch.nn import functional as F +from torch.nn.modules.normalization import GroupNorm + +from diffusers.configuration_utils import ConfigMixin, register_to_config +from diffusers.models.attention_processor import USE_PEFT_BACKEND, AttentionProcessor +from diffusers.models.autoencoders import AutoencoderKL +from diffusers.models.lora import LoRACompatibleConv +from diffusers.models.modeling_utils import ModelMixin +from diffusers.models.unet_2d_blocks import ( + CrossAttnDownBlock2D, + CrossAttnUpBlock2D, + DownBlock2D, + Downsample2D, + ResnetBlock2D, + Transformer2DModel, + UpBlock2D, + Upsample2D, +) +from diffusers.models.unet_2d_condition import UNet2DConditionModel +from diffusers.utils import BaseOutput, logging + + +logger = logging.get_logger(__name__) # pylint: disable=invalid-name + + +@dataclass +class ControlNetXSOutput(BaseOutput): + """ + The output of [`ControlNetXSModel`]. + + Args: + sample (`torch.FloatTensor` of shape `(batch_size, num_channels, height, width)`): + The output of the `ControlNetXSModel`. Unlike `ControlNetOutput` this is NOT to be added to the base model + output, but is already the final output. + """ + + sample: torch.FloatTensor = None + + +# copied from diffusers.models.controlnet.ControlNetConditioningEmbedding +class ControlNetConditioningEmbedding(nn.Module): + """ + Quoting from https://arxiv.org/abs/2302.05543: "Stable Diffusion uses a pre-processing method similar to VQ-GAN + [11] to convert the entire dataset of 512 × 512 images into smaller 64 × 64 “latent images” for stabilized + training. This requires ControlNets to convert image-based conditions to 64 × 64 feature space to match the + convolution size. We use a tiny network E(·) of four convolution layers with 4 × 4 kernels and 2 × 2 strides + (activated by ReLU, channels are 16, 32, 64, 128, initialized with Gaussian weights, trained jointly with the full + model) to encode image-space conditions ... into feature maps ..." + """ + + def __init__( + self, + conditioning_embedding_channels: int, + conditioning_channels: int = 3, + block_out_channels: Tuple[int, ...] = (16, 32, 96, 256), + ): + super().__init__() + + self.conv_in = nn.Conv2d(conditioning_channels, block_out_channels[0], kernel_size=3, padding=1) + + self.blocks = nn.ModuleList([]) + + for i in range(len(block_out_channels) - 1): + channel_in = block_out_channels[i] + channel_out = block_out_channels[i + 1] + self.blocks.append(nn.Conv2d(channel_in, channel_in, kernel_size=3, padding=1)) + self.blocks.append(nn.Conv2d(channel_in, channel_out, kernel_size=3, padding=1, stride=2)) + + self.conv_out = zero_module( + nn.Conv2d(block_out_channels[-1], conditioning_embedding_channels, kernel_size=3, padding=1) + ) + + def forward(self, conditioning): + embedding = self.conv_in(conditioning) + embedding = F.silu(embedding) + + for block in self.blocks: + embedding = block(embedding) + embedding = F.silu(embedding) + + embedding = self.conv_out(embedding) + + return embedding + + +class ControlNetXSModel(ModelMixin, ConfigMixin): + r""" + A ControlNet-XS model + + This model inherits from [`ModelMixin`] and [`ConfigMixin`]. Check the superclass documentation for it's generic + methods implemented for all models (such as downloading or saving). + + Most of parameters for this model are passed into the [`UNet2DConditionModel`] it creates. Check the documentation + of [`UNet2DConditionModel`] for them. + + Parameters: + conditioning_channels (`int`, defaults to 3): + Number of channels of conditioning input (e.g. an image) + controlnet_conditioning_channel_order (`str`, defaults to `"rgb"`): + The channel order of conditional image. Will convert to `rgb` if it's `bgr`. + conditioning_embedding_out_channels (`tuple[int]`, defaults to `(16, 32, 96, 256)`): + The tuple of output channel for each block in the `controlnet_cond_embedding` layer. + time_embedding_input_dim (`int`, defaults to 320): + Dimension of input into time embedding. Needs to be same as in the base model. + time_embedding_dim (`int`, defaults to 1280): + Dimension of output from time embedding. Needs to be same as in the base model. + learn_embedding (`bool`, defaults to `False`): + Whether to use time embedding of the control model. If yes, the time embedding is a linear interpolation of + the time embeddings of the control and base model with interpolation parameter `time_embedding_mix**3`. + time_embedding_mix (`float`, defaults to 1.0): + Linear interpolation parameter used if `learn_embedding` is `True`. A value of 1.0 means only the + control model's time embedding will be used. A value of 0.0 means only the base model's time embedding will be used. + base_model_channel_sizes (`Dict[str, List[Tuple[int]]]`): + Channel sizes of each subblock of base model. Use `gather_subblock_sizes` on your base model to compute it. + """ + + @classmethod + def init_original(cls, base_model: UNet2DConditionModel, is_sdxl=True): + """ + Create a ControlNetXS model with the same parameters as in the original paper (https://github.com/vislearn/ControlNet-XS). + + Parameters: + base_model (`UNet2DConditionModel`): + Base UNet model. Needs to be either StableDiffusion or StableDiffusion-XL. + is_sdxl (`bool`, defaults to `True`): + Whether passed `base_model` is a StableDiffusion-XL model. + """ + + def get_dim_attn_heads(base_model: UNet2DConditionModel, size_ratio: float, num_attn_heads: int): + """ + Currently, diffusers can only set the dimension of attention heads (see https://github.com/huggingface/diffusers/issues/2011#issuecomment-1547958131 for why). + The original ControlNet-XS model, however, define the number of attention heads. + That's why compute the dimensions needed to get the correct number of attention heads. + """ + block_out_channels = [int(size_ratio * c) for c in base_model.config.block_out_channels] + dim_attn_heads = [math.ceil(c / num_attn_heads) for c in block_out_channels] + return dim_attn_heads + + if is_sdxl: + return ControlNetXSModel.from_unet( + base_model, + time_embedding_mix=0.95, + learn_embedding=True, + size_ratio=0.1, + conditioning_embedding_out_channels=(16, 32, 96, 256), + num_attention_heads=get_dim_attn_heads(base_model, 0.1, 64), + ) + else: + return ControlNetXSModel.from_unet( + base_model, + time_embedding_mix=1.0, + learn_embedding=True, + size_ratio=0.0125, + conditioning_embedding_out_channels=(16, 32, 96, 256), + num_attention_heads=get_dim_attn_heads(base_model, 0.0125, 8), + ) + + @classmethod + def _gather_subblock_sizes(cls, unet: UNet2DConditionModel, base_or_control: str): + """To create correctly sized connections between base and control model, we need to know + the input and output channels of each subblock. + + Parameters: + unet (`UNet2DConditionModel`): + Unet of which the subblock channels sizes are to be gathered. + base_or_control (`str`): + Needs to be either "base" or "control". If "base", decoder is also considered. + """ + if base_or_control not in ["base", "control"]: + raise ValueError("`base_or_control` needs to be either `base` or `control`") + + channel_sizes = {"down": [], "mid": [], "up": []} + + # input convolution + channel_sizes["down"].append((unet.conv_in.in_channels, unet.conv_in.out_channels)) + + # encoder blocks + for module in unet.down_blocks: + if isinstance(module, (CrossAttnDownBlock2D, DownBlock2D)): + for r in module.resnets: + channel_sizes["down"].append((r.in_channels, r.out_channels)) + if module.downsamplers: + channel_sizes["down"].append( + (module.downsamplers[0].channels, module.downsamplers[0].out_channels) + ) + else: + raise ValueError(f"Encountered unknown module of type {type(module)} while creating ControlNet-XS.") + + # middle block + channel_sizes["mid"].append((unet.mid_block.resnets[0].in_channels, unet.mid_block.resnets[0].out_channels)) + + # decoder blocks + if base_or_control == "base": + for module in unet.up_blocks: + if isinstance(module, (CrossAttnUpBlock2D, UpBlock2D)): + for r in module.resnets: + channel_sizes["up"].append((r.in_channels, r.out_channels)) + else: + raise ValueError( + f"Encountered unknown module of type {type(module)} while creating ControlNet-XS." + ) + + return channel_sizes + + @register_to_config + def __init__( + self, + conditioning_channels: int = 3, + conditioning_embedding_out_channels: Tuple[int] = (16, 32, 96, 256), + controlnet_conditioning_channel_order: str = "rgb", + time_embedding_input_dim: int = 320, + time_embedding_dim: int = 1280, + time_embedding_mix: float = 1.0, + learn_embedding: bool = False, + base_model_channel_sizes: Dict[str, List[Tuple[int]]] = { + "down": [ + (4, 320), + (320, 320), + (320, 320), + (320, 320), + (320, 640), + (640, 640), + (640, 640), + (640, 1280), + (1280, 1280), + ], + "mid": [(1280, 1280)], + "up": [ + (2560, 1280), + (2560, 1280), + (1920, 1280), + (1920, 640), + (1280, 640), + (960, 640), + (960, 320), + (640, 320), + (640, 320), + ], + }, + sample_size: Optional[int] = None, + down_block_types: Tuple[str] = ( + "CrossAttnDownBlock2D", + "CrossAttnDownBlock2D", + "CrossAttnDownBlock2D", + "DownBlock2D", + ), + up_block_types: Tuple[str] = ("UpBlock2D", "CrossAttnUpBlock2D", "CrossAttnUpBlock2D", "CrossAttnUpBlock2D"), + block_out_channels: Tuple[int] = (320, 640, 1280, 1280), + norm_num_groups: Optional[int] = 32, + cross_attention_dim: Union[int, Tuple[int]] = 1280, + transformer_layers_per_block: Union[int, Tuple[int], Tuple[Tuple]] = 1, + num_attention_heads: Optional[Union[int, Tuple[int]]] = 8, + upcast_attention: bool = False, + ): + super().__init__() + + # 1 - Create control unet + self.control_model = UNet2DConditionModel( + sample_size=sample_size, + down_block_types=down_block_types, + up_block_types=up_block_types, + block_out_channels=block_out_channels, + norm_num_groups=norm_num_groups, + cross_attention_dim=cross_attention_dim, + transformer_layers_per_block=transformer_layers_per_block, + attention_head_dim=num_attention_heads, + use_linear_projection=True, + upcast_attention=upcast_attention, + time_embedding_dim=time_embedding_dim, + ) + + # 2 - Do model surgery on control model + # 2.1 - Allow to use the same time information as the base model + adjust_time_dims(self.control_model, time_embedding_input_dim, time_embedding_dim) + + # 2.2 - Allow for information infusion from base model + + # We concat the output of each base encoder subblocks to the input of the next control encoder subblock + # (We ignore the 1st element, as it represents the `conv_in`.) + extra_input_channels = [input_channels for input_channels, _ in base_model_channel_sizes["down"][1:]] + it_extra_input_channels = iter(extra_input_channels) + + for b, block in enumerate(self.control_model.down_blocks): + for r in range(len(block.resnets)): + increase_block_input_in_encoder_resnet( + self.control_model, block_no=b, resnet_idx=r, by=next(it_extra_input_channels) + ) + + if block.downsamplers: + increase_block_input_in_encoder_downsampler( + self.control_model, block_no=b, by=next(it_extra_input_channels) + ) + + increase_block_input_in_mid_resnet(self.control_model, by=extra_input_channels[-1]) + + # 2.3 - Make group norms work with modified channel sizes + adjust_group_norms(self.control_model) + + # 3 - Gather Channel Sizes + self.ch_inout_ctrl = ControlNetXSModel._gather_subblock_sizes(self.control_model, base_or_control="control") + self.ch_inout_base = base_model_channel_sizes + + # 4 - Build connections between base and control model + self.down_zero_convs_out = nn.ModuleList([]) + self.down_zero_convs_in = nn.ModuleList([]) + self.middle_block_out = nn.ModuleList([]) + self.middle_block_in = nn.ModuleList([]) + self.up_zero_convs_out = nn.ModuleList([]) + self.up_zero_convs_in = nn.ModuleList([]) + + for ch_io_base in self.ch_inout_base["down"]: + self.down_zero_convs_in.append(self._make_zero_conv(in_channels=ch_io_base[1], out_channels=ch_io_base[1])) + for i in range(len(self.ch_inout_ctrl["down"])): + self.down_zero_convs_out.append( + self._make_zero_conv(self.ch_inout_ctrl["down"][i][1], self.ch_inout_base["down"][i][1]) + ) + + self.middle_block_out = self._make_zero_conv( + self.ch_inout_ctrl["mid"][-1][1], self.ch_inout_base["mid"][-1][1] + ) + + self.up_zero_convs_out.append( + self._make_zero_conv(self.ch_inout_ctrl["down"][-1][1], self.ch_inout_base["mid"][-1][1]) + ) + for i in range(1, len(self.ch_inout_ctrl["down"])): + self.up_zero_convs_out.append( + self._make_zero_conv(self.ch_inout_ctrl["down"][-(i + 1)][1], self.ch_inout_base["up"][i - 1][1]) + ) + + # 5 - Create conditioning hint embedding + self.controlnet_cond_embedding = ControlNetConditioningEmbedding( + conditioning_embedding_channels=block_out_channels[0], + block_out_channels=conditioning_embedding_out_channels, + conditioning_channels=conditioning_channels, + ) + + # In the mininal implementation setting, we only need the control model up to the mid block + del self.control_model.up_blocks + del self.control_model.conv_norm_out + del self.control_model.conv_out + + @classmethod + def from_unet( + cls, + unet: UNet2DConditionModel, + conditioning_channels: int = 3, + conditioning_embedding_out_channels: Tuple[int] = (16, 32, 96, 256), + controlnet_conditioning_channel_order: str = "rgb", + learn_embedding: bool = False, + time_embedding_mix: float = 1.0, + block_out_channels: Optional[Tuple[int]] = None, + size_ratio: Optional[float] = None, + num_attention_heads: Optional[Union[int, Tuple[int]]] = 8, + norm_num_groups: Optional[int] = None, + ): + r""" + Instantiate a [`ControlNetXSModel`] from [`UNet2DConditionModel`]. + + Parameters: + unet (`UNet2DConditionModel`): + The UNet model we want to control. The dimensions of the ControlNetXSModel will be adapted to it. + conditioning_channels (`int`, defaults to 3): + Number of channels of conditioning input (e.g. an image) + conditioning_embedding_out_channels (`tuple[int]`, defaults to `(16, 32, 96, 256)`): + The tuple of output channel for each block in the `controlnet_cond_embedding` layer. + controlnet_conditioning_channel_order (`str`, defaults to `"rgb"`): + The channel order of conditional image. Will convert to `rgb` if it's `bgr`. + learn_embedding (`bool`, defaults to `False`): + Wether to use time embedding of the control model. If yes, the time embedding is a linear interpolation + of the time embeddings of the control and base model with interpolation parameter + `time_embedding_mix**3`. + time_embedding_mix (`float`, defaults to 1.0): + Linear interpolation parameter used if `learn_embedding` is `True`. + block_out_channels (`Tuple[int]`, *optional*): + Down blocks output channels in control model. Either this or `size_ratio` must be given. + size_ratio (float, *optional*): + When given, block_out_channels is set to a relative fraction of the base model's block_out_channels. + Either this or `block_out_channels` must be given. + num_attention_heads (`Union[int, Tuple[int]]`, *optional*): + The dimension of the attention heads. The naming seems a bit confusing and it is, see https://github.com/huggingface/diffusers/issues/2011#issuecomment-1547958131 for why. + norm_num_groups (int, *optional*, defaults to `None`): + The number of groups to use for the normalization of the control unet. If `None`, + `int(unet.config.norm_num_groups * size_ratio)` is taken. + """ + + # Check input + fixed_size = block_out_channels is not None + relative_size = size_ratio is not None + if not (fixed_size ^ relative_size): + raise ValueError( + "Pass exactly one of `block_out_channels` (for absolute sizing) or `control_model_ratio` (for relative sizing)." + ) + + # Create model + if block_out_channels is None: + block_out_channels = [int(size_ratio * c) for c in unet.config.block_out_channels] + + # Check that attention heads and group norms match channel sizes + # - attention heads + def attn_heads_match_channel_sizes(attn_heads, channel_sizes): + if isinstance(attn_heads, (tuple, list)): + return all(c % a == 0 for a, c in zip(attn_heads, channel_sizes)) + else: + return all(c % attn_heads == 0 for c in channel_sizes) + + num_attention_heads = num_attention_heads or unet.config.attention_head_dim + if not attn_heads_match_channel_sizes(num_attention_heads, block_out_channels): + raise ValueError( + f"The dimension of attention heads ({num_attention_heads}) must divide `block_out_channels` ({block_out_channels}). If you didn't set `num_attention_heads` the default settings don't match your model. Set `num_attention_heads` manually." + ) + + # - group norms + def group_norms_match_channel_sizes(num_groups, channel_sizes): + return all(c % num_groups == 0 for c in channel_sizes) + + if norm_num_groups is None: + if group_norms_match_channel_sizes(unet.config.norm_num_groups, block_out_channels): + norm_num_groups = unet.config.norm_num_groups + else: + norm_num_groups = min(block_out_channels) + + if group_norms_match_channel_sizes(norm_num_groups, block_out_channels): + print( + f"`norm_num_groups` was set to `min(block_out_channels)` (={norm_num_groups}) so it divides all block_out_channels` ({block_out_channels}). Set it explicitly to remove this information." + ) + else: + raise ValueError( + f"`block_out_channels` ({block_out_channels}) don't match the base models `norm_num_groups` ({unet.config.norm_num_groups}). Setting `norm_num_groups` to `min(block_out_channels)` ({norm_num_groups}) didn't fix this. Pass `norm_num_groups` explicitly so it divides all block_out_channels." + ) + + def get_time_emb_input_dim(unet: UNet2DConditionModel): + return unet.time_embedding.linear_1.in_features + + def get_time_emb_dim(unet: UNet2DConditionModel): + return unet.time_embedding.linear_2.out_features + + # Clone params from base unet if + # (i) it's required to build SD or SDXL, and + # (ii) it's not used for the time embedding (as time embedding of control model is never used), and + # (iii) it's not set further below anyway + to_keep = [ + "cross_attention_dim", + "down_block_types", + "sample_size", + "transformer_layers_per_block", + "up_block_types", + "upcast_attention", + ] + kwargs = {k: v for k, v in dict(unet.config).items() if k in to_keep} + kwargs.update(block_out_channels=block_out_channels) + kwargs.update(num_attention_heads=num_attention_heads) + kwargs.update(norm_num_groups=norm_num_groups) + + # Add controlnetxs-specific params + kwargs.update( + conditioning_channels=conditioning_channels, + controlnet_conditioning_channel_order=controlnet_conditioning_channel_order, + time_embedding_input_dim=get_time_emb_input_dim(unet), + time_embedding_dim=get_time_emb_dim(unet), + time_embedding_mix=time_embedding_mix, + learn_embedding=learn_embedding, + base_model_channel_sizes=ControlNetXSModel._gather_subblock_sizes(unet, base_or_control="base"), + conditioning_embedding_out_channels=conditioning_embedding_out_channels, + ) + + return cls(**kwargs) + + @property + def attn_processors(self) -> Dict[str, AttentionProcessor]: + r""" + Returns: + `dict` of attention processors: A dictionary containing all attention processors used in the model with + indexed by its weight name. + """ + return self.control_model.attn_processors + + def set_attn_processor( + self, processor: Union[AttentionProcessor, Dict[str, AttentionProcessor]], _remove_lora=False + ): + r""" + Sets the attention processor to use to compute attention. + + Parameters: + processor (`dict` of `AttentionProcessor` or only `AttentionProcessor`): + The instantiated processor class or a dictionary of processor classes that will be set as the processor + for **all** `Attention` layers. + + If `processor` is a dict, the key needs to define the path to the corresponding cross attention + processor. This is strongly recommended when setting trainable attention processors. + + """ + self.control_model.set_attn_processor(processor, _remove_lora) + + def set_default_attn_processor(self): + """ + Disables custom attention processors and sets the default attention implementation. + """ + self.control_model.set_default_attn_processor() + + def set_attention_slice(self, slice_size): + r""" + Enable sliced attention computation. + + When this option is enabled, the attention module splits the input tensor in slices to compute attention in + several steps. This is useful for saving some memory in exchange for a small decrease in speed. + + Args: + slice_size (`str` or `int` or `list(int)`, *optional*, defaults to `"auto"`): + When `"auto"`, input to the attention heads is halved, so attention is computed in two steps. If + `"max"`, maximum amount of memory is saved by running only one slice at a time. If a number is + provided, uses as many slices as `attention_head_dim // slice_size`. In this case, `attention_head_dim` + must be a multiple of `slice_size`. + """ + self.control_model.set_attention_slice(slice_size) + + def _set_gradient_checkpointing(self, module, value=False): + if isinstance(module, (UNet2DConditionModel)): + if value: + module.enable_gradient_checkpointing() + else: + module.disable_gradient_checkpointing() + + def forward( + self, + base_model: UNet2DConditionModel, + sample: torch.FloatTensor, + timestep: Union[torch.Tensor, float, int], + encoder_hidden_states: torch.Tensor, + controlnet_cond: torch.Tensor, + conditioning_scale: float = 1.0, + class_labels: Optional[torch.Tensor] = None, + timestep_cond: Optional[torch.Tensor] = None, + attention_mask: Optional[torch.Tensor] = None, + cross_attention_kwargs: Optional[Dict[str, Any]] = None, + added_cond_kwargs: Optional[Dict[str, torch.Tensor]] = None, + return_dict: bool = True, + ) -> Union[ControlNetXSOutput, Tuple]: + """ + The [`ControlNetModel`] forward method. + + Args: + base_model (`UNet2DConditionModel`): + The base unet model we want to control. + sample (`torch.FloatTensor`): + The noisy input tensor. + timestep (`Union[torch.Tensor, float, int]`): + The number of timesteps to denoise an input. + encoder_hidden_states (`torch.Tensor`): + The encoder hidden states. + controlnet_cond (`torch.FloatTensor`): + The conditional input tensor of shape `(batch_size, sequence_length, hidden_size)`. + conditioning_scale (`float`, defaults to `1.0`): + How much the control model affects the base model outputs. + class_labels (`torch.Tensor`, *optional*, defaults to `None`): + Optional class labels for conditioning. Their embeddings will be summed with the timestep embeddings. + timestep_cond (`torch.Tensor`, *optional*, defaults to `None`): + Additional conditional embeddings for timestep. If provided, the embeddings will be summed with the + timestep_embedding passed through the `self.time_embedding` layer to obtain the final timestep + embeddings. + attention_mask (`torch.Tensor`, *optional*, defaults to `None`): + An attention mask of shape `(batch, key_tokens)` is applied to `encoder_hidden_states`. If `1` the mask + is kept, otherwise if `0` it is discarded. Mask will be converted into a bias, which adds large + negative values to the attention scores corresponding to "discard" tokens. + added_cond_kwargs (`dict`): + Additional conditions for the Stable Diffusion XL UNet. + cross_attention_kwargs (`dict[str]`, *optional*, defaults to `None`): + A kwargs dictionary that if specified is passed along to the `AttnProcessor`. + return_dict (`bool`, defaults to `True`): + Whether or not to return a [`~models.controlnet.ControlNetOutput`] instead of a plain tuple. + + Returns: + [`~models.controlnetxs.ControlNetXSOutput`] **or** `tuple`: + If `return_dict` is `True`, a [`~models.controlnetxs.ControlNetXSOutput`] is returned, otherwise a + tuple is returned where the first element is the sample tensor. + """ + # check channel order + channel_order = self.config.controlnet_conditioning_channel_order + + if channel_order == "rgb": + # in rgb order by default + ... + elif channel_order == "bgr": + controlnet_cond = torch.flip(controlnet_cond, dims=[1]) + else: + raise ValueError(f"unknown `controlnet_conditioning_channel_order`: {channel_order}") + + # scale control strength + n_connections = len(self.down_zero_convs_out) + 1 + len(self.up_zero_convs_out) + scale_list = torch.full((n_connections,), conditioning_scale) + + # prepare attention_mask + if attention_mask is not None: + attention_mask = (1 - attention_mask.to(sample.dtype)) * -10000.0 + attention_mask = attention_mask.unsqueeze(1) + + # 1. time + timesteps = timestep + if not torch.is_tensor(timesteps): + # TODO: this requires sync between CPU and GPU. So try to pass timesteps as tensors if you can + # This would be a good case for the `match` statement (Python 3.10+) + is_mps = sample.device.type == "mps" + if isinstance(timestep, float): + dtype = torch.float32 if is_mps else torch.float64 + else: + dtype = torch.int32 if is_mps else torch.int64 + timesteps = torch.tensor([timesteps], dtype=dtype, device=sample.device) + elif len(timesteps.shape) == 0: + timesteps = timesteps[None].to(sample.device) + + # broadcast to batch dimension in a way that's compatible with ONNX/Core ML + timesteps = timesteps.expand(sample.shape[0]) + + t_emb = base_model.time_proj(timesteps) + + # timesteps does not contain any weights and will always return f32 tensors + # but time_embedding might actually be running in fp16. so we need to cast here. + # there might be better ways to encapsulate this. + t_emb = t_emb.to(dtype=sample.dtype) + + if self.config.learn_embedding: + ctrl_temb = self.control_model.time_embedding(t_emb, timestep_cond) + base_temb = base_model.time_embedding(t_emb, timestep_cond) + interpolation_param = self.config.time_embedding_mix**0.3 + + temb = ctrl_temb * interpolation_param + base_temb * (1 - interpolation_param) + else: + temb = base_model.time_embedding(t_emb) + + # added time & text embeddings + aug_emb = None + + if base_model.class_embedding is not None: + if class_labels is None: + raise ValueError("class_labels should be provided when num_class_embeds > 0") + + if base_model.config.class_embed_type == "timestep": + class_labels = base_model.time_proj(class_labels) + + class_emb = base_model.class_embedding(class_labels).to(dtype=self.dtype) + temb = temb + class_emb + + if base_model.config.addition_embed_type is not None: + if base_model.config.addition_embed_type == "text": + aug_emb = base_model.add_embedding(encoder_hidden_states) + elif base_model.config.addition_embed_type == "text_image": + raise NotImplementedError() + elif base_model.config.addition_embed_type == "text_time": + # SDXL - style + if "text_embeds" not in added_cond_kwargs: + raise ValueError( + f"{self.__class__} has the config param `addition_embed_type` set to 'text_time' which requires the keyword argument `text_embeds` to be passed in `added_cond_kwargs`" + ) + text_embeds = added_cond_kwargs.get("text_embeds") + if "time_ids" not in added_cond_kwargs: + raise ValueError( + f"{self.__class__} has the config param `addition_embed_type` set to 'text_time' which requires the keyword argument `time_ids` to be passed in `added_cond_kwargs`" + ) + time_ids = added_cond_kwargs.get("time_ids") + time_embeds = base_model.add_time_proj(time_ids.flatten()) + time_embeds = time_embeds.reshape((text_embeds.shape[0], -1)) + add_embeds = torch.concat([text_embeds, time_embeds], dim=-1) + add_embeds = add_embeds.to(temb.dtype) + aug_emb = base_model.add_embedding(add_embeds) + elif base_model.config.addition_embed_type == "image": + raise NotImplementedError() + elif base_model.config.addition_embed_type == "image_hint": + raise NotImplementedError() + + temb = temb + aug_emb if aug_emb is not None else temb + + # text embeddings + cemb = encoder_hidden_states + + # Preparation + guided_hint = self.controlnet_cond_embedding(controlnet_cond) + + h_ctrl = h_base = sample + hs_base, hs_ctrl = [], [] + it_down_convs_in, it_down_convs_out, it_dec_convs_in, it_up_convs_out = map( + iter, (self.down_zero_convs_in, self.down_zero_convs_out, self.up_zero_convs_in, self.up_zero_convs_out) + ) + scales = iter(scale_list) + + base_down_subblocks = to_sub_blocks(base_model.down_blocks) + ctrl_down_subblocks = to_sub_blocks(self.control_model.down_blocks) + base_mid_subblocks = to_sub_blocks([base_model.mid_block]) + ctrl_mid_subblocks = to_sub_blocks([self.control_model.mid_block]) + base_up_subblocks = to_sub_blocks(base_model.up_blocks) + + # Cross Control + # 0 - conv in + h_base = base_model.conv_in(h_base) + h_ctrl = self.control_model.conv_in(h_ctrl) + if guided_hint is not None: + h_ctrl += guided_hint + h_base = h_base + next(it_down_convs_out)(h_ctrl) * next(scales) # D - add ctrl -> base + + hs_base.append(h_base) + hs_ctrl.append(h_ctrl) + + # 1 - down + for m_base, m_ctrl in zip(base_down_subblocks, ctrl_down_subblocks): + h_ctrl = torch.cat([h_ctrl, next(it_down_convs_in)(h_base)], dim=1) # A - concat base -> ctrl + h_base = m_base(h_base, temb, cemb, attention_mask, cross_attention_kwargs) # B - apply base subblock + h_ctrl = m_ctrl(h_ctrl, temb, cemb, attention_mask, cross_attention_kwargs) # C - apply ctrl subblock + h_base = h_base + next(it_down_convs_out)(h_ctrl) * next(scales) # D - add ctrl -> base + hs_base.append(h_base) + hs_ctrl.append(h_ctrl) + + # 2 - mid + h_ctrl = torch.cat([h_ctrl, next(it_down_convs_in)(h_base)], dim=1) # A - concat base -> ctrl + for m_base, m_ctrl in zip(base_mid_subblocks, ctrl_mid_subblocks): + h_base = m_base(h_base, temb, cemb, attention_mask, cross_attention_kwargs) # B - apply base subblock + h_ctrl = m_ctrl(h_ctrl, temb, cemb, attention_mask, cross_attention_kwargs) # C - apply ctrl subblock + h_base = h_base + self.middle_block_out(h_ctrl) * next(scales) # D - add ctrl -> base + + # 3 - up + for i, m_base in enumerate(base_up_subblocks): + h_base = h_base + next(it_up_convs_out)(hs_ctrl.pop()) * next(scales) # add info from ctrl encoder + h_base = torch.cat([h_base, hs_base.pop()], dim=1) # concat info from base encoder+ctrl encoder + h_base = m_base(h_base, temb, cemb, attention_mask, cross_attention_kwargs) + + h_base = base_model.conv_norm_out(h_base) + h_base = base_model.conv_act(h_base) + h_base = base_model.conv_out(h_base) + + if not return_dict: + return h_base + + return ControlNetXSOutput(sample=h_base) + + def _make_zero_conv(self, in_channels, out_channels=None): + # keep running track of channels sizes + self.in_channels = in_channels + self.out_channels = out_channels or in_channels + + return zero_module(nn.Conv2d(in_channels, out_channels, 1, padding=0)) + + @torch.no_grad() + def _check_if_vae_compatible(self, vae: AutoencoderKL): + condition_downscale_factor = 2 ** (len(self.config.conditioning_embedding_out_channels) - 1) + vae_downscale_factor = 2 ** (len(vae.config.block_out_channels) - 1) + compatible = condition_downscale_factor == vae_downscale_factor + return compatible, condition_downscale_factor, vae_downscale_factor + + +class SubBlock(nn.ModuleList): + """A SubBlock is the largest piece of either base or control model, that is executed independently of the other model respectively. + Before each subblock, information is concatted from base to control. And after each subblock, information is added from control to base. + """ + + def __init__(self, ms, *args, **kwargs): + if not is_iterable(ms): + ms = [ms] + super().__init__(ms, *args, **kwargs) + + def forward( + self, + x: torch.Tensor, + temb: torch.Tensor, + cemb: torch.Tensor, + attention_mask: Optional[torch.Tensor] = None, + cross_attention_kwargs: Optional[Dict[str, Any]] = None, + ): + """Iterate through children and pass correct information to each.""" + for m in self: + if isinstance(m, ResnetBlock2D): + x = m(x, temb) + elif isinstance(m, Transformer2DModel): + x = m(x, cemb, attention_mask=attention_mask, cross_attention_kwargs=cross_attention_kwargs).sample + elif isinstance(m, Downsample2D): + x = m(x) + elif isinstance(m, Upsample2D): + x = m(x) + else: + raise ValueError( + f"Type of m is {type(m)} but should be `ResnetBlock2D`, `Transformer2DModel`, `Downsample2D` or `Upsample2D`" + ) + + return x + + +def adjust_time_dims(unet: UNet2DConditionModel, in_dim: int, out_dim: int): + unet.time_embedding.linear_1 = nn.Linear(in_dim, out_dim) + + +def increase_block_input_in_encoder_resnet(unet: UNet2DConditionModel, block_no, resnet_idx, by): + """Increase channels sizes to allow for additional concatted information from base model""" + r = unet.down_blocks[block_no].resnets[resnet_idx] + old_norm1, old_conv1 = r.norm1, r.conv1 + # norm + norm_args = "num_groups num_channels eps affine".split(" ") + for a in norm_args: + assert hasattr(old_norm1, a) + norm_kwargs = {a: getattr(old_norm1, a) for a in norm_args} + norm_kwargs["num_channels"] += by # surgery done here + # conv1 + conv1_args = [ + "in_channels", + "out_channels", + "kernel_size", + "stride", + "padding", + "dilation", + "groups", + "bias", + "padding_mode", + ] + if not USE_PEFT_BACKEND: + conv1_args.append("lora_layer") + + for a in conv1_args: + assert hasattr(old_conv1, a) + + conv1_kwargs = {a: getattr(old_conv1, a) for a in conv1_args} + conv1_kwargs["bias"] = "bias" in conv1_kwargs # as param, bias is a boolean, but as attr, it's a tensor. + conv1_kwargs["in_channels"] += by # surgery done here + # conv_shortcut + # as we changed the input size of the block, the input and output sizes are likely different, + # therefore we need a conv_shortcut (simply adding won't work) + conv_shortcut_args_kwargs = { + "in_channels": conv1_kwargs["in_channels"], + "out_channels": conv1_kwargs["out_channels"], + # default arguments from resnet.__init__ + "kernel_size": 1, + "stride": 1, + "padding": 0, + "bias": True, + } + # swap old with new modules + unet.down_blocks[block_no].resnets[resnet_idx].norm1 = GroupNorm(**norm_kwargs) + unet.down_blocks[block_no].resnets[resnet_idx].conv1 = ( + nn.Conv2d(**conv1_kwargs) if USE_PEFT_BACKEND else LoRACompatibleConv(**conv1_kwargs) + ) + unet.down_blocks[block_no].resnets[resnet_idx].conv_shortcut = ( + nn.Conv2d(**conv_shortcut_args_kwargs) if USE_PEFT_BACKEND else LoRACompatibleConv(**conv_shortcut_args_kwargs) + ) + unet.down_blocks[block_no].resnets[resnet_idx].in_channels += by # surgery done here + + +def increase_block_input_in_encoder_downsampler(unet: UNet2DConditionModel, block_no, by): + """Increase channels sizes to allow for additional concatted information from base model""" + old_down = unet.down_blocks[block_no].downsamplers[0].conv + + args = [ + "in_channels", + "out_channels", + "kernel_size", + "stride", + "padding", + "dilation", + "groups", + "bias", + "padding_mode", + ] + if not USE_PEFT_BACKEND: + args.append("lora_layer") + + for a in args: + assert hasattr(old_down, a) + kwargs = {a: getattr(old_down, a) for a in args} + kwargs["bias"] = "bias" in kwargs # as param, bias is a boolean, but as attr, it's a tensor. + kwargs["in_channels"] += by # surgery done here + # swap old with new modules + unet.down_blocks[block_no].downsamplers[0].conv = ( + nn.Conv2d(**kwargs) if USE_PEFT_BACKEND else LoRACompatibleConv(**kwargs) + ) + unet.down_blocks[block_no].downsamplers[0].channels += by # surgery done here + + +def increase_block_input_in_mid_resnet(unet: UNet2DConditionModel, by): + """Increase channels sizes to allow for additional concatted information from base model""" + m = unet.mid_block.resnets[0] + old_norm1, old_conv1 = m.norm1, m.conv1 + # norm + norm_args = "num_groups num_channels eps affine".split(" ") + for a in norm_args: + assert hasattr(old_norm1, a) + norm_kwargs = {a: getattr(old_norm1, a) for a in norm_args} + norm_kwargs["num_channels"] += by # surgery done here + conv1_args = [ + "in_channels", + "out_channels", + "kernel_size", + "stride", + "padding", + "dilation", + "groups", + "bias", + "padding_mode", + ] + if not USE_PEFT_BACKEND: + conv1_args.append("lora_layer") + + conv1_kwargs = {a: getattr(old_conv1, a) for a in conv1_args} + conv1_kwargs["bias"] = "bias" in conv1_kwargs # as param, bias is a boolean, but as attr, it's a tensor. + conv1_kwargs["in_channels"] += by # surgery done here + # conv_shortcut + # as we changed the input size of the block, the input and output sizes are likely different, + # therefore we need a conv_shortcut (simply adding won't work) + conv_shortcut_args_kwargs = { + "in_channels": conv1_kwargs["in_channels"], + "out_channels": conv1_kwargs["out_channels"], + # default arguments from resnet.__init__ + "kernel_size": 1, + "stride": 1, + "padding": 0, + "bias": True, + } + # swap old with new modules + unet.mid_block.resnets[0].norm1 = GroupNorm(**norm_kwargs) + unet.mid_block.resnets[0].conv1 = ( + nn.Conv2d(**conv1_kwargs) if USE_PEFT_BACKEND else LoRACompatibleConv(**conv1_kwargs) + ) + unet.mid_block.resnets[0].conv_shortcut = ( + nn.Conv2d(**conv_shortcut_args_kwargs) if USE_PEFT_BACKEND else LoRACompatibleConv(**conv_shortcut_args_kwargs) + ) + unet.mid_block.resnets[0].in_channels += by # surgery done here + + +def adjust_group_norms(unet: UNet2DConditionModel, max_num_group: int = 32): + def find_denominator(number, start): + if start >= number: + return number + while start != 0: + residual = number % start + if residual == 0: + return start + start -= 1 + + for block in [*unet.down_blocks, unet.mid_block]: + # resnets + for r in block.resnets: + if r.norm1.num_groups < max_num_group: + r.norm1.num_groups = find_denominator(r.norm1.num_channels, start=max_num_group) + + if r.norm2.num_groups < max_num_group: + r.norm2.num_groups = find_denominator(r.norm2.num_channels, start=max_num_group) + + # transformers + if hasattr(block, "attentions"): + for a in block.attentions: + if a.norm.num_groups < max_num_group: + a.norm.num_groups = find_denominator(a.norm.num_channels, start=max_num_group) + + +def is_iterable(o): + if isinstance(o, str): + return False + try: + iter(o) + return True + except TypeError: + return False + + +def to_sub_blocks(blocks): + if not is_iterable(blocks): + blocks = [blocks] + + sub_blocks = [] + + for b in blocks: + if hasattr(b, "resnets"): + if hasattr(b, "attentions") and b.attentions is not None: + for r, a in zip(b.resnets, b.attentions): + sub_blocks.append([r, a]) + + num_resnets = len(b.resnets) + num_attns = len(b.attentions) + + if num_resnets > num_attns: + # we can have more resnets than attentions, so add each resnet as separate subblock + for i in range(num_attns, num_resnets): + sub_blocks.append([b.resnets[i]]) + else: + for r in b.resnets: + sub_blocks.append([r]) + + # upsamplers are part of the same subblock + if hasattr(b, "upsamplers") and b.upsamplers is not None: + for u in b.upsamplers: + sub_blocks[-1].extend([u]) + + # downsamplers are own subblock + if hasattr(b, "downsamplers") and b.downsamplers is not None: + for d in b.downsamplers: + sub_blocks.append([d]) + + return list(map(SubBlock, sub_blocks)) + + +def zero_module(module): + for p in module.parameters(): + nn.init.zeros_(p) + return module diff --git a/modules/control/units/xs_pipe.py b/modules/control/units/xs_pipe.py new file mode 100644 index 000000000..51e6d8191 --- /dev/null +++ b/modules/control/units/xs_pipe.py @@ -0,0 +1,1938 @@ +# Copyright 2023 The HuggingFace Team. All rights reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +import inspect +from typing import Any, Callable, Dict, List, Optional, Tuple, Union + +import numpy as np +import PIL.Image +import torch +import torch.nn.functional as F +from transformers import CLIPTextModel, CLIPTextModelWithProjection, CLIPTokenizer, CLIPImageProcessor + +from diffusers.image_processor import PipelineImageInput, VaeImageProcessor +from diffusers.loaders import FromSingleFileMixin, LoraLoaderMixin, StableDiffusionXLLoraLoaderMixin, TextualInversionLoaderMixin +from diffusers.models import AutoencoderKL, UNet2DConditionModel +from diffusers.models.attention_processor import ( + AttnProcessor2_0, + LoRAAttnProcessor2_0, + LoRAXFormersAttnProcessor, + XFormersAttnProcessor, +) +from diffusers.models.lora import adjust_lora_scale_text_encoder +from diffusers.pipelines.pipeline_utils import DiffusionPipeline +from diffusers.pipelines.stable_diffusion_xl.pipeline_output import StableDiffusionXLPipelineOutput +from diffusers.schedulers import KarrasDiffusionSchedulers +from diffusers.utils import ( + USE_PEFT_BACKEND, + logging, + scale_lora_layers, + unscale_lora_layers, +) +from diffusers.utils.import_utils import is_invisible_watermark_available +from diffusers.utils.torch_utils import is_compiled_module, is_torch_version, randn_tensor +from diffusers.pipelines.stable_diffusion.safety_checker import StableDiffusionSafetyChecker +from modules.control.units.xs_model import ControlNetXSModel + + +if is_invisible_watermark_available(): + from diffusers.pipelines.stable_diffusion_xl.watermark import StableDiffusionXLWatermarker + + +logger = logging.get_logger(__name__) # pylint: disable=invalid-name + + +class StableDiffusionXLControlNetXSPipeline( + DiffusionPipeline, TextualInversionLoaderMixin, StableDiffusionXLLoraLoaderMixin, FromSingleFileMixin +): + r""" + Pipeline for text-to-image generation using Stable Diffusion XL with ControlNet-XS guidance. + + This model inherits from [`DiffusionPipeline`]. Check the superclass documentation for the generic methods + implemented for all pipelines (downloading, saving, running on a particular device, etc.). + + The pipeline also inherits the following loading methods: + - [`~loaders.TextualInversionLoaderMixin.load_textual_inversion`] for loading textual inversion embeddings + - [`~loaders.StableDiffusionXLLoraLoaderMixin.load_lora_weights`] for loading LoRA weights + - [`~loaders.StableDiffusionXLLoraLoaderMixin.save_lora_weights`] for saving LoRA weights + - [`~loaders.FromSingleFileMixin.from_single_file`] for loading `.ckpt` files + + Args: + vae ([`AutoencoderKL`]): + Variational Auto-Encoder (VAE) model to encode and decode images to and from latent representations. + text_encoder ([`~transformers.CLIPTextModel`]): + Frozen text-encoder ([clip-vit-large-patch14](https://huggingface.co/openai/clip-vit-large-patch14)). + text_encoder_2 ([`~transformers.CLIPTextModelWithProjection`]): + Second frozen text-encoder + ([laion/CLIP-ViT-bigG-14-laion2B-39B-b160k](https://huggingface.co/laion/CLIP-ViT-bigG-14-laion2B-39B-b160k)). + tokenizer ([`~transformers.CLIPTokenizer`]): + A `CLIPTokenizer` to tokenize text. + tokenizer_2 ([`~transformers.CLIPTokenizer`]): + A `CLIPTokenizer` to tokenize text. + unet ([`UNet2DConditionModel`]): + A `UNet2DConditionModel` to denoise the encoded image latents. + controlnet ([`ControlNetXSModel`]: + Provides additional conditioning to the `unet` during the denoising process. + scheduler ([`SchedulerMixin`]): + A scheduler to be used in combination with `unet` to denoise the encoded image latents. Can be one of + [`DDIMScheduler`], [`LMSDiscreteScheduler`], or [`PNDMScheduler`]. + force_zeros_for_empty_prompt (`bool`, *optional*, defaults to `"True"`): + Whether the negative prompt embeddings should always be set to 0. Also see the config of + `stabilityai/stable-diffusion-xl-base-1-0`. + add_watermarker (`bool`, *optional*): + Whether to use the [invisible_watermark](https://github.com/ShieldMnt/invisible-watermark/) library to + watermark output images. If not defined, it defaults to `True` if the package is installed; otherwise no + watermarker is used. + """ + + # leave controlnet out on purpose because it iterates with unet + model_cpu_offload_seq = "text_encoder->text_encoder_2->unet->vae->controlnet" + _optional_components = ["tokenizer", "tokenizer_2", "text_encoder", "text_encoder_2"] + + def __init__( + self, + vae: AutoencoderKL, + text_encoder: CLIPTextModel, + text_encoder_2: CLIPTextModelWithProjection, + tokenizer: CLIPTokenizer, + tokenizer_2: CLIPTokenizer, + unet: UNet2DConditionModel, + controlnet: ControlNetXSModel, + scheduler: KarrasDiffusionSchedulers, + force_zeros_for_empty_prompt: bool = True, + add_watermarker: Optional[bool] = None, + ): + super().__init__() + + vae_compatible, cnxs_condition_downsample_factor, vae_downsample_factor = controlnet._check_if_vae_compatible( + vae + ) + if not vae_compatible: + raise ValueError( + f"The downsampling factors of the VAE ({vae_downsample_factor}) and the conditioning part of ControlNetXS model {cnxs_condition_downsample_factor} need to be equal. Consider building the ControlNetXS model with different `conditioning_block_sizes`." + ) + + self.register_modules( + vae=vae, + text_encoder=text_encoder, + text_encoder_2=text_encoder_2, + tokenizer=tokenizer, + tokenizer_2=tokenizer_2, + unet=unet, + controlnet=controlnet, + scheduler=scheduler, + ) + self.vae_scale_factor = 2 ** (len(self.vae.config.block_out_channels) - 1) + self.image_processor = VaeImageProcessor(vae_scale_factor=self.vae_scale_factor, do_convert_rgb=True) + self.control_image_processor = VaeImageProcessor( + vae_scale_factor=self.vae_scale_factor, do_convert_rgb=True, do_normalize=False + ) + add_watermarker = add_watermarker if add_watermarker is not None else is_invisible_watermark_available() + + if add_watermarker: + self.watermark = StableDiffusionXLWatermarker() + else: + self.watermark = None + + self.register_to_config(force_zeros_for_empty_prompt=force_zeros_for_empty_prompt) + + # Copied from diffusers.pipelines.stable_diffusion.pipeline_stable_diffusion.StableDiffusionPipeline.enable_vae_slicing + def enable_vae_slicing(self): + r""" + Enable sliced VAE decoding. When this option is enabled, the VAE will split the input tensor in slices to + compute decoding in several steps. This is useful to save some memory and allow larger batch sizes. + """ + self.vae.enable_slicing() + + # Copied from diffusers.pipelines.stable_diffusion.pipeline_stable_diffusion.StableDiffusionPipeline.disable_vae_slicing + def disable_vae_slicing(self): + r""" + Disable sliced VAE decoding. If `enable_vae_slicing` was previously enabled, this method will go back to + computing decoding in one step. + """ + self.vae.disable_slicing() + + # Copied from diffusers.pipelines.stable_diffusion.pipeline_stable_diffusion.StableDiffusionPipeline.enable_vae_tiling + def enable_vae_tiling(self): + r""" + Enable tiled VAE decoding. When this option is enabled, the VAE will split the input tensor into tiles to + compute decoding and encoding in several steps. This is useful for saving a large amount of memory and to allow + processing larger images. + """ + self.vae.enable_tiling() + + # Copied from diffusers.pipelines.stable_diffusion.pipeline_stable_diffusion.StableDiffusionPipeline.disable_vae_tiling + def disable_vae_tiling(self): + r""" + Disable tiled VAE decoding. If `enable_vae_tiling` was previously enabled, this method will go back to + computing decoding in one step. + """ + self.vae.disable_tiling() + + # Copied from diffusers.pipelines.stable_diffusion_xl.pipeline_stable_diffusion_xl.StableDiffusionXLPipeline.encode_prompt + def encode_prompt( + self, + prompt: str, + prompt_2: Optional[str] = None, + device: Optional[torch.device] = None, + num_images_per_prompt: int = 1, + do_classifier_free_guidance: bool = True, + negative_prompt: Optional[str] = None, + negative_prompt_2: Optional[str] = None, + prompt_embeds: Optional[torch.FloatTensor] = None, + negative_prompt_embeds: Optional[torch.FloatTensor] = None, + pooled_prompt_embeds: Optional[torch.FloatTensor] = None, + negative_pooled_prompt_embeds: Optional[torch.FloatTensor] = None, + lora_scale: Optional[float] = None, + clip_skip: Optional[int] = None, + ): + r""" + Encodes the prompt into text encoder hidden states. + + Args: + prompt (`str` or `List[str]`, *optional*): + prompt to be encoded + prompt_2 (`str` or `List[str]`, *optional*): + The prompt or prompts to be sent to the `tokenizer_2` and `text_encoder_2`. If not defined, `prompt` is + used in both text-encoders + device: (`torch.device`): + torch device + num_images_per_prompt (`int`): + number of images that should be generated per prompt + do_classifier_free_guidance (`bool`): + whether to use classifier free guidance or not + negative_prompt (`str` or `List[str]`, *optional*): + The prompt or prompts not to guide the image generation. If not defined, one has to pass + `negative_prompt_embeds` instead. Ignored when not using guidance (i.e., ignored if `guidance_scale` is + less than `1`). + negative_prompt_2 (`str` or `List[str]`, *optional*): + The prompt or prompts not to guide the image generation to be sent to `tokenizer_2` and + `text_encoder_2`. If not defined, `negative_prompt` is used in both text-encoders + prompt_embeds (`torch.FloatTensor`, *optional*): + Pre-generated text embeddings. Can be used to easily tweak text inputs, *e.g.* prompt weighting. If not + provided, text embeddings will be generated from `prompt` input argument. + negative_prompt_embeds (`torch.FloatTensor`, *optional*): + Pre-generated negative text embeddings. Can be used to easily tweak text inputs, *e.g.* prompt + weighting. If not provided, negative_prompt_embeds will be generated from `negative_prompt` input + argument. + pooled_prompt_embeds (`torch.FloatTensor`, *optional*): + Pre-generated pooled text embeddings. Can be used to easily tweak text inputs, *e.g.* prompt weighting. + If not provided, pooled text embeddings will be generated from `prompt` input argument. + negative_pooled_prompt_embeds (`torch.FloatTensor`, *optional*): + Pre-generated negative pooled text embeddings. Can be used to easily tweak text inputs, *e.g.* prompt + weighting. If not provided, pooled negative_prompt_embeds will be generated from `negative_prompt` + input argument. + lora_scale (`float`, *optional*): + A lora scale that will be applied to all LoRA layers of the text encoder if LoRA layers are loaded. + clip_skip (`int`, *optional*): + Number of layers to be skipped from CLIP while computing the prompt embeddings. A value of 1 means that + the output of the pre-final layer will be used for computing the prompt embeddings. + """ + device = device or self._execution_device + + # set lora scale so that monkey patched LoRA + # function of text encoder can correctly access it + if lora_scale is not None and isinstance(self, StableDiffusionXLLoraLoaderMixin): + self._lora_scale = lora_scale + + # dynamically adjust the LoRA scale + if self.text_encoder is not None: + if not USE_PEFT_BACKEND: + adjust_lora_scale_text_encoder(self.text_encoder, lora_scale) + else: + scale_lora_layers(self.text_encoder, lora_scale) + + if self.text_encoder_2 is not None: + if not USE_PEFT_BACKEND: + adjust_lora_scale_text_encoder(self.text_encoder_2, lora_scale) + else: + scale_lora_layers(self.text_encoder_2, lora_scale) + + prompt = [prompt] if isinstance(prompt, str) else prompt + + if prompt is not None: + batch_size = len(prompt) + else: + batch_size = prompt_embeds.shape[0] + + # Define tokenizers and text encoders + tokenizers = [self.tokenizer, self.tokenizer_2] if self.tokenizer is not None else [self.tokenizer_2] + text_encoders = ( + [self.text_encoder, self.text_encoder_2] if self.text_encoder is not None else [self.text_encoder_2] + ) + + if prompt_embeds is None: + prompt_2 = prompt_2 or prompt + prompt_2 = [prompt_2] if isinstance(prompt_2, str) else prompt_2 + + # textual inversion: procecss multi-vector tokens if necessary + prompt_embeds_list = [] + prompts = [prompt, prompt_2] + for prompt, tokenizer, text_encoder in zip(prompts, tokenizers, text_encoders): + if isinstance(self, TextualInversionLoaderMixin): + prompt = self.maybe_convert_prompt(prompt, tokenizer) + + text_inputs = tokenizer( + prompt, + padding="max_length", + max_length=tokenizer.model_max_length, + truncation=True, + return_tensors="pt", + ) + + text_input_ids = text_inputs.input_ids + untruncated_ids = tokenizer(prompt, padding="longest", return_tensors="pt").input_ids + + if untruncated_ids.shape[-1] >= text_input_ids.shape[-1] and not torch.equal( + text_input_ids, untruncated_ids + ): + removed_text = tokenizer.batch_decode(untruncated_ids[:, tokenizer.model_max_length - 1 : -1]) + logger.warning( + "The following part of your input was truncated because CLIP can only handle sequences up to" + f" {tokenizer.model_max_length} tokens: {removed_text}" + ) + + prompt_embeds = text_encoder(text_input_ids.to(device), output_hidden_states=True) + + # We are only ALWAYS interested in the pooled output of the final text encoder + pooled_prompt_embeds = prompt_embeds[0] + if clip_skip is None: + prompt_embeds = prompt_embeds.hidden_states[-2] + else: + # "2" because SDXL always indexes from the penultimate layer. + prompt_embeds = prompt_embeds.hidden_states[-(clip_skip + 2)] + + prompt_embeds_list.append(prompt_embeds) + + prompt_embeds = torch.concat(prompt_embeds_list, dim=-1) + + # get unconditional embeddings for classifier free guidance + zero_out_negative_prompt = negative_prompt is None and self.config.force_zeros_for_empty_prompt + if do_classifier_free_guidance and negative_prompt_embeds is None and zero_out_negative_prompt: + negative_prompt_embeds = torch.zeros_like(prompt_embeds) + negative_pooled_prompt_embeds = torch.zeros_like(pooled_prompt_embeds) + elif do_classifier_free_guidance and negative_prompt_embeds is None: + negative_prompt = negative_prompt or "" + negative_prompt_2 = negative_prompt_2 or negative_prompt + + # normalize str to list + negative_prompt = batch_size * [negative_prompt] if isinstance(negative_prompt, str) else negative_prompt + negative_prompt_2 = ( + batch_size * [negative_prompt_2] if isinstance(negative_prompt_2, str) else negative_prompt_2 + ) + + uncond_tokens: List[str] + if prompt is not None and type(prompt) is not type(negative_prompt): + raise TypeError( + f"`negative_prompt` should be the same type to `prompt`, but got {type(negative_prompt)} !=" + f" {type(prompt)}." + ) + elif batch_size != len(negative_prompt): + raise ValueError( + f"`negative_prompt`: {negative_prompt} has batch size {len(negative_prompt)}, but `prompt`:" + f" {prompt} has batch size {batch_size}. Please make sure that passed `negative_prompt` matches" + " the batch size of `prompt`." + ) + else: + uncond_tokens = [negative_prompt, negative_prompt_2] + + negative_prompt_embeds_list = [] + for negative_prompt, tokenizer, text_encoder in zip(uncond_tokens, tokenizers, text_encoders): + if isinstance(self, TextualInversionLoaderMixin): + negative_prompt = self.maybe_convert_prompt(negative_prompt, tokenizer) + + max_length = prompt_embeds.shape[1] + uncond_input = tokenizer( + negative_prompt, + padding="max_length", + max_length=max_length, + truncation=True, + return_tensors="pt", + ) + + negative_prompt_embeds = text_encoder( + uncond_input.input_ids.to(device), + output_hidden_states=True, + ) + # We are only ALWAYS interested in the pooled output of the final text encoder + negative_pooled_prompt_embeds = negative_prompt_embeds[0] + negative_prompt_embeds = negative_prompt_embeds.hidden_states[-2] + + negative_prompt_embeds_list.append(negative_prompt_embeds) + + negative_prompt_embeds = torch.concat(negative_prompt_embeds_list, dim=-1) + + if self.text_encoder_2 is not None: + prompt_embeds = prompt_embeds.to(dtype=self.text_encoder_2.dtype, device=device) + else: + prompt_embeds = prompt_embeds.to(dtype=self.unet.dtype, device=device) + + bs_embed, seq_len, _ = prompt_embeds.shape + # duplicate text embeddings for each generation per prompt, using mps friendly method + prompt_embeds = prompt_embeds.repeat(1, num_images_per_prompt, 1) + prompt_embeds = prompt_embeds.view(bs_embed * num_images_per_prompt, seq_len, -1) + + if do_classifier_free_guidance: + # duplicate unconditional embeddings for each generation per prompt, using mps friendly method + seq_len = negative_prompt_embeds.shape[1] + + if self.text_encoder_2 is not None: + negative_prompt_embeds = negative_prompt_embeds.to(dtype=self.text_encoder_2.dtype, device=device) + else: + negative_prompt_embeds = negative_prompt_embeds.to(dtype=self.unet.dtype, device=device) + + negative_prompt_embeds = negative_prompt_embeds.repeat(1, num_images_per_prompt, 1) + negative_prompt_embeds = negative_prompt_embeds.view(batch_size * num_images_per_prompt, seq_len, -1) + + pooled_prompt_embeds = pooled_prompt_embeds.repeat(1, num_images_per_prompt).view( + bs_embed * num_images_per_prompt, -1 + ) + if do_classifier_free_guidance: + negative_pooled_prompt_embeds = negative_pooled_prompt_embeds.repeat(1, num_images_per_prompt).view( + bs_embed * num_images_per_prompt, -1 + ) + + if self.text_encoder is not None: + if isinstance(self, StableDiffusionXLLoraLoaderMixin) and USE_PEFT_BACKEND: + # Retrieve the original scale by scaling back the LoRA layers + unscale_lora_layers(self.text_encoder, lora_scale) + + if self.text_encoder_2 is not None: + if isinstance(self, StableDiffusionXLLoraLoaderMixin) and USE_PEFT_BACKEND: + # Retrieve the original scale by scaling back the LoRA layers + unscale_lora_layers(self.text_encoder_2, lora_scale) + + return prompt_embeds, negative_prompt_embeds, pooled_prompt_embeds, negative_pooled_prompt_embeds + + # Copied from diffusers.pipelines.stable_diffusion.pipeline_stable_diffusion.StableDiffusionPipeline.prepare_extra_step_kwargs + def prepare_extra_step_kwargs(self, generator, eta): + # prepare extra kwargs for the scheduler step, since not all schedulers have the same signature + # eta (η) is only used with the DDIMScheduler, it will be ignored for other schedulers. + # eta corresponds to η in DDIM paper: https://arxiv.org/abs/2010.02502 + # and should be between [0, 1] + + accepts_eta = "eta" in set(inspect.signature(self.scheduler.step).parameters.keys()) + extra_step_kwargs = {} + if accepts_eta: + extra_step_kwargs["eta"] = eta + + # check if the scheduler accepts generator + accepts_generator = "generator" in set(inspect.signature(self.scheduler.step).parameters.keys()) + if accepts_generator: + extra_step_kwargs["generator"] = generator + return extra_step_kwargs + + def check_inputs( + self, + prompt, + prompt_2, + image, + callback_steps, + negative_prompt=None, + negative_prompt_2=None, + prompt_embeds=None, + negative_prompt_embeds=None, + pooled_prompt_embeds=None, + negative_pooled_prompt_embeds=None, + controlnet_conditioning_scale=1.0, + control_guidance_start=0.0, + control_guidance_end=1.0, + ): + if (callback_steps is None) or ( + callback_steps is not None and (not isinstance(callback_steps, int) or callback_steps <= 0) + ): + raise ValueError( + f"`callback_steps` has to be a positive integer but is {callback_steps} of type" + f" {type(callback_steps)}." + ) + + if prompt is not None and prompt_embeds is not None: + raise ValueError( + f"Cannot forward both `prompt`: {prompt} and `prompt_embeds`: {prompt_embeds}. Please make sure to" + " only forward one of the two." + ) + elif prompt_2 is not None and prompt_embeds is not None: + raise ValueError( + f"Cannot forward both `prompt_2`: {prompt_2} and `prompt_embeds`: {prompt_embeds}. Please make sure to" + " only forward one of the two." + ) + elif prompt is None and prompt_embeds is None: + raise ValueError( + "Provide either `prompt` or `prompt_embeds`. Cannot leave both `prompt` and `prompt_embeds` undefined." + ) + elif prompt is not None and (not isinstance(prompt, str) and not isinstance(prompt, list)): + raise ValueError(f"`prompt` has to be of type `str` or `list` but is {type(prompt)}") + elif prompt_2 is not None and (not isinstance(prompt_2, str) and not isinstance(prompt_2, list)): + raise ValueError(f"`prompt_2` has to be of type `str` or `list` but is {type(prompt_2)}") + + if negative_prompt is not None and negative_prompt_embeds is not None: + raise ValueError( + f"Cannot forward both `negative_prompt`: {negative_prompt} and `negative_prompt_embeds`:" + f" {negative_prompt_embeds}. Please make sure to only forward one of the two." + ) + elif negative_prompt_2 is not None and negative_prompt_embeds is not None: + raise ValueError( + f"Cannot forward both `negative_prompt_2`: {negative_prompt_2} and `negative_prompt_embeds`:" + f" {negative_prompt_embeds}. Please make sure to only forward one of the two." + ) + + if prompt_embeds is not None and negative_prompt_embeds is not None: + if prompt_embeds.shape != negative_prompt_embeds.shape: + raise ValueError( + "`prompt_embeds` and `negative_prompt_embeds` must have the same shape when passed directly, but" + f" got: `prompt_embeds` {prompt_embeds.shape} != `negative_prompt_embeds`" + f" {negative_prompt_embeds.shape}." + ) + + if prompt_embeds is not None and pooled_prompt_embeds is None: + raise ValueError( + "If `prompt_embeds` are provided, `pooled_prompt_embeds` also have to be passed. Make sure to generate `pooled_prompt_embeds` from the same text encoder that was used to generate `prompt_embeds`." + ) + + if negative_prompt_embeds is not None and negative_pooled_prompt_embeds is None: + raise ValueError( + "If `negative_prompt_embeds` are provided, `negative_pooled_prompt_embeds` also have to be passed. Make sure to generate `negative_pooled_prompt_embeds` from the same text encoder that was used to generate `negative_prompt_embeds`." + ) + + # Check `image` + is_compiled = hasattr(F, "scaled_dot_product_attention") and isinstance( + self.controlnet, torch._dynamo.eval_frame.OptimizedModule + ) + if ( + isinstance(self.controlnet, ControlNetXSModel) + or is_compiled + and isinstance(self.controlnet._orig_mod, ControlNetXSModel) + ): + self.check_image(image, prompt, prompt_embeds) + else: + assert False + + # Check `controlnet_conditioning_scale` + if ( + isinstance(self.controlnet, ControlNetXSModel) + or is_compiled + and isinstance(self.controlnet._orig_mod, ControlNetXSModel) + ): + if not isinstance(controlnet_conditioning_scale, float): + raise TypeError("For single controlnet: `controlnet_conditioning_scale` must be type `float`.") + else: + assert False + + start, end = control_guidance_start, control_guidance_end + if start >= end: + raise ValueError( + f"control guidance start: {start} cannot be larger or equal to control guidance end: {end}." + ) + if start < 0.0: + raise ValueError(f"control guidance start: {start} can't be smaller than 0.") + if end > 1.0: + raise ValueError(f"control guidance end: {end} can't be larger than 1.0.") + + # Copied from diffusers.pipelines.controlnet.pipeline_controlnet.StableDiffusionControlNetPipeline.check_image + def check_image(self, image, prompt, prompt_embeds): + image_is_pil = isinstance(image, PIL.Image.Image) + image_is_tensor = isinstance(image, torch.Tensor) + image_is_np = isinstance(image, np.ndarray) + image_is_pil_list = isinstance(image, list) and isinstance(image[0], PIL.Image.Image) + image_is_tensor_list = isinstance(image, list) and isinstance(image[0], torch.Tensor) + image_is_np_list = isinstance(image, list) and isinstance(image[0], np.ndarray) + + if ( + not image_is_pil + and not image_is_tensor + and not image_is_np + and not image_is_pil_list + and not image_is_tensor_list + and not image_is_np_list + ): + raise TypeError( + f"image must be passed and be one of PIL image, numpy array, torch tensor, list of PIL images, list of numpy arrays or list of torch tensors, but is {type(image)}" + ) + + if image_is_pil: + image_batch_size = 1 + else: + image_batch_size = len(image) + + if prompt is not None and isinstance(prompt, str): + prompt_batch_size = 1 + elif prompt is not None and isinstance(prompt, list): + prompt_batch_size = len(prompt) + elif prompt_embeds is not None: + prompt_batch_size = prompt_embeds.shape[0] + + if image_batch_size != 1 and image_batch_size != prompt_batch_size: + raise ValueError( + f"If image batch size is not 1, image batch size must be same as prompt batch size. image batch size: {image_batch_size}, prompt batch size: {prompt_batch_size}" + ) + + def prepare_image( + self, + image, + width, + height, + batch_size, + num_images_per_prompt, + device, + dtype, + do_classifier_free_guidance=False, + ): + image = self.control_image_processor.preprocess(image, height=height, width=width).to(dtype=torch.float32) + image_batch_size = image.shape[0] + + if image_batch_size == 1: + repeat_by = batch_size + else: + # image batch size is the same as prompt batch size + repeat_by = num_images_per_prompt + + image = image.repeat_interleave(repeat_by, dim=0) + + image = image.to(device=device, dtype=dtype) + + if do_classifier_free_guidance: + image = torch.cat([image] * 2) + + return image + + # Copied from diffusers.pipelines.stable_diffusion.pipeline_stable_diffusion.StableDiffusionPipeline.prepare_latents + def prepare_latents(self, batch_size, num_channels_latents, height, width, dtype, device, generator, latents=None): + shape = (batch_size, num_channels_latents, height // self.vae_scale_factor, width // self.vae_scale_factor) + if isinstance(generator, list) and len(generator) != batch_size: + raise ValueError( + f"You have passed a list of generators of length {len(generator)}, but requested an effective batch" + f" size of {batch_size}. Make sure the batch size matches the length of the generators." + ) + + if latents is None: + latents = randn_tensor(shape, generator=generator, device=device, dtype=dtype) + else: + latents = latents.to(device) + + # scale the initial noise by the standard deviation required by the scheduler + latents = latents * self.scheduler.init_noise_sigma + return latents + + # Copied from diffusers.pipelines.stable_diffusion_xl.pipeline_stable_diffusion_xl.StableDiffusionXLPipeline._get_add_time_ids + def _get_add_time_ids( + self, original_size, crops_coords_top_left, target_size, dtype, text_encoder_projection_dim=None + ): + add_time_ids = list(original_size + crops_coords_top_left + target_size) + + passed_add_embed_dim = ( + self.unet.config.addition_time_embed_dim * len(add_time_ids) + text_encoder_projection_dim + ) + expected_add_embed_dim = self.unet.add_embedding.linear_1.in_features + + if expected_add_embed_dim != passed_add_embed_dim: + raise ValueError( + f"Model expects an added time embedding vector of length {expected_add_embed_dim}, but a vector of {passed_add_embed_dim} was created. The model has an incorrect config. Please check `unet.config.time_embedding_type` and `text_encoder_2.config.projection_dim`." + ) + + add_time_ids = torch.tensor([add_time_ids], dtype=dtype) + return add_time_ids + + # Copied from diffusers.pipelines.stable_diffusion.pipeline_stable_diffusion_upscale.StableDiffusionUpscalePipeline.upcast_vae + def upcast_vae(self): + dtype = self.vae.dtype + self.vae.to(dtype=torch.float32) + use_torch_2_0_or_xformers = isinstance( + self.vae.decoder.mid_block.attentions[0].processor, + ( + AttnProcessor2_0, + XFormersAttnProcessor, + LoRAXFormersAttnProcessor, + LoRAAttnProcessor2_0, + ), + ) + # if xformers or torch_2_0 is used attention block does not need + # to be in float32 which can save lots of memory + if use_torch_2_0_or_xformers: + self.vae.post_quant_conv.to(dtype) + self.vae.decoder.conv_in.to(dtype) + self.vae.decoder.mid_block.to(dtype) + + # Copied from diffusers.pipelines.stable_diffusion.pipeline_stable_diffusion.StableDiffusionPipeline.enable_freeu + def enable_freeu(self, s1: float, s2: float, b1: float, b2: float): + r"""Enables the FreeU mechanism as in https://arxiv.org/abs/2309.11497. + + The suffixes after the scaling factors represent the stages where they are being applied. + + Please refer to the [official repository](https://github.com/ChenyangSi/FreeU) for combinations of the values + that are known to work well for different pipelines such as Stable Diffusion v1, v2, and Stable Diffusion XL. + + Args: + s1 (`float`): + Scaling factor for stage 1 to attenuate the contributions of the skip features. This is done to + mitigate "oversmoothing effect" in the enhanced denoising process. + s2 (`float`): + Scaling factor for stage 2 to attenuate the contributions of the skip features. This is done to + mitigate "oversmoothing effect" in the enhanced denoising process. + b1 (`float`): Scaling factor for stage 1 to amplify the contributions of backbone features. + b2 (`float`): Scaling factor for stage 2 to amplify the contributions of backbone features. + """ + if not hasattr(self, "unet"): + raise ValueError("The pipeline must have `unet` for using FreeU.") + self.unet.enable_freeu(s1=s1, s2=s2, b1=b1, b2=b2) + + # Copied from diffusers.pipelines.stable_diffusion.pipeline_stable_diffusion.StableDiffusionPipeline.disable_freeu + def disable_freeu(self): + """Disables the FreeU mechanism if enabled.""" + self.unet.disable_freeu() + + @torch.no_grad() + def __call__( + self, + prompt: Union[str, List[str]] = None, + prompt_2: Optional[Union[str, List[str]]] = None, + image: PipelineImageInput = None, + height: Optional[int] = None, + width: Optional[int] = None, + num_inference_steps: int = 50, + guidance_scale: float = 5.0, + negative_prompt: Optional[Union[str, List[str]]] = None, + negative_prompt_2: Optional[Union[str, List[str]]] = None, + num_images_per_prompt: Optional[int] = 1, + eta: float = 0.0, + generator: Optional[Union[torch.Generator, List[torch.Generator]]] = None, + latents: Optional[torch.FloatTensor] = None, + prompt_embeds: Optional[torch.FloatTensor] = None, + negative_prompt_embeds: Optional[torch.FloatTensor] = None, + pooled_prompt_embeds: Optional[torch.FloatTensor] = None, + negative_pooled_prompt_embeds: Optional[torch.FloatTensor] = None, + output_type: Optional[str] = "pil", + return_dict: bool = True, + callback: Optional[Callable[[int, int, torch.FloatTensor], None]] = None, + callback_steps: int = 1, + cross_attention_kwargs: Optional[Dict[str, Any]] = None, + controlnet_conditioning_scale: Union[float, List[float]] = 1.0, + control_guidance_start: float = 0.0, + control_guidance_end: float = 1.0, + original_size: Tuple[int, int] = None, + crops_coords_top_left: Tuple[int, int] = (0, 0), + target_size: Tuple[int, int] = None, + negative_original_size: Optional[Tuple[int, int]] = None, + negative_crops_coords_top_left: Tuple[int, int] = (0, 0), + negative_target_size: Optional[Tuple[int, int]] = None, + clip_skip: Optional[int] = None, + ): + r""" + The call function to the pipeline for generation. + + Args: + prompt (`str` or `List[str]`, *optional*): + The prompt or prompts to guide image generation. If not defined, you need to pass `prompt_embeds`. + prompt_2 (`str` or `List[str]`, *optional*): + The prompt or prompts to be sent to `tokenizer_2` and `text_encoder_2`. If not defined, `prompt` is + used in both text-encoders. + image (`torch.FloatTensor`, `PIL.Image.Image`, `np.ndarray`, `List[torch.FloatTensor]`, `List[PIL.Image.Image]`, `List[np.ndarray]`, + `List[List[torch.FloatTensor]]`, `List[List[np.ndarray]]` or `List[List[PIL.Image.Image]]`): + The ControlNet input condition to provide guidance to the `unet` for generation. If the type is + specified as `torch.FloatTensor`, it is passed to ControlNet as is. `PIL.Image.Image` can also be + accepted as an image. The dimensions of the output image defaults to `image`'s dimensions. If height + and/or width are passed, `image` is resized accordingly. If multiple ControlNets are specified in + `init`, images must be passed as a list such that each element of the list can be correctly batched for + input to a single ControlNet. + height (`int`, *optional*, defaults to `self.unet.config.sample_size * self.vae_scale_factor`): + The height in pixels of the generated image. Anything below 512 pixels won't work well for + [stabilityai/stable-diffusion-xl-base-1.0](https://huggingface.co/stabilityai/stable-diffusion-xl-base-1.0) + and checkpoints that are not specifically fine-tuned on low resolutions. + width (`int`, *optional*, defaults to `self.unet.config.sample_size * self.vae_scale_factor`): + The width in pixels of the generated image. Anything below 512 pixels won't work well for + [stabilityai/stable-diffusion-xl-base-1.0](https://huggingface.co/stabilityai/stable-diffusion-xl-base-1.0) + and checkpoints that are not specifically fine-tuned on low resolutions. + num_inference_steps (`int`, *optional*, defaults to 50): + The number of denoising steps. More denoising steps usually lead to a higher quality image at the + expense of slower inference. + guidance_scale (`float`, *optional*, defaults to 5.0): + A higher guidance scale value encourages the model to generate images closely linked to the text + `prompt` at the expense of lower image quality. Guidance scale is enabled when `guidance_scale > 1`. + negative_prompt (`str` or `List[str]`, *optional*): + The prompt or prompts to guide what to not include in image generation. If not defined, you need to + pass `negative_prompt_embeds` instead. Ignored when not using guidance (`guidance_scale < 1`). + negative_prompt_2 (`str` or `List[str]`, *optional*): + The prompt or prompts to guide what to not include in image generation. This is sent to `tokenizer_2` + and `text_encoder_2`. If not defined, `negative_prompt` is used in both text-encoders. + num_images_per_prompt (`int`, *optional*, defaults to 1): + The number of images to generate per prompt. + eta (`float`, *optional*, defaults to 0.0): + Corresponds to parameter eta (η) from the [DDIM](https://arxiv.org/abs/2010.02502) paper. Only applies + to the [`~schedulers.DDIMScheduler`], and is ignored in other schedulers. + generator (`torch.Generator` or `List[torch.Generator]`, *optional*): + A [`torch.Generator`](https://pytorch.org/docs/stable/generated/torch.Generator.html) to make + generation deterministic. + latents (`torch.FloatTensor`, *optional*): + Pre-generated noisy latents sampled from a Gaussian distribution, to be used as inputs for image + generation. Can be used to tweak the same generation with different prompts. If not provided, a latents + tensor is generated by sampling using the supplied random `generator`. + prompt_embeds (`torch.FloatTensor`, *optional*): + Pre-generated text embeddings. Can be used to easily tweak text inputs (prompt weighting). If not + provided, text embeddings are generated from the `prompt` input argument. + negative_prompt_embeds (`torch.FloatTensor`, *optional*): + Pre-generated negative text embeddings. Can be used to easily tweak text inputs (prompt weighting). If + not provided, `negative_prompt_embeds` are generated from the `negative_prompt` input argument. + pooled_prompt_embeds (`torch.FloatTensor`, *optional*): + Pre-generated pooled text embeddings. Can be used to easily tweak text inputs (prompt weighting). If + not provided, pooled text embeddings are generated from `prompt` input argument. + negative_pooled_prompt_embeds (`torch.FloatTensor`, *optional*): + Pre-generated negative pooled text embeddings. Can be used to easily tweak text inputs (prompt + weighting). If not provided, pooled `negative_prompt_embeds` are generated from `negative_prompt` input + argument. + output_type (`str`, *optional*, defaults to `"pil"`): + The output format of the generated image. Choose between `PIL.Image` or `np.array`. + return_dict (`bool`, *optional*, defaults to `True`): + Whether or not to return a [`~pipelines.stable_diffusion.StableDiffusionPipelineOutput`] instead of a + plain tuple. + callback (`Callable`, *optional*): + A function that calls every `callback_steps` steps during inference. The function is called with the + following arguments: `callback(step: int, timestep: int, latents: torch.FloatTensor)`. + callback_steps (`int`, *optional*, defaults to 1): + The frequency at which the `callback` function is called. If not specified, the callback is called at + every step. + cross_attention_kwargs (`dict`, *optional*): + A kwargs dictionary that if specified is passed along to the [`AttentionProcessor`] as defined in + [`self.processor`](https://github.com/huggingface/diffusers/blob/main/src/diffusers/models/attention_processor.py). + controlnet_conditioning_scale (`float` or `List[float]`, *optional*, defaults to 1.0): + The outputs of the ControlNet are multiplied by `controlnet_conditioning_scale` before they are added + to the residual in the original `unet`. + control_guidance_start (`float`, *optional*, defaults to 0.0): + The percentage of total steps at which the ControlNet starts applying. + control_guidance_end (`float`, *optional*, defaults to 1.0): + The percentage of total steps at which the ControlNet stops applying. + original_size (`Tuple[int]`, *optional*, defaults to (1024, 1024)): + If `original_size` is not the same as `target_size` the image will appear to be down- or upsampled. + `original_size` defaults to `(width, height)` if not specified. Part of SDXL's micro-conditioning as + explained in section 2.2 of + [https://huggingface.co/papers/2307.01952](https://huggingface.co/papers/2307.01952). + crops_coords_top_left (`Tuple[int]`, *optional*, defaults to (0, 0)): + `crops_coords_top_left` can be used to generate an image that appears to be "cropped" from the position + `crops_coords_top_left` downwards. Favorable, well-centered images are usually achieved by setting + `crops_coords_top_left` to (0, 0). Part of SDXL's micro-conditioning as explained in section 2.2 of + [https://huggingface.co/papers/2307.01952](https://huggingface.co/papers/2307.01952). + target_size (`Tuple[int]`, *optional*, defaults to (1024, 1024)): + For most cases, `target_size` should be set to the desired height and width of the generated image. If + not specified it will default to `(width, height)`. Part of SDXL's micro-conditioning as explained in + section 2.2 of [https://huggingface.co/papers/2307.01952](https://huggingface.co/papers/2307.01952). + negative_original_size (`Tuple[int]`, *optional*, defaults to (1024, 1024)): + To negatively condition the generation process based on a specific image resolution. Part of SDXL's + micro-conditioning as explained in section 2.2 of + [https://huggingface.co/papers/2307.01952](https://huggingface.co/papers/2307.01952). For more + information, refer to this issue thread: https://github.com/huggingface/diffusers/issues/4208. + negative_crops_coords_top_left (`Tuple[int]`, *optional*, defaults to (0, 0)): + To negatively condition the generation process based on a specific crop coordinates. Part of SDXL's + micro-conditioning as explained in section 2.2 of + [https://huggingface.co/papers/2307.01952](https://huggingface.co/papers/2307.01952). For more + information, refer to this issue thread: https://github.com/huggingface/diffusers/issues/4208. + negative_target_size (`Tuple[int]`, *optional*, defaults to (1024, 1024)): + To negatively condition the generation process based on a target image resolution. It should be as same + as the `target_size` for most cases. Part of SDXL's micro-conditioning as explained in section 2.2 of + [https://huggingface.co/papers/2307.01952](https://huggingface.co/papers/2307.01952). For more + information, refer to this issue thread: https://github.com/huggingface/diffusers/issues/4208. + clip_skip (`int`, *optional*): + Number of layers to be skipped from CLIP while computing the prompt embeddings. A value of 1 means that + the output of the pre-final layer will be used for computing the prompt embeddings. + + Examples: + + Returns: + [`~pipelines.stable_diffusion.StableDiffusionXLPipelineOutput`] or `tuple`: + If `return_dict` is `True`, [`~pipelines.stable_diffusion.StableDiffusionXLPipelineOutput`] is + returned, otherwise a `tuple` is returned containing the output images. + """ + controlnet = self.controlnet._orig_mod if is_compiled_module(self.controlnet) else self.controlnet + + # 1. Check inputs. Raise error if not correct + self.check_inputs( + prompt, + prompt_2, + image, + callback_steps, + negative_prompt, + negative_prompt_2, + prompt_embeds, + negative_prompt_embeds, + pooled_prompt_embeds, + negative_pooled_prompt_embeds, + controlnet_conditioning_scale, + control_guidance_start, + control_guidance_end, + ) + + # 2. Define call parameters + if prompt is not None and isinstance(prompt, str): + batch_size = 1 + elif prompt is not None and isinstance(prompt, list): + batch_size = len(prompt) + else: + batch_size = prompt_embeds.shape[0] + + device = self._execution_device + # here `guidance_scale` is defined analog to the guidance weight `w` of equation (2) + # of the Imagen paper: https://arxiv.org/pdf/2205.11487.pdf . `guidance_scale = 1` + # corresponds to doing no classifier free guidance. + do_classifier_free_guidance = guidance_scale > 1.0 + + # 3. Encode input prompt + text_encoder_lora_scale = ( + cross_attention_kwargs.get("scale", None) if cross_attention_kwargs is not None else None + ) + ( + prompt_embeds, + negative_prompt_embeds, + pooled_prompt_embeds, + negative_pooled_prompt_embeds, + ) = self.encode_prompt( + prompt, + prompt_2, + device, + num_images_per_prompt, + do_classifier_free_guidance, + negative_prompt, + negative_prompt_2, + prompt_embeds=prompt_embeds, + negative_prompt_embeds=negative_prompt_embeds, + pooled_prompt_embeds=pooled_prompt_embeds, + negative_pooled_prompt_embeds=negative_pooled_prompt_embeds, + lora_scale=text_encoder_lora_scale, + clip_skip=clip_skip, + ) + + # 4. Prepare image + if isinstance(controlnet, ControlNetXSModel): + image = self.prepare_image( + image=image, + width=width, + height=height, + batch_size=batch_size * num_images_per_prompt, + num_images_per_prompt=num_images_per_prompt, + device=device, + dtype=controlnet.dtype, + do_classifier_free_guidance=do_classifier_free_guidance, + ) + height, width = image.shape[-2:] + else: + assert False + + # 5. Prepare timesteps + self.scheduler.set_timesteps(num_inference_steps, device=device) + timesteps = self.scheduler.timesteps + + # 6. Prepare latent variables + num_channels_latents = self.unet.config.in_channels + latents = self.prepare_latents( + batch_size * num_images_per_prompt, + num_channels_latents, + height, + width, + prompt_embeds.dtype, + device, + generator, + latents, + ) + + # 7. Prepare extra step kwargs. TODO: Logic should ideally just be moved out of the pipeline + extra_step_kwargs = self.prepare_extra_step_kwargs(generator, eta) + + # 7.1 Prepare added time ids & embeddings + if isinstance(image, list): + original_size = original_size or image[0].shape[-2:] + else: + original_size = original_size or image.shape[-2:] + target_size = target_size or (height, width) + + add_text_embeds = pooled_prompt_embeds + if self.text_encoder_2 is None: + text_encoder_projection_dim = int(pooled_prompt_embeds.shape[-1]) + else: + text_encoder_projection_dim = self.text_encoder_2.config.projection_dim + + add_time_ids = self._get_add_time_ids( + original_size, + crops_coords_top_left, + target_size, + dtype=prompt_embeds.dtype, + text_encoder_projection_dim=text_encoder_projection_dim, + ) + + if negative_original_size is not None and negative_target_size is not None: + negative_add_time_ids = self._get_add_time_ids( + negative_original_size, + negative_crops_coords_top_left, + negative_target_size, + dtype=prompt_embeds.dtype, + text_encoder_projection_dim=text_encoder_projection_dim, + ) + else: + negative_add_time_ids = add_time_ids + + if do_classifier_free_guidance: + prompt_embeds = torch.cat([negative_prompt_embeds, prompt_embeds], dim=0) + add_text_embeds = torch.cat([negative_pooled_prompt_embeds, add_text_embeds], dim=0) + add_time_ids = torch.cat([negative_add_time_ids, add_time_ids], dim=0) + + prompt_embeds = prompt_embeds.to(device) + add_text_embeds = add_text_embeds.to(device) + add_time_ids = add_time_ids.to(device).repeat(batch_size * num_images_per_prompt, 1) + + # 8. Denoising loop + num_warmup_steps = len(timesteps) - num_inference_steps * self.scheduler.order + is_unet_compiled = is_compiled_module(self.unet) + is_controlnet_compiled = is_compiled_module(self.controlnet) + is_torch_higher_equal_2_1 = is_torch_version(">=", "2.1") + with self.progress_bar(total=num_inference_steps) as progress_bar: + for i, t in enumerate(timesteps): + # Relevant thread: + # https://dev-discuss.pytorch.org/t/cudagraphs-in-pytorch-2-0/1428 + if (is_unet_compiled and is_controlnet_compiled) and is_torch_higher_equal_2_1: + torch._inductor.cudagraph_mark_step_begin() + # expand the latents if we are doing classifier free guidance + latent_model_input = torch.cat([latents] * 2) if do_classifier_free_guidance else latents + latent_model_input = self.scheduler.scale_model_input(latent_model_input, t) + + added_cond_kwargs = {"text_embeds": add_text_embeds, "time_ids": add_time_ids} + + # predict the noise residual + dont_control = ( + i / len(timesteps) < control_guidance_start or (i + 1) / len(timesteps) > control_guidance_end + ) + if dont_control: + noise_pred = self.unet( + sample=latent_model_input, + timestep=t, + encoder_hidden_states=prompt_embeds, + cross_attention_kwargs=cross_attention_kwargs, + added_cond_kwargs=added_cond_kwargs, + return_dict=True, + ).sample + else: + noise_pred = self.controlnet( + base_model=self.unet, + sample=latent_model_input, + timestep=t, + encoder_hidden_states=prompt_embeds, + controlnet_cond=image, + conditioning_scale=controlnet_conditioning_scale, + cross_attention_kwargs=cross_attention_kwargs, + added_cond_kwargs=added_cond_kwargs, + return_dict=True, + ).sample + + # perform guidance + if do_classifier_free_guidance: + noise_pred_uncond, noise_pred_text = noise_pred.chunk(2) + noise_pred = noise_pred_uncond + guidance_scale * (noise_pred_text - noise_pred_uncond) + + # compute the previous noisy sample x_t -> x_t-1 + latents = self.scheduler.step(noise_pred, t, latents, **extra_step_kwargs, return_dict=False)[0] + + # call the callback, if provided + if i == len(timesteps) - 1 or ((i + 1) > num_warmup_steps and (i + 1) % self.scheduler.order == 0): + progress_bar.update() + if callback is not None and i % callback_steps == 0: + step_idx = i // getattr(self.scheduler, "order", 1) + callback(step_idx, t, latents) + + # manually for max memory savings + if self.vae.dtype == torch.float16 and self.vae.config.force_upcast: + self.upcast_vae() + latents = latents.to(next(iter(self.vae.post_quant_conv.parameters())).dtype) + + if not output_type == "latent": + # make sure the VAE is in float32 mode, as it overflows in float16 + needs_upcasting = self.vae.dtype == torch.float16 and self.vae.config.force_upcast + + if needs_upcasting: + self.upcast_vae() + latents = latents.to(next(iter(self.vae.post_quant_conv.parameters())).dtype) + + image = self.vae.decode(latents / self.vae.config.scaling_factor, return_dict=False)[0] + + # cast back to fp16 if needed + if needs_upcasting: + self.vae.to(dtype=torch.float16) + else: + image = latents + + if not output_type == "latent": + # apply watermark if available + if self.watermark is not None: + image = self.watermark.apply_watermark(image) + + image = self.image_processor.postprocess(image, output_type=output_type) + + # Offload all models + self.maybe_free_model_hooks() + + if not return_dict: + return (image,) + + return StableDiffusionXLPipelineOutput(images=image) + + +class StableDiffusionControlNetXSPipeline( + DiffusionPipeline, TextualInversionLoaderMixin, LoraLoaderMixin, FromSingleFileMixin +): + r""" + Pipeline for text-to-image generation using Stable Diffusion with ControlNet-XS guidance. + + This model inherits from [`DiffusionPipeline`]. Check the superclass documentation for the generic methods + implemented for all pipelines (downloading, saving, running on a particular device, etc.). + + The pipeline also inherits the following loading methods: + - [`~loaders.TextualInversionLoaderMixin.load_textual_inversion`] for loading textual inversion embeddings + - [`~loaders.LoraLoaderMixin.load_lora_weights`] for loading LoRA weights + - [`~loaders.LoraLoaderMixin.save_lora_weights`] for saving LoRA weights + - [`~loaders.FromSingleFileMixin.from_single_file`] for loading `.ckpt` files + + Args: + vae ([`AutoencoderKL`]): + Variational Auto-Encoder (VAE) model to encode and decode images to and from latent representations. + text_encoder ([`~transformers.CLIPTextModel`]): + Frozen text-encoder ([clip-vit-large-patch14](https://huggingface.co/openai/clip-vit-large-patch14)). + tokenizer ([`~transformers.CLIPTokenizer`]): + A `CLIPTokenizer` to tokenize text. + unet ([`UNet2DConditionModel`]): + A `UNet2DConditionModel` to denoise the encoded image latents. + controlnet ([`ControlNetXSModel`]): + Provides additional conditioning to the `unet` during the denoising process. + scheduler ([`SchedulerMixin`]): + A scheduler to be used in combination with `unet` to denoise the encoded image latents. Can be one of + [`DDIMScheduler`], [`LMSDiscreteScheduler`], or [`PNDMScheduler`]. + safety_checker ([`StableDiffusionSafetyChecker`]): + Classification module that estimates whether generated images could be considered offensive or harmful. + Please refer to the [model card](https://huggingface.co/runwayml/stable-diffusion-v1-5) for more details + about a model's potential harms. + feature_extractor ([`~transformers.CLIPImageProcessor`]): + A `CLIPImageProcessor` to extract features from generated images; used as inputs to the `safety_checker`. + """ + + model_cpu_offload_seq = "text_encoder->unet->vae>controlnet" + _optional_components = ["safety_checker", "feature_extractor"] + _exclude_from_cpu_offload = ["safety_checker"] + + def __init__( + self, + vae: AutoencoderKL, + text_encoder: CLIPTextModel, + tokenizer: CLIPTokenizer, + unet: UNet2DConditionModel, + controlnet: ControlNetXSModel, + scheduler: KarrasDiffusionSchedulers, + safety_checker: StableDiffusionSafetyChecker, + feature_extractor: CLIPImageProcessor, + requires_safety_checker: bool = True, + ): + super().__init__() + + if safety_checker is None and requires_safety_checker: + logger.warning( + f"You have disabled the safety checker for {self.__class__} by passing `safety_checker=None`. Ensure" + " that you abide to the conditions of the Stable Diffusion license and do not expose unfiltered" + " results in services or applications open to the public. Both the diffusers team and Hugging Face" + " strongly recommend to keep the safety filter enabled in all public facing circumstances, disabling" + " it only for use-cases that involve analyzing network behavior or auditing its results. For more" + " information, please have a look at https://github.com/huggingface/diffusers/pull/254 ." + ) + + if safety_checker is not None and feature_extractor is None: + raise ValueError( + "Make sure to define a feature extractor when loading {self.__class__} if you want to use the safety" + " checker. If you do not want to use the safety checker, you can pass `'safety_checker=None'` instead." + ) + + vae_compatible, cnxs_condition_downsample_factor, vae_downsample_factor = controlnet._check_if_vae_compatible( + vae + ) + if not vae_compatible: + raise ValueError( + f"The downsampling factors of the VAE ({vae_downsample_factor}) and the conditioning part of ControlNetXS model {cnxs_condition_downsample_factor} need to be equal. Consider building the ControlNetXS model with different `conditioning_block_sizes`." + ) + + self.register_modules( + vae=vae, + text_encoder=text_encoder, + tokenizer=tokenizer, + unet=unet, + controlnet=controlnet, + scheduler=scheduler, + safety_checker=safety_checker, + feature_extractor=feature_extractor, + ) + self.vae_scale_factor = 2 ** (len(self.vae.config.block_out_channels) - 1) + self.image_processor = VaeImageProcessor(vae_scale_factor=self.vae_scale_factor, do_convert_rgb=True) + self.control_image_processor = VaeImageProcessor( + vae_scale_factor=self.vae_scale_factor, do_convert_rgb=True, do_normalize=False + ) + self.register_to_config(requires_safety_checker=requires_safety_checker) + + # Copied from diffusers.pipelines.stable_diffusion.pipeline_stable_diffusion.StableDiffusionPipeline.enable_vae_slicing + def enable_vae_slicing(self): + r""" + Enable sliced VAE decoding. When this option is enabled, the VAE will split the input tensor in slices to + compute decoding in several steps. This is useful to save some memory and allow larger batch sizes. + """ + self.vae.enable_slicing() + + # Copied from diffusers.pipelines.stable_diffusion.pipeline_stable_diffusion.StableDiffusionPipeline.disable_vae_slicing + def disable_vae_slicing(self): + r""" + Disable sliced VAE decoding. If `enable_vae_slicing` was previously enabled, this method will go back to + computing decoding in one step. + """ + self.vae.disable_slicing() + + # Copied from diffusers.pipelines.stable_diffusion.pipeline_stable_diffusion.StableDiffusionPipeline.enable_vae_tiling + def enable_vae_tiling(self): + r""" + Enable tiled VAE decoding. When this option is enabled, the VAE will split the input tensor into tiles to + compute decoding and encoding in several steps. This is useful for saving a large amount of memory and to allow + processing larger images. + """ + self.vae.enable_tiling() + + # Copied from diffusers.pipelines.stable_diffusion.pipeline_stable_diffusion.StableDiffusionPipeline.disable_vae_tiling + def disable_vae_tiling(self): + r""" + Disable tiled VAE decoding. If `enable_vae_tiling` was previously enabled, this method will go back to + computing decoding in one step. + """ + self.vae.disable_tiling() + + # Copied from diffusers.pipelines.stable_diffusion.pipeline_stable_diffusion.StableDiffusionPipeline._encode_prompt + def _encode_prompt( + self, + prompt, + device, + num_images_per_prompt, + do_classifier_free_guidance, + negative_prompt=None, + prompt_embeds: Optional[torch.FloatTensor] = None, + negative_prompt_embeds: Optional[torch.FloatTensor] = None, + lora_scale: Optional[float] = None, + **kwargs, + ): + deprecation_message = "`_encode_prompt()` is deprecated and it will be removed in a future version. Use `encode_prompt()` instead. Also, be aware that the output format changed from a concatenated tensor to a tuple." + deprecate("_encode_prompt()", "1.0.0", deprecation_message, standard_warn=False) + + prompt_embeds_tuple = self.encode_prompt( + prompt=prompt, + device=device, + num_images_per_prompt=num_images_per_prompt, + do_classifier_free_guidance=do_classifier_free_guidance, + negative_prompt=negative_prompt, + prompt_embeds=prompt_embeds, + negative_prompt_embeds=negative_prompt_embeds, + lora_scale=lora_scale, + **kwargs, + ) + + # concatenate for backwards comp + prompt_embeds = torch.cat([prompt_embeds_tuple[1], prompt_embeds_tuple[0]]) + + return prompt_embeds + + # Copied from diffusers.pipelines.stable_diffusion.pipeline_stable_diffusion.StableDiffusionPipeline.encode_prompt + def encode_prompt( + self, + prompt, + device, + num_images_per_prompt, + do_classifier_free_guidance, + negative_prompt=None, + prompt_embeds: Optional[torch.FloatTensor] = None, + negative_prompt_embeds: Optional[torch.FloatTensor] = None, + lora_scale: Optional[float] = None, + clip_skip: Optional[int] = None, + ): + r""" + Encodes the prompt into text encoder hidden states. + + Args: + prompt (`str` or `List[str]`, *optional*): + prompt to be encoded + device: (`torch.device`): + torch device + num_images_per_prompt (`int`): + number of images that should be generated per prompt + do_classifier_free_guidance (`bool`): + whether to use classifier free guidance or not + negative_prompt (`str` or `List[str]`, *optional*): + The prompt or prompts not to guide the image generation. If not defined, one has to pass + `negative_prompt_embeds` instead. Ignored when not using guidance (i.e., ignored if `guidance_scale` is + less than `1`). + prompt_embeds (`torch.FloatTensor`, *optional*): + Pre-generated text embeddings. Can be used to easily tweak text inputs, *e.g.* prompt weighting. If not + provided, text embeddings will be generated from `prompt` input argument. + negative_prompt_embeds (`torch.FloatTensor`, *optional*): + Pre-generated negative text embeddings. Can be used to easily tweak text inputs, *e.g.* prompt + weighting. If not provided, negative_prompt_embeds will be generated from `negative_prompt` input + argument. + lora_scale (`float`, *optional*): + A LoRA scale that will be applied to all LoRA layers of the text encoder if LoRA layers are loaded. + clip_skip (`int`, *optional*): + Number of layers to be skipped from CLIP while computing the prompt embeddings. A value of 1 means that + the output of the pre-final layer will be used for computing the prompt embeddings. + """ + # set lora scale so that monkey patched LoRA + # function of text encoder can correctly access it + if lora_scale is not None and isinstance(self, LoraLoaderMixin): + self._lora_scale = lora_scale + + # dynamically adjust the LoRA scale + if not USE_PEFT_BACKEND: + adjust_lora_scale_text_encoder(self.text_encoder, lora_scale) + else: + scale_lora_layers(self.text_encoder, lora_scale) + + if prompt is not None and isinstance(prompt, str): + batch_size = 1 + elif prompt is not None and isinstance(prompt, list): + batch_size = len(prompt) + else: + batch_size = prompt_embeds.shape[0] + + if prompt_embeds is None: + # textual inversion: procecss multi-vector tokens if necessary + if isinstance(self, TextualInversionLoaderMixin): + prompt = self.maybe_convert_prompt(prompt, self.tokenizer) + + text_inputs = self.tokenizer( + prompt, + padding="max_length", + max_length=self.tokenizer.model_max_length, + truncation=True, + return_tensors="pt", + ) + text_input_ids = text_inputs.input_ids + untruncated_ids = self.tokenizer(prompt, padding="longest", return_tensors="pt").input_ids + + if untruncated_ids.shape[-1] >= text_input_ids.shape[-1] and not torch.equal( + text_input_ids, untruncated_ids + ): + removed_text = self.tokenizer.batch_decode( + untruncated_ids[:, self.tokenizer.model_max_length - 1 : -1] + ) + logger.warning( + "The following part of your input was truncated because CLIP can only handle sequences up to" + f" {self.tokenizer.model_max_length} tokens: {removed_text}" + ) + + if hasattr(self.text_encoder.config, "use_attention_mask") and self.text_encoder.config.use_attention_mask: + attention_mask = text_inputs.attention_mask.to(device) + else: + attention_mask = None + + if clip_skip is None: + prompt_embeds = self.text_encoder(text_input_ids.to(device), attention_mask=attention_mask) + prompt_embeds = prompt_embeds[0] + else: + prompt_embeds = self.text_encoder( + text_input_ids.to(device), attention_mask=attention_mask, output_hidden_states=True + ) + # Access the `hidden_states` first, that contains a tuple of + # all the hidden states from the encoder layers. Then index into + # the tuple to access the hidden states from the desired layer. + prompt_embeds = prompt_embeds[-1][-(clip_skip + 1)] + # We also need to apply the final LayerNorm here to not mess with the + # representations. The `last_hidden_states` that we typically use for + # obtaining the final prompt representations passes through the LayerNorm + # layer. + prompt_embeds = self.text_encoder.text_model.final_layer_norm(prompt_embeds) + + if self.text_encoder is not None: + prompt_embeds_dtype = self.text_encoder.dtype + elif self.unet is not None: + prompt_embeds_dtype = self.unet.dtype + else: + prompt_embeds_dtype = prompt_embeds.dtype + + prompt_embeds = prompt_embeds.to(dtype=prompt_embeds_dtype, device=device) + + bs_embed, seq_len, _ = prompt_embeds.shape + # duplicate text embeddings for each generation per prompt, using mps friendly method + prompt_embeds = prompt_embeds.repeat(1, num_images_per_prompt, 1) + prompt_embeds = prompt_embeds.view(bs_embed * num_images_per_prompt, seq_len, -1) + + # get unconditional embeddings for classifier free guidance + if do_classifier_free_guidance and negative_prompt_embeds is None: + uncond_tokens: List[str] + if negative_prompt is None: + uncond_tokens = [""] * batch_size + elif prompt is not None and type(prompt) is not type(negative_prompt): + raise TypeError( + f"`negative_prompt` should be the same type to `prompt`, but got {type(negative_prompt)} !=" + f" {type(prompt)}." + ) + elif isinstance(negative_prompt, str): + uncond_tokens = [negative_prompt] + elif batch_size != len(negative_prompt): + raise ValueError( + f"`negative_prompt`: {negative_prompt} has batch size {len(negative_prompt)}, but `prompt`:" + f" {prompt} has batch size {batch_size}. Please make sure that passed `negative_prompt` matches" + " the batch size of `prompt`." + ) + else: + uncond_tokens = negative_prompt + + # textual inversion: procecss multi-vector tokens if necessary + if isinstance(self, TextualInversionLoaderMixin): + uncond_tokens = self.maybe_convert_prompt(uncond_tokens, self.tokenizer) + + max_length = prompt_embeds.shape[1] + uncond_input = self.tokenizer( + uncond_tokens, + padding="max_length", + max_length=max_length, + truncation=True, + return_tensors="pt", + ) + + if hasattr(self.text_encoder.config, "use_attention_mask") and self.text_encoder.config.use_attention_mask: + attention_mask = uncond_input.attention_mask.to(device) + else: + attention_mask = None + + negative_prompt_embeds = self.text_encoder( + uncond_input.input_ids.to(device), + attention_mask=attention_mask, + ) + negative_prompt_embeds = negative_prompt_embeds[0] + + if do_classifier_free_guidance: + # duplicate unconditional embeddings for each generation per prompt, using mps friendly method + seq_len = negative_prompt_embeds.shape[1] + + negative_prompt_embeds = negative_prompt_embeds.to(dtype=prompt_embeds_dtype, device=device) + + negative_prompt_embeds = negative_prompt_embeds.repeat(1, num_images_per_prompt, 1) + negative_prompt_embeds = negative_prompt_embeds.view(batch_size * num_images_per_prompt, seq_len, -1) + + if isinstance(self, LoraLoaderMixin) and USE_PEFT_BACKEND: + # Retrieve the original scale by scaling back the LoRA layers + unscale_lora_layers(self.text_encoder, lora_scale) + + return prompt_embeds, negative_prompt_embeds + + # Copied from diffusers.pipelines.stable_diffusion.pipeline_stable_diffusion.StableDiffusionPipeline.run_safety_checker + def run_safety_checker(self, image, device, dtype): + if self.safety_checker is None: + has_nsfw_concept = None + else: + if torch.is_tensor(image): + feature_extractor_input = self.image_processor.postprocess(image, output_type="pil") + else: + feature_extractor_input = self.image_processor.numpy_to_pil(image) + safety_checker_input = self.feature_extractor(feature_extractor_input, return_tensors="pt").to(device) + image, has_nsfw_concept = self.safety_checker( + images=image, clip_input=safety_checker_input.pixel_values.to(dtype) + ) + return image, has_nsfw_concept + + # Copied from diffusers.pipelines.stable_diffusion.pipeline_stable_diffusion.StableDiffusionPipeline.decode_latents + def decode_latents(self, latents): + deprecation_message = "The decode_latents method is deprecated and will be removed in 1.0.0. Please use VaeImageProcessor.postprocess(...) instead" + deprecate("decode_latents", "1.0.0", deprecation_message, standard_warn=False) + + latents = 1 / self.vae.config.scaling_factor * latents + image = self.vae.decode(latents, return_dict=False)[0] + image = (image / 2 + 0.5).clamp(0, 1) + # we always cast to float32 as this does not cause significant overhead and is compatible with bfloat16 + image = image.cpu().permute(0, 2, 3, 1).float().numpy() + return image + + # Copied from diffusers.pipelines.stable_diffusion.pipeline_stable_diffusion.StableDiffusionPipeline.prepare_extra_step_kwargs + def prepare_extra_step_kwargs(self, generator, eta): + # prepare extra kwargs for the scheduler step, since not all schedulers have the same signature + # eta (η) is only used with the DDIMScheduler, it will be ignored for other schedulers. + # eta corresponds to η in DDIM paper: https://arxiv.org/abs/2010.02502 + # and should be between [0, 1] + + accepts_eta = "eta" in set(inspect.signature(self.scheduler.step).parameters.keys()) + extra_step_kwargs = {} + if accepts_eta: + extra_step_kwargs["eta"] = eta + + # check if the scheduler accepts generator + accepts_generator = "generator" in set(inspect.signature(self.scheduler.step).parameters.keys()) + if accepts_generator: + extra_step_kwargs["generator"] = generator + return extra_step_kwargs + + def check_inputs( + self, + prompt, + image, + callback_steps, + negative_prompt=None, + prompt_embeds=None, + negative_prompt_embeds=None, + controlnet_conditioning_scale=1.0, + control_guidance_start=0.0, + control_guidance_end=1.0, + ): + if (callback_steps is None) or ( + callback_steps is not None and (not isinstance(callback_steps, int) or callback_steps <= 0) + ): + raise ValueError( + f"`callback_steps` has to be a positive integer but is {callback_steps} of type" + f" {type(callback_steps)}." + ) + + if prompt is not None and prompt_embeds is not None: + raise ValueError( + f"Cannot forward both `prompt`: {prompt} and `prompt_embeds`: {prompt_embeds}. Please make sure to" + " only forward one of the two." + ) + elif prompt is None and prompt_embeds is None: + raise ValueError( + "Provide either `prompt` or `prompt_embeds`. Cannot leave both `prompt` and `prompt_embeds` undefined." + ) + elif prompt is not None and (not isinstance(prompt, str) and not isinstance(prompt, list)): + raise ValueError(f"`prompt` has to be of type `str` or `list` but is {type(prompt)}") + + if negative_prompt is not None and negative_prompt_embeds is not None: + raise ValueError( + f"Cannot forward both `negative_prompt`: {negative_prompt} and `negative_prompt_embeds`:" + f" {negative_prompt_embeds}. Please make sure to only forward one of the two." + ) + + if prompt_embeds is not None and negative_prompt_embeds is not None: + if prompt_embeds.shape != negative_prompt_embeds.shape: + raise ValueError( + "`prompt_embeds` and `negative_prompt_embeds` must have the same shape when passed directly, but" + f" got: `prompt_embeds` {prompt_embeds.shape} != `negative_prompt_embeds`" + f" {negative_prompt_embeds.shape}." + ) + + # Check `image` + is_compiled = hasattr(F, "scaled_dot_product_attention") and isinstance( + self.controlnet, torch._dynamo.eval_frame.OptimizedModule + ) + if ( + isinstance(self.controlnet, ControlNetXSModel) + or is_compiled + and isinstance(self.controlnet._orig_mod, ControlNetXSModel) + ): + self.check_image(image, prompt, prompt_embeds) + else: + assert False + + # Check `controlnet_conditioning_scale` + if ( + isinstance(self.controlnet, ControlNetXSModel) + or is_compiled + and isinstance(self.controlnet._orig_mod, ControlNetXSModel) + ): + if not isinstance(controlnet_conditioning_scale, float): + raise TypeError("For single controlnet: `controlnet_conditioning_scale` must be type `float`.") + else: + assert False + + start, end = control_guidance_start, control_guidance_end + if start >= end: + raise ValueError( + f"control guidance start: {start} cannot be larger or equal to control guidance end: {end}." + ) + if start < 0.0: + raise ValueError(f"control guidance start: {start} can't be smaller than 0.") + if end > 1.0: + raise ValueError(f"control guidance end: {end} can't be larger than 1.0.") + + def check_image(self, image, prompt, prompt_embeds): + image_is_pil = isinstance(image, PIL.Image.Image) + image_is_tensor = isinstance(image, torch.Tensor) + image_is_np = isinstance(image, np.ndarray) + image_is_pil_list = isinstance(image, list) and isinstance(image[0], PIL.Image.Image) + image_is_tensor_list = isinstance(image, list) and isinstance(image[0], torch.Tensor) + image_is_np_list = isinstance(image, list) and isinstance(image[0], np.ndarray) + + if ( + not image_is_pil + and not image_is_tensor + and not image_is_np + and not image_is_pil_list + and not image_is_tensor_list + and not image_is_np_list + ): + raise TypeError( + f"image must be passed and be one of PIL image, numpy array, torch tensor, list of PIL images, list of numpy arrays or list of torch tensors, but is {type(image)}" + ) + + if image_is_pil: + image_batch_size = 1 + else: + image_batch_size = len(image) + + if prompt is not None and isinstance(prompt, str): + prompt_batch_size = 1 + elif prompt is not None and isinstance(prompt, list): + prompt_batch_size = len(prompt) + elif prompt_embeds is not None: + prompt_batch_size = prompt_embeds.shape[0] + + if image_batch_size != 1 and image_batch_size != prompt_batch_size: + raise ValueError( + f"If image batch size is not 1, image batch size must be same as prompt batch size. image batch size: {image_batch_size}, prompt batch size: {prompt_batch_size}" + ) + + def prepare_image( + self, + image, + width, + height, + batch_size, + num_images_per_prompt, + device, + dtype, + do_classifier_free_guidance=False, + ): + image = self.control_image_processor.preprocess(image, height=height, width=width).to(dtype=torch.float32) + image_batch_size = image.shape[0] + + if image_batch_size == 1: + repeat_by = batch_size + else: + # image batch size is the same as prompt batch size + repeat_by = num_images_per_prompt + + image = image.repeat_interleave(repeat_by, dim=0) + + image = image.to(device=device, dtype=dtype) + + if do_classifier_free_guidance: + image = torch.cat([image] * 2) + + return image + + # Copied from diffusers.pipelines.stable_diffusion.pipeline_stable_diffusion.StableDiffusionPipeline.prepare_latents + def prepare_latents(self, batch_size, num_channels_latents, height, width, dtype, device, generator, latents=None): + shape = (batch_size, num_channels_latents, height // self.vae_scale_factor, width // self.vae_scale_factor) + if isinstance(generator, list) and len(generator) != batch_size: + raise ValueError( + f"You have passed a list of generators of length {len(generator)}, but requested an effective batch" + f" size of {batch_size}. Make sure the batch size matches the length of the generators." + ) + + if latents is None: + latents = randn_tensor(shape, generator=generator, device=device, dtype=dtype) + else: + latents = latents.to(device) + + # scale the initial noise by the standard deviation required by the scheduler + latents = latents * self.scheduler.init_noise_sigma + return latents + + # Copied from diffusers.pipelines.stable_diffusion.pipeline_stable_diffusion.StableDiffusionPipeline.enable_freeu + def enable_freeu(self, s1: float, s2: float, b1: float, b2: float): + r"""Enables the FreeU mechanism as in https://arxiv.org/abs/2309.11497. + + The suffixes after the scaling factors represent the stages where they are being applied. + + Please refer to the [official repository](https://github.com/ChenyangSi/FreeU) for combinations of the values + that are known to work well for different pipelines such as Stable Diffusion v1, v2, and Stable Diffusion XL. + + Args: + s1 (`float`): + Scaling factor for stage 1 to attenuate the contributions of the skip features. This is done to + mitigate "oversmoothing effect" in the enhanced denoising process. + s2 (`float`): + Scaling factor for stage 2 to attenuate the contributions of the skip features. This is done to + mitigate "oversmoothing effect" in the enhanced denoising process. + b1 (`float`): Scaling factor for stage 1 to amplify the contributions of backbone features. + b2 (`float`): Scaling factor for stage 2 to amplify the contributions of backbone features. + """ + if not hasattr(self, "unet"): + raise ValueError("The pipeline must have `unet` for using FreeU.") + self.unet.enable_freeu(s1=s1, s2=s2, b1=b1, b2=b2) + + # Copied from diffusers.pipelines.stable_diffusion.pipeline_stable_diffusion.StableDiffusionPipeline.disable_freeu + def disable_freeu(self): + """Disables the FreeU mechanism if enabled.""" + self.unet.disable_freeu() + + @torch.no_grad() + def __call__( + self, + prompt: Union[str, List[str]] = None, + image: PipelineImageInput = None, + height: Optional[int] = None, + width: Optional[int] = None, + num_inference_steps: int = 50, + guidance_scale: float = 7.5, + negative_prompt: Optional[Union[str, List[str]]] = None, + num_images_per_prompt: Optional[int] = 1, + eta: float = 0.0, + generator: Optional[Union[torch.Generator, List[torch.Generator]]] = None, + latents: Optional[torch.FloatTensor] = None, + prompt_embeds: Optional[torch.FloatTensor] = None, + negative_prompt_embeds: Optional[torch.FloatTensor] = None, + output_type: Optional[str] = "pil", + return_dict: bool = True, + callback: Optional[Callable[[int, int, torch.FloatTensor], None]] = None, + callback_steps: int = 1, + cross_attention_kwargs: Optional[Dict[str, Any]] = None, + controlnet_conditioning_scale: Union[float, List[float]] = 1.0, + control_guidance_start: float = 0.0, + control_guidance_end: float = 1.0, + clip_skip: Optional[int] = None, + ): + r""" + The call function to the pipeline for generation. + + Args: + prompt (`str` or `List[str]`, *optional*): + The prompt or prompts to guide image generation. If not defined, you need to pass `prompt_embeds`. + image (`torch.FloatTensor`, `PIL.Image.Image`, `np.ndarray`, `List[torch.FloatTensor]`, `List[PIL.Image.Image]`, `List[np.ndarray]`, + `List[List[torch.FloatTensor]]`, `List[List[np.ndarray]]` or `List[List[PIL.Image.Image]]`): + The ControlNet input condition to provide guidance to the `unet` for generation. If the type is + specified as `torch.FloatTensor`, it is passed to ControlNet as is. `PIL.Image.Image` can also be + accepted as an image. The dimensions of the output image defaults to `image`'s dimensions. If height + and/or width are passed, `image` is resized accordingly. If multiple ControlNets are specified in + `init`, images must be passed as a list such that each element of the list can be correctly batched for + input to a single ControlNet. + height (`int`, *optional*, defaults to `self.unet.config.sample_size * self.vae_scale_factor`): + The height in pixels of the generated image. + width (`int`, *optional*, defaults to `self.unet.config.sample_size * self.vae_scale_factor`): + The width in pixels of the generated image. + num_inference_steps (`int`, *optional*, defaults to 50): + The number of denoising steps. More denoising steps usually lead to a higher quality image at the + expense of slower inference. + guidance_scale (`float`, *optional*, defaults to 7.5): + A higher guidance scale value encourages the model to generate images closely linked to the text + `prompt` at the expense of lower image quality. Guidance scale is enabled when `guidance_scale > 1`. + negative_prompt (`str` or `List[str]`, *optional*): + The prompt or prompts to guide what to not include in image generation. If not defined, you need to + pass `negative_prompt_embeds` instead. Ignored when not using guidance (`guidance_scale < 1`). + num_images_per_prompt (`int`, *optional*, defaults to 1): + The number of images to generate per prompt. + eta (`float`, *optional*, defaults to 0.0): + Corresponds to parameter eta (η) from the [DDIM](https://arxiv.org/abs/2010.02502) paper. Only applies + to the [`~schedulers.DDIMScheduler`], and is ignored in other schedulers. + generator (`torch.Generator` or `List[torch.Generator]`, *optional*): + A [`torch.Generator`](https://pytorch.org/docs/stable/generated/torch.Generator.html) to make + generation deterministic. + latents (`torch.FloatTensor`, *optional*): + Pre-generated noisy latents sampled from a Gaussian distribution, to be used as inputs for image + generation. Can be used to tweak the same generation with different prompts. If not provided, a latents + tensor is generated by sampling using the supplied random `generator`. + prompt_embeds (`torch.FloatTensor`, *optional*): + Pre-generated text embeddings. Can be used to easily tweak text inputs (prompt weighting). If not + provided, text embeddings are generated from the `prompt` input argument. + negative_prompt_embeds (`torch.FloatTensor`, *optional*): + Pre-generated negative text embeddings. Can be used to easily tweak text inputs (prompt weighting). If + not provided, `negative_prompt_embeds` are generated from the `negative_prompt` input argument. + output_type (`str`, *optional*, defaults to `"pil"`): + The output format of the generated image. Choose between `PIL.Image` or `np.array`. + return_dict (`bool`, *optional*, defaults to `True`): + Whether or not to return a [`~pipelines.stable_diffusion.StableDiffusionPipelineOutput`] instead of a + plain tuple. + callback (`Callable`, *optional*): + A function that calls every `callback_steps` steps during inference. The function is called with the + following arguments: `callback(step: int, timestep: int, latents: torch.FloatTensor)`. + callback_steps (`int`, *optional*, defaults to 1): + The frequency at which the `callback` function is called. If not specified, the callback is called at + every step. + cross_attention_kwargs (`dict`, *optional*): + A kwargs dictionary that if specified is passed along to the [`AttentionProcessor`] as defined in + [`self.processor`](https://github.com/huggingface/diffusers/blob/main/src/diffusers/models/attention_processor.py). + controlnet_conditioning_scale (`float` or `List[float]`, *optional*, defaults to 1.0): + The outputs of the ControlNet are multiplied by `controlnet_conditioning_scale` before they are added + to the residual in the original `unet`. If multiple ControlNets are specified in `init`, you can set + the corresponding scale as a list. + control_guidance_start (`float` or `List[float]`, *optional*, defaults to 0.0): + The percentage of total steps at which the ControlNet starts applying. + control_guidance_end (`float` or `List[float]`, *optional*, defaults to 1.0): + The percentage of total steps at which the ControlNet stops applying. + clip_skip (`int`, *optional*): + Number of layers to be skipped from CLIP while computing the prompt embeddings. A value of 1 means that + the output of the pre-final layer will be used for computing the prompt embeddings. + + Examples: + + Returns: + [`~pipelines.stable_diffusion.StableDiffusionPipelineOutput`] or `tuple`: + If `return_dict` is `True`, [`~pipelines.stable_diffusion.StableDiffusionPipelineOutput`] is returned, + otherwise a `tuple` is returned where the first element is a list with the generated images and the + second element is a list of `bool`s indicating whether the corresponding generated image contains + "not-safe-for-work" (nsfw) content. + """ + controlnet = self.controlnet._orig_mod if is_compiled_module(self.controlnet) else self.controlnet + + # 1. Check inputs. Raise error if not correct + self.check_inputs( + prompt, + image, + callback_steps, + negative_prompt, + prompt_embeds, + negative_prompt_embeds, + controlnet_conditioning_scale, + control_guidance_start, + control_guidance_end, + ) + + # 2. Define call parameters + if prompt is not None and isinstance(prompt, str): + batch_size = 1 + elif prompt is not None and isinstance(prompt, list): + batch_size = len(prompt) + else: + batch_size = prompt_embeds.shape[0] + + device = self._execution_device + # here `guidance_scale` is defined analog to the guidance weight `w` of equation (2) + # of the Imagen paper: https://arxiv.org/pdf/2205.11487.pdf . `guidance_scale = 1` + # corresponds to doing no classifier free guidance. + do_classifier_free_guidance = guidance_scale > 1.0 + + # 3. Encode input prompt + text_encoder_lora_scale = ( + cross_attention_kwargs.get("scale", None) if cross_attention_kwargs is not None else None + ) + prompt_embeds, negative_prompt_embeds = self.encode_prompt( + prompt, + device, + num_images_per_prompt, + do_classifier_free_guidance, + negative_prompt, + prompt_embeds=prompt_embeds, + negative_prompt_embeds=negative_prompt_embeds, + lora_scale=text_encoder_lora_scale, + clip_skip=clip_skip, + ) + # For classifier free guidance, we need to do two forward passes. + # Here we concatenate the unconditional and text embeddings into a single batch + # to avoid doing two forward passes + if do_classifier_free_guidance: + prompt_embeds = torch.cat([negative_prompt_embeds, prompt_embeds]) + + # 4. Prepare image + if isinstance(controlnet, ControlNetXSModel): + image = self.prepare_image( + image=image, + width=width, + height=height, + batch_size=batch_size * num_images_per_prompt, + num_images_per_prompt=num_images_per_prompt, + device=device, + dtype=controlnet.dtype, + do_classifier_free_guidance=do_classifier_free_guidance, + ) + height, width = image.shape[-2:] + else: + assert False + + # 5. Prepare timesteps + self.scheduler.set_timesteps(num_inference_steps, device=device) + timesteps = self.scheduler.timesteps + + # 6. Prepare latent variables + num_channels_latents = self.unet.config.in_channels + latents = self.prepare_latents( + batch_size * num_images_per_prompt, + num_channels_latents, + height, + width, + prompt_embeds.dtype, + device, + generator, + latents, + ) + + # 7. Prepare extra step kwargs. TODO: Logic should ideally just be moved out of the pipeline + extra_step_kwargs = self.prepare_extra_step_kwargs(generator, eta) + + # 8. Denoising loop + num_warmup_steps = len(timesteps) - num_inference_steps * self.scheduler.order + is_unet_compiled = is_compiled_module(self.unet) + is_controlnet_compiled = is_compiled_module(self.controlnet) + is_torch_higher_equal_2_1 = is_torch_version(">=", "2.1") + with self.progress_bar(total=num_inference_steps) as progress_bar: + for i, t in enumerate(timesteps): + # Relevant thread: + # https://dev-discuss.pytorch.org/t/cudagraphs-in-pytorch-2-0/1428 + if (is_unet_compiled and is_controlnet_compiled) and is_torch_higher_equal_2_1: + torch._inductor.cudagraph_mark_step_begin() + # expand the latents if we are doing classifier free guidance + latent_model_input = torch.cat([latents] * 2) if do_classifier_free_guidance else latents + latent_model_input = self.scheduler.scale_model_input(latent_model_input, t) + + # predict the noise residual + dont_control = ( + i / len(timesteps) < control_guidance_start or (i + 1) / len(timesteps) > control_guidance_end + ) + if dont_control: + noise_pred = self.unet( + sample=latent_model_input, + timestep=t, + encoder_hidden_states=prompt_embeds, + cross_attention_kwargs=cross_attention_kwargs, + return_dict=True, + ).sample + else: + noise_pred = self.controlnet( + base_model=self.unet, + sample=latent_model_input, + timestep=t, + encoder_hidden_states=prompt_embeds, + controlnet_cond=image, + conditioning_scale=controlnet_conditioning_scale, + cross_attention_kwargs=cross_attention_kwargs, + return_dict=True, + ).sample + + # perform guidance + if do_classifier_free_guidance: + noise_pred_uncond, noise_pred_text = noise_pred.chunk(2) + noise_pred = noise_pred_uncond + guidance_scale * (noise_pred_text - noise_pred_uncond) + + latents = self.scheduler.step(noise_pred, t, latents, **extra_step_kwargs, return_dict=False)[0] + + # call the callback, if provided + if i == len(timesteps) - 1 or ((i + 1) > num_warmup_steps and (i + 1) % self.scheduler.order == 0): + progress_bar.update() + if callback is not None and i % callback_steps == 0: + step_idx = i // getattr(self.scheduler, "order", 1) + callback(step_idx, t, latents) + + # If we do sequential model offloading, let's offload unet and controlnet + # manually for max memory savings + if hasattr(self, "final_offload_hook") and self.final_offload_hook is not None: + self.unet.to("cpu") + self.controlnet.to("cpu") + torch.cuda.empty_cache() + + if not output_type == "latent": + image = self.vae.decode(latents / self.vae.config.scaling_factor, return_dict=False, generator=generator)[ + 0 + ] + image, has_nsfw_concept = self.run_safety_checker(image, device, prompt_embeds.dtype) + else: + image = latents + has_nsfw_concept = None + + if has_nsfw_concept is None: + do_denormalize = [True] * image.shape[0] + else: + do_denormalize = [not has_nsfw for has_nsfw in has_nsfw_concept] + + image = self.image_processor.postprocess(image, output_type=output_type, do_denormalize=do_denormalize) + + # Offload all models + self.maybe_free_model_hooks() + + if not return_dict: + return (image, has_nsfw_concept) + + return StableDiffusionPipelineOutput(images=image, nsfw_content_detected=has_nsfw_concept) diff --git a/modules/ui.py b/modules/ui.py index 77676790d..60c7ba0a8 100644 --- a/modules/ui.py +++ b/modules/ui.py @@ -648,9 +648,6 @@ def create_ui(startup_timer = None): ui_extra_networks.setup_ui(extra_networks_ui, txt2img_gallery) - with FormRow(): - gr.HTML(value="", elem_id="main_info", visible=False, elem_classes=["main-info"]) - timer.startup.record("ui-txt2img") import modules.img2img # pylint: disable=redefined-outer-name diff --git a/modules/ui_common.py b/modules/ui_common.py index ca8efa096..5c35e3f4c 100644 --- a/modules/ui_common.py +++ b/modules/ui_common.py @@ -202,6 +202,8 @@ def create_output_panel(tabname, preview=True): with gr.Column(variant='panel', elem_id=f"{tabname}_results"): with gr.Group(elem_id=f"{tabname}_gallery_container"): + if tabname == "txt2img": + gr.HTML(value="", elem_id="main_info", visible=False, elem_classes=["main-info"]) # columns are for <576px, <768px, <992px, <1200px, <1400px, >1400px result_gallery = gr.Gallery(value=[], label='Output', show_label=False, show_download_button=True, allow_preview=True, elem_id=f"{tabname}_gallery", container=False, preview=preview, columns=5, object_fit='scale-down', height=shared.opts.gallery_height or None) diff --git a/modules/ui_extra_networks.py b/modules/ui_extra_networks.py index 6bf5644a1..e996940a2 100644 --- a/modules/ui_extra_networks.py +++ b/modules/ui_extra_networks.py @@ -233,11 +233,10 @@ def create_page(self, tabname, skip = False): allowed_folders = [os.path.abspath(x) for x in self.allowed_directories_for_previews()] for parentdir, dirs in {d: modelloader.directory_list(d) for d in allowed_folders}.items(): for tgt in dirs.keys(): - if shared.backend == shared.Backend.DIFFUSERS: - if os.path.join(paths.models_path, 'Reference') in tgt: - subdirs['Reference'] = 1 - if shared.opts.diffusers_dir in tgt: - subdirs[os.path.basename(shared.opts.diffusers_dir)] = 1 + if os.path.join(paths.models_path, 'Reference') in tgt: + subdirs['Reference'] = 1 + if shared.backend == shared.Backend.DIFFUSERS and shared.opts.diffusers_dir in tgt: + subdirs[os.path.basename(shared.opts.diffusers_dir)] = 1 if 'models--' in tgt: continue subdir = tgt[len(parentdir):].replace("\\", "/") @@ -248,7 +247,7 @@ def create_page(self, tabname, skip = False): subdirs[subdir] = 1 debug(f"Extra networks: page='{self.name}' subfolders={list(subdirs)}") subdirs = OrderedDict(sorted(subdirs.items())) - if shared.backend == shared.Backend.DIFFUSERS and self.name == 'model': + if self.name == 'model': subdirs['Reference'] = 1 subdirs[os.path.basename(shared.opts.diffusers_dir)] = 1 subdirs.move_to_end(os.path.basename(shared.opts.diffusers_dir)) diff --git a/modules/ui_extra_networks_checkpoints.py b/modules/ui_extra_networks_checkpoints.py index 692acdc18..c217348c0 100644 --- a/modules/ui_extra_networks_checkpoints.py +++ b/modules/ui_extra_networks_checkpoints.py @@ -15,21 +15,25 @@ def refresh(self): shared.refresh_checkpoints() def list_reference(self): # pylint: disable=inconsistent-return-statements - if shared.backend != shared.Backend.DIFFUSERS: - return [] reference_models = shared.readfile(os.path.join('html', 'reference.json')) for k, v in reference_models.items(): + if shared.backend != shared.Backend.DIFFUSERS: + if not v.get('original', False): + continue + url = v.get('alt', None) or v['path'] + else: + url = v['path'] name = os.path.join(reference_dir, k) preview = v.get('preview', v['path']) yield { "type": 'Model', "name": name, "title": name, - "filename": v['path'], + "filename": url, "search_term": self.search_terms_from_path(name), "preview": self.find_preview(os.path.join(reference_dir, preview)), "local_preview": self.find_preview_file(os.path.join(reference_dir, preview)), - "onclick": '"' + html.escape(f"""return selectReference({json.dumps(v['path'])})""") + '"', + "onclick": '"' + html.escape(f"""return selectReference({json.dumps(url)})""") + '"', "hash": None, "mtime": 0, "size": 0, @@ -74,4 +78,7 @@ def list_items(self): yield record def allowed_directories_for_previews(self): - return [v for v in [shared.opts.ckpt_dir, shared.opts.diffusers_dir, reference_dir, sd_models.model_path] if v is not None] + if shared.backend == shared.Backend.DIFFUSERS: + return [v for v in [shared.opts.ckpt_dir, shared.opts.diffusers_dir, reference_dir] if v is not None] + else: + return [v for v in [shared.opts.ckpt_dir, reference_dir, sd_models.model_path] if v is not None] diff --git a/pyproject.toml b/pyproject.toml index 9df236ad9..1cc12d98a 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -40,6 +40,8 @@ exclude = [ "repositories/blip", "repositories/codeformer", "modules/control/proc/normalbae/nets/submodules/efficientnet_repo/geffnet", + "modules/control/units/*_model.py", + "modules/control/units/*_pipe.py", ] ignore = [ "A003", # Class attirbute shadowing builtin diff --git a/requirements.txt b/requirements.txt index 04634b171..ce01e21c4 100644 --- a/requirements.txt +++ b/requirements.txt @@ -24,7 +24,6 @@ lmdb lpips omegaconf open-clip-torch -opencv-contrib-python-headless piexif psutil pyyaml @@ -51,7 +50,7 @@ antlr4-python3-runtime==4.9.3 requests==2.31.0 tqdm==4.66.1 accelerate==0.25.0 -opencv-python-headless==4.8.1.78 +opencv-contrib-python-headless==4.8.1.78 diffusers==0.24.0 einops==0.4.1 gradio==3.43.2 From 1de3c6af0fa3cbd0cb5d1d73cb105dd4f5afd1a1 Mon Sep 17 00:00:00 2001 From: Vladimir Mandic Date: Wed, 27 Dec 2023 10:15:36 -0500 Subject: [PATCH 128/143] add amused model support and update reference list --- CHANGELOG.md | 35 +++++++++++++----------- html/reference.json | 10 +++++++ models/Reference/amused--amused-256.jpg | Bin 0 -> 32356 bytes models/Reference/amused--amused-512.jpg | Bin 0 -> 32518 bytes modules/dml/__init__.py | 2 +- modules/postprocess/sdupscaler_model.py | 2 +- modules/processing_diffusers.py | 12 ++++++-- modules/sd_models.py | 2 ++ modules/shared.py | 2 +- requirements.txt | 2 +- 10 files changed, 44 insertions(+), 23 deletions(-) create mode 100644 models/Reference/amused--amused-256.jpg create mode 100644 models/Reference/amused--amused-512.jpg diff --git a/CHANGELOG.md b/CHANGELOG.md index 50d1ca0d8..4d81c6fa4 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,8 +2,6 @@ ## Update for 2023-12-27 -*Note*: based on `diffusers==0.25.0.dev0` - - **Control** - native implementation of all image control methods: **ControlNet**, **ControlNet XS**, **Control LLLite**, **T2I Adapters** and **IP Adapters** @@ -13,6 +11,25 @@ - for details and list of supported models and workflows, see Wiki documentation: - **Diffusers** + - [Segmind Vega](https://huggingface.co/segmind/Segmind-Vega) model support + - small and fast version of **SDXL**, only 3.1GB in size! + - select from *networks -> reference* + - [aMUSEd 256](https://huggingface.co/amused/amused-256) and [aMUSEd 512](https://huggingface.co/amused/amused-512) model support + - lightweigt models that excel at fast image generation + - *note*: must select: settings -> diffusers -> generator device: unset + - select from *networks -> reference* + - [Playground v1](https://huggingface.co/playgroundai/playground-v1), [Playground v2 256](https://huggingface.co/playgroundai/playground-v2-256px-base), [Playground v2 512](https://huggingface.co/playgroundai/playground-v2-512px-base), [Playground v2 1024](https://huggingface.co/playgroundai/playground-v2-1024px-aesthetic) model support + - comparable to SD15 and SD-XL, trained from scratch for highly aesthetic images + - simply select from *networks -> reference* and use as usual + - [BLIP-Diffusion](https://dxli94.github.io/BLIP-Diffusion-website/) + - img2img model that can replace subjects in images using prompt keywords + - download and load by selecting from *networks -> reference -> blip diffusion* + - in image tab, select `blip diffusion` script + - [DemoFusion](https://github.com/PRIS-CV/DemoFusion) run your SDXL generations at any resolution! + - in **Text** tab select *script* -> *demofusion* + - *note*: GPU VRAM limits do not automatically go away so be careful when using it with large resolutions + in the future, expect more optimizations, especially related to offloading/slicing/tiling, + but at the moment this is pretty much experimental-only - **AnimateDiff** - can now be used with *second pass* - enhance, upscale and hires your videos! - **IP Adapter** @@ -30,20 +47,6 @@ *if you know of any other t2v models you'd like to see supported, let me know!* - models are auto-downloaded on first use - *note*: current base model will be unloaded to free up resources - - [Segmind Vega](https://huggingface.co/segmind/Segmind-Vega) support - - small and fast version of **SDXL**, only 3.1GB in size! - - select from *networks -> reference* - - [Playground v1](https://huggingface.co/playgroundai/playground-v1), [Playground v2 256](https://huggingface.co/playgroundai/playground-v2-256px-base), [Playground v2 512](https://huggingface.co/playgroundai/playground-v2-512px-base), [Playground v2 1024](https://huggingface.co/playgroundai/playground-v2-1024px-aesthetic) model support - - simply select from *networks -> reference* and use as usual - - [BLIP-Diffusion](https://dxli94.github.io/BLIP-Diffusion-website/) - - img2img model that can replace subjects in images using prompt keywords - - download and load by selecting from *networks -> reference -> blip diffusion* - - in image tab, select `blip diffusion` script - - [DemoFusion](https://github.com/PRIS-CV/DemoFusion) run your SDXL generations at any resolution! - - in **Text** tab select *script* -> *demofusion* - - *note*: GPU VRAM limits do not automatically go away so be careful when using it with large resolutions - in the future, expect more optimizations, especially related to offloading/slicing/tiling, - but at the moment this is pretty much experimental-only - **Prompt scheduling** now implemented for Diffusers backend, thanks @AI-Casanova - **Custom pipelines** contribute by adding your own custom pipelines! - for details, see fully documented example: diff --git a/html/reference.json b/html/reference.json index 6ad5f0700..1ab1291f7 100644 --- a/html/reference.json +++ b/html/reference.json @@ -144,6 +144,16 @@ "desc": "DeepFloyd-IF is a pixel-based text-to-image triple-cascaded diffusion model, that can generate pictures with new state-of-the-art for photorealism and language understanding. The result is a highly efficient model that outperforms current state-of-the-art models, achieving a zero-shot FID-30K score of 6.66 on the COCO dataset. It is modular and composed of frozen text mode and three pixel cascaded diffusion modules, each designed to generate images of increasing resolution: 64x64, 256x256, and 1024x1024.", "preview": "DeepFloyd--IF-I-M-v1.0.jpg" }, + "aMUSEd 256": { + "path": "amused/amused-256", + "desc": "Amused is a lightweight text to image model based off of the muse architecture. Amused is particularly useful in applications that require a lightweight and fast model such as generating many images quickly at once.", + "preview": "amused--amused-256.jpg" + }, + "aMUSEd 512": { + "path": "amused/amused-512", + "desc": "Amused is a lightweight text to image model based off of the muse architecture. Amused is particularly useful in applications that require a lightweight and fast model such as generating many images quickly at once.", + "preview": "amused--amused-512.jpg" + }, "Tsinghua UniDiffuser": { "path": "thu-ml/unidiffuser-v1", "desc": "UniDiffuser is a unified diffusion framework to fit all distributions relevant to a set of multi-modal data in one transformer. UniDiffuser is able to perform image, text, text-to-image, image-to-text, and image-text pair generation by setting proper timesteps without additional overhead.\nSpecifically, UniDiffuser employs a variation of transformer, called U-ViT, which parameterizes the joint noise prediction network. Other components perform as encoders and decoders of different modalities, including a pretrained image autoencoder from Stable Diffusion, a pretrained image ViT-B/32 CLIP encoder, a pretrained text ViT-L CLIP encoder, and a GPT-2 text decoder finetuned by ourselves.", diff --git a/models/Reference/amused--amused-256.jpg b/models/Reference/amused--amused-256.jpg new file mode 100644 index 0000000000000000000000000000000000000000..f410817a82466da2721db8ab61ecf2ebfacf4d11 GIT binary patch literal 32356 zcmbUIWmFtZ)HaL`?k>UIg4+Ot1$TE%aEAbcTaaLbYhZ8~oWWg^5FCQLyKAsuNj~o9 z`PTccb$*?5s(W|W>Z*~-2nf#0XFh}j&=Zm zx;lUz005u^kP#>VD6ct$SCK@Z{6BdG1U3ND|6E6WE#w2d9s#(oqVX#KC5u=2@1FlT zyIVTCd2zehdeH&7c>(`c0kQyeR8%xn6m&E+Gz<)MOl)EtY%DBnazY|JVrmLn8fpqE zDmo@^RyqbwMk*>c;kTT;e1d|4w5%Wr5q@!Q0YU!%9)f^@fq{*MO@@O*#!pX0&;S2( z`u80`fQ|@2XhuRH1RxS1AQ2$^>j%)jwjAZPPyXwc|2H8ZA|a!oqM>78V!c*q!3Q8B zAR!?lBcY%mBfr)Tek}(e6QB^%^GTx;X>i0v~qu6xAZ-gN|{`bkf_Q zA4{EiG_8{5l#|SE9CuCq(%5MjZq(1-;Zqt_WXOWxHmfX^p=L%pu(osdg*yEMM5`2z zTkEXEY z(jmqAb`W-wwR!rJZO@-?Ie^3V3pRbR=(nS?1R=Unw{Wzk%u)5VEt(d?GY*vIYq?$< zWIU6VdeY9%e(r=C2Ss1^jf~t5B5)Hjp5_bAhFXKmvsR1Fb-hS|XF#?KDFd+&QwvlJ zjm9D#$vrV83M`4;2u4pKg%d?&dr1prKN^}KRzJ(@P?Kwvr7WeeAOim*m~Xo(ketT; zN)Y|}>t$4MEBNv$&lT_Ulgqb1EAQU@Y*NKGm5C}$jL*$54u1uVglt&&? z99P&$<|vS#4$xi+i&I_+CCz~X+)Li&0k}-tX1Oo?m6SeQ3L~ba%8Aw|o>iJm7MfKN z9Vlp)If92eI0%&Q_#J2Deu0#UtMN>#*X9N9DPLR11}F~2Gy$*`B9|6o$N4>Z7;w#s z`PG7V)v=V?sr1L>jtBVTl4ZZ|)XL=IzQrb`n(xFNEHQ2=(i4L1@&c z6^k|fD`rcyygKrN{nNX@eU*u}pp(v^i_6?$ba@wpRyk^%%$j8B=dVgN`_U;B*a!ov z^&~*moSet(Jx@hTlW2`cetE$eq|ZH#Ot-aF=3M1j3bC2i$NE1p%(ngkn%+AKDMV)F zx^t{F(_-}6#0d^l=uR@0%GRW)q}dl7{R80Hcj*U)YHM1*pQgerL6E9hcjTWJbkE$x z_1aV8cayw3a(vUG+5E+Gj^NOj7N>^kL#nu8DZ2Ez>WXUnBf(BUQeRHB?7^~_jZdAc zP*q<>`MC|ZoS%L|e`_O_x(b7s8zbZnZvA6nC0&jz^rM;Xp&Ceof||-hqS`$mY^G)a zWi#p$Sr~KNNrR+eCe2!@+yfSSlXd^iqlV)Ln7*TZi51OCXnN#+Xgrl)A=M{;{f>ZFSX2~Sb_96rPD zXy!xEmo$f0`LBa1@}4o*Cs^)h%qf14CF5sr?R2ewQcg0OQLXrMl8D!%XFr{~yL9Ms z06E}x*IejOb?$KChQ47oHl><3Z6Fo<*wk6UuDPpq{8VhFK=R;Z5?iePv;0s;;$)V+ zkqVqYXO_yTJMZ2;K*u;B4hJ|deHu#tlyp=4zr)_UTaxIw@gLx~!^=Ma|2VVu3Kuaz zdnS~ya^pP=D^%eGJws{8$92VdqV)R{?Z_Ce(+ZLVf6(#VZ-PZoz?ZyXtI#;d0_EOpIQw97+%a}r_ zA^|LlDbepmYE$36HB4pqhdpJDz0tqp|DvAGGW^cxYadB7Q@k&6JhO?*LFsHcSFG{g z`x6a$uji7G@3ZafGm2Cz*!+jqg6fZulUmp5#r;RkitI9k$TV1#qDfG+KO5ho}47bOx^3F^jFJOxFC!;Ag|Oi);|Pb{wB(D9cu8DG%Uc{%@Cc>V~Aw zdDY-0DGU@}v8!s&B#*PnD`VakV(}%u^|i(<;S9h8q?IG39qZ5;X*JOVq>N=jh?N z@0i1ARSsTcciPW?54g!%hi`7H8G?eHeu#;cCGtwTIq?Xob_6Z^d$KVnFE8!?CNMQ< zKu%)4Ddg162c+y$EtJB)t+3pf1;zbVgtMFhDU**)R0f_O4Y>pCTigKbR5?IxCB!*M z_gp$=?tE5eSh(V;L52fDHEoMVg+o0y0VIoy6p@xwKOx=7PNS%_HyBBg(;{4gL$4f8 ziZmJ7GZ}U9L&&Dly7yh5`M^5Nkhj^h!j_SYAq|P1lycaY9vfyLJLIw5)%`MK)aCxE zLuZy8cvsfXWV3riQf0z%gq%V!e6I2xtVZfrufg=8bK`bhMYtvXXGCa-_uuC4EoGB8 zS+HGSzSj@DQ7)#Nrlp0Y+W5`Bi$G;IUJ)KVSEQOQMJcBfwMEUMc+Lpjyv~ z6NgZ8$(_=E15p|FX5ngvP){87tc0<54|ke_agwh1e?H}X{J-D8d`L;NKvQv{^F&8dxMm;I$XIYwBI6l%TrwsM5YH)7XevyS;!P@?^{vu9H zZ5B$V8s7UGUUh6zfAtUWdBMSuU#-7YBG7=}oM2o7+9sHNHoduGR6InEoLDZtnBfQ3 z`u3H}Lkz#hkL=P|g>>yKTs^S7j-@sAmdbz`5`me}ZoSatwo38LXxqZdx(|HMCQWA* z_vLIS#l^{<^&9hvfGHv7>`hC7;)xu#ZK0wzbb|!A^)MA0*Q@7L; z^oC>}`qtzv)AQFmC(cnl`lVY}&ui_nF|N9&c^TbPGvW|_$eKS`_+mBGXnJ&^Oe3Dl zhC(wQ{_^TJSV&iy5~UweQv20%nByOP<(s`bmE^j}Av`}$TWHbS1$tSWXyQqD%SzJu zh3}ZcTH8G9^G(q%DcZbkQ7gBrEBDk!J_@8;h}l);G&PByw&`GM2T(~Q#I1kb$S>>E z#B>xef1gVH=znSgV*2SgtqE3DxjI8c8L{e{GK4w)rHx`!IA85Kq1N+}$+dDfl%&Cy z)MpCjm@1#`u9tahsXbvv_>$~gJ`OjiY=G;qJNWe=3@8YJYYKX zg%N3~Qi6k3f0{u#WZG#%FMH1#lE>V^&A(c-u`arYEpupzh%N|iPqwS@ipgEJ<-rI#5Ireu4`XA$SWcZ)RlWZ>5gQC(*8MZ|iK!J4rC z55R5-wgb>bmgR+7@%QJW__HPi`LYzg@6-4))}kBBJh@p`yj1(quWj(?x#o;%Sez8? z(lF;(3%Fa2=y@J#{RFLM?hx*1v0+Yc*`Fn%3W~pFDOtE6q-{FRJf!BAsO0feILK80 zeq-8EzU=y z{tF`j>S8vR1Vjt%P{qS`7;mMUHIh4JUw|`XH3$I|=TM)QAS3))zql91{Nx zPDj_(ns`78k;ks#l`e16A@5V;?iL6zJxJ}Hq&+h<^>@Wt!@;g1H?oM3BJ-7eUxGM} zg*tjIArG$Q?tWXv-{B~@tU0{7CK;8{1sEYT7do`Aynf%duLKm+Oph`kN7iXS15dpV zhWcuw=CBUXq_+-qEwNa(!z z5~%Rz*wA%+9QtR0?CeK!>~>5q;a~tHV05-3r8B>!Sx7R6=O4f)Y4V0&TpF$jTh8Sb z=EqjZ``hxvFY@EZAVV-wN0Q?Y`^MdhYSToxPw5!3{s#t7zG?Nr#!I6d|ImrE!kB6d z&lgufi2Lj)ztL=)H3>$KiLjvagRPzs*B-mb;}!cvN@=e%uQgw)<(v=k69w$do79OG z$!_+-X#h$G*d>YM&@-p-^FE|z-_t1q*fn|Dx#{p-deQUaiv}7zonNZv75gvmO7cvr zcdG?j^-%VA^3K)R=*e>`pgKF~Us&rvQ>T6v;I3*`$^Xf(n5d?+PWb`3`v<`F^W5xv zvg#)>@egtQK&@`CQ`HgAX6&Rh)128BNsw+4fO#A=+~z{*oHu%?#yA?;+Kg6Cc4@Hd z-f$?cL+O$!0w5nEVNm*N%v!1=;F@%;OY4(sL!CrS;7`KMm9c9(^gjK#T9$6uH0z!=$jx!&${QMhH_V~Ulq6DJVcVV zBlYlyIyHR}D`e7pQTmt3kz^`p|NT`JFh3_69&$aX{Dn3^`0>lJ zUJ1tusH)lv+}ReLt+~Q!BlfKw54BPmHYt`l%fqcP8t?VJ&0;H@GEo7T zu#u}dWiD7?Fg=DoSMfU|{x%FBP);D(XN*KjGdp^CZYoS0WMEpRZXU@;PUR*xjCcOZ zJm+?i&6E|1c7gOAt}mp?H}_CINElr3EHtCTXvd;?=~#>-wBcNd%P7A}c2B!&HVFYr zG%$bW`c5mv0i?0YwpMKqHhmvh{@m5{puOGxQf)hg!+KrLB`B46F%U8@O7xo)Hbcg~ z7rYVP;8Iai6#q-B#11yqk~>W{4C9xh(t~_WM-Ws*)}ech1UUSV$1f+wu75k44%k9{ z+v|W3O~TSEh5u$iNh?4LnMK3#fKQ5`IXt}|hdx;VX(o5tDwK{()8XM*$(CbPiRXL+ zV;7Gy9^a`KSqm*c1-z}U@4C;$lE)Ep@!^72*1W957Uxe>uPCYl*`Chyg;*|60wZ2| z>5IrCp7ycoCwu0+go!kFRfSPC?qCV_?GhGKs=V4lnhB-YqW}Z0)}sv@5Q?(w9}YJ? zYp}enU#%*xFr=z6lXPf42I#}`1byOe`jZzRu(Zg-Ilx1$yp{jK}~%Cr!b{z}?4Y<=fqD38UIj92yt0P$m{m<46T*J+V@!zYsmbe)R{z%i=NuCv}4wg|s@-XuO zESSSfAywxou|wKCKGi_wp5lRWYY^TU+xGvF9Jm z?xo#iyoS{-J?a>;%(WP8X$tr;iu3{pPa4Iq1a*8R(43Cq4Rt}1<{FVf_O4FP?~Pvl z_s;IXej%Q+#;l)UM(m3TqhM!!b%{h}YuD<;=;;987*`(nbaA@^Q}Ej2gy@?X9>HV% zOc_}ra*2uzN(OpJiEvfJ**5~km1c)S=@hCg3kg26js?lp?bC|V^jzxYNevDW7T8B~ zdEGXdy1uu_#!G&A&u$fug{>X~P_9}Uij=4${A;yWTlTfQkU1sVa`49*VI?y_g zkFV+)v;&tOvi@YT)3acC>LVJJ;1@DU(Agrv%X@rR`~7E|0gNacFj`;&g0*3KW^geF z()jW~YHvrnyQ-BLF57X&lE5bQj=F-salA<4_)f<$ofa8d0#7wQvBU+?#%}6}70IP} ztVW(f&%~Lxv8buPt`!9xqsk!sFaSdXY4p|14Pj9mej!IvZJWW_p2KJy=3l27yh!-c z5(o7fJj*$jZml})F%~tCezi7Xl8qKNQRqPzk&)R_nTZ-n0(-HRC-n0?9K?cz> z4ai307hy)Su9zqgCh5P!1J)B?WcU0{#ia$~D!JF)zJZ$#(!Y_BtouF7$E$F2t>S@b zELiK|jFMJVF7BckdphTBt(302+n-i`SJZIkKWjr4_);&Eel#!TO;q`ZO}N>$k62RP1=ABv>mj%*~}1kkIeKt10rEq-!Tmv+1PL zmuyEZTnubF=&RuNmI`LiSNA-Z`7&4q?qCAJ)r`Lc?ANX~Ovoz+cs`;ft2M`({q0}GV*8yRu7j5>@AlhX#`Df{sT-Uu!heiVJsv4-TWKnNUQnfQuG{K&2MERp`sz*OE%B)Wr*>3 zZK?ORG;C;2@&Fa}+B%x&8Jjzhk;!zIqbsUFx%+_vb^|YQ?q{CwD^xx9SHkka0}Qt< zols`%*l^dL+23@)I zgR$c0gBjMX_4!s51g3sI0wZFlU@o0&=*F2pP!B^>cWT+%ul@38Bm>yS?F0GY;Pg2l zy0zyQKC_jEUH8XygW8HV5qjwQA~}NqIBg!OB|My-I=p zO=Dhv#Qw03XSf%-OQ^eylgDZVvp?p5f)m)Cv)#1kKFSG!`|HB{91t7q|J+>< z6RXHoGkVuPMb9lrBrL_mFpCpiw@BdV%{ANW=nUQ?vJ%Sk(grK7ecRQy>>Fx(mL0R4 z;s=FMZKKK|E9lYoC_3{bJ(-_*jG z7Z<39sspk+VqZPJZyRg%>-|kd(Q8_Htq7_yeQ)NlxguP49wq4+7$A!ufMBmYiu^eoj`ZlLNWz7v=|m$cu)Owe%?)YcHKx{_@Uy8M0g&YIPpTT0dT-v_-b=F=Vx9IL{2|dh3J6&JjNC{llU!_ZK9p zhE(+N#h^_Tx!G>Gl;OOT~IK!!$880<2<&D&QQ#osS=lx5wWS=Xtw7U z;g6xiE$=$vd2bqX-(aUXH|(>`q0FmKev6sKW+y~3+rwtp&ho@<^ko6@p9n~m5zAj^ zvtEE(r#jmgYs2dv(99iDdvnK~!|YIO_9wqUH3Ca3z*B^-7Y6^H$&6CTPcg8OXjI}a zdS`zugcneW^oiTLN=;-&q{sA0lS~Rj3n$*>C(%s>3%91uL1!5i+TpTqVircQ2NupQ{k z(v}OJr|c{+@dX{q|4AF)OXJ(}4A2Eqa=;nRR?U$@LlmjW^r>djG=9Q8w-v;8s;NeV zSt00-UTo_MIzWWpJoezj4zBpx?yv)bL ziGWE&2pdmE#k-2bE0T=sF%7K_vHtf5SvM=^N~=`<5!X!WRttT{i>cc2n~n<-_jjTd zwZqk$l?Y0^C`X{X6S&Z-?87!&RC8%W z?h6+)NMHss#0lTM51LDCI2z@o_o0CsAaE9@)oh45q(d1-CR08wVDXAblU>=9TyaeTzn|awl@ohC5VHYLaF`q5<2cWE?(U#nInQL(Sf0?7V6j?W>UQqBM;# zyU6>rHw@3k2kQ)G1*u$dC48;;$Piy+2x!-%rtI;SWLr3|yLF~dw$m*rW1m}D0%>nY zOSgTdF_kRq;vsM>o^c(1=god8d**PMqQZCNKR%Gs%dR{Wu+i2`xT=J{muAAANk7Ya zHAFgZE!XDkH`-Q50;I4cu`ZGy{UyiB2)A;Oy{F7J%c~BXWrdsg!5QZHh%84&f91?R|dPoJEsB;8V_9NlQ@bXIY0m4IVX!* zp{?76mGzZjhJ#e(tA^lrq`NLo*=boAC99Qpl^ypIU!l_ZSEz>Cc3{(OBCX?>s zX|?U`WmY2V9#CD2UJDuD?rg`+?0FYIw^qXC!qgg#y;{#v^erJM6+q3_P9;<*R1z4@ zwxIciGHq3G=MJ@5eC6a#s`10Nyvx!GR)?+Z^dxY3+&Xm;^z4k5-O4)8jI$ z>qqx*7wsqI3$+wBqkO4N;t@Kt+|6-|ZS88luSVCf%1PAwYo)`&Z*->{od5jaV zCf)+)uOWnA-COq19HYiBD&JBkEJAcc`-8VnP23VOI~^HF44%Qn1f3C)8v&3?d@lBV zQmK-M9FjDV7PSk(S&w~|RV<$|cey-uyW?$Q4lK~8^W5xlONm5?%Zab#>^D{P)y~ST zKkQ+td_*DFcVys*;SZ(YX0IED+nWr}+O#5S;Z}SDxkYBZ14kROiE9vTo-_K|gpN{+ z#5Jz$9-**uMCOwrb%nDW$M*8xAW2LwNmbZ8<9Kf&*t~RB@|IHcPr8tdVUrKE1-?4% zs8u7#d;}F2Pc+%PI4X-~4}my+?ZWt9ImRx%(NfI{yE!|mP8`$c_FV<0n9Qhf{cdY=;BJ)qG=V{VRf)=Ffh~N*TRjJk zRNO{e2qWVjCgm|Kb2eD`*OpW$XlZxF$28t96IfewbBjSzU)Wk(EvmwzKbag_lY_)> zlv^j+%lP5u%OzzpCbpC0;zTno%}UboQRd(8FW4#Tb)to8j?akE!-eU*b?1WzM3+e7 zEv`WdclW^fB#q3Jxx;R`J!e3w20ku_m{FH;ORNU&ngwJ5OLlBvTqS^_GkDu9XzBN> zEgo%zuT>uA?{2OI&NepiRsx&Ea$I#CFn>y)r}fqvh^30=CJisRp{Wcgb7{<|n|_py zxI5@T5D+$CJ@F?F6l9L|oX(S9sG?ZD&!vVfGW+}QuL@zgF**R9m{jo>qIIozRI}iW z=39IA(C^YQB}n3I<+e-il}xc=Wsv@DCdZ#_AExvK;=lg`s5qdlj05{aTxyhA!;aWO zU0UMIZzXe)$)GHZxaRO|4Ztqjd2MUGXHx{TX0}p zS|ug}d8GyY$4An^?^AI=XdWpwvhx3MQ8-f6MFe82Zi@fKoxvItq-|UNL((EUCHkr^ zVE*ejP0S--(dM<2l`r^3{{WeeD^2%F4*ARhQDH&2wG6bnxp}AG{MEWu2`P0}3XC>JzV@_?$xiuMz4eF+FINH)+DycU z;F7hY8r0ptXW0xzU-k{w;>)QK#%*2I&1&pVn#6j$1#19@J_xiXB|%%JO%`5Ttm zejHTuwnd}LjN&y$dvh+H{m%=TA{{L(imO_7al~cQ^j1#dQ>wm#oOej}#Q8>lcHQwx z?GNH8espiaNi~@!1#{Np`0gsCi~B(xVvRG}Z$x0DiDmf(lpn4Z~!e`q`(>7ZApJH$;oL*E^+>l&GC2b|h3|#Z3B4b&SdmUzMu(9jUf(PuJ zg|+)&s`)Pn`RT>A115D$f*=ex3g@KR|*iH~&5`<2oQoJ29%N|K-W0+AxTNI6<8iLaj0O+u0)MuxF1WGOVNKm@9=>bvwx2` zVN?-L44`8seWxLyDH{RxMO+ygWN(?@Ts~iZ-h~jl1kkh0ou8LJ3!X=w!2iseW&BJK zkDu^VcRWioNt;+09KWx#qqPB73pdoV->>!YdOz1VRw+>{cwcQ?NPvwr|Rkd^1+5oVz&>PAjlkwu|1*z2rJ+2E}z__%cED<~R>%S;^9Gs5h zG$4+4z1o2JEV35WX@USMN0AvT%0c^ZqdD|AZT$~G zL+YZvy{GYb}kIEzdplEP(aVuZZSW0$F`GcYcE4DRge#Ru5bZoV%;`&;TT*z~saiI?ZQ3;vj+@ z5FDs-wNOswILA&`;EvO1wrCMcb6wQLg%dfdpWT%xqqPk==c=ldcmrv#fVp`SCmzmV zefLpx>#*rR5($bO#V6${b6&vgxfP|uAk%kG;_g>iRM3%M90lj}WLBdQMdzk6Tr_qp zj$Py!X}uzrQ*+1oNFLASo291qqH(ksph?Y3%uK$Ps82{M8G}2^o-B0OyT?m&%=fkE zj4PQ3vC9M}!i;vkbMwry-hTLmo*oYdBTR|*tgP0>)0U+7G?v@2k?KaesA)!|lz0QK z8-ZG^Oi8LIqh=WeJ%xf5LF(Nv-h_xEbSvxHL972M7C0MlKZ-vc3lWROGD>y&+htia#eVC#k zv5U*NXmR?faRd`r#Ws9^sNFmm(tEpErIm5$YX}K|9qw{y$;d*-k|w$nxb2cc4}X;` z(+A3~F#js9XNHkoj{EI-j*B5M#p~UXm!3^M=18dd%`EKO5Dcq&4Z*=*Nv>8l*WBKD zd1^Q@lNIkQ&pSd@*Bzrgc`sBkPvP5;iD@gcl@gMSv4(Mz_hSwR>Zl55aa>G@x2O~TM`aLj&2}=y%g&NU$I;SaD^7pLy@?01Hw05xE`O#3_OnsAR+A z-ucYa-R(h3i=WA0x1y)p%o@TXD|jaM=-N`a(qGDDe}-y_TzCZt(uH*g#e1~-z(3)x zykz&r_If*Q34zRYQ)AV^6@)We&#S^o~+bKO2sP;x#b}0}7v$ z_lh4M@JjXGASj|jWp$MD54MoMS2@&qdsg%q?LZ9}bX+#C*0b!Kv(czYTerhp;3Q}O zw%v_lov%^1MPrwI{4?uJPQn&DEkS&C+%58UW|5Dvl}aZiD*9!qVqKITAFZ)Jtefe* zB(i75Jdw#OyrpqxBl#JWysat~<7P@gXBvb%Av;!lPBz|p1=ToA$uxb50J5NJug9qfTe}2qPI#E9!4^}8bTsmnpfNU?k#f+lzK#Yz78otnLq^_ zH|y9NI{H(w3nX%+=V+bhzx6Z&!5h}fV{|Erpf{K2Rvr3e9gmaS>3**~6V}J-5FvrK5)>+WyTNhIPMh0?b zf-`R_XT}A)FF|k;@swxb43gE*U-8}7B?x-aS}iRt+JeLJ2uwud|B*4(kawvB-Jbv2 zm0JA+{7^5gIM}kE!YrxWdCRruYAE2lGi<%xCl5Q4jbyV6hwPrTtN~Fwafw#~O z{5t2IJse4)aJHTqmwP<~64OZ-kNZZHZe^|Z(@*a2<+la6BMmKNIJQ=3%-w%NgoImo zZE|U``bE*kV5Zs8jg=y*G}BHj^wgtX&rJv+#j@1#8d0uZ z>#W?bgymZ@67h;VOX3|*8isi0(a_Z>fk~&d0W#6h73IfZQGIzDH}+8<`6Ogh4DZzQ zX?$qg76WRLh=huV33KIaX0M8CQ)^GNYV})7TQtvSK2my zbN}||9vtT*gAMhsH}O`*nniGtJHbQupN!;;U&ZG0gjUZDJu0ZWEdH?L#tiB+k0$o@ zkut1m8fO8nZypTh92jG@6%gYA?9;a*PMGOja~`Mi0};;w$!|Aa2%aX`AT1DO`b|F% z1@{*huPzLPuH=wkz5f6P?*_X)4-QPbVp;RXM(Edm1}-#&%YlRxY^>>hm>NJ#zhT;$ zh&AadOueYF3>?G65MTKvw-7V?7JCkShx*631DT)x72B5W_B{ygRp&4GO;lGNrUpmY zvJNEN@B)dok!jx_GPTFwwlhzwd$ZIO45zC5aS zGN^Qtd%#ov4#Za1;*dTR3qI0Y)bkGH|K1Dk*1Elj@l4W}{-X~PANrj%m^?*ZxcmM22Zq33y;L5UUBXV&;MZKd@{ zZDG+%#6MXlVh7&HUZ@=+%zjy-dntrp+lqamVC_4FaO<6 zuwOeehH27ZTticTTXoW-Q$Vm&{?MqfQ~VLvqR5{Gk#p^3CF#M1r-M=&IM3&UAjN`n zA+*M;x8hPTr|ZB_c}J3>{TJD{phrihGog znMM8?T<>-K&`ob)lNFxLpd^-GOh1A>j~B~c^sz?7$0|QOPpGR%>U2LIEWd{(_yPrp zjhkaLc0GAvSI$0{8oUp_8Xbee!^@SLy7uZ7>p4$iDHI7q<(7J0Bi$8)WDp9W5~dV* zpy#F+(wS3wi0wrwHm2_z3qGjMG`0BJI73~6;3lKYto;g-kT0#-qT%kdT-uLO;!hQf z(9!f38UfrA&1@Rs7rh-xr(^N7sTVw_Mu#u{+R-B8npgXup}}+H$WgudP4`@DD3*5og+zm~fYF&( z2uaJj|3O-Y+#hGzA1Wx_=bP@dr6T!XP1VhZj|P~vPZI^#rqJ@kRA86~l2R%1+CB5G zHAG&u0!z5fOdGvkj^St~H-A+7e;eAs-gPFeR#xk+u6+^o@$LMRnO`0hmiq~FF-t%o zw}I&iI;|tu(Ns1UWE%+X;F?UMZ8JHLnXh2twi9*S%NrXWh`mr3ARZyiMH2*6peNJH zY9K}lIzJ*5BT=R3IG!J@hOVUy*sW(5C5W>KADapr6ou_nDt#fMA<}T@E)(pfB;`-# z(=t|igFL59n(!9BJe$~3BFEOA0Qrd0U%$;HRw&plA`2g@Ew5WdbPX z95a@)=&0t+Ap?O%OqQc5m!?9VOcB`ZW?6Fzrv19K6SW$@UE&E|5w7>orFySQZ0Og~saeFgrFEM8<-Do}ClMdo~8vz?EG_<%iYN#jl%Y_aX zrk&HobJ6XOrAzt#gA}?lj>PQZtt$D{S$;O|qhS6}rm};u#Btsfsag?e^iN`}Ji_bZRBXL&l+fb;HqKsyV zzRE!Hhxxu|Xt{pDvmfFkewXKZYOv{WmC)~p%xM`VQJqQnJIS6vS(Jjt!x1!y<)Odr zmWL{~s4MQ!MU4vGeO5u893}G>sk$dj&o*+kLk>Qf2&DFMGOo@$j0atHSNJp8ml`gz zCVs*au*u+vw=Nn0&#v9^@#_@(Bgcm0RaBa=TqF4)FM8aRYi^mgwDc4l{5WI1uJsu- z41?LaetS!jp#-MXsUCi10F5uyNsR4Xcq*`W6J&oNm+*&oxWt=wb<8hO^}|HasNc3a z#8iIskc?;nPlAi{&V2ro1}XDnpVLvq$z?O-ge8m(sJ$<*{4%$nrgmanRhN&xw-8-* zlJ=Nk`je)a@PtOY;xvJfB<9A52yvMes)9kwzBdY9Q}&?uCu@*wj?bN6`k4sRF9Q_A z1^_G~rG==wD|u2l16)W5DkuO4$M~Qh9luH5FKDuFQ*IQi?91m7$1o*HoEU|AKgBQD zX0^E^mNPVS)02zqsws*mS_b)$I328}g&79MSs8td;xl$hBHu~WqZ&+KRND#-STHyo z$6(@sYJIcv(9_?X$%_UTCHn1XM~qt{3D7#_Yu^Z{9NZ4CQIsVM(16vMVqF{5;$JYk zaOzsbiU_1#Y#1TyO|9RbTX5x>@TD5$XIo5DczIEj4fuH8c!=wd#rTQqv#5fOi4K5P z-U#Ckv9=8Dl`2!nfsPM_vQp(cVd1IebjiV$ONBV9)B%5o6H+s^m#cX()mhNs4xMqE zj=bi)2)R^AMGTGNKzGI|uJrp?|FDRHP|=oRtwHG&Wf*JRYRoTCYscvqSm9@rJNi45 z!vZbW_Hn8*qW)2Z_U@Gjv>xz}XB>^;m_=w{o{cW)NuZ$wO=TOcFmZ?W(Me3a z(k15MyB7VVe}MOK)E`Wv!KH*q8y&sXVfl)O@(`nTUtyLU2NB@JNR@pFZtzhfUuCSi z!??Dm@_sc&-5|yq8)$*~gMr&fK%q5oOtm7H#_251Z!fd#L(*)}y|1}~5VU$ntnQuq zA(}HHyNkP(GkbzEu}itvYFo-m6TCLh8fkaWJ|H5>}^uAu%@#s&}vAa)rt0oaQ z9I5iw^-OasKdDxe*l?SR++Swtre<{olC-2@jUTx!95M%H9hHRwf|@u(NIHD1-ub?+ z+=5S3a@|3e%Yrg40-*<5PKNfSx`d^gnXIA+>$x}QrrHjgOz*AY+jKZWypkleb6Xm4 z{-4&~Dk`p^X&arv-Q8hucXxsY7~CyDa0u=)cpx~zZGyW5w*(39PO#wa4gvDd`~KfL z-^IB*>)hW-Q6`+tGk|hs!E9f4+x*%wOErM!T7l&UG}G+nq017lRfwwXN~`k ztkT(-828vtVWR)Uyj2SG#?q&;`GENWwrsuJuOg~-u(h-;e`BB1;) zJ-4lAHuO7*ozG@IA5|uE_UQL_1830-mk!^yO=ay9Zccd%S~j0S#hHi3)ReYv{X)*ijvL$L`t4jt zkGS$9tv0B9pk>}hCX#Ro0mQy+8v-gzVu7q_B@YomR0C$WGtpS=;EQI<@Qr~l$^#$m zGo)-f$P-7`$R;&UO$>kXA{jN%`$-=S4+&)H@sxhvywWQH!2&MY;*>$8B7~L}=oGr8 zj2g@BN7_${{5_V?igd;)6=_j^sx%jJccoz{DsAhUB4Lp;7SDjvknwNp+ZspT1OVK( zhC_uUCo@DRWBXYZMG}HqBx~=1DV5V|PjCi0iOQ>_*CbR z_?JVd_Ze<3E@F4j0nwApHHT+Sj@r`ncr zg{foWq#8XJKP8B@i`YzNEN^sO`j|P@(OQ>z=!{Q&bdv9L!oG!@*6-tNf2EQ=mb+^5 zu9iC^zo;61HUPqdET7#dPU zXdC0pY&HogwAD&ImhO3t*K;dZ!YSg0fQm*<90F`mLW}F2TmIGaNdaU>c|Ptzw_NHP z=Xk6O7wkjU?qcQVX7$03g&BFo5^CnKaKhlH#Pdm%qvTkL;(#y9I;L1rbLup8q*M!t z@%L4w(1h)YQuxOr@kPAUL$5hyEH|@fLAY{C*!vq`M8W8{9k6zw1As+qy`_ zJb66o%9?a$>O3^Xa&w&0*e#D6(29JNNw+#g^d=H@?v69IGD%&?si;hItid>%X>3YR zzWq`bX@pJ+dgm0n&~RaTj8gE06Ke~n#MH!rgi~}sn`IReN zLbY3Ffjg7}w2~%T7jH*$nj&%TxQbh{aRF%V*~VFO8-GsNW#3I3AEc*-1&7r zK@(ZuyW;r}N-G_ntE1p9wh%Bbu`r!^hD@kBt~?>BAeX$$c=xkSN;GuaZj!{Z!^wRu zX|3T6SJN^N5|SIz!IB9@6~B3%(cJz5WP;tZifdXh+<>9whj3v2XS4(e{r}!roJixv zV3+V~@VQKx=aZy^Fx;);8U&KbKSY9AKa5W%$$R)m>BQ(SaAclJ!zO`X;0AO z5%^}fo=hkPNv&{_fsNS1PGy1>U%s1AvY9)@fxG#-9OZ#48;k{hTF zd6=n=g|$4ix-v=r2N;WBIzBVG-d72Ny7q_)_TQvUwUu=s*R?S~Y7A8bSR7Uc+TUl8PBE2z}bUOb5ekz-VA%dF= z+}pLqZ{7$w^F#JIeEtD~Q@V)>0t_FB*#oHDgK_N--Yl029*)>%%I_P`DH_BN-UzYs z{sCkHrvBch9o}{q|BYq`!_a7vie(|wMi+0d4E_RCv!+AVe#?()W!O)#Fmv&zxb9ua z6z$s0#iOpM?>|6h-qP!{$}NTF6zvV)6u8fs|8D;L=f#TtH_HS+^z4J2*4_!yNu&GC zS4*SYY>{GO@%ccq8IOXNwGdcWDnByS{Zr^Bc^^hb8H&`o|Ua_XKYP;roMWWdi`s3=BCmMtJ(?K zGW-1xFa^UGJ{&oIA7G==E$zs)yF~o{Iw9+J8w0E2)RBU9Mw%APK4`G`Yhz&E#&W(W9=my= zub0hB+YnflTlCghmDjwB;tSBRaX{?5vD5ZHz?Sh!$h7*w+iQ(Cbh|=1#r5oRaZ2hM zza6&Fj;8G=iel%;{~6TA)LWRo6l_qfum$-|O^T}b?pG{!Yl-ybcTViR>~j%CWyit8 zguUBymI@w4WTsd^1eU|2fs5jU_1&pG@ICx&Y5Imz42JL>0nj8)JCPqq$%zSI-~cdi zG6M+Opojz-@D}ZM=wW^0pvB?ZX*K-$w7icHEk$L zLuPJ1GGn!gg*{#ah@podllomC=pP`(>c0X{HXlg$zc&j5+S|6OlK8D9)nhQHVhI#3 zJrwnQEI`FX0}iM@0Q+tl6e9qvcryc=PBq{xGYA0x{Vf(&(e|H4 zvA!@rFU(zg;51TyGNlbrwbr1Aq-sSsIZ8AQ&qh=yh*To)>}EVmt~zek~)u zr99!!M4QLa080Bb+7x&p>>Nc(ayw~PKd#*Y>V3hqn{d)7;I=q84^={|U(Y2B`C+hR;>hH?rL82P)^zlWO)y25_MeVU}-*u7FAd>@j%4K<-TVfe= z-9+$GgjU0D3wCfAI{S?9B>)-lsx)R9A`=vfG+&4+)fU1Eh%Mn(5&ia34#(=IKq&)7 z(n^wt$4h~rG2^7sYrN@4>=BL7_I9yNj|=AWYrBM%j{ z;A78^5`-CvZw)nmmPsHa4Z@adY`N?PH>cUrgq$BXy>cVS&x=~pqyD`(M|w$Cn6&oT zbI|e^FtfACuPjtFb_eYG>dfTZPkmfZjAV+h&Hn&1HSaIKyeUV)biQ-Qt)o6AI3|z# zQGFwJ99jlc1)zBW7+?~xrrjVNG4S6)1thZsiU5NxF|ybJdDI7hZvT;lwl%G3csCd# zFxHn8inLwOq(Mv?>qY`ankxsg*a6Cb(or|+pjbfMv>nNcksKaO;EVvy1OP29_}woC zA3Qp=01xGJ%#wk$l4ZI2C00obqIunwPu|ml0ja(G^))rsDqAwiQ5OZNv4GMGH*aNQ z8@4+do$n4=77bNDuxpWbSzM3;XC}J{&?IoBY7A4ZFDf8365OQ_o)W!DWA7l83YS{z zq`fp{N@Wu*()=f68q4Hs|A)dJgVt`A>lFJWqo>RRgy&ema%>16LZrA4Az^ppJ86k1 zMShfy`l6u2=f7HAIU2Yr*l-XU!Nsj>0xS@RLtG;%A%qB1ZQ(sV+YlVwbU&j;FY@TVUy#W2 zAT@yC_33C}vlLxEn-r6P5S#2cvteW0LKSzZcvaqdPo)y4ahUbz58pnV%>P`o-&~=^GclBH~|7c|q23T4cYI7g?@!*7;HN}crEC^X@Hmh_^ zsl&S@APhtAz@GApo7P5{EotRiP?cUsjtDjz?$d(Y;&+Q6@MZM>%AVE>Elf|D|6irG zFapz4?L^M9lhKm)5H%8>@bh@<|a?GOnH;sGqhP$b-quhTqju+xdzY&mjy0ROZd zP^1tdDTni!0S|m5WcuUQgu=SfR;t8bk##<>HQ0@3fQZ*mWJD@nr!}{TN1h!_a$neQ zw-9Un8RaXItE6)`YtH1Av6W?!iwK%X1${I9oLUN<9`ZL{%W`>dYtzK}TN({`E}Ayf zo7x46O+kkZ!*Bg?J+}h@>!1{^u^yUhlp4EwI>5~og=aCMFX%eyf?7aSfhklPeiSU1 z;IFN)^x;VZA>r>gK$5HPfWA}NC|H68NE=w0dnsWblk0J!3eQ9`72HJaVh6+mlnR+4 z*<*Oxy7f@1vAX&Me?>`3o&!*QT{&9v;LdIFNIa58PeC0yQ4MxQ-6CUXS-79?;j{g4d!UPS+0kJ-EwC{7YxV3QKP9yYm*T1G$ zQkKXn4=#wu2KG3dHVa$B0KzC1@)kv6+#2>3z$&jwGky+-2Sz1udAVf+BcmTluIKMb zyH0dmIZ`P}=^>dDeHweh&*@m$fSqigdP>CCQzA)#;8nw4`O|*4K}l(;!3J?fS5+dx zHZ)M8H{fo+P!%gU79v^kDEALQ->>u!(DL(HpyDS=CU%slqSVdOKY&8B`}||p40gon zr}dOU_h<|deJ~+dw>x%`U409bx@YKLDdfB{Si%# zdXRi#7P9?0oZ4S@qTy?}%ZVya zf3~JRbG{I7qb%AofjcZRa1TNcch*8!DV#t@aat7^-@g8bfolU6mBV8)^`*m9M1A~38RU@S#OcrBn;Sw=@;0f( zKm8y_lkUXTP&Ps8AR)~ z9gjA0Z*qL^_VYotNZ>lJq)$#bMF-E~02BNhS>#UoDmpsO%wplJ<@vmhMl+%!EQm3u}`>2eek!pWv2s^ z##D4OpNHqq+~WQLstKn8$g&oC%YU21_4*)W9etN} z+#^rrRhpux?%H*B3SpJV^q@A{)gSdXZ_*GvPi8ifnE71WMfGQ= zwzy@BlmC(G)6-!wBm~WDQf^A^StHYj!y6J&?xrEQq0`91v)IRp^5$Y=rB;g-{#uDg@osaR-S z9i_Y$6uPU8pWJrMGc>Pj;?vHag8DgB!9WnmSjD|ER2&Em+8o7>Qb~AbDvKY~Iz!((g zF`+a}3g*YiqqNp+ml;0MFh1}z31Y^HrkzxG8??9CmbD`p-?UoH8Pf6?03cjT!8!^H zh_z6Uk3wcTI?;$8Y9IjGy8iwBQrSp>hYaEeN?v59{>iSEypGF<8M$5;^$;H2tYORU z?xRo_{1K5uURL4jQRp=`BZQIkBfJ0Y)D1Kx6R?YYARS!=J9SGyRFHVnly%r zzd8`o9h+xvWIwl<7x<+~sCv;$xqKH{7($T90jR5U?ydrmV zw^QY@tq&*B^w7g$Gm%17_)l24ECaA-TYax|iV1B21lU6ejEZec-KvJF8aZDg6cm zu3NI8gduJ8w{cLKY@1t#K4~xiX(-Q^3KK9K^qr?V!tY)}e`Y+3%3~R4l-EE5>R#(Srw`l%WdwFY=`9bUBO<=?g8zTVt)cV|MBv2dA4c5IhJ! zOc8p!UE@IJXuK;*crSq{0k}1#-s!MkY+2;kx^79L`<3Iu+bi93EFv)U_^BsDlBuyH zW$o=6VLj0(K`G(%?tMYR8`axrwxgrt&z8!!^9`LItAYzG9y45@LNnv>BRU+79R$r0 zoKA_@L+ zw{=0)4TV&Damrd)QvC>R;9kj1tYSe-T8%pQl;bVg~;Y)aZSj4PK3>%eR`mP7Bfzwz8 z@x+9&?{Ym7`|3<1lHW(_QulxVk}wbuG&7n~g)=IH;9c1#8+^)9M zV@YunNEwP!5_!IAV_<=te7r*`R2Zk$W3DTXIKPzwm56A6*L}E*ebUO;oEOz z8@;cp3xoI4RR? zlQ}&T_x&WeUALcI($M_qxdsOw9y(d+!u%+VRffSz$vMFCAlZp%09TD7fl>!Q^Tu_Y z?UF-T+@fJZ@`y*Y;&#p^si3)^{(#QTh}uq>_^lF6Dape$nuql~cE=t*e9)nfHW2?I znr1(*#|_RrGnlPlHUsdnA@oc-JhJDY4z%&LMJK82%@bf9CW^a~b%WnK)eI2FeN|)J z6h1gnLf~q`$ld0t`cM@Z#nX#CKvDC)w@FlXidm&D zLKr8pkX1c2NV)rCVLR<~QsLY3_q)?bCE1s*B24hvPP}vq#dmk5v{U5#d;Sn`hUwn+}lmfC(t`@XD-g$qCOJ5>V$4aHQV&py2URDeoI&!;uaTz?uilz@QvR0cf$Z zFvX+i=0N?&rcJw@^$ZV~Z2^6#i>q z3btx&G1l_cS^^?B%O5l409WgzOJQ9~0)Jiatpo*;BdtLO0thn{D}A|G6tkgc?Qe|K zB}kXUV5Cfr+}6RCI;&WVP2q>ve5%lMTNKO=7S{#Z z%cqlmpjdPvS|G@~rE8b;$n~_E0MpA5U_Fl9SE-T~_%Nl1@vP253wS4q=eM&NT0GP3 zcaNsAb=Ms1htlw%r%(-$=qZ0haxO4GuW|;cfxqS^jnhwY^DD451>LK!a|r7)>!|Cl zf-Xps<=5<&y#5PCD!8950oXm$h~$tNv3JKmEGc-@wQ)X=c2MK{URn*lFWp}c$tn^1 ze5l5gAVyO1YA}W~eEStf*>HwWBzIiAtKabAd^xq^M6(2yHd}l0xLUxLd!#;4-%*TM zH6HKP7bE@J$xY3zA&TvxPqs%TJo+<@-*(sKk1PvO*YKT$2<@EwM9mnOYMz%(P}M6wp7rqXUi;QW0WH??QD;$9IBr#lK!lhNz=T=#>vvBCOr zc7$NMYtxMqxs&7kCSHiN9njgJxfEMdQ;{a_te2_~!iXI;RHqV}lY4(AeX9BkKSfD^ zg#NymH*@bN8FBekjh&@^e=;@28~^g~b+n2lmI&x+UUk!2s05z=rSERL^65BMdE}HC z?pI176BA>3{A}yEKTOXYahfSS0eh>ajcd7)B ziHnU@l1d_+QD31YY%cSTVxWP6qrhBHu{X9Lz{tyRx(&s@n9LZ^(2Q#-xTne^^lZV>Q@Mxpr6E zKNyUa(cg%Qwk;?Onr-Q~vfCy;UmvgBIsof84E;2Las261+zxnUVj`t>)6i?TZKuer!nzhd<&8b#|4;@N<_2MBtP6->$HLMr7;QXA{x+g+rS*!_@gHM4h<%Vm@KzVzRutgc@rtX_K6xSJhT^zo*dYddq)VTf?uFM55(wqim>Hhaz4veMVe89 z#h4sAfj~+aNIon*4@lKVnPh6U;0;R@sS7fsSpOEA?2ToMYCGV_A=s#>d~&3EO_X}) zd6u-ZOooC1m0Ur>fz5a!ie@iqRB;sTp_tG8A3)QQBuTN=jRHxWtfW-jL!GM@P0byJ zjT*+TbfWy#qcuApx`de9fWMNdR8oCv~=ZTwLhH*hD3lXGQ&% zxp8|4eW-et5Brg9@0Eth=rO1SxY8O7LcBabU4Ilr2*k^hNnLHWdErz{RBTf~lunJy z@6y5-?;qm9MAn{1E5Bh(;@zq9Ka}J=JZ)|G6JbNHv}v0>J%`}iBt)$5lhgH^;vC*G ze|HnhKli?B#XF1DjJ&YYj1en(CR#3Gy9(;)p~J9H)Un1^olGGr+;2ObuuwqpoKRFH zK07?CbigaN<$D%-$b$7t=tc(J3`gO}Vm0nzxccuSJ^nhC3N5^3I6qK*(Bo4GM%U%q z&TK)MAvh$2unV`bU8L_B3w+4Z9i!h~qs|$N&1_P|*m= zMXi~?9T1*nlU!_duCB&|&M@rRLlOJAzg5Bxj!1;kx3rCP0ATRFHT~_H+<>4drA=OQ zE&DDpOcLww$CJm{YpJ!>y!77ajo`CKO*g(3ai+qu=s-TJGEO0Rk`3wOp;R;btB}E! z^t2GHFQw!6e+zFIl7%@^A$%*@`ZV_fStMJg;2m>L1IO%Q)P0VZJ}sGH4_MU4?MB%L zq^eRAgEeNmvbZAb8o%hie*o%@6aVJHm_XwkG>bhlC?w8yZtP8 zDZfv&CeH~&*LB*DPh0D$hL2GQPu8!$cCmKO3yx&$^SSnu+%}*S@Jt)yJlU4vq1k|o zU~JZc7(rtgi^WiZX)|(PRr04RkSar1^dHixj_lYROwthCCaFio7q<$dLg&ccn{PYu ziNkM&-Eupv(Q1QP_S-0+G2`Iv&CnNwm$FOoVE%7xhk}VHnr%Ca)%MAxcPN%8Kjck< z)LEAk%`T(+J~vZi^#+uy%#4X}eimbLxHtACLbv-S4L2AI!Tu22JF-?BXi%1IC+u({ zGC=?u18k>oW5_`t_vMks8e$P@T(p=iZHJK!|Go++dxRD8&v#lpf(C*!G57!H9T z65HG#aD2uU20#K%ay#ZH?3RN>3i~@HreIS@AfLj~()8VA6#&AcSsRsDYI~?7j6G`X zHV6}?SfZ%s&wbjieu>w$H=5EuR(hj-6r583O;m>97Izi{vd~|3VLK30vw0&N%B^ib zVvVA%k*fTmh~-QT?Jn#-^^(GUvH!STO4Je56t-?G>GY+kb=qHFO0tOo#cXB4RFN|o z#t1L2v-SO%#CAf>an0kGx?|d@`;wmfD&==(3~49ZW@T^1++EKpqca{pw+ZEZS7Cl2 z`};w+Ngi+CFNwmb)Il)c-HHdnc3w9R3d>I|MTDcFzC`a~e0 zA}#|Iz1Ps08QKR9jPhvu0#ZukgW(BlOX^ zH0PwH!)vW-(&Iu}0$b;@G}pxK&T{ceJ$ZgEjiA4zk3p;X^EWlSpnzv21<6RZdF^R?CIU!!%cY3?r` z7qAX=FVz>>eo{1P(^vU>jlZOPuP;O^?! z*3^o)qwthPfHH{W=U+?YP^v6Lq3M2jFd(h3z zK3?;4k6ghY_%2QvA^n9J4woW41#E7Vlwu6beB%dVjl@h2N<|tVy%S+_3F(>#B7Z#< zF$;KUwl-Bl-ax*b0BG7>x;4G&hMYMmENb+xX&}C103+buZI?MG_EjL<(Zm8YX=!kj zUL80}j4NL3;HHzD2hSaTO8M>-_yfXrQ~m)?6aABC3`LmuA>~;dp?U~*xx2xh2I$xZes3hlT0bBW#sm8nxfD_DER~yi|5$)6@=7{H@YM56 z_Ii|Lt0if$CoQQ0J?{C{@pmwVQJ!2+hl=uzB1$UyU2;r$XS<%F$Yx0NQiX3T7X;qx zLvNkWTn*j^1JG@#gt4ye9F~6P0$a^FpzN5I7sHbAK`6YT8;9s$q8A1w+`xj=?@nyr2@?)Hp^P$Y;6SL6E z{{E_R2ce}5oPxFf&tq5#0n?bvw_Mn-IVy8C)gMd~02(NJ-ADPvbDxmPmxKaT-2y)^ z_c9&HGURQ?Cu8{y$g{V}63=GFbynD<=%c2g&&N*voFB}v_Ym*r*-TPoB8YOAq`$gL zjIPDt0Iiwi2bNEPE6&W&%gu78a7`!28Myc)J?3p%;^{~BZOhF9HQRIXJab$`iA7!= zr!E-coj}ax26e)g;2r%_jrOd$lkA%41pcFggx}DG8anooygs|0-SA`4SGMrkW}${9 zj&mAzdi=6c5d>R=U=Us7SFPpZDVawpo0*eEpUjmXHfztjmK|KkUt1P9EFxH0O$mP| zxV3H9=S}op-noZa!stM0f_2VsF&;PGNQ>rOaH?GJ>S?e&O!I%1eQti0m4JRX z5#}iHFfRLsOpMr+;0oKb@R-uO6>Gsg!b>O@Zu?)npYUFFs;NBAM zJjd^KVYCYByZ3q)kx%dNrwz{9b~`>tc9&H6cp_lzCeQxxqXI1jDtmmg3h?=XvnfV* zM(?x#g@@i-D1+?h7fyz7rSOm0FNr+oK~)RlQqD!aMg7iYXF3|Flq2Q)tRqgoeJ^5H zh@~zck_qQ+FM^KmO+yT3o74oAYj<^fgh_V`RJGgAr|4(I?fsmq7;`EVtkQxNMCzm# z?_#_-yq)DqiW<*lg+Y{3WVIUJl65mKc#izczjspD4r-9M_gjl(c1`37w>{T}9Cx0y z8tDcPhBB>0OEWy%ZqStRbMF9QQh9D>!dG8>``$KoPXE{%B*k|&ruY}k=HeU2FYjd@ zz|TgKid5m+(6&e|#%;kRyxLbtj9e@Eq^ChAW%SZz6_ONaSCi;mduf- zwFA=1Sc-bgLTK5Iqt&__vy($z+9(I@NU5fgHE#*FlXqRlf+;i*K1R8V`D@p=ZAnvo z1sWOn1djUOSCM-Rfd?ThL#WC3wwQXmiSeU;W>KZEC=o27hsd|FB*tx$#k}V25L**c zYK*Gu+JGgo+_{FM!34_wOc)I(jI4xp9`^!CpPJ7j_AoXGEa)EpINZV4y=$Oo!_--T>Y+};->roqS-D4;&b1VF-# zJZJw-mv4rEx64V^(c!MRxMxiZmgS?CMRdAVg@$OcgqZ|~{+79dDSiVu;V{=CQW3f$ zVnFJY+#GBZkRWm(uIsm_L7KQ!1Nd~ zdO%2W%-_NeZ2a{h4>5KtA7L9pH%q%s?Cix2lLy`|wi!9kvSTvcS^sYWZM%5fgT7{tv%!?UbkFN#h9BsTcLAe+u16%m-vr`Ss=m2}AA4LpBLNS4mctQq1s(l<+MI!2tQ{}6wv3B zQ{9zB2pYykU*KzozD>Xj@sp;keBw^bnNDD1CCpl8wWeAwUKq?vlnxG8MQVATS zX4al>!iX%nsy{piaH*u+{{&RvK!^KG@D+=dJn6KzxT+NoeBW{_GlRw|{VMWYC zu2h&Yl$WZsRHJBO=o9i}PvV#5T*EBDvr%-zWU^3&yINO{fwg)2!!mCO?=9@Y) z+^K<+9GlG-X*y(zY*l1M)`NN73HPXx#r-H+Mz-=4OurAoXiHp%F0jvo6deXN?TV+Y zCm5uu3HaP1H?`eTLru$sceREH_?!_5?vCw%6P(jpe0QIWq96usA2AY&(jEh0`|Ry%SS+|)7o%O`JV+tHIb$^wt|rT0l|hH z)PtGUOn?u15c*yOoL2|D$AhP7gm$OP-{s;kcnDB(XMX3VT`H@%KQ(m=W5m$QAv@SwH94tv4Za|1b*}+xZG~!S9<;p(T`QlwU+~l}> z3eVY6c7!VS()2XKzFcc+oBx7$Dt-R02K)n-EBI%#KG)ol66zQnibLIxFQLrj9^s3H zH_UKa0k|4je9~zCm%il>WPo58E;TC~z`|UQNYL|)zh!zE0%y2d6+gotD=nUwS#YB9 z0=gUqqbOFAQpWFypkbpo$HHj6CeF3Ql*uem^%EM*J7%jpUfqdS`??mpS&W+|e4r(Tr?pGUwvEC$82C&U_V-=m z&$!|%B+2f>d44WsA9&8YJeA~mT$9{_+9iRJF8g0{6e6bqfLZgnEnt@UCk=Q z%2=jHI=Bh4-yCH5SJPI{1_!p4vur+SjNwHYv@gZjs&i(-;k)J*)m!{NoL&o{WSvgX zR=T2m%e(($ z*V?!`^5RDk8fj==a?A7NtQf|vF#%#H=3EebP}HR-82SArhN2hW7L2%!;cse-`r+7e z2)u3&C|L#MoUU_zVvS+!^5XDqbR!SU3;;N}`Q~R_71K@oS~kniHnIYk@_63PwBW_} z5=XHYtDO%*Ro^+1!Z!@!8ImP>&3`+$nfpZkJ!x5!OhmS z#(?nfFlJl?YX_50Q1MdcJQbiaupOB2NJlgdYc)Hwerz|FxKAm$|qq3pw~r&|NKM_SH|S%d74 zW}Dv$`u-@75?ey>srf^A#No`9O>k-7z^Hw>u+v*U(4wHBPM*JzVKLyMlK@@x&{wGU zwq5=~ZLc0}_t9cv561XHS=k!FC`5%nXDzX1w5Kq;w#pc59S@xYGD|iAw?jzJKA0+? z_R4^UqDXfCE}9Qq;H)~3wf^S#P0@RuVQ#Lng1Q{tzL&zi=0Id=oZ={B(rKrd-R|U} zVca??*%TEJ5{eP5u;6W#&|kJ#`NE$^MWl>^A$e52iy(!A{CjEK$FgRXuk;*me!^pEX9FtWZwBf~p=%mvdya*xw z){haS_MYPN%vQZoC@{HDONMp`gp}RT(uevFa54=)Uc=@$UeVp|Tm?f1sbaQM;I2T( zL2Bw_9;!(mqWtg!%DvYVQ~6S2Fw4ZvHX871YX5ZRGn*)@^ZLiq2L8W z>j7>qRHO}Qaa|s!Zh$h&)DH(@r$jet-25i3|M%1X(;58nRJc>fmcfw-h0T9r9LzT- zR;2p#M6rYvX2XC7StPThhqr67067^4K$uc5fp~Mj{>N4o zWhZ(DY+)ERDU5$} zGwL0)$EkfV)y-y&_yeo*%SqCqt>$|f5T=};8)Zp#D{sBQw(Kd`LUc}g61>e6&(Q^f zw&StJ%;pagMrSi&MD$2GJ6-B-Hk!ry|Hzb+0lW# zN#(oBh$Nou227@GXI>ZQLnGh$vTMSY;oytK)cyxvdEZWM6q*C^*mN!1PesNKqKGr= z*VTRDq* zuF;f^^8)e4YK@U|dz{L?=dAfKt_i-rY*B^4w9uu z2)InNQzV(;xW50(bM{}dv;R*Q zY^z7}Z9mT%-o@qm>n}ni4+4{q$o*sq-@ov4UiG&@`r|h5VJXe5j?hib*gh|$^$u5? zvh^={C$aL!t$Er^vY;HQElXg^P~~yS4Yk+-Qm%BYlhvJ>mHY9f30wrm0z4TF38g6~ z7%sv+*X7YnK^^(MpP?8h*n~&f&3_Glugmct^J>BXW3{Y7#9OV=;!@@0uae_Kt~#~X zyPH(qYTN?!?hV%tj$h&5EU9v8jv4f&GQAOd3g0x+|D))RRvyJ(Mcs%E$0+^zz*m8<1n?GXeZ0 zHvOC2T`loW^C_RhJ<(V=J1*tKQQHAwjlg^imBQJON*ze=7jFAVG5?5D3d*9PY@>xw zajZG}E*j9_v`b(s+(WpnHP0}up;c=3+ok+kxS?7KB25D7po5`e=cDEC8jc9nHoBHm z+2&&BL8X0|nZp@PK1w0R3=`Am+W7~-{gFnTy62)#Qi}ZbL>s$VvTwlSMK|8B9CeSK zyZubpf=&H5Q$n-p9=reBSHPZ&uaV_}IKC>na_Z_`DI4y5!Dteml55vnGeY7jY`fFU zoo@%krc2ERa0h6&iRUxJq5c0MTjg&U%Y8qwZdc>O1EaLSbr&QdD&3c#s4uHLL#qG4 zK(-j?VHo~%H?aWO!Y~+G8;6Co9LUS3(FF7S*;&-c6NE;aOd~p0Gc)G+_akQmM(lOQ zT3snmc+Wo&{B89)Q^GXlBqH3styTG}_ji<<6pCporxT~plZ5H!Bg7%HS}+XD6f!X% zk$v&m$%zgzE)^h6*=QQlKS4^r$mu1bpo+dXbSDAcGgJ@4Ee1J*mBMflaDR2<$|m-e zRDU6VFHOw_3ukkzBQuXaAwPYoO#HxI88gBsWcU+)wg1JmCzT( i>k?O<>1jltlU}5#%{e%6GBFK!`v5HzL3mTF@F28f`J$J45 z*L$yeeciRXs(baWUM1hRcl}%cw+p~lkdc=Gz`(!&%-#>czdeAJv@h5O08my2FaZDn zWB@!22>{_e2lH;CFr@#Jmw{mb!2Q>K*!MyX!2211`EDxj_Mc?-ZvT1azpk$4U}sM@ zXKPO?9yWHszfFK702vVx2@wGq2?+@W1sN3`9|Ii?4V@Sl4-221gpz`sgp7=ehK-(z zngvKk#=ytO!p_0X%}q%!Ai~cn%*Msd`Jb1-prD|jqoEUFU=VSBB>Twu|2h2|0N^0Q z0$@hrU~mDjI52QHF#kpXlX_o(;U|`|k5fG7(QBcv|E3{(+U}50kVBz5q z5a8k8YX`lT1K@EGa6fX0BjTx>BT>8IbB3jTMg~eW4-#lB-qLVcxQC;l5`G{eCZVOH zXJBOF=Hcbz7Z8+`l9rK`lULBx($>+{(>JiRvbM3c1KWFedU^Z!`uRshMn%WO#>J;+ zWM*aOu^B@Y4OXHTn!hI2ykcNAY_UC_~{TH(Tcfi8`e^xZ>L3JsfI0{j#xhJ*i78to02SaEYe_{$mb_gfF!)>oqt!gF>MssL zJ;&bV4U#Lr0^!cn7cyqDZ5@Oy<#v(#QDg!((uT@&Ww!b~KsYPcb#%r^wZo!8 z-AAfu=M1pza#NVFk7ti1$Y-OXn)5SQ)S=iE$W=00e6th6B!HB5ff#;@Ho%6T5kMB$ z58y&_ede!1AfhR-?1Tb%qDjG5szCpU@hO24&b z`r@~K0Eg^sJk5Y*+pVA)Tt(v8`h@6ysg5H~kq85vgCcSJKR_7d`U|lgVP}v0SBMzL z$Yng1cw$JBn?>`(?M+r)tGzE+`?=9dbHUSC-(a#=YIJe_1?~{9v&UVqVN%PU&HyuuceZxJ3vZYHa=0I!|-Ez5i*N#?fJ+ zXvMOG`)gaX!&NZc@8}ONVq8;g~Fv*X8{u?%W`X9`1O z3U!+B9%I|#vOFqMJ@1Ao{eI#Qylnv{YaYV3?KugV>T9@BE?Md;>r02F!~-7R-m++| zDii~SQ9pyvS$E(pq9mItNhh9Zr+8kEOJ+Xq-xLC;5NU!2n)#Qkh|jvsr{mm%{Q`^28?yuTb%sUk{>fvly(%-8 z2~pC%LWNH4_Y7m_qwyzUvj+Je7ZP}nx^|ek<|i(cXMoaF-Od=gMM}x-bU)+rz5T=# ztf#%oofGqZL*!HF!t+#Ad%!e*+SZQU<6wD-e%qAMqdR;rc^wJ1%^M7|ktZB{;4wBz zc5uQHO3OxhtWFh9B#=>NRN&BctX40SoksAcNlG)!7Nq+t96|r1-CdPfMaVvYKQIPQ zbN@L=#e5H90*u=f4exzihW&)foUQl#Wa0nN<&6?>|0jhfN2PD@b+S>sXaIAy_*<>F ztj2`v>yO7f;V*V{vBjLclu_Q_vYEt5WtX6q)~h%y@rC{0xgtSW{_DL%dYkK z(a#p=5%1JOT8(9UY*+Fb{vtpm|?kU168T354qd^zh z6F2Kxrxh~N0O`Xfps-DS)eW)+$nkdMEfZmC3=yU8wx8=pM^{K6c~T?^#=ScBq-fQw zjt!Gt_Ckpb)CHetD^{V{M$FD$nu^`>3~_+3(OM6x)n1(Eqo_18Cq7*Hwf8kiqmaVIjNoSiYC(?&Dm7 zLM=@XZ*G|Qo5a2FD|7N+N=Xn-kDtrE*KtLzOP;U{ro8y@a7Ywi~R<7`fkL9QW zS-MtX!g;`ANh5H%56&9iABg6=3cTM8y9m=2B^e_fPLu^+VTy^Hiseb>kpt7>7uf^bzdeeezc%J&(7?fm>C&jkH88FCDFb=U=P6M3 z>$e$>yDIA)^X7LX*nKZFw>?MA`7HHX=Y>@(Ueel{tMBoOs~e(!V~MJ=VD%3|ZwWYX z>g)v@`eR~~h&B5)=N_DJjZM_nnE_&4mW}QHL0OUBxAkr%+T>9|K3oo($F}P9*TmWW zQiN%d@$4l!&lzD$={oF%^3&ed?JWn_@jVyuK3q0ZYX?ivnSOAk@}=3<=G9A1gw6>W zPP__7b`pvDsCMa%^7?$dyWmqb9fe_mXM;kQ1M|r5zFy%Zotkfp&)iGSf@}UtZt+K= zNi>hr-{~f5T|VLCr;$&uf)D5HxaI1Y-R+4*tZzZqKQY`wb>kla-EnQ`=IUzIA+-8Ctm; z9H>zq$zL*C3tMU$Ae^m7X(twE{No$T+j!FS1jcbDvI=Keyvc!VyaYNuoVOXt80vT= z3FLjp8pel?NT}m!THjzrrUG^&)_PSlTDYOK41s+Q!j0{cPSpGwKqe6L|mJtRx;AMqB zdX7e3+~KG7iwdlPLtm+ONmfPm(x8fniEIl&XYto3Li0%EFLk=uFCQq>=gCK@CsMPV zHGQ1zwPY~s!(!*esdHlwWYGQp_7&S_m9sL-38wp?g4l_Asc3A`0z%DO%3P_xN<9Ej4h7@Ooxj9 zJxc=Nv0K9xu0LKV9h%)|D4|h(T3-*9#6=8Zx=KJf)PeA9l7#-cMp9wGF{)_|<5g$? zO}iC#*QP6s>BC}H(S^l6JMR)Y?z&xRY@FR~=jde;YHz0)0NTO8S)z_4#a{H#@%ZlY zvuEaNnI)Ntx=9x$HATA=r6v08GwW=TSm?d;Q$xK{{Y=D-ZBwm0_Tl>Nj*%Y-Kvzf1 zH;3&Bc%v&^B=n&LhbK)TULR`H7yg}X=#}H&ly9zX(SP&vJ`s{RuGL*?QU3U+?*K8) zsQg2xBAx2C_;b*O=-zbt`fpU}@_^zYq`UF6xPWT8Zr7xH{tHMgpFJaz##CGL0?Il4xtyoqShc z9=X{(zQE&rcmG#rskq~26@go~vr)}-ak6fD*lvftn;WU$iN%74_DMcf9&(d+$JH+b z$r!HOi%&R^0FHYxgNAFt+tT~SpjZ|F^VrBtz{j}$`)_G{6#SpUN(ka= zc@zJrTmQUr8FX1+eWhqsV#gKVBYq86ou665ec{xLIny^(bEC<&XX=1vExkT3itH1x zTjq6;u4YHE;C`)pvsZ*jW{h=jcSYCbxx3^$5c9sD_wuo_s4~G4_fMrX=VO;>d&@Q_ znI|}zo;o#X(WK8SUKF*lS3~DoYRY4Blc67Ka`0!U6)^gwU#c{acp;~`3Tp~1u<^GKu z&r(J<){tFlLsdt_fz>A6{LhZ7e~NI<30>jg3>i8N2cwvDJS7 zrYqrC91i*!!$gbhGNpZEx61J0~1l{Y)u;jlB9b5s5ans=ZmhCSbnV(lJm5~+#PMcvX8rL)fB&OjyR z@mRl1mfq89drYi;%2>wR0Dk>UwA(qG+^#husvJq?HSQlcqW6`cFIc}wW&^ssvcjc6 zohy^nt!4SM!adWG?{3Y&dK3Gsk^Z2%B5e#y+@#H; zERWSFEr`+`#$o7K_|MO%_uuVk;=W5+~+I@Ldo#!c&)TGbF zY%XvI_Cj~#v$rJ;oE`}V4N#v&HyKB|hJ-iqE$0Ti(u2;&bCQ`2)Q{q2Gm;vw9268S z*ts;K_S^W!u4;G};5bxA0bsClID&3-P`YkTe1vBAlYq9iuweW)c~*HY9Hhd554M~5 zjN4OQjNE~TZa-_{h}*jB#g`|aX3O0U0fvos_g;%GN8~KKRHr+*UE|oKZ@F2A>$+H| zwtuC7q#7#ozI|%`6PX-)(Y6E@c&mHe`El?DewG zQh^^Rnw9NoV4h_#*=2m|T-dXeYF%f)Ei4oED>Mgwmm=~Z1yQt9#YOm~RU;*fFk<27@Tp!c9SDDr$fr>8iJiOF^GqjcDbF1XyL>3 zAZgbXxP&&7Wcy)|855(pyXq3B=km^8KOm+W{&?ew(%eYBIb#P=p7;eD*DozBk0*;T zG(Y#dx+0ho?;!LbaoX?$^Aq7{iY(_FK7i$s=K<1E8rX+$ZkK07eK#T$L#LyoKi$jF z?(aBx(C7y=pblD=laAb~>h$tXM;r8GX}Ge4-J5IqLDTOgv!V`eR^g}?&c48#LHcwA z57W&h$;zNC*%_jo%tv~WekL!bVIF1n=>D$OEGzx;kD6Zr9{RH(OfdNRxT1<=cODxJ zthCj|X~pme?L_ts=N`F0CA#gKYy zI@OVXfGB`^4~$Fh=v#f~6v`>nut9OVpZv7TfM^>GG$;MY*#@n5$|M3RE1YlRYbk&h z`L|~QF{*x#MX@D(n15DVVhP-sj4ZssOwDa^t%o;xrsD)1abwy0;X{goKbGk^pFFH<$lS$N^mgOa+uZ|nH=75l-57M^l-)t>Wk5_&Zyxe zMI|m@MF`iTWqxwIH0{KNiAA-iJH*6?Kx%T_fE;v4mu}LxN?f_$k|l8fD-_ogU%_2K z;m4EV1gS39hBzA2i!h7syLN&z*5iMp$5Z?*PCGijYsc@K%ib_Su-#A{4I)n|a2F&x z+OIC}3(zE(UXs6C{g9K~hPtmx?_c4@8)Qe@0v*2BW~05(O3Swv$~pY0T@@@3#r@cl zHM|;-$6qHm+Y&W7IVyAFS#}|5lNwhA*hCaTpmI$sNab%Zh(B4_tD30iBCgQU;HP?J zmYfG~Zz;E(Tab1Xg3w19%Q+Scp-T0&%i07flAp%?3)nf#T#NZzKoLxYB9<}l(M%`Yv}ux5RnMBRmfsr(49H;@_v+L z>cn7Yk|23xY)#4C?9>xw`;P69%ktkSj2lln(R)e0$KH17>LTx69BZDgKyB88oUMm{ zp;Bqo)hGT%&?0FVOBoCaO!F?apK0MjtN4n3iyn7r<7;91XdF0mut@0u_X__MsGVK{ z1BY<+b@>(lY1K~H&M<*91=pVrq7yi%Jzc>U&8_k=b^#4lq%(0gxmm?HOCA0#w~3nU zhhe@PVF|T?q~SzOTG6op1^RgntR19wJ>CW3ima)LZ5T0DpmZ~dfYu*Tk8SWjfKA2> zu?8`gk{ZqTLYB0bVz-vy+Y4Q<1gj43yG~$Q@6U3Ll^Xn|VJg6ptg62u!xj~Vf5Ag>r-;85R*G_LEn6gwomCpnKlsf}NABW^-a=W~wswpq z>4pe6XWS+$Jk@q)jRMu>dd@ny5ZVO3K{wnY`H#pMSzvjmD6{)bg&WYdin_V zy!-g8+MH^meHygfP#x9sIVC;F&Jj$|qA@=2*tR4Z^iu~tZItOSsX3`uP$UH9LoAe0Gg&9*`(gNRwcqWVlnFOMZ&4nVi<~7w@GD4x4E}^+ovejRM~IF*O})I zc1s=0JBqzIW!i}8J6YkjUgP@#N8CBa@6S$U3FHthCY>vq9_>m=%EJQb zE^bcr>4&L8!w=tQ=QiZD^6cHvK88~yae1PQX)u~oBBuBaXo83^D1Nio23n(+Dl8wa z0%N|EoBR(jP)FMksUaSHpVxdfYV}K33&*fcC0O(0aKKMfz5=uJmAWg7hI;>8k_ELx z7tO_k_imO?FhNE3ZJvLUA}NC>aaD5eAihvCi_j`(WtEs7Udj5vc+5 zGB0kN8%V3r(xzUh&{Ww-r_o=~etab6UL7MqQ`D~<6pV&K#?89&vSl4oF9s}c?mR9rPec)pQ`FbPQJc5*-F(2m(> z%6FX|U)<>5Z<$fpr_(m);O>%q=IZ6ew~15X*v?lU)~>(8m-P6^ebY)v?0ByytZ?eD zb(iz>9kOF{&B?5LsU+i%I{VF~W*Z%Y;emtpo@Q@5Wo4~?jaJ5gzifjXHLFABQ|5$w zgZLAV*8yqxLD55ic#r&bEO3R*X?{j3;byHO!0zd8RiTd4*H>gQc?WnD7(Trc?orI>n{(>pqM&~vRSD;;ySJ> ztoXW^93DQ4kT(3?;}Vx#vp0fC7S;~Y9HgS<;$CC@XWD?~%?#xg*3pi9+fP|>wD7HP zgD3Gr+zkPEBZH1p9!42Ub)|ht6Tl0@v9M7?_C!J`x=^{;QUi@b(Q#)Ie98x$f`{lb zV2Z_`m8?FC?C}JHeubsJ^}-s)JQQhm?8lNNxWcf(msC$m{-;Z69Tj5yBLZ~3}lPi;vG! z<@Tsm!a$d(RxQ6Uj^YWk!nH2h>+p&On#h#y=3-V8Y!xy2`O+Bu7N(qYTb|+!cm7IO zw^m+@Agn5N%W->vLj@B%*NRV>`e_n0nDfXr*lGLRVawRSz{rv&Dd`cv{sYs-_k1B; znZTgxa73ZxEC<~p!+>0P#u+=nes;C<(n5rGi}W!k!XO~*+FntP_Kr}ok{QUWpF*qI zM4;QvMAl34M)n0VG%?*SeCtXcLs7`Yz-F2Kw1bR&IZpErAQ)p~(7m~J{tf4s8^Q)s zmkz>!8j7m85n=PbJ`Y`nN+-<=>gJDLOQ>}A(f5q=FYGN`M4sykoL0RNJAJ>VbW|A; z(I{s~tQmkZzh7wja3jr06OWSRd1PlYnJaZIQW0g`!Mp}BQqBfWJ&9LCDgtCddurEr zC*gA9-R3o2_d=Abq-NKeD)0^K6&gh|E{6VuLbfW$&{btx&Qi@leq=J+zIa z+bEPI?;8YcxCI}cVlVcBm&-ncO>AJ(jg~x#;nW}R#<{IG{5aP=QWnYP-4q227Fe@v zAQ`n%M5^rjPl(dx(}krLtl2H>TYzshB8|4T>lEN;K?GDEe=i zjw&Te1}e80yRzl|eJnRpNj88s!*7USZkJy_|HHA_@bC#LvZ^S$;(YOZ(=Nbp*BMl+ zzu`Z3QvF~6Y13b>gyJRMz=^icF`Gr4&ALw0l|R`U%$%%}=J*%q+@a?H`FV8pM6Flw z4q)Z**D~E9*go0iYcouS+Y6P~O9Hk6mP(BZ_y-7fbrhcG3eI;v3@K>pZe)a)JG|ZwWlYZOw@n21NNICU^>@oetP_Z(yZ@Vw~scL zX%*P%*UB{+hHoDhE4#DsKatN@xNA4;q=^KI!s0*H)KRrA=KW4C^lj8xZetGcA~s4D z*zY#w+Iq;68`THS=}JgGr>mMF_|HFiN?B1+Myb;DCTYuNDDR!f1x}PY_mlK%U*RBa zA)PZE28~~-A10Ir-vnL6-Y@8y?k5o%Hc{|Q3Il}HZ6XSGKY4D-9oU3hrzV%^r2M7U z-1u5}oHef|Yv7+WvD-)!G^#;JX>is~iqx^)?NJm%Q0h&eM`oMx(X`;s!A4Pcv#|UG zvc`7oZX?qB3b~@0*9>P0$aM`uhHBN%*dox{INFqSxIdka93DVc?h70*m6b~*PU4;I zNO*W)4^*3E&YFtXilIpfwv^_`VuiLUv%TcG-M!MLpEKf_HKXF>?68-`OAsLjh`jM; zyb*GkrZ6w`MED8x1-Sz{lu#tI`l{$_5kV5Ua=Sag{8}om6;Wn!UzGs)*;H@p9gK*y z5b6PYJs$u|P6F>_>@i@+kd)Q5Os6fJ=D z6H#-$hJYj!>ZH(_QTR#DFu)Nl?t3OhJvLcz+ve%giCRy0Tuc_uMuZ5UCQAm4>fk|; zvR)6-$4^Lz=)AGC$E^BU*9tauhAbL6+feeV+GlCA&IUz2vXgzvw58-C-0bou*x0Zm zv065wk7!lN^hF!$n!s_Klc?8^t3Nt z9^#gRzvwqF96**h-Z|Yobsf3Oz^gqg^Jd?(3+29a=IO6&jHrW@$fa$~8BUoR@@P~t zFPVYw9;zVZbku<|12=-D*l+Sr*-~ztRd>4$Oy8K~slPUJ0Hby2)JUzj*p6s;0*skl z!Byf5^W2jEZ=1Sl0SSUtU6)(9;<*}>jI4mxwogY3+QgrZ+^J2FN)u77(&yW4^jbts zPz-WZTkx{8k3p&QEIVI@!bA`W(<(XLfu=`iDt-O9Mm2G;QhiKm8=))phqLs?Wlc4r zE9xoP@kEanWe+9D3KKtiQ3o)0+Jxlph=~fq(&bAq`ahz32k3?S6fApM<^zjwAU2v? z%jG3ALG=EBrGgiTb$(GTv6zAV-wpnEUqV8589yPrx=y*$N-!Q^pa9QG3!sT5ca;I# zkt}2=hgtQa%1RDYv}a89XbAn()9F4nFECj4Sm#EwtFK@A`#H%R18mj|!ZdWD>rU&H zb?9YAb$a>2YjuAL}UiP}$bR$*px_>plF-1dOm zwRzP&Fiw!>7m#>VOTx;Dc3a^m!c6k}AAl+ptmk9HThn4ZoHIg=h5jn+)$rz=YD5HaTXvRz@M%B4=V8RqMTOEI#bDQz&|3CG3|>mpDi(R-Gu_`mQ0HC-IY8+j`#?h3h8XR;oB< zoqxgGg2|aB7)Uxd^A6E`>>9k%oOg=c^C;|SLZi-&9;7bZ?1h}0DsI;_QH`Y? zX<_ZvzvR`nC%coPm?aGt&WQt(hyOZMcP_DQFVCf59~_ESH9yz%TJJE7Myp%GgQ*=k zf2Imz{p6|hL+*&NWP8c^h{#E_aNx#NIHg7vFnbPL$39JPm}2iSOxpnYrh5ck{+0VU zz641MTTzt|6C65!y&U;S%1V`m#UcwMU)?sniQv#_sLlKI^9F0{jR$P%!6@X&R%72} zkUyPB$V33toQa{F>eg!~ZpTngT*6KcR)1Xxdgp2=h2KuRI?5|zT9y~vJaL&*0uFQm(4!JKY#1Z z{!R+`2k`xk>UFiANuwBhbwEjtx$_U8sRZBmH9H^?0kvz0fsCQH#gv`YOs&t0Tp5=9 z$@MY7)SQq_6vJ*WdKd_IM@5eYr$8nmROk;RvCU4q!j#p zQuKhnx!A-ar&(OEWLL#25|a22pzyvj;$dvV7R?8dxO!Bw{{vXPkAY5|e|?*tU5>;R zElSSh7&ys6B}f`-u!|rBS^)gj$9@dp>oO!yHcxLYNk#qxrIyVmNx^k}ulDSI4H46D z#F^0|1M|oip5-ex^A7Me+VnHB3C6qgyGZgo@<=R!M^&~x!{DWiHiul98Lov< z-o4=m$9|KrF*k%@TAFdgBfh+W1lL}b!Vt4;lP3(m<1VDJOC5;*&S^7sgQ7j!C+7*s z_xJ8%jvgxIi`nEwA5JvxqRy1<&;m$!-ClI2%01TdT^Q3@$D2o;2q6o5b;L3*xy=w~ za2|Dlv-4-ltCSPzV!o!3FAM=6RQIK~T~7?VpB%Ley6N?L=AUc9byD?`eN)l-Iv!E@ z=#p}FFc=?8;IHICBizxU0{nDvM?9u3yUH|k9Sz1-YJ8UQ$<)DG^aA`lm=zwDNpUMYwguru}*qkK$_nOCuhCMO%8zh}+F_AZSBr@jbSNPHBLYe2mi<>R#Hf{98&@0GX(kf8?F_nPW> zwMp|)_2>PI=5iihoi1nF^lm5E^?+1HwHvlAPz)VIAn9sN?q^RkTvUvy1tSVG(uzk~1!&iPd*hr|>zAleV`1KmJ>+>6$IW0885@q_V}&oG{-vr>fw)SNPs|fCZ79S z9E4e3B3Z)#17?7kkj>5SRvYFar)#L!vYuKNGWH&ulGCwj<1k7sB}{aqwfA+I{N|Z} zJNLtGdFv4Kt+>Kfg)_1~2%TOLM^$+)Wq9eeOu3VFI1>=>TogRbkqFDKiqx**!(Tz6 zYpL@OFw6@{k8st`bXTLmu#{1L z4R_*;_8f1J*rgf^RrUr)y1Rp_fq6P$6lKeatgbMCb+?m0JMHgPY`ft;-MTLPN@pXa2WG zE)9W|D$z%d_1Yge2KicMth8|0iy&lw4LlGAHwhwddZ+*~l2Fc1e7J~-a&O$tu0xKK z>Qep{^6h>GSPP=0DOnM>jnX4i__P}(sc4bubRUa+{6rL_>kK&*sn|WKLd{14qrdsj zUL|PgKA!v!+-$={7RcKm9P4K%#~^7Dw?5dCPt_%ekr^`xGP+;v_32<-lj6BCBrnHo zG`q6q>FV-h$Oz|~%Euavdi$jkE*Cycm1V3$rdj6G#ZaS@v>?1~FKld1W%|}Md9V`Y zw*?SP%JYO0(tcTF zrIpItIr#?Yq>pPS%%&BZGE>dD6M6hv)9#7AWOL4QKfw9?K;L*KnKYT%L&B%yI_UEh z*I1UmWJ4?5F`e}=YT-BSd<^QQ4OTx@Mg_Wq?Hvc}Ws(;L#+dc2b>am?&15Cht_8nw z8#>(jKh_;_c<8nY#;nvg&^YMVOO7QQ&`UmxKTu>v=4fPjqALpe`H4h(j3QHJSCyW= zQ7l6rF0zX?lLPcujh9%QmHgklPINk34Q}+xt#>NO;8wKwg>q&+y$ou{Nu@S4rD?+) zS)`kGlPg9o!bJ1SX}|NQ?|o4orb#}5)ETd5UnY>u<$-Nby85-cfz0@BxMFyEe0;im zozxE9Cc0E?PXfE11opgK?k?_fYs5Vb$j{Y*WvisXrP{-Fh@ukPX+HlY*di4B`A|9lu?9xSc@*G;Goqh9 zs3+j1+(4Y;{sAbE-g-!E2t3P5!p(QvOxIa<<01co1o;8KkP(BX+0~8{7GGk_j z{uGD1mj_|a@`ho~(gK9IHvmS6y>l1IBrHqS*s&D;%wHqiphHD8b`J)5t{h{ojmzoTv^#56x41Wx1^Edbm|y4 zmhsPf(lng)*^51?;^fkvqk8w}%99zITuL*k6yTBt&efXH8QH}ssicUsjZ}JD_v>KR!N?TXBqKNEvv3<|>q-O)=nd4*Ax$El;-TMGbi8^8vljDRAa0V2!i1P^t=Mh;R-Ds$Ov>kd?ZU0!QW+8d_TxA5;>m#nQ=w|(0 z?2v%ezq>+vE`dVzCnVp_Ag53BzWDM@T#W5q8 zmv{hw#3W8Vm0dV~o{M0@)T9k{ETJG#$7O?9fWBx2Z%WiJPX)CVwZ_f;QkFh#qNF_b znw;!!ylf*6CK%t-fc~#<$iVzV`J&jtd&o1Sf{XatUK5;`TwY>*s(LN^>gIByJ{02f z?|t9JD;Sn~9)AN~4udz8Qh632m=A#+ZZ)RM+3UKd(q}V{;GZgas4^2Mb%2U~Xf-V6 z^TzpNZvGI)&EJmJ-rs9REg#q>^F{}) za%tJvGK_smMFIN$;^-0qb1RizLwLNHZI`c)5}ZGHgPy8c9xK;8hk9DdKe(j>GYe1v zH&?|9W1c%FPJ(gF1j3Kr7ZF;Gk^_8#yA+a`F+b}`>`ryk3`ck;y&Re*lV2S^XnvK} z>`_6g`D}7T>y)TjHHx0crWZfmzhdRX=s=bsW){KIvf`cz?XEQ9@&iwD_ekfDyByC! zk2{T?o}l+%;m#If>&XV=aAQ3v<-|<)izwlE;x9gl*+yv>*=xr<7+=0$#i7cOG zCAF9Ioowum^35`jHi@aTtDP807maAj81}xBgm3<#p!v~pEKhl)S$4nWCwb)CmZZMs(uo<^{JQdxN`6B$m|J%S!-DB#0l& zxdEA(Fq5d>Oe#$}z<9~TT~G3}FlXvWCkru>C>c`MQ#;s8TYD!e9PLhmL%fdM1PfZK zRPR3|W|?xZ8PRYNa!71s*n*7&6vQ-GKfd+@6q3&sin{h8Mt-3W&m~fGq{K)}{(m&p z-)y$OHLitXOO1wK1JpQ1acI!>4O4P87bT<&o5|v>w(10XVS~?Ca@4y*odn?3xMiIi znp@GaOb z>+Xf8B#e`jla-wYlBCiAPr*b~V|60f3cgcO3D8tHXl};V%F?3D{}r6YC4-t6 zSl?Gui-zWkI>j-z7jdIzWOU4WQcG6EKpsOBwRCA7LBv`t!T8(K@6EHYz$}Onao`Zz zBL|>$m4BKwyc}#WQ#zwZH z_W}J4uHZNEigj@p`;O+yl|*#6#NUubAf{zr|MD8Mj9Hc6u5EWi{n@m;o@Ll`aNEcn z^|MU${thiew~3-fkDp_%BuhZBJ2GqnV)X%)VUVc6rz#a}bFP)&F#0>pJ#@!H+FN}GQcr2;GKqNr}5Cs4R_ria&WR+lhm1Yh_B^0ea zHzlM3HMkWB$kh(kD0+|#=S)eXdlsPBbxnM-QR-M9n*0D`)-|@oRlJm|m)35C<2}RpkI#(R~V~rMUPFAOolP#Ie?@Un>N)8t^pkD zd^ASp>Q9nc+M1=o8v#OYeu)YdZUpXRcBKfZ?E4oDh2m7QAGiVW$4$9w=xNjt4ttWV zw5bx8StSOD%g6Q>{825eKU3hScf+M=AN8<>mQbC9+Y!WUPizXe4Mk|}XjlUZQVbAN z_aal-=_|y!BfrRk+(3BBY*tUTo1Jjs{3R^PjB}(*wSLi|_yrx(bbck~(o}p8|4rf* zpUEV>FBna4xs-{tww7k8DnWbF0KtrYDspLd!ca7J?Q<0bIBw4XE+60Wa7z_K@{6)X zDDDP<^w>V!T54^0MuEo*nyqO+CK=)u`%%=f; z#r%Gy%R*2pH%vcK4v_zRYD>(>Tqu)MJ}i1>s?!NX@we^M+5v_$YN0jrwnkCV8qCGNjCIz zhuNUK4LO3PDIQTBZH?Bca&KY!=CI&Vn&CO~yknn(XAMDD+;f|B1m~e`6J2eRid_|L zwMxFqWT6UlPekSH(KtX(bWd5k%GLs%S)*-7z3#XX*&?T>_LH9IP#2)3<$wc z2kLPsS($KI;Pzl88PZe{27o&!X>iQ%p$7A0*NY+(hIF#G7Ur5sybZl2T^b+*Gbe%f zOKZ%8y`;9N>jH$`EQ~@#LK(bk6)}{Q=$MHrs-ybt zqauXaeU+r;JD#E5R?OBj9@0E|IeIWbtL@%;6-;HeF8yn5i~-#RipEBBv&e*TrCm92 z)z!hC66I_c)@+h|n#5Eu#bOQCrB*=MK%Zj0EYk6|l50Xt;VMVIGt!(|{Fhdrtd$cw ze9p)maJUEW``EV+Fkcpda1R8V!fqv}{PnMW$tg9O+gZU*p@aa;Rh8TBePD#z5#LJJ zLK%6fQNBhR>)V{Z0>|W~vd0yepSC*J{7R@$^#Bo%YO}%AH*a?XV2tgLYlkv|^FjJhsqy&mF}{ zit8*F+4!~NoueZwZ$K!@DTiJU+1!z^ZR^s^3C1uMJIlC`cKAsYLB8wam>v>p2^sJ8 zcW4%OYJiE29nW|U+ZjFXr%Znx@|D=MS*F(!)2y%k5S0(o9yA84UdEh9tYz&9uhcvv zE&h-rN0`*UlH04D26lCcg6^W|tFf{K-2v9HcllBE?i-J;lzZCQ)0;PKu!kLUo1xGuZ1tol+O zaTL{PSgq!9l+%U6g3A{G%G}F8~ADTRn?Mg~A>C=FSIo#x%E9jqW0Vkn+BCKv-C zT)0k`Evi*RR+I@y42YPe-xiw-W$`PrcNg@j9z^~7`oD_$>aaGRsNLXBi?u*;C=%RV zin|A=xFl%s(&8=0PacxVByStR)MO(CJZ+_o*pL_q=WRu;QeRgKfoH^%x z-&sAw5lc_vyEftb)^pj_KTK(`z72k+< zesafIIQn^GzfLtr+$l^}Yd9!#53k5~=G%|381sir4|-EHStbNW>wI(qlNYLuh&?Y? zMNi$DJG9x7o3foUj6RjEO+E0V+*%Nh-&@K28Clj*QOs1(6SAFO=m;vh&O2R+r(ecV zurwg*8Yo_#S|YJ~kd<((ar+^Vry<=>b;`}2T4OqOkv%Pr$uWSa+<s`l9ypY3%)F}LFHE8aCDwSt?Ru`tTWuEK2j>TGe~?cb8zVN zBC`4MU_H6HZW2@BX)(&xe>1;9*KhatleWYC)5RD{<()+OigK3C4#B7`iPv(^drc}j zm(%DnTF>Sf1ZR0AmUcK58nk#2vKv2s6aTDQJYr2Ot^tQb=IML#aJ1KiA^rL1xqk=0 zl|HX8h_2w>P7PtTL}#NSb_yW<*_r8(XpXzBu@%XAn=riY_nCvIp1lNt8E$ET=KHqg zBc9`xv|FaSI|M96#G7k#75@O=b24_15U$N*#(6WnB@vXmSqEqEG0BY6fJ2LY`=k2b z@r%7n!fOm3NA7Q3QjnJvKKF{{adT{wr!g zVdmqf)T>==t(&@?DEvDh)Fbr@d~HSs_t&X%aK(l*?#(H;9M*Trt)*oD0kSKxcbSDg ztzE?2#oZ$c2V-bY<1Cdrm3L78m2Eu~Xc<-CcYCf3$K&&$3%!wv8Z-L`cr{Ky*%JZ2Lv9RKe%l!f0zCPqrL!)8(uU0f0T;zobSJRki7 zxY&a7e8v2Pn?HKHEXF>4v@nl(&2w+Xvt>8_=Pd5t30r7X>R)3ce!29@u73apvy&Rf zS1M^y!=lQ^SYR6|9z5R}JRUWMe_NH!`f!W&l-%g%HtKt_8azMtzu#$D-vp?w3zXhF zLH_}MoZizmQd$IM?6y8F$|_b&)!otRexY^yz4Ra8>&`7-0D^B?v!l(TDv7H$-I{Y< z|LYk^Dz*m{{w{a5Y(U!j(0~F@hXEJpu66awdneKVE%A&7t~DJP_S|JSL!Qy^R)ELv z>WvomvB&?QH)^Y@wG65S}!!7=^tR<Y!KPCP_-TPiBDM|<*S-jo25W1isNr_S$`3BXx|k(i1w7#6LMd>c8)Rq1K1>B z>C(x3LoR#5f2|p{!g5dPPVo`?{oNF*fpIB?cj2M$crrY%eH{|zbbEVTr=0p?IU2?2L+ zY@heFEZ!Mq*^@8sMV3L@44Te z&6mo``eBq@nX(tiQ#7Mm%4YLBCYFMliS7xoE0aBeIH{TIns@^!*tBk{+YG z2IZexaw^3pyXh$c!cWjH`!;o9S)6n?xJ*$~YWK8!m7iqH4SVzqWH&N{iV7iFe=F~c ze_`9y=&#u}`&6#rJFBld`Nm58{{WTPO1@U!^0q1sYNjr5a^5-YTPO5X-oJW$e_!~! zWc?yQ;=YgdlE4jX|E!{RdZLc8<&X-ojA~ZSPu;}Uef=rmclF2N=5N&Zcc}~grFt+5 zHQEM%^ZP~sH=pN2=j>=wJ7ZBks0xW;4?vpsz!=x8R)v^Ap1ugbM~DLORGt>F7{ZIw zQt2E`gZ&qe@5U3S;ozk)9(Wp%b=VN?%Xedd#{i3@07wRvAg(rQbfy)cB78D4v@xEEEb334l%I+rSQKObra?Zp1_z(Dx6pg7hy25S z@(-JDSNSGW8sl)43|i=l*3N1n3y0T-G2(<_hf5>nF-d4R704^No)H=PQB~Zuirs1@ z>a8p5_lnSYw2l2aaE1HI94|=DrE8?xNn``^D`v_ z#`pEf-gpW!_Tzht+crRjOO>V^pv@}Ed(g)v+z)}*sApgDXzV?u~;$sJpO&M zVx#-3(rwv22>4Nh2%p$=bwej|$h2*W4pt3R<;!$tOoN5ez5O*`L}6p&N_SwM~2c9(4*|fP(n+_QJjrtM)S(3}7=hlH@3*g5yszy84!eGV>iI zRc_-)c#@^Z%$6lfw=nd2I>t|};K~AGiJ;mpqK0GiJtA!nc^F1yO>*DhPvU&292f1F zLXrC_qW%{BzZNIQs6>@Tb!x~PW7F4#e~HnyBv}_z_45yQPTEiDde0CvprsXF3swL+A4S-~N5gA2N1pL#VA=1KC zZyA;jQ$8>%w_@1(Rh@XlTHYCRvPQY4#n0*U;5Edc_WsnmRLyh*!rdgpF-X~1=Dxw3 zdwSC&z5VnXDsCF&JZ<{$JI6`ss46dOxr|7NpBrHwyvLw1$iq(OX8i%FVc;3<21CJl zA}cT;@-y0G{E!|ZQmpfHM=;Im4kF#k?FjOZ`BZIW(-1Hq5ApWtk!J#ZNbccdBS!#| zqw%l_O{9LR+9_EiPO85;y%yhG0YkYSE4;cG?(W*CDhpoWn7 z4Su=lBwpNHjQ)_ypnjt`24Lp4UEx;2QEQV1b8==PO~c^UMrK#{^LgTEifTascBU}= zF4{AL0|1-H><6-rE87S9+p)T+=foob_R3&Z8+@q(niK618-}Luk_$kHexm7z{*I3{MnS}*XdyR6XwtP>36~%RJ zW=*mW9NK#GhnIa&4i>`HeH_$JWJZi?goX?aW!K{qNUX{$>vx8|nF9ODpH&PrM!R1H zn(#8BG{U?riTXu}2N*kJ-!9P)>()w>LBM2lqp+dLrUft&1{#=u(# z;2rSR_^qS%TuG8ewyc|wyJ{CAs6(viW;jY0U)7?*wTLvSQCtvU?uN?o#LX4N4g%T* zh`WuW1-~hLJDR(sL4=8ymfWAoJbg1ZCcpsqNeW+QAk3NW;U}2`F>q&|b9BovQxMAK zdw+SW-Kh#y^fS8B7f?Ten6+q4Kqt$WGlKX;!8e~w7{vYoXc%K9z)T!`0(}5d+>57T z6769FyD~MUMw34$ooJQ;ms}o1B~j-RTZ1XsGc;cWEYY@lxeTMB16=Q*4TvC&ZanLVI6JRF|*F{ zlt<*n@#5o*c0Dq-rna6>Ti^4%X7)047O0M9@+jwzwVaP#4@!Q8ypFlD2;cgUxeC8| zJh1FldkgVAbj_*7;_d7vm&*-rs5skdWvhnD2em0fGCPSr8#USwUI^C)Ob$}0E^TEc z0=5iHvq=P2;7mtqu^N)n%LzJ4alQwu6HVd*=`7vbNd2+Q3`5A(d^R(eWoun>{nOS_ zx#B>m7QYEuKAKRwjq}W|Mdu)YA2}9qyng@8R9?{bxn|tYI@u`_;&@?_K+MQy)%k=5~sf^Pv zm4p~j%~8)MV}Q`hm&RiG6DQ|HeE}3|P+r8w@GeOY0k>W9UAT)R8SBc|8ckl^wJj$h z-|Z}5dj_g=tQ-VS-ABy5*JG%}43ws+ev?Z~24mHxW(V8Gi|~`F_r8XtQqCess63RFYxoZ=Hmq0Cu%^VN=!mTogMv1w>4gm=^i(_Ppu@DdjPZpVVH(be{&nA-Os zmUHS?MLXHt`PGKo?GUOB(j?m^FMikJ&_kI+M7Akx4`RcqeJr1s*@^aFvE#$f!&zzs zoX^kj8%Kd{*L#RUTg1B~P?4H370T<#2zUPtjs>!1`O9LZn=w!uQpskezj0@hRE?0}bG;7Vo zd7IP()kEmFOxf#M26j;&HZ!G=UC%<(9b%(M1(zqZYD2yazvthk)_6&WR~@xn@|(nCLfA2p%|vAJG?&yat!o@kDcJWP`fs)>vi&cJx|v&HQV# zEfhZeGA^I4NFN7ZP-#Kli1ftk(0S-KNj2%wCP`CvOd8b7+FXbR3nX6f_D-eSgi3L| zJlOQRn zlNm1R-w=Hka;(KcJ?BU$9L6$m8m9>TFhF2VI!BzTs9a_ZCs{ z5rud&9hjj>i+ylAuamg{qDPAOV@lw!5yi*b&%>Nx4xS(DY@?6Zb{_l#6q}h!eKQu* z^0Tq~tZP&vbc`m^bJw^Q{#6R@8_!Vr6#H(G@-^eb~;`{e}C*LYG4;G zO|iSUm!@v*9A~us=WjP1KR#hs?Bj^_9^kS;sE}@k7HI}z3^>LKxS6JtWQ$0@XE%s4 zefG3*)VB>koj3k0s!hbP$uvFQ`&`51M@mBtDBgR!(POe;2^wkvz=O-~>*$D{)XO_> zz+T{mh(PsopFBzvY|8RbP^bjlke1D*{MKR^u<8YrE$9l0>2G8}5hYX90Y;LP2aBTG zOR-kf8xf-nAN+|RJv6begI)HmW$bgU&!nEV5(;PQZ{AH zmpAYxs|T1&xp$YgmAh0CngxSf4@EW*&3?Y|`pOQvl_pk@?DBo?L4Mh-Frvit=_Oqs-%2hoc+e9Zt~>B@n}s$Bd5&~yX=zBs)k^l)gbs{Q>g*-80=(NzDq_8bpZfD09g5f#WVbrt)MO%b zDgY`q@}Lu;odXzc?~lU1eqU^(mJ1SmWzd-wJ62{tp^+4o0h&wHLHxD6;yq=tl84XiLtMzvH`>5H8y=ilm|PmTLxD9 z74v8=4IRuIPL3>Qa28)Y)sP3xkW;rW`7>=u><%`Z%d=&xExhcoRSKH@paM1AWKwHry$HHi%y&3VhZ&q zp-!XO-Jyc<3)jXj-%@sXGH!%y&w$LOfVCpyeqih@rH&bo5N?joLCSy9C zX4Kc8frC#j%LreU6!I34>dTg6wH1(jDgzk(1oZUyp~aJA?%uRqZJUNQ`A6r6x*J>U zsWKZF+m!j1U8v3gZP7gJxSk+!#3o)?;H>``QQ$nyU_@>-V!4?R;AVzfJUlA_Gv#$? z3p9e0rf3McXy{iM>Y&KEGMf@O!zS67A#Tmy(}7RWp6Z&Th8otSDx2o5?-{jg@igsT zmF0!6mn7SUSkz^CY+5%ys*shH9IA|16EJ>Lxh+ z6rL?C{~?vjF6>3#5d;r}ZJL#$xuvX%>uS=boQH}**@G5uj37`t z?f9gsiTg}!FoAY|#;?Hx+&eMK530>vjsf>4Z2-$so%Bf_!NVtU&1pBw#6Yy0MI^>% z+0n8q;fEDjDwUVux#c;H3tPvWu(UTjT*gF1TY>$3jh-^nmfm}!pX(Ign5s94jgEy_ zC*U@MEO=ud^rFAF<|?9(&NdJc!yg2ViWta=)5n7aW6V0+KQgL~2LOGL)VZc+ACGCF z$CjpIS~>GjL=|_8R1V$eZ}UtvEliTKKS^)0{;-X0ST{^b8c1mCR<~ysVOUr=bw{qDp zZ&b++X;g8Uha+7EF6d0&wNr-?HF-UKv|4eb!>?W^i5KwZ$tEu}Rt_d{^Qa|#o0HT~ zsRdJnX&sTO(Q4e5W#~g;vYX8fA6GV7r@x+f&9ymGg{h1d*Cw}3+_bP0EjfhtGvVoz zT?~7?%ghU=voy+e>MIu!ep)39jJ)&B~)a0iW%Ynn)Be{mQ6$~5&GmzNpPiDa-bF-$XM zP_?lJ$LO?;-+9sik;kSzRGBSwde`gaiS(H=)kF9Ui%AF>j3FapNHl|^V&Hm9s zE8pwkU`}o0#2kuT-`j)ORDdFZs*-DTe54)LsVMPRUfMfRcw3^g&STu@#l_hMTKBvKR9K-0NXAUHN-=dgf zT;gT=!Y2@6`gndq;jrzC8QVGoT%UF>;|zLCZNu&KNYdteMK93fj+K!tpH|(?hUC^n ziTA_{oQnM6P<4ay-&r}u7C131EoCBpzWMwZ-tsDu?e}Ufx}V}N&aH{wGYUoQPBN|& zy?4Havh8yNN(zXk=h zJGK)KhMR`CV8JK6)Z}?AMK_Zz1RGlE{l(Clp21N_9(-&)}@tO4G>q?hGiHf+Q= zNW33H_a4~T*F9Ry+gXLD+l_Xkm`XT6GQQ`))@9mH^h~gZ^vq02!OE~=&`Bo%6iw7_d#lwxaiEQLP?N^Ue?TNvCQZHFV8g@IK@5eczTQI>jWb zb)7E#N`nxSan`s#-`|bQs)_AxvXsFIoU9itmtiUSkn%yBd8|g5poxA;No;xO=x%~UGbt&pF&g$ZEp}Xd?a*z>AW3HP1d`8u~*Es>ne?!ZiN*GX2 zCCHI?V5U=*hkUYY;{={A(3;K63>{jz6bko=1MwBn5LC{9XF68jdlp z+^jE;InVrDyf-DklJu_;wU`cwc=d#EN&Nm$PN;^+VkZ_O+|!ih_dMKEQ8!depS!R- z`11N$W7cod0`m=ImBnQ$3M|^YBc^c@KW1W=Qnq$5Qfi<$kOdz)M+q$dv;zEYT>phB|bY|9esxROOZ<_AZ|U{aQLY;%c952i=7{* z9{;lHgje8Iyu26X3%&k=kXZ!XR%1M_$byr;NdAuOh~2CC*~L2TZ~MA$%^Im`o|?<< z!-BYSWPe%pClo_{-LymgT$z_hkjiK9Gw@!B_)5-aTdNih}s=x^(FStZR9c1!U+#7cgM z8bKJ6ynAP|)$RG?@kWWZoB$Q8M;JeD$^Gle>!Dp`&m?x={+^sLMSjhrbxcGDZ{ zvEbrz`7dwe-z5s3y~q)f&d)(F4#x#AW#d`vb?u{V!_Xs;}q$fZ~@s$RZ8EE|QUN{sEe>8C44rl^w-B z80y$SgNl20yW3Jw2#>Y=8JN1+rO+lsOW?3I6Z1`?Eq*hYY%6Bqdg|4)7Ra*~*!wyn zgZ*uoGM6z-4^{BASG`jD%FRjm^C+>rqgwn)`}XDDT3|fU6pTe8Zpd6VWV4Z58=Q|1DAn>zfDe$>E8oJYIg}MmB7>Nyq|?0ZBomFUcVNWm?Z4Qbi8b)F?1c( z*?>8>Y*WRI0wv;-$Ht1v6p5GoRyA%RmaKeuF^1n;b;g)}EAs7-MnJV)XCEcMjG|~u zl1LIikiUmd{WUTO>)NAvkkdOKb0Ck%?sF*><$-Vc5J(;N%Jk?Mi;r}ul_({L?sSth zr2UR+i{KwW!u)J+4&zbz)AEfFrEQ_`ebKHi?Zu%PWYK()`C|0>%BXXcgUhjvppJmr z;~akRyBUR>8?#b*6Z1UjC<3pUdtBN}#)8Ee zUoMZdTeP@NQl5iA3bKoQ*+T&osxSMUxhri2WfjVkc50l652Fu7eL7O6T>}nx^CQwV zI7+!;?baOpNg<{n|N{d~X*Z@yXLe{!LG0k<)9_5<#k3 zdZjqxGzq9x@2K{B%2<8(s90YlXlMr_WND~#K!xfhUZQk15V!eyeJbP)E z&pJAfvN?>nF>eiz9!%ElP!Qwm8xqk)_$RV; ztm{Hd#ji@)6&TId1!{_+)5%RrD>*(2w%`E?y?6xm?LiU5ob{?*A0?q|?`1h@b6cs= zhb#Zk0r+P4t zF{e~=v8NB4t~r}vG|k!*1f}uv&cI{V5!?DHpC&d%h;YwS}CS z8J@v4^IB>{c{wEbXDC8`bJqo;>*Y`rKOYc@)?5KX( zP&0I{us`YVWQj#>H3xWVlayT*%QF8(QMtNVoQ#f#uNTB}H)^lvxx|xYf@Yfft?MX7 zHLr0>pY}=;;2qUD&O$%rJTfxm{X{d|7oO6^Hsg`jZicJ+)6zY#S5(i;|Rilfw0OX|cz1AzBqy`+9V7DK8dLB4V!^z(T0phmp?*_1# zsc==bsCK&nFjsn6$dKp&&*#UV4u*=N+Y=a&MVZN|$*src2pavm%E{a(gzlZ42jE?f z@68y^vbfno-WqFi(b?j`xOkpQ%!6lT3!;a=^o^NBO?%8;Pjwm|xxStzy;2~g4OD+s zM|Wb5NL@!(9=fgnZ;SU!*@^)n3 zBG8WUWfq)0`ZS9%^HmZKLVN9>Ldzz~xYykYWY<8yI00_YR`!jjxxa^Sg) za29yw^3!SEg&n3j{?ff7%&-DY;Y|J$|GRr-ieahW~)f z(Hnlugbe4WD(K=>i>s6M3WHp_ZQ^6HDMV9KCeET7!XR2bPUKrHERHC#lU57D=` zQYuUD|J%7a{bydc)c@FcePN8RtCgYoMWnOHOt4RdBi=(SX&~>0;l|5*@b}_FCCHbi z(XYXMUp#|0n>7P4k!4Sv6C<6jO#7*9DVM&)BeZpXShz2X4{B>%d8vBkT1^3votu|s z8#VFc$ZPd)USM~5kYQxjelkQR4iNca57G(@(sEU-U*tSIJ8LvO&7&+stT%p+JTjd) zcKjRf9-k8yd^$KjOw zBfUiC-T))68ChEe(lgZ0jR&t`EtMSA1!p>l{UibZ(Msp`SOdeVY>Y*MME)S;b+hi& zNvZl(irv`-S}eep8vI9COe~rx&{(ss=^?IBg{S9Wcs{;Kg0cCIQr4ZgVN0Tgj?SXi z%31Y7ZgL_%OGyL%^BJbjk{4g!y?1XSx)XKSH~qWp^9R6s_DvZRY!aF{2C~sos6bFa z-1X^nww~Sc(T)=|_yUk1B(Aj}d#iU7s2yI(X^fx#%L4U;kGv;d%*d$RirS5Uowhd> zpuEp@2V8>?KAN4d^%!C*C_67-QK9iel8xh+-|e-A1AI<<+YI}-;gsnSoh}dEtoFI>Y<&c3}8GtAfGAyctm18 z;70(6dT0ABBKygE;lyaXX;Rq_0qo|~B2IpT7?L!~hP)4V2>#xh_NML)PNLPk%V?z; zp8Knk0}EAw0K9I8TvLY4NLb(R9`3;{+Dci+cW2uloZ#0#`RpK^u3>YiWewqlagW%Y z*_iqEV%?1AO>1J0`FcE#3m#ju)4M5vII%>Zg1ut5FrmY)GpDYP#b`0%N663#M=n9* z$3-N1P|(m6-lJnpKgHEY4g#BN=PWZQ&OfkFXIW1@EjEia)UnOu*_ZWPuVglFrf_CM zwK#b$J>^)o>t{^|kzowm?{F&l9#cQYKb=j0vy)M~HKl}%6Q=d}YE^!}-C>!DLnqHL zh%=kBz$dlJI{h_?Fh)&k7t|t-O{ea}c0w-;;B#sv&)5cQ*5o#)JJj{8z)T&pDEjYG zDhk-GWo-Y3Gniim_&6*5Dv@08-qm4}z#=bYOs4Q)E3M-^NiAu-)@b%N?Q3Fq8waqjm}2FQ+r zS+*#{vUsZQKt5G|N~&H<*3XyWlb%yR!@@QJ`BOW1hL#WTz1$EPYJ;8H*RtBIyz!|MGT7}wpNJUFju<_xnzQ-zyy*Af4tCD9})Y_j2C zQWogsG`>HzV|TV8Y11te?Z?AhZ*N{{DB4gOIE13KwidH?J(-IcF>i12%+Qx%ON!vn z_BD;%50R+U8OS;kb`^;9cCNQ)v}-N^(3O-fpE+p~XY2LiJ?$|q02IFX=J(7TS>Z$f zEUU)1OWZa6&}7Wh&AU?Ir3E_6WQN5&sl6&gT0rFp-G+4wVEHAQ`zb}18bDo}ZUp?w zBq5Yv{1ewX@@d3ohSwK~44JLB-Y5~Q{8HxERGS$MP7;H<#OE_87R2FzDc}ga!q6Hj zZn|8%8EBvV{%2q?Yu;F6gy;Y&xRhFYeusUc9HIC}{W$e#uEG}l$6A#mSwwzXLCZ-- z>c#Ob=lcrwoBV~_{fT>He?ki3>VCLJsKUkAv$n^8miel=6t8hoUi>t^wi8c9gvjK> zss{G!*(!FY*U!#TpeK*(j9mCi)SU)r)82VUDWV?)jtE8QZR)Vd{=vJ~G)cX>G}?w3 zC0erpO`?@2N;~nG7u$c2{#LlvZ5Y)z!>W|#r7^02W5f^|zqCoO6CXA~IE7 zOv^aIM=|tK5gDOwwXFnT|6L)FLu>UAB=mR-|5Sy`(&)qOicxrm1WpW=cC&zfb`kKR zZ_CoyM={VOP=j{(?!(0`zrnh}(!68LY>MNB8!eb~C+rsN>&xK)_a8B`;Lg~yj{mzu z@zAf_HIa*=)o@bIImP6qdJx&GI81$Nr-goa2U4m(k&XFJoEASLipKUvm-+9B-ODs! zR%p|Eay-$*)XN0ce1mESv6tfN%f8AS8A@57Y{YT4>@>A%%NF_JNrB>paMh2fss~Qe zx94|!_PF{bKl@e#eiT&^F0sDKzBX&AHW6#t#lf4zbT|s(uICRxfloKnzWhB&KHFD+ z*9*)P_gra0gc?jqs8gkxqQRASnxL<_Z!Pyncp>0i-02yw zdVNk0rL2|+C|B2D7xMXH-%q5iAG)RxGxJ{0rIqq48c|A( zCd8ya&kX*FNAD->CG0DUw))Tztf6Tu{$^@87Q5qH&EQxU`Odbc-Aju=458l>wGjKs z0zMzJkpbTv9>i4L_-l5PPELiqIogBO7`fk#N7gBmBBfvk#oEb+sJb~%s(wvHFGWh} z#CK5-`!{083E?q31#rIu;GT4o(PVsy<6f`B6QDh$sHR_8F6XSQuYdlQ&zg?JX+7}N zZt%p%eKcY}^yU$@n&?nCtYxW0&H@P2lAAqdCoQitg6ug>O%joJY}8gl6@pAJ^{FaH zs56BDDOPjO0zEF^-(H!7P7liT@f>>w)EW4>WxN)rnl6BkM>*Sw`M8dT&%De<-1!#T z)(=YtG(RTalRYvDKYKDpYBgHeM!-7T&gaZd=bGg9T>NE$`h|BNC+wU|nZTxtdp}WO zT#nqiDXfKqsidvIi@8=~CpG;1PsuMNX?e9HE2I$9bH`oyQ!!)z4@AHM`GB^PtHvEQQPc*b}?L${copg|^PeLgBR1q60o&DQS>k!pe^UC_ITSb*{AvQ-NNJR=O9r z0fvadEiOf;n5OGh$GL!2Oy4-$QuVhz-e5fc>&9lIJ3=jk9IE&1Qeqb9-E4nLrqvrN{asf4bs~CWRkzu3&I-4<(NMY-{UeyRy1Dhw8kgvIjnU3 zT$&$q#uASN7(>vulp!}4sLQ0)hX)(E{sW5p1@Daeuk1uFWRX5OdZgFPrba0V=QYsa z2R4@OMuekFEYi zO++ysX4|-Ou&t=ZGM2N}4_}JK(Wi?0aJda$PCtpjPuFkMDm`ix&lmmyI+U`ma=e6) z!2yOGN$nzlaKz7=U>$Ddj(~`Ii@cn&fSrq+*?#~?=d-KM%NpimBlk-1lL*zEIRlJl zGW{0?d;;d&_^7RK|3CFPZM*^6SOaeqVU!>cH{ljWZwvtRlQ zx)xL}Xd=9wXt)NQdqeShUXa=8PF%TL&MPMpFACR5ha~zam`-bwJ|L z^ZU=C1tO3YR*?pI`mc17WD#1uWQz5^sR!vb5hpWo>s?s)f(%RMZc6(HvsT6CL^L5M@;0+a2U{!Bmguc;u1Iu+-3br zZscCFHm!CXV;Z20J?|*6uj3=?2roq;5vjEYP?LiRH2Nm@H#`T=G4R?#>UZt{p$-{5oOH{}P1B)B z>Me(_@)QbgAtHc+3nXg@>VbVp|IEKt5tA{hfN0sMmvVy7Kzm7>^!uohvw;myP~-Pde*B=g2$=Lno`4o{OSASP<7#+4nQPZInW2*n$+F^nqf{%|y3U_+En3`n zoDqaQHZSuKP?x7y;#EcT zlKdBHn~4apd~dvb*mA&hAH1L`s5yKJ6*JAB$kl*g6+;1Q;07EvNfu8kUWt)u93gN{ z1%6INoCYPbBdh=6M6?9{_=h?@(+6&a^w#om# zOmqNw$8eN+>DkA;XR8es^(AypkDws)@fWj0jyZz^*-vVHHh*{*ojYW|c{e~2KN^8H lBYY#M&y7?SH`n*XVMa?ykb@ Date: Wed, 27 Dec 2023 13:59:33 -0500 Subject: [PATCH 129/143] add depth3d support --- CHANGELOG.md | 19 +++++++++++++------ modules/control/processors.py | 3 ++- modules/sd_models.py | 27 +++++++++++++++------------ modules/ui_control.py | 2 ++ wiki | 2 +- 5 files changed, 33 insertions(+), 20 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 4d81c6fa4..cb8602300 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -35,12 +35,6 @@ - **IP Adapter** - add support for `ip-adapter-plus_sd15`, `ip-adapter-plus-face_sd15` and `ip-adapter-full-face_sd15` - can now be used in *xyz-grid* - - **FaceID** - - also based on IP adapters, but with additional face detection and external embeddings calculation - - calculates face embeds based on input image and uses it to guide generation - - simply select from *scripts -> faceid* - - *experimental module*: requirements must be installed manually: - > pip install insightface ip_adapter - **Text-to-Video** - in text tab, select `text-to-video` script - supported models: ModelScope v1.7b, ZeroScope v1, ZeroScope v1.1, ZeroScope v2, ZeroScope v2 Dark, Potat v1 @@ -60,6 +54,19 @@ controls that are not applicable in current mode are now hidden - allow setting of resize method directly in image tab (previously via settings -> upscaler_for_img2img) +- **Optional** + - **FaceID** face guidance during generation + - also based on IP adapters, but with additional face detection and external embeddings calculation + - calculates face embeds based on input image and uses it to guide generation + - simply select from *scripts -> faceid* + - *experimental module*: requirements must be installed manually: + > pip install insightface ip_adapter + - **Depth 3D** image to 3D scene + - delivered as an extension, install from extensions tab + + - creates fully compatible 3D scene from any image by using depth estimation + and creating a fully populated mesh + - scene can be freely viewed in 3D in the UI itself or downloaded for use in other applications - **General** - new **onboarding** - if no models are found during startup, app will no longer ask to download default checkpoint diff --git a/modules/control/processors.py b/modules/control/processors.py index ab6196d88..a52356da2 100644 --- a/modules/control/processors.py +++ b/modules/control/processors.py @@ -43,7 +43,7 @@ # depth models 'Midas Depth Hybrid': {'class': MidasDetector, 'checkpoint': True, 'params': {'bg_th': 0.1, 'depth_and_normal': False}}, 'Leres Depth': {'class': LeresDetector, 'checkpoint': True, 'params': {'boost': False, 'thr_a':0, 'thr_b':0}}, - 'Zoe Depth': {'class': ZoeDetector, 'checkpoint': True, 'params': {}, 'load_config': {'pretrained_model_or_path': 'halffried/gyre_zoedepth', 'filename': 'ZoeD_M12_N.safetensors', 'model_type': "zoedepth"}}, + 'Zoe Depth': {'class': ZoeDetector, 'checkpoint': True, 'params': {'gamma_corrected': False}, 'load_config': {'pretrained_model_or_path': 'halffried/gyre_zoedepth', 'filename': 'ZoeD_M12_N.safetensors', 'model_type': "zoedepth"}}, 'Normal Bae': {'class': NormalBaeDetector, 'checkpoint': True, 'params': {}}, # segmentation models 'SegmentAnything': {'class': SamDetector, 'checkpoint': True, 'model': 'Base', 'params': {}}, @@ -105,6 +105,7 @@ def update(what, val): update(['SegmentAnything', 'model'], settings[20]) update(['Edge', 'params', 'pf'], settings[21]) update(['Edge', 'params', 'mode'], settings[22]) + update(['Zoe Depth', 'params', 'gamma_corrected'], settings[23]) class Processor(): diff --git a/modules/sd_models.py b/modules/sd_models.py index 60d4244ef..71b877f8b 100644 --- a/modules/sd_models.py +++ b/modules/sd_models.py @@ -1144,20 +1144,23 @@ def load_model(checkpoint_info=None, already_loaded_state_dict=None, timer=None, shared.log.debug(f'Model config loaded: {memory_stats()}') sd_model = None stdout = io.StringIO() - with contextlib.redirect_stdout(stdout): - """ - try: - clip_is_included_into_sd = sd1_clip_weight in state_dict or sd2_clip_weight in state_dict - with sd_disable_initialization.DisableInitialization(disable_clip=clip_is_included_into_sd): + if os.environ.get('SD_LDM_DEBUG', None) is not None: + sd_model = instantiate_from_config(sd_config.model) + else: + with contextlib.redirect_stdout(stdout): + """ + try: + clip_is_included_into_sd = sd1_clip_weight in state_dict or sd2_clip_weight in state_dict + with sd_disable_initialization.DisableInitialization(disable_clip=clip_is_included_into_sd): + sd_model = instantiate_from_config(sd_config.model) + except Exception as e: + shared.log.error(f'LDM: instantiate from config: {e}') sd_model = instantiate_from_config(sd_config.model) - except Exception as e: - shared.log.error(f'LDM: instantiate from config: {e}') + """ sd_model = instantiate_from_config(sd_config.model) - """ - sd_model = instantiate_from_config(sd_config.model) - for line in stdout.getvalue().splitlines(): - if len(line) > 0: - shared.log.info(f'LDM: {line.strip()}') + for line in stdout.getvalue().splitlines(): + if len(line) > 0: + shared.log.info(f'LDM: {line.strip()}') shared.log.debug(f"Model created from config: {checkpoint_config}") sd_model.used_config = checkpoint_config sd_model.has_accelerate = False diff --git a/modules/ui_control.py b/modules/ui_control.py index 9315426d4..8365d0127 100644 --- a/modules/ui_control.py +++ b/modules/ui_control.py @@ -556,6 +556,8 @@ def create_ui(_blocks: gr.Blocks=None): with gr.Accordion('Edge', open=True, elem_classes=['processor-settings']): settings.append(gr.Checkbox(label="Parameter free", value=True)) settings.append(gr.Radio(label="Mode", choices=['edge', 'gradient'], value='edge')) + with gr.Accordion('Zoe Depth', open=True, elem_classes=['processor-settings']): + settings.append(gr.Checkbox(label="Gamma corrected", value=False)) for setting in settings: setting.change(fn=processors.update_settings, inputs=settings, outputs=[]) diff --git a/wiki b/wiki index 7dadbd912..9a9713ecb 160000 --- a/wiki +++ b/wiki @@ -1 +1 @@ -Subproject commit 7dadbd912a1854bad2d7c6463eaecc458f2808c3 +Subproject commit 9a9713ecbbebaeebe9b15f895d6cab566d3a79a7 From c31671d3564253e902b86497fca2ffbdb99fc97c Mon Sep 17 00:00:00 2001 From: Vladimir Mandic Date: Wed, 27 Dec 2023 14:03:50 -0500 Subject: [PATCH 130/143] update changelog --- CHANGELOG.md | 3 +++ 1 file changed, 3 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index cb8602300..e65b67177 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -67,6 +67,9 @@ - creates fully compatible 3D scene from any image by using depth estimation and creating a fully populated mesh - scene can be freely viewed in 3D in the UI itself or downloaded for use in other applications + - [ONNX/Olive](https://github.com/vladmandic/automatic/wiki/ONNX-Olive) + - major work continues in olive branch, see wiki for details, thanks @lshqqytiger + as a highlight, 4-5 it/s using DirectML on AMD GPU translates to 23-25 it/s using ONNX/Olive! - **General** - new **onboarding** - if no models are found during startup, app will no longer ask to download default checkpoint From fd91b4b111f8730fca3abfc9c08f594f299d2ef9 Mon Sep 17 00:00:00 2001 From: Vladimir Mandic Date: Wed, 27 Dec 2023 19:01:09 -0500 Subject: [PATCH 131/143] nitpicks --- .gitignore | 1 + installer.py | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/.gitignore b/.gitignore index 32c9bf88d..c95a4cd30 100644 --- a/.gitignore +++ b/.gitignore @@ -32,6 +32,7 @@ cache *.lock *.zip *.rar +*.7z *.pyc /*.bat /*.sh diff --git a/installer.py b/installer.py index 8b571d02d..c36382ded 100644 --- a/installer.py +++ b/installer.py @@ -603,7 +603,7 @@ def list_extensions_folder(folder, quiet=False): if disabled_extensions_all != 'none': return [] disabled_extensions = opts.get('disabled_extensions', []) - enabled_extensions = [x for x in os.listdir(folder) if x not in disabled_extensions and not x.startswith('.')] + enabled_extensions = [x for x in os.listdir(folder) if os.path.isdir(os.path.join(folder, x)) and x not in disabled_extensions and not x.startswith('.')] if not quiet: log.info(f'Extensions: enabled={enabled_extensions} {name}') return enabled_extensions From 1a0e3c6bee765f4d43e58ed80b2ae4c261bfe096 Mon Sep 17 00:00:00 2001 From: Vladimir Mandic Date: Wed, 27 Dec 2023 19:49:13 -0500 Subject: [PATCH 132/143] cleanup logging --- modules/generation_parameters_copypaste.py | 3 ++- modules/images.py | 5 +++-- modules/modelloader.py | 4 +--- modules/processing.py | 7 ++++--- 4 files changed, 10 insertions(+), 9 deletions(-) diff --git a/modules/generation_parameters_copypaste.py b/modules/generation_parameters_copypaste.py index f28dc233b..9c5e50063 100644 --- a/modules/generation_parameters_copypaste.py +++ b/modules/generation_parameters_copypaste.py @@ -240,7 +240,8 @@ def parse_generation_parameters(x: str): res[k] = v except Exception: pass - res["Full quality"] = res.get('VAE', None) != 'TAESD' + if res.get('VAE', None) == 'TAESD': + res["Full quality"] = False debug(f"Parse prompt: {res}") return res diff --git a/modules/images.py b/modules/images.py index 22fe42148..87a6b43d0 100644 --- a/modules/images.py +++ b/modules/images.py @@ -569,13 +569,14 @@ def atomically_save_image(): fn = os.path.join(paths.data_path, shared.opts.save_log_fn) if not fn.endswith('.json'): fn += '.json' - entries = shared.readfile(fn) + entries = shared.readfile(fn, silent=True) idx = len(list(entries)) if idx == 0: entries = [] entry = { 'id': idx, 'filename': filename, 'time': datetime.datetime.now().isoformat(), 'info': exifinfo } entries.append(entry) - shared.writefile(entries, fn, mode='w') + shared.writefile(entries, fn, mode='w', silent=True) + shared.log.debug(f'Saving: json="{fn}" records={len(entries)}') save_queue.task_done() diff --git a/modules/modelloader.py b/modules/modelloader.py index 171d20a9e..9cd05dc82 100644 --- a/modules/modelloader.py +++ b/modules/modelloader.py @@ -102,7 +102,6 @@ def download_civit_preview(model_path: str, preview_url: str): os.remove(preview_file) res += f' error={e}' shared.log.error(f'CivitAI download error: url={preview_url} file={preview_file} written={written} {e}') - errors.display(e, 'CivitAI download error') shared.state.end() if img is None: return res @@ -236,8 +235,7 @@ def download_diffusers_model(hub_id: str, cache_dir: str = None, download_config shared.log.error(f"Diffusers download error: {hub_id} {err}") return None try: - # TODO diffusers is this real error? - model_info_dict = hf.model_info(hub_id).cardData if pipeline_dir is not None else None # pylint: disable=no-member + model_info_dict = hf.model_info(hub_id).cardData if pipeline_dir is not None else None except Exception: model_info_dict = None if model_info_dict is not None and "prior" in model_info_dict: # some checkpoints need to be downloaded as "hidden" as they just serve as pre- or post-pipelines of other pipelines diff --git a/modules/processing.py b/modules/processing.py index b40973bbb..d02b8b4b3 100644 --- a/modules/processing.py +++ b/modules/processing.py @@ -210,7 +210,7 @@ def __init__(self, sd_model=None, outpath_samples=None, outpath_grids=None, prom self.s_tmax = float('inf') # not representable as a standard ui option shared.opts.data['clip_skip'] = clip_skip self.task_args = {} - # TODO a1111 compatibility items + # a1111 compatibility items self.refiner_switch_at = 0 self.hr_prompt = '' self.all_hr_prompts = [] @@ -596,8 +596,6 @@ def create_infotext(p: StableDiffusionProcessing, all_prompts=None, all_seeds=No "Model": None if (not shared.opts.add_model_name_to_info) or (not shared.sd_model.sd_checkpoint_info.model_name) else shared.sd_model.sd_checkpoint_info.model_name.replace(',', '').replace(':', ''), "Model hash": getattr(p, 'sd_model_hash', None if (not shared.opts.add_model_hash_to_info) or (not shared.sd_model.sd_model_hash) else shared.sd_model.sd_model_hash), "VAE": (None if not shared.opts.add_model_name_to_info or modules.sd_vae.loaded_vae_file is None else os.path.splitext(os.path.basename(modules.sd_vae.loaded_vae_file))[0]) if p.full_quality else 'TAESD', - "Variation seed": None if p.subseed_strength == 0 else all_subseeds[index], - "Variation strength": None if p.subseed_strength == 0 else p.subseed_strength, "Seed resize from": None if p.seed_resize_from_w == 0 or p.seed_resize_from_h == 0 else f"{p.seed_resize_from_w}x{p.seed_resize_from_h}", "Clip skip": p.clip_skip if p.clip_skip > 1 else None, "Prompt2": p.refiner_prompt if len(p.refiner_prompt) > 0 else None, @@ -613,6 +611,9 @@ def create_infotext(p: StableDiffusionProcessing, all_prompts=None, all_seeds=No } if 'txt2img' in p.ops: pass + if shared.backend == shared.Backend.ORIGINAL: + args["Variation seed"] = None if p.subseed_strength == 0 else all_subseeds[index], + args["Variation strength"] = None if p.subseed_strength == 0 else p.subseed_strength, if 'hires' in p.ops or 'upscale' in p.ops: args["Second pass"] = p.enable_hr args["Hires force"] = p.hr_force From 8944d3bdfa23f9219d07cd011ea2b58a240dbff4 Mon Sep 17 00:00:00 2001 From: Vladimir Mandic Date: Wed, 27 Dec 2023 20:24:41 -0500 Subject: [PATCH 133/143] simplify processing call --- modules/processing.py | 2 +- modules/processing_diffusers.py | 33 +++++++++++++++++---------------- 2 files changed, 18 insertions(+), 17 deletions(-) diff --git a/modules/processing.py b/modules/processing.py index d02b8b4b3..72c61072b 100644 --- a/modules/processing.py +++ b/modules/processing.py @@ -919,7 +919,7 @@ def infotext(_inxex=0): # dummy function overriden if there are iterations elif shared.backend == shared.Backend.DIFFUSERS: from modules.processing_diffusers import process_diffusers - x_samples_ddim = process_diffusers(p, p.seeds, p.prompts, p.negative_prompts) + x_samples_ddim = process_diffusers(p) else: raise ValueError(f"Unknown backend {shared.backend}") diff --git a/modules/processing_diffusers.py b/modules/processing_diffusers.py index 2d881a1b1..47a3ed64e 100644 --- a/modules/processing_diffusers.py +++ b/modules/processing_diffusers.py @@ -24,7 +24,8 @@ debug_steps('Trace: STEPS') -def process_diffusers(p: StableDiffusionProcessing, seeds, prompts, negative_prompts): +def process_diffusers(p: StableDiffusionProcessing): + debug(f'Process diffusers args: {vars(p)}') results = [] def is_txt2img(): @@ -71,7 +72,7 @@ def save_intermediate(latents, suffix): info=create_infotext(p, p.all_prompts, p.all_seeds, p.all_subseeds, [], iteration=p.iteration, position_in_batch=i) decoded = vae_decode(latents=latents, model=shared.sd_model, output_type='pil', full_quality=p.full_quality) for j in range(len(decoded)): - images.save_image(decoded[j], path=p.outpath_samples, basename="", seed=seeds[i], prompt=prompts[i], extension=shared.opts.samples_format, info=info, p=p, suffix=suffix) + images.save_image(decoded[j], path=p.outpath_samples, basename="", seed=p.seeds[i], prompt=p.prompts[i], extension=shared.opts.samples_format, info=info, p=p, suffix=suffix) def diffusers_callback_legacy(step: int, timestep: int, latents: torch.FloatTensor): shared.state.sampling_step = step @@ -218,7 +219,7 @@ def set_pipeline_args(model, prompts: list, negative_prompts: list, prompts_2: t generator = None else: generator_device = devices.cpu if shared.opts.diffusers_generator_device == "CPU" else shared.device - generator = [torch.Generator(generator_device).manual_seed(s) for s in seeds] + generator = [torch.Generator(generator_device).manual_seed(s) for s in p.seeds] prompts, negative_prompts, prompts_2, negative_prompts_2 = fix_prompts(prompts, negative_prompts, prompts_2, negative_prompts_2) parser = 'Fixed attention' if shared.opts.prompt_attention != 'Fixed attention' and 'StableDiffusion' in model.__class__.__name__: @@ -246,9 +247,9 @@ def set_pipeline_args(model, prompts: list, negative_prompts: list, prompts_2: t args['negative_prompt'] = negative_prompts if hasattr(model, 'scheduler') and hasattr(model.scheduler, 'noise_sampler_seed') and hasattr(model.scheduler, 'noise_sampler'): model.scheduler.noise_sampler = None # noise needs to be reset instead of using cached values - model.scheduler.noise_sampler_seed = seeds[0] # some schedulers have internal noise generator and do not use pipeline generator + model.scheduler.noise_sampler_seed = p.seeds[0] # some schedulers have internal noise generator and do not use pipeline generator if 'noise_sampler_seed' in possible: - args['noise_sampler_seed'] = seeds[0] + args['noise_sampler_seed'] = p.seeds[0] if 'guidance_scale' in possible: args['guidance_scale'] = p.cfg_scale if 'generator' in possible and generator is not None: @@ -378,7 +379,7 @@ def update_sampler(sd_model, second_pass=False): # p.extra_generation_params['Sampler options'] = '' if len(getattr(p, 'init_images', [])) > 0: - while len(p.init_images) < len(prompts): + while len(p.init_images) < len(p.prompts): p.init_images.append(p.init_images[-1]) if shared.state.interrupted or shared.state.skipped: @@ -441,10 +442,10 @@ def calculate_refiner_steps(): base_args = set_pipeline_args( model=shared.sd_model, - prompts=prompts, - negative_prompts=negative_prompts, - prompts_2=[p.refiner_prompt] if len(p.refiner_prompt) > 0 else prompts, - negative_prompts_2=[p.refiner_negative] if len(p.refiner_negative) > 0 else negative_prompts, + prompts=p.prompts, + negative_prompts=p.negative_prompts, + prompts_2=[p.refiner_prompt] if len(p.refiner_prompt) > 0 else p.prompts, + negative_prompts_2=[p.refiner_negative] if len(p.refiner_negative) > 0 else p.negative_prompts, num_inference_steps=calculate_base_steps(), eta=shared.opts.scheduler_eta, guidance_scale=p.cfg_scale, @@ -510,10 +511,10 @@ def calculate_refiner_steps(): update_sampler(shared.sd_model, second_pass=True) hires_args = set_pipeline_args( model=shared.sd_model, - prompts=[p.refiner_prompt] if len(p.refiner_prompt) > 0 else prompts, - negative_prompts=[p.refiner_negative] if len(p.refiner_negative) > 0 else negative_prompts, - prompts_2=[p.refiner_prompt] if len(p.refiner_prompt) > 0 else prompts, - negative_prompts_2=[p.refiner_negative] if len(p.refiner_negative) > 0 else negative_prompts, + prompts=[p.refiner_prompt] if len(p.refiner_prompt) > 0 else p.prompts, + negative_prompts=[p.refiner_negative] if len(p.refiner_negative) > 0 else p.negative_prompts, + prompts_2=[p.refiner_prompt] if len(p.refiner_prompt) > 0 else p.prompts, + negative_prompts_2=[p.refiner_negative] if len(p.refiner_negative) > 0 else p.negative_prompts, num_inference_steps=calculate_hires_steps(), eta=shared.opts.scheduler_eta, guidance_scale=p.image_cfg_scale if p.image_cfg_scale is not None else p.cfg_scale, @@ -569,8 +570,8 @@ def calculate_refiner_steps(): output_type = 'np' refiner_args = set_pipeline_args( model=shared.sd_refiner, - prompts=[p.refiner_prompt] if len(p.refiner_prompt) > 0 else prompts[i], - negative_prompts=[p.refiner_negative] if len(p.refiner_negative) > 0 else negative_prompts[i], + prompts=[p.refiner_prompt] if len(p.refiner_prompt) > 0 else p.prompts[i], + negative_prompts=[p.refiner_negative] if len(p.refiner_negative) > 0 else p.negative_prompts[i], num_inference_steps=calculate_refiner_steps(), eta=shared.opts.scheduler_eta, # strength=p.denoising_strength, From 0171b4231277e6c392d81fd4ebe4c572916fcc81 Mon Sep 17 00:00:00 2001 From: Vladimir Mandic Date: Wed, 27 Dec 2023 20:58:44 -0500 Subject: [PATCH 134/143] fix prompt drag/drop --- javascript/dragDrop.js | 20 +++++++++++++------- javascript/imageParams.js | 13 ++++++------- 2 files changed, 19 insertions(+), 14 deletions(-) diff --git a/javascript/dragDrop.js b/javascript/dragDrop.js index bc7777102..8edb6483e 100644 --- a/javascript/dragDrop.js +++ b/javascript/dragDrop.js @@ -45,27 +45,33 @@ function dropReplaceImage(imgWrap, files) { } window.document.addEventListener('dragover', (e) => { + log('dragoverEvent', e); const target = e.composedPath()[0]; const imgWrap = target.closest('[data-testid="image"]'); if (!imgWrap && target.placeholder && target.placeholder.indexOf('Prompt') === -1) return; - e.stopPropagation(); - e.preventDefault(); - e.dataTransfer.dropEffect = 'copy'; + if ((e.dataTransfer?.files?.length || 0) > 0) { + e.stopPropagation(); + e.preventDefault(); + e.dataTransfer.dropEffect = 'copy'; + } }); window.document.addEventListener('drop', (e) => { + log('dropEvent', e); const target = e.composedPath()[0]; if (!target.placeholder) return; if (target.placeholder.indexOf('Prompt') === -1) return; const imgWrap = target.closest('[data-testid="image"]'); if (!imgWrap) return; - e.stopPropagation(); - e.preventDefault(); - const { files } = e.dataTransfer; - dropReplaceImage(imgWrap, files); + if ((e.dataTransfer?.files?.length || 0) > 0) { + e.stopPropagation(); + e.preventDefault(); + dropReplaceImage(imgWrap, e.dataTransfer.files); + } }); window.addEventListener('paste', (e) => { + log('pasteEvent', e.clipboardData); const { files } = e.clipboardData; if (!isValidImageList(files)) return; const visibleImageFields = [...gradioApp().querySelectorAll('[data-testid="image"]')] diff --git a/javascript/imageParams.js b/javascript/imageParams.js index c93523988..0b739cd00 100644 --- a/javascript/imageParams.js +++ b/javascript/imageParams.js @@ -9,16 +9,15 @@ async function initDragDrop() { if (!target.placeholder) return; if (target.placeholder.indexOf('Prompt') === -1) return; const promptTarget = get_tab_index('tabs') === 1 ? 'img2img_prompt_image' : 'txt2img_prompt_image'; - e.stopPropagation(); - e.preventDefault(); const imgParent = gradioApp().getElementById(promptTarget); - if (!imgParent) return; - const { files } = e.dataTransfer; const fileInput = imgParent.querySelector('input[type="file"]'); - if (fileInput) { - fileInput.files = files; + if (!imgParent || !fileInput) return; + if ((e.dataTransfer?.files?.length || 0) > 0) { + e.stopPropagation(); + e.preventDefault(); + fileInput.files = e.dataTransfer.files; fileInput.dispatchEvent(new Event('change')); - log('dropEvent'); + log('dropEvent files', fileInput.files); } }); } From af606973cfbf299f98838b6dee133abd7bee8045 Mon Sep 17 00:00:00 2001 From: Vladimir Mandic Date: Wed, 27 Dec 2023 21:03:39 -0500 Subject: [PATCH 135/143] remove extra logging --- javascript/dragDrop.js | 3 --- 1 file changed, 3 deletions(-) diff --git a/javascript/dragDrop.js b/javascript/dragDrop.js index 8edb6483e..73c0c33c3 100644 --- a/javascript/dragDrop.js +++ b/javascript/dragDrop.js @@ -45,7 +45,6 @@ function dropReplaceImage(imgWrap, files) { } window.document.addEventListener('dragover', (e) => { - log('dragoverEvent', e); const target = e.composedPath()[0]; const imgWrap = target.closest('[data-testid="image"]'); if (!imgWrap && target.placeholder && target.placeholder.indexOf('Prompt') === -1) return; @@ -57,7 +56,6 @@ window.document.addEventListener('dragover', (e) => { }); window.document.addEventListener('drop', (e) => { - log('dropEvent', e); const target = e.composedPath()[0]; if (!target.placeholder) return; if (target.placeholder.indexOf('Prompt') === -1) return; @@ -71,7 +69,6 @@ window.document.addEventListener('drop', (e) => { }); window.addEventListener('paste', (e) => { - log('pasteEvent', e.clipboardData); const { files } = e.clipboardData; if (!isValidImageList(files)) return; const visibleImageFields = [...gradioApp().querySelectorAll('[data-testid="image"]')] From eef08675b2f37b2c981b7b27302ccca0c00f327c Mon Sep 17 00:00:00 2001 From: Vladimir Mandic Date: Thu, 28 Dec 2023 07:14:14 -0500 Subject: [PATCH 136/143] improve inpainting quality --- CHANGELOG.md | 4 +++- modules/processing.py | 12 +++++------- modules/processing_diffusers.py | 21 ++------------------- modules/ui.py | 2 +- 4 files changed, 11 insertions(+), 28 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index e65b67177..54b002231 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,6 @@ # Change Log for SD.Next -## Update for 2023-12-27 +## Update for 2023-12-28 - **Control** - native implementation of all image control methods: @@ -48,6 +48,8 @@ - **Schedulers** - add timesteps range, changing it will make scheduler to be over-complete or under-complete - add rescale betas with zero SNR option (applicable to Euler, Euler a and DDIM, allows for higher dynamic range) + - **Inpaint** + - improved quality when using mask blur and padding - **UI** - 3 new native UI themes: **orchid-dreams**, **emerald-paradise** and **timeless-beige**, thanks @illu_Zn - more dynamic controls depending on the backend (original or diffusers) diff --git a/modules/processing.py b/modules/processing.py index 72c61072b..e749eb59c 100644 --- a/modules/processing.py +++ b/modules/processing.py @@ -1227,9 +1227,9 @@ def __init__(self, init_images: list = None, resize_mode: int = 0, resize_name: self.image_mask = mask self.latent_mask = None self.mask_for_overlay = None - self.mask_blur = mask_blur self.mask_blur_x: int = 4 self.mask_blur_y: int = 4 + self.mask_blur = mask_blur self.inpainting_fill = inpainting_fill self.inpaint_full_res = inpaint_full_res self.inpaint_full_res_padding = inpaint_full_res_padding @@ -1251,15 +1251,13 @@ def __init__(self, init_images: list = None, resize_mode: int = 0, resize_name: @property def mask_blur(self): - if self.mask_blur_x == self.mask_blur_y: - return self.mask_blur_x - return None + mask_blur = max(self.mask_blur_x, self.mask_blur_y) + return mask_blur @mask_blur.setter def mask_blur(self, value): - if isinstance(value, int): - self.mask_blur_x = value - self.mask_blur_y = value + self.mask_blur_x = value + self.mask_blur_y = value def init(self, all_prompts, all_seeds, all_subseeds): if shared.backend == shared.Backend.DIFFUSERS and self.image_mask is not None and not self.is_control: diff --git a/modules/processing_diffusers.py b/modules/processing_diffusers.py index 47a3ed64e..da616b24a 100644 --- a/modules/processing_diffusers.py +++ b/modules/processing_diffusers.py @@ -164,34 +164,17 @@ def task_specific_kwargs(model): p.ops.append('inpaint') if getattr(p, 'mask', None) is None: p.mask = TF.to_pil_image(torch.ones_like(TF.to_tensor(p.init_images[0]))).convert("L") + p.mask = shared.sd_model.mask_processor.blur(p.mask, blur_factor=p.mask_blur) width = 8 * math.ceil(p.init_images[0].width / 8) height = 8 * math.ceil(p.init_images[0].height / 8) - # option-1: use images as inputs task_args = { 'image': p.init_images, 'mask_image': p.mask, 'strength': p.denoising_strength, 'height': height, 'width': width, + # 'padding_mask_crop': p.inpaint_full_res_padding # done back in main processing method } - """ # option-2: preprocess images into latents using diffusers - vae_scale_factor = 2 ** (len(model.vae.config.block_out_channels) - 1) - image_processor = diffusers.image_processor.VaeImageProcessor(vae_scale_factor=vae_scale_factor) - mask_processor = diffusers.image_processor.VaeImageProcessor(vae_scale_factor=vae_scale_factor, do_normalize=False, do_binarize=True, do_convert_grayscale=True) - init_image = image_processor.preprocess(p.init_images[0], width=width, height=height) - mask_image = mask_processor.preprocess(p.mask, width=width, height=height) - task_args = {"image": p.init_images, "mask_image": p.mask, "strength": p.denoising_strength, "height": height, "width": width} - """ - """ # option-2: manually assemble masked image latents - masked_image_latents = [] - mask_image = TF.to_tensor(p.mask) - for init_image in p.init_images: - init_image = TF.to_tensor(p.init_images[0]) - masked_image = init_image * (mask_image > 0.5) - masked_image_latents.append(torch.cat([masked_image, mask_image], dim=0)) - masked_image_latents = torch.stack(masked_image_latents, dim=0).to(shared.device) - task_args = {"image": p.init_images, "mask_image": mask_image, "masked_image_latents": masked_image_latents, "strength": p.denoising_strength, "height": height, "width": width} - """ if model.__class__.__name__ == 'LatentConsistencyModelPipeline' and hasattr(p, 'init_images') and len(p.init_images) > 0: p.ops.append('lcm') init_latents = [vae_encode(image, model=shared.sd_model, full_quality=p.full_quality).squeeze(dim=0) for image in p.init_images] diff --git a/modules/ui.py b/modules/ui.py index 60c7ba0a8..6bba53a3e 100644 --- a/modules/ui.py +++ b/modules/ui.py @@ -472,7 +472,7 @@ def create_hires_inputs(tab): hr_force = gr.Checkbox(label='Force Hires', value=False, elem_id=f"{tab}_hr_force") with FormRow(elem_id=f"{tab}_hires_fix_row2", variant="compact"): hr_second_pass_steps = gr.Slider(minimum=0, maximum=99, step=1, label='Hires steps', elem_id=f"{tab}_steps_alt", value=20) - hr_scale = gr.Slider(minimum=1.0, maximum=4.0, step=0.05, label="Upscale by", value=2.0, elem_id=f"{tab}_hr_scale") + hr_scale = gr.Slider(minimum=1.0, maximum=8.0, step=0.05, label="Upscale by", value=2.0, elem_id=f"{tab}_hr_scale") with FormRow(elem_id=f"{tab}_hires_fix_row3", variant="compact"): hr_resize_x = gr.Slider(minimum=0, maximum=4096, step=8, label="Resize width to", value=0, elem_id=f"{tab}_hr_resize_x") hr_resize_y = gr.Slider(minimum=0, maximum=4096, step=8, label="Resize height to", value=0, elem_id=f"{tab}_hr_resize_y") From 8c395a8bc9e36a40e7e7df94a5db64e59a529eeb Mon Sep 17 00:00:00 2001 From: Vladimir Mandic Date: Thu, 28 Dec 2023 11:30:12 -0500 Subject: [PATCH 137/143] improve animatediff --- CHANGELOG.md | 9 ++++--- modules/face_restoration.py | 2 -- modules/processing_diffusers.py | 5 +++- scripts/animatediff.py | 43 ++++++++++++++++++++++++++------- 4 files changed, 43 insertions(+), 16 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 54b002231..bd176b3ef 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -30,14 +30,15 @@ - *note*: GPU VRAM limits do not automatically go away so be careful when using it with large resolutions in the future, expect more optimizations, especially related to offloading/slicing/tiling, but at the moment this is pretty much experimental-only - - **AnimateDiff** + - [AnimateDiff](https://github.com/guoyww/animatediff/) + - overall improved quality - can now be used with *second pass* - enhance, upscale and hires your videos! - - **IP Adapter** - - add support for `ip-adapter-plus_sd15`, `ip-adapter-plus-face_sd15` and `ip-adapter-full-face_sd15` + - [IP Adapter](https://github.com/tencent-ailab/IP-Adapter) + - add support for **ip-adapter-plus_sd15, ip-adapter-plus-face_sd15 and ip-adapter-full-face_sd15** - can now be used in *xyz-grid* - **Text-to-Video** - in text tab, select `text-to-video` script - - supported models: ModelScope v1.7b, ZeroScope v1, ZeroScope v1.1, ZeroScope v2, ZeroScope v2 Dark, Potat v1 + - supported models: **ModelScope v1.7b, ZeroScope v1, ZeroScope v1.1, ZeroScope v2, ZeroScope v2 Dark, Potat v1** *if you know of any other t2v models you'd like to see supported, let me know!* - models are auto-downloaded on first use - *note*: current base model will be unloaded to free up resources diff --git a/modules/face_restoration.py b/modules/face_restoration.py index 4ae53d21b..55e1033c6 100644 --- a/modules/face_restoration.py +++ b/modules/face_restoration.py @@ -13,7 +13,5 @@ def restore_faces(np_image): face_restorers = [x for x in shared.face_restorers if x.name() == shared.opts.face_restoration_model or shared.opts.face_restoration_model is None] if len(face_restorers) == 0: return np_image - face_restorer = face_restorers[0] - return face_restorer.restore(np_image) diff --git a/modules/processing_diffusers.py b/modules/processing_diffusers.py index da616b24a..66877a83a 100644 --- a/modules/processing_diffusers.py +++ b/modules/processing_diffusers.py @@ -453,7 +453,10 @@ def calculate_refiner_steps(): t1 = time.time() shared.log.debug(f'Profile: pipeline call: {t1-t0:.2f}') if not hasattr(output, 'images') and hasattr(output, 'frames'): - shared.log.debug(f'Generated: frames={len(output.frames[0])}') + if hasattr(output.frames[0], 'shape'): + shared.log.debug(f'Generated: frames={output.frames[0].shape[1]}') + else: + shared.log.debug(f'Generated: frames={len(output.frames[0])}') output.images = output.frames[0] except AssertionError as e: shared.log.info(e) diff --git a/scripts/animatediff.py b/scripts/animatediff.py index 067e1b82d..074d31d6b 100644 --- a/scripts/animatediff.py +++ b/scripts/animatediff.py @@ -10,6 +10,7 @@ - AnimateFace: https://huggingface.co/nlper2022/animatediff_face_512/tree/main """ +import os import gradio as gr import diffusers from modules import scripts, processing, shared, devices, sd_models @@ -18,12 +19,15 @@ # config ADAPTERS = { 'None': None, - 'Motion 1.4': 'guoyww/animatediff-motion-adapter-v1-4', - 'Motion 1.5 v1': 'guoyww/animatediff-motion-adapter-v1-5', + 'Motion 1.5 v3' :'vladmandic/animatediff-v3', 'Motion 1.5 v2' :'guoyww/animatediff-motion-adapter-v1-5-2', - # 'Motion SD-XL Beta v1' :'vladmandic/animatediff-sdxl', + 'Motion 1.5 v1': 'guoyww/animatediff-motion-adapter-v1-5', + 'Motion 1.4': 'guoyww/animatediff-motion-adapter-v1-4', 'TemporalDiff': 'vladmandic/temporaldiff', 'AnimateFace': 'vladmandic/animateface', + # 'LongAnimateDiff 32': 'vladmandic/longanimatediff-32', + # 'LongAnimateDiff 64': 'vladmandic/longanimatediff-64', + # 'Motion SD-XL Beta v1' :'vladmandic/animatediff-sdxl', } LORAS = { 'None': None, @@ -70,6 +74,10 @@ def set_adapter(adapter_name: str = 'None'): # shared.sd_model.image_encoder = None shared.sd_model.unet.set_default_attn_processor() shared.sd_model.unet.config.encoder_hid_dim_type = None + if adapter_name.endswith('.ckpt') or adapter_name.endswith('.safetensors'): + import huggingface_hub as hf + folder, filename = os.path.split(adapter_name) + adapter_name = hf.hf_hub_download(repo_id=folder, filename=filename, cache_dir=shared.opts.diffusers_dir) try: shared.log.info(f'AnimateDiff load: adapter="{adapter_name}"') motion_adapter = None @@ -77,13 +85,14 @@ def set_adapter(adapter_name: str = 'None'): motion_adapter.to(shared.device) sd_models.set_diffuser_options(motion_adapter, vae=None, op='adapter') loaded_adapter = adapter_name - new_pipe = diffusers.AnimateDiffPipeline( vae=shared.sd_model.vae, text_encoder=shared.sd_model.text_encoder, tokenizer=shared.sd_model.tokenizer, unet=shared.sd_model.unet, scheduler=shared.sd_model.scheduler, + feature_extractor=getattr(shared.sd_model, 'feature_extractor', None), + image_encoder=getattr(shared.sd_model, 'image_encoder', None), motion_adapter=motion_adapter, ) orig_pipe = shared.sd_model @@ -119,7 +128,9 @@ def video_type_change(video_type): with gr.Accordion('AnimateDiff', open=False, elem_id='animatediff'): with gr.Row(): adapter_index = gr.Dropdown(label='Adapter', choices=list(ADAPTERS), value='None') - frames = gr.Slider(label='Frames', minimum=1, maximum=32, step=1, value=16) + frames = gr.Slider(label='Frames', minimum=1, maximum=64, step=1, value=16) + with gr.Row(): + override_scheduler = gr.Checkbox(label='Override sampler', value=True) with gr.Row(): lora_index = gr.Dropdown(label='Lora', choices=list(LORAS), value='None') strength = gr.Slider(label='Strength', minimum=0.0, maximum=2.0, step=0.05, value=1.0) @@ -133,15 +144,29 @@ def video_type_change(video_type): mp4_pad = gr.Slider(label='Pad frames', minimum=0, maximum=24, step=1, value=1, visible=False) mp4_interpolate = gr.Slider(label='Interpolate frames', minimum=0, maximum=24, step=1, value=0, visible=False) video_type.change(fn=video_type_change, inputs=[video_type], outputs=[duration, gif_loop, mp4_pad, mp4_interpolate]) - return [adapter_index, frames, lora_index, strength, latent_mode, video_type, duration, gif_loop, mp4_pad, mp4_interpolate] + return [adapter_index, frames, lora_index, strength, latent_mode, video_type, duration, gif_loop, mp4_pad, mp4_interpolate, override_scheduler] - def process(self, p: processing.StableDiffusionProcessing, adapter_index, frames, lora_index, strength, latent_mode, video_type, duration, gif_loop, mp4_pad, mp4_interpolate): # pylint: disable=arguments-differ, unused-argument + def process(self, p: processing.StableDiffusionProcessing, adapter_index, frames, lora_index, strength, latent_mode, video_type, duration, gif_loop, mp4_pad, mp4_interpolate, override_scheduler): # pylint: disable=arguments-differ, unused-argument adapter = ADAPTERS[adapter_index] lora = LORAS[lora_index] set_adapter(adapter) if motion_adapter is None: return - shared.log.debug(f'AnimateDiff: adapter="{adapter}" lora="{lora}" strength={strength} video={video_type}') + if override_scheduler: + p.sampler_name = 'Default' + shared.sd_model.scheduler = diffusers.DDIMScheduler( + beta_start=0.00085, + beta_end=0.012, + beta_schedule="linear", + clip_sample=False, + num_train_timesteps=1000, + rescale_betas_zero_snr=False, + set_alpha_to_one=True, + steps_offset=0, + timestep_spacing="linspace", + trained_betas=None, + ) + shared.log.debug(f'AnimateDiff: adapter="{adapter}" lora="{lora}" strength={strength} video={video_type} scheduler={shared.sd_model.scheduler.__class__.__name__ if override_scheduler else p.sampler_name}') if lora is not None and lora != 'None': shared.sd_model.load_lora_weights(lora, adapter_name=lora) shared.sd_model.set_adapters([lora], adapter_weights=[strength]) @@ -155,7 +180,7 @@ def process(self, p: processing.StableDiffusionProcessing, adapter_index, frames if not latent_mode: p.task_args['output_type'] = 'np' - def postprocess(self, p: processing.StableDiffusionProcessing, processed: processing.Processed, adapter_index, frames, lora_index, strength, latent_mode, video_type, duration, gif_loop, mp4_pad, mp4_interpolate): # pylint: disable=arguments-differ, unused-argument + def postprocess(self, p: processing.StableDiffusionProcessing, processed: processing.Processed, adapter_index, frames, lora_index, strength, latent_mode, video_type, duration, gif_loop, mp4_pad, mp4_interpolate, override_scheduler): # pylint: disable=arguments-differ, unused-argument from modules.images import save_video if video_type != 'None': save_video(p, filename=None, images=processed.images, video_type=video_type, duration=duration, loop=gif_loop, pad=mp4_pad, interpolate=mp4_interpolate) From 1c7ab986502785d7547c490b0186f854f4336c5d Mon Sep 17 00:00:00 2001 From: Vladimir Mandic Date: Thu, 28 Dec 2023 11:35:13 -0500 Subject: [PATCH 138/143] set vae options on change --- modules/sd_models.py | 24 ++++++++++++------------ modules/sd_vae.py | 3 +-- 2 files changed, 13 insertions(+), 14 deletions(-) diff --git a/modules/sd_models.py b/modules/sd_models.py index 71b877f8b..675358b9d 100644 --- a/modules/sd_models.py +++ b/modules/sd_models.py @@ -706,6 +706,18 @@ def set_diffuser_options(sd_model, vae = None, op: str = 'model'): if hasattr(sd_model, "watermark"): sd_model.watermark = NoWatermark() sd_model.has_accelerate = False + if hasattr(sd_model, "vae"): + if vae is not None: + sd_model.vae = vae + if shared.opts.diffusers_vae_upcast != 'default': + if shared.opts.diffusers_vae_upcast == 'true': + sd_model.vae.config.force_upcast = True + else: + sd_model.vae.config.force_upcast = False + if shared.opts.no_half_vae: + devices.dtype_vae = torch.float32 + sd_model.vae.to(devices.dtype_vae) + shared.log.debug(f'Setting {op} VAE: name={sd_vae.loaded_vae_file} upcast={sd_model.vae.config.get("force_upcast", None)}') if hasattr(sd_model, "enable_model_cpu_offload"): if (shared.cmd_opts.medvram and devices.backend != "directml") or shared.opts.diffusers_model_cpu_offload: shared.log.debug(f'Setting {op}: enable model CPU offload') @@ -744,18 +756,6 @@ def set_diffuser_options(sd_model, vae = None, op: str = 'model'): sd_model.enable_attention_slicing() else: sd_model.disable_attention_slicing() - if hasattr(sd_model, "vae"): - if vae is not None: - sd_model.vae = vae - if shared.opts.diffusers_vae_upcast != 'default': - if shared.opts.diffusers_vae_upcast == 'true': - sd_model.vae.config.force_upcast = True - else: - sd_model.vae.config.force_upcast = False - if shared.opts.no_half_vae: - devices.dtype_vae = torch.float32 - sd_model.vae.to(devices.dtype_vae) - shared.log.debug(f'Setting {op} VAE: name={sd_vae.loaded_vae_file} upcast={sd_model.vae.config.get("force_upcast", None)}') if hasattr(sd_model, "vqvae"): sd_model.vqvae.to(torch.float32) # vqvae is producing nans in fp16 if shared.opts.cross_attention_optimization == "xFormers" and hasattr(sd_model, 'enable_xformers_memory_efficient_attention'): diff --git a/modules/sd_vae.py b/modules/sd_vae.py index 46a1e5a45..f3707e3d8 100644 --- a/modules/sd_vae.py +++ b/modules/sd_vae.py @@ -274,8 +274,7 @@ def reload_vae_weights(sd_model=None, vae_file=unspecified): if hasattr(shared.sd_model, "vae") and hasattr(shared.sd_model, "sd_checkpoint_info"): vae = load_vae_diffusers(shared.sd_model.sd_checkpoint_info.filename, vae_file, vae_source) if vae is not None: - if vae is not None: - sd_model.vae = vae + sd_models.set_diffuser_options(sd_model, vae=vae, op='vae') if not shared.cmd_opts.lowvram and not shared.cmd_opts.medvram and not getattr(sd_model, 'has_accelerate', False): sd_model.to(devices.device) From 4d4edad80676f920a0541092f185200d65959ac3 Mon Sep 17 00:00:00 2001 From: JetVarimax <140423365+JetVarimax@users.noreply.github.com> Date: Thu, 28 Dec 2023 18:20:33 +0000 Subject: [PATCH 139/143] Apostrophe --- CHANGELOG.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index bd176b3ef..59e4c0451 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -39,7 +39,7 @@ - **Text-to-Video** - in text tab, select `text-to-video` script - supported models: **ModelScope v1.7b, ZeroScope v1, ZeroScope v1.1, ZeroScope v2, ZeroScope v2 Dark, Potat v1** - *if you know of any other t2v models you'd like to see supported, let me know!* + *if you know of any other t2v models youd like to see supported, let me know!* - models are auto-downloaded on first use - *note*: current base model will be unloaded to free up resources - **Prompt scheduling** now implemented for Diffusers backend, thanks @AI-Casanova From 4136d60177e56e1522907b75942283e4e77f9eee Mon Sep 17 00:00:00 2001 From: Disty0 Date: Fri, 29 Dec 2023 01:48:45 +0300 Subject: [PATCH 140/143] IPEX Attention reduce CPU overhead --- modules/intel/ipex/attention.py | 150 +++++++++++++++++--------------- modules/intel/ipex/diffusers.py | 21 +++-- 2 files changed, 93 insertions(+), 78 deletions(-) diff --git a/modules/intel/ipex/attention.py b/modules/intel/ipex/attention.py index 30a889b11..ce795771b 100644 --- a/modules/intel/ipex/attention.py +++ b/modules/intel/ipex/attention.py @@ -1,41 +1,90 @@ +import os import torch import intel_extension_for_pytorch as ipex # pylint: disable=import-error, unused-import +from functools import cache # pylint: disable=protected-access, missing-function-docstring, line-too-long -original_torch_bmm = torch.bmm -def torch_bmm_32_bit(input, mat2, *, out=None): - # ARC GPUs can't allocate more than 4GB to a single block, Slice it: - batch_size_attention, input_tokens, mat2_shape = input.shape[0], input.shape[1], mat2.shape[2] - block_multiply = input.element_size() - slice_block_size = input_tokens * mat2_shape / 1024 / 1024 * block_multiply +# ARC GPUs can't allocate more than 4GB to a single block so we slice the attetion layers + +sdpa_slice_trigger_rate = float(os.environ.get('IPEX_SDPA_SLICE_TRIGGER_RATE', 6)) +attention_slice_rate = float(os.environ.get('IPEX_ATTENTION_SLICE_RATE', 4)) + +# Find something divisible with the input_tokens +@cache +def find_slice_size(slice_size, slice_block_size): + while (slice_size * slice_block_size) > attention_slice_rate: + slice_size = slice_size // 2 + if slice_size <= 1: + slice_size = 1 + break + return slice_size + +# Find slice sizes for SDPA +@cache +def find_sdpa_slice_sizes(query_shape, query_element_size): + if len(query_shape) == 3: + batch_size_attention, query_tokens, shape_three = query_shape + shape_four = 1 + else: + batch_size_attention, query_tokens, shape_three, shape_four = query_shape + + slice_block_size = query_tokens * shape_three * shape_four / 1024 / 1024 * query_element_size block_size = batch_size_attention * slice_block_size split_slice_size = batch_size_attention - if block_size > 4: + split_2_slice_size = query_tokens + split_3_slice_size = shape_three + + do_split = False + do_split_2 = False + do_split_3 = False + + if block_size > sdpa_slice_trigger_rate: do_split = True - # Find something divisible with the input_tokens - while (split_slice_size * slice_block_size) > 4: - split_slice_size = split_slice_size // 2 - if split_slice_size <= 1: - split_slice_size = 1 - break - split_2_slice_size = input_tokens - if split_slice_size * slice_block_size > 4: - slice_block_size_2 = split_slice_size * mat2_shape / 1024 / 1024 * block_multiply + split_slice_size = find_slice_size(split_slice_size, slice_block_size) + if split_slice_size * slice_block_size > attention_slice_rate: + slice_block_size_2 = split_slice_size * shape_three * shape_four / 1024 / 1024 * query_element_size do_split_2 = True - # Find something divisible with the input_tokens - while (split_2_slice_size * slice_block_size_2) > 4: - split_2_slice_size = split_2_slice_size // 2 - if split_2_slice_size <= 1: - split_2_slice_size = 1 - break - else: - do_split_2 = False - else: - do_split = False + split_2_slice_size = find_slice_size(split_2_slice_size, slice_block_size_2) + if split_2_slice_size * slice_block_size_2 > attention_slice_rate: + slice_block_size_3 = split_slice_size * split_2_slice_size * shape_four / 1024 / 1024 * query_element_size + do_split_3 = True + split_3_slice_size = find_slice_size(split_3_slice_size, slice_block_size_3) + + return do_split, do_split_2, do_split_3, split_slice_size, split_2_slice_size, split_3_slice_size + +# Find slice sizes for BMM +@cache +def find_bmm_slice_sizes(input_shape, input_element_size, mat2_shape): + batch_size_attention, input_tokens, mat2_atten_shape = input_shape[0], input_shape[1], mat2_shape[2] + slice_block_size = input_tokens * mat2_atten_shape / 1024 / 1024 * input_element_size + block_size = batch_size_attention * slice_block_size + split_slice_size = batch_size_attention + split_2_slice_size = input_tokens + + do_split = False + do_split_2 = False + + if block_size > attention_slice_rate: + do_split = True + split_slice_size = find_slice_size(split_slice_size, slice_block_size) + if split_slice_size * slice_block_size > attention_slice_rate: + slice_block_size_2 = split_slice_size * mat2_atten_shape / 1024 / 1024 * input_element_size + do_split_2 = True + split_2_slice_size = find_slice_size(split_2_slice_size, slice_block_size_2) + + return do_split, do_split_2, split_slice_size, split_2_slice_size + + +original_torch_bmm = torch.bmm +def torch_bmm_32_bit(input, mat2, *, out=None): + do_split, do_split_2, split_slice_size, split_2_slice_size = find_bmm_slice_sizes(input.shape, input.element_size(), mat2.shape) + + # Slice BMM if do_split: + batch_size_attention, input_tokens = input.shape[0], input.shape[1] hidden_states = torch.zeros(input.shape[0], input.shape[1], mat2.shape[2], device=input.device, dtype=input.dtype) for i in range(batch_size_attention // split_slice_size): start_idx = i * split_slice_size @@ -61,54 +110,11 @@ def torch_bmm_32_bit(input, mat2, *, out=None): original_scaled_dot_product_attention = torch.nn.functional.scaled_dot_product_attention def scaled_dot_product_attention_32_bit(query, key, value, attn_mask=None, dropout_p=0.0, is_causal=False): - # ARC GPUs can't allocate more than 4GB to a single block, Slice it: - if len(query.shape) == 3: - batch_size_attention, query_tokens, shape_three = query.shape - shape_four = 1 - else: - batch_size_attention, query_tokens, shape_three, shape_four = query.shape - - block_multiply = query.element_size() - slice_block_size = query_tokens * shape_three * shape_four / 1024 / 1024 * block_multiply - block_size = batch_size_attention * slice_block_size - - split_slice_size = batch_size_attention - if block_size > 6: - do_split = True - # Find something divisible with the batch_size_attention - while (split_slice_size * slice_block_size) > 4: - split_slice_size = split_slice_size // 2 - if split_slice_size <= 1: - split_slice_size = 1 - break - split_2_slice_size = query_tokens - if split_slice_size * slice_block_size > 4: - slice_block_size_2 = split_slice_size * shape_three * shape_four / 1024 / 1024 * block_multiply - do_split_2 = True - # Find something divisible with the query_tokens - while (split_2_slice_size * slice_block_size_2) > 4: - split_2_slice_size = split_2_slice_size // 2 - if split_2_slice_size <= 1: - split_2_slice_size = 1 - break - split_3_slice_size = shape_three - if split_2_slice_size * slice_block_size_2 > 4: - slice_block_size_3 = split_slice_size * split_2_slice_size * shape_four / 1024 / 1024 * block_multiply - do_split_3 = True - # Find something divisible with the shape_three - while (split_3_slice_size * slice_block_size_3) > 4: - split_3_slice_size = split_3_slice_size // 2 - if split_3_slice_size <= 1: - split_3_slice_size = 1 - break - else: - do_split_3 = False - else: - do_split_2 = False - else: - do_split = False + do_split, do_split_2, do_split_3, split_slice_size, split_2_slice_size, split_3_slice_size = find_sdpa_slice_sizes(query.shape, query.element_size()) + # Slice SDPA if do_split: + batch_size_attention, query_tokens, shape_three = query.shape[0], query.shape[1], query.shape[2] hidden_states = torch.zeros(query.shape, device=query.device, dtype=query.dtype) for i in range(batch_size_attention // split_slice_size): start_idx = i * split_slice_size diff --git a/modules/intel/ipex/diffusers.py b/modules/intel/ipex/diffusers.py index c32af507b..6bc6aae31 100644 --- a/modules/intel/ipex/diffusers.py +++ b/modules/intel/ipex/diffusers.py @@ -1,10 +1,24 @@ +import os import torch import intel_extension_for_pytorch as ipex # pylint: disable=import-error, unused-import import diffusers #0.24.0 # pylint: disable=import-error from diffusers.models.attention_processor import Attention +from functools import cache # pylint: disable=protected-access, missing-function-docstring, line-too-long +attention_slice_rate = float(os.environ.get('IPEX_ATTENTION_SLICE_RATE', 4)) + +@cache +def find_slice_size(slice_size, slice_block_size): + while (slice_size * slice_block_size) > attention_slice_rate: + slice_size = slice_size // 2 + if slice_size <= 1: + slice_size = 1 + break + return slice_size + + class SlicedAttnProcessor: # pylint: disable=too-few-public-methods r""" Processor for implementing sliced attention. @@ -61,12 +75,7 @@ def __call__(self, attn: Attention, hidden_states, encoder_hidden_states=None, a split_2_slice_size = query_tokens if block_size > 4: do_split_2 = True - #Find something divisible with the query_tokens - while (split_2_slice_size * slice_block_size) > 4: - split_2_slice_size = split_2_slice_size // 2 - if split_2_slice_size <= 1: - split_2_slice_size = 1 - break + split_2_slice_size = find_slice_size(split_2_slice_size, slice_block_size) else: do_split_2 = False From 066c27c619b34163dfe4d818fa5c8c31c4d5c241 Mon Sep 17 00:00:00 2001 From: Disty0 Date: Fri, 29 Dec 2023 11:14:52 +0300 Subject: [PATCH 141/143] Update changelog --- CHANGELOG.md | 2 +- installer.py | 1 + 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 59e4c0451..38dee1d32 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -102,7 +102,7 @@ - fix IPEX Optimize not applying with Diffusers backend - disable 32bit workarounds if the GPU supports 64bit - add `DISABLE_IPEXRUN` and `DISABLE_IPEX_1024_WA` environment variables - - compatibility improvements + - performance and compatibility improvements - **OpenVINO**, thanks @disty0 - **8 bit support for CPUs** - reduce System RAM usage diff --git a/installer.py b/installer.py index c36382ded..5972f66c0 100644 --- a/installer.py +++ b/installer.py @@ -452,6 +452,7 @@ def check_torch(): install(os.environ.get('DPCPP_PACKAGE', 'mkl-dpcpp==2024.0.0'), 'mkl-dpcpp') torch_command = os.environ.get('TORCH_COMMAND', f'{pytorch_pip} {torchvision_pip} {ipex_pip}') install(os.environ.get('OPENVINO_PACKAGE', 'openvino==2023.2.0'), 'openvino', ignore=True) + install('nncf==2.7.0', 'nncf', ignore=True) install('onnxruntime-openvino', 'onnxruntime-openvino', ignore=True) elif allow_openvino and args.use_openvino: log.info('Using OpenVINO') From b10a579421348fe907c607ac1def3aeaba1a3860 Mon Sep 17 00:00:00 2001 From: Disty0 Date: Fri, 29 Dec 2023 12:26:00 +0300 Subject: [PATCH 142/143] IPEX 8 bit support with NNCF --- CHANGELOG.md | 1 + modules/intel/openvino/__init__.py | 4 ++-- modules/sd_hijack.py | 5 ----- modules/sd_models_compile.py | 21 +++++++++++++++++++++ modules/shared.py | 3 ++- 5 files changed, 26 insertions(+), 8 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 38dee1d32..c25e8c7dc 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -99,6 +99,7 @@ if you get file not found errors, set `DISABLE_IPEXRUN=1` and run the webui with `--reinstall` - built-in *MKL* and *DPCPP* for IPEX, no need to install OneAPI anymore - **StableVideoDiffusion** is now supported with IPEX + - **8 bit support with NNCF** on Diffusers backend - fix IPEX Optimize not applying with Diffusers backend - disable 32bit workarounds if the GPU supports 64bit - add `DISABLE_IPEXRUN` and `DISABLE_IPEX_1024_WA` environment variables diff --git a/modules/intel/openvino/__init__.py b/modules/intel/openvino/__init__.py index 84e4edf43..0c61a9757 100644 --- a/modules/intel/openvino/__init__.py +++ b/modules/intel/openvino/__init__.py @@ -229,7 +229,7 @@ def openvino_compile(gm: GraphModule, *args, model_hash_str: str = None, file_na om.inputs[idx].get_node().set_element_type(dtype_mapping[input_data.dtype]) om.inputs[idx].get_node().set_partial_shape(PartialShape(list(input_data.shape))) om.validate_nodes_and_infer_types() - if shared.opts.openvino_compress_weights: + if shared.opts.nncf_compress_weights: om = nncf.compress_weights(om) if model_hash_str is not None: @@ -257,7 +257,7 @@ def openvino_compile_cached_model(cached_model_path, *example_inputs): om.inputs[idx].get_node().set_element_type(dtype_mapping[input_data.dtype]) om.inputs[idx].get_node().set_partial_shape(PartialShape(list(input_data.shape))) om.validate_nodes_and_infer_types() - if shared.opts.openvino_compress_weights: + if shared.opts.nncf_compress_weights: om = nncf.compress_weights(om) core.set_property({'CACHE_DIR': shared.opts.openvino_cache_path + '/blob'}) diff --git a/modules/sd_hijack.py b/modules/sd_hijack.py index dd5c68f7c..a1975b0c0 100644 --- a/modules/sd_hijack.py +++ b/modules/sd_hijack.py @@ -183,11 +183,6 @@ def hijack(self, m): import logging shared.log.info(f"Compiling pipeline={m.model.__class__.__name__} mode={opts.cuda_compile_backend}") import torch._dynamo # pylint: disable=unused-import,redefined-outer-name - if shared.opts.cuda_compile_backend == "openvino_fx": - torch._dynamo.reset() # pylint: disable=protected-access - from modules.intel.openvino import openvino_fx, openvino_clear_caches # pylint: disable=unused-import, no-name-in-module - openvino_clear_caches() - torch._dynamo.eval_frame.check_if_dynamo_supported = lambda: True # pylint: disable=protected-access log_level = logging.WARNING if opts.cuda_compile_verbose else logging.CRITICAL # pylint: disable=protected-access if hasattr(torch, '_logging'): torch._logging.set_logs(dynamo=log_level, aot=log_level, inductor=log_level) # pylint: disable=protected-access diff --git a/modules/sd_models_compile.py b/modules/sd_models_compile.py index e8dcdcdf9..4840e4c49 100644 --- a/modules/sd_models_compile.py +++ b/modules/sd_models_compile.py @@ -43,6 +43,25 @@ def ipex_optimize(sd_model): except Exception as e: shared.log.warning(f"IPEX Optimize: error: {e}") +def nncf_compress_weights(sd_model): + try: + t0 = time.time() + import nncf + if hasattr(sd_model, 'unet'): + sd_model.unet = nncf.compress_weights(sd_model.unet) + else: + shared.log.warning('Compress Weights enabled but model has no Unet') + if shared.opts.nncf_compress_vae_weights: + if hasattr(sd_model, 'vae'): + sd_model.vae = nncf.compress_weights(sd_model.vae) + if hasattr(sd_model, 'movq'): + sd_model.movq = nncf.compress_weights(sd_model.movq) + t1 = time.time() + shared.log.info(f"Compress Weights: time={t1-t0:.2f}") + return sd_model + except Exception as e: + shared.log.warning(f"Compress Weights: error: {e}") + def optimize_openvino(): try: @@ -137,6 +156,8 @@ def compile_torch(sd_model): def compile_diffusers(sd_model): if shared.opts.ipex_optimize: sd_model = ipex_optimize(sd_model) + if shared.opts.nncf_compress_weights: + sd_model = nncf_compress_weights(sd_model) if not (shared.opts.cuda_compile or shared.opts.cuda_compile_vae or shared.opts.cuda_compile_upscaler): return sd_model if shared.opts.cuda_compile_backend == 'none': diff --git a/modules/shared.py b/modules/shared.py index 150f0af8d..30c06a1fb 100644 --- a/modules/shared.py +++ b/modules/shared.py @@ -339,7 +339,8 @@ def default(obj): "openvino_hetero_gpu": OptionInfo(False, "OpenVINO use Hetero Device for single inference with multiple devices"), "openvino_remove_cpu_from_hetero": OptionInfo(False, "OpenVINO remove CPU from Hetero Device"), "openvino_remove_igpu_from_hetero": OptionInfo(False, "OpenVINO remove iGPU from Hetero Device"), - "openvino_compress_weights": OptionInfo(False, "OpenVINO compress weights to 8 bit (CPU Only)"), + "nncf_compress_weights": OptionInfo(False, "Compress Model weights to 8 bit with NNCF"), + "nncf_compress_vae_weights": OptionInfo(False, "Compress VAE weights to 8 bit with NNCF"), })) options_templates.update(options_section(('advanced', "Inference Settings"), { From f4f4f42770a2703594b00d8422d82cf2e6c958ec Mon Sep 17 00:00:00 2001 From: Vladimir Mandic Date: Fri, 29 Dec 2023 09:25:01 -0500 Subject: [PATCH 143/143] update docs --- CHANGELOG.md | 2 +- README.md | 11 ++++++++--- extensions-builtin/sd-webui-controlnet | 2 +- html/xmas-control.jpg | Bin 0 -> 146419 bytes html/xmas-default.jpg | Bin 0 -> 201019 bytes modules/control/test.py | 5 +++++ 6 files changed, 15 insertions(+), 5 deletions(-) create mode 100644 html/xmas-control.jpg create mode 100644 html/xmas-default.jpg diff --git a/CHANGELOG.md b/CHANGELOG.md index c25e8c7dc..42b738dd5 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,6 @@ # Change Log for SD.Next -## Update for 2023-12-28 +## Update for 2023-12-29 - **Control** - native implementation of all image control methods: diff --git a/README.md b/README.md index 090ab01e8..7c3c49add 100644 --- a/README.md +++ b/README.md @@ -20,7 +20,9 @@ All individual features are not listed here, instead check [ChangeLog](CHANGELOG - Multiple backends! ▹ **Original | Diffusers** - Multiple diffusion models! - ▹ **Stable Diffusion | SD-XL | LCM | Segmind | Kandinsky | Pixart-α | Würstchen | DeepFloyd IF | UniDiffusion | SD-Distilled | etc.** + ▹ **Stable Diffusion 1.5/2.1 | SD-XL | LCM | Segmind | Kandinsky | Pixart-α | Würstchen | aMUSEd | DeepFloyd IF | UniDiffusion | SD-Distilled | BLiP Diffusion | etc.** +- Built-in Control for Text, Image, Batch and video processing! + ▹ **ControlNet | ControlNet XS | Control LLLite | T2I Adapters | IP Adapters** - Multiplatform! ▹ **Windows | Linux | MacOS with CPU | nVidia | AMD | IntelArc | DirectML | OpenVINO | ONNX+Olive** - Platform specific autodetection and tuning performed on install @@ -28,7 +30,6 @@ All individual features are not listed here, instead check [ChangeLog](CHANGELOG - Improved prompt parser - Enhanced *Lora*/*LoCon*/*Lyco* code supporting latest trends in training - Built-in queue management -- Advanced metadata caching and handling to speed up operations - Enterprise level logging and hardened API - Modern localization and hints engine - Broad compatibility with existing extensions ecosystem and new extensions manager @@ -37,7 +38,8 @@ All individual features are not listed here, instead check [ChangeLog](CHANGELOG
-![Screenshot-Dark](html/black-teal.jpg) +![Screenshot-Dark](html/xmas-default.jpg) +![Screenshot-Control](html/xmas-control.jpg) ![Screenshot-Light](html/light-teal.jpg)
@@ -62,6 +64,7 @@ Additional models will be added as they become available and there is public int - [StabilityAI Stable Diffusion XL](https://github.com/Stability-AI/generative-models) - [StabilityAI Stable Video Diffusion](https://huggingface.co/stabilityai/stable-video-diffusion-img2vid) Base and XT - [LCM: Latent Consistency Models](https://github.com/openai/consistency_models) +- [aMUSEd 256](https://huggingface.co/amused/amused-256) 256 and 512 - [Segmind Vega](https://huggingface.co/segmind/Segmind-Vega) - [Segmind SSD-1B](https://huggingface.co/segmind/SSD-1B) - [Kandinsky](https://github.com/ai-forever/Kandinsky-2) *2.1 and 2.2 and latest 3.0* @@ -72,6 +75,8 @@ Additional models will be added as they become available and there is public int - [DeepFloyd IF](https://github.com/deep-floyd/IF) *Medium and Large* - [ModelScope T2V](https://huggingface.co/damo-vilab/text-to-video-ms-1.7b) - [Segmind SD Distilled](https://huggingface.co/blog/sd_distillation) *(all variants)* +- [BLIP-Diffusion](https://dxli94.github.io/BLIP-Diffusion-website/) + Also supported are modifiers such as: - **LCM** and **Turbo** (Adversarial Diffusion Distillation) networks diff --git a/extensions-builtin/sd-webui-controlnet b/extensions-builtin/sd-webui-controlnet index b7a9bea23..dc0d590fa 160000 --- a/extensions-builtin/sd-webui-controlnet +++ b/extensions-builtin/sd-webui-controlnet @@ -1 +1 @@ -Subproject commit b7a9bea231c1eecdb2bf25ff7554e8b11d727fb6 +Subproject commit dc0d590faf3240746212477a95c3026aa7e9584f diff --git a/html/xmas-control.jpg b/html/xmas-control.jpg new file mode 100644 index 0000000000000000000000000000000000000000..ce86b0ee752ca659f47a33e99331ba35799af199 GIT binary patch literal 146419 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%0A5E+* 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%0DPO*@Z9j@4({LHBJ+n*KNd; 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%0DEnt>yJOf88XQnfU2l-IwdX!HZ)+=~FH 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 literal 0 HcmV?d00001 diff --git a/html/xmas-default.jpg b/html/xmas-default.jpg new file mode 100644 index 0000000000000000000000000000000000000000..33633d886a960d2b2e67bb13de32a149f58a0faa GIT binary patch literal 201019 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 M=il1;k^i0fFCW2^Z~y=R literal 0 HcmV?d00001 diff --git a/modules/control/test.py b/modules/control/test.py index 235cee0b4..8345997a0 100644 --- a/modules/control/test.py +++ b/modules/control/test.py @@ -22,6 +22,11 @@ def test_processors(image): else: output = processor(image) processor.reset() + if output.size != image.size: + output = output.resize(image.size, Image.Resampling.LANCZOS) + if output.mode != image.mode: + output = output.convert(image.mode) + shared.log.debug(f'Testing processor: input={image} mode={image.mode} output={output} mode={output.mode}') diff = ImageChops.difference(image, output) if not diff.getbbox(): processor_id = f'{processor_id} null'