diff --git a/invokeai/app/services/config.py b/invokeai/app/services/config.py index 49e0b6bed4..75ae034235 100644 --- a/invokeai/app/services/config.py +++ b/invokeai/app/services/config.py @@ -359,6 +359,7 @@ setting environment variables INVOKEAI_. conf_path : Path = Field(default='configs/models.yaml', description='Path to models definition file', category='Paths') embedding_dir : Path = Field(default='embeddings', description='Path to InvokeAI textual inversion aembeddings directory', category='Paths') gfpgan_model_dir : Path = Field(default="./models/gfpgan/GFPGANv1.4.pth", description='Path to GFPGAN models directory.', category='Paths') + controlnet_dir : Path = Field(default="controlnet", description='Path to directory of ControlNet models.', category='Paths') legacy_conf_dir : Path = Field(default='configs/stable-diffusion', description='Path to directory of legacy checkpoint config files', category='Paths') lora_dir : Path = Field(default='loras', description='Path to InvokeAI LoRA model directory', category='Paths') outdir : Path = Field(default='outputs', description='Default folder for output images', category='Paths') @@ -465,6 +466,13 @@ setting environment variables INVOKEAI_. ''' return self._resolve(self.lora_dir) if self.lora_dir else None + @property + def controlnet_path(self)->Path: + ''' + Path to the controlnet models directory. + ''' + return self._resolve(self.controlnet_dir) if self.controlnet_dir else None + @property def autoconvert_path(self)->Path: ''' diff --git a/invokeai/backend/config/invokeai_configure.py b/invokeai/backend/config/invokeai_configure.py index 59f11d35bc..2e9c068461 100755 --- a/invokeai/backend/config/invokeai_configure.py +++ b/invokeai/backend/config/invokeai_configure.py @@ -12,7 +12,6 @@ print("Loading Python libraries...\n",file=sys.stderr) import argparse import io import os -import re import shutil import traceback import warnings @@ -67,14 +66,9 @@ config = get_invokeai_config() Model_dir = "models" Weights_dir = "ldm/stable-diffusion-v1/" -# the initial "configs" dir is now bundled in the `invokeai.configs` package -Dataset_path = Path(configs.__path__[0]) / "INITIAL_MODELS.yaml" - Default_config_file = config.model_conf_path SD_Configs = config.legacy_conf_path -Datasets = OmegaConf.load(Dataset_path) - # minimum size for the UI MIN_COLS = 135 MIN_LINES = 45 diff --git a/invokeai/backend/config/model_install_backend.py b/invokeai/backend/config/model_install_backend.py index 25510ab371..644ecea0fc 100644 --- a/invokeai/backend/config/model_install_backend.py +++ b/invokeai/backend/config/model_install_backend.py @@ -49,9 +49,9 @@ Config_preamble = """ def default_config_file(): + print(config.root_dir) return config.model_conf_path - def sd_configs(): return config.legacy_conf_path @@ -59,8 +59,7 @@ def initial_models(): global Datasets if Datasets: return Datasets - return (Datasets := OmegaConf.load(Dataset_path)) - + return (Datasets := OmegaConf.load(Dataset_path)['diffusers']) def install_requested_models( install_initial_models: List[str] = None, @@ -79,7 +78,7 @@ def install_requested_models( if not config_file_path.exists(): open(config_file_path, "w") - model_manager = ModelManager(OmegaConf.load(config_file_path), precision=precision) + model_manager = ModelManager(OmegaConf.load(config_file_path)['diffusers'], precision=precision) if remove_models and len(remove_models) > 0: print("== DELETING UNCHECKED STARTER MODELS ==") diff --git a/invokeai/configs/INITIAL_MODELS.yaml b/invokeai/configs/INITIAL_MODELS.yaml index 3ebd38a5f6..aa72bb85c7 100644 --- a/invokeai/configs/INITIAL_MODELS.yaml +++ b/invokeai/configs/INITIAL_MODELS.yaml @@ -1,83 +1,98 @@ -stable-diffusion-1.5: - description: Stable Diffusion version 1.5 diffusers model (4.27 GB) - repo_id: runwayml/stable-diffusion-v1-5 - format: diffusers - vae: - repo_id: stabilityai/sd-vae-ft-mse - recommended: True - default: True -sd-inpainting-1.5: - description: RunwayML SD 1.5 model optimized for inpainting, diffusers version (4.27 GB) - repo_id: runwayml/stable-diffusion-inpainting - format: diffusers - vae: - repo_id: stabilityai/sd-vae-ft-mse - recommended: True -stable-diffusion-2.1: - description: Stable Diffusion version 2.1 diffusers model, trained on 768 pixel images (5.21 GB) - repo_id: stabilityai/stable-diffusion-2-1 - format: diffusers - recommended: True -sd-inpainting-2.0: - description: Stable Diffusion version 2.0 inpainting model (5.21 GB) - repo_id: stabilityai/stable-diffusion-2-inpainting - format: diffusers - recommended: False -analog-diffusion-1.0: - description: An SD-1.5 model trained on diverse analog photographs (2.13 GB) - repo_id: wavymulder/Analog-Diffusion - format: diffusers - recommended: false -deliberate-1.0: - description: Versatile model that produces detailed images up to 768px (4.27 GB) - format: diffusers - repo_id: XpucT/Deliberate - recommended: False -d&d-diffusion-1.0: - description: Dungeons & Dragons characters (2.13 GB) - format: diffusers - repo_id: 0xJustin/Dungeons-and-Diffusion - recommended: False -dreamlike-photoreal-2.0: - description: A photorealistic model trained on 768 pixel images based on SD 1.5 (2.13 GB) - format: diffusers - repo_id: dreamlike-art/dreamlike-photoreal-2.0 - recommended: False -inkpunk-1.0: - description: Stylized illustrations inspired by Gorillaz, FLCL and Shinkawa; prompt with "nvinkpunk" (4.27 GB) - format: diffusers - repo_id: Envvi/Inkpunk-Diffusion - recommended: False -openjourney-4.0: - description: An SD 1.5 model fine tuned on Midjourney; prompt with "mdjrny-v4 style" (2.13 GB) - format: diffusers - repo_id: prompthero/openjourney - vae: - repo_id: stabilityai/sd-vae-ft-mse - recommended: False -portrait-plus-1.0: - description: An SD-1.5 model trained on close range portraits of people; prompt with "portrait+" (2.13 GB) - format: diffusers - repo_id: wavymulder/portraitplus - recommended: False -seek-art-mega-1.0: - description: A general use SD-1.5 "anything" model that supports multiple styles (2.1 GB) - repo_id: coreco/seek.art_MEGA - format: diffusers - vae: - repo_id: stabilityai/sd-vae-ft-mse - recommended: False -trinart-2.0: - description: An SD-1.5 model finetuned with ~40K assorted high resolution manga/anime-style images (2.13 GB) - repo_id: naclbit/trinart_stable_diffusion_v2 - format: diffusers - vae: - repo_id: stabilityai/sd-vae-ft-mse - recommended: False -waifu-diffusion-1.4: - description: An SD-1.5 model trained on 680k anime/manga-style images (2.13 GB) - repo_id: hakurei/waifu-diffusion - format: diffusers - vae: - repo_id: stabilityai/sd-vae-ft-mse - recommended: False +diffusers: + stable-diffusion-1.5: + description: Stable Diffusion version 1.5 diffusers model (4.27 GB) + repo_id: runwayml/stable-diffusion-v1-5 + format: diffusers + vae: + repo_id: stabilityai/sd-vae-ft-mse + recommended: True + default: True + sd-inpainting-1.5: + description: RunwayML SD 1.5 model optimized for inpainting, diffusers version (4.27 GB) + repo_id: runwayml/stable-diffusion-inpainting + format: diffusers + vae: + repo_id: stabilityai/sd-vae-ft-mse + recommended: True + stable-diffusion-2.1: + description: Stable Diffusion version 2.1 diffusers model, trained on 768 pixel images (5.21 GB) + repo_id: stabilityai/stable-diffusion-2-1 + format: diffusers + recommended: True + sd-inpainting-2.0: + description: Stable Diffusion version 2.0 inpainting model (5.21 GB) + repo_id: stabilityai/stable-diffusion-2-inpainting + format: diffusers + recommended: False + analog-diffusion-1.0: + description: An SD-1.5 model trained on diverse analog photographs (2.13 GB) + repo_id: wavymulder/Analog-Diffusion + format: diffusers + recommended: false + deliberate-1.0: + description: Versatile model that produces detailed images up to 768px (4.27 GB) + format: diffusers + repo_id: XpucT/Deliberate + recommended: False + d&d-diffusion-1.0: + description: Dungeons & Dragons characters (2.13 GB) + format: diffusers + repo_id: 0xJustin/Dungeons-and-Diffusion + recommended: False + dreamlike-photoreal-2.0: + description: A photorealistic model trained on 768 pixel images based on SD 1.5 (2.13 GB) + format: diffusers + repo_id: dreamlike-art/dreamlike-photoreal-2.0 + recommended: False + inkpunk-1.0: + description: Stylized illustrations inspired by Gorillaz, FLCL and Shinkawa; prompt with "nvinkpunk" (4.27 GB) + format: diffusers + repo_id: Envvi/Inkpunk-Diffusion + recommended: False + openjourney-4.0: + description: An SD 1.5 model fine tuned on Midjourney; prompt with "mdjrny-v4 style" (2.13 GB) + format: diffusers + repo_id: prompthero/openjourney + vae: + repo_id: stabilityai/sd-vae-ft-mse + recommended: False + portrait-plus-1.0: + description: An SD-1.5 model trained on close range portraits of people; prompt with "portrait+" (2.13 GB) + format: diffusers + repo_id: wavymulder/portraitplus + recommended: False + seek-art-mega-1.0: + description: A general use SD-1.5 "anything" model that supports multiple styles (2.1 GB) + repo_id: coreco/seek.art_MEGA + format: diffusers + vae: + repo_id: stabilityai/sd-vae-ft-mse + recommended: False + trinart-2.0: + description: An SD-1.5 model finetuned with ~40K assorted high resolution manga/anime-style images (2.13 GB) + repo_id: naclbit/trinart_stable_diffusion_v2 + format: diffusers + vae: + repo_id: stabilityai/sd-vae-ft-mse + recommended: False + waifu-diffusion-1.4: + description: An SD-1.5 model trained on 680k anime/manga-style images (2.13 GB) + repo_id: hakurei/waifu-diffusion + format: diffusers + vae: + repo_id: stabilityai/sd-vae-ft-mse + recommended: False +controlnet: + canny: lllyasviel/control_v11p_sd15_canny + inpaint: lllyasviel/control_v11p_sd15_inpaint + mlsd: lllyasviel/control_v11p_sd15_mlsd + depth: lllyasviel/control_v11f1p_sd15_depth + normal_bae: lllyasviel/control_v11p_sd15_normalbae + seg: lllyasviel/control_v11p_sd15_seg + lineart: lllyasviel/control_v11p_sd15_lineart + lineart_anime: lllyasviel/control_v11p_sd15s2_lineart_anime + scribble: lllyasviel/control_v11p_sd15_scribble + softedge: lllyasviel/control_v11p_sd15_softedge + shuffle: lllyasviel/control_v11e_sd15_shuffle + tile: lllyasviel/control_v11f1e_sd15_tile + ip2p: lllyasviel/control_v11e_sd15_ip2p diff --git a/invokeai/frontend/install/model_install.py b/invokeai/frontend/install/model_install.py index ae6434055d..59b2075b28 100644 --- a/invokeai/frontend/install/model_install.py +++ b/invokeai/frontend/install/model_install.py @@ -53,14 +53,17 @@ class addModelsForm(npyscreen.FormMultiPage): def __init__(self, parentApp, name, multipage=False, *args, **keywords): self.multipage = multipage - self.initial_models = OmegaConf.load(Dataset_path) + self.initial_models = OmegaConf.load(Dataset_path)['diffusers'] + self.control_net_models = OmegaConf.load(Dataset_path)['controlnet'] + self.installed_cn_models = self._get_installed_cn_models() try: self.existing_models = OmegaConf.load(default_config_file()) except: self.existing_models = dict() - self.starter_model_list = [ - x for x in list(self.initial_models.keys()) if x not in self.existing_models - ] + # self.starter_model_list = [ + # x for x in list(self.initial_models.keys()) if x not in self.existing_models + # ] + self.starter_model_list = list(self.initial_models.keys()) self.installed_models = dict() super().__init__(parentApp=parentApp, name=name, *args, **keywords) @@ -75,6 +78,9 @@ class addModelsForm(npyscreen.FormMultiPage): self.installed_models = sorted( [x for x in list(self.initial_models.keys()) if x in self.existing_models] ) + + cn_model_list = sorted(self.control_net_models.keys()) + self.nextrely -= 1 self.add_widget_intelligent( npyscreen.FixedText, @@ -89,44 +95,44 @@ class addModelsForm(npyscreen.FormMultiPage): color="CAUTION", ) self.nextrely += 1 - if len(self.installed_models) > 0: - self.add_widget_intelligent( - CenteredTitleText, - name="== INSTALLED STARTER MODELS ==", - editable=False, - color="CONTROL", - ) - self.nextrely -= 1 - self.add_widget_intelligent( - CenteredTitleText, - name="Currently installed starter models. Uncheck to delete:", - editable=False, - labelColor="CAUTION", - ) - self.nextrely -= 1 - columns = self._get_columns() - self.previously_installed_models = self.add_widget_intelligent( - MultiSelectColumns, - columns=columns, - values=self.installed_models, - value=[x for x in range(0, len(self.installed_models))], - max_height=1 + len(self.installed_models) // columns, - relx=4, - slow_scroll=True, - scroll_exit=True, - ) - self.purge_deleted = self.add_widget_intelligent( - npyscreen.Checkbox, - name="Purge deleted models from disk", - value=False, - scroll_exit=True, - relx=4, - ) - self.nextrely += 1 + # if len(self.installed_models) > 0: + # self.add_widget_intelligent( + # CenteredTitleText, + # name="== INSTALLED STARTER MODELS ==", + # editable=False, + # color="CONTROL", + # ) + # self.nextrely -= 1 + # self.add_widget_intelligent( + # CenteredTitleText, + # name="Currently installed starter models. Uncheck to delete:", + # editable=False, + # labelColor="CAUTION", + # ) + # self.nextrely -= 1 + # columns = self._get_columns() + # self.previously_installed_models = self.add_widget_intelligent( + # MultiSelectColumns, + # columns=columns, + # values=self.installed_models, + # value=[x for x in range(0, len(self.installed_models))], + # max_height=1 + len(self.installed_models) // columns, + # relx=4, + # slow_scroll=True, + # scroll_exit=True, + # ) + # self.purge_deleted = self.add_widget_intelligent( + # npyscreen.Checkbox, + # name="Purge deleted models from disk", + # value=False, + # scroll_exit=True, + # relx=4, + # ) + # self.nextrely += 1 if len(self.starter_model_list) > 0: self.add_widget_intelligent( CenteredTitleText, - name="== STARTER MODELS (recommended ones selected) ==", + name="== DIFFUSERS MODELS (recommended ones selected) ==", editable=False, color="CONTROL", ) @@ -148,12 +154,42 @@ class addModelsForm(npyscreen.FormMultiPage): value=[ self.starter_model_list.index(x) for x in self.starter_model_list - if show_recommended and x in recommended_models + if (show_recommended and x in recommended_models)\ + or (x in self.existing_models) ], max_height=len(starter_model_labels) + 1, relx=4, scroll_exit=True, ) + self.add_widget_intelligent( + CenteredTitleText, + name="== CONTROLNET MODELS ==", + editable=False, + color="CONTROL", + ) + columns=6 + self.cn_models_selected = self.add_widget_intelligent( + MultiSelectColumns, + columns=columns, + name="Install ControlNet Models", + values=cn_model_list, + value=[ + cn_model_list.index(x) + for x in cn_model_list + if x in self.installed_cn_models + ], + max_height=len(cn_model_list)//columns + 1, + relx=4, + scroll_exit=True, + ) + self.nextrely += 1 + self.purge_deleted = self.add_widget_intelligent( + npyscreen.Checkbox, + name="Purge unchecked models from disk", + value=False, + scroll_exit=True, + relx=4, + ) self.add_widget_intelligent( CenteredTitleText, name="== IMPORT LOCAL AND REMOTE MODELS ==", @@ -263,6 +299,21 @@ class addModelsForm(npyscreen.FormMultiPage): for x in range(0, len(names)) ] + def _get_installed_cn_models(self)->list[str]: + with open('log.txt','w') as file: + cn_dir = config.controlnet_path + file.write(f'cn_dir={cn_dir}\n') + installed_cn_models = set() + for root, dirs, files in os.walk(cn_dir): + for name in dirs: + file.write(f'{root}/{name}/config.json\n') + if Path(root, name, 'config.json').exists(): + installed_cn_models.add(name) + inverse_dict = {name.split('/')[1]: key for key, name in self.control_net_models.items()} + file.write(f'inverse={inverse_dict}') + return [inverse_dict[x] for x in installed_cn_models] + + def _get_columns(self) -> int: window_width, window_height = get_terminal_size() cols = ( @@ -318,16 +369,20 @@ class addModelsForm(npyscreen.FormMultiPage): ) else: starter_models = dict() - selections.purge_deleted_models = False - if hasattr(self, "previously_installed_models"): - unchecked = [ - self.previously_installed_models.values[x] - for x in range(0, len(self.previously_installed_models.values)) - if x not in self.previously_installed_models.value - ] - starter_models.update(map(lambda x: (x, False), unchecked)) - selections.purge_deleted_models = self.purge_deleted.value - selections.starter_models = starter_models + selections.purge_deleted_models = self.purge_deleted.value + + selections.install_models = [x for x in starter_models if x not in self.existing_models] + selections.remove_models = [x for x in self.starter_model_list if x in self.existing_models and x not in starter_models] + + selections.install_cn_models = [self.control_net_models[self.cn_models_selected.values[x]] + for x in self.cn_models_selected.value + if self.cn_models_selected.values[x] not in self.installed_cn_models + ] + selections.remove_cn_models = [self.control_net_models[x] + for x in self.cn_models_selected.values + if x in self.installed_cn_models + and self.cn_models_selected.values.index(x) not in self.cn_models_selected.value + ] # load directory and whether to scan on startup if self.show_directory_fields.value: @@ -346,8 +401,11 @@ class AddModelApplication(npyscreen.NPSAppManaged): super().__init__() self.user_cancelled = False self.user_selections = Namespace( - starter_models=None, + install_models=None, + remove_models=None, purge_deleted_models=False, + install_cn_models = None, + remove_cn_models = None, scan_directory=None, autoscan_on_startup=None, import_model_paths=None, @@ -362,28 +420,29 @@ class AddModelApplication(npyscreen.NPSAppManaged): # -------------------------------------------------------- def process_and_execute(opt: Namespace, selections: Namespace): - models_to_remove = [ - x for x in selections.starter_models if not selections.starter_models[x] - ] - models_to_install = [ - x for x in selections.starter_models if selections.starter_models[x] - ] + models_to_install = selections.install_models + models_to_remove = selections.remove_models directory_to_scan = selections.scan_directory scan_at_startup = selections.autoscan_on_startup potential_models_to_install = selections.import_model_paths - install_requested_models( - install_initial_models=models_to_install, - remove_models=models_to_remove, - scan_directory=Path(directory_to_scan) if directory_to_scan else None, - external_models=potential_models_to_install, - scan_at_startup=scan_at_startup, - precision="float32" - if opt.full_precision - else choose_precision(torch.device(choose_torch_device())), - purge_deleted=selections.purge_deleted_models, - config_file_path=Path(opt.config_file) if opt.config_file else None, - ) + print('NOT INSTALLING MODELS DURING DEBUGGING') + print('models to install:',models_to_install) + print('models to remove:',models_to_remove) + print('CN models to install:',selections.install_cn_models) + print('CN models to remove:',selections.remove_cn_models) + # install_requested_models( + # install_initial_models=models_to_install, + # remove_models=models_to_remove, + # scan_directory=Path(directory_to_scan) if directory_to_scan else None, + # external_models=potential_models_to_install, + # scan_at_startup=scan_at_startup, + # precision="float32" + # if opt.full_precision + # else choose_precision(torch.device(choose_torch_device())), + # purge_deleted=selections.purge_deleted_models, + # config_file_path=Path(opt.config_file) if opt.config_file else None, + # ) # -------------------------------------------------------- @@ -453,8 +512,9 @@ def main(): opt = parser.parse_args() # setting a global here - config.root = Path(opt.root or '') - + if opt.root and Path(opt.root).exists(): + config.root = Path(opt.root) + if not (config.root_dir / config.conf_path.parent).exists(): logger.info( "Your InvokeAI root directory is not set up. Calling invokeai-configure."