From b013d0e06454ee4248c7e752d57c9fb7f402d07c Mon Sep 17 00:00:00 2001 From: blessedcoolant <54517381+blessedcoolant@users.noreply.github.com> Date: Sun, 24 Mar 2024 01:40:28 +0530 Subject: [PATCH 01/89] wip: Initial implementation of safetensor support for IP Adapter --- invokeai/app/invocations/ip_adapter.py | 23 +++++---- invokeai/backend/ip_adapter/ip_adapter.py | 50 ++++++++++++------- invokeai/backend/ip_adapter/resampler.py | 40 +++++++++------ invokeai/backend/model_manager/config.py | 22 ++++++-- .../load/model_loaders/ip_adapter.py | 3 +- invokeai/backend/model_manager/probe.py | 18 ++++--- 6 files changed, 103 insertions(+), 53 deletions(-) diff --git a/invokeai/app/invocations/ip_adapter.py b/invokeai/app/invocations/ip_adapter.py index e302c2b97a..165a6bee24 100644 --- a/invokeai/app/invocations/ip_adapter.py +++ b/invokeai/app/invocations/ip_adapter.py @@ -4,18 +4,19 @@ from typing import List, Union from pydantic import BaseModel, Field, field_validator, model_validator from typing_extensions import Self -from invokeai.app.invocations.baseinvocation import ( - BaseInvocation, - BaseInvocationOutput, - invocation, - invocation_output, -) +from invokeai.app.invocations.baseinvocation import BaseInvocation, BaseInvocationOutput, invocation, invocation_output from invokeai.app.invocations.fields import FieldDescriptions, Input, InputField, OutputField, UIType from invokeai.app.invocations.model import ModelIdentifierField from invokeai.app.invocations.primitives import ImageField from invokeai.app.invocations.util import validate_begin_end_step, validate_weights from invokeai.app.services.shared.invocation_context import InvocationContext -from invokeai.backend.model_manager.config import AnyModelConfig, BaseModelType, IPAdapterConfig, ModelType +from invokeai.backend.model_manager.config import ( + AnyModelConfig, + BaseModelType, + IPAdapterCheckpointConfig, + IPAdapterDiffusersConfig, + ModelType, +) class IPAdapterField(BaseModel): @@ -86,8 +87,12 @@ class IPAdapterInvocation(BaseInvocation): def invoke(self, context: InvocationContext) -> IPAdapterOutput: # Lookup the CLIP Vision encoder that is intended to be used with the IP-Adapter model. ip_adapter_info = context.models.get_config(self.ip_adapter_model.key) - assert isinstance(ip_adapter_info, IPAdapterConfig) - image_encoder_model_id = ip_adapter_info.image_encoder_model_id + assert isinstance(ip_adapter_info, (IPAdapterDiffusersConfig, IPAdapterCheckpointConfig)) + image_encoder_model_id = ( + ip_adapter_info.image_encoder_model_id + if isinstance(ip_adapter_info, IPAdapterDiffusersConfig) + else "InvokeAI/ip_adapter_sd_image_encoder" + ) image_encoder_model_name = image_encoder_model_id.split("/")[-1].strip() image_encoder_model = self._get_image_encoder(context, image_encoder_model_name) return IPAdapterOutput( diff --git a/invokeai/backend/ip_adapter/ip_adapter.py b/invokeai/backend/ip_adapter/ip_adapter.py index e51966c779..81514a9f8b 100644 --- a/invokeai/backend/ip_adapter/ip_adapter.py +++ b/invokeai/backend/ip_adapter/ip_adapter.py @@ -1,10 +1,11 @@ # copied from https://github.com/tencent-ailab/IP-Adapter (Apache License 2.0) # and modified as needed -from typing import Optional, Union +from typing import List, Optional, TypedDict, Union import torch from PIL import Image +from safetensors import safe_open from transformers import CLIPImageProcessor, CLIPVisionModelWithProjection from invokeai.backend.ip_adapter.ip_attention_weights import IPAttentionWeights @@ -13,10 +14,17 @@ from ..raw_model import RawModel from .resampler import Resampler +class IPAdapterStateDict(TypedDict): + ip_adapter: dict[str, torch.Tensor] + image_proj: dict[str, torch.Tensor] + + class ImageProjModel(torch.nn.Module): """Image Projection Model""" - def __init__(self, cross_attention_dim=1024, clip_embeddings_dim=1024, clip_extra_context_tokens=4): + def __init__( + self, cross_attention_dim: int = 1024, clip_embeddings_dim: int = 1024, clip_extra_context_tokens: int = 4 + ): super().__init__() self.cross_attention_dim = cross_attention_dim @@ -25,7 +33,7 @@ class ImageProjModel(torch.nn.Module): self.norm = torch.nn.LayerNorm(cross_attention_dim) @classmethod - def from_state_dict(cls, state_dict: dict[torch.Tensor], clip_extra_context_tokens=4): + def from_state_dict(cls, state_dict: dict[str, torch.Tensor], clip_extra_context_tokens: int = 4): """Initialize an ImageProjModel from a state_dict. The cross_attention_dim and clip_embeddings_dim are inferred from the shape of the tensors in the state_dict. @@ -57,7 +65,7 @@ class ImageProjModel(torch.nn.Module): class MLPProjModel(torch.nn.Module): """SD model with image prompt""" - def __init__(self, cross_attention_dim=1024, clip_embeddings_dim=1024): + def __init__(self, cross_attention_dim: int = 1024, clip_embeddings_dim: int = 1024): super().__init__() self.proj = torch.nn.Sequential( @@ -68,7 +76,7 @@ class MLPProjModel(torch.nn.Module): ) @classmethod - def from_state_dict(cls, state_dict: dict[torch.Tensor]): + def from_state_dict(cls, state_dict: dict[str, torch.Tensor]): """Initialize an MLPProjModel from a state_dict. The cross_attention_dim and clip_embeddings_dim are inferred from the shape of the tensors in the state_dict. @@ -97,7 +105,7 @@ class IPAdapter(RawModel): def __init__( self, - state_dict: dict[str, torch.Tensor], + state_dict: IPAdapterStateDict, device: torch.device, dtype: torch.dtype = torch.float16, num_tokens: int = 4, @@ -129,13 +137,11 @@ class IPAdapter(RawModel): return calc_model_size_by_data(self._image_proj_model) + calc_model_size_by_data(self.attn_weights) - def _init_image_proj_model(self, state_dict): + def _init_image_proj_model(self, state_dict: dict[str, torch.Tensor]): return ImageProjModel.from_state_dict(state_dict, self._num_tokens).to(self.device, dtype=self.dtype) @torch.inference_mode() - def get_image_embeds(self, pil_image, image_encoder: CLIPVisionModelWithProjection): - if isinstance(pil_image, Image.Image): - pil_image = [pil_image] + def get_image_embeds(self, pil_image: List[Image.Image], image_encoder: CLIPVisionModelWithProjection): clip_image = self._clip_image_processor(images=pil_image, return_tensors="pt").pixel_values clip_image_embeds = image_encoder(clip_image.to(self.device, dtype=self.dtype)).image_embeds image_prompt_embeds = self._image_proj_model(clip_image_embeds) @@ -146,7 +152,7 @@ class IPAdapter(RawModel): class IPAdapterPlus(IPAdapter): """IP-Adapter with fine-grained features""" - def _init_image_proj_model(self, state_dict): + def _init_image_proj_model(self, state_dict: dict[str, torch.Tensor]): return Resampler.from_state_dict( state_dict=state_dict, depth=4, @@ -157,9 +163,7 @@ class IPAdapterPlus(IPAdapter): ).to(self.device, dtype=self.dtype) @torch.inference_mode() - def get_image_embeds(self, pil_image, image_encoder: CLIPVisionModelWithProjection): - if isinstance(pil_image, Image.Image): - pil_image = [pil_image] + def get_image_embeds(self, pil_image: List[Image.Image], image_encoder: CLIPVisionModelWithProjection): clip_image = self._clip_image_processor(images=pil_image, return_tensors="pt").pixel_values clip_image = clip_image.to(self.device, dtype=self.dtype) clip_image_embeds = image_encoder(clip_image, output_hidden_states=True).hidden_states[-2] @@ -174,14 +178,14 @@ class IPAdapterPlus(IPAdapter): class IPAdapterFull(IPAdapterPlus): """IP-Adapter Plus with full features.""" - def _init_image_proj_model(self, state_dict: dict[torch.Tensor]): + def _init_image_proj_model(self, state_dict: dict[str, torch.Tensor]): return MLPProjModel.from_state_dict(state_dict).to(self.device, dtype=self.dtype) class IPAdapterPlusXL(IPAdapterPlus): """IP-Adapter Plus for SDXL.""" - def _init_image_proj_model(self, state_dict): + def _init_image_proj_model(self, state_dict: dict[str, torch.Tensor]): return Resampler.from_state_dict( state_dict=state_dict, depth=4, @@ -195,7 +199,19 @@ class IPAdapterPlusXL(IPAdapterPlus): def build_ip_adapter( ip_adapter_ckpt_path: str, device: torch.device, dtype: torch.dtype = torch.float16 ) -> Union[IPAdapter, IPAdapterPlus]: - state_dict = torch.load(ip_adapter_ckpt_path, map_location="cpu") + state_dict: IPAdapterStateDict = {"ip_adapter": {}, "image_proj": {}} + + if ip_adapter_ckpt_path.endswith("safetensors"): + state_dict = {"ip_adapter": {}, "image_proj": {}} + model = safe_open(ip_adapter_ckpt_path, device=device.type, framework="pt") + for key in model.keys(): + if key.startswith("image_proj."): + state_dict["image_proj"][key.replace("image_proj.", "")] = model.get_tensor(key) + if key.startswith("ip_adapter."): + state_dict["ip_adapter"][key.replace("ip_adapter.", "")] = model.get_tensor(key) + else: + ip_adapter_diffusers_checkpoint_path = ip_adapter_ckpt_path + "/ip_adapter.bin" + state_dict = torch.load(ip_adapter_diffusers_checkpoint_path, map_location="cpu") if "proj.weight" in state_dict["image_proj"]: # IPAdapter (with ImageProjModel). return IPAdapter(state_dict, device=device, dtype=dtype) diff --git a/invokeai/backend/ip_adapter/resampler.py b/invokeai/backend/ip_adapter/resampler.py index a8db22c0fd..a32eeacfdc 100644 --- a/invokeai/backend/ip_adapter/resampler.py +++ b/invokeai/backend/ip_adapter/resampler.py @@ -9,8 +9,8 @@ import torch.nn as nn # FFN -def FeedForward(dim, mult=4): - inner_dim = int(dim * mult) +def FeedForward(dim: int, mult: int = 4): + inner_dim = dim * mult return nn.Sequential( nn.LayerNorm(dim), nn.Linear(dim, inner_dim, bias=False), @@ -19,8 +19,8 @@ def FeedForward(dim, mult=4): ) -def reshape_tensor(x, heads): - bs, length, width = x.shape +def reshape_tensor(x: torch.Tensor, heads: int): + bs, length, _ = x.shape # (bs, length, width) --> (bs, length, n_heads, dim_per_head) x = x.view(bs, length, heads, -1) # (bs, length, n_heads, dim_per_head) --> (bs, n_heads, length, dim_per_head) @@ -31,7 +31,7 @@ def reshape_tensor(x, heads): class PerceiverAttention(nn.Module): - def __init__(self, *, dim, dim_head=64, heads=8): + def __init__(self, *, dim: int, dim_head: int = 64, heads: int = 8): super().__init__() self.scale = dim_head**-0.5 self.dim_head = dim_head @@ -45,7 +45,7 @@ class PerceiverAttention(nn.Module): self.to_kv = nn.Linear(dim, inner_dim * 2, bias=False) self.to_out = nn.Linear(inner_dim, dim, bias=False) - def forward(self, x, latents): + def forward(self, x: torch.Tensor, latents: torch.Tensor): """ Args: x (torch.Tensor): image features @@ -80,14 +80,14 @@ class PerceiverAttention(nn.Module): class Resampler(nn.Module): def __init__( self, - dim=1024, - depth=8, - dim_head=64, - heads=16, - num_queries=8, - embedding_dim=768, - output_dim=1024, - ff_mult=4, + dim: int = 1024, + depth: int = 8, + dim_head: int = 64, + heads: int = 16, + num_queries: int = 8, + embedding_dim: int = 768, + output_dim: int = 1024, + ff_mult: int = 4, ): super().__init__() @@ -110,7 +110,15 @@ class Resampler(nn.Module): ) @classmethod - def from_state_dict(cls, state_dict: dict[torch.Tensor], depth=8, dim_head=64, heads=16, num_queries=8, ff_mult=4): + def from_state_dict( + cls, + state_dict: dict[str, torch.Tensor], + depth: int = 8, + dim_head: int = 64, + heads: int = 16, + num_queries: int = 8, + ff_mult: int = 4, + ): """A convenience function that initializes a Resampler from a state_dict. Some of the shape parameters are inferred from the state_dict (e.g. dim, embedding_dim, etc.). At the time of @@ -145,7 +153,7 @@ class Resampler(nn.Module): model.load_state_dict(state_dict) return model - def forward(self, x): + def forward(self, x: torch.Tensor): latents = self.latents.repeat(x.size(0), 1, 1) x = self.proj_in(x) diff --git a/invokeai/backend/model_manager/config.py b/invokeai/backend/model_manager/config.py index 524e39b2a1..172045d3fc 100644 --- a/invokeai/backend/model_manager/config.py +++ b/invokeai/backend/model_manager/config.py @@ -323,10 +323,13 @@ class MainDiffusersConfig(DiffusersConfigBase, MainConfigBase): return Tag(f"{ModelType.Main.value}.{ModelFormat.Diffusers.value}") -class IPAdapterConfig(ModelConfigBase): - """Model config for IP Adaptor format models.""" - +class IPAdapterBaseConfig(ModelConfigBase): type: Literal[ModelType.IPAdapter] = ModelType.IPAdapter + + +class IPAdapterDiffusersConfig(IPAdapterBaseConfig): + """Model config for IP Adapter diffusers format models.""" + image_encoder_model_id: str format: Literal[ModelFormat.InvokeAI] @@ -335,6 +338,16 @@ class IPAdapterConfig(ModelConfigBase): return Tag(f"{ModelType.IPAdapter.value}.{ModelFormat.InvokeAI.value}") +class IPAdapterCheckpointConfig(IPAdapterBaseConfig): + """Model config for IP Adapter checkpoint format models.""" + + format: Literal[ModelFormat.Checkpoint] + + @staticmethod + def get_tag() -> Tag: + return Tag(f"{ModelType.IPAdapter.value}.{ModelFormat.Checkpoint.value}") + + class CLIPVisionDiffusersConfig(DiffusersConfigBase): """Model config for CLIPVision.""" @@ -390,7 +403,8 @@ AnyModelConfig = Annotated[ Annotated[LoRADiffusersConfig, LoRADiffusersConfig.get_tag()], Annotated[TextualInversionFileConfig, TextualInversionFileConfig.get_tag()], Annotated[TextualInversionFolderConfig, TextualInversionFolderConfig.get_tag()], - Annotated[IPAdapterConfig, IPAdapterConfig.get_tag()], + Annotated[IPAdapterDiffusersConfig, IPAdapterDiffusersConfig.get_tag()], + Annotated[IPAdapterCheckpointConfig, IPAdapterCheckpointConfig.get_tag()], Annotated[T2IAdapterConfig, T2IAdapterConfig.get_tag()], Annotated[CLIPVisionDiffusersConfig, CLIPVisionDiffusersConfig.get_tag()], ], diff --git a/invokeai/backend/model_manager/load/model_loaders/ip_adapter.py b/invokeai/backend/model_manager/load/model_loaders/ip_adapter.py index 89c54948ff..a149cedde2 100644 --- a/invokeai/backend/model_manager/load/model_loaders/ip_adapter.py +++ b/invokeai/backend/model_manager/load/model_loaders/ip_adapter.py @@ -19,6 +19,7 @@ from invokeai.backend.model_manager.load import ModelLoader, ModelLoaderRegistry @ModelLoaderRegistry.register(base=BaseModelType.Any, type=ModelType.IPAdapter, format=ModelFormat.InvokeAI) +@ModelLoaderRegistry.register(base=BaseModelType.Any, type=ModelType.IPAdapter, format=ModelFormat.Checkpoint) class IPAdapterInvokeAILoader(ModelLoader): """Class to load IP Adapter diffusers models.""" @@ -31,7 +32,7 @@ class IPAdapterInvokeAILoader(ModelLoader): if submodel_type is not None: raise ValueError("There are no submodels in an IP-Adapter model.") model = build_ip_adapter( - ip_adapter_ckpt_path=str(model_path / "ip_adapter.bin"), + ip_adapter_ckpt_path=str(model_path), device=torch.device("cpu"), dtype=self._torch_dtype, ) diff --git a/invokeai/backend/model_manager/probe.py b/invokeai/backend/model_manager/probe.py index ddd9e99eda..ed73fc56c6 100644 --- a/invokeai/backend/model_manager/probe.py +++ b/invokeai/backend/model_manager/probe.py @@ -230,9 +230,10 @@ class ModelProbe(object): return ModelType.LoRA elif any(key.startswith(v) for v in {"controlnet", "control_model", "input_blocks"}): return ModelType.ControlNet + elif any(key.startswith(v) for v in {"image_proj.", "ip_adapter."}): + return ModelType.IPAdapter elif key in {"emb_params", "string_to_param"}: return ModelType.TextualInversion - else: # diffusers-ti if len(ckpt) < 10 and all(isinstance(v, torch.Tensor) for v in ckpt.values()): @@ -527,8 +528,15 @@ class ControlNetCheckpointProbe(CheckpointProbeBase): class IPAdapterCheckpointProbe(CheckpointProbeBase): + """Class for probing IP Adapters""" + def get_base_type(self) -> BaseModelType: - raise NotImplementedError() + checkpoint = self.checkpoint + for key in checkpoint.keys(): + if not key.startswith(("image_proj.", "ip_adapter.")): + continue + return BaseModelType.StableDiffusionXL + raise InvalidModelConfigException(f"{self.model_path}: Unable to determine base type") class CLIPVisionCheckpointProbe(CheckpointProbeBase): @@ -689,9 +697,7 @@ class ControlNetFolderProbe(FolderProbeBase): else ( BaseModelType.StableDiffusion2 if dimension == 1024 - else BaseModelType.StableDiffusionXL - if dimension == 2048 - else None + else BaseModelType.StableDiffusionXL if dimension == 2048 else None ) ) if not base_model: @@ -768,7 +774,7 @@ class T2IAdapterFolderProbe(FolderProbeBase): ) -############## register probe classes ###### +# Register probe classes ModelProbe.register_probe("diffusers", ModelType.Main, PipelineFolderProbe) ModelProbe.register_probe("diffusers", ModelType.VAE, VaeFolderProbe) ModelProbe.register_probe("diffusers", ModelType.LoRA, LoRAFolderProbe) From 60bf0caca331460e683ec0387f23cc54f80c9825 Mon Sep 17 00:00:00 2001 From: blessedcoolant <54517381+blessedcoolant@users.noreply.github.com> Date: Sun, 24 Mar 2024 01:58:46 +0530 Subject: [PATCH 02/89] feat: add base model recognition for ip adapter safetensor files --- invokeai/app/invocations/latent.py | 61 +++++++++---------------- invokeai/backend/model_manager/probe.py | 13 +++++- 2 files changed, 34 insertions(+), 40 deletions(-) diff --git a/invokeai/app/invocations/latent.py b/invokeai/app/invocations/latent.py index bc79efdeba..8ad1684bcb 100644 --- a/invokeai/app/invocations/latent.py +++ b/invokeai/app/invocations/latent.py @@ -14,12 +14,10 @@ from diffusers import AutoencoderKL, AutoencoderTiny from diffusers.configuration_utils import ConfigMixin from diffusers.image_processor import VaeImageProcessor from diffusers.models.adapter import T2IAdapter -from diffusers.models.attention_processor import ( - AttnProcessor2_0, - LoRAAttnProcessor2_0, - LoRAXFormersAttnProcessor, - XFormersAttnProcessor, -) +from diffusers.models.attention_processor import (AttnProcessor2_0, + LoRAAttnProcessor2_0, + LoRAXFormersAttnProcessor, + XFormersAttnProcessor) from diffusers.models.unets.unet_2d_condition import UNet2DConditionModel from diffusers.schedulers import DPMSolverSDEScheduler from diffusers.schedulers import SchedulerMixin as Scheduler @@ -28,26 +26,17 @@ from pydantic import field_validator from torchvision.transforms.functional import resize as tv_resize from transformers import CLIPVisionModelWithProjection -from invokeai.app.invocations.constants import LATENT_SCALE_FACTOR, SCHEDULER_NAME_VALUES -from invokeai.app.invocations.fields import ( - ConditioningField, - DenoiseMaskField, - FieldDescriptions, - ImageField, - Input, - InputField, - LatentsField, - OutputField, - UIType, - WithBoard, - WithMetadata, -) +from invokeai.app.invocations.constants import (LATENT_SCALE_FACTOR, + SCHEDULER_NAME_VALUES) +from invokeai.app.invocations.fields import (ConditioningField, + DenoiseMaskField, + FieldDescriptions, ImageField, + Input, InputField, LatentsField, + OutputField, UIType, WithBoard, + WithMetadata) from invokeai.app.invocations.ip_adapter import IPAdapterField -from invokeai.app.invocations.primitives import ( - DenoiseMaskOutput, - ImageOutput, - LatentsOutput, -) +from invokeai.app.invocations.primitives import (DenoiseMaskOutput, + ImageOutput, LatentsOutput) from invokeai.app.invocations.t2i_adapter import T2IAdapterField from invokeai.app.services.shared.invocation_context import InvocationContext from invokeai.app.util.controlnet_utils import prepare_control_image @@ -55,25 +44,19 @@ from invokeai.backend.ip_adapter.ip_adapter import IPAdapter, IPAdapterPlus from invokeai.backend.lora import LoRAModelRaw from invokeai.backend.model_manager import BaseModelType, LoadedModel from invokeai.backend.model_patcher import ModelPatcher -from invokeai.backend.stable_diffusion import PipelineIntermediateState, set_seamless -from invokeai.backend.stable_diffusion.diffusion.conditioning_data import ConditioningData, IPAdapterConditioningInfo +from invokeai.backend.stable_diffusion import (PipelineIntermediateState, + set_seamless) +from invokeai.backend.stable_diffusion.diffusion.conditioning_data import ( + ConditioningData, IPAdapterConditioningInfo) from invokeai.backend.util.silence_warnings import SilenceWarnings from ...backend.stable_diffusion.diffusers_pipeline import ( - ControlNetData, - IPAdapterData, - StableDiffusionGeneratorPipeline, - T2IAdapterData, - image_resized_to_grid_as_tensor, -) + ControlNetData, IPAdapterData, StableDiffusionGeneratorPipeline, + T2IAdapterData, image_resized_to_grid_as_tensor) from ...backend.stable_diffusion.schedulers import SCHEDULER_MAP from ...backend.util.devices import choose_precision, choose_torch_device -from .baseinvocation import ( - BaseInvocation, - BaseInvocationOutput, - invocation, - invocation_output, -) +from .baseinvocation import (BaseInvocation, BaseInvocationOutput, invocation, + invocation_output) from .controlnet_image_processors import ControlField from .model import ModelIdentifierField, UNetField, VAEField diff --git a/invokeai/backend/model_manager/probe.py b/invokeai/backend/model_manager/probe.py index ed73fc56c6..bd47cc1a48 100644 --- a/invokeai/backend/model_manager/probe.py +++ b/invokeai/backend/model_manager/probe.py @@ -535,7 +535,18 @@ class IPAdapterCheckpointProbe(CheckpointProbeBase): for key in checkpoint.keys(): if not key.startswith(("image_proj.", "ip_adapter.")): continue - return BaseModelType.StableDiffusionXL + cross_attention_dim = checkpoint["ip_adapter.1.to_k_ip.weight"].shape[-1] + print(cross_attention_dim) + if cross_attention_dim == 768: + return BaseModelType.StableDiffusion1 + elif cross_attention_dim == 1024: + return BaseModelType.StableDiffusion2 + elif cross_attention_dim == 2048: + return BaseModelType.StableDiffusionXL + else: + raise InvalidModelConfigException( + f"IP-Adapter had unexpected cross-attention dimension: {cross_attention_dim}." + ) raise InvalidModelConfigException(f"{self.model_path}: Unable to determine base type") From 4ed2bf53ca2d57da7c3df037fdd39e6b47c63eac Mon Sep 17 00:00:00 2001 From: blessedcoolant <54517381+blessedcoolant@users.noreply.github.com> Date: Sun, 24 Mar 2024 02:27:38 +0530 Subject: [PATCH 03/89] fix: cleanup across various ip adapter files --- invokeai/app/invocations/ip_adapter.py | 2 +- invokeai/app/invocations/latent.py | 52 +++++++++++--------- invokeai/backend/model_manager/probe.py | 24 +++------ invokeai/frontend/web/public/locales/en.json | 1 + 4 files changed, 40 insertions(+), 39 deletions(-) diff --git a/invokeai/app/invocations/ip_adapter.py b/invokeai/app/invocations/ip_adapter.py index 165a6bee24..94bb909433 100644 --- a/invokeai/app/invocations/ip_adapter.py +++ b/invokeai/app/invocations/ip_adapter.py @@ -91,7 +91,7 @@ class IPAdapterInvocation(BaseInvocation): image_encoder_model_id = ( ip_adapter_info.image_encoder_model_id if isinstance(ip_adapter_info, IPAdapterDiffusersConfig) - else "InvokeAI/ip_adapter_sd_image_encoder" + else "ip_adapter_sd_image_encoder" ) image_encoder_model_name = image_encoder_model_id.split("/")[-1].strip() image_encoder_model = self._get_image_encoder(context, image_encoder_model_name) diff --git a/invokeai/app/invocations/latent.py b/invokeai/app/invocations/latent.py index 8ad1684bcb..3c66b7014f 100644 --- a/invokeai/app/invocations/latent.py +++ b/invokeai/app/invocations/latent.py @@ -14,10 +14,12 @@ from diffusers import AutoencoderKL, AutoencoderTiny from diffusers.configuration_utils import ConfigMixin from diffusers.image_processor import VaeImageProcessor from diffusers.models.adapter import T2IAdapter -from diffusers.models.attention_processor import (AttnProcessor2_0, - LoRAAttnProcessor2_0, - LoRAXFormersAttnProcessor, - XFormersAttnProcessor) +from diffusers.models.attention_processor import ( + AttnProcessor2_0, + LoRAAttnProcessor2_0, + LoRAXFormersAttnProcessor, + XFormersAttnProcessor, +) from diffusers.models.unets.unet_2d_condition import UNet2DConditionModel from diffusers.schedulers import DPMSolverSDEScheduler from diffusers.schedulers import SchedulerMixin as Scheduler @@ -26,17 +28,22 @@ from pydantic import field_validator from torchvision.transforms.functional import resize as tv_resize from transformers import CLIPVisionModelWithProjection -from invokeai.app.invocations.constants import (LATENT_SCALE_FACTOR, - SCHEDULER_NAME_VALUES) -from invokeai.app.invocations.fields import (ConditioningField, - DenoiseMaskField, - FieldDescriptions, ImageField, - Input, InputField, LatentsField, - OutputField, UIType, WithBoard, - WithMetadata) +from invokeai.app.invocations.constants import LATENT_SCALE_FACTOR, SCHEDULER_NAME_VALUES +from invokeai.app.invocations.fields import ( + ConditioningField, + DenoiseMaskField, + FieldDescriptions, + ImageField, + Input, + InputField, + LatentsField, + OutputField, + UIType, + WithBoard, + WithMetadata, +) from invokeai.app.invocations.ip_adapter import IPAdapterField -from invokeai.app.invocations.primitives import (DenoiseMaskOutput, - ImageOutput, LatentsOutput) +from invokeai.app.invocations.primitives import DenoiseMaskOutput, ImageOutput, LatentsOutput from invokeai.app.invocations.t2i_adapter import T2IAdapterField from invokeai.app.services.shared.invocation_context import InvocationContext from invokeai.app.util.controlnet_utils import prepare_control_image @@ -44,19 +51,20 @@ from invokeai.backend.ip_adapter.ip_adapter import IPAdapter, IPAdapterPlus from invokeai.backend.lora import LoRAModelRaw from invokeai.backend.model_manager import BaseModelType, LoadedModel from invokeai.backend.model_patcher import ModelPatcher -from invokeai.backend.stable_diffusion import (PipelineIntermediateState, - set_seamless) -from invokeai.backend.stable_diffusion.diffusion.conditioning_data import ( - ConditioningData, IPAdapterConditioningInfo) +from invokeai.backend.stable_diffusion import PipelineIntermediateState, set_seamless +from invokeai.backend.stable_diffusion.diffusion.conditioning_data import ConditioningData, IPAdapterConditioningInfo from invokeai.backend.util.silence_warnings import SilenceWarnings from ...backend.stable_diffusion.diffusers_pipeline import ( - ControlNetData, IPAdapterData, StableDiffusionGeneratorPipeline, - T2IAdapterData, image_resized_to_grid_as_tensor) + ControlNetData, + IPAdapterData, + StableDiffusionGeneratorPipeline, + T2IAdapterData, + image_resized_to_grid_as_tensor, +) from ...backend.stable_diffusion.schedulers import SCHEDULER_MAP from ...backend.util.devices import choose_precision, choose_torch_device -from .baseinvocation import (BaseInvocation, BaseInvocationOutput, invocation, - invocation_output) +from .baseinvocation import BaseInvocation, BaseInvocationOutput, invocation, invocation_output from .controlnet_image_processors import ControlField from .model import ModelIdentifierField, UNetField, VAEField diff --git a/invokeai/backend/model_manager/probe.py b/invokeai/backend/model_manager/probe.py index bd47cc1a48..75f156ce21 100644 --- a/invokeai/backend/model_manager/probe.py +++ b/invokeai/backend/model_manager/probe.py @@ -9,23 +9,16 @@ from picklescan.scanner import scan_file_path import invokeai.backend.util.logging as logger from invokeai.app.util.misc import uuid_string -from invokeai.backend.model_hash.model_hash import HASHING_ALGORITHMS, ModelHash +from invokeai.backend.model_hash.model_hash import (HASHING_ALGORITHMS, + ModelHash) from invokeai.backend.util.util import SilenceWarnings -from .config import ( - AnyModelConfig, - BaseModelType, - ControlAdapterDefaultSettings, - InvalidModelConfigException, - MainModelDefaultSettings, - ModelConfigFactory, - ModelFormat, - ModelRepoVariant, - ModelSourceType, - ModelType, - ModelVariantType, - SchedulerPredictionType, -) +from .config import (AnyModelConfig, BaseModelType, + ControlAdapterDefaultSettings, + InvalidModelConfigException, MainModelDefaultSettings, + ModelConfigFactory, ModelFormat, ModelRepoVariant, + ModelSourceType, ModelType, ModelVariantType, + SchedulerPredictionType) from .util.model_util import lora_token_vector_length, read_checkpoint_meta CkptType = Dict[str | int, Any] @@ -536,7 +529,6 @@ class IPAdapterCheckpointProbe(CheckpointProbeBase): if not key.startswith(("image_proj.", "ip_adapter.")): continue cross_attention_dim = checkpoint["ip_adapter.1.to_k_ip.weight"].shape[-1] - print(cross_attention_dim) if cross_attention_dim == 768: return BaseModelType.StableDiffusion1 elif cross_attention_dim == 1024: diff --git a/invokeai/frontend/web/public/locales/en.json b/invokeai/frontend/web/public/locales/en.json index 1601169e03..d2402c61be 100644 --- a/invokeai/frontend/web/public/locales/en.json +++ b/invokeai/frontend/web/public/locales/en.json @@ -655,6 +655,7 @@ "install": "Install", "installAll": "Install All", "installRepo": "Install Repo", + "ipAdapters": "IP Adapters", "load": "Load", "localOnly": "local only", "manual": "Manual", From c4a856de4abc25970b82371a70649653df0f0fd1 Mon Sep 17 00:00:00 2001 From: blessedcoolant <54517381+blessedcoolant@users.noreply.github.com> Date: Sun, 24 Mar 2024 02:58:57 +0530 Subject: [PATCH 04/89] ui: update the new ip adapter configs on the frontend --- invokeai/app/invocations/ip_adapter.py | 2 + .../frontend/web/src/services/api/schema.ts | 245 +++++++++++++++++- .../frontend/web/src/services/api/types.ts | 2 +- 3 files changed, 237 insertions(+), 12 deletions(-) diff --git a/invokeai/app/invocations/ip_adapter.py b/invokeai/app/invocations/ip_adapter.py index 94bb909433..2874c92701 100644 --- a/invokeai/app/invocations/ip_adapter.py +++ b/invokeai/app/invocations/ip_adapter.py @@ -88,6 +88,7 @@ class IPAdapterInvocation(BaseInvocation): # Lookup the CLIP Vision encoder that is intended to be used with the IP-Adapter model. ip_adapter_info = context.models.get_config(self.ip_adapter_model.key) assert isinstance(ip_adapter_info, (IPAdapterDiffusersConfig, IPAdapterCheckpointConfig)) + image_encoder_model_id = ( ip_adapter_info.image_encoder_model_id if isinstance(ip_adapter_info, IPAdapterDiffusersConfig) @@ -95,6 +96,7 @@ class IPAdapterInvocation(BaseInvocation): ) image_encoder_model_name = image_encoder_model_id.split("/")[-1].strip() image_encoder_model = self._get_image_encoder(context, image_encoder_model_name) + return IPAdapterOutput( ip_adapter=IPAdapterField( image=self.image, diff --git a/invokeai/frontend/web/src/services/api/schema.ts b/invokeai/frontend/web/src/services/api/schema.ts index 55e5743629..70cd37376a 100644 --- a/invokeai/frontend/web/src/services/api/schema.ts +++ b/invokeai/frontend/web/src/services/api/schema.ts @@ -159,6 +159,12 @@ export type paths = { /** Get Starter Models */ get: operations["get_starter_models"]; }; + "/api/v2/models/hf_login": { + /** Get Hf Login Status */ + get: operations["get_hf_login_status"]; + /** Do Hf Login */ + post: operations["do_hf_login"]; + }; "/api/v1/download_queue/": { /** * List Downloads @@ -1022,6 +1028,14 @@ export type components = { */ image_names: string[]; }; + /** Body_do_hf_login */ + Body_do_hf_login: { + /** + * Token + * @description Hugging Face token to use for login + */ + token: string; + }; /** Body_download */ Body_download: { /** @@ -1248,6 +1262,39 @@ export type components = { */ type: "boolean_output"; }; + /** + * BRIA AI Background Removal + * @description Uses the new Bria 1.4 model to remove backgrounds from images. + */ + BriaRemoveBackgroundInvocation: { + /** @description Optional metadata to be saved with the image */ + metadata?: components["schemas"]["MetadataField"] | null; + /** + * Id + * @description The id of this instance of an invocation. Must be unique among all instances of invocations. + */ + id: string; + /** + * Is Intermediate + * @description Whether or not this is an intermediate invocation. + * @default false + */ + is_intermediate?: boolean; + /** + * Use Cache + * @description Whether or not to use the cache + * @default true + */ + use_cache?: boolean; + /** @description The image to crop */ + image?: components["schemas"]["ImageField"]; + /** + * type + * @default bria_bg_remove + * @constant + */ + type: "bria_bg_remove"; + }; /** CLIPField */ CLIPField: { /** @description Info to load tokenizer submodel */ @@ -4122,7 +4169,7 @@ export type components = { * @description The nodes in this graph */ nodes: { - [key: string]: components["schemas"]["CreateGradientMaskInvocation"] | components["schemas"]["DivideInvocation"] | components["schemas"]["MaskFromAlphaInvocation"] | components["schemas"]["ContentShuffleImageProcessorInvocation"] | components["schemas"]["FaceIdentifierInvocation"] | components["schemas"]["StringCollectionInvocation"] | components["schemas"]["CalculateImageTilesMinimumOverlapInvocation"] | components["schemas"]["FloatLinearRangeInvocation"] | components["schemas"]["LineartImageProcessorInvocation"] | components["schemas"]["IntegerMathInvocation"] | components["schemas"]["PromptsFromFileInvocation"] | components["schemas"]["FloatToIntegerInvocation"] | components["schemas"]["TileResamplerProcessorInvocation"] | components["schemas"]["FloatInvocation"] | components["schemas"]["NoiseInvocation"] | components["schemas"]["CalculateImageTilesInvocation"] | components["schemas"]["ImageChannelMultiplyInvocation"] | components["schemas"]["DynamicPromptInvocation"] | components["schemas"]["MainModelLoaderInvocation"] | components["schemas"]["SDXLRefinerModelLoaderInvocation"] | components["schemas"]["CollectInvocation"] | components["schemas"]["PairTileImageInvocation"] | components["schemas"]["FloatMathInvocation"] | components["schemas"]["DepthAnythingImageProcessorInvocation"] | components["schemas"]["ImageHueAdjustmentInvocation"] | components["schemas"]["BooleanInvocation"] | components["schemas"]["ConditioningInvocation"] | components["schemas"]["ImageResizeInvocation"] | components["schemas"]["StringJoinInvocation"] | components["schemas"]["ImageWatermarkInvocation"] | components["schemas"]["BlankImageInvocation"] | components["schemas"]["UnsharpMaskInvocation"] | components["schemas"]["TileToPropertiesInvocation"] | components["schemas"]["ZoeDepthImageProcessorInvocation"] | components["schemas"]["ImageNSFWBlurInvocation"] | components["schemas"]["ImageLerpInvocation"] | components["schemas"]["SaveImageInvocation"] | components["schemas"]["ImageCollectionInvocation"] | components["schemas"]["NormalbaeImageProcessorInvocation"] | components["schemas"]["StringSplitInvocation"] | components["schemas"]["ShowImageInvocation"] | components["schemas"]["ImagePasteInvocation"] | components["schemas"]["CLIPSkipInvocation"] | components["schemas"]["PidiImageProcessorInvocation"] | components["schemas"]["StringSplitNegInvocation"] | components["schemas"]["ImageInverseLerpInvocation"] | components["schemas"]["SeamlessModeInvocation"] | components["schemas"]["IntegerCollectionInvocation"] | components["schemas"]["MultiplyInvocation"] | components["schemas"]["LeresImageProcessorInvocation"] | components["schemas"]["SDXLRefinerCompelPromptInvocation"] | components["schemas"]["MergeMetadataInvocation"] | components["schemas"]["CoreMetadataInvocation"] | components["schemas"]["ConditioningCollectionInvocation"] | components["schemas"]["ControlNetInvocation"] | components["schemas"]["CalculateImageTilesEvenSplitInvocation"] | components["schemas"]["MetadataInvocation"] | components["schemas"]["ImageChannelInvocation"] | components["schemas"]["LaMaInfillInvocation"] | components["schemas"]["InfillTileInvocation"] | components["schemas"]["LatentsToImageInvocation"] | components["schemas"]["SchedulerInvocation"] | components["schemas"]["MlsdImageProcessorInvocation"] | components["schemas"]["SDXLLoRALoaderInvocation"] | components["schemas"]["RoundInvocation"] | components["schemas"]["ColorMapImageProcessorInvocation"] | components["schemas"]["StringJoinThreeInvocation"] | components["schemas"]["StringInvocation"] | components["schemas"]["ImageChannelOffsetInvocation"] | components["schemas"]["ImageMultiplyInvocation"] | components["schemas"]["IdealSizeInvocation"] | components["schemas"]["SubtractInvocation"] | components["schemas"]["ColorCorrectInvocation"] | components["schemas"]["CompelInvocation"] | components["schemas"]["ResizeLatentsInvocation"] | components["schemas"]["MidasDepthImageProcessorInvocation"] | components["schemas"]["ImageToLatentsInvocation"] | components["schemas"]["HedImageProcessorInvocation"] | components["schemas"]["CreateDenoiseMaskInvocation"] | components["schemas"]["CenterPadCropInvocation"] | components["schemas"]["ScaleLatentsInvocation"] | components["schemas"]["MaskFromIDInvocation"] | components["schemas"]["FreeUInvocation"] | components["schemas"]["InfillColorInvocation"] | components["schemas"]["BooleanCollectionInvocation"] | components["schemas"]["LatentsInvocation"] | components["schemas"]["LoRALoaderInvocation"] | components["schemas"]["T2IAdapterInvocation"] | components["schemas"]["ImageCropInvocation"] | components["schemas"]["IterateInvocation"] | components["schemas"]["LineartAnimeImageProcessorInvocation"] | components["schemas"]["CvInpaintInvocation"] | components["schemas"]["InfillPatchMatchInvocation"] | components["schemas"]["LatentsCollectionInvocation"] | components["schemas"]["ColorInvocation"] | components["schemas"]["FaceMaskInvocation"] | components["schemas"]["FloatCollectionInvocation"] | components["schemas"]["IPAdapterInvocation"] | components["schemas"]["StepParamEasingInvocation"] | components["schemas"]["MetadataItemInvocation"] | components["schemas"]["SDXLModelLoaderInvocation"] | components["schemas"]["FaceOffInvocation"] | components["schemas"]["AddInvocation"] | components["schemas"]["MaskEdgeInvocation"] | components["schemas"]["BlendLatentsInvocation"] | components["schemas"]["CropLatentsCoreInvocation"] | components["schemas"]["CannyImageProcessorInvocation"] | components["schemas"]["DWOpenposeImageProcessorInvocation"] | components["schemas"]["SDXLCompelPromptInvocation"] | components["schemas"]["RandomRangeInvocation"] | components["schemas"]["MediapipeFaceProcessorInvocation"] | components["schemas"]["IntegerInvocation"] | components["schemas"]["RandomFloatInvocation"] | components["schemas"]["ESRGANInvocation"] | components["schemas"]["DenoiseLatentsInvocation"] | components["schemas"]["MaskCombineInvocation"] | components["schemas"]["CV2InfillInvocation"] | components["schemas"]["StringReplaceInvocation"] | components["schemas"]["ImageBlurInvocation"] | components["schemas"]["RangeOfSizeInvocation"] | components["schemas"]["SegmentAnythingProcessorInvocation"] | components["schemas"]["RandomIntInvocation"] | components["schemas"]["RangeInvocation"] | components["schemas"]["MergeTilesToImageInvocation"] | components["schemas"]["CanvasPasteBackInvocation"] | components["schemas"]["ImageInvocation"] | components["schemas"]["ImageScaleInvocation"] | components["schemas"]["ImageConvertInvocation"] | components["schemas"]["VAELoaderInvocation"]; + [key: string]: components["schemas"]["FaceIdentifierInvocation"] | components["schemas"]["AddInvocation"] | components["schemas"]["NormalbaeImageProcessorInvocation"] | components["schemas"]["T2IAdapterInvocation"] | components["schemas"]["CenterPadCropInvocation"] | components["schemas"]["ColorCorrectInvocation"] | components["schemas"]["IntegerInvocation"] | components["schemas"]["FaceOffInvocation"] | components["schemas"]["StringCollectionInvocation"] | components["schemas"]["SchedulerInvocation"] | components["schemas"]["LatentsInvocation"] | components["schemas"]["MlsdImageProcessorInvocation"] | components["schemas"]["CalculateImageTilesMinimumOverlapInvocation"] | components["schemas"]["ImageChannelInvocation"] | components["schemas"]["ESRGANInvocation"] | components["schemas"]["RandomIntInvocation"] | components["schemas"]["RoundInvocation"] | components["schemas"]["LineartImageProcessorInvocation"] | components["schemas"]["BooleanInvocation"] | components["schemas"]["ScaleLatentsInvocation"] | components["schemas"]["ImageChannelMultiplyInvocation"] | components["schemas"]["RandomRangeInvocation"] | components["schemas"]["BooleanCollectionInvocation"] | components["schemas"]["LeresImageProcessorInvocation"] | components["schemas"]["ConditioningCollectionInvocation"] | components["schemas"]["CropLatentsCoreInvocation"] | components["schemas"]["HedImageProcessorInvocation"] | components["schemas"]["PromptsFromFileInvocation"] | components["schemas"]["CalculateImageTilesEvenSplitInvocation"] | components["schemas"]["MediapipeFaceProcessorInvocation"] | components["schemas"]["ImagePasteInvocation"] | components["schemas"]["ImageHueAdjustmentInvocation"] | components["schemas"]["StringJoinInvocation"] | components["schemas"]["LatentsCollectionInvocation"] | components["schemas"]["MergeTilesToImageInvocation"] | components["schemas"]["MaskFromIDInvocation"] | components["schemas"]["LoRALoaderInvocation"] | components["schemas"]["RandomFloatInvocation"] | components["schemas"]["IntegerCollectionInvocation"] | components["schemas"]["CanvasPasteBackInvocation"] | components["schemas"]["NoiseInvocation"] | components["schemas"]["MetadataInvocation"] | components["schemas"]["ImageNSFWBlurInvocation"] | components["schemas"]["FreeUInvocation"] | components["schemas"]["LaMaInfillInvocation"] | components["schemas"]["CannyImageProcessorInvocation"] | components["schemas"]["StringSplitInvocation"] | components["schemas"]["ImageInvocation"] | components["schemas"]["BlendLatentsInvocation"] | components["schemas"]["VAELoaderInvocation"] | components["schemas"]["CompelInvocation"] | components["schemas"]["IterateInvocation"] | components["schemas"]["PidiImageProcessorInvocation"] | components["schemas"]["ShowImageInvocation"] | components["schemas"]["ImageLerpInvocation"] | components["schemas"]["SegmentAnythingProcessorInvocation"] | components["schemas"]["MaskFromAlphaInvocation"] | components["schemas"]["StringInvocation"] | components["schemas"]["DepthAnythingImageProcessorInvocation"] | components["schemas"]["DenoiseLatentsInvocation"] | components["schemas"]["ImageCropInvocation"] | components["schemas"]["ImageChannelOffsetInvocation"] | components["schemas"]["IdealSizeInvocation"] | components["schemas"]["InfillColorInvocation"] | components["schemas"]["FloatMathInvocation"] | components["schemas"]["LineartAnimeImageProcessorInvocation"] | components["schemas"]["CLIPSkipInvocation"] | components["schemas"]["CreateGradientMaskInvocation"] | components["schemas"]["SDXLModelLoaderInvocation"] | components["schemas"]["StringJoinThreeInvocation"] | components["schemas"]["ColorInvocation"] | components["schemas"]["ImageMultiplyInvocation"] | components["schemas"]["ResizeLatentsInvocation"] | components["schemas"]["DynamicPromptInvocation"] | components["schemas"]["MergeMetadataInvocation"] | components["schemas"]["StepParamEasingInvocation"] | components["schemas"]["TileToPropertiesInvocation"] | components["schemas"]["LatentsToImageInvocation"] | components["schemas"]["CreateDenoiseMaskInvocation"] | components["schemas"]["UnsharpMaskInvocation"] | components["schemas"]["FloatInvocation"] | components["schemas"]["ImageWatermarkInvocation"] | components["schemas"]["ContentShuffleImageProcessorInvocation"] | components["schemas"]["CV2InfillInvocation"] | components["schemas"]["IntegerMathInvocation"] | components["schemas"]["BriaRemoveBackgroundInvocation"] | components["schemas"]["ColorMapImageProcessorInvocation"] | components["schemas"]["HandDepthMeshGraphormerProcessor"] | components["schemas"]["SDXLCompelPromptInvocation"] | components["schemas"]["MainModelLoaderInvocation"] | components["schemas"]["InfillPatchMatchInvocation"] | components["schemas"]["BlankImageInvocation"] | components["schemas"]["SaveImageInvocation"] | components["schemas"]["SDXLLoRALoaderInvocation"] | components["schemas"]["DivideInvocation"] | components["schemas"]["ControlNetInvocation"] | components["schemas"]["InfillTileInvocation"] | components["schemas"]["StringSplitNegInvocation"] | components["schemas"]["ImageCollectionInvocation"] | components["schemas"]["SeamlessModeInvocation"] | components["schemas"]["StringReplaceInvocation"] | components["schemas"]["ImageToLatentsInvocation"] | components["schemas"]["MetadataItemInvocation"] | components["schemas"]["MidasDepthImageProcessorInvocation"] | components["schemas"]["ConditioningInvocation"] | components["schemas"]["MaskCombineInvocation"] | components["schemas"]["MultiplyInvocation"] | components["schemas"]["RangeInvocation"] | components["schemas"]["TileResamplerProcessorInvocation"] | components["schemas"]["ImageResizeInvocation"] | components["schemas"]["FloatToIntegerInvocation"] | components["schemas"]["CvInpaintInvocation"] | components["schemas"]["SDXLRefinerModelLoaderInvocation"] | components["schemas"]["IPAdapterInvocation"] | components["schemas"]["SubtractInvocation"] | components["schemas"]["ZoeDepthImageProcessorInvocation"] | components["schemas"]["FaceMaskInvocation"] | components["schemas"]["FloatLinearRangeInvocation"] | components["schemas"]["ImageInverseLerpInvocation"] | components["schemas"]["RangeOfSizeInvocation"] | components["schemas"]["SDXLRefinerCompelPromptInvocation"] | components["schemas"]["PairTileImageInvocation"] | components["schemas"]["MaskEdgeInvocation"] | components["schemas"]["CollectInvocation"] | components["schemas"]["DWOpenposeImageProcessorInvocation"] | components["schemas"]["ImageConvertInvocation"] | components["schemas"]["FloatCollectionInvocation"] | components["schemas"]["CalculateImageTilesInvocation"] | components["schemas"]["ImageBlurInvocation"] | components["schemas"]["ImageScaleInvocation"] | components["schemas"]["CoreMetadataInvocation"]; }; /** * Edges @@ -4159,7 +4206,7 @@ export type components = { * @description The results of node executions */ results: { - [key: string]: components["schemas"]["LatentsCollectionOutput"] | components["schemas"]["FloatOutput"] | components["schemas"]["SDXLLoRALoaderOutput"] | components["schemas"]["CalculateImageTilesOutput"] | components["schemas"]["CollectInvocationOutput"] | components["schemas"]["SDXLRefinerModelLoaderOutput"] | components["schemas"]["IntegerOutput"] | components["schemas"]["ModelLoaderOutput"] | components["schemas"]["LatentsOutput"] | components["schemas"]["CLIPOutput"] | components["schemas"]["StringCollectionOutput"] | components["schemas"]["MetadataItemOutput"] | components["schemas"]["ImageCollectionOutput"] | components["schemas"]["GradientMaskOutput"] | components["schemas"]["LoRALoaderOutput"] | components["schemas"]["IntegerCollectionOutput"] | components["schemas"]["String2Output"] | components["schemas"]["FloatCollectionOutput"] | components["schemas"]["T2IAdapterOutput"] | components["schemas"]["BooleanCollectionOutput"] | components["schemas"]["SeamlessModeOutput"] | components["schemas"]["StringPosNegOutput"] | components["schemas"]["IdealSizeOutput"] | components["schemas"]["SchedulerOutput"] | components["schemas"]["BooleanOutput"] | components["schemas"]["FaceMaskOutput"] | components["schemas"]["ColorOutput"] | components["schemas"]["CLIPSkipInvocationOutput"] | components["schemas"]["ImageOutput"] | components["schemas"]["ColorCollectionOutput"] | components["schemas"]["ConditioningOutput"] | components["schemas"]["MetadataOutput"] | components["schemas"]["PairTileImageOutput"] | components["schemas"]["VAEOutput"] | components["schemas"]["StringOutput"] | components["schemas"]["IterateInvocationOutput"] | components["schemas"]["ConditioningCollectionOutput"] | components["schemas"]["ControlOutput"] | components["schemas"]["TileToPropertiesOutput"] | components["schemas"]["SDXLModelLoaderOutput"] | components["schemas"]["DenoiseMaskOutput"] | components["schemas"]["IPAdapterOutput"] | components["schemas"]["NoiseOutput"] | components["schemas"]["FaceOffOutput"] | components["schemas"]["UNetOutput"]; + [key: string]: components["schemas"]["BooleanOutput"] | components["schemas"]["ImageCollectionOutput"] | components["schemas"]["SDXLLoRALoaderOutput"] | components["schemas"]["LatentsCollectionOutput"] | components["schemas"]["GradientMaskOutput"] | components["schemas"]["ModelLoaderOutput"] | components["schemas"]["PairTileImageOutput"] | components["schemas"]["StringPosNegOutput"] | components["schemas"]["CollectInvocationOutput"] | components["schemas"]["StringOutput"] | components["schemas"]["SchedulerOutput"] | components["schemas"]["CLIPOutput"] | components["schemas"]["BooleanCollectionOutput"] | components["schemas"]["FaceMaskOutput"] | components["schemas"]["ConditioningCollectionOutput"] | components["schemas"]["MetadataOutput"] | components["schemas"]["UNetOutput"] | components["schemas"]["IPAdapterOutput"] | components["schemas"]["T2IAdapterOutput"] | components["schemas"]["ConditioningOutput"] | components["schemas"]["ControlOutput"] | components["schemas"]["StringCollectionOutput"] | components["schemas"]["CalculateImageTilesOutput"] | components["schemas"]["ColorOutput"] | components["schemas"]["IterateInvocationOutput"] | components["schemas"]["SDXLModelLoaderOutput"] | components["schemas"]["TileToPropertiesOutput"] | components["schemas"]["SeamlessModeOutput"] | components["schemas"]["FloatOutput"] | components["schemas"]["FaceOffOutput"] | components["schemas"]["ImageOutput"] | components["schemas"]["SDXLRefinerModelLoaderOutput"] | components["schemas"]["String2Output"] | components["schemas"]["IntegerCollectionOutput"] | components["schemas"]["ColorCollectionOutput"] | components["schemas"]["VAEOutput"] | components["schemas"]["NoiseOutput"] | components["schemas"]["DenoiseMaskOutput"] | components["schemas"]["LatentsOutput"] | components["schemas"]["MetadataItemOutput"] | components["schemas"]["LoRALoaderOutput"] | components["schemas"]["FloatCollectionOutput"] | components["schemas"]["HandDepthOutput"] | components["schemas"]["IntegerOutput"] | components["schemas"]["IdealSizeOutput"] | components["schemas"]["CLIPSkipInvocationOutput"]; }; /** * Errors @@ -4205,11 +4252,93 @@ export type components = { */ type?: "hf"; }; + /** + * HFTokenStatus + * @enum {string} + */ + HFTokenStatus: "valid" | "invalid" | "unknown"; /** HTTPValidationError */ HTTPValidationError: { /** Detail */ detail?: components["schemas"]["ValidationError"][]; }; + /** + * Hand Depth w/ MeshGraphormer + * @description Generate hand depth maps to inpaint with using ControlNet + */ + HandDepthMeshGraphormerProcessor: { + /** @description Optional metadata to be saved with the image */ + metadata?: components["schemas"]["MetadataField"] | null; + /** + * Id + * @description The id of this instance of an invocation. Must be unique among all instances of invocations. + */ + id: string; + /** + * Is Intermediate + * @description Whether or not this is an intermediate invocation. + * @default false + */ + is_intermediate?: boolean; + /** + * Use Cache + * @description Whether or not to use the cache + * @default true + */ + use_cache?: boolean; + /** @description The image to process */ + image?: components["schemas"]["ImageField"]; + /** + * Resolution + * @description Pixel resolution for output image + * @default 512 + */ + resolution?: number; + /** + * Mask Padding + * @description Amount to pad the hand mask by + * @default 30 + */ + mask_padding?: number; + /** + * Offload + * @description Offload model after usage + * @default false + */ + offload?: boolean; + /** + * type + * @default hand_depth_mesh_graphormer_image_processor + * @constant + */ + type: "hand_depth_mesh_graphormer_image_processor"; + }; + /** + * HandDepthOutput + * @description Base class for to output Meshgraphormer results + */ + HandDepthOutput: { + /** @description Improved hands depth map */ + image: components["schemas"]["ImageField"]; + /** @description Hands area mask */ + mask: components["schemas"]["ImageField"]; + /** + * Width + * @description The width of the depth map in pixels + */ + width: number; + /** + * Height + * @description The height of the depth map in pixels + */ + height: number; + /** + * type + * @default meshgraphormer_output + * @constant + */ + type: "meshgraphormer_output"; + }; /** * HED (softedge) Processor * @description Applies HED edge detection to image @@ -4320,10 +4449,71 @@ export type components = { is_diffusers: boolean; }; /** - * IPAdapterConfig - * @description Model config for IP Adaptor format models. + * IPAdapterCheckpointConfig + * @description Model config for IP Adapter checkpoint format models. */ - IPAdapterConfig: { + IPAdapterCheckpointConfig: { + /** + * Key + * @description A unique key for this model. + */ + key: string; + /** + * Hash + * @description The hash of the model file(s). + */ + hash: string; + /** + * Path + * @description Path to the model on the filesystem. Relative paths are relative to the Invoke root directory. + */ + path: string; + /** + * Name + * @description Name of the model. + */ + name: string; + /** @description The base model. */ + base: components["schemas"]["BaseModelType"]; + /** + * Description + * @description Model description + */ + description?: string | null; + /** + * Source + * @description The original source of the model (path, URL or repo_id). + */ + source: string; + /** @description The type of source */ + source_type: components["schemas"]["ModelSourceType"]; + /** + * Source Api Response + * @description The original API response from the source, as stringified JSON. + */ + source_api_response?: string | null; + /** + * Cover Image + * @description Url for image to preview model + */ + cover_image?: string | null; + /** + * Type + * @default ip_adapter + * @constant + */ + type: "ip_adapter"; + /** + * Format + * @constant + */ + format: "checkpoint"; + }; + /** + * IPAdapterDiffusersConfig + * @description Model config for IP Adapter diffusers format models. + */ + IPAdapterDiffusersConfig: { /** * Key * @description A unique key for this model. @@ -7481,7 +7671,7 @@ export type components = { * Config Out * @description After successful installation, this will hold the configuration object. */ - config_out?: (components["schemas"]["MainDiffusersConfig"] | components["schemas"]["MainCheckpointConfig"] | components["schemas"]["VAEDiffusersConfig"] | components["schemas"]["VAECheckpointConfig"] | components["schemas"]["ControlNetDiffusersConfig"] | components["schemas"]["ControlNetCheckpointConfig"] | components["schemas"]["LoRALyCORISConfig"] | components["schemas"]["LoRADiffusersConfig"] | components["schemas"]["TextualInversionFileConfig"] | components["schemas"]["TextualInversionFolderConfig"] | components["schemas"]["IPAdapterConfig"] | components["schemas"]["T2IAdapterConfig"] | components["schemas"]["CLIPVisionDiffusersConfig"]) | null; + config_out?: (components["schemas"]["MainDiffusersConfig"] | components["schemas"]["MainCheckpointConfig"] | components["schemas"]["VAEDiffusersConfig"] | components["schemas"]["VAECheckpointConfig"] | components["schemas"]["ControlNetDiffusersConfig"] | components["schemas"]["ControlNetCheckpointConfig"] | components["schemas"]["LoRALyCORISConfig"] | components["schemas"]["LoRADiffusersConfig"] | components["schemas"]["TextualInversionFileConfig"] | components["schemas"]["TextualInversionFolderConfig"] | components["schemas"]["IPAdapterDiffusersConfig"] | components["schemas"]["IPAdapterCheckpointConfig"] | components["schemas"]["T2IAdapterConfig"] | components["schemas"]["CLIPVisionDiffusersConfig"]) | null; /** * Inplace * @description Leave model in its current location; otherwise install under models directory @@ -7636,7 +7826,7 @@ export type components = { */ ModelsList: { /** Models */ - models: (components["schemas"]["MainDiffusersConfig"] | components["schemas"]["MainCheckpointConfig"] | components["schemas"]["VAEDiffusersConfig"] | components["schemas"]["VAECheckpointConfig"] | components["schemas"]["ControlNetDiffusersConfig"] | components["schemas"]["ControlNetCheckpointConfig"] | components["schemas"]["LoRALyCORISConfig"] | components["schemas"]["LoRADiffusersConfig"] | components["schemas"]["TextualInversionFileConfig"] | components["schemas"]["TextualInversionFolderConfig"] | components["schemas"]["IPAdapterConfig"] | components["schemas"]["T2IAdapterConfig"] | components["schemas"]["CLIPVisionDiffusersConfig"])[]; + models: (components["schemas"]["MainDiffusersConfig"] | components["schemas"]["MainCheckpointConfig"] | components["schemas"]["VAEDiffusersConfig"] | components["schemas"]["VAECheckpointConfig"] | components["schemas"]["ControlNetDiffusersConfig"] | components["schemas"]["ControlNetCheckpointConfig"] | components["schemas"]["LoRALyCORISConfig"] | components["schemas"]["LoRADiffusersConfig"] | components["schemas"]["TextualInversionFileConfig"] | components["schemas"]["TextualInversionFolderConfig"] | components["schemas"]["IPAdapterDiffusersConfig"] | components["schemas"]["IPAdapterCheckpointConfig"] | components["schemas"]["T2IAdapterConfig"] | components["schemas"]["CLIPVisionDiffusersConfig"])[]; }; /** * Multiply Integers @@ -11165,7 +11355,7 @@ export type operations = { /** @description Successful Response */ 200: { content: { - "application/json": components["schemas"]["MainDiffusersConfig"] | components["schemas"]["MainCheckpointConfig"] | components["schemas"]["VAEDiffusersConfig"] | components["schemas"]["VAECheckpointConfig"] | components["schemas"]["ControlNetDiffusersConfig"] | components["schemas"]["ControlNetCheckpointConfig"] | components["schemas"]["LoRALyCORISConfig"] | components["schemas"]["LoRADiffusersConfig"] | components["schemas"]["TextualInversionFileConfig"] | components["schemas"]["TextualInversionFolderConfig"] | components["schemas"]["IPAdapterConfig"] | components["schemas"]["T2IAdapterConfig"] | components["schemas"]["CLIPVisionDiffusersConfig"]; + "application/json": components["schemas"]["MainDiffusersConfig"] | components["schemas"]["MainCheckpointConfig"] | components["schemas"]["VAEDiffusersConfig"] | components["schemas"]["VAECheckpointConfig"] | components["schemas"]["ControlNetDiffusersConfig"] | components["schemas"]["ControlNetCheckpointConfig"] | components["schemas"]["LoRALyCORISConfig"] | components["schemas"]["LoRADiffusersConfig"] | components["schemas"]["TextualInversionFileConfig"] | components["schemas"]["TextualInversionFolderConfig"] | components["schemas"]["IPAdapterDiffusersConfig"] | components["schemas"]["IPAdapterCheckpointConfig"] | components["schemas"]["T2IAdapterConfig"] | components["schemas"]["CLIPVisionDiffusersConfig"]; }; }; /** @description Validation Error */ @@ -11191,7 +11381,7 @@ export type operations = { /** @description The model configuration was retrieved successfully */ 200: { content: { - "application/json": components["schemas"]["MainDiffusersConfig"] | components["schemas"]["MainCheckpointConfig"] | components["schemas"]["VAEDiffusersConfig"] | components["schemas"]["VAECheckpointConfig"] | components["schemas"]["ControlNetDiffusersConfig"] | components["schemas"]["ControlNetCheckpointConfig"] | components["schemas"]["LoRALyCORISConfig"] | components["schemas"]["LoRADiffusersConfig"] | components["schemas"]["TextualInversionFileConfig"] | components["schemas"]["TextualInversionFolderConfig"] | components["schemas"]["IPAdapterConfig"] | components["schemas"]["T2IAdapterConfig"] | components["schemas"]["CLIPVisionDiffusersConfig"]; + "application/json": components["schemas"]["MainDiffusersConfig"] | components["schemas"]["MainCheckpointConfig"] | components["schemas"]["VAEDiffusersConfig"] | components["schemas"]["VAECheckpointConfig"] | components["schemas"]["ControlNetDiffusersConfig"] | components["schemas"]["ControlNetCheckpointConfig"] | components["schemas"]["LoRALyCORISConfig"] | components["schemas"]["LoRADiffusersConfig"] | components["schemas"]["TextualInversionFileConfig"] | components["schemas"]["TextualInversionFolderConfig"] | components["schemas"]["IPAdapterDiffusersConfig"] | components["schemas"]["IPAdapterCheckpointConfig"] | components["schemas"]["T2IAdapterConfig"] | components["schemas"]["CLIPVisionDiffusersConfig"]; }; }; /** @description Bad request */ @@ -11273,7 +11463,7 @@ export type operations = { /** @description The model was updated successfully */ 200: { content: { - "application/json": components["schemas"]["MainDiffusersConfig"] | components["schemas"]["MainCheckpointConfig"] | components["schemas"]["VAEDiffusersConfig"] | components["schemas"]["VAECheckpointConfig"] | components["schemas"]["ControlNetDiffusersConfig"] | components["schemas"]["ControlNetCheckpointConfig"] | components["schemas"]["LoRALyCORISConfig"] | components["schemas"]["LoRADiffusersConfig"] | components["schemas"]["TextualInversionFileConfig"] | components["schemas"]["TextualInversionFolderConfig"] | components["schemas"]["IPAdapterConfig"] | components["schemas"]["T2IAdapterConfig"] | components["schemas"]["CLIPVisionDiffusersConfig"]; + "application/json": components["schemas"]["MainDiffusersConfig"] | components["schemas"]["MainCheckpointConfig"] | components["schemas"]["VAEDiffusersConfig"] | components["schemas"]["VAECheckpointConfig"] | components["schemas"]["ControlNetDiffusersConfig"] | components["schemas"]["ControlNetCheckpointConfig"] | components["schemas"]["LoRALyCORISConfig"] | components["schemas"]["LoRADiffusersConfig"] | components["schemas"]["TextualInversionFileConfig"] | components["schemas"]["TextualInversionFolderConfig"] | components["schemas"]["IPAdapterDiffusersConfig"] | components["schemas"]["IPAdapterCheckpointConfig"] | components["schemas"]["T2IAdapterConfig"] | components["schemas"]["CLIPVisionDiffusersConfig"]; }; }; /** @description Bad request */ @@ -11672,7 +11862,7 @@ export type operations = { /** @description Model converted successfully */ 200: { content: { - "application/json": components["schemas"]["MainDiffusersConfig"] | components["schemas"]["MainCheckpointConfig"] | components["schemas"]["VAEDiffusersConfig"] | components["schemas"]["VAECheckpointConfig"] | components["schemas"]["ControlNetDiffusersConfig"] | components["schemas"]["ControlNetCheckpointConfig"] | components["schemas"]["LoRALyCORISConfig"] | components["schemas"]["LoRADiffusersConfig"] | components["schemas"]["TextualInversionFileConfig"] | components["schemas"]["TextualInversionFolderConfig"] | components["schemas"]["IPAdapterConfig"] | components["schemas"]["T2IAdapterConfig"] | components["schemas"]["CLIPVisionDiffusersConfig"]; + "application/json": components["schemas"]["MainDiffusersConfig"] | components["schemas"]["MainCheckpointConfig"] | components["schemas"]["VAEDiffusersConfig"] | components["schemas"]["VAECheckpointConfig"] | components["schemas"]["ControlNetDiffusersConfig"] | components["schemas"]["ControlNetCheckpointConfig"] | components["schemas"]["LoRALyCORISConfig"] | components["schemas"]["LoRADiffusersConfig"] | components["schemas"]["TextualInversionFileConfig"] | components["schemas"]["TextualInversionFolderConfig"] | components["schemas"]["IPAdapterDiffusersConfig"] | components["schemas"]["IPAdapterCheckpointConfig"] | components["schemas"]["T2IAdapterConfig"] | components["schemas"]["CLIPVisionDiffusersConfig"]; }; }; /** @description Bad request */ @@ -11706,6 +11896,39 @@ export type operations = { }; }; }; + /** Get Hf Login Status */ + get_hf_login_status: { + responses: { + /** @description Successful Response */ + 200: { + content: { + "application/json": components["schemas"]["HFTokenStatus"]; + }; + }; + }; + }; + /** Do Hf Login */ + do_hf_login: { + requestBody: { + content: { + "application/json": components["schemas"]["Body_do_hf_login"]; + }; + }; + responses: { + /** @description Successful Response */ + 200: { + content: { + "application/json": components["schemas"]["HFTokenStatus"]; + }; + }; + /** @description Validation Error */ + 422: { + content: { + "application/json": components["schemas"]["HTTPValidationError"]; + }; + }; + }; + }; /** * List Downloads * @description Get a list of active and inactive jobs. diff --git a/invokeai/frontend/web/src/services/api/types.ts b/invokeai/frontend/web/src/services/api/types.ts index 6a81b7b6dc..d24d4bddd5 100644 --- a/invokeai/frontend/web/src/services/api/types.ts +++ b/invokeai/frontend/web/src/services/api/types.ts @@ -46,7 +46,7 @@ export type LoRAModelConfig = S['LoRADiffusersConfig'] | S['LoRALyCORISConfig']; // TODO(MM2): Can we rename this from Vae -> VAE export type VAEModelConfig = S['VAECheckpointConfig'] | S['VAEDiffusersConfig']; export type ControlNetModelConfig = S['ControlNetDiffusersConfig'] | S['ControlNetCheckpointConfig']; -export type IPAdapterModelConfig = S['IPAdapterConfig']; +export type IPAdapterModelConfig = S['IPAdapterDiffusersConfig'] | S['IPAdapterCheckpointConfig']; export type T2IAdapterModelConfig = S['T2IAdapterConfig']; type TextualInversionModelConfig = S['TextualInversionFileConfig'] | S['TextualInversionFolderConfig']; type DiffusersModelConfig = S['MainDiffusersConfig']; From 318bc938fe0281dc5a14bdc3da2a97cc418baa9e Mon Sep 17 00:00:00 2001 From: blessedcoolant <54517381+blessedcoolant@users.noreply.github.com> Date: Sun, 24 Mar 2024 03:06:41 +0530 Subject: [PATCH 05/89] fix: Update ModelView to accommodate for the new config changes to IP Adapter --- .../features/modelManagerV2/subpanels/ModelPanel/ModelView.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/invokeai/frontend/web/src/features/modelManagerV2/subpanels/ModelPanel/ModelView.tsx b/invokeai/frontend/web/src/features/modelManagerV2/subpanels/ModelPanel/ModelView.tsx index adb123f24d..0618af5dd0 100644 --- a/invokeai/frontend/web/src/features/modelManagerV2/subpanels/ModelPanel/ModelView.tsx +++ b/invokeai/frontend/web/src/features/modelManagerV2/subpanels/ModelPanel/ModelView.tsx @@ -53,7 +53,7 @@ export const ModelView = () => { )} - {data.type === 'ip_adapter' && ( + {data.type === 'ip_adapter' && data.format === 'invokeai' && ( From 688a0f30bbdfaf5bad2410244b51f382ca6fd145 Mon Sep 17 00:00:00 2001 From: blessedcoolant <54517381+blessedcoolant@users.noreply.github.com> Date: Sun, 24 Mar 2024 08:34:11 +0530 Subject: [PATCH 06/89] chore: improve types in ip_adapter backend file --- invokeai/backend/ip_adapter/ip_adapter.py | 46 ++++++++++++++--------- 1 file changed, 29 insertions(+), 17 deletions(-) diff --git a/invokeai/backend/ip_adapter/ip_adapter.py b/invokeai/backend/ip_adapter/ip_adapter.py index 81514a9f8b..5444c76c8c 100644 --- a/invokeai/backend/ip_adapter/ip_adapter.py +++ b/invokeai/backend/ip_adapter/ip_adapter.py @@ -53,7 +53,7 @@ class ImageProjModel(torch.nn.Module): model.load_state_dict(state_dict) return model - def forward(self, image_embeds): + def forward(self, image_embeds: torch.Tensor): embeds = image_embeds clip_extra_context_tokens = self.proj(embeds).reshape( -1, self.clip_extra_context_tokens, self.cross_attention_dim @@ -95,7 +95,7 @@ class MLPProjModel(torch.nn.Module): model.load_state_dict(state_dict) return model - def forward(self, image_embeds): + def forward(self, image_embeds: torch.Tensor): clip_extra_context_tokens = self.proj(image_embeds) return clip_extra_context_tokens @@ -137,7 +137,9 @@ class IPAdapter(RawModel): return calc_model_size_by_data(self._image_proj_model) + calc_model_size_by_data(self.attn_weights) - def _init_image_proj_model(self, state_dict: dict[str, torch.Tensor]): + def _init_image_proj_model( + self, state_dict: dict[str, torch.Tensor] + ) -> Union[ImageProjModel, Resampler, MLPProjModel]: return ImageProjModel.from_state_dict(state_dict, self._num_tokens).to(self.device, dtype=self.dtype) @torch.inference_mode() @@ -152,7 +154,7 @@ class IPAdapter(RawModel): class IPAdapterPlus(IPAdapter): """IP-Adapter with fine-grained features""" - def _init_image_proj_model(self, state_dict: dict[str, torch.Tensor]): + def _init_image_proj_model(self, state_dict: dict[str, torch.Tensor]) -> Union[Resampler, MLPProjModel]: return Resampler.from_state_dict( state_dict=state_dict, depth=4, @@ -196,36 +198,46 @@ class IPAdapterPlusXL(IPAdapterPlus): ).to(self.device, dtype=self.dtype) -def build_ip_adapter( - ip_adapter_ckpt_path: str, device: torch.device, dtype: torch.dtype = torch.float16 -) -> Union[IPAdapter, IPAdapterPlus]: +def load_ip_adapter_tensors(ip_adapter_ckpt_path: str, device: str) -> IPAdapterStateDict: state_dict: IPAdapterStateDict = {"ip_adapter": {}, "image_proj": {}} if ip_adapter_ckpt_path.endswith("safetensors"): - state_dict = {"ip_adapter": {}, "image_proj": {}} - model = safe_open(ip_adapter_ckpt_path, device=device.type, framework="pt") + model = safe_open(ip_adapter_ckpt_path, device=device, framework="pt") for key in model.keys(): if key.startswith("image_proj."): state_dict["image_proj"][key.replace("image_proj.", "")] = model.get_tensor(key) - if key.startswith("ip_adapter."): + elif key.startswith("ip_adapter."): state_dict["ip_adapter"][key.replace("ip_adapter.", "")] = model.get_tensor(key) else: ip_adapter_diffusers_checkpoint_path = ip_adapter_ckpt_path + "/ip_adapter.bin" state_dict = torch.load(ip_adapter_diffusers_checkpoint_path, map_location="cpu") - if "proj.weight" in state_dict["image_proj"]: # IPAdapter (with ImageProjModel). + return state_dict + + +def build_ip_adapter( + ip_adapter_ckpt_path: str, device: torch.device, dtype: torch.dtype = torch.float16 +) -> Union[IPAdapter, IPAdapterPlus, IPAdapterPlusXL, IPAdapterPlus]: + state_dict = load_ip_adapter_tensors(ip_adapter_ckpt_path, device.type) + + # IPAdapter (with ImageProjModel) + if "proj.weight" in state_dict["image_proj"]: return IPAdapter(state_dict, device=device, dtype=dtype) - elif "proj_in.weight" in state_dict["image_proj"]: # IPAdaterPlus or IPAdapterPlusXL (with Resampler). + + # IPAdaterPlus or IPAdapterPlusXL (with Resampler) + elif "proj_in.weight" in state_dict["image_proj"]: cross_attention_dim = state_dict["ip_adapter"]["1.to_k_ip.weight"].shape[-1] if cross_attention_dim == 768: - # SD1 IP-Adapter Plus - return IPAdapterPlus(state_dict, device=device, dtype=dtype) + return IPAdapterPlus(state_dict, device=device, dtype=dtype) # SD1 IP-Adapter Plus elif cross_attention_dim == 2048: - # SDXL IP-Adapter Plus - return IPAdapterPlusXL(state_dict, device=device, dtype=dtype) + return IPAdapterPlusXL(state_dict, device=device, dtype=dtype) # SDXL IP-Adapter Plus else: raise Exception(f"Unsupported IP-Adapter Plus cross-attention dimension: {cross_attention_dim}.") - elif "proj.0.weight" in state_dict["image_proj"]: # IPAdapterFull (with MLPProjModel). + + # IPAdapterFull (with MLPProjModel) + elif "proj.0.weight" in state_dict["image_proj"]: return IPAdapterFull(state_dict, device=device, dtype=dtype) + + # Unrecognized IP Adapter Architectures else: raise ValueError(f"'{ip_adapter_ckpt_path}' has an unrecognized IP-Adapter model architecture.") From 16c366a0600d7bac352f3d6f3714b8fb329f99b5 Mon Sep 17 00:00:00 2001 From: blessedcoolant <54517381+blessedcoolant@users.noreply.github.com> Date: Wed, 27 Mar 2024 20:32:41 +0530 Subject: [PATCH 07/89] feat: Let users pick CLIP Vision model for Checkpoint IP Adapters --- invokeai/app/invocations/ip_adapter.py | 53 +++++++++------ invokeai/app/invocations/metadata.py | 13 +--- invokeai/frontend/web/public/locales/en.json | 1 + .../parameters/ParamControlAdapterModel.tsx | 64 +++++++++++++++---- .../hooks/useControlAdapterCLIPVisionModel.ts | 24 +++++++ .../store/controlAdaptersSlice.ts | 9 +++ .../features/controlAdapters/store/types.ts | 3 + .../util/buildControlAdapter.ts | 1 + .../util/graph/addIPAdapterToLinearGraph.ts | 6 +- .../frontend/web/src/services/api/schema.ts | 17 ++++- 10 files changed, 145 insertions(+), 46 deletions(-) create mode 100644 invokeai/frontend/web/src/features/controlAdapters/hooks/useControlAdapterCLIPVisionModel.ts diff --git a/invokeai/app/invocations/ip_adapter.py b/invokeai/app/invocations/ip_adapter.py index 2874c92701..603a85148d 100644 --- a/invokeai/app/invocations/ip_adapter.py +++ b/invokeai/app/invocations/ip_adapter.py @@ -1,5 +1,5 @@ from builtins import float -from typing import List, Union +from typing import List, Literal, Union from pydantic import BaseModel, Field, field_validator, model_validator from typing_extensions import Self @@ -49,12 +49,15 @@ class IPAdapterOutput(BaseInvocationOutput): ip_adapter: IPAdapterField = OutputField(description=FieldDescriptions.ip_adapter, title="IP-Adapter") +CLIP_VISION_MODEL_MAP = {"ViT-H": "ip_adapter_sd_image_encoder", "ViT-G": "ip_adapter_sdxl_image_encoder"} + + @invocation("ip_adapter", title="IP-Adapter", tags=["ip_adapter", "control"], category="ip_adapter", version="1.2.2") class IPAdapterInvocation(BaseInvocation): """Collects IP-Adapter info to pass to other nodes.""" # Inputs - image: Union[ImageField, List[ImageField]] = InputField(description="The IP-Adapter image prompt(s).") + image: Union[ImageField, List[ImageField]] = InputField(description="The IP-Adapter image prompt(s).", ui_order=1) ip_adapter_model: ModelIdentifierField = InputField( description="The IP-Adapter model.", title="IP-Adapter Model", @@ -62,7 +65,9 @@ class IPAdapterInvocation(BaseInvocation): ui_order=-1, ui_type=UIType.IPAdapterModel, ) - + clip_vision_model: Literal["ViT-H", "ViT-G"] = InputField( + description="CLIP Vision model to use", default="ViT-H", ui_order=2 + ) weight: Union[float, List[float]] = InputField( default=1, description="The weight given to the IP-Adapter", title="Weight" ) @@ -89,12 +94,12 @@ class IPAdapterInvocation(BaseInvocation): ip_adapter_info = context.models.get_config(self.ip_adapter_model.key) assert isinstance(ip_adapter_info, (IPAdapterDiffusersConfig, IPAdapterCheckpointConfig)) - image_encoder_model_id = ( - ip_adapter_info.image_encoder_model_id - if isinstance(ip_adapter_info, IPAdapterDiffusersConfig) - else "ip_adapter_sd_image_encoder" - ) - image_encoder_model_name = image_encoder_model_id.split("/")[-1].strip() + if isinstance(ip_adapter_info, IPAdapterDiffusersConfig): + image_encoder_model_id = ip_adapter_info.image_encoder_model_id + image_encoder_model_name = image_encoder_model_id.split("/")[-1].strip() + else: + image_encoder_model_name = CLIP_VISION_MODEL_MAP[self.clip_vision_model] + image_encoder_model = self._get_image_encoder(context, image_encoder_model_name) return IPAdapterOutput( @@ -109,19 +114,25 @@ class IPAdapterInvocation(BaseInvocation): ) def _get_image_encoder(self, context: InvocationContext, image_encoder_model_name: str) -> AnyModelConfig: - found = False - while not found: + image_encoder_models = context.models.search_by_attrs( + name=image_encoder_model_name, base=BaseModelType.Any, type=ModelType.CLIPVision + ) + + if not len(image_encoder_models) > 0: + context.logger.warning( + f"The image encoder required by this IP Adapter ({image_encoder_model_name}) is not installed. \ + Downloading and installing now. This may take a while." + ) + + installer = context._services.model_manager.install + job = installer.heuristic_import(f"InvokeAI/{image_encoder_model_name}") + installer.wait_for_job(job, timeout=600) # Wait for up to 10 minutes image_encoder_models = context.models.search_by_attrs( name=image_encoder_model_name, base=BaseModelType.Any, type=ModelType.CLIPVision ) - found = len(image_encoder_models) > 0 - if not found: - context.logger.warning( - f"The image encoder required by this IP Adapter ({image_encoder_model_name}) is not installed." - ) - context.logger.warning("Downloading and installing now. This may take a while.") - installer = context._services.model_manager.install - job = installer.heuristic_import(f"InvokeAI/{image_encoder_model_name}") - installer.wait_for_job(job, timeout=600) # wait up to 10 minutes - then raise a TimeoutException - assert len(image_encoder_models) == 1 + + if len(image_encoder_models) == 0: + context.logger.error("Error while fetching CLIP Vision Image Encoder") + assert len(image_encoder_models) == 1 + return image_encoder_models[0] diff --git a/invokeai/app/invocations/metadata.py b/invokeai/app/invocations/metadata.py index 6fc72a1c3f..2da482c833 100644 --- a/invokeai/app/invocations/metadata.py +++ b/invokeai/app/invocations/metadata.py @@ -2,16 +2,8 @@ from typing import Any, Literal, Optional, Union from pydantic import BaseModel, ConfigDict, Field -from invokeai.app.invocations.baseinvocation import ( - BaseInvocation, - BaseInvocationOutput, - invocation, - invocation_output, -) -from invokeai.app.invocations.controlnet_image_processors import ( - CONTROLNET_MODE_VALUES, - CONTROLNET_RESIZE_VALUES, -) +from invokeai.app.invocations.baseinvocation import BaseInvocation, BaseInvocationOutput, invocation, invocation_output +from invokeai.app.invocations.controlnet_image_processors import CONTROLNET_MODE_VALUES, CONTROLNET_RESIZE_VALUES from invokeai.app.invocations.fields import ( FieldDescriptions, ImageField, @@ -43,6 +35,7 @@ class IPAdapterMetadataField(BaseModel): image: ImageField = Field(description="The IP-Adapter image prompt.") ip_adapter_model: ModelIdentifierField = Field(description="The IP-Adapter model.") + clip_vision_model: Literal["ViT-H", "ViT-G"] = Field(description="The CLIP Vision model") weight: Union[float, list[float]] = Field(description="The weight given to the IP-Adapter") begin_step_percent: float = Field(description="When the IP-Adapter is first applied (% of total steps)") end_step_percent: float = Field(description="When the IP-Adapter is last applied (% of total steps)") diff --git a/invokeai/frontend/web/public/locales/en.json b/invokeai/frontend/web/public/locales/en.json index d2402c61be..5872d22dfe 100644 --- a/invokeai/frontend/web/public/locales/en.json +++ b/invokeai/frontend/web/public/locales/en.json @@ -217,6 +217,7 @@ "saveControlImage": "Save Control Image", "scribble": "scribble", "selectModel": "Select a model", + "selectCLIPVisionModel": "Select a CLIP Vision model", "setControlImageDimensions": "Set Control Image Dimensions To W/H", "showAdvanced": "Show Advanced", "small": "Small", diff --git a/invokeai/frontend/web/src/features/controlAdapters/components/parameters/ParamControlAdapterModel.tsx b/invokeai/frontend/web/src/features/controlAdapters/components/parameters/ParamControlAdapterModel.tsx index 25d327e54e..380a33185d 100644 --- a/invokeai/frontend/web/src/features/controlAdapters/components/parameters/ParamControlAdapterModel.tsx +++ b/invokeai/frontend/web/src/features/controlAdapters/components/parameters/ParamControlAdapterModel.tsx @@ -1,12 +1,18 @@ -import { Combobox, FormControl, Tooltip } from '@invoke-ai/ui-library'; +import type { ComboboxOnChange, ComboboxOption } from '@invoke-ai/ui-library'; +import { Combobox, Flex, FormControl, Tooltip } from '@invoke-ai/ui-library'; import { createMemoizedSelector } from 'app/store/createMemoizedSelector'; import { useAppDispatch, useAppSelector } from 'app/store/storeHooks'; import { useGroupedModelCombobox } from 'common/hooks/useGroupedModelCombobox'; +import { useControlAdapterCLIPVisionModel } from 'features/controlAdapters/hooks/useControlAdapterCLIPVisionModel'; import { useControlAdapterIsEnabled } from 'features/controlAdapters/hooks/useControlAdapterIsEnabled'; import { useControlAdapterModel } from 'features/controlAdapters/hooks/useControlAdapterModel'; import { useControlAdapterModels } from 'features/controlAdapters/hooks/useControlAdapterModels'; import { useControlAdapterType } from 'features/controlAdapters/hooks/useControlAdapterType'; -import { controlAdapterModelChanged } from 'features/controlAdapters/store/controlAdaptersSlice'; +import { + controlAdapterCLIPVisionModelChanged, + controlAdapterModelChanged, +} from 'features/controlAdapters/store/controlAdaptersSlice'; +import type { CLIPVisionModel } from 'features/controlAdapters/store/types'; import { selectGenerationSlice } from 'features/parameters/store/generationSlice'; import { memo, useCallback, useMemo } from 'react'; import { useTranslation } from 'react-i18next'; @@ -29,6 +35,7 @@ const ParamControlAdapterModel = ({ id }: ParamControlAdapterModelProps) => { const { modelConfig } = useControlAdapterModel(id); const dispatch = useAppDispatch(); const currentBaseModel = useAppSelector((s) => s.generation.model?.base); + const currentCLIPVisionModel = useControlAdapterCLIPVisionModel(id); const mainModel = useAppSelector(selectMainModel); const { t } = useTranslation(); @@ -49,6 +56,16 @@ const ParamControlAdapterModel = ({ id }: ParamControlAdapterModelProps) => { [dispatch, id] ); + const onCLIPVisionModelChange = useCallback( + (v) => { + if (!v?.value) { + return; + } + dispatch(controlAdapterCLIPVisionModelChanged({ id, clipVisionModel: v.value as CLIPVisionModel })); + }, + [dispatch, id] + ); + const selectedModel = useMemo( () => (modelConfig && controlAdapterType ? { ...modelConfig, model_type: controlAdapterType } : null), [controlAdapterType, modelConfig] @@ -71,17 +88,42 @@ const ParamControlAdapterModel = ({ id }: ParamControlAdapterModelProps) => { isLoading, }); + const clipVisionOptions = useMemo( + () => [ + { label: 'ViT-H', value: 'ViT-H' }, + { label: 'ViT-G', value: 'ViT-G' }, + ], + [] + ); + + const clipVisionModel = useMemo( + () => clipVisionOptions.find((o) => o.value === currentCLIPVisionModel), + [clipVisionOptions, currentCLIPVisionModel] + ); + return ( - - - + + + + + {modelConfig?.type === 'ip_adapter' && modelConfig.format === 'checkpoint' && ( + + + + )} + ); }; diff --git a/invokeai/frontend/web/src/features/controlAdapters/hooks/useControlAdapterCLIPVisionModel.ts b/invokeai/frontend/web/src/features/controlAdapters/hooks/useControlAdapterCLIPVisionModel.ts new file mode 100644 index 0000000000..249d2022fe --- /dev/null +++ b/invokeai/frontend/web/src/features/controlAdapters/hooks/useControlAdapterCLIPVisionModel.ts @@ -0,0 +1,24 @@ +import { createMemoizedSelector } from 'app/store/createMemoizedSelector'; +import { useAppSelector } from 'app/store/storeHooks'; +import { + selectControlAdapterById, + selectControlAdaptersSlice, +} from 'features/controlAdapters/store/controlAdaptersSlice'; +import { useMemo } from 'react'; + +export const useControlAdapterCLIPVisionModel = (id: string) => { + const selector = useMemo( + () => + createMemoizedSelector(selectControlAdaptersSlice, (controlAdapters) => { + const cn = selectControlAdapterById(controlAdapters, id); + if (cn && cn?.type === 'ip_adapter') { + return cn.clipVisionModel; + } + }), + [id] + ); + + const clipVisionModel = useAppSelector(selector); + + return clipVisionModel; +}; diff --git a/invokeai/frontend/web/src/features/controlAdapters/store/controlAdaptersSlice.ts b/invokeai/frontend/web/src/features/controlAdapters/store/controlAdaptersSlice.ts index 39dc0dce3d..3fb19b50a7 100644 --- a/invokeai/frontend/web/src/features/controlAdapters/store/controlAdaptersSlice.ts +++ b/invokeai/frontend/web/src/features/controlAdapters/store/controlAdaptersSlice.ts @@ -13,6 +13,7 @@ import { v4 as uuidv4 } from 'uuid'; import { controlAdapterImageProcessed } from './actions'; import { CONTROLNET_PROCESSORS } from './constants'; import type { + CLIPVisionModel, ControlAdapterConfig, ControlAdapterProcessorType, ControlAdaptersState, @@ -243,6 +244,13 @@ export const controlAdaptersSlice = createSlice({ } caAdapter.updateOne(state, { id, changes: { controlMode } }); }, + controlAdapterCLIPVisionModelChanged: ( + state, + action: PayloadAction<{ id: string; clipVisionModel: CLIPVisionModel }> + ) => { + const { id, clipVisionModel } = action.payload; + caAdapter.updateOne(state, { id, changes: { clipVisionModel } }); + }, controlAdapterResizeModeChanged: ( state, action: PayloadAction<{ @@ -380,6 +388,7 @@ export const { controlAdapterProcessedImageChanged, controlAdapterIsEnabledChanged, controlAdapterModelChanged, + controlAdapterCLIPVisionModelChanged, controlAdapterWeightChanged, controlAdapterBeginStepPctChanged, controlAdapterEndStepPctChanged, diff --git a/invokeai/frontend/web/src/features/controlAdapters/store/types.ts b/invokeai/frontend/web/src/features/controlAdapters/store/types.ts index 93d4915cdf..329c318759 100644 --- a/invokeai/frontend/web/src/features/controlAdapters/store/types.ts +++ b/invokeai/frontend/web/src/features/controlAdapters/store/types.ts @@ -243,12 +243,15 @@ export type T2IAdapterConfig = { shouldAutoConfig: boolean; }; +export type CLIPVisionModel = 'ViT-H' | 'ViT-G'; + export type IPAdapterConfig = { type: 'ip_adapter'; id: string; isEnabled: boolean; controlImage: string | null; model: ParameterIPAdapterModel | null; + clipVisionModel: CLIPVisionModel; weight: number; beginStepPct: number; endStepPct: number; diff --git a/invokeai/frontend/web/src/features/controlAdapters/util/buildControlAdapter.ts b/invokeai/frontend/web/src/features/controlAdapters/util/buildControlAdapter.ts index 94a867cf88..c2cdd9ccd9 100644 --- a/invokeai/frontend/web/src/features/controlAdapters/util/buildControlAdapter.ts +++ b/invokeai/frontend/web/src/features/controlAdapters/util/buildControlAdapter.ts @@ -45,6 +45,7 @@ export const initialIPAdapter: Omit = { isEnabled: true, controlImage: null, model: null, + clipVisionModel: 'ViT-H', weight: 1, beginStepPct: 0, endStepPct: 1, diff --git a/invokeai/frontend/web/src/features/nodes/util/graph/addIPAdapterToLinearGraph.ts b/invokeai/frontend/web/src/features/nodes/util/graph/addIPAdapterToLinearGraph.ts index 2298e84d43..ad563de468 100644 --- a/invokeai/frontend/web/src/features/nodes/util/graph/addIPAdapterToLinearGraph.ts +++ b/invokeai/frontend/web/src/features/nodes/util/graph/addIPAdapterToLinearGraph.ts @@ -48,7 +48,7 @@ export const addIPAdapterToLinearGraph = async ( if (!ipAdapter.model) { return; } - const { id, weight, model, beginStepPct, endStepPct, controlImage } = ipAdapter; + const { id, weight, model, clipVisionModel, beginStepPct, endStepPct, controlImage } = ipAdapter; assert(controlImage, 'IP Adapter image is required'); @@ -58,6 +58,7 @@ export const addIPAdapterToLinearGraph = async ( is_intermediate: true, weight: weight, ip_adapter_model: model, + clip_vision_model: clipVisionModel, begin_step_percent: beginStepPct, end_step_percent: endStepPct, image: { @@ -83,7 +84,7 @@ export const addIPAdapterToLinearGraph = async ( }; const buildIPAdapterMetadata = (ipAdapter: IPAdapterConfig): S['IPAdapterMetadataField'] => { - const { controlImage, beginStepPct, endStepPct, model, weight } = ipAdapter; + const { controlImage, beginStepPct, endStepPct, model, clipVisionModel, weight } = ipAdapter; assert(model, 'IP Adapter model is required'); @@ -99,6 +100,7 @@ const buildIPAdapterMetadata = (ipAdapter: IPAdapterConfig): S['IPAdapterMetadat return { ip_adapter_model: model, + clip_vision_model: clipVisionModel, weight, begin_step_percent: beginStepPct, end_step_percent: endStepPct, diff --git a/invokeai/frontend/web/src/services/api/schema.ts b/invokeai/frontend/web/src/services/api/schema.ts index 70cd37376a..fcc7e593b4 100644 --- a/invokeai/frontend/web/src/services/api/schema.ts +++ b/invokeai/frontend/web/src/services/api/schema.ts @@ -4169,7 +4169,7 @@ export type components = { * @description The nodes in this graph */ nodes: { - [key: string]: components["schemas"]["FaceIdentifierInvocation"] | components["schemas"]["AddInvocation"] | components["schemas"]["NormalbaeImageProcessorInvocation"] | components["schemas"]["T2IAdapterInvocation"] | components["schemas"]["CenterPadCropInvocation"] | components["schemas"]["ColorCorrectInvocation"] | components["schemas"]["IntegerInvocation"] | components["schemas"]["FaceOffInvocation"] | components["schemas"]["StringCollectionInvocation"] | components["schemas"]["SchedulerInvocation"] | components["schemas"]["LatentsInvocation"] | components["schemas"]["MlsdImageProcessorInvocation"] | components["schemas"]["CalculateImageTilesMinimumOverlapInvocation"] | components["schemas"]["ImageChannelInvocation"] | components["schemas"]["ESRGANInvocation"] | components["schemas"]["RandomIntInvocation"] | components["schemas"]["RoundInvocation"] | components["schemas"]["LineartImageProcessorInvocation"] | components["schemas"]["BooleanInvocation"] | components["schemas"]["ScaleLatentsInvocation"] | components["schemas"]["ImageChannelMultiplyInvocation"] | components["schemas"]["RandomRangeInvocation"] | components["schemas"]["BooleanCollectionInvocation"] | components["schemas"]["LeresImageProcessorInvocation"] | components["schemas"]["ConditioningCollectionInvocation"] | components["schemas"]["CropLatentsCoreInvocation"] | components["schemas"]["HedImageProcessorInvocation"] | components["schemas"]["PromptsFromFileInvocation"] | components["schemas"]["CalculateImageTilesEvenSplitInvocation"] | components["schemas"]["MediapipeFaceProcessorInvocation"] | components["schemas"]["ImagePasteInvocation"] | components["schemas"]["ImageHueAdjustmentInvocation"] | components["schemas"]["StringJoinInvocation"] | components["schemas"]["LatentsCollectionInvocation"] | components["schemas"]["MergeTilesToImageInvocation"] | components["schemas"]["MaskFromIDInvocation"] | components["schemas"]["LoRALoaderInvocation"] | components["schemas"]["RandomFloatInvocation"] | components["schemas"]["IntegerCollectionInvocation"] | components["schemas"]["CanvasPasteBackInvocation"] | components["schemas"]["NoiseInvocation"] | components["schemas"]["MetadataInvocation"] | components["schemas"]["ImageNSFWBlurInvocation"] | components["schemas"]["FreeUInvocation"] | components["schemas"]["LaMaInfillInvocation"] | components["schemas"]["CannyImageProcessorInvocation"] | components["schemas"]["StringSplitInvocation"] | components["schemas"]["ImageInvocation"] | components["schemas"]["BlendLatentsInvocation"] | components["schemas"]["VAELoaderInvocation"] | components["schemas"]["CompelInvocation"] | components["schemas"]["IterateInvocation"] | components["schemas"]["PidiImageProcessorInvocation"] | components["schemas"]["ShowImageInvocation"] | components["schemas"]["ImageLerpInvocation"] | components["schemas"]["SegmentAnythingProcessorInvocation"] | components["schemas"]["MaskFromAlphaInvocation"] | components["schemas"]["StringInvocation"] | components["schemas"]["DepthAnythingImageProcessorInvocation"] | components["schemas"]["DenoiseLatentsInvocation"] | components["schemas"]["ImageCropInvocation"] | components["schemas"]["ImageChannelOffsetInvocation"] | components["schemas"]["IdealSizeInvocation"] | components["schemas"]["InfillColorInvocation"] | components["schemas"]["FloatMathInvocation"] | components["schemas"]["LineartAnimeImageProcessorInvocation"] | components["schemas"]["CLIPSkipInvocation"] | components["schemas"]["CreateGradientMaskInvocation"] | components["schemas"]["SDXLModelLoaderInvocation"] | components["schemas"]["StringJoinThreeInvocation"] | components["schemas"]["ColorInvocation"] | components["schemas"]["ImageMultiplyInvocation"] | components["schemas"]["ResizeLatentsInvocation"] | components["schemas"]["DynamicPromptInvocation"] | components["schemas"]["MergeMetadataInvocation"] | components["schemas"]["StepParamEasingInvocation"] | components["schemas"]["TileToPropertiesInvocation"] | components["schemas"]["LatentsToImageInvocation"] | components["schemas"]["CreateDenoiseMaskInvocation"] | components["schemas"]["UnsharpMaskInvocation"] | components["schemas"]["FloatInvocation"] | components["schemas"]["ImageWatermarkInvocation"] | components["schemas"]["ContentShuffleImageProcessorInvocation"] | components["schemas"]["CV2InfillInvocation"] | components["schemas"]["IntegerMathInvocation"] | components["schemas"]["BriaRemoveBackgroundInvocation"] | components["schemas"]["ColorMapImageProcessorInvocation"] | components["schemas"]["HandDepthMeshGraphormerProcessor"] | components["schemas"]["SDXLCompelPromptInvocation"] | components["schemas"]["MainModelLoaderInvocation"] | components["schemas"]["InfillPatchMatchInvocation"] | components["schemas"]["BlankImageInvocation"] | components["schemas"]["SaveImageInvocation"] | components["schemas"]["SDXLLoRALoaderInvocation"] | components["schemas"]["DivideInvocation"] | components["schemas"]["ControlNetInvocation"] | components["schemas"]["InfillTileInvocation"] | components["schemas"]["StringSplitNegInvocation"] | components["schemas"]["ImageCollectionInvocation"] | components["schemas"]["SeamlessModeInvocation"] | components["schemas"]["StringReplaceInvocation"] | components["schemas"]["ImageToLatentsInvocation"] | components["schemas"]["MetadataItemInvocation"] | components["schemas"]["MidasDepthImageProcessorInvocation"] | components["schemas"]["ConditioningInvocation"] | components["schemas"]["MaskCombineInvocation"] | components["schemas"]["MultiplyInvocation"] | components["schemas"]["RangeInvocation"] | components["schemas"]["TileResamplerProcessorInvocation"] | components["schemas"]["ImageResizeInvocation"] | components["schemas"]["FloatToIntegerInvocation"] | components["schemas"]["CvInpaintInvocation"] | components["schemas"]["SDXLRefinerModelLoaderInvocation"] | components["schemas"]["IPAdapterInvocation"] | components["schemas"]["SubtractInvocation"] | components["schemas"]["ZoeDepthImageProcessorInvocation"] | components["schemas"]["FaceMaskInvocation"] | components["schemas"]["FloatLinearRangeInvocation"] | components["schemas"]["ImageInverseLerpInvocation"] | components["schemas"]["RangeOfSizeInvocation"] | components["schemas"]["SDXLRefinerCompelPromptInvocation"] | components["schemas"]["PairTileImageInvocation"] | components["schemas"]["MaskEdgeInvocation"] | components["schemas"]["CollectInvocation"] | components["schemas"]["DWOpenposeImageProcessorInvocation"] | components["schemas"]["ImageConvertInvocation"] | components["schemas"]["FloatCollectionInvocation"] | components["schemas"]["CalculateImageTilesInvocation"] | components["schemas"]["ImageBlurInvocation"] | components["schemas"]["ImageScaleInvocation"] | components["schemas"]["CoreMetadataInvocation"]; + [key: string]: components["schemas"]["SaveImageInvocation"] | components["schemas"]["AddInvocation"] | components["schemas"]["IPAdapterInvocation"] | components["schemas"]["BriaRemoveBackgroundInvocation"] | components["schemas"]["BooleanInvocation"] | components["schemas"]["StringCollectionInvocation"] | components["schemas"]["ImageChannelInvocation"] | components["schemas"]["LatentsCollectionInvocation"] | components["schemas"]["ImageNSFWBlurInvocation"] | components["schemas"]["FloatLinearRangeInvocation"] | components["schemas"]["ImageCropInvocation"] | components["schemas"]["IdealSizeInvocation"] | components["schemas"]["ImageCollectionInvocation"] | components["schemas"]["ScaleLatentsInvocation"] | components["schemas"]["IntegerMathInvocation"] | components["schemas"]["MaskEdgeInvocation"] | components["schemas"]["BlendLatentsInvocation"] | components["schemas"]["CalculateImageTilesEvenSplitInvocation"] | components["schemas"]["HandDepthMeshGraphormerProcessor"] | components["schemas"]["FloatInvocation"] | components["schemas"]["DepthAnythingImageProcessorInvocation"] | components["schemas"]["PairTileImageInvocation"] | components["schemas"]["MlsdImageProcessorInvocation"] | components["schemas"]["NoiseInvocation"] | components["schemas"]["CoreMetadataInvocation"] | components["schemas"]["UnsharpMaskInvocation"] | components["schemas"]["DivideInvocation"] | components["schemas"]["SDXLModelLoaderInvocation"] | components["schemas"]["ControlNetInvocation"] | components["schemas"]["ZoeDepthImageProcessorInvocation"] | components["schemas"]["CV2InfillInvocation"] | components["schemas"]["CalculateImageTilesMinimumOverlapInvocation"] | components["schemas"]["IterateInvocation"] | components["schemas"]["SeamlessModeInvocation"] | components["schemas"]["CreateGradientMaskInvocation"] | components["schemas"]["CollectInvocation"] | components["schemas"]["MediapipeFaceProcessorInvocation"] | components["schemas"]["FreeUInvocation"] | components["schemas"]["InfillTileInvocation"] | components["schemas"]["CanvasPasteBackInvocation"] | components["schemas"]["StepParamEasingInvocation"] | components["schemas"]["StringReplaceInvocation"] | components["schemas"]["SDXLRefinerCompelPromptInvocation"] | components["schemas"]["VAELoaderInvocation"] | components["schemas"]["FaceIdentifierInvocation"] | components["schemas"]["ImageChannelOffsetInvocation"] | components["schemas"]["StringJoinInvocation"] | components["schemas"]["InfillPatchMatchInvocation"] | components["schemas"]["CompelInvocation"] | components["schemas"]["ImageToLatentsInvocation"] | components["schemas"]["ColorCorrectInvocation"] | components["schemas"]["CannyImageProcessorInvocation"] | components["schemas"]["LeresImageProcessorInvocation"] | components["schemas"]["StringSplitInvocation"] | components["schemas"]["BlankImageInvocation"] | components["schemas"]["CalculateImageTilesInvocation"] | components["schemas"]["MergeMetadataInvocation"] | components["schemas"]["ImageBlurInvocation"] | components["schemas"]["ImageChannelMultiplyInvocation"] | components["schemas"]["T2IAdapterInvocation"] | components["schemas"]["ConditioningInvocation"] | components["schemas"]["ImageWatermarkInvocation"] | components["schemas"]["BooleanCollectionInvocation"] | components["schemas"]["PidiImageProcessorInvocation"] | components["schemas"]["FloatCollectionInvocation"] | components["schemas"]["MaskFromIDInvocation"] | components["schemas"]["ImageConvertInvocation"] | components["schemas"]["SegmentAnythingProcessorInvocation"] | components["schemas"]["LaMaInfillInvocation"] | components["schemas"]["RangeOfSizeInvocation"] | components["schemas"]["ImageInverseLerpInvocation"] | components["schemas"]["MultiplyInvocation"] | components["schemas"]["ImageHueAdjustmentInvocation"] | components["schemas"]["RoundInvocation"] | components["schemas"]["ImageMultiplyInvocation"] | components["schemas"]["MaskCombineInvocation"] | components["schemas"]["MetadataItemInvocation"] | components["schemas"]["NormalbaeImageProcessorInvocation"] | components["schemas"]["CenterPadCropInvocation"] | components["schemas"]["TileToPropertiesInvocation"] | components["schemas"]["PromptsFromFileInvocation"] | components["schemas"]["ContentShuffleImageProcessorInvocation"] | components["schemas"]["SchedulerInvocation"] | components["schemas"]["MainModelLoaderInvocation"] | components["schemas"]["SDXLCompelPromptInvocation"] | components["schemas"]["ImageLerpInvocation"] | components["schemas"]["FloatToIntegerInvocation"] | components["schemas"]["StringJoinThreeInvocation"] | components["schemas"]["MidasDepthImageProcessorInvocation"] | components["schemas"]["FaceOffInvocation"] | components["schemas"]["ShowImageInvocation"] | components["schemas"]["ConditioningCollectionInvocation"] | components["schemas"]["RandomRangeInvocation"] | components["schemas"]["ImageResizeInvocation"] | components["schemas"]["CLIPSkipInvocation"] | components["schemas"]["CreateDenoiseMaskInvocation"] | components["schemas"]["IntegerInvocation"] | components["schemas"]["TileResamplerProcessorInvocation"] | components["schemas"]["ESRGANInvocation"] | components["schemas"]["MetadataInvocation"] | components["schemas"]["ImageScaleInvocation"] | components["schemas"]["RandomFloatInvocation"] | components["schemas"]["ColorInvocation"] | components["schemas"]["CropLatentsCoreInvocation"] | components["schemas"]["StringInvocation"] | components["schemas"]["SDXLRefinerModelLoaderInvocation"] | components["schemas"]["HedImageProcessorInvocation"] | components["schemas"]["ColorMapImageProcessorInvocation"] | components["schemas"]["SDXLLoRALoaderInvocation"] | components["schemas"]["MergeTilesToImageInvocation"] | components["schemas"]["LatentsInvocation"] | components["schemas"]["ImagePasteInvocation"] | components["schemas"]["LineartAnimeImageProcessorInvocation"] | components["schemas"]["StringSplitNegInvocation"] | components["schemas"]["SubtractInvocation"] | components["schemas"]["DynamicPromptInvocation"] | components["schemas"]["CvInpaintInvocation"] | components["schemas"]["FaceMaskInvocation"] | components["schemas"]["ResizeLatentsInvocation"] | components["schemas"]["ImageInvocation"] | components["schemas"]["InfillColorInvocation"] | components["schemas"]["RandomIntInvocation"] | components["schemas"]["RangeInvocation"] | components["schemas"]["MaskFromAlphaInvocation"] | components["schemas"]["LatentsToImageInvocation"] | components["schemas"]["LineartImageProcessorInvocation"] | components["schemas"]["DWOpenposeImageProcessorInvocation"] | components["schemas"]["DenoiseLatentsInvocation"] | components["schemas"]["FloatMathInvocation"] | components["schemas"]["LoRALoaderInvocation"] | components["schemas"]["IntegerCollectionInvocation"]; }; /** * Edges @@ -4206,7 +4206,7 @@ export type components = { * @description The results of node executions */ results: { - [key: string]: components["schemas"]["BooleanOutput"] | components["schemas"]["ImageCollectionOutput"] | components["schemas"]["SDXLLoRALoaderOutput"] | components["schemas"]["LatentsCollectionOutput"] | components["schemas"]["GradientMaskOutput"] | components["schemas"]["ModelLoaderOutput"] | components["schemas"]["PairTileImageOutput"] | components["schemas"]["StringPosNegOutput"] | components["schemas"]["CollectInvocationOutput"] | components["schemas"]["StringOutput"] | components["schemas"]["SchedulerOutput"] | components["schemas"]["CLIPOutput"] | components["schemas"]["BooleanCollectionOutput"] | components["schemas"]["FaceMaskOutput"] | components["schemas"]["ConditioningCollectionOutput"] | components["schemas"]["MetadataOutput"] | components["schemas"]["UNetOutput"] | components["schemas"]["IPAdapterOutput"] | components["schemas"]["T2IAdapterOutput"] | components["schemas"]["ConditioningOutput"] | components["schemas"]["ControlOutput"] | components["schemas"]["StringCollectionOutput"] | components["schemas"]["CalculateImageTilesOutput"] | components["schemas"]["ColorOutput"] | components["schemas"]["IterateInvocationOutput"] | components["schemas"]["SDXLModelLoaderOutput"] | components["schemas"]["TileToPropertiesOutput"] | components["schemas"]["SeamlessModeOutput"] | components["schemas"]["FloatOutput"] | components["schemas"]["FaceOffOutput"] | components["schemas"]["ImageOutput"] | components["schemas"]["SDXLRefinerModelLoaderOutput"] | components["schemas"]["String2Output"] | components["schemas"]["IntegerCollectionOutput"] | components["schemas"]["ColorCollectionOutput"] | components["schemas"]["VAEOutput"] | components["schemas"]["NoiseOutput"] | components["schemas"]["DenoiseMaskOutput"] | components["schemas"]["LatentsOutput"] | components["schemas"]["MetadataItemOutput"] | components["schemas"]["LoRALoaderOutput"] | components["schemas"]["FloatCollectionOutput"] | components["schemas"]["HandDepthOutput"] | components["schemas"]["IntegerOutput"] | components["schemas"]["IdealSizeOutput"] | components["schemas"]["CLIPSkipInvocationOutput"]; + [key: string]: components["schemas"]["TileToPropertiesOutput"] | components["schemas"]["ImageCollectionOutput"] | components["schemas"]["ColorOutput"] | components["schemas"]["VAEOutput"] | components["schemas"]["String2Output"] | components["schemas"]["UNetOutput"] | components["schemas"]["SDXLModelLoaderOutput"] | components["schemas"]["ConditioningCollectionOutput"] | components["schemas"]["SchedulerOutput"] | components["schemas"]["IterateInvocationOutput"] | components["schemas"]["CLIPSkipInvocationOutput"] | components["schemas"]["PairTileImageOutput"] | components["schemas"]["StringCollectionOutput"] | components["schemas"]["LatentsCollectionOutput"] | components["schemas"]["T2IAdapterOutput"] | components["schemas"]["GradientMaskOutput"] | components["schemas"]["StringOutput"] | components["schemas"]["BooleanCollectionOutput"] | components["schemas"]["LoRALoaderOutput"] | components["schemas"]["ColorCollectionOutput"] | components["schemas"]["StringPosNegOutput"] | components["schemas"]["ConditioningOutput"] | components["schemas"]["IPAdapterOutput"] | components["schemas"]["IntegerCollectionOutput"] | components["schemas"]["ImageOutput"] | components["schemas"]["FaceOffOutput"] | components["schemas"]["CLIPOutput"] | components["schemas"]["CollectInvocationOutput"] | components["schemas"]["CalculateImageTilesOutput"] | components["schemas"]["SDXLLoRALoaderOutput"] | components["schemas"]["LatentsOutput"] | components["schemas"]["NoiseOutput"] | components["schemas"]["ModelLoaderOutput"] | components["schemas"]["MetadataItemOutput"] | components["schemas"]["BooleanOutput"] | components["schemas"]["IdealSizeOutput"] | components["schemas"]["SeamlessModeOutput"] | components["schemas"]["FloatCollectionOutput"] | components["schemas"]["DenoiseMaskOutput"] | components["schemas"]["ControlOutput"] | components["schemas"]["HandDepthOutput"] | components["schemas"]["MetadataOutput"] | components["schemas"]["FloatOutput"] | components["schemas"]["FaceMaskOutput"] | components["schemas"]["SDXLRefinerModelLoaderOutput"] | components["schemas"]["IntegerOutput"]; }; /** * Errors @@ -4634,6 +4634,13 @@ export type components = { * @description The IP-Adapter model. */ ip_adapter_model: components["schemas"]["ModelIdentifierField"]; + /** + * Clip Vision Model + * @description CLIP Vision model to use + * @default ViT-H + * @enum {string} + */ + clip_vision_model?: "ViT-H" | "ViT-G"; /** * Weight * @description The weight given to the IP-Adapter @@ -4668,6 +4675,12 @@ export type components = { image: components["schemas"]["ImageField"]; /** @description The IP-Adapter model. */ ip_adapter_model: components["schemas"]["ModelIdentifierField"]; + /** + * Clip Vision Model + * @description The CLIP Vision model + * @enum {string} + */ + clip_vision_model: "ViT-H" | "ViT-G"; /** * Weight * @description The weight given to the IP-Adapter From 1a93f56d06b6de2b87ab7575c6f6e317cb082a5f Mon Sep 17 00:00:00 2001 From: blessedcoolant <54517381+blessedcoolant@users.noreply.github.com> Date: Wed, 27 Mar 2024 22:05:53 +0530 Subject: [PATCH 08/89] ui: improve the clip vision model picker layout --- invokeai/backend/model_manager/probe.py | 23 +++++--- .../parameters/ParamControlAdapterModel.tsx | 38 ++++++++----- .../frontend/web/src/services/api/schema.ts | 56 +------------------ 3 files changed, 40 insertions(+), 77 deletions(-) diff --git a/invokeai/backend/model_manager/probe.py b/invokeai/backend/model_manager/probe.py index 75f156ce21..7fc8c99e29 100644 --- a/invokeai/backend/model_manager/probe.py +++ b/invokeai/backend/model_manager/probe.py @@ -9,16 +9,23 @@ from picklescan.scanner import scan_file_path import invokeai.backend.util.logging as logger from invokeai.app.util.misc import uuid_string -from invokeai.backend.model_hash.model_hash import (HASHING_ALGORITHMS, - ModelHash) +from invokeai.backend.model_hash.model_hash import HASHING_ALGORITHMS, ModelHash from invokeai.backend.util.util import SilenceWarnings -from .config import (AnyModelConfig, BaseModelType, - ControlAdapterDefaultSettings, - InvalidModelConfigException, MainModelDefaultSettings, - ModelConfigFactory, ModelFormat, ModelRepoVariant, - ModelSourceType, ModelType, ModelVariantType, - SchedulerPredictionType) +from .config import ( + AnyModelConfig, + BaseModelType, + ControlAdapterDefaultSettings, + InvalidModelConfigException, + MainModelDefaultSettings, + ModelConfigFactory, + ModelFormat, + ModelRepoVariant, + ModelSourceType, + ModelType, + ModelVariantType, + SchedulerPredictionType, +) from .util.model_util import lora_token_vector_length, read_checkpoint_meta CkptType = Dict[str | int, Any] diff --git a/invokeai/frontend/web/src/features/controlAdapters/components/parameters/ParamControlAdapterModel.tsx b/invokeai/frontend/web/src/features/controlAdapters/components/parameters/ParamControlAdapterModel.tsx index 380a33185d..91f8822352 100644 --- a/invokeai/frontend/web/src/features/controlAdapters/components/parameters/ParamControlAdapterModel.tsx +++ b/invokeai/frontend/web/src/features/controlAdapters/components/parameters/ParamControlAdapterModel.tsx @@ -102,9 +102,13 @@ const ParamControlAdapterModel = ({ id }: ParamControlAdapterModelProps) => { ); return ( - - - + + + { noOptionsMessage={noOptionsMessage} /> - {modelConfig?.type === 'ip_adapter' && modelConfig.format === 'checkpoint' && ( - - - - )} - - + + {modelConfig?.type === 'ip_adapter' && modelConfig.format === 'checkpoint' && ( + + + + )} + ); }; diff --git a/invokeai/frontend/web/src/services/api/schema.ts b/invokeai/frontend/web/src/services/api/schema.ts index fcc7e593b4..9b78deccac 100644 --- a/invokeai/frontend/web/src/services/api/schema.ts +++ b/invokeai/frontend/web/src/services/api/schema.ts @@ -159,12 +159,6 @@ export type paths = { /** Get Starter Models */ get: operations["get_starter_models"]; }; - "/api/v2/models/hf_login": { - /** Get Hf Login Status */ - get: operations["get_hf_login_status"]; - /** Do Hf Login */ - post: operations["do_hf_login"]; - }; "/api/v1/download_queue/": { /** * List Downloads @@ -1028,14 +1022,6 @@ export type components = { */ image_names: string[]; }; - /** Body_do_hf_login */ - Body_do_hf_login: { - /** - * Token - * @description Hugging Face token to use for login - */ - token: string; - }; /** Body_download */ Body_download: { /** @@ -4169,7 +4155,7 @@ export type components = { * @description The nodes in this graph */ nodes: { - [key: string]: components["schemas"]["SaveImageInvocation"] | components["schemas"]["AddInvocation"] | components["schemas"]["IPAdapterInvocation"] | components["schemas"]["BriaRemoveBackgroundInvocation"] | components["schemas"]["BooleanInvocation"] | components["schemas"]["StringCollectionInvocation"] | components["schemas"]["ImageChannelInvocation"] | components["schemas"]["LatentsCollectionInvocation"] | components["schemas"]["ImageNSFWBlurInvocation"] | components["schemas"]["FloatLinearRangeInvocation"] | components["schemas"]["ImageCropInvocation"] | components["schemas"]["IdealSizeInvocation"] | components["schemas"]["ImageCollectionInvocation"] | components["schemas"]["ScaleLatentsInvocation"] | components["schemas"]["IntegerMathInvocation"] | components["schemas"]["MaskEdgeInvocation"] | components["schemas"]["BlendLatentsInvocation"] | components["schemas"]["CalculateImageTilesEvenSplitInvocation"] | components["schemas"]["HandDepthMeshGraphormerProcessor"] | components["schemas"]["FloatInvocation"] | components["schemas"]["DepthAnythingImageProcessorInvocation"] | components["schemas"]["PairTileImageInvocation"] | components["schemas"]["MlsdImageProcessorInvocation"] | components["schemas"]["NoiseInvocation"] | components["schemas"]["CoreMetadataInvocation"] | components["schemas"]["UnsharpMaskInvocation"] | components["schemas"]["DivideInvocation"] | components["schemas"]["SDXLModelLoaderInvocation"] | components["schemas"]["ControlNetInvocation"] | components["schemas"]["ZoeDepthImageProcessorInvocation"] | components["schemas"]["CV2InfillInvocation"] | components["schemas"]["CalculateImageTilesMinimumOverlapInvocation"] | components["schemas"]["IterateInvocation"] | components["schemas"]["SeamlessModeInvocation"] | components["schemas"]["CreateGradientMaskInvocation"] | components["schemas"]["CollectInvocation"] | components["schemas"]["MediapipeFaceProcessorInvocation"] | components["schemas"]["FreeUInvocation"] | components["schemas"]["InfillTileInvocation"] | components["schemas"]["CanvasPasteBackInvocation"] | components["schemas"]["StepParamEasingInvocation"] | components["schemas"]["StringReplaceInvocation"] | components["schemas"]["SDXLRefinerCompelPromptInvocation"] | components["schemas"]["VAELoaderInvocation"] | components["schemas"]["FaceIdentifierInvocation"] | components["schemas"]["ImageChannelOffsetInvocation"] | components["schemas"]["StringJoinInvocation"] | components["schemas"]["InfillPatchMatchInvocation"] | components["schemas"]["CompelInvocation"] | components["schemas"]["ImageToLatentsInvocation"] | components["schemas"]["ColorCorrectInvocation"] | components["schemas"]["CannyImageProcessorInvocation"] | components["schemas"]["LeresImageProcessorInvocation"] | components["schemas"]["StringSplitInvocation"] | components["schemas"]["BlankImageInvocation"] | components["schemas"]["CalculateImageTilesInvocation"] | components["schemas"]["MergeMetadataInvocation"] | components["schemas"]["ImageBlurInvocation"] | components["schemas"]["ImageChannelMultiplyInvocation"] | components["schemas"]["T2IAdapterInvocation"] | components["schemas"]["ConditioningInvocation"] | components["schemas"]["ImageWatermarkInvocation"] | components["schemas"]["BooleanCollectionInvocation"] | components["schemas"]["PidiImageProcessorInvocation"] | components["schemas"]["FloatCollectionInvocation"] | components["schemas"]["MaskFromIDInvocation"] | components["schemas"]["ImageConvertInvocation"] | components["schemas"]["SegmentAnythingProcessorInvocation"] | components["schemas"]["LaMaInfillInvocation"] | components["schemas"]["RangeOfSizeInvocation"] | components["schemas"]["ImageInverseLerpInvocation"] | components["schemas"]["MultiplyInvocation"] | components["schemas"]["ImageHueAdjustmentInvocation"] | components["schemas"]["RoundInvocation"] | components["schemas"]["ImageMultiplyInvocation"] | components["schemas"]["MaskCombineInvocation"] | components["schemas"]["MetadataItemInvocation"] | components["schemas"]["NormalbaeImageProcessorInvocation"] | components["schemas"]["CenterPadCropInvocation"] | components["schemas"]["TileToPropertiesInvocation"] | components["schemas"]["PromptsFromFileInvocation"] | components["schemas"]["ContentShuffleImageProcessorInvocation"] | components["schemas"]["SchedulerInvocation"] | components["schemas"]["MainModelLoaderInvocation"] | components["schemas"]["SDXLCompelPromptInvocation"] | components["schemas"]["ImageLerpInvocation"] | components["schemas"]["FloatToIntegerInvocation"] | components["schemas"]["StringJoinThreeInvocation"] | components["schemas"]["MidasDepthImageProcessorInvocation"] | components["schemas"]["FaceOffInvocation"] | components["schemas"]["ShowImageInvocation"] | components["schemas"]["ConditioningCollectionInvocation"] | components["schemas"]["RandomRangeInvocation"] | components["schemas"]["ImageResizeInvocation"] | components["schemas"]["CLIPSkipInvocation"] | components["schemas"]["CreateDenoiseMaskInvocation"] | components["schemas"]["IntegerInvocation"] | components["schemas"]["TileResamplerProcessorInvocation"] | components["schemas"]["ESRGANInvocation"] | components["schemas"]["MetadataInvocation"] | components["schemas"]["ImageScaleInvocation"] | components["schemas"]["RandomFloatInvocation"] | components["schemas"]["ColorInvocation"] | components["schemas"]["CropLatentsCoreInvocation"] | components["schemas"]["StringInvocation"] | components["schemas"]["SDXLRefinerModelLoaderInvocation"] | components["schemas"]["HedImageProcessorInvocation"] | components["schemas"]["ColorMapImageProcessorInvocation"] | components["schemas"]["SDXLLoRALoaderInvocation"] | components["schemas"]["MergeTilesToImageInvocation"] | components["schemas"]["LatentsInvocation"] | components["schemas"]["ImagePasteInvocation"] | components["schemas"]["LineartAnimeImageProcessorInvocation"] | components["schemas"]["StringSplitNegInvocation"] | components["schemas"]["SubtractInvocation"] | components["schemas"]["DynamicPromptInvocation"] | components["schemas"]["CvInpaintInvocation"] | components["schemas"]["FaceMaskInvocation"] | components["schemas"]["ResizeLatentsInvocation"] | components["schemas"]["ImageInvocation"] | components["schemas"]["InfillColorInvocation"] | components["schemas"]["RandomIntInvocation"] | components["schemas"]["RangeInvocation"] | components["schemas"]["MaskFromAlphaInvocation"] | components["schemas"]["LatentsToImageInvocation"] | components["schemas"]["LineartImageProcessorInvocation"] | components["schemas"]["DWOpenposeImageProcessorInvocation"] | components["schemas"]["DenoiseLatentsInvocation"] | components["schemas"]["FloatMathInvocation"] | components["schemas"]["LoRALoaderInvocation"] | components["schemas"]["IntegerCollectionInvocation"]; + [key: string]: components["schemas"]["CalculateImageTilesInvocation"] | components["schemas"]["ImageScaleInvocation"] | components["schemas"]["LaMaInfillInvocation"] | components["schemas"]["FaceOffInvocation"] | components["schemas"]["ImageMultiplyInvocation"] | components["schemas"]["SaveImageInvocation"] | components["schemas"]["FloatMathInvocation"] | components["schemas"]["LoRALoaderInvocation"] | components["schemas"]["ShowImageInvocation"] | components["schemas"]["StringSplitNegInvocation"] | components["schemas"]["CV2InfillInvocation"] | components["schemas"]["ConditioningCollectionInvocation"] | components["schemas"]["StringSplitInvocation"] | components["schemas"]["CropLatentsCoreInvocation"] | components["schemas"]["AddInvocation"] | components["schemas"]["CalculateImageTilesMinimumOverlapInvocation"] | components["schemas"]["ControlNetInvocation"] | components["schemas"]["LatentsCollectionInvocation"] | components["schemas"]["SDXLCompelPromptInvocation"] | components["schemas"]["PidiImageProcessorInvocation"] | components["schemas"]["StringJoinThreeInvocation"] | components["schemas"]["SDXLRefinerCompelPromptInvocation"] | components["schemas"]["ImageWatermarkInvocation"] | components["schemas"]["RandomFloatInvocation"] | components["schemas"]["DynamicPromptInvocation"] | components["schemas"]["RandomRangeInvocation"] | components["schemas"]["BriaRemoveBackgroundInvocation"] | components["schemas"]["InfillPatchMatchInvocation"] | components["schemas"]["MainModelLoaderInvocation"] | components["schemas"]["InfillColorInvocation"] | components["schemas"]["MediapipeFaceProcessorInvocation"] | components["schemas"]["MultiplyInvocation"] | components["schemas"]["ScaleLatentsInvocation"] | components["schemas"]["HedImageProcessorInvocation"] | components["schemas"]["LeresImageProcessorInvocation"] | components["schemas"]["ImageCollectionInvocation"] | components["schemas"]["NoiseInvocation"] | components["schemas"]["BlankImageInvocation"] | components["schemas"]["StringInvocation"] | components["schemas"]["DivideInvocation"] | components["schemas"]["CenterPadCropInvocation"] | components["schemas"]["CompelInvocation"] | components["schemas"]["ImageInverseLerpInvocation"] | components["schemas"]["CannyImageProcessorInvocation"] | components["schemas"]["BlendLatentsInvocation"] | components["schemas"]["MaskFromIDInvocation"] | components["schemas"]["BooleanInvocation"] | components["schemas"]["ConditioningInvocation"] | components["schemas"]["SDXLRefinerModelLoaderInvocation"] | components["schemas"]["ImageNSFWBlurInvocation"] | components["schemas"]["TileResamplerProcessorInvocation"] | components["schemas"]["T2IAdapterInvocation"] | components["schemas"]["CanvasPasteBackInvocation"] | components["schemas"]["SubtractInvocation"] | components["schemas"]["DepthAnythingImageProcessorInvocation"] | components["schemas"]["ImageCropInvocation"] | components["schemas"]["MlsdImageProcessorInvocation"] | components["schemas"]["FaceMaskInvocation"] | components["schemas"]["ImagePasteInvocation"] | components["schemas"]["SegmentAnythingProcessorInvocation"] | components["schemas"]["RangeInvocation"] | components["schemas"]["MaskEdgeInvocation"] | components["schemas"]["HandDepthMeshGraphormerProcessor"] | components["schemas"]["SDXLModelLoaderInvocation"] | components["schemas"]["MetadataInvocation"] | components["schemas"]["IntegerInvocation"] | components["schemas"]["IterateInvocation"] | components["schemas"]["ColorInvocation"] | components["schemas"]["PromptsFromFileInvocation"] | components["schemas"]["LatentsToImageInvocation"] | components["schemas"]["CLIPSkipInvocation"] | components["schemas"]["ImageLerpInvocation"] | components["schemas"]["ImageInvocation"] | components["schemas"]["SchedulerInvocation"] | components["schemas"]["ImageConvertInvocation"] | components["schemas"]["CalculateImageTilesEvenSplitInvocation"] | components["schemas"]["NormalbaeImageProcessorInvocation"] | components["schemas"]["StringCollectionInvocation"] | components["schemas"]["MaskCombineInvocation"] | components["schemas"]["ImageToLatentsInvocation"] | components["schemas"]["LineartAnimeImageProcessorInvocation"] | components["schemas"]["FloatToIntegerInvocation"] | components["schemas"]["StepParamEasingInvocation"] | components["schemas"]["PairTileImageInvocation"] | components["schemas"]["VAELoaderInvocation"] | components["schemas"]["ImageHueAdjustmentInvocation"] | components["schemas"]["ImageBlurInvocation"] | components["schemas"]["RandomIntInvocation"] | components["schemas"]["CollectInvocation"] | components["schemas"]["IntegerMathInvocation"] | components["schemas"]["ColorCorrectInvocation"] | components["schemas"]["IPAdapterInvocation"] | components["schemas"]["FloatLinearRangeInvocation"] | components["schemas"]["ImageChannelInvocation"] | components["schemas"]["StringJoinInvocation"] | components["schemas"]["MidasDepthImageProcessorInvocation"] | components["schemas"]["MergeMetadataInvocation"] | components["schemas"]["IntegerCollectionInvocation"] | components["schemas"]["DenoiseLatentsInvocation"] | components["schemas"]["LatentsInvocation"] | components["schemas"]["FaceIdentifierInvocation"] | components["schemas"]["MaskFromAlphaInvocation"] | components["schemas"]["UnsharpMaskInvocation"] | components["schemas"]["SeamlessModeInvocation"] | components["schemas"]["MetadataItemInvocation"] | components["schemas"]["LineartImageProcessorInvocation"] | components["schemas"]["ImageChannelMultiplyInvocation"] | components["schemas"]["FloatCollectionInvocation"] | components["schemas"]["CreateGradientMaskInvocation"] | components["schemas"]["ColorMapImageProcessorInvocation"] | components["schemas"]["StringReplaceInvocation"] | components["schemas"]["ImageChannelOffsetInvocation"] | components["schemas"]["SDXLLoRALoaderInvocation"] | components["schemas"]["CreateDenoiseMaskInvocation"] | components["schemas"]["IdealSizeInvocation"] | components["schemas"]["CoreMetadataInvocation"] | components["schemas"]["InfillTileInvocation"] | components["schemas"]["BooleanCollectionInvocation"] | components["schemas"]["ContentShuffleImageProcessorInvocation"] | components["schemas"]["TileToPropertiesInvocation"] | components["schemas"]["FreeUInvocation"] | components["schemas"]["ResizeLatentsInvocation"] | components["schemas"]["MergeTilesToImageInvocation"] | components["schemas"]["ZoeDepthImageProcessorInvocation"] | components["schemas"]["CvInpaintInvocation"] | components["schemas"]["DWOpenposeImageProcessorInvocation"] | components["schemas"]["ImageResizeInvocation"] | components["schemas"]["FloatInvocation"] | components["schemas"]["RoundInvocation"] | components["schemas"]["RangeOfSizeInvocation"] | components["schemas"]["ESRGANInvocation"]; }; /** * Edges @@ -4206,7 +4192,7 @@ export type components = { * @description The results of node executions */ results: { - [key: string]: components["schemas"]["TileToPropertiesOutput"] | components["schemas"]["ImageCollectionOutput"] | components["schemas"]["ColorOutput"] | components["schemas"]["VAEOutput"] | components["schemas"]["String2Output"] | components["schemas"]["UNetOutput"] | components["schemas"]["SDXLModelLoaderOutput"] | components["schemas"]["ConditioningCollectionOutput"] | components["schemas"]["SchedulerOutput"] | components["schemas"]["IterateInvocationOutput"] | components["schemas"]["CLIPSkipInvocationOutput"] | components["schemas"]["PairTileImageOutput"] | components["schemas"]["StringCollectionOutput"] | components["schemas"]["LatentsCollectionOutput"] | components["schemas"]["T2IAdapterOutput"] | components["schemas"]["GradientMaskOutput"] | components["schemas"]["StringOutput"] | components["schemas"]["BooleanCollectionOutput"] | components["schemas"]["LoRALoaderOutput"] | components["schemas"]["ColorCollectionOutput"] | components["schemas"]["StringPosNegOutput"] | components["schemas"]["ConditioningOutput"] | components["schemas"]["IPAdapterOutput"] | components["schemas"]["IntegerCollectionOutput"] | components["schemas"]["ImageOutput"] | components["schemas"]["FaceOffOutput"] | components["schemas"]["CLIPOutput"] | components["schemas"]["CollectInvocationOutput"] | components["schemas"]["CalculateImageTilesOutput"] | components["schemas"]["SDXLLoRALoaderOutput"] | components["schemas"]["LatentsOutput"] | components["schemas"]["NoiseOutput"] | components["schemas"]["ModelLoaderOutput"] | components["schemas"]["MetadataItemOutput"] | components["schemas"]["BooleanOutput"] | components["schemas"]["IdealSizeOutput"] | components["schemas"]["SeamlessModeOutput"] | components["schemas"]["FloatCollectionOutput"] | components["schemas"]["DenoiseMaskOutput"] | components["schemas"]["ControlOutput"] | components["schemas"]["HandDepthOutput"] | components["schemas"]["MetadataOutput"] | components["schemas"]["FloatOutput"] | components["schemas"]["FaceMaskOutput"] | components["schemas"]["SDXLRefinerModelLoaderOutput"] | components["schemas"]["IntegerOutput"]; + [key: string]: components["schemas"]["BooleanOutput"] | components["schemas"]["IntegerOutput"] | components["schemas"]["ModelLoaderOutput"] | components["schemas"]["SDXLModelLoaderOutput"] | components["schemas"]["BooleanCollectionOutput"] | components["schemas"]["TileToPropertiesOutput"] | components["schemas"]["LatentsCollectionOutput"] | components["schemas"]["MetadataOutput"] | components["schemas"]["LoRALoaderOutput"] | components["schemas"]["PairTileImageOutput"] | components["schemas"]["VAEOutput"] | components["schemas"]["FaceOffOutput"] | components["schemas"]["SDXLRefinerModelLoaderOutput"] | components["schemas"]["FloatCollectionOutput"] | components["schemas"]["ConditioningCollectionOutput"] | components["schemas"]["FloatOutput"] | components["schemas"]["CollectInvocationOutput"] | components["schemas"]["IterateInvocationOutput"] | components["schemas"]["ImageOutput"] | components["schemas"]["IPAdapterOutput"] | components["schemas"]["CLIPOutput"] | components["schemas"]["SchedulerOutput"] | components["schemas"]["StringPosNegOutput"] | components["schemas"]["String2Output"] | components["schemas"]["IntegerCollectionOutput"] | components["schemas"]["StringOutput"] | components["schemas"]["FaceMaskOutput"] | components["schemas"]["CalculateImageTilesOutput"] | components["schemas"]["SDXLLoRALoaderOutput"] | components["schemas"]["StringCollectionOutput"] | components["schemas"]["DenoiseMaskOutput"] | components["schemas"]["CLIPSkipInvocationOutput"] | components["schemas"]["HandDepthOutput"] | components["schemas"]["ImageCollectionOutput"] | components["schemas"]["GradientMaskOutput"] | components["schemas"]["LatentsOutput"] | components["schemas"]["ColorCollectionOutput"] | components["schemas"]["SeamlessModeOutput"] | components["schemas"]["IdealSizeOutput"] | components["schemas"]["MetadataItemOutput"] | components["schemas"]["ConditioningOutput"] | components["schemas"]["ControlOutput"] | components["schemas"]["UNetOutput"] | components["schemas"]["NoiseOutput"] | components["schemas"]["T2IAdapterOutput"] | components["schemas"]["ColorOutput"]; }; /** * Errors @@ -4252,11 +4238,6 @@ export type components = { */ type?: "hf"; }; - /** - * HFTokenStatus - * @enum {string} - */ - HFTokenStatus: "valid" | "invalid" | "unknown"; /** HTTPValidationError */ HTTPValidationError: { /** Detail */ @@ -11909,39 +11890,6 @@ export type operations = { }; }; }; - /** Get Hf Login Status */ - get_hf_login_status: { - responses: { - /** @description Successful Response */ - 200: { - content: { - "application/json": components["schemas"]["HFTokenStatus"]; - }; - }; - }; - }; - /** Do Hf Login */ - do_hf_login: { - requestBody: { - content: { - "application/json": components["schemas"]["Body_do_hf_login"]; - }; - }; - responses: { - /** @description Successful Response */ - 200: { - content: { - "application/json": components["schemas"]["HFTokenStatus"]; - }; - }; - /** @description Validation Error */ - 422: { - content: { - "application/json": components["schemas"]["HTTPValidationError"]; - }; - }; - }; - }; /** * List Downloads * @description Get a list of active and inactive jobs. From 0d8b5351312d87087fd052a08229ee0892c46260 Mon Sep 17 00:00:00 2001 From: blessedcoolant <54517381+blessedcoolant@users.noreply.github.com> Date: Fri, 29 Mar 2024 11:50:18 +0530 Subject: [PATCH 09/89] chore: rename IPAdapterDiffusersConfig to IPAdapterInvokeAIConfig --- invokeai/app/invocations/ip_adapter.py | 6 +- invokeai/backend/model_manager/config.py | 4 +- .../frontend/web/src/services/api/schema.ts | 252 +++++------------- .../frontend/web/src/services/api/types.ts | 2 +- 4 files changed, 77 insertions(+), 187 deletions(-) diff --git a/invokeai/app/invocations/ip_adapter.py b/invokeai/app/invocations/ip_adapter.py index 603a85148d..5e24721b2f 100644 --- a/invokeai/app/invocations/ip_adapter.py +++ b/invokeai/app/invocations/ip_adapter.py @@ -14,7 +14,7 @@ from invokeai.backend.model_manager.config import ( AnyModelConfig, BaseModelType, IPAdapterCheckpointConfig, - IPAdapterDiffusersConfig, + IPAdapterInvokeAIConfig, ModelType, ) @@ -92,9 +92,9 @@ class IPAdapterInvocation(BaseInvocation): def invoke(self, context: InvocationContext) -> IPAdapterOutput: # Lookup the CLIP Vision encoder that is intended to be used with the IP-Adapter model. ip_adapter_info = context.models.get_config(self.ip_adapter_model.key) - assert isinstance(ip_adapter_info, (IPAdapterDiffusersConfig, IPAdapterCheckpointConfig)) + assert isinstance(ip_adapter_info, (IPAdapterInvokeAIConfig, IPAdapterCheckpointConfig)) - if isinstance(ip_adapter_info, IPAdapterDiffusersConfig): + if isinstance(ip_adapter_info, IPAdapterInvokeAIConfig): image_encoder_model_id = ip_adapter_info.image_encoder_model_id image_encoder_model_name = image_encoder_model_id.split("/")[-1].strip() else: diff --git a/invokeai/backend/model_manager/config.py b/invokeai/backend/model_manager/config.py index 172045d3fc..82f88c0e81 100644 --- a/invokeai/backend/model_manager/config.py +++ b/invokeai/backend/model_manager/config.py @@ -327,7 +327,7 @@ class IPAdapterBaseConfig(ModelConfigBase): type: Literal[ModelType.IPAdapter] = ModelType.IPAdapter -class IPAdapterDiffusersConfig(IPAdapterBaseConfig): +class IPAdapterInvokeAIConfig(IPAdapterBaseConfig): """Model config for IP Adapter diffusers format models.""" image_encoder_model_id: str @@ -403,7 +403,7 @@ AnyModelConfig = Annotated[ Annotated[LoRADiffusersConfig, LoRADiffusersConfig.get_tag()], Annotated[TextualInversionFileConfig, TextualInversionFileConfig.get_tag()], Annotated[TextualInversionFolderConfig, TextualInversionFolderConfig.get_tag()], - Annotated[IPAdapterDiffusersConfig, IPAdapterDiffusersConfig.get_tag()], + Annotated[IPAdapterInvokeAIConfig, IPAdapterInvokeAIConfig.get_tag()], Annotated[IPAdapterCheckpointConfig, IPAdapterCheckpointConfig.get_tag()], Annotated[T2IAdapterConfig, T2IAdapterConfig.get_tag()], Annotated[CLIPVisionDiffusersConfig, CLIPVisionDiffusersConfig.get_tag()], diff --git a/invokeai/frontend/web/src/services/api/schema.ts b/invokeai/frontend/web/src/services/api/schema.ts index 9b78deccac..6557da78ff 100644 --- a/invokeai/frontend/web/src/services/api/schema.ts +++ b/invokeai/frontend/web/src/services/api/schema.ts @@ -1248,39 +1248,6 @@ export type components = { */ type: "boolean_output"; }; - /** - * BRIA AI Background Removal - * @description Uses the new Bria 1.4 model to remove backgrounds from images. - */ - BriaRemoveBackgroundInvocation: { - /** @description Optional metadata to be saved with the image */ - metadata?: components["schemas"]["MetadataField"] | null; - /** - * Id - * @description The id of this instance of an invocation. Must be unique among all instances of invocations. - */ - id: string; - /** - * Is Intermediate - * @description Whether or not this is an intermediate invocation. - * @default false - */ - is_intermediate?: boolean; - /** - * Use Cache - * @description Whether or not to use the cache - * @default true - */ - use_cache?: boolean; - /** @description The image to crop */ - image?: components["schemas"]["ImageField"]; - /** - * type - * @default bria_bg_remove - * @constant - */ - type: "bria_bg_remove"; - }; /** CLIPField */ CLIPField: { /** @description Info to load tokenizer submodel */ @@ -4155,7 +4122,7 @@ export type components = { * @description The nodes in this graph */ nodes: { - [key: string]: components["schemas"]["CalculateImageTilesInvocation"] | components["schemas"]["ImageScaleInvocation"] | components["schemas"]["LaMaInfillInvocation"] | components["schemas"]["FaceOffInvocation"] | components["schemas"]["ImageMultiplyInvocation"] | components["schemas"]["SaveImageInvocation"] | components["schemas"]["FloatMathInvocation"] | components["schemas"]["LoRALoaderInvocation"] | components["schemas"]["ShowImageInvocation"] | components["schemas"]["StringSplitNegInvocation"] | components["schemas"]["CV2InfillInvocation"] | components["schemas"]["ConditioningCollectionInvocation"] | components["schemas"]["StringSplitInvocation"] | components["schemas"]["CropLatentsCoreInvocation"] | components["schemas"]["AddInvocation"] | components["schemas"]["CalculateImageTilesMinimumOverlapInvocation"] | components["schemas"]["ControlNetInvocation"] | components["schemas"]["LatentsCollectionInvocation"] | components["schemas"]["SDXLCompelPromptInvocation"] | components["schemas"]["PidiImageProcessorInvocation"] | components["schemas"]["StringJoinThreeInvocation"] | components["schemas"]["SDXLRefinerCompelPromptInvocation"] | components["schemas"]["ImageWatermarkInvocation"] | components["schemas"]["RandomFloatInvocation"] | components["schemas"]["DynamicPromptInvocation"] | components["schemas"]["RandomRangeInvocation"] | components["schemas"]["BriaRemoveBackgroundInvocation"] | components["schemas"]["InfillPatchMatchInvocation"] | components["schemas"]["MainModelLoaderInvocation"] | components["schemas"]["InfillColorInvocation"] | components["schemas"]["MediapipeFaceProcessorInvocation"] | components["schemas"]["MultiplyInvocation"] | components["schemas"]["ScaleLatentsInvocation"] | components["schemas"]["HedImageProcessorInvocation"] | components["schemas"]["LeresImageProcessorInvocation"] | components["schemas"]["ImageCollectionInvocation"] | components["schemas"]["NoiseInvocation"] | components["schemas"]["BlankImageInvocation"] | components["schemas"]["StringInvocation"] | components["schemas"]["DivideInvocation"] | components["schemas"]["CenterPadCropInvocation"] | components["schemas"]["CompelInvocation"] | components["schemas"]["ImageInverseLerpInvocation"] | components["schemas"]["CannyImageProcessorInvocation"] | components["schemas"]["BlendLatentsInvocation"] | components["schemas"]["MaskFromIDInvocation"] | components["schemas"]["BooleanInvocation"] | components["schemas"]["ConditioningInvocation"] | components["schemas"]["SDXLRefinerModelLoaderInvocation"] | components["schemas"]["ImageNSFWBlurInvocation"] | components["schemas"]["TileResamplerProcessorInvocation"] | components["schemas"]["T2IAdapterInvocation"] | components["schemas"]["CanvasPasteBackInvocation"] | components["schemas"]["SubtractInvocation"] | components["schemas"]["DepthAnythingImageProcessorInvocation"] | components["schemas"]["ImageCropInvocation"] | components["schemas"]["MlsdImageProcessorInvocation"] | components["schemas"]["FaceMaskInvocation"] | components["schemas"]["ImagePasteInvocation"] | components["schemas"]["SegmentAnythingProcessorInvocation"] | components["schemas"]["RangeInvocation"] | components["schemas"]["MaskEdgeInvocation"] | components["schemas"]["HandDepthMeshGraphormerProcessor"] | components["schemas"]["SDXLModelLoaderInvocation"] | components["schemas"]["MetadataInvocation"] | components["schemas"]["IntegerInvocation"] | components["schemas"]["IterateInvocation"] | components["schemas"]["ColorInvocation"] | components["schemas"]["PromptsFromFileInvocation"] | components["schemas"]["LatentsToImageInvocation"] | components["schemas"]["CLIPSkipInvocation"] | components["schemas"]["ImageLerpInvocation"] | components["schemas"]["ImageInvocation"] | components["schemas"]["SchedulerInvocation"] | components["schemas"]["ImageConvertInvocation"] | components["schemas"]["CalculateImageTilesEvenSplitInvocation"] | components["schemas"]["NormalbaeImageProcessorInvocation"] | components["schemas"]["StringCollectionInvocation"] | components["schemas"]["MaskCombineInvocation"] | components["schemas"]["ImageToLatentsInvocation"] | components["schemas"]["LineartAnimeImageProcessorInvocation"] | components["schemas"]["FloatToIntegerInvocation"] | components["schemas"]["StepParamEasingInvocation"] | components["schemas"]["PairTileImageInvocation"] | components["schemas"]["VAELoaderInvocation"] | components["schemas"]["ImageHueAdjustmentInvocation"] | components["schemas"]["ImageBlurInvocation"] | components["schemas"]["RandomIntInvocation"] | components["schemas"]["CollectInvocation"] | components["schemas"]["IntegerMathInvocation"] | components["schemas"]["ColorCorrectInvocation"] | components["schemas"]["IPAdapterInvocation"] | components["schemas"]["FloatLinearRangeInvocation"] | components["schemas"]["ImageChannelInvocation"] | components["schemas"]["StringJoinInvocation"] | components["schemas"]["MidasDepthImageProcessorInvocation"] | components["schemas"]["MergeMetadataInvocation"] | components["schemas"]["IntegerCollectionInvocation"] | components["schemas"]["DenoiseLatentsInvocation"] | components["schemas"]["LatentsInvocation"] | components["schemas"]["FaceIdentifierInvocation"] | components["schemas"]["MaskFromAlphaInvocation"] | components["schemas"]["UnsharpMaskInvocation"] | components["schemas"]["SeamlessModeInvocation"] | components["schemas"]["MetadataItemInvocation"] | components["schemas"]["LineartImageProcessorInvocation"] | components["schemas"]["ImageChannelMultiplyInvocation"] | components["schemas"]["FloatCollectionInvocation"] | components["schemas"]["CreateGradientMaskInvocation"] | components["schemas"]["ColorMapImageProcessorInvocation"] | components["schemas"]["StringReplaceInvocation"] | components["schemas"]["ImageChannelOffsetInvocation"] | components["schemas"]["SDXLLoRALoaderInvocation"] | components["schemas"]["CreateDenoiseMaskInvocation"] | components["schemas"]["IdealSizeInvocation"] | components["schemas"]["CoreMetadataInvocation"] | components["schemas"]["InfillTileInvocation"] | components["schemas"]["BooleanCollectionInvocation"] | components["schemas"]["ContentShuffleImageProcessorInvocation"] | components["schemas"]["TileToPropertiesInvocation"] | components["schemas"]["FreeUInvocation"] | components["schemas"]["ResizeLatentsInvocation"] | components["schemas"]["MergeTilesToImageInvocation"] | components["schemas"]["ZoeDepthImageProcessorInvocation"] | components["schemas"]["CvInpaintInvocation"] | components["schemas"]["DWOpenposeImageProcessorInvocation"] | components["schemas"]["ImageResizeInvocation"] | components["schemas"]["FloatInvocation"] | components["schemas"]["RoundInvocation"] | components["schemas"]["RangeOfSizeInvocation"] | components["schemas"]["ESRGANInvocation"]; + [key: string]: components["schemas"]["SeamlessModeInvocation"] | components["schemas"]["FreeUInvocation"] | components["schemas"]["ImageResizeInvocation"] | components["schemas"]["ZoeDepthImageProcessorInvocation"] | components["schemas"]["FloatMathInvocation"] | components["schemas"]["ColorCorrectInvocation"] | components["schemas"]["SubtractInvocation"] | components["schemas"]["FloatCollectionInvocation"] | components["schemas"]["ImageCollectionInvocation"] | components["schemas"]["SDXLRefinerCompelPromptInvocation"] | components["schemas"]["ImageHueAdjustmentInvocation"] | components["schemas"]["MultiplyInvocation"] | components["schemas"]["StringInvocation"] | components["schemas"]["StringSplitNegInvocation"] | components["schemas"]["StringJoinThreeInvocation"] | components["schemas"]["CvInpaintInvocation"] | components["schemas"]["RoundInvocation"] | components["schemas"]["NoiseInvocation"] | components["schemas"]["LaMaInfillInvocation"] | components["schemas"]["TileResamplerProcessorInvocation"] | components["schemas"]["CreateGradientMaskInvocation"] | components["schemas"]["PidiImageProcessorInvocation"] | components["schemas"]["DynamicPromptInvocation"] | components["schemas"]["ColorMapImageProcessorInvocation"] | components["schemas"]["FaceMaskInvocation"] | components["schemas"]["MaskEdgeInvocation"] | components["schemas"]["StringJoinInvocation"] | components["schemas"]["MediapipeFaceProcessorInvocation"] | components["schemas"]["MergeMetadataInvocation"] | components["schemas"]["ImageScaleInvocation"] | components["schemas"]["ControlNetInvocation"] | components["schemas"]["FaceOffInvocation"] | components["schemas"]["MidasDepthImageProcessorInvocation"] | components["schemas"]["CV2InfillInvocation"] | components["schemas"]["MergeTilesToImageInvocation"] | components["schemas"]["ScaleLatentsInvocation"] | components["schemas"]["IntegerInvocation"] | components["schemas"]["ConditioningCollectionInvocation"] | components["schemas"]["ImageCropInvocation"] | components["schemas"]["ImageLerpInvocation"] | components["schemas"]["SaveImageInvocation"] | components["schemas"]["IterateInvocation"] | components["schemas"]["BooleanInvocation"] | components["schemas"]["RangeInvocation"] | components["schemas"]["AddInvocation"] | components["schemas"]["ImagePasteInvocation"] | components["schemas"]["FloatInvocation"] | components["schemas"]["ImageConvertInvocation"] | components["schemas"]["LineartAnimeImageProcessorInvocation"] | components["schemas"]["RandomIntInvocation"] | components["schemas"]["MetadataItemInvocation"] | components["schemas"]["UnsharpMaskInvocation"] | components["schemas"]["SDXLCompelPromptInvocation"] | components["schemas"]["CanvasPasteBackInvocation"] | components["schemas"]["IPAdapterInvocation"] | components["schemas"]["DenoiseLatentsInvocation"] | components["schemas"]["ImageInverseLerpInvocation"] | components["schemas"]["PromptsFromFileInvocation"] | components["schemas"]["ESRGANInvocation"] | components["schemas"]["MetadataInvocation"] | components["schemas"]["ConditioningInvocation"] | components["schemas"]["ImageChannelOffsetInvocation"] | components["schemas"]["InfillColorInvocation"] | components["schemas"]["T2IAdapterInvocation"] | components["schemas"]["BlankImageInvocation"] | components["schemas"]["TileToPropertiesInvocation"] | components["schemas"]["IdealSizeInvocation"] | components["schemas"]["MaskFromAlphaInvocation"] | components["schemas"]["CompelInvocation"] | components["schemas"]["CalculateImageTilesEvenSplitInvocation"] | components["schemas"]["BlendLatentsInvocation"] | components["schemas"]["SDXLLoRALoaderInvocation"] | components["schemas"]["ImageInvocation"] | components["schemas"]["ImageBlurInvocation"] | components["schemas"]["CLIPSkipInvocation"] | components["schemas"]["DWOpenposeImageProcessorInvocation"] | components["schemas"]["IntegerMathInvocation"] | components["schemas"]["ColorInvocation"] | components["schemas"]["MaskFromIDInvocation"] | components["schemas"]["HedImageProcessorInvocation"] | components["schemas"]["DepthAnythingImageProcessorInvocation"] | components["schemas"]["RandomFloatInvocation"] | components["schemas"]["RangeOfSizeInvocation"] | components["schemas"]["FaceIdentifierInvocation"] | components["schemas"]["ImageWatermarkInvocation"] | components["schemas"]["CollectInvocation"] | components["schemas"]["FloatToIntegerInvocation"] | components["schemas"]["MaskCombineInvocation"] | components["schemas"]["NormalbaeImageProcessorInvocation"] | components["schemas"]["CropLatentsCoreInvocation"] | components["schemas"]["ImageMultiplyInvocation"] | components["schemas"]["LatentsCollectionInvocation"] | components["schemas"]["InfillPatchMatchInvocation"] | components["schemas"]["VAELoaderInvocation"] | components["schemas"]["ShowImageInvocation"] | components["schemas"]["ImageNSFWBlurInvocation"] | components["schemas"]["CannyImageProcessorInvocation"] | components["schemas"]["ResizeLatentsInvocation"] | components["schemas"]["IntegerCollectionInvocation"] | components["schemas"]["CalculateImageTilesMinimumOverlapInvocation"] | components["schemas"]["SDXLModelLoaderInvocation"] | components["schemas"]["PairTileImageInvocation"] | components["schemas"]["ImageToLatentsInvocation"] | components["schemas"]["BooleanCollectionInvocation"] | components["schemas"]["CenterPadCropInvocation"] | components["schemas"]["ImageChannelMultiplyInvocation"] | components["schemas"]["StepParamEasingInvocation"] | components["schemas"]["MlsdImageProcessorInvocation"] | components["schemas"]["SegmentAnythingProcessorInvocation"] | components["schemas"]["LatentsInvocation"] | components["schemas"]["StringSplitInvocation"] | components["schemas"]["LineartImageProcessorInvocation"] | components["schemas"]["LeresImageProcessorInvocation"] | components["schemas"]["CreateDenoiseMaskInvocation"] | components["schemas"]["RandomRangeInvocation"] | components["schemas"]["StringCollectionInvocation"] | components["schemas"]["SDXLRefinerModelLoaderInvocation"] | components["schemas"]["ContentShuffleImageProcessorInvocation"] | components["schemas"]["SchedulerInvocation"] | components["schemas"]["FloatLinearRangeInvocation"] | components["schemas"]["MainModelLoaderInvocation"] | components["schemas"]["ImageChannelInvocation"] | components["schemas"]["CalculateImageTilesInvocation"] | components["schemas"]["LoRALoaderInvocation"] | components["schemas"]["InfillTileInvocation"] | components["schemas"]["StringReplaceInvocation"] | components["schemas"]["LatentsToImageInvocation"] | components["schemas"]["CoreMetadataInvocation"] | components["schemas"]["DivideInvocation"]; }; /** * Edges @@ -4192,7 +4159,7 @@ export type components = { * @description The results of node executions */ results: { - [key: string]: components["schemas"]["BooleanOutput"] | components["schemas"]["IntegerOutput"] | components["schemas"]["ModelLoaderOutput"] | components["schemas"]["SDXLModelLoaderOutput"] | components["schemas"]["BooleanCollectionOutput"] | components["schemas"]["TileToPropertiesOutput"] | components["schemas"]["LatentsCollectionOutput"] | components["schemas"]["MetadataOutput"] | components["schemas"]["LoRALoaderOutput"] | components["schemas"]["PairTileImageOutput"] | components["schemas"]["VAEOutput"] | components["schemas"]["FaceOffOutput"] | components["schemas"]["SDXLRefinerModelLoaderOutput"] | components["schemas"]["FloatCollectionOutput"] | components["schemas"]["ConditioningCollectionOutput"] | components["schemas"]["FloatOutput"] | components["schemas"]["CollectInvocationOutput"] | components["schemas"]["IterateInvocationOutput"] | components["schemas"]["ImageOutput"] | components["schemas"]["IPAdapterOutput"] | components["schemas"]["CLIPOutput"] | components["schemas"]["SchedulerOutput"] | components["schemas"]["StringPosNegOutput"] | components["schemas"]["String2Output"] | components["schemas"]["IntegerCollectionOutput"] | components["schemas"]["StringOutput"] | components["schemas"]["FaceMaskOutput"] | components["schemas"]["CalculateImageTilesOutput"] | components["schemas"]["SDXLLoRALoaderOutput"] | components["schemas"]["StringCollectionOutput"] | components["schemas"]["DenoiseMaskOutput"] | components["schemas"]["CLIPSkipInvocationOutput"] | components["schemas"]["HandDepthOutput"] | components["schemas"]["ImageCollectionOutput"] | components["schemas"]["GradientMaskOutput"] | components["schemas"]["LatentsOutput"] | components["schemas"]["ColorCollectionOutput"] | components["schemas"]["SeamlessModeOutput"] | components["schemas"]["IdealSizeOutput"] | components["schemas"]["MetadataItemOutput"] | components["schemas"]["ConditioningOutput"] | components["schemas"]["ControlOutput"] | components["schemas"]["UNetOutput"] | components["schemas"]["NoiseOutput"] | components["schemas"]["T2IAdapterOutput"] | components["schemas"]["ColorOutput"]; + [key: string]: components["schemas"]["StringCollectionOutput"] | components["schemas"]["ConditioningCollectionOutput"] | components["schemas"]["IntegerCollectionOutput"] | components["schemas"]["ColorCollectionOutput"] | components["schemas"]["TileToPropertiesOutput"] | components["schemas"]["StringOutput"] | components["schemas"]["FaceMaskOutput"] | components["schemas"]["MetadataItemOutput"] | components["schemas"]["T2IAdapterOutput"] | components["schemas"]["SeamlessModeOutput"] | components["schemas"]["LatentsOutput"] | components["schemas"]["ConditioningOutput"] | components["schemas"]["FloatCollectionOutput"] | components["schemas"]["CollectInvocationOutput"] | components["schemas"]["SDXLModelLoaderOutput"] | components["schemas"]["VAEOutput"] | components["schemas"]["SDXLLoRALoaderOutput"] | components["schemas"]["CalculateImageTilesOutput"] | components["schemas"]["ColorOutput"] | components["schemas"]["IdealSizeOutput"] | components["schemas"]["DenoiseMaskOutput"] | components["schemas"]["LatentsCollectionOutput"] | components["schemas"]["FaceOffOutput"] | components["schemas"]["IntegerOutput"] | components["schemas"]["LoRALoaderOutput"] | components["schemas"]["IPAdapterOutput"] | components["schemas"]["GradientMaskOutput"] | components["schemas"]["IterateInvocationOutput"] | components["schemas"]["SchedulerOutput"] | components["schemas"]["ModelLoaderOutput"] | components["schemas"]["BooleanCollectionOutput"] | components["schemas"]["MetadataOutput"] | components["schemas"]["NoiseOutput"] | components["schemas"]["ImageCollectionOutput"] | components["schemas"]["CLIPOutput"] | components["schemas"]["ImageOutput"] | components["schemas"]["SDXLRefinerModelLoaderOutput"] | components["schemas"]["BooleanOutput"] | components["schemas"]["UNetOutput"] | components["schemas"]["FloatOutput"] | components["schemas"]["StringPosNegOutput"] | components["schemas"]["PairTileImageOutput"] | components["schemas"]["CLIPSkipInvocationOutput"] | components["schemas"]["String2Output"] | components["schemas"]["ControlOutput"]; }; /** * Errors @@ -4243,83 +4210,6 @@ export type components = { /** Detail */ detail?: components["schemas"]["ValidationError"][]; }; - /** - * Hand Depth w/ MeshGraphormer - * @description Generate hand depth maps to inpaint with using ControlNet - */ - HandDepthMeshGraphormerProcessor: { - /** @description Optional metadata to be saved with the image */ - metadata?: components["schemas"]["MetadataField"] | null; - /** - * Id - * @description The id of this instance of an invocation. Must be unique among all instances of invocations. - */ - id: string; - /** - * Is Intermediate - * @description Whether or not this is an intermediate invocation. - * @default false - */ - is_intermediate?: boolean; - /** - * Use Cache - * @description Whether or not to use the cache - * @default true - */ - use_cache?: boolean; - /** @description The image to process */ - image?: components["schemas"]["ImageField"]; - /** - * Resolution - * @description Pixel resolution for output image - * @default 512 - */ - resolution?: number; - /** - * Mask Padding - * @description Amount to pad the hand mask by - * @default 30 - */ - mask_padding?: number; - /** - * Offload - * @description Offload model after usage - * @default false - */ - offload?: boolean; - /** - * type - * @default hand_depth_mesh_graphormer_image_processor - * @constant - */ - type: "hand_depth_mesh_graphormer_image_processor"; - }; - /** - * HandDepthOutput - * @description Base class for to output Meshgraphormer results - */ - HandDepthOutput: { - /** @description Improved hands depth map */ - image: components["schemas"]["ImageField"]; - /** @description Hands area mask */ - mask: components["schemas"]["ImageField"]; - /** - * Width - * @description The width of the depth map in pixels - */ - width: number; - /** - * Height - * @description The height of the depth map in pixels - */ - height: number; - /** - * type - * @default meshgraphormer_output - * @constant - */ - type: "meshgraphormer_output"; - }; /** * HED (softedge) Processor * @description Applies HED edge detection to image @@ -4490,69 +4380,6 @@ export type components = { */ format: "checkpoint"; }; - /** - * IPAdapterDiffusersConfig - * @description Model config for IP Adapter diffusers format models. - */ - IPAdapterDiffusersConfig: { - /** - * Key - * @description A unique key for this model. - */ - key: string; - /** - * Hash - * @description The hash of the model file(s). - */ - hash: string; - /** - * Path - * @description Path to the model on the filesystem. Relative paths are relative to the Invoke root directory. - */ - path: string; - /** - * Name - * @description Name of the model. - */ - name: string; - /** @description The base model. */ - base: components["schemas"]["BaseModelType"]; - /** - * Description - * @description Model description - */ - description?: string | null; - /** - * Source - * @description The original source of the model (path, URL or repo_id). - */ - source: string; - /** @description The type of source */ - source_type: components["schemas"]["ModelSourceType"]; - /** - * Source Api Response - * @description The original API response from the source, as stringified JSON. - */ - source_api_response?: string | null; - /** - * Cover Image - * @description Url for image to preview model - */ - cover_image?: string | null; - /** - * Type - * @default ip_adapter - * @constant - */ - type: "ip_adapter"; - /** Image Encoder Model Id */ - image_encoder_model_id: string; - /** - * Format - * @constant - */ - format: "invokeai"; - }; /** IPAdapterField */ IPAdapterField: { /** @@ -4647,6 +4474,69 @@ export type components = { */ type: "ip_adapter"; }; + /** + * IPAdapterInvokeAIConfig + * @description Model config for IP Adapter diffusers format models. + */ + IPAdapterInvokeAIConfig: { + /** + * Key + * @description A unique key for this model. + */ + key: string; + /** + * Hash + * @description The hash of the model file(s). + */ + hash: string; + /** + * Path + * @description Path to the model on the filesystem. Relative paths are relative to the Invoke root directory. + */ + path: string; + /** + * Name + * @description Name of the model. + */ + name: string; + /** @description The base model. */ + base: components["schemas"]["BaseModelType"]; + /** + * Description + * @description Model description + */ + description?: string | null; + /** + * Source + * @description The original source of the model (path, URL or repo_id). + */ + source: string; + /** @description The type of source */ + source_type: components["schemas"]["ModelSourceType"]; + /** + * Source Api Response + * @description The original API response from the source, as stringified JSON. + */ + source_api_response?: string | null; + /** + * Cover Image + * @description Url for image to preview model + */ + cover_image?: string | null; + /** + * Type + * @default ip_adapter + * @constant + */ + type: "ip_adapter"; + /** Image Encoder Model Id */ + image_encoder_model_id: string; + /** + * Format + * @constant + */ + format: "invokeai"; + }; /** * IPAdapterMetadataField * @description IP Adapter Field, minus the CLIP Vision Encoder model @@ -7665,7 +7555,7 @@ export type components = { * Config Out * @description After successful installation, this will hold the configuration object. */ - config_out?: (components["schemas"]["MainDiffusersConfig"] | components["schemas"]["MainCheckpointConfig"] | components["schemas"]["VAEDiffusersConfig"] | components["schemas"]["VAECheckpointConfig"] | components["schemas"]["ControlNetDiffusersConfig"] | components["schemas"]["ControlNetCheckpointConfig"] | components["schemas"]["LoRALyCORISConfig"] | components["schemas"]["LoRADiffusersConfig"] | components["schemas"]["TextualInversionFileConfig"] | components["schemas"]["TextualInversionFolderConfig"] | components["schemas"]["IPAdapterDiffusersConfig"] | components["schemas"]["IPAdapterCheckpointConfig"] | components["schemas"]["T2IAdapterConfig"] | components["schemas"]["CLIPVisionDiffusersConfig"]) | null; + config_out?: (components["schemas"]["MainDiffusersConfig"] | components["schemas"]["MainCheckpointConfig"] | components["schemas"]["VAEDiffusersConfig"] | components["schemas"]["VAECheckpointConfig"] | components["schemas"]["ControlNetDiffusersConfig"] | components["schemas"]["ControlNetCheckpointConfig"] | components["schemas"]["LoRALyCORISConfig"] | components["schemas"]["LoRADiffusersConfig"] | components["schemas"]["TextualInversionFileConfig"] | components["schemas"]["TextualInversionFolderConfig"] | components["schemas"]["IPAdapterInvokeAIConfig"] | components["schemas"]["IPAdapterCheckpointConfig"] | components["schemas"]["T2IAdapterConfig"] | components["schemas"]["CLIPVisionDiffusersConfig"]) | null; /** * Inplace * @description Leave model in its current location; otherwise install under models directory @@ -7820,7 +7710,7 @@ export type components = { */ ModelsList: { /** Models */ - models: (components["schemas"]["MainDiffusersConfig"] | components["schemas"]["MainCheckpointConfig"] | components["schemas"]["VAEDiffusersConfig"] | components["schemas"]["VAECheckpointConfig"] | components["schemas"]["ControlNetDiffusersConfig"] | components["schemas"]["ControlNetCheckpointConfig"] | components["schemas"]["LoRALyCORISConfig"] | components["schemas"]["LoRADiffusersConfig"] | components["schemas"]["TextualInversionFileConfig"] | components["schemas"]["TextualInversionFolderConfig"] | components["schemas"]["IPAdapterDiffusersConfig"] | components["schemas"]["IPAdapterCheckpointConfig"] | components["schemas"]["T2IAdapterConfig"] | components["schemas"]["CLIPVisionDiffusersConfig"])[]; + models: (components["schemas"]["MainDiffusersConfig"] | components["schemas"]["MainCheckpointConfig"] | components["schemas"]["VAEDiffusersConfig"] | components["schemas"]["VAECheckpointConfig"] | components["schemas"]["ControlNetDiffusersConfig"] | components["schemas"]["ControlNetCheckpointConfig"] | components["schemas"]["LoRALyCORISConfig"] | components["schemas"]["LoRADiffusersConfig"] | components["schemas"]["TextualInversionFileConfig"] | components["schemas"]["TextualInversionFolderConfig"] | components["schemas"]["IPAdapterInvokeAIConfig"] | components["schemas"]["IPAdapterCheckpointConfig"] | components["schemas"]["T2IAdapterConfig"] | components["schemas"]["CLIPVisionDiffusersConfig"])[]; }; /** * Multiply Integers @@ -11349,7 +11239,7 @@ export type operations = { /** @description Successful Response */ 200: { content: { - "application/json": components["schemas"]["MainDiffusersConfig"] | components["schemas"]["MainCheckpointConfig"] | components["schemas"]["VAEDiffusersConfig"] | components["schemas"]["VAECheckpointConfig"] | components["schemas"]["ControlNetDiffusersConfig"] | components["schemas"]["ControlNetCheckpointConfig"] | components["schemas"]["LoRALyCORISConfig"] | components["schemas"]["LoRADiffusersConfig"] | components["schemas"]["TextualInversionFileConfig"] | components["schemas"]["TextualInversionFolderConfig"] | components["schemas"]["IPAdapterDiffusersConfig"] | components["schemas"]["IPAdapterCheckpointConfig"] | components["schemas"]["T2IAdapterConfig"] | components["schemas"]["CLIPVisionDiffusersConfig"]; + "application/json": components["schemas"]["MainDiffusersConfig"] | components["schemas"]["MainCheckpointConfig"] | components["schemas"]["VAEDiffusersConfig"] | components["schemas"]["VAECheckpointConfig"] | components["schemas"]["ControlNetDiffusersConfig"] | components["schemas"]["ControlNetCheckpointConfig"] | components["schemas"]["LoRALyCORISConfig"] | components["schemas"]["LoRADiffusersConfig"] | components["schemas"]["TextualInversionFileConfig"] | components["schemas"]["TextualInversionFolderConfig"] | components["schemas"]["IPAdapterInvokeAIConfig"] | components["schemas"]["IPAdapterCheckpointConfig"] | components["schemas"]["T2IAdapterConfig"] | components["schemas"]["CLIPVisionDiffusersConfig"]; }; }; /** @description Validation Error */ @@ -11375,7 +11265,7 @@ export type operations = { /** @description The model configuration was retrieved successfully */ 200: { content: { - "application/json": components["schemas"]["MainDiffusersConfig"] | components["schemas"]["MainCheckpointConfig"] | components["schemas"]["VAEDiffusersConfig"] | components["schemas"]["VAECheckpointConfig"] | components["schemas"]["ControlNetDiffusersConfig"] | components["schemas"]["ControlNetCheckpointConfig"] | components["schemas"]["LoRALyCORISConfig"] | components["schemas"]["LoRADiffusersConfig"] | components["schemas"]["TextualInversionFileConfig"] | components["schemas"]["TextualInversionFolderConfig"] | components["schemas"]["IPAdapterDiffusersConfig"] | components["schemas"]["IPAdapterCheckpointConfig"] | components["schemas"]["T2IAdapterConfig"] | components["schemas"]["CLIPVisionDiffusersConfig"]; + "application/json": components["schemas"]["MainDiffusersConfig"] | components["schemas"]["MainCheckpointConfig"] | components["schemas"]["VAEDiffusersConfig"] | components["schemas"]["VAECheckpointConfig"] | components["schemas"]["ControlNetDiffusersConfig"] | components["schemas"]["ControlNetCheckpointConfig"] | components["schemas"]["LoRALyCORISConfig"] | components["schemas"]["LoRADiffusersConfig"] | components["schemas"]["TextualInversionFileConfig"] | components["schemas"]["TextualInversionFolderConfig"] | components["schemas"]["IPAdapterInvokeAIConfig"] | components["schemas"]["IPAdapterCheckpointConfig"] | components["schemas"]["T2IAdapterConfig"] | components["schemas"]["CLIPVisionDiffusersConfig"]; }; }; /** @description Bad request */ @@ -11457,7 +11347,7 @@ export type operations = { /** @description The model was updated successfully */ 200: { content: { - "application/json": components["schemas"]["MainDiffusersConfig"] | components["schemas"]["MainCheckpointConfig"] | components["schemas"]["VAEDiffusersConfig"] | components["schemas"]["VAECheckpointConfig"] | components["schemas"]["ControlNetDiffusersConfig"] | components["schemas"]["ControlNetCheckpointConfig"] | components["schemas"]["LoRALyCORISConfig"] | components["schemas"]["LoRADiffusersConfig"] | components["schemas"]["TextualInversionFileConfig"] | components["schemas"]["TextualInversionFolderConfig"] | components["schemas"]["IPAdapterDiffusersConfig"] | components["schemas"]["IPAdapterCheckpointConfig"] | components["schemas"]["T2IAdapterConfig"] | components["schemas"]["CLIPVisionDiffusersConfig"]; + "application/json": components["schemas"]["MainDiffusersConfig"] | components["schemas"]["MainCheckpointConfig"] | components["schemas"]["VAEDiffusersConfig"] | components["schemas"]["VAECheckpointConfig"] | components["schemas"]["ControlNetDiffusersConfig"] | components["schemas"]["ControlNetCheckpointConfig"] | components["schemas"]["LoRALyCORISConfig"] | components["schemas"]["LoRADiffusersConfig"] | components["schemas"]["TextualInversionFileConfig"] | components["schemas"]["TextualInversionFolderConfig"] | components["schemas"]["IPAdapterInvokeAIConfig"] | components["schemas"]["IPAdapterCheckpointConfig"] | components["schemas"]["T2IAdapterConfig"] | components["schemas"]["CLIPVisionDiffusersConfig"]; }; }; /** @description Bad request */ @@ -11856,7 +11746,7 @@ export type operations = { /** @description Model converted successfully */ 200: { content: { - "application/json": components["schemas"]["MainDiffusersConfig"] | components["schemas"]["MainCheckpointConfig"] | components["schemas"]["VAEDiffusersConfig"] | components["schemas"]["VAECheckpointConfig"] | components["schemas"]["ControlNetDiffusersConfig"] | components["schemas"]["ControlNetCheckpointConfig"] | components["schemas"]["LoRALyCORISConfig"] | components["schemas"]["LoRADiffusersConfig"] | components["schemas"]["TextualInversionFileConfig"] | components["schemas"]["TextualInversionFolderConfig"] | components["schemas"]["IPAdapterDiffusersConfig"] | components["schemas"]["IPAdapterCheckpointConfig"] | components["schemas"]["T2IAdapterConfig"] | components["schemas"]["CLIPVisionDiffusersConfig"]; + "application/json": components["schemas"]["MainDiffusersConfig"] | components["schemas"]["MainCheckpointConfig"] | components["schemas"]["VAEDiffusersConfig"] | components["schemas"]["VAECheckpointConfig"] | components["schemas"]["ControlNetDiffusersConfig"] | components["schemas"]["ControlNetCheckpointConfig"] | components["schemas"]["LoRALyCORISConfig"] | components["schemas"]["LoRADiffusersConfig"] | components["schemas"]["TextualInversionFileConfig"] | components["schemas"]["TextualInversionFolderConfig"] | components["schemas"]["IPAdapterInvokeAIConfig"] | components["schemas"]["IPAdapterCheckpointConfig"] | components["schemas"]["T2IAdapterConfig"] | components["schemas"]["CLIPVisionDiffusersConfig"]; }; }; /** @description Bad request */ diff --git a/invokeai/frontend/web/src/services/api/types.ts b/invokeai/frontend/web/src/services/api/types.ts index d24d4bddd5..bdbbc93963 100644 --- a/invokeai/frontend/web/src/services/api/types.ts +++ b/invokeai/frontend/web/src/services/api/types.ts @@ -46,7 +46,7 @@ export type LoRAModelConfig = S['LoRADiffusersConfig'] | S['LoRALyCORISConfig']; // TODO(MM2): Can we rename this from Vae -> VAE export type VAEModelConfig = S['VAECheckpointConfig'] | S['VAEDiffusersConfig']; export type ControlNetModelConfig = S['ControlNetDiffusersConfig'] | S['ControlNetCheckpointConfig']; -export type IPAdapterModelConfig = S['IPAdapterDiffusersConfig'] | S['IPAdapterCheckpointConfig']; +export type IPAdapterModelConfig = S['IPAdapterInvokeAIConfig'] | S['IPAdapterCheckpointConfig']; export type T2IAdapterModelConfig = S['T2IAdapterConfig']; type TextualInversionModelConfig = S['TextualInversionFileConfig'] | S['TextualInversionFolderConfig']; type DiffusersModelConfig = S['MainDiffusersConfig']; From cd078b1865681e0a45c4cd1768963ed94dd58652 Mon Sep 17 00:00:00 2001 From: blessedcoolant <54517381+blessedcoolant@users.noreply.github.com> Date: Fri, 29 Mar 2024 11:58:10 +0530 Subject: [PATCH 10/89] fix: Raise a better error when incorrect CLIP Vision model is used --- invokeai/backend/ip_adapter/ip_adapter.py | 18 ++++++++++++------ 1 file changed, 12 insertions(+), 6 deletions(-) diff --git a/invokeai/backend/ip_adapter/ip_adapter.py b/invokeai/backend/ip_adapter/ip_adapter.py index 5444c76c8c..1155e571ae 100644 --- a/invokeai/backend/ip_adapter/ip_adapter.py +++ b/invokeai/backend/ip_adapter/ip_adapter.py @@ -146,9 +146,12 @@ class IPAdapter(RawModel): def get_image_embeds(self, pil_image: List[Image.Image], image_encoder: CLIPVisionModelWithProjection): clip_image = self._clip_image_processor(images=pil_image, return_tensors="pt").pixel_values clip_image_embeds = image_encoder(clip_image.to(self.device, dtype=self.dtype)).image_embeds - image_prompt_embeds = self._image_proj_model(clip_image_embeds) - uncond_image_prompt_embeds = self._image_proj_model(torch.zeros_like(clip_image_embeds)) - return image_prompt_embeds, uncond_image_prompt_embeds + try: + image_prompt_embeds = self._image_proj_model(clip_image_embeds) + uncond_image_prompt_embeds = self._image_proj_model(torch.zeros_like(clip_image_embeds)) + return image_prompt_embeds, uncond_image_prompt_embeds + except RuntimeError: + raise RuntimeError("Selected CLIP Vision Model is incompatible with the current IP Adapter") class IPAdapterPlus(IPAdapter): @@ -169,12 +172,15 @@ class IPAdapterPlus(IPAdapter): clip_image = self._clip_image_processor(images=pil_image, return_tensors="pt").pixel_values clip_image = clip_image.to(self.device, dtype=self.dtype) clip_image_embeds = image_encoder(clip_image, output_hidden_states=True).hidden_states[-2] - image_prompt_embeds = self._image_proj_model(clip_image_embeds) uncond_clip_image_embeds = image_encoder(torch.zeros_like(clip_image), output_hidden_states=True).hidden_states[ -2 ] - uncond_image_prompt_embeds = self._image_proj_model(uncond_clip_image_embeds) - return image_prompt_embeds, uncond_image_prompt_embeds + try: + image_prompt_embeds = self._image_proj_model(clip_image_embeds) + uncond_image_prompt_embeds = self._image_proj_model(uncond_clip_image_embeds) + return image_prompt_embeds, uncond_image_prompt_embeds + except RuntimeError: + raise RuntimeError("Selected CLIP Vision Model is incompatible with the current IP Adapter") class IPAdapterFull(IPAdapterPlus): From 56ed697c237d54ce808cda937c1871e15657a590 Mon Sep 17 00:00:00 2001 From: blessedcoolant <54517381+blessedcoolant@users.noreply.github.com> Date: Fri, 29 Mar 2024 12:12:16 +0530 Subject: [PATCH 11/89] fix: clip vision model auto param Setting to 'auto' works only for InvokeAI config and auto detects the SD model but will override if user explicitly sets it. If auto used with checkpoint models, we raise an error. Checkpoints will always need to set to non-auto. --- invokeai/app/invocations/ip_adapter.py | 17 ++++++++++++----- .../frontend/web/src/services/api/schema.ts | 10 +++++----- 2 files changed, 17 insertions(+), 10 deletions(-) diff --git a/invokeai/app/invocations/ip_adapter.py b/invokeai/app/invocations/ip_adapter.py index 5e24721b2f..40a667c9d0 100644 --- a/invokeai/app/invocations/ip_adapter.py +++ b/invokeai/app/invocations/ip_adapter.py @@ -65,8 +65,10 @@ class IPAdapterInvocation(BaseInvocation): ui_order=-1, ui_type=UIType.IPAdapterModel, ) - clip_vision_model: Literal["ViT-H", "ViT-G"] = InputField( - description="CLIP Vision model to use", default="ViT-H", ui_order=2 + clip_vision_model: Literal["auto", "ViT-H", "ViT-G"] = InputField( + description="CLIP Vision model to use. Overrides model settings. Mandatory for checkpoint models.", + default="auto", + ui_order=2, ) weight: Union[float, List[float]] = InputField( default=1, description="The weight given to the IP-Adapter", title="Weight" @@ -94,9 +96,14 @@ class IPAdapterInvocation(BaseInvocation): ip_adapter_info = context.models.get_config(self.ip_adapter_model.key) assert isinstance(ip_adapter_info, (IPAdapterInvokeAIConfig, IPAdapterCheckpointConfig)) - if isinstance(ip_adapter_info, IPAdapterInvokeAIConfig): - image_encoder_model_id = ip_adapter_info.image_encoder_model_id - image_encoder_model_name = image_encoder_model_id.split("/")[-1].strip() + if self.clip_vision_model == "auto": + if isinstance(ip_adapter_info, IPAdapterInvokeAIConfig): + image_encoder_model_id = ip_adapter_info.image_encoder_model_id + image_encoder_model_name = image_encoder_model_id.split("/")[-1].strip() + else: + raise RuntimeError( + "You need to set the appropriate CLIP Vision model for checkpoint IP Adapter models." + ) else: image_encoder_model_name = CLIP_VISION_MODEL_MAP[self.clip_vision_model] diff --git a/invokeai/frontend/web/src/services/api/schema.ts b/invokeai/frontend/web/src/services/api/schema.ts index 6557da78ff..b75463a8c3 100644 --- a/invokeai/frontend/web/src/services/api/schema.ts +++ b/invokeai/frontend/web/src/services/api/schema.ts @@ -4122,7 +4122,7 @@ export type components = { * @description The nodes in this graph */ nodes: { - [key: string]: components["schemas"]["SeamlessModeInvocation"] | components["schemas"]["FreeUInvocation"] | components["schemas"]["ImageResizeInvocation"] | components["schemas"]["ZoeDepthImageProcessorInvocation"] | components["schemas"]["FloatMathInvocation"] | components["schemas"]["ColorCorrectInvocation"] | components["schemas"]["SubtractInvocation"] | components["schemas"]["FloatCollectionInvocation"] | components["schemas"]["ImageCollectionInvocation"] | components["schemas"]["SDXLRefinerCompelPromptInvocation"] | components["schemas"]["ImageHueAdjustmentInvocation"] | components["schemas"]["MultiplyInvocation"] | components["schemas"]["StringInvocation"] | components["schemas"]["StringSplitNegInvocation"] | components["schemas"]["StringJoinThreeInvocation"] | components["schemas"]["CvInpaintInvocation"] | components["schemas"]["RoundInvocation"] | components["schemas"]["NoiseInvocation"] | components["schemas"]["LaMaInfillInvocation"] | components["schemas"]["TileResamplerProcessorInvocation"] | components["schemas"]["CreateGradientMaskInvocation"] | components["schemas"]["PidiImageProcessorInvocation"] | components["schemas"]["DynamicPromptInvocation"] | components["schemas"]["ColorMapImageProcessorInvocation"] | components["schemas"]["FaceMaskInvocation"] | components["schemas"]["MaskEdgeInvocation"] | components["schemas"]["StringJoinInvocation"] | components["schemas"]["MediapipeFaceProcessorInvocation"] | components["schemas"]["MergeMetadataInvocation"] | components["schemas"]["ImageScaleInvocation"] | components["schemas"]["ControlNetInvocation"] | components["schemas"]["FaceOffInvocation"] | components["schemas"]["MidasDepthImageProcessorInvocation"] | components["schemas"]["CV2InfillInvocation"] | components["schemas"]["MergeTilesToImageInvocation"] | components["schemas"]["ScaleLatentsInvocation"] | components["schemas"]["IntegerInvocation"] | components["schemas"]["ConditioningCollectionInvocation"] | components["schemas"]["ImageCropInvocation"] | components["schemas"]["ImageLerpInvocation"] | components["schemas"]["SaveImageInvocation"] | components["schemas"]["IterateInvocation"] | components["schemas"]["BooleanInvocation"] | components["schemas"]["RangeInvocation"] | components["schemas"]["AddInvocation"] | components["schemas"]["ImagePasteInvocation"] | components["schemas"]["FloatInvocation"] | components["schemas"]["ImageConvertInvocation"] | components["schemas"]["LineartAnimeImageProcessorInvocation"] | components["schemas"]["RandomIntInvocation"] | components["schemas"]["MetadataItemInvocation"] | components["schemas"]["UnsharpMaskInvocation"] | components["schemas"]["SDXLCompelPromptInvocation"] | components["schemas"]["CanvasPasteBackInvocation"] | components["schemas"]["IPAdapterInvocation"] | components["schemas"]["DenoiseLatentsInvocation"] | components["schemas"]["ImageInverseLerpInvocation"] | components["schemas"]["PromptsFromFileInvocation"] | components["schemas"]["ESRGANInvocation"] | components["schemas"]["MetadataInvocation"] | components["schemas"]["ConditioningInvocation"] | components["schemas"]["ImageChannelOffsetInvocation"] | components["schemas"]["InfillColorInvocation"] | components["schemas"]["T2IAdapterInvocation"] | components["schemas"]["BlankImageInvocation"] | components["schemas"]["TileToPropertiesInvocation"] | components["schemas"]["IdealSizeInvocation"] | components["schemas"]["MaskFromAlphaInvocation"] | components["schemas"]["CompelInvocation"] | components["schemas"]["CalculateImageTilesEvenSplitInvocation"] | components["schemas"]["BlendLatentsInvocation"] | components["schemas"]["SDXLLoRALoaderInvocation"] | components["schemas"]["ImageInvocation"] | components["schemas"]["ImageBlurInvocation"] | components["schemas"]["CLIPSkipInvocation"] | components["schemas"]["DWOpenposeImageProcessorInvocation"] | components["schemas"]["IntegerMathInvocation"] | components["schemas"]["ColorInvocation"] | components["schemas"]["MaskFromIDInvocation"] | components["schemas"]["HedImageProcessorInvocation"] | components["schemas"]["DepthAnythingImageProcessorInvocation"] | components["schemas"]["RandomFloatInvocation"] | components["schemas"]["RangeOfSizeInvocation"] | components["schemas"]["FaceIdentifierInvocation"] | components["schemas"]["ImageWatermarkInvocation"] | components["schemas"]["CollectInvocation"] | components["schemas"]["FloatToIntegerInvocation"] | components["schemas"]["MaskCombineInvocation"] | components["schemas"]["NormalbaeImageProcessorInvocation"] | components["schemas"]["CropLatentsCoreInvocation"] | components["schemas"]["ImageMultiplyInvocation"] | components["schemas"]["LatentsCollectionInvocation"] | components["schemas"]["InfillPatchMatchInvocation"] | components["schemas"]["VAELoaderInvocation"] | components["schemas"]["ShowImageInvocation"] | components["schemas"]["ImageNSFWBlurInvocation"] | components["schemas"]["CannyImageProcessorInvocation"] | components["schemas"]["ResizeLatentsInvocation"] | components["schemas"]["IntegerCollectionInvocation"] | components["schemas"]["CalculateImageTilesMinimumOverlapInvocation"] | components["schemas"]["SDXLModelLoaderInvocation"] | components["schemas"]["PairTileImageInvocation"] | components["schemas"]["ImageToLatentsInvocation"] | components["schemas"]["BooleanCollectionInvocation"] | components["schemas"]["CenterPadCropInvocation"] | components["schemas"]["ImageChannelMultiplyInvocation"] | components["schemas"]["StepParamEasingInvocation"] | components["schemas"]["MlsdImageProcessorInvocation"] | components["schemas"]["SegmentAnythingProcessorInvocation"] | components["schemas"]["LatentsInvocation"] | components["schemas"]["StringSplitInvocation"] | components["schemas"]["LineartImageProcessorInvocation"] | components["schemas"]["LeresImageProcessorInvocation"] | components["schemas"]["CreateDenoiseMaskInvocation"] | components["schemas"]["RandomRangeInvocation"] | components["schemas"]["StringCollectionInvocation"] | components["schemas"]["SDXLRefinerModelLoaderInvocation"] | components["schemas"]["ContentShuffleImageProcessorInvocation"] | components["schemas"]["SchedulerInvocation"] | components["schemas"]["FloatLinearRangeInvocation"] | components["schemas"]["MainModelLoaderInvocation"] | components["schemas"]["ImageChannelInvocation"] | components["schemas"]["CalculateImageTilesInvocation"] | components["schemas"]["LoRALoaderInvocation"] | components["schemas"]["InfillTileInvocation"] | components["schemas"]["StringReplaceInvocation"] | components["schemas"]["LatentsToImageInvocation"] | components["schemas"]["CoreMetadataInvocation"] | components["schemas"]["DivideInvocation"]; + [key: string]: components["schemas"]["CLIPSkipInvocation"] | components["schemas"]["SegmentAnythingProcessorInvocation"] | components["schemas"]["StringReplaceInvocation"] | components["schemas"]["CreateGradientMaskInvocation"] | components["schemas"]["RangeInvocation"] | components["schemas"]["InfillTileInvocation"] | components["schemas"]["DynamicPromptInvocation"] | components["schemas"]["ImagePasteInvocation"] | components["schemas"]["StringInvocation"] | components["schemas"]["RandomIntInvocation"] | components["schemas"]["SaveImageInvocation"] | components["schemas"]["StepParamEasingInvocation"] | components["schemas"]["IPAdapterInvocation"] | components["schemas"]["MaskEdgeInvocation"] | components["schemas"]["StringJoinThreeInvocation"] | components["schemas"]["FloatInvocation"] | components["schemas"]["ImageInverseLerpInvocation"] | components["schemas"]["IntegerMathInvocation"] | components["schemas"]["CanvasPasteBackInvocation"] | components["schemas"]["MlsdImageProcessorInvocation"] | components["schemas"]["SDXLLoRALoaderInvocation"] | components["schemas"]["PairTileImageInvocation"] | components["schemas"]["BooleanInvocation"] | components["schemas"]["ShowImageInvocation"] | components["schemas"]["FreeUInvocation"] | components["schemas"]["MergeTilesToImageInvocation"] | components["schemas"]["CvInpaintInvocation"] | components["schemas"]["SDXLModelLoaderInvocation"] | components["schemas"]["MaskFromAlphaInvocation"] | components["schemas"]["SDXLCompelPromptInvocation"] | components["schemas"]["HedImageProcessorInvocation"] | components["schemas"]["RandomFloatInvocation"] | components["schemas"]["MaskFromIDInvocation"] | components["schemas"]["FloatToIntegerInvocation"] | components["schemas"]["ImageToLatentsInvocation"] | components["schemas"]["ImageInvocation"] | components["schemas"]["TileResamplerProcessorInvocation"] | components["schemas"]["IntegerInvocation"] | components["schemas"]["LatentsInvocation"] | components["schemas"]["RangeOfSizeInvocation"] | components["schemas"]["ImageChannelOffsetInvocation"] | components["schemas"]["MetadataInvocation"] | components["schemas"]["ScaleLatentsInvocation"] | components["schemas"]["ConditioningInvocation"] | components["schemas"]["ImageScaleInvocation"] | components["schemas"]["ColorMapImageProcessorInvocation"] | components["schemas"]["LoRALoaderInvocation"] | components["schemas"]["BooleanCollectionInvocation"] | components["schemas"]["DepthAnythingImageProcessorInvocation"] | components["schemas"]["T2IAdapterInvocation"] | components["schemas"]["BlankImageInvocation"] | components["schemas"]["ImageConvertInvocation"] | components["schemas"]["StringJoinInvocation"] | components["schemas"]["SeamlessModeInvocation"] | components["schemas"]["LatentsCollectionInvocation"] | components["schemas"]["MaskCombineInvocation"] | components["schemas"]["CoreMetadataInvocation"] | components["schemas"]["CV2InfillInvocation"] | components["schemas"]["ZoeDepthImageProcessorInvocation"] | components["schemas"]["FloatCollectionInvocation"] | components["schemas"]["StringCollectionInvocation"] | components["schemas"]["ImageLerpInvocation"] | components["schemas"]["TileToPropertiesInvocation"] | components["schemas"]["ImageResizeInvocation"] | components["schemas"]["SubtractInvocation"] | components["schemas"]["IterateInvocation"] | components["schemas"]["NoiseInvocation"] | components["schemas"]["LineartAnimeImageProcessorInvocation"] | components["schemas"]["FaceMaskInvocation"] | components["schemas"]["IntegerCollectionInvocation"] | components["schemas"]["ImageMultiplyInvocation"] | components["schemas"]["CreateDenoiseMaskInvocation"] | components["schemas"]["IdealSizeInvocation"] | components["schemas"]["InfillPatchMatchInvocation"] | components["schemas"]["InfillColorInvocation"] | components["schemas"]["ESRGANInvocation"] | components["schemas"]["ImageChannelInvocation"] | components["schemas"]["LeresImageProcessorInvocation"] | components["schemas"]["LineartImageProcessorInvocation"] | components["schemas"]["DWOpenposeImageProcessorInvocation"] | components["schemas"]["MultiplyInvocation"] | components["schemas"]["NormalbaeImageProcessorInvocation"] | components["schemas"]["LatentsToImageInvocation"] | components["schemas"]["FloatLinearRangeInvocation"] | components["schemas"]["StringSplitNegInvocation"] | components["schemas"]["AddInvocation"] | components["schemas"]["ColorCorrectInvocation"] | components["schemas"]["FloatMathInvocation"] | components["schemas"]["CannyImageProcessorInvocation"] | components["schemas"]["CalculateImageTilesMinimumOverlapInvocation"] | components["schemas"]["ImageCropInvocation"] | components["schemas"]["ImageCollectionInvocation"] | components["schemas"]["RandomRangeInvocation"] | components["schemas"]["CenterPadCropInvocation"] | components["schemas"]["VAELoaderInvocation"] | components["schemas"]["CalculateImageTilesInvocation"] | components["schemas"]["ImageNSFWBlurInvocation"] | components["schemas"]["ContentShuffleImageProcessorInvocation"] | components["schemas"]["DivideInvocation"] | components["schemas"]["MetadataItemInvocation"] | components["schemas"]["FaceOffInvocation"] | components["schemas"]["DenoiseLatentsInvocation"] | components["schemas"]["CollectInvocation"] | components["schemas"]["MergeMetadataInvocation"] | components["schemas"]["MidasDepthImageProcessorInvocation"] | components["schemas"]["ControlNetInvocation"] | components["schemas"]["SchedulerInvocation"] | components["schemas"]["ConditioningCollectionInvocation"] | components["schemas"]["MediapipeFaceProcessorInvocation"] | components["schemas"]["FaceIdentifierInvocation"] | components["schemas"]["PromptsFromFileInvocation"] | components["schemas"]["CalculateImageTilesEvenSplitInvocation"] | components["schemas"]["ColorInvocation"] | components["schemas"]["ImageBlurInvocation"] | components["schemas"]["CompelInvocation"] | components["schemas"]["ImageChannelMultiplyInvocation"] | components["schemas"]["StringSplitInvocation"] | components["schemas"]["RoundInvocation"] | components["schemas"]["UnsharpMaskInvocation"] | components["schemas"]["ImageHueAdjustmentInvocation"] | components["schemas"]["LaMaInfillInvocation"] | components["schemas"]["BlendLatentsInvocation"] | components["schemas"]["CropLatentsCoreInvocation"] | components["schemas"]["ResizeLatentsInvocation"] | components["schemas"]["ImageWatermarkInvocation"] | components["schemas"]["PidiImageProcessorInvocation"] | components["schemas"]["MainModelLoaderInvocation"] | components["schemas"]["SDXLRefinerCompelPromptInvocation"] | components["schemas"]["SDXLRefinerModelLoaderInvocation"]; }; /** * Edges @@ -4159,7 +4159,7 @@ export type components = { * @description The results of node executions */ results: { - [key: string]: components["schemas"]["StringCollectionOutput"] | components["schemas"]["ConditioningCollectionOutput"] | components["schemas"]["IntegerCollectionOutput"] | components["schemas"]["ColorCollectionOutput"] | components["schemas"]["TileToPropertiesOutput"] | components["schemas"]["StringOutput"] | components["schemas"]["FaceMaskOutput"] | components["schemas"]["MetadataItemOutput"] | components["schemas"]["T2IAdapterOutput"] | components["schemas"]["SeamlessModeOutput"] | components["schemas"]["LatentsOutput"] | components["schemas"]["ConditioningOutput"] | components["schemas"]["FloatCollectionOutput"] | components["schemas"]["CollectInvocationOutput"] | components["schemas"]["SDXLModelLoaderOutput"] | components["schemas"]["VAEOutput"] | components["schemas"]["SDXLLoRALoaderOutput"] | components["schemas"]["CalculateImageTilesOutput"] | components["schemas"]["ColorOutput"] | components["schemas"]["IdealSizeOutput"] | components["schemas"]["DenoiseMaskOutput"] | components["schemas"]["LatentsCollectionOutput"] | components["schemas"]["FaceOffOutput"] | components["schemas"]["IntegerOutput"] | components["schemas"]["LoRALoaderOutput"] | components["schemas"]["IPAdapterOutput"] | components["schemas"]["GradientMaskOutput"] | components["schemas"]["IterateInvocationOutput"] | components["schemas"]["SchedulerOutput"] | components["schemas"]["ModelLoaderOutput"] | components["schemas"]["BooleanCollectionOutput"] | components["schemas"]["MetadataOutput"] | components["schemas"]["NoiseOutput"] | components["schemas"]["ImageCollectionOutput"] | components["schemas"]["CLIPOutput"] | components["schemas"]["ImageOutput"] | components["schemas"]["SDXLRefinerModelLoaderOutput"] | components["schemas"]["BooleanOutput"] | components["schemas"]["UNetOutput"] | components["schemas"]["FloatOutput"] | components["schemas"]["StringPosNegOutput"] | components["schemas"]["PairTileImageOutput"] | components["schemas"]["CLIPSkipInvocationOutput"] | components["schemas"]["String2Output"] | components["schemas"]["ControlOutput"]; + [key: string]: components["schemas"]["SDXLRefinerModelLoaderOutput"] | components["schemas"]["TileToPropertiesOutput"] | components["schemas"]["FaceMaskOutput"] | components["schemas"]["StringOutput"] | components["schemas"]["ImageCollectionOutput"] | components["schemas"]["IntegerCollectionOutput"] | components["schemas"]["IterateInvocationOutput"] | components["schemas"]["CLIPSkipInvocationOutput"] | components["schemas"]["MetadataOutput"] | components["schemas"]["PairTileImageOutput"] | components["schemas"]["MetadataItemOutput"] | components["schemas"]["CLIPOutput"] | components["schemas"]["ModelLoaderOutput"] | components["schemas"]["ColorOutput"] | components["schemas"]["SDXLLoRALoaderOutput"] | components["schemas"]["VAEOutput"] | components["schemas"]["String2Output"] | components["schemas"]["ControlOutput"] | components["schemas"]["ConditioningOutput"] | components["schemas"]["FloatCollectionOutput"] | components["schemas"]["SeamlessModeOutput"] | components["schemas"]["FloatOutput"] | components["schemas"]["LatentsCollectionOutput"] | components["schemas"]["IdealSizeOutput"] | components["schemas"]["ImageOutput"] | components["schemas"]["IPAdapterOutput"] | components["schemas"]["SDXLModelLoaderOutput"] | components["schemas"]["BooleanCollectionOutput"] | components["schemas"]["FaceOffOutput"] | components["schemas"]["DenoiseMaskOutput"] | components["schemas"]["UNetOutput"] | components["schemas"]["CalculateImageTilesOutput"] | components["schemas"]["ColorCollectionOutput"] | components["schemas"]["BooleanOutput"] | components["schemas"]["IntegerOutput"] | components["schemas"]["SchedulerOutput"] | components["schemas"]["StringCollectionOutput"] | components["schemas"]["LatentsOutput"] | components["schemas"]["CollectInvocationOutput"] | components["schemas"]["NoiseOutput"] | components["schemas"]["ConditioningCollectionOutput"] | components["schemas"]["LoRALoaderOutput"] | components["schemas"]["StringPosNegOutput"] | components["schemas"]["T2IAdapterOutput"] | components["schemas"]["GradientMaskOutput"]; }; /** * Errors @@ -4444,11 +4444,11 @@ export type components = { ip_adapter_model: components["schemas"]["ModelIdentifierField"]; /** * Clip Vision Model - * @description CLIP Vision model to use - * @default ViT-H + * @description CLIP Vision model to use. Overrides model settings. Mandatory for checkpoint models. + * @default auto * @enum {string} */ - clip_vision_model?: "ViT-H" | "ViT-G"; + clip_vision_model?: "auto" | "ViT-H" | "ViT-G"; /** * Weight * @description The weight given to the IP-Adapter From 6e4c2d3685c9aa765c88228baae8607994452cde Mon Sep 17 00:00:00 2001 From: blessedcoolant <54517381+blessedcoolant@users.noreply.github.com> Date: Fri, 29 Mar 2024 12:34:56 +0530 Subject: [PATCH 12/89] fix: Fail when unexpected keys are found in IP Adapter models --- invokeai/backend/ip_adapter/ip_adapter.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/invokeai/backend/ip_adapter/ip_adapter.py b/invokeai/backend/ip_adapter/ip_adapter.py index 1155e571ae..02788c0ba6 100644 --- a/invokeai/backend/ip_adapter/ip_adapter.py +++ b/invokeai/backend/ip_adapter/ip_adapter.py @@ -214,6 +214,8 @@ def load_ip_adapter_tensors(ip_adapter_ckpt_path: str, device: str) -> IPAdapter state_dict["image_proj"][key.replace("image_proj.", "")] = model.get_tensor(key) elif key.startswith("ip_adapter."): state_dict["ip_adapter"][key.replace("ip_adapter.", "")] = model.get_tensor(key) + else: + raise RuntimeError(f"Encountered unexpected IP Adapter state dict key: '{key}'.") else: ip_adapter_diffusers_checkpoint_path = ip_adapter_ckpt_path + "/ip_adapter.bin" state_dict = torch.load(ip_adapter_diffusers_checkpoint_path, map_location="cpu") From 298cae5bb9389d63ca5cd72bf26f2ea965879a11 Mon Sep 17 00:00:00 2001 From: blessedcoolant <54517381+blessedcoolant@users.noreply.github.com> Date: Fri, 29 Mar 2024 12:41:10 +0530 Subject: [PATCH 13/89] Update schema.ts --- .../frontend/web/src/services/api/schema.ts | 39 +------------------ 1 file changed, 2 insertions(+), 37 deletions(-) diff --git a/invokeai/frontend/web/src/services/api/schema.ts b/invokeai/frontend/web/src/services/api/schema.ts index b75463a8c3..497a55fc08 100644 --- a/invokeai/frontend/web/src/services/api/schema.ts +++ b/invokeai/frontend/web/src/services/api/schema.ts @@ -136,16 +136,6 @@ export type paths = { */ delete: operations["cancel_model_install_job"]; }; - "/api/v2/models/sync": { - /** - * Sync Models To Config - * @description Traverse the models and autoimport directories. - * - * Model files without a corresponding - * record in the database are added. Orphan records without a models file are deleted. - */ - patch: operations["sync_models_to_config"]; - }; "/api/v2/models/convert/{key}": { /** * Convert Model @@ -4122,7 +4112,7 @@ export type components = { * @description The nodes in this graph */ nodes: { - [key: string]: components["schemas"]["CLIPSkipInvocation"] | components["schemas"]["SegmentAnythingProcessorInvocation"] | components["schemas"]["StringReplaceInvocation"] | components["schemas"]["CreateGradientMaskInvocation"] | components["schemas"]["RangeInvocation"] | components["schemas"]["InfillTileInvocation"] | components["schemas"]["DynamicPromptInvocation"] | components["schemas"]["ImagePasteInvocation"] | components["schemas"]["StringInvocation"] | components["schemas"]["RandomIntInvocation"] | components["schemas"]["SaveImageInvocation"] | components["schemas"]["StepParamEasingInvocation"] | components["schemas"]["IPAdapterInvocation"] | components["schemas"]["MaskEdgeInvocation"] | components["schemas"]["StringJoinThreeInvocation"] | components["schemas"]["FloatInvocation"] | components["schemas"]["ImageInverseLerpInvocation"] | components["schemas"]["IntegerMathInvocation"] | components["schemas"]["CanvasPasteBackInvocation"] | components["schemas"]["MlsdImageProcessorInvocation"] | components["schemas"]["SDXLLoRALoaderInvocation"] | components["schemas"]["PairTileImageInvocation"] | components["schemas"]["BooleanInvocation"] | components["schemas"]["ShowImageInvocation"] | components["schemas"]["FreeUInvocation"] | components["schemas"]["MergeTilesToImageInvocation"] | components["schemas"]["CvInpaintInvocation"] | components["schemas"]["SDXLModelLoaderInvocation"] | components["schemas"]["MaskFromAlphaInvocation"] | components["schemas"]["SDXLCompelPromptInvocation"] | components["schemas"]["HedImageProcessorInvocation"] | components["schemas"]["RandomFloatInvocation"] | components["schemas"]["MaskFromIDInvocation"] | components["schemas"]["FloatToIntegerInvocation"] | components["schemas"]["ImageToLatentsInvocation"] | components["schemas"]["ImageInvocation"] | components["schemas"]["TileResamplerProcessorInvocation"] | components["schemas"]["IntegerInvocation"] | components["schemas"]["LatentsInvocation"] | components["schemas"]["RangeOfSizeInvocation"] | components["schemas"]["ImageChannelOffsetInvocation"] | components["schemas"]["MetadataInvocation"] | components["schemas"]["ScaleLatentsInvocation"] | components["schemas"]["ConditioningInvocation"] | components["schemas"]["ImageScaleInvocation"] | components["schemas"]["ColorMapImageProcessorInvocation"] | components["schemas"]["LoRALoaderInvocation"] | components["schemas"]["BooleanCollectionInvocation"] | components["schemas"]["DepthAnythingImageProcessorInvocation"] | components["schemas"]["T2IAdapterInvocation"] | components["schemas"]["BlankImageInvocation"] | components["schemas"]["ImageConvertInvocation"] | components["schemas"]["StringJoinInvocation"] | components["schemas"]["SeamlessModeInvocation"] | components["schemas"]["LatentsCollectionInvocation"] | components["schemas"]["MaskCombineInvocation"] | components["schemas"]["CoreMetadataInvocation"] | components["schemas"]["CV2InfillInvocation"] | components["schemas"]["ZoeDepthImageProcessorInvocation"] | components["schemas"]["FloatCollectionInvocation"] | components["schemas"]["StringCollectionInvocation"] | components["schemas"]["ImageLerpInvocation"] | components["schemas"]["TileToPropertiesInvocation"] | components["schemas"]["ImageResizeInvocation"] | components["schemas"]["SubtractInvocation"] | components["schemas"]["IterateInvocation"] | components["schemas"]["NoiseInvocation"] | components["schemas"]["LineartAnimeImageProcessorInvocation"] | components["schemas"]["FaceMaskInvocation"] | components["schemas"]["IntegerCollectionInvocation"] | components["schemas"]["ImageMultiplyInvocation"] | components["schemas"]["CreateDenoiseMaskInvocation"] | components["schemas"]["IdealSizeInvocation"] | components["schemas"]["InfillPatchMatchInvocation"] | components["schemas"]["InfillColorInvocation"] | components["schemas"]["ESRGANInvocation"] | components["schemas"]["ImageChannelInvocation"] | components["schemas"]["LeresImageProcessorInvocation"] | components["schemas"]["LineartImageProcessorInvocation"] | components["schemas"]["DWOpenposeImageProcessorInvocation"] | components["schemas"]["MultiplyInvocation"] | components["schemas"]["NormalbaeImageProcessorInvocation"] | components["schemas"]["LatentsToImageInvocation"] | components["schemas"]["FloatLinearRangeInvocation"] | components["schemas"]["StringSplitNegInvocation"] | components["schemas"]["AddInvocation"] | components["schemas"]["ColorCorrectInvocation"] | components["schemas"]["FloatMathInvocation"] | components["schemas"]["CannyImageProcessorInvocation"] | components["schemas"]["CalculateImageTilesMinimumOverlapInvocation"] | components["schemas"]["ImageCropInvocation"] | components["schemas"]["ImageCollectionInvocation"] | components["schemas"]["RandomRangeInvocation"] | components["schemas"]["CenterPadCropInvocation"] | components["schemas"]["VAELoaderInvocation"] | components["schemas"]["CalculateImageTilesInvocation"] | components["schemas"]["ImageNSFWBlurInvocation"] | components["schemas"]["ContentShuffleImageProcessorInvocation"] | components["schemas"]["DivideInvocation"] | components["schemas"]["MetadataItemInvocation"] | components["schemas"]["FaceOffInvocation"] | components["schemas"]["DenoiseLatentsInvocation"] | components["schemas"]["CollectInvocation"] | components["schemas"]["MergeMetadataInvocation"] | components["schemas"]["MidasDepthImageProcessorInvocation"] | components["schemas"]["ControlNetInvocation"] | components["schemas"]["SchedulerInvocation"] | components["schemas"]["ConditioningCollectionInvocation"] | components["schemas"]["MediapipeFaceProcessorInvocation"] | components["schemas"]["FaceIdentifierInvocation"] | components["schemas"]["PromptsFromFileInvocation"] | components["schemas"]["CalculateImageTilesEvenSplitInvocation"] | components["schemas"]["ColorInvocation"] | components["schemas"]["ImageBlurInvocation"] | components["schemas"]["CompelInvocation"] | components["schemas"]["ImageChannelMultiplyInvocation"] | components["schemas"]["StringSplitInvocation"] | components["schemas"]["RoundInvocation"] | components["schemas"]["UnsharpMaskInvocation"] | components["schemas"]["ImageHueAdjustmentInvocation"] | components["schemas"]["LaMaInfillInvocation"] | components["schemas"]["BlendLatentsInvocation"] | components["schemas"]["CropLatentsCoreInvocation"] | components["schemas"]["ResizeLatentsInvocation"] | components["schemas"]["ImageWatermarkInvocation"] | components["schemas"]["PidiImageProcessorInvocation"] | components["schemas"]["MainModelLoaderInvocation"] | components["schemas"]["SDXLRefinerCompelPromptInvocation"] | components["schemas"]["SDXLRefinerModelLoaderInvocation"]; + [key: string]: components["schemas"]["FloatInvocation"] | components["schemas"]["LatentsInvocation"] | components["schemas"]["SubtractInvocation"] | components["schemas"]["MlsdImageProcessorInvocation"] | components["schemas"]["ImageBlurInvocation"] | components["schemas"]["CvInpaintInvocation"] | components["schemas"]["LaMaInfillInvocation"] | components["schemas"]["RangeOfSizeInvocation"] | components["schemas"]["RandomFloatInvocation"] | components["schemas"]["CoreMetadataInvocation"] | components["schemas"]["PairTileImageInvocation"] | components["schemas"]["CenterPadCropInvocation"] | components["schemas"]["MidasDepthImageProcessorInvocation"] | components["schemas"]["ImageInverseLerpInvocation"] | components["schemas"]["SeamlessModeInvocation"] | components["schemas"]["TileToPropertiesInvocation"] | components["schemas"]["ResizeLatentsInvocation"] | components["schemas"]["CollectInvocation"] | components["schemas"]["MediapipeFaceProcessorInvocation"] | components["schemas"]["FaceIdentifierInvocation"] | components["schemas"]["PromptsFromFileInvocation"] | components["schemas"]["BlendLatentsInvocation"] | components["schemas"]["SegmentAnythingProcessorInvocation"] | components["schemas"]["T2IAdapterInvocation"] | components["schemas"]["ImageInvocation"] | components["schemas"]["ScaleLatentsInvocation"] | components["schemas"]["UnsharpMaskInvocation"] | components["schemas"]["StepParamEasingInvocation"] | components["schemas"]["SDXLLoRALoaderInvocation"] | components["schemas"]["ContentShuffleImageProcessorInvocation"] | components["schemas"]["StringReplaceInvocation"] | components["schemas"]["MultiplyInvocation"] | components["schemas"]["SDXLRefinerCompelPromptInvocation"] | components["schemas"]["ColorCorrectInvocation"] | components["schemas"]["ImageCollectionInvocation"] | components["schemas"]["ImageResizeInvocation"] | components["schemas"]["NormalbaeImageProcessorInvocation"] | components["schemas"]["HedImageProcessorInvocation"] | components["schemas"]["MaskFromIDInvocation"] | components["schemas"]["StringJoinThreeInvocation"] | components["schemas"]["StringCollectionInvocation"] | components["schemas"]["FaceOffInvocation"] | components["schemas"]["ImageToLatentsInvocation"] | components["schemas"]["ImagePasteInvocation"] | components["schemas"]["DivideInvocation"] | components["schemas"]["ImageMultiplyInvocation"] | components["schemas"]["VAELoaderInvocation"] | components["schemas"]["RoundInvocation"] | components["schemas"]["IdealSizeInvocation"] | components["schemas"]["ControlNetInvocation"] | components["schemas"]["ImageChannelInvocation"] | components["schemas"]["ColorMapImageProcessorInvocation"] | components["schemas"]["CV2InfillInvocation"] | components["schemas"]["LineartImageProcessorInvocation"] | components["schemas"]["ImageNSFWBlurInvocation"] | components["schemas"]["MergeTilesToImageInvocation"] | components["schemas"]["RandomRangeInvocation"] | components["schemas"]["MetadataItemInvocation"] | components["schemas"]["MaskFromAlphaInvocation"] | components["schemas"]["StringJoinInvocation"] | components["schemas"]["CropLatentsCoreInvocation"] | components["schemas"]["BlankImageInvocation"] | components["schemas"]["ImageScaleInvocation"] | components["schemas"]["ImageChannelOffsetInvocation"] | components["schemas"]["MaskEdgeInvocation"] | components["schemas"]["IterateInvocation"] | components["schemas"]["MainModelLoaderInvocation"] | components["schemas"]["StringSplitInvocation"] | components["schemas"]["PidiImageProcessorInvocation"] | components["schemas"]["FaceMaskInvocation"] | components["schemas"]["DenoiseLatentsInvocation"] | components["schemas"]["FloatCollectionInvocation"] | components["schemas"]["CalculateImageTilesEvenSplitInvocation"] | components["schemas"]["ColorInvocation"] | components["schemas"]["RandomIntInvocation"] | components["schemas"]["DepthAnythingImageProcessorInvocation"] | components["schemas"]["CompelInvocation"] | components["schemas"]["MaskCombineInvocation"] | components["schemas"]["StringSplitNegInvocation"] | components["schemas"]["BooleanInvocation"] | components["schemas"]["CreateDenoiseMaskInvocation"] | components["schemas"]["RangeInvocation"] | components["schemas"]["ZoeDepthImageProcessorInvocation"] | components["schemas"]["DWOpenposeImageProcessorInvocation"] | components["schemas"]["ImageChannelMultiplyInvocation"] | components["schemas"]["InfillPatchMatchInvocation"] | components["schemas"]["BooleanCollectionInvocation"] | components["schemas"]["ImageWatermarkInvocation"] | components["schemas"]["FreeUInvocation"] | components["schemas"]["DynamicPromptInvocation"] | components["schemas"]["IntegerCollectionInvocation"] | components["schemas"]["LeresImageProcessorInvocation"] | components["schemas"]["NoiseInvocation"] | components["schemas"]["ConditioningInvocation"] | components["schemas"]["SDXLCompelPromptInvocation"] | components["schemas"]["SchedulerInvocation"] | components["schemas"]["CalculateImageTilesInvocation"] | components["schemas"]["StringInvocation"] | components["schemas"]["AddInvocation"] | components["schemas"]["ImageConvertInvocation"] | components["schemas"]["LatentsCollectionInvocation"] | components["schemas"]["IntegerMathInvocation"] | components["schemas"]["CannyImageProcessorInvocation"] | components["schemas"]["SaveImageInvocation"] | components["schemas"]["MetadataInvocation"] | components["schemas"]["InfillTileInvocation"] | components["schemas"]["SDXLModelLoaderInvocation"] | components["schemas"]["ConditioningCollectionInvocation"] | components["schemas"]["ImageCropInvocation"] | components["schemas"]["MergeMetadataInvocation"] | components["schemas"]["ImageHueAdjustmentInvocation"] | components["schemas"]["LineartAnimeImageProcessorInvocation"] | components["schemas"]["InfillColorInvocation"] | components["schemas"]["IPAdapterInvocation"] | components["schemas"]["ESRGANInvocation"] | components["schemas"]["LoRALoaderInvocation"] | components["schemas"]["FloatMathInvocation"] | components["schemas"]["TileResamplerProcessorInvocation"] | components["schemas"]["SDXLRefinerModelLoaderInvocation"] | components["schemas"]["IntegerInvocation"] | components["schemas"]["CalculateImageTilesMinimumOverlapInvocation"] | components["schemas"]["FloatToIntegerInvocation"] | components["schemas"]["ShowImageInvocation"] | components["schemas"]["ImageLerpInvocation"] | components["schemas"]["CanvasPasteBackInvocation"] | components["schemas"]["FloatLinearRangeInvocation"] | components["schemas"]["CreateGradientMaskInvocation"] | components["schemas"]["LatentsToImageInvocation"] | components["schemas"]["CLIPSkipInvocation"]; }; /** * Edges @@ -4159,7 +4149,7 @@ export type components = { * @description The results of node executions */ results: { - [key: string]: components["schemas"]["SDXLRefinerModelLoaderOutput"] | components["schemas"]["TileToPropertiesOutput"] | components["schemas"]["FaceMaskOutput"] | components["schemas"]["StringOutput"] | components["schemas"]["ImageCollectionOutput"] | components["schemas"]["IntegerCollectionOutput"] | components["schemas"]["IterateInvocationOutput"] | components["schemas"]["CLIPSkipInvocationOutput"] | components["schemas"]["MetadataOutput"] | components["schemas"]["PairTileImageOutput"] | components["schemas"]["MetadataItemOutput"] | components["schemas"]["CLIPOutput"] | components["schemas"]["ModelLoaderOutput"] | components["schemas"]["ColorOutput"] | components["schemas"]["SDXLLoRALoaderOutput"] | components["schemas"]["VAEOutput"] | components["schemas"]["String2Output"] | components["schemas"]["ControlOutput"] | components["schemas"]["ConditioningOutput"] | components["schemas"]["FloatCollectionOutput"] | components["schemas"]["SeamlessModeOutput"] | components["schemas"]["FloatOutput"] | components["schemas"]["LatentsCollectionOutput"] | components["schemas"]["IdealSizeOutput"] | components["schemas"]["ImageOutput"] | components["schemas"]["IPAdapterOutput"] | components["schemas"]["SDXLModelLoaderOutput"] | components["schemas"]["BooleanCollectionOutput"] | components["schemas"]["FaceOffOutput"] | components["schemas"]["DenoiseMaskOutput"] | components["schemas"]["UNetOutput"] | components["schemas"]["CalculateImageTilesOutput"] | components["schemas"]["ColorCollectionOutput"] | components["schemas"]["BooleanOutput"] | components["schemas"]["IntegerOutput"] | components["schemas"]["SchedulerOutput"] | components["schemas"]["StringCollectionOutput"] | components["schemas"]["LatentsOutput"] | components["schemas"]["CollectInvocationOutput"] | components["schemas"]["NoiseOutput"] | components["schemas"]["ConditioningCollectionOutput"] | components["schemas"]["LoRALoaderOutput"] | components["schemas"]["StringPosNegOutput"] | components["schemas"]["T2IAdapterOutput"] | components["schemas"]["GradientMaskOutput"]; + [key: string]: components["schemas"]["ConditioningOutput"] | components["schemas"]["SDXLModelLoaderOutput"] | components["schemas"]["ModelLoaderOutput"] | components["schemas"]["LatentsCollectionOutput"] | components["schemas"]["FloatOutput"] | components["schemas"]["CalculateImageTilesOutput"] | components["schemas"]["ColorOutput"] | components["schemas"]["IntegerOutput"] | components["schemas"]["GradientMaskOutput"] | components["schemas"]["ImageCollectionOutput"] | components["schemas"]["IPAdapterOutput"] | components["schemas"]["MetadataItemOutput"] | components["schemas"]["SDXLRefinerModelLoaderOutput"] | components["schemas"]["ImageOutput"] | components["schemas"]["IdealSizeOutput"] | components["schemas"]["ColorCollectionOutput"] | components["schemas"]["T2IAdapterOutput"] | components["schemas"]["StringPosNegOutput"] | components["schemas"]["IterateInvocationOutput"] | components["schemas"]["UNetOutput"] | components["schemas"]["TileToPropertiesOutput"] | components["schemas"]["SeamlessModeOutput"] | components["schemas"]["StringOutput"] | components["schemas"]["LatentsOutput"] | components["schemas"]["NoiseOutput"] | components["schemas"]["CollectInvocationOutput"] | components["schemas"]["SDXLLoRALoaderOutput"] | components["schemas"]["CLIPSkipInvocationOutput"] | components["schemas"]["SchedulerOutput"] | components["schemas"]["DenoiseMaskOutput"] | components["schemas"]["FaceOffOutput"] | components["schemas"]["PairTileImageOutput"] | components["schemas"]["StringCollectionOutput"] | components["schemas"]["String2Output"] | components["schemas"]["LoRALoaderOutput"] | components["schemas"]["BooleanOutput"] | components["schemas"]["VAEOutput"] | components["schemas"]["ControlOutput"] | components["schemas"]["FaceMaskOutput"] | components["schemas"]["IntegerCollectionOutput"] | components["schemas"]["ConditioningCollectionOutput"] | components["schemas"]["CLIPOutput"] | components["schemas"]["MetadataOutput"] | components["schemas"]["BooleanCollectionOutput"] | components["schemas"]["FloatCollectionOutput"]; }; /** * Errors @@ -11704,31 +11694,6 @@ export type operations = { }; }; }; - /** - * Sync Models To Config - * @description Traverse the models and autoimport directories. - * - * Model files without a corresponding - * record in the database are added. Orphan records without a models file are deleted. - */ - sync_models_to_config: { - responses: { - /** @description Successful Response */ - 200: { - content: { - "application/json": unknown; - }; - }; - /** @description Model config record database resynced with files on disk */ - 204: { - content: never; - }; - /** @description Bad request */ - 400: { - content: never; - }; - }; - }; /** * Convert Model * @description Permanently convert a model into diffusers format, replacing the safetensors version. From 23390f151698ef56663ce329f0d093c3445c1226 Mon Sep 17 00:00:00 2001 From: blessedcoolant <54517381+blessedcoolant@users.noreply.github.com> Date: Mon, 1 Apr 2024 06:37:38 +0530 Subject: [PATCH 14/89] cleanup: use load_file of safetensors directly for loading ip adapters --- invokeai/backend/ip_adapter/ip_adapter.py | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/invokeai/backend/ip_adapter/ip_adapter.py b/invokeai/backend/ip_adapter/ip_adapter.py index 02788c0ba6..920cb3780a 100644 --- a/invokeai/backend/ip_adapter/ip_adapter.py +++ b/invokeai/backend/ip_adapter/ip_adapter.py @@ -3,12 +3,14 @@ from typing import List, Optional, TypedDict, Union +import safetensors +import safetensors.torch import torch from PIL import Image -from safetensors import safe_open from transformers import CLIPImageProcessor, CLIPVisionModelWithProjection from invokeai.backend.ip_adapter.ip_attention_weights import IPAttentionWeights +from invokeai.backend.util.devices import choose_torch_device from ..raw_model import RawModel from .resampler import Resampler @@ -208,12 +210,12 @@ def load_ip_adapter_tensors(ip_adapter_ckpt_path: str, device: str) -> IPAdapter state_dict: IPAdapterStateDict = {"ip_adapter": {}, "image_proj": {}} if ip_adapter_ckpt_path.endswith("safetensors"): - model = safe_open(ip_adapter_ckpt_path, device=device, framework="pt") + model = safetensors.torch.load_file(ip_adapter_ckpt_path, device=device) for key in model.keys(): if key.startswith("image_proj."): - state_dict["image_proj"][key.replace("image_proj.", "")] = model.get_tensor(key) + state_dict["image_proj"][key.replace("image_proj.", "")] = model[key] elif key.startswith("ip_adapter."): - state_dict["ip_adapter"][key.replace("ip_adapter.", "")] = model.get_tensor(key) + state_dict["ip_adapter"][key.replace("ip_adapter.", "")] = model[key] else: raise RuntimeError(f"Encountered unexpected IP Adapter state dict key: '{key}'.") else: From 67afb1763e675605fcad0f3a1e5c4d3e72920b27 Mon Sep 17 00:00:00 2001 From: blessedcoolant <54517381+blessedcoolant@users.noreply.github.com> Date: Sun, 24 Mar 2024 01:40:28 +0530 Subject: [PATCH 15/89] wip: Initial implementation of safetensor support for IP Adapter --- invokeai/app/invocations/ip_adapter.py | 23 +++++---- invokeai/backend/ip_adapter/ip_adapter.py | 50 ++++++++++++------- invokeai/backend/ip_adapter/resampler.py | 40 +++++++++------ invokeai/backend/model_manager/config.py | 22 ++++++-- .../load/model_loaders/ip_adapter.py | 12 ++--- invokeai/backend/model_manager/probe.py | 18 ++++--- 6 files changed, 104 insertions(+), 61 deletions(-) diff --git a/invokeai/app/invocations/ip_adapter.py b/invokeai/app/invocations/ip_adapter.py index e302c2b97a..165a6bee24 100644 --- a/invokeai/app/invocations/ip_adapter.py +++ b/invokeai/app/invocations/ip_adapter.py @@ -4,18 +4,19 @@ from typing import List, Union from pydantic import BaseModel, Field, field_validator, model_validator from typing_extensions import Self -from invokeai.app.invocations.baseinvocation import ( - BaseInvocation, - BaseInvocationOutput, - invocation, - invocation_output, -) +from invokeai.app.invocations.baseinvocation import BaseInvocation, BaseInvocationOutput, invocation, invocation_output from invokeai.app.invocations.fields import FieldDescriptions, Input, InputField, OutputField, UIType from invokeai.app.invocations.model import ModelIdentifierField from invokeai.app.invocations.primitives import ImageField from invokeai.app.invocations.util import validate_begin_end_step, validate_weights from invokeai.app.services.shared.invocation_context import InvocationContext -from invokeai.backend.model_manager.config import AnyModelConfig, BaseModelType, IPAdapterConfig, ModelType +from invokeai.backend.model_manager.config import ( + AnyModelConfig, + BaseModelType, + IPAdapterCheckpointConfig, + IPAdapterDiffusersConfig, + ModelType, +) class IPAdapterField(BaseModel): @@ -86,8 +87,12 @@ class IPAdapterInvocation(BaseInvocation): def invoke(self, context: InvocationContext) -> IPAdapterOutput: # Lookup the CLIP Vision encoder that is intended to be used with the IP-Adapter model. ip_adapter_info = context.models.get_config(self.ip_adapter_model.key) - assert isinstance(ip_adapter_info, IPAdapterConfig) - image_encoder_model_id = ip_adapter_info.image_encoder_model_id + assert isinstance(ip_adapter_info, (IPAdapterDiffusersConfig, IPAdapterCheckpointConfig)) + image_encoder_model_id = ( + ip_adapter_info.image_encoder_model_id + if isinstance(ip_adapter_info, IPAdapterDiffusersConfig) + else "InvokeAI/ip_adapter_sd_image_encoder" + ) image_encoder_model_name = image_encoder_model_id.split("/")[-1].strip() image_encoder_model = self._get_image_encoder(context, image_encoder_model_name) return IPAdapterOutput( diff --git a/invokeai/backend/ip_adapter/ip_adapter.py b/invokeai/backend/ip_adapter/ip_adapter.py index e51966c779..81514a9f8b 100644 --- a/invokeai/backend/ip_adapter/ip_adapter.py +++ b/invokeai/backend/ip_adapter/ip_adapter.py @@ -1,10 +1,11 @@ # copied from https://github.com/tencent-ailab/IP-Adapter (Apache License 2.0) # and modified as needed -from typing import Optional, Union +from typing import List, Optional, TypedDict, Union import torch from PIL import Image +from safetensors import safe_open from transformers import CLIPImageProcessor, CLIPVisionModelWithProjection from invokeai.backend.ip_adapter.ip_attention_weights import IPAttentionWeights @@ -13,10 +14,17 @@ from ..raw_model import RawModel from .resampler import Resampler +class IPAdapterStateDict(TypedDict): + ip_adapter: dict[str, torch.Tensor] + image_proj: dict[str, torch.Tensor] + + class ImageProjModel(torch.nn.Module): """Image Projection Model""" - def __init__(self, cross_attention_dim=1024, clip_embeddings_dim=1024, clip_extra_context_tokens=4): + def __init__( + self, cross_attention_dim: int = 1024, clip_embeddings_dim: int = 1024, clip_extra_context_tokens: int = 4 + ): super().__init__() self.cross_attention_dim = cross_attention_dim @@ -25,7 +33,7 @@ class ImageProjModel(torch.nn.Module): self.norm = torch.nn.LayerNorm(cross_attention_dim) @classmethod - def from_state_dict(cls, state_dict: dict[torch.Tensor], clip_extra_context_tokens=4): + def from_state_dict(cls, state_dict: dict[str, torch.Tensor], clip_extra_context_tokens: int = 4): """Initialize an ImageProjModel from a state_dict. The cross_attention_dim and clip_embeddings_dim are inferred from the shape of the tensors in the state_dict. @@ -57,7 +65,7 @@ class ImageProjModel(torch.nn.Module): class MLPProjModel(torch.nn.Module): """SD model with image prompt""" - def __init__(self, cross_attention_dim=1024, clip_embeddings_dim=1024): + def __init__(self, cross_attention_dim: int = 1024, clip_embeddings_dim: int = 1024): super().__init__() self.proj = torch.nn.Sequential( @@ -68,7 +76,7 @@ class MLPProjModel(torch.nn.Module): ) @classmethod - def from_state_dict(cls, state_dict: dict[torch.Tensor]): + def from_state_dict(cls, state_dict: dict[str, torch.Tensor]): """Initialize an MLPProjModel from a state_dict. The cross_attention_dim and clip_embeddings_dim are inferred from the shape of the tensors in the state_dict. @@ -97,7 +105,7 @@ class IPAdapter(RawModel): def __init__( self, - state_dict: dict[str, torch.Tensor], + state_dict: IPAdapterStateDict, device: torch.device, dtype: torch.dtype = torch.float16, num_tokens: int = 4, @@ -129,13 +137,11 @@ class IPAdapter(RawModel): return calc_model_size_by_data(self._image_proj_model) + calc_model_size_by_data(self.attn_weights) - def _init_image_proj_model(self, state_dict): + def _init_image_proj_model(self, state_dict: dict[str, torch.Tensor]): return ImageProjModel.from_state_dict(state_dict, self._num_tokens).to(self.device, dtype=self.dtype) @torch.inference_mode() - def get_image_embeds(self, pil_image, image_encoder: CLIPVisionModelWithProjection): - if isinstance(pil_image, Image.Image): - pil_image = [pil_image] + def get_image_embeds(self, pil_image: List[Image.Image], image_encoder: CLIPVisionModelWithProjection): clip_image = self._clip_image_processor(images=pil_image, return_tensors="pt").pixel_values clip_image_embeds = image_encoder(clip_image.to(self.device, dtype=self.dtype)).image_embeds image_prompt_embeds = self._image_proj_model(clip_image_embeds) @@ -146,7 +152,7 @@ class IPAdapter(RawModel): class IPAdapterPlus(IPAdapter): """IP-Adapter with fine-grained features""" - def _init_image_proj_model(self, state_dict): + def _init_image_proj_model(self, state_dict: dict[str, torch.Tensor]): return Resampler.from_state_dict( state_dict=state_dict, depth=4, @@ -157,9 +163,7 @@ class IPAdapterPlus(IPAdapter): ).to(self.device, dtype=self.dtype) @torch.inference_mode() - def get_image_embeds(self, pil_image, image_encoder: CLIPVisionModelWithProjection): - if isinstance(pil_image, Image.Image): - pil_image = [pil_image] + def get_image_embeds(self, pil_image: List[Image.Image], image_encoder: CLIPVisionModelWithProjection): clip_image = self._clip_image_processor(images=pil_image, return_tensors="pt").pixel_values clip_image = clip_image.to(self.device, dtype=self.dtype) clip_image_embeds = image_encoder(clip_image, output_hidden_states=True).hidden_states[-2] @@ -174,14 +178,14 @@ class IPAdapterPlus(IPAdapter): class IPAdapterFull(IPAdapterPlus): """IP-Adapter Plus with full features.""" - def _init_image_proj_model(self, state_dict: dict[torch.Tensor]): + def _init_image_proj_model(self, state_dict: dict[str, torch.Tensor]): return MLPProjModel.from_state_dict(state_dict).to(self.device, dtype=self.dtype) class IPAdapterPlusXL(IPAdapterPlus): """IP-Adapter Plus for SDXL.""" - def _init_image_proj_model(self, state_dict): + def _init_image_proj_model(self, state_dict: dict[str, torch.Tensor]): return Resampler.from_state_dict( state_dict=state_dict, depth=4, @@ -195,7 +199,19 @@ class IPAdapterPlusXL(IPAdapterPlus): def build_ip_adapter( ip_adapter_ckpt_path: str, device: torch.device, dtype: torch.dtype = torch.float16 ) -> Union[IPAdapter, IPAdapterPlus]: - state_dict = torch.load(ip_adapter_ckpt_path, map_location="cpu") + state_dict: IPAdapterStateDict = {"ip_adapter": {}, "image_proj": {}} + + if ip_adapter_ckpt_path.endswith("safetensors"): + state_dict = {"ip_adapter": {}, "image_proj": {}} + model = safe_open(ip_adapter_ckpt_path, device=device.type, framework="pt") + for key in model.keys(): + if key.startswith("image_proj."): + state_dict["image_proj"][key.replace("image_proj.", "")] = model.get_tensor(key) + if key.startswith("ip_adapter."): + state_dict["ip_adapter"][key.replace("ip_adapter.", "")] = model.get_tensor(key) + else: + ip_adapter_diffusers_checkpoint_path = ip_adapter_ckpt_path + "/ip_adapter.bin" + state_dict = torch.load(ip_adapter_diffusers_checkpoint_path, map_location="cpu") if "proj.weight" in state_dict["image_proj"]: # IPAdapter (with ImageProjModel). return IPAdapter(state_dict, device=device, dtype=dtype) diff --git a/invokeai/backend/ip_adapter/resampler.py b/invokeai/backend/ip_adapter/resampler.py index a8db22c0fd..a32eeacfdc 100644 --- a/invokeai/backend/ip_adapter/resampler.py +++ b/invokeai/backend/ip_adapter/resampler.py @@ -9,8 +9,8 @@ import torch.nn as nn # FFN -def FeedForward(dim, mult=4): - inner_dim = int(dim * mult) +def FeedForward(dim: int, mult: int = 4): + inner_dim = dim * mult return nn.Sequential( nn.LayerNorm(dim), nn.Linear(dim, inner_dim, bias=False), @@ -19,8 +19,8 @@ def FeedForward(dim, mult=4): ) -def reshape_tensor(x, heads): - bs, length, width = x.shape +def reshape_tensor(x: torch.Tensor, heads: int): + bs, length, _ = x.shape # (bs, length, width) --> (bs, length, n_heads, dim_per_head) x = x.view(bs, length, heads, -1) # (bs, length, n_heads, dim_per_head) --> (bs, n_heads, length, dim_per_head) @@ -31,7 +31,7 @@ def reshape_tensor(x, heads): class PerceiverAttention(nn.Module): - def __init__(self, *, dim, dim_head=64, heads=8): + def __init__(self, *, dim: int, dim_head: int = 64, heads: int = 8): super().__init__() self.scale = dim_head**-0.5 self.dim_head = dim_head @@ -45,7 +45,7 @@ class PerceiverAttention(nn.Module): self.to_kv = nn.Linear(dim, inner_dim * 2, bias=False) self.to_out = nn.Linear(inner_dim, dim, bias=False) - def forward(self, x, latents): + def forward(self, x: torch.Tensor, latents: torch.Tensor): """ Args: x (torch.Tensor): image features @@ -80,14 +80,14 @@ class PerceiverAttention(nn.Module): class Resampler(nn.Module): def __init__( self, - dim=1024, - depth=8, - dim_head=64, - heads=16, - num_queries=8, - embedding_dim=768, - output_dim=1024, - ff_mult=4, + dim: int = 1024, + depth: int = 8, + dim_head: int = 64, + heads: int = 16, + num_queries: int = 8, + embedding_dim: int = 768, + output_dim: int = 1024, + ff_mult: int = 4, ): super().__init__() @@ -110,7 +110,15 @@ class Resampler(nn.Module): ) @classmethod - def from_state_dict(cls, state_dict: dict[torch.Tensor], depth=8, dim_head=64, heads=16, num_queries=8, ff_mult=4): + def from_state_dict( + cls, + state_dict: dict[str, torch.Tensor], + depth: int = 8, + dim_head: int = 64, + heads: int = 16, + num_queries: int = 8, + ff_mult: int = 4, + ): """A convenience function that initializes a Resampler from a state_dict. Some of the shape parameters are inferred from the state_dict (e.g. dim, embedding_dim, etc.). At the time of @@ -145,7 +153,7 @@ class Resampler(nn.Module): model.load_state_dict(state_dict) return model - def forward(self, x): + def forward(self, x: torch.Tensor): latents = self.latents.repeat(x.size(0), 1, 1) x = self.proj_in(x) diff --git a/invokeai/backend/model_manager/config.py b/invokeai/backend/model_manager/config.py index 524e39b2a1..172045d3fc 100644 --- a/invokeai/backend/model_manager/config.py +++ b/invokeai/backend/model_manager/config.py @@ -323,10 +323,13 @@ class MainDiffusersConfig(DiffusersConfigBase, MainConfigBase): return Tag(f"{ModelType.Main.value}.{ModelFormat.Diffusers.value}") -class IPAdapterConfig(ModelConfigBase): - """Model config for IP Adaptor format models.""" - +class IPAdapterBaseConfig(ModelConfigBase): type: Literal[ModelType.IPAdapter] = ModelType.IPAdapter + + +class IPAdapterDiffusersConfig(IPAdapterBaseConfig): + """Model config for IP Adapter diffusers format models.""" + image_encoder_model_id: str format: Literal[ModelFormat.InvokeAI] @@ -335,6 +338,16 @@ class IPAdapterConfig(ModelConfigBase): return Tag(f"{ModelType.IPAdapter.value}.{ModelFormat.InvokeAI.value}") +class IPAdapterCheckpointConfig(IPAdapterBaseConfig): + """Model config for IP Adapter checkpoint format models.""" + + format: Literal[ModelFormat.Checkpoint] + + @staticmethod + def get_tag() -> Tag: + return Tag(f"{ModelType.IPAdapter.value}.{ModelFormat.Checkpoint.value}") + + class CLIPVisionDiffusersConfig(DiffusersConfigBase): """Model config for CLIPVision.""" @@ -390,7 +403,8 @@ AnyModelConfig = Annotated[ Annotated[LoRADiffusersConfig, LoRADiffusersConfig.get_tag()], Annotated[TextualInversionFileConfig, TextualInversionFileConfig.get_tag()], Annotated[TextualInversionFolderConfig, TextualInversionFolderConfig.get_tag()], - Annotated[IPAdapterConfig, IPAdapterConfig.get_tag()], + Annotated[IPAdapterDiffusersConfig, IPAdapterDiffusersConfig.get_tag()], + Annotated[IPAdapterCheckpointConfig, IPAdapterCheckpointConfig.get_tag()], Annotated[T2IAdapterConfig, T2IAdapterConfig.get_tag()], Annotated[CLIPVisionDiffusersConfig, CLIPVisionDiffusersConfig.get_tag()], ], diff --git a/invokeai/backend/model_manager/load/model_loaders/ip_adapter.py b/invokeai/backend/model_manager/load/model_loaders/ip_adapter.py index 89dd46e929..414d2c7247 100644 --- a/invokeai/backend/model_manager/load/model_loaders/ip_adapter.py +++ b/invokeai/backend/model_manager/load/model_loaders/ip_adapter.py @@ -7,19 +7,13 @@ from typing import Optional import torch from invokeai.backend.ip_adapter.ip_adapter import build_ip_adapter -from invokeai.backend.model_manager import ( - AnyModel, - AnyModelConfig, - BaseModelType, - ModelFormat, - ModelType, - SubModelType, -) +from invokeai.backend.model_manager import AnyModel, AnyModelConfig, BaseModelType, ModelFormat, ModelType, SubModelType from invokeai.backend.model_manager.load import ModelLoader, ModelLoaderRegistry from invokeai.backend.raw_model import RawModel @ModelLoaderRegistry.register(base=BaseModelType.Any, type=ModelType.IPAdapter, format=ModelFormat.InvokeAI) +@ModelLoaderRegistry.register(base=BaseModelType.Any, type=ModelType.IPAdapter, format=ModelFormat.Checkpoint) class IPAdapterInvokeAILoader(ModelLoader): """Class to load IP Adapter diffusers models.""" @@ -32,7 +26,7 @@ class IPAdapterInvokeAILoader(ModelLoader): raise ValueError("There are no submodels in an IP-Adapter model.") model_path = Path(config.path) model: RawModel = build_ip_adapter( - ip_adapter_ckpt_path=str(model_path / "ip_adapter.bin"), + ip_adapter_ckpt_path=str(model_path), device=torch.device("cpu"), dtype=self._torch_dtype, ) diff --git a/invokeai/backend/model_manager/probe.py b/invokeai/backend/model_manager/probe.py index ddd9e99eda..ed73fc56c6 100644 --- a/invokeai/backend/model_manager/probe.py +++ b/invokeai/backend/model_manager/probe.py @@ -230,9 +230,10 @@ class ModelProbe(object): return ModelType.LoRA elif any(key.startswith(v) for v in {"controlnet", "control_model", "input_blocks"}): return ModelType.ControlNet + elif any(key.startswith(v) for v in {"image_proj.", "ip_adapter."}): + return ModelType.IPAdapter elif key in {"emb_params", "string_to_param"}: return ModelType.TextualInversion - else: # diffusers-ti if len(ckpt) < 10 and all(isinstance(v, torch.Tensor) for v in ckpt.values()): @@ -527,8 +528,15 @@ class ControlNetCheckpointProbe(CheckpointProbeBase): class IPAdapterCheckpointProbe(CheckpointProbeBase): + """Class for probing IP Adapters""" + def get_base_type(self) -> BaseModelType: - raise NotImplementedError() + checkpoint = self.checkpoint + for key in checkpoint.keys(): + if not key.startswith(("image_proj.", "ip_adapter.")): + continue + return BaseModelType.StableDiffusionXL + raise InvalidModelConfigException(f"{self.model_path}: Unable to determine base type") class CLIPVisionCheckpointProbe(CheckpointProbeBase): @@ -689,9 +697,7 @@ class ControlNetFolderProbe(FolderProbeBase): else ( BaseModelType.StableDiffusion2 if dimension == 1024 - else BaseModelType.StableDiffusionXL - if dimension == 2048 - else None + else BaseModelType.StableDiffusionXL if dimension == 2048 else None ) ) if not base_model: @@ -768,7 +774,7 @@ class T2IAdapterFolderProbe(FolderProbeBase): ) -############## register probe classes ###### +# Register probe classes ModelProbe.register_probe("diffusers", ModelType.Main, PipelineFolderProbe) ModelProbe.register_probe("diffusers", ModelType.VAE, VaeFolderProbe) ModelProbe.register_probe("diffusers", ModelType.LoRA, LoRAFolderProbe) From b1c8266e2254673fdc2de3a6cbabca5715bf9158 Mon Sep 17 00:00:00 2001 From: blessedcoolant <54517381+blessedcoolant@users.noreply.github.com> Date: Sun, 24 Mar 2024 01:58:46 +0530 Subject: [PATCH 16/89] feat: add base model recognition for ip adapter safetensor files --- invokeai/app/invocations/latent.py | 61 +++++++++---------------- invokeai/backend/model_manager/probe.py | 13 +++++- 2 files changed, 34 insertions(+), 40 deletions(-) diff --git a/invokeai/app/invocations/latent.py b/invokeai/app/invocations/latent.py index bc79efdeba..8ad1684bcb 100644 --- a/invokeai/app/invocations/latent.py +++ b/invokeai/app/invocations/latent.py @@ -14,12 +14,10 @@ from diffusers import AutoencoderKL, AutoencoderTiny from diffusers.configuration_utils import ConfigMixin from diffusers.image_processor import VaeImageProcessor from diffusers.models.adapter import T2IAdapter -from diffusers.models.attention_processor import ( - AttnProcessor2_0, - LoRAAttnProcessor2_0, - LoRAXFormersAttnProcessor, - XFormersAttnProcessor, -) +from diffusers.models.attention_processor import (AttnProcessor2_0, + LoRAAttnProcessor2_0, + LoRAXFormersAttnProcessor, + XFormersAttnProcessor) from diffusers.models.unets.unet_2d_condition import UNet2DConditionModel from diffusers.schedulers import DPMSolverSDEScheduler from diffusers.schedulers import SchedulerMixin as Scheduler @@ -28,26 +26,17 @@ from pydantic import field_validator from torchvision.transforms.functional import resize as tv_resize from transformers import CLIPVisionModelWithProjection -from invokeai.app.invocations.constants import LATENT_SCALE_FACTOR, SCHEDULER_NAME_VALUES -from invokeai.app.invocations.fields import ( - ConditioningField, - DenoiseMaskField, - FieldDescriptions, - ImageField, - Input, - InputField, - LatentsField, - OutputField, - UIType, - WithBoard, - WithMetadata, -) +from invokeai.app.invocations.constants import (LATENT_SCALE_FACTOR, + SCHEDULER_NAME_VALUES) +from invokeai.app.invocations.fields import (ConditioningField, + DenoiseMaskField, + FieldDescriptions, ImageField, + Input, InputField, LatentsField, + OutputField, UIType, WithBoard, + WithMetadata) from invokeai.app.invocations.ip_adapter import IPAdapterField -from invokeai.app.invocations.primitives import ( - DenoiseMaskOutput, - ImageOutput, - LatentsOutput, -) +from invokeai.app.invocations.primitives import (DenoiseMaskOutput, + ImageOutput, LatentsOutput) from invokeai.app.invocations.t2i_adapter import T2IAdapterField from invokeai.app.services.shared.invocation_context import InvocationContext from invokeai.app.util.controlnet_utils import prepare_control_image @@ -55,25 +44,19 @@ from invokeai.backend.ip_adapter.ip_adapter import IPAdapter, IPAdapterPlus from invokeai.backend.lora import LoRAModelRaw from invokeai.backend.model_manager import BaseModelType, LoadedModel from invokeai.backend.model_patcher import ModelPatcher -from invokeai.backend.stable_diffusion import PipelineIntermediateState, set_seamless -from invokeai.backend.stable_diffusion.diffusion.conditioning_data import ConditioningData, IPAdapterConditioningInfo +from invokeai.backend.stable_diffusion import (PipelineIntermediateState, + set_seamless) +from invokeai.backend.stable_diffusion.diffusion.conditioning_data import ( + ConditioningData, IPAdapterConditioningInfo) from invokeai.backend.util.silence_warnings import SilenceWarnings from ...backend.stable_diffusion.diffusers_pipeline import ( - ControlNetData, - IPAdapterData, - StableDiffusionGeneratorPipeline, - T2IAdapterData, - image_resized_to_grid_as_tensor, -) + ControlNetData, IPAdapterData, StableDiffusionGeneratorPipeline, + T2IAdapterData, image_resized_to_grid_as_tensor) from ...backend.stable_diffusion.schedulers import SCHEDULER_MAP from ...backend.util.devices import choose_precision, choose_torch_device -from .baseinvocation import ( - BaseInvocation, - BaseInvocationOutput, - invocation, - invocation_output, -) +from .baseinvocation import (BaseInvocation, BaseInvocationOutput, invocation, + invocation_output) from .controlnet_image_processors import ControlField from .model import ModelIdentifierField, UNetField, VAEField diff --git a/invokeai/backend/model_manager/probe.py b/invokeai/backend/model_manager/probe.py index ed73fc56c6..bd47cc1a48 100644 --- a/invokeai/backend/model_manager/probe.py +++ b/invokeai/backend/model_manager/probe.py @@ -535,7 +535,18 @@ class IPAdapterCheckpointProbe(CheckpointProbeBase): for key in checkpoint.keys(): if not key.startswith(("image_proj.", "ip_adapter.")): continue - return BaseModelType.StableDiffusionXL + cross_attention_dim = checkpoint["ip_adapter.1.to_k_ip.weight"].shape[-1] + print(cross_attention_dim) + if cross_attention_dim == 768: + return BaseModelType.StableDiffusion1 + elif cross_attention_dim == 1024: + return BaseModelType.StableDiffusion2 + elif cross_attention_dim == 2048: + return BaseModelType.StableDiffusionXL + else: + raise InvalidModelConfigException( + f"IP-Adapter had unexpected cross-attention dimension: {cross_attention_dim}." + ) raise InvalidModelConfigException(f"{self.model_path}: Unable to determine base type") From 79f7b61dfe16611209f05197e4ab335198991044 Mon Sep 17 00:00:00 2001 From: blessedcoolant <54517381+blessedcoolant@users.noreply.github.com> Date: Sun, 24 Mar 2024 02:27:38 +0530 Subject: [PATCH 17/89] fix: cleanup across various ip adapter files --- invokeai/app/invocations/ip_adapter.py | 2 +- invokeai/app/invocations/latent.py | 52 +++++++++++--------- invokeai/backend/model_manager/probe.py | 24 +++------ invokeai/frontend/web/public/locales/en.json | 1 + 4 files changed, 40 insertions(+), 39 deletions(-) diff --git a/invokeai/app/invocations/ip_adapter.py b/invokeai/app/invocations/ip_adapter.py index 165a6bee24..94bb909433 100644 --- a/invokeai/app/invocations/ip_adapter.py +++ b/invokeai/app/invocations/ip_adapter.py @@ -91,7 +91,7 @@ class IPAdapterInvocation(BaseInvocation): image_encoder_model_id = ( ip_adapter_info.image_encoder_model_id if isinstance(ip_adapter_info, IPAdapterDiffusersConfig) - else "InvokeAI/ip_adapter_sd_image_encoder" + else "ip_adapter_sd_image_encoder" ) image_encoder_model_name = image_encoder_model_id.split("/")[-1].strip() image_encoder_model = self._get_image_encoder(context, image_encoder_model_name) diff --git a/invokeai/app/invocations/latent.py b/invokeai/app/invocations/latent.py index 8ad1684bcb..3c66b7014f 100644 --- a/invokeai/app/invocations/latent.py +++ b/invokeai/app/invocations/latent.py @@ -14,10 +14,12 @@ from diffusers import AutoencoderKL, AutoencoderTiny from diffusers.configuration_utils import ConfigMixin from diffusers.image_processor import VaeImageProcessor from diffusers.models.adapter import T2IAdapter -from diffusers.models.attention_processor import (AttnProcessor2_0, - LoRAAttnProcessor2_0, - LoRAXFormersAttnProcessor, - XFormersAttnProcessor) +from diffusers.models.attention_processor import ( + AttnProcessor2_0, + LoRAAttnProcessor2_0, + LoRAXFormersAttnProcessor, + XFormersAttnProcessor, +) from diffusers.models.unets.unet_2d_condition import UNet2DConditionModel from diffusers.schedulers import DPMSolverSDEScheduler from diffusers.schedulers import SchedulerMixin as Scheduler @@ -26,17 +28,22 @@ from pydantic import field_validator from torchvision.transforms.functional import resize as tv_resize from transformers import CLIPVisionModelWithProjection -from invokeai.app.invocations.constants import (LATENT_SCALE_FACTOR, - SCHEDULER_NAME_VALUES) -from invokeai.app.invocations.fields import (ConditioningField, - DenoiseMaskField, - FieldDescriptions, ImageField, - Input, InputField, LatentsField, - OutputField, UIType, WithBoard, - WithMetadata) +from invokeai.app.invocations.constants import LATENT_SCALE_FACTOR, SCHEDULER_NAME_VALUES +from invokeai.app.invocations.fields import ( + ConditioningField, + DenoiseMaskField, + FieldDescriptions, + ImageField, + Input, + InputField, + LatentsField, + OutputField, + UIType, + WithBoard, + WithMetadata, +) from invokeai.app.invocations.ip_adapter import IPAdapterField -from invokeai.app.invocations.primitives import (DenoiseMaskOutput, - ImageOutput, LatentsOutput) +from invokeai.app.invocations.primitives import DenoiseMaskOutput, ImageOutput, LatentsOutput from invokeai.app.invocations.t2i_adapter import T2IAdapterField from invokeai.app.services.shared.invocation_context import InvocationContext from invokeai.app.util.controlnet_utils import prepare_control_image @@ -44,19 +51,20 @@ from invokeai.backend.ip_adapter.ip_adapter import IPAdapter, IPAdapterPlus from invokeai.backend.lora import LoRAModelRaw from invokeai.backend.model_manager import BaseModelType, LoadedModel from invokeai.backend.model_patcher import ModelPatcher -from invokeai.backend.stable_diffusion import (PipelineIntermediateState, - set_seamless) -from invokeai.backend.stable_diffusion.diffusion.conditioning_data import ( - ConditioningData, IPAdapterConditioningInfo) +from invokeai.backend.stable_diffusion import PipelineIntermediateState, set_seamless +from invokeai.backend.stable_diffusion.diffusion.conditioning_data import ConditioningData, IPAdapterConditioningInfo from invokeai.backend.util.silence_warnings import SilenceWarnings from ...backend.stable_diffusion.diffusers_pipeline import ( - ControlNetData, IPAdapterData, StableDiffusionGeneratorPipeline, - T2IAdapterData, image_resized_to_grid_as_tensor) + ControlNetData, + IPAdapterData, + StableDiffusionGeneratorPipeline, + T2IAdapterData, + image_resized_to_grid_as_tensor, +) from ...backend.stable_diffusion.schedulers import SCHEDULER_MAP from ...backend.util.devices import choose_precision, choose_torch_device -from .baseinvocation import (BaseInvocation, BaseInvocationOutput, invocation, - invocation_output) +from .baseinvocation import BaseInvocation, BaseInvocationOutput, invocation, invocation_output from .controlnet_image_processors import ControlField from .model import ModelIdentifierField, UNetField, VAEField diff --git a/invokeai/backend/model_manager/probe.py b/invokeai/backend/model_manager/probe.py index bd47cc1a48..75f156ce21 100644 --- a/invokeai/backend/model_manager/probe.py +++ b/invokeai/backend/model_manager/probe.py @@ -9,23 +9,16 @@ from picklescan.scanner import scan_file_path import invokeai.backend.util.logging as logger from invokeai.app.util.misc import uuid_string -from invokeai.backend.model_hash.model_hash import HASHING_ALGORITHMS, ModelHash +from invokeai.backend.model_hash.model_hash import (HASHING_ALGORITHMS, + ModelHash) from invokeai.backend.util.util import SilenceWarnings -from .config import ( - AnyModelConfig, - BaseModelType, - ControlAdapterDefaultSettings, - InvalidModelConfigException, - MainModelDefaultSettings, - ModelConfigFactory, - ModelFormat, - ModelRepoVariant, - ModelSourceType, - ModelType, - ModelVariantType, - SchedulerPredictionType, -) +from .config import (AnyModelConfig, BaseModelType, + ControlAdapterDefaultSettings, + InvalidModelConfigException, MainModelDefaultSettings, + ModelConfigFactory, ModelFormat, ModelRepoVariant, + ModelSourceType, ModelType, ModelVariantType, + SchedulerPredictionType) from .util.model_util import lora_token_vector_length, read_checkpoint_meta CkptType = Dict[str | int, Any] @@ -536,7 +529,6 @@ class IPAdapterCheckpointProbe(CheckpointProbeBase): if not key.startswith(("image_proj.", "ip_adapter.")): continue cross_attention_dim = checkpoint["ip_adapter.1.to_k_ip.weight"].shape[-1] - print(cross_attention_dim) if cross_attention_dim == 768: return BaseModelType.StableDiffusion1 elif cross_attention_dim == 1024: diff --git a/invokeai/frontend/web/public/locales/en.json b/invokeai/frontend/web/public/locales/en.json index 1601169e03..d2402c61be 100644 --- a/invokeai/frontend/web/public/locales/en.json +++ b/invokeai/frontend/web/public/locales/en.json @@ -655,6 +655,7 @@ "install": "Install", "installAll": "Install All", "installRepo": "Install Repo", + "ipAdapters": "IP Adapters", "load": "Load", "localOnly": "local only", "manual": "Manual", From 5829b87b8d0196a6038b7e0cee382cd175586751 Mon Sep 17 00:00:00 2001 From: blessedcoolant <54517381+blessedcoolant@users.noreply.github.com> Date: Sun, 24 Mar 2024 02:58:57 +0530 Subject: [PATCH 18/89] ui: update the new ip adapter configs on the frontend --- invokeai/app/invocations/ip_adapter.py | 2 ++ invokeai/frontend/web/src/services/api/types.ts | 2 +- 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/invokeai/app/invocations/ip_adapter.py b/invokeai/app/invocations/ip_adapter.py index 94bb909433..2874c92701 100644 --- a/invokeai/app/invocations/ip_adapter.py +++ b/invokeai/app/invocations/ip_adapter.py @@ -88,6 +88,7 @@ class IPAdapterInvocation(BaseInvocation): # Lookup the CLIP Vision encoder that is intended to be used with the IP-Adapter model. ip_adapter_info = context.models.get_config(self.ip_adapter_model.key) assert isinstance(ip_adapter_info, (IPAdapterDiffusersConfig, IPAdapterCheckpointConfig)) + image_encoder_model_id = ( ip_adapter_info.image_encoder_model_id if isinstance(ip_adapter_info, IPAdapterDiffusersConfig) @@ -95,6 +96,7 @@ class IPAdapterInvocation(BaseInvocation): ) image_encoder_model_name = image_encoder_model_id.split("/")[-1].strip() image_encoder_model = self._get_image_encoder(context, image_encoder_model_name) + return IPAdapterOutput( ip_adapter=IPAdapterField( image=self.image, diff --git a/invokeai/frontend/web/src/services/api/types.ts b/invokeai/frontend/web/src/services/api/types.ts index 0bc141960d..1c7e816d16 100644 --- a/invokeai/frontend/web/src/services/api/types.ts +++ b/invokeai/frontend/web/src/services/api/types.ts @@ -46,7 +46,7 @@ export type LoRAModelConfig = S['LoRADiffusersConfig'] | S['LoRALyCORISConfig']; // TODO(MM2): Can we rename this from Vae -> VAE export type VAEModelConfig = S['VAECheckpointConfig'] | S['VAEDiffusersConfig']; export type ControlNetModelConfig = S['ControlNetDiffusersConfig'] | S['ControlNetCheckpointConfig']; -export type IPAdapterModelConfig = S['IPAdapterConfig']; +export type IPAdapterModelConfig = S['IPAdapterDiffusersConfig'] | S['IPAdapterCheckpointConfig']; export type T2IAdapterModelConfig = S['T2IAdapterConfig']; type TextualInversionModelConfig = S['TextualInversionFileConfig'] | S['TextualInversionFolderConfig']; type DiffusersModelConfig = S['MainDiffusersConfig']; From 9ff729a7e6ca0ea2881189b6486bb2dcfbd4c113 Mon Sep 17 00:00:00 2001 From: blessedcoolant <54517381+blessedcoolant@users.noreply.github.com> Date: Sun, 24 Mar 2024 03:06:41 +0530 Subject: [PATCH 19/89] fix: Update ModelView to accommodate for the new config changes to IP Adapter --- .../features/modelManagerV2/subpanels/ModelPanel/ModelView.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/invokeai/frontend/web/src/features/modelManagerV2/subpanels/ModelPanel/ModelView.tsx b/invokeai/frontend/web/src/features/modelManagerV2/subpanels/ModelPanel/ModelView.tsx index adb123f24d..0618af5dd0 100644 --- a/invokeai/frontend/web/src/features/modelManagerV2/subpanels/ModelPanel/ModelView.tsx +++ b/invokeai/frontend/web/src/features/modelManagerV2/subpanels/ModelPanel/ModelView.tsx @@ -53,7 +53,7 @@ export const ModelView = () => { )} - {data.type === 'ip_adapter' && ( + {data.type === 'ip_adapter' && data.format === 'invokeai' && ( From 936b99bd3c77e15af723e65b4e21a7f72d2d6fcd Mon Sep 17 00:00:00 2001 From: blessedcoolant <54517381+blessedcoolant@users.noreply.github.com> Date: Sun, 24 Mar 2024 08:34:11 +0530 Subject: [PATCH 20/89] chore: improve types in ip_adapter backend file --- invokeai/backend/ip_adapter/ip_adapter.py | 46 ++++++++++++++--------- 1 file changed, 29 insertions(+), 17 deletions(-) diff --git a/invokeai/backend/ip_adapter/ip_adapter.py b/invokeai/backend/ip_adapter/ip_adapter.py index 81514a9f8b..5444c76c8c 100644 --- a/invokeai/backend/ip_adapter/ip_adapter.py +++ b/invokeai/backend/ip_adapter/ip_adapter.py @@ -53,7 +53,7 @@ class ImageProjModel(torch.nn.Module): model.load_state_dict(state_dict) return model - def forward(self, image_embeds): + def forward(self, image_embeds: torch.Tensor): embeds = image_embeds clip_extra_context_tokens = self.proj(embeds).reshape( -1, self.clip_extra_context_tokens, self.cross_attention_dim @@ -95,7 +95,7 @@ class MLPProjModel(torch.nn.Module): model.load_state_dict(state_dict) return model - def forward(self, image_embeds): + def forward(self, image_embeds: torch.Tensor): clip_extra_context_tokens = self.proj(image_embeds) return clip_extra_context_tokens @@ -137,7 +137,9 @@ class IPAdapter(RawModel): return calc_model_size_by_data(self._image_proj_model) + calc_model_size_by_data(self.attn_weights) - def _init_image_proj_model(self, state_dict: dict[str, torch.Tensor]): + def _init_image_proj_model( + self, state_dict: dict[str, torch.Tensor] + ) -> Union[ImageProjModel, Resampler, MLPProjModel]: return ImageProjModel.from_state_dict(state_dict, self._num_tokens).to(self.device, dtype=self.dtype) @torch.inference_mode() @@ -152,7 +154,7 @@ class IPAdapter(RawModel): class IPAdapterPlus(IPAdapter): """IP-Adapter with fine-grained features""" - def _init_image_proj_model(self, state_dict: dict[str, torch.Tensor]): + def _init_image_proj_model(self, state_dict: dict[str, torch.Tensor]) -> Union[Resampler, MLPProjModel]: return Resampler.from_state_dict( state_dict=state_dict, depth=4, @@ -196,36 +198,46 @@ class IPAdapterPlusXL(IPAdapterPlus): ).to(self.device, dtype=self.dtype) -def build_ip_adapter( - ip_adapter_ckpt_path: str, device: torch.device, dtype: torch.dtype = torch.float16 -) -> Union[IPAdapter, IPAdapterPlus]: +def load_ip_adapter_tensors(ip_adapter_ckpt_path: str, device: str) -> IPAdapterStateDict: state_dict: IPAdapterStateDict = {"ip_adapter": {}, "image_proj": {}} if ip_adapter_ckpt_path.endswith("safetensors"): - state_dict = {"ip_adapter": {}, "image_proj": {}} - model = safe_open(ip_adapter_ckpt_path, device=device.type, framework="pt") + model = safe_open(ip_adapter_ckpt_path, device=device, framework="pt") for key in model.keys(): if key.startswith("image_proj."): state_dict["image_proj"][key.replace("image_proj.", "")] = model.get_tensor(key) - if key.startswith("ip_adapter."): + elif key.startswith("ip_adapter."): state_dict["ip_adapter"][key.replace("ip_adapter.", "")] = model.get_tensor(key) else: ip_adapter_diffusers_checkpoint_path = ip_adapter_ckpt_path + "/ip_adapter.bin" state_dict = torch.load(ip_adapter_diffusers_checkpoint_path, map_location="cpu") - if "proj.weight" in state_dict["image_proj"]: # IPAdapter (with ImageProjModel). + return state_dict + + +def build_ip_adapter( + ip_adapter_ckpt_path: str, device: torch.device, dtype: torch.dtype = torch.float16 +) -> Union[IPAdapter, IPAdapterPlus, IPAdapterPlusXL, IPAdapterPlus]: + state_dict = load_ip_adapter_tensors(ip_adapter_ckpt_path, device.type) + + # IPAdapter (with ImageProjModel) + if "proj.weight" in state_dict["image_proj"]: return IPAdapter(state_dict, device=device, dtype=dtype) - elif "proj_in.weight" in state_dict["image_proj"]: # IPAdaterPlus or IPAdapterPlusXL (with Resampler). + + # IPAdaterPlus or IPAdapterPlusXL (with Resampler) + elif "proj_in.weight" in state_dict["image_proj"]: cross_attention_dim = state_dict["ip_adapter"]["1.to_k_ip.weight"].shape[-1] if cross_attention_dim == 768: - # SD1 IP-Adapter Plus - return IPAdapterPlus(state_dict, device=device, dtype=dtype) + return IPAdapterPlus(state_dict, device=device, dtype=dtype) # SD1 IP-Adapter Plus elif cross_attention_dim == 2048: - # SDXL IP-Adapter Plus - return IPAdapterPlusXL(state_dict, device=device, dtype=dtype) + return IPAdapterPlusXL(state_dict, device=device, dtype=dtype) # SDXL IP-Adapter Plus else: raise Exception(f"Unsupported IP-Adapter Plus cross-attention dimension: {cross_attention_dim}.") - elif "proj.0.weight" in state_dict["image_proj"]: # IPAdapterFull (with MLPProjModel). + + # IPAdapterFull (with MLPProjModel) + elif "proj.0.weight" in state_dict["image_proj"]: return IPAdapterFull(state_dict, device=device, dtype=dtype) + + # Unrecognized IP Adapter Architectures else: raise ValueError(f"'{ip_adapter_ckpt_path}' has an unrecognized IP-Adapter model architecture.") From 91a70c8d0702fc2bdca92e37b3793dc947337987 Mon Sep 17 00:00:00 2001 From: blessedcoolant <54517381+blessedcoolant@users.noreply.github.com> Date: Wed, 27 Mar 2024 20:32:41 +0530 Subject: [PATCH 21/89] feat: Let users pick CLIP Vision model for Checkpoint IP Adapters --- invokeai/app/invocations/ip_adapter.py | 53 +++++++++------ invokeai/app/invocations/metadata.py | 13 +--- invokeai/frontend/web/public/locales/en.json | 1 + .../parameters/ParamControlAdapterModel.tsx | 64 +++++++++++++++---- .../hooks/useControlAdapterCLIPVisionModel.ts | 24 +++++++ .../store/controlAdaptersSlice.ts | 9 +++ .../features/controlAdapters/store/types.ts | 3 + .../util/buildControlAdapter.ts | 1 + .../util/graph/addIPAdapterToLinearGraph.ts | 6 +- 9 files changed, 130 insertions(+), 44 deletions(-) create mode 100644 invokeai/frontend/web/src/features/controlAdapters/hooks/useControlAdapterCLIPVisionModel.ts diff --git a/invokeai/app/invocations/ip_adapter.py b/invokeai/app/invocations/ip_adapter.py index 2874c92701..603a85148d 100644 --- a/invokeai/app/invocations/ip_adapter.py +++ b/invokeai/app/invocations/ip_adapter.py @@ -1,5 +1,5 @@ from builtins import float -from typing import List, Union +from typing import List, Literal, Union from pydantic import BaseModel, Field, field_validator, model_validator from typing_extensions import Self @@ -49,12 +49,15 @@ class IPAdapterOutput(BaseInvocationOutput): ip_adapter: IPAdapterField = OutputField(description=FieldDescriptions.ip_adapter, title="IP-Adapter") +CLIP_VISION_MODEL_MAP = {"ViT-H": "ip_adapter_sd_image_encoder", "ViT-G": "ip_adapter_sdxl_image_encoder"} + + @invocation("ip_adapter", title="IP-Adapter", tags=["ip_adapter", "control"], category="ip_adapter", version="1.2.2") class IPAdapterInvocation(BaseInvocation): """Collects IP-Adapter info to pass to other nodes.""" # Inputs - image: Union[ImageField, List[ImageField]] = InputField(description="The IP-Adapter image prompt(s).") + image: Union[ImageField, List[ImageField]] = InputField(description="The IP-Adapter image prompt(s).", ui_order=1) ip_adapter_model: ModelIdentifierField = InputField( description="The IP-Adapter model.", title="IP-Adapter Model", @@ -62,7 +65,9 @@ class IPAdapterInvocation(BaseInvocation): ui_order=-1, ui_type=UIType.IPAdapterModel, ) - + clip_vision_model: Literal["ViT-H", "ViT-G"] = InputField( + description="CLIP Vision model to use", default="ViT-H", ui_order=2 + ) weight: Union[float, List[float]] = InputField( default=1, description="The weight given to the IP-Adapter", title="Weight" ) @@ -89,12 +94,12 @@ class IPAdapterInvocation(BaseInvocation): ip_adapter_info = context.models.get_config(self.ip_adapter_model.key) assert isinstance(ip_adapter_info, (IPAdapterDiffusersConfig, IPAdapterCheckpointConfig)) - image_encoder_model_id = ( - ip_adapter_info.image_encoder_model_id - if isinstance(ip_adapter_info, IPAdapterDiffusersConfig) - else "ip_adapter_sd_image_encoder" - ) - image_encoder_model_name = image_encoder_model_id.split("/")[-1].strip() + if isinstance(ip_adapter_info, IPAdapterDiffusersConfig): + image_encoder_model_id = ip_adapter_info.image_encoder_model_id + image_encoder_model_name = image_encoder_model_id.split("/")[-1].strip() + else: + image_encoder_model_name = CLIP_VISION_MODEL_MAP[self.clip_vision_model] + image_encoder_model = self._get_image_encoder(context, image_encoder_model_name) return IPAdapterOutput( @@ -109,19 +114,25 @@ class IPAdapterInvocation(BaseInvocation): ) def _get_image_encoder(self, context: InvocationContext, image_encoder_model_name: str) -> AnyModelConfig: - found = False - while not found: + image_encoder_models = context.models.search_by_attrs( + name=image_encoder_model_name, base=BaseModelType.Any, type=ModelType.CLIPVision + ) + + if not len(image_encoder_models) > 0: + context.logger.warning( + f"The image encoder required by this IP Adapter ({image_encoder_model_name}) is not installed. \ + Downloading and installing now. This may take a while." + ) + + installer = context._services.model_manager.install + job = installer.heuristic_import(f"InvokeAI/{image_encoder_model_name}") + installer.wait_for_job(job, timeout=600) # Wait for up to 10 minutes image_encoder_models = context.models.search_by_attrs( name=image_encoder_model_name, base=BaseModelType.Any, type=ModelType.CLIPVision ) - found = len(image_encoder_models) > 0 - if not found: - context.logger.warning( - f"The image encoder required by this IP Adapter ({image_encoder_model_name}) is not installed." - ) - context.logger.warning("Downloading and installing now. This may take a while.") - installer = context._services.model_manager.install - job = installer.heuristic_import(f"InvokeAI/{image_encoder_model_name}") - installer.wait_for_job(job, timeout=600) # wait up to 10 minutes - then raise a TimeoutException - assert len(image_encoder_models) == 1 + + if len(image_encoder_models) == 0: + context.logger.error("Error while fetching CLIP Vision Image Encoder") + assert len(image_encoder_models) == 1 + return image_encoder_models[0] diff --git a/invokeai/app/invocations/metadata.py b/invokeai/app/invocations/metadata.py index 6fc72a1c3f..2da482c833 100644 --- a/invokeai/app/invocations/metadata.py +++ b/invokeai/app/invocations/metadata.py @@ -2,16 +2,8 @@ from typing import Any, Literal, Optional, Union from pydantic import BaseModel, ConfigDict, Field -from invokeai.app.invocations.baseinvocation import ( - BaseInvocation, - BaseInvocationOutput, - invocation, - invocation_output, -) -from invokeai.app.invocations.controlnet_image_processors import ( - CONTROLNET_MODE_VALUES, - CONTROLNET_RESIZE_VALUES, -) +from invokeai.app.invocations.baseinvocation import BaseInvocation, BaseInvocationOutput, invocation, invocation_output +from invokeai.app.invocations.controlnet_image_processors import CONTROLNET_MODE_VALUES, CONTROLNET_RESIZE_VALUES from invokeai.app.invocations.fields import ( FieldDescriptions, ImageField, @@ -43,6 +35,7 @@ class IPAdapterMetadataField(BaseModel): image: ImageField = Field(description="The IP-Adapter image prompt.") ip_adapter_model: ModelIdentifierField = Field(description="The IP-Adapter model.") + clip_vision_model: Literal["ViT-H", "ViT-G"] = Field(description="The CLIP Vision model") weight: Union[float, list[float]] = Field(description="The weight given to the IP-Adapter") begin_step_percent: float = Field(description="When the IP-Adapter is first applied (% of total steps)") end_step_percent: float = Field(description="When the IP-Adapter is last applied (% of total steps)") diff --git a/invokeai/frontend/web/public/locales/en.json b/invokeai/frontend/web/public/locales/en.json index d2402c61be..5872d22dfe 100644 --- a/invokeai/frontend/web/public/locales/en.json +++ b/invokeai/frontend/web/public/locales/en.json @@ -217,6 +217,7 @@ "saveControlImage": "Save Control Image", "scribble": "scribble", "selectModel": "Select a model", + "selectCLIPVisionModel": "Select a CLIP Vision model", "setControlImageDimensions": "Set Control Image Dimensions To W/H", "showAdvanced": "Show Advanced", "small": "Small", diff --git a/invokeai/frontend/web/src/features/controlAdapters/components/parameters/ParamControlAdapterModel.tsx b/invokeai/frontend/web/src/features/controlAdapters/components/parameters/ParamControlAdapterModel.tsx index 25d327e54e..380a33185d 100644 --- a/invokeai/frontend/web/src/features/controlAdapters/components/parameters/ParamControlAdapterModel.tsx +++ b/invokeai/frontend/web/src/features/controlAdapters/components/parameters/ParamControlAdapterModel.tsx @@ -1,12 +1,18 @@ -import { Combobox, FormControl, Tooltip } from '@invoke-ai/ui-library'; +import type { ComboboxOnChange, ComboboxOption } from '@invoke-ai/ui-library'; +import { Combobox, Flex, FormControl, Tooltip } from '@invoke-ai/ui-library'; import { createMemoizedSelector } from 'app/store/createMemoizedSelector'; import { useAppDispatch, useAppSelector } from 'app/store/storeHooks'; import { useGroupedModelCombobox } from 'common/hooks/useGroupedModelCombobox'; +import { useControlAdapterCLIPVisionModel } from 'features/controlAdapters/hooks/useControlAdapterCLIPVisionModel'; import { useControlAdapterIsEnabled } from 'features/controlAdapters/hooks/useControlAdapterIsEnabled'; import { useControlAdapterModel } from 'features/controlAdapters/hooks/useControlAdapterModel'; import { useControlAdapterModels } from 'features/controlAdapters/hooks/useControlAdapterModels'; import { useControlAdapterType } from 'features/controlAdapters/hooks/useControlAdapterType'; -import { controlAdapterModelChanged } from 'features/controlAdapters/store/controlAdaptersSlice'; +import { + controlAdapterCLIPVisionModelChanged, + controlAdapterModelChanged, +} from 'features/controlAdapters/store/controlAdaptersSlice'; +import type { CLIPVisionModel } from 'features/controlAdapters/store/types'; import { selectGenerationSlice } from 'features/parameters/store/generationSlice'; import { memo, useCallback, useMemo } from 'react'; import { useTranslation } from 'react-i18next'; @@ -29,6 +35,7 @@ const ParamControlAdapterModel = ({ id }: ParamControlAdapterModelProps) => { const { modelConfig } = useControlAdapterModel(id); const dispatch = useAppDispatch(); const currentBaseModel = useAppSelector((s) => s.generation.model?.base); + const currentCLIPVisionModel = useControlAdapterCLIPVisionModel(id); const mainModel = useAppSelector(selectMainModel); const { t } = useTranslation(); @@ -49,6 +56,16 @@ const ParamControlAdapterModel = ({ id }: ParamControlAdapterModelProps) => { [dispatch, id] ); + const onCLIPVisionModelChange = useCallback( + (v) => { + if (!v?.value) { + return; + } + dispatch(controlAdapterCLIPVisionModelChanged({ id, clipVisionModel: v.value as CLIPVisionModel })); + }, + [dispatch, id] + ); + const selectedModel = useMemo( () => (modelConfig && controlAdapterType ? { ...modelConfig, model_type: controlAdapterType } : null), [controlAdapterType, modelConfig] @@ -71,17 +88,42 @@ const ParamControlAdapterModel = ({ id }: ParamControlAdapterModelProps) => { isLoading, }); + const clipVisionOptions = useMemo( + () => [ + { label: 'ViT-H', value: 'ViT-H' }, + { label: 'ViT-G', value: 'ViT-G' }, + ], + [] + ); + + const clipVisionModel = useMemo( + () => clipVisionOptions.find((o) => o.value === currentCLIPVisionModel), + [clipVisionOptions, currentCLIPVisionModel] + ); + return ( - - - + + + + + {modelConfig?.type === 'ip_adapter' && modelConfig.format === 'checkpoint' && ( + + + + )} + ); }; diff --git a/invokeai/frontend/web/src/features/controlAdapters/hooks/useControlAdapterCLIPVisionModel.ts b/invokeai/frontend/web/src/features/controlAdapters/hooks/useControlAdapterCLIPVisionModel.ts new file mode 100644 index 0000000000..249d2022fe --- /dev/null +++ b/invokeai/frontend/web/src/features/controlAdapters/hooks/useControlAdapterCLIPVisionModel.ts @@ -0,0 +1,24 @@ +import { createMemoizedSelector } from 'app/store/createMemoizedSelector'; +import { useAppSelector } from 'app/store/storeHooks'; +import { + selectControlAdapterById, + selectControlAdaptersSlice, +} from 'features/controlAdapters/store/controlAdaptersSlice'; +import { useMemo } from 'react'; + +export const useControlAdapterCLIPVisionModel = (id: string) => { + const selector = useMemo( + () => + createMemoizedSelector(selectControlAdaptersSlice, (controlAdapters) => { + const cn = selectControlAdapterById(controlAdapters, id); + if (cn && cn?.type === 'ip_adapter') { + return cn.clipVisionModel; + } + }), + [id] + ); + + const clipVisionModel = useAppSelector(selector); + + return clipVisionModel; +}; diff --git a/invokeai/frontend/web/src/features/controlAdapters/store/controlAdaptersSlice.ts b/invokeai/frontend/web/src/features/controlAdapters/store/controlAdaptersSlice.ts index f4edca41bb..100bb3f6ad 100644 --- a/invokeai/frontend/web/src/features/controlAdapters/store/controlAdaptersSlice.ts +++ b/invokeai/frontend/web/src/features/controlAdapters/store/controlAdaptersSlice.ts @@ -14,6 +14,7 @@ import { v4 as uuidv4 } from 'uuid'; import { controlAdapterImageProcessed } from './actions'; import { CONTROLNET_PROCESSORS } from './constants'; import type { + CLIPVisionModel, ControlAdapterConfig, ControlAdapterProcessorType, ControlAdaptersState, @@ -244,6 +245,13 @@ export const controlAdaptersSlice = createSlice({ } caAdapter.updateOne(state, { id, changes: { controlMode } }); }, + controlAdapterCLIPVisionModelChanged: ( + state, + action: PayloadAction<{ id: string; clipVisionModel: CLIPVisionModel }> + ) => { + const { id, clipVisionModel } = action.payload; + caAdapter.updateOne(state, { id, changes: { clipVisionModel } }); + }, controlAdapterResizeModeChanged: ( state, action: PayloadAction<{ @@ -381,6 +389,7 @@ export const { controlAdapterProcessedImageChanged, controlAdapterIsEnabledChanged, controlAdapterModelChanged, + controlAdapterCLIPVisionModelChanged, controlAdapterWeightChanged, controlAdapterBeginStepPctChanged, controlAdapterEndStepPctChanged, diff --git a/invokeai/frontend/web/src/features/controlAdapters/store/types.ts b/invokeai/frontend/web/src/features/controlAdapters/store/types.ts index 93d4915cdf..329c318759 100644 --- a/invokeai/frontend/web/src/features/controlAdapters/store/types.ts +++ b/invokeai/frontend/web/src/features/controlAdapters/store/types.ts @@ -243,12 +243,15 @@ export type T2IAdapterConfig = { shouldAutoConfig: boolean; }; +export type CLIPVisionModel = 'ViT-H' | 'ViT-G'; + export type IPAdapterConfig = { type: 'ip_adapter'; id: string; isEnabled: boolean; controlImage: string | null; model: ParameterIPAdapterModel | null; + clipVisionModel: CLIPVisionModel; weight: number; beginStepPct: number; endStepPct: number; diff --git a/invokeai/frontend/web/src/features/controlAdapters/util/buildControlAdapter.ts b/invokeai/frontend/web/src/features/controlAdapters/util/buildControlAdapter.ts index d4796572d4..dc893ceb1c 100644 --- a/invokeai/frontend/web/src/features/controlAdapters/util/buildControlAdapter.ts +++ b/invokeai/frontend/web/src/features/controlAdapters/util/buildControlAdapter.ts @@ -46,6 +46,7 @@ export const initialIPAdapter: Omit = { isEnabled: true, controlImage: null, model: null, + clipVisionModel: 'ViT-H', weight: 1, beginStepPct: 0, endStepPct: 1, diff --git a/invokeai/frontend/web/src/features/nodes/util/graph/addIPAdapterToLinearGraph.ts b/invokeai/frontend/web/src/features/nodes/util/graph/addIPAdapterToLinearGraph.ts index 2298e84d43..ad563de468 100644 --- a/invokeai/frontend/web/src/features/nodes/util/graph/addIPAdapterToLinearGraph.ts +++ b/invokeai/frontend/web/src/features/nodes/util/graph/addIPAdapterToLinearGraph.ts @@ -48,7 +48,7 @@ export const addIPAdapterToLinearGraph = async ( if (!ipAdapter.model) { return; } - const { id, weight, model, beginStepPct, endStepPct, controlImage } = ipAdapter; + const { id, weight, model, clipVisionModel, beginStepPct, endStepPct, controlImage } = ipAdapter; assert(controlImage, 'IP Adapter image is required'); @@ -58,6 +58,7 @@ export const addIPAdapterToLinearGraph = async ( is_intermediate: true, weight: weight, ip_adapter_model: model, + clip_vision_model: clipVisionModel, begin_step_percent: beginStepPct, end_step_percent: endStepPct, image: { @@ -83,7 +84,7 @@ export const addIPAdapterToLinearGraph = async ( }; const buildIPAdapterMetadata = (ipAdapter: IPAdapterConfig): S['IPAdapterMetadataField'] => { - const { controlImage, beginStepPct, endStepPct, model, weight } = ipAdapter; + const { controlImage, beginStepPct, endStepPct, model, clipVisionModel, weight } = ipAdapter; assert(model, 'IP Adapter model is required'); @@ -99,6 +100,7 @@ const buildIPAdapterMetadata = (ipAdapter: IPAdapterConfig): S['IPAdapterMetadat return { ip_adapter_model: model, + clip_vision_model: clipVisionModel, weight, begin_step_percent: beginStepPct, end_step_percent: endStepPct, From 4a0dfc3b2d970ab447e539e62ec70ca9c2655928 Mon Sep 17 00:00:00 2001 From: blessedcoolant <54517381+blessedcoolant@users.noreply.github.com> Date: Wed, 27 Mar 2024 22:05:53 +0530 Subject: [PATCH 22/89] ui: improve the clip vision model picker layout --- invokeai/backend/model_manager/probe.py | 23 +++++++---- .../parameters/ParamControlAdapterModel.tsx | 38 +++++++++++-------- 2 files changed, 38 insertions(+), 23 deletions(-) diff --git a/invokeai/backend/model_manager/probe.py b/invokeai/backend/model_manager/probe.py index 75f156ce21..7fc8c99e29 100644 --- a/invokeai/backend/model_manager/probe.py +++ b/invokeai/backend/model_manager/probe.py @@ -9,16 +9,23 @@ from picklescan.scanner import scan_file_path import invokeai.backend.util.logging as logger from invokeai.app.util.misc import uuid_string -from invokeai.backend.model_hash.model_hash import (HASHING_ALGORITHMS, - ModelHash) +from invokeai.backend.model_hash.model_hash import HASHING_ALGORITHMS, ModelHash from invokeai.backend.util.util import SilenceWarnings -from .config import (AnyModelConfig, BaseModelType, - ControlAdapterDefaultSettings, - InvalidModelConfigException, MainModelDefaultSettings, - ModelConfigFactory, ModelFormat, ModelRepoVariant, - ModelSourceType, ModelType, ModelVariantType, - SchedulerPredictionType) +from .config import ( + AnyModelConfig, + BaseModelType, + ControlAdapterDefaultSettings, + InvalidModelConfigException, + MainModelDefaultSettings, + ModelConfigFactory, + ModelFormat, + ModelRepoVariant, + ModelSourceType, + ModelType, + ModelVariantType, + SchedulerPredictionType, +) from .util.model_util import lora_token_vector_length, read_checkpoint_meta CkptType = Dict[str | int, Any] diff --git a/invokeai/frontend/web/src/features/controlAdapters/components/parameters/ParamControlAdapterModel.tsx b/invokeai/frontend/web/src/features/controlAdapters/components/parameters/ParamControlAdapterModel.tsx index 380a33185d..91f8822352 100644 --- a/invokeai/frontend/web/src/features/controlAdapters/components/parameters/ParamControlAdapterModel.tsx +++ b/invokeai/frontend/web/src/features/controlAdapters/components/parameters/ParamControlAdapterModel.tsx @@ -102,9 +102,13 @@ const ParamControlAdapterModel = ({ id }: ParamControlAdapterModelProps) => { ); return ( - - - + + + { noOptionsMessage={noOptionsMessage} /> - {modelConfig?.type === 'ip_adapter' && modelConfig.format === 'checkpoint' && ( - - - - )} - - + + {modelConfig?.type === 'ip_adapter' && modelConfig.format === 'checkpoint' && ( + + + + )} + ); }; From a14ce0edab4e88d2b337ca68641a666f61d623a1 Mon Sep 17 00:00:00 2001 From: blessedcoolant <54517381+blessedcoolant@users.noreply.github.com> Date: Fri, 29 Mar 2024 11:50:18 +0530 Subject: [PATCH 23/89] chore: rename IPAdapterDiffusersConfig to IPAdapterInvokeAIConfig --- invokeai/app/invocations/ip_adapter.py | 6 +++--- invokeai/backend/model_manager/config.py | 4 ++-- invokeai/frontend/web/src/services/api/types.ts | 2 +- 3 files changed, 6 insertions(+), 6 deletions(-) diff --git a/invokeai/app/invocations/ip_adapter.py b/invokeai/app/invocations/ip_adapter.py index 603a85148d..5e24721b2f 100644 --- a/invokeai/app/invocations/ip_adapter.py +++ b/invokeai/app/invocations/ip_adapter.py @@ -14,7 +14,7 @@ from invokeai.backend.model_manager.config import ( AnyModelConfig, BaseModelType, IPAdapterCheckpointConfig, - IPAdapterDiffusersConfig, + IPAdapterInvokeAIConfig, ModelType, ) @@ -92,9 +92,9 @@ class IPAdapterInvocation(BaseInvocation): def invoke(self, context: InvocationContext) -> IPAdapterOutput: # Lookup the CLIP Vision encoder that is intended to be used with the IP-Adapter model. ip_adapter_info = context.models.get_config(self.ip_adapter_model.key) - assert isinstance(ip_adapter_info, (IPAdapterDiffusersConfig, IPAdapterCheckpointConfig)) + assert isinstance(ip_adapter_info, (IPAdapterInvokeAIConfig, IPAdapterCheckpointConfig)) - if isinstance(ip_adapter_info, IPAdapterDiffusersConfig): + if isinstance(ip_adapter_info, IPAdapterInvokeAIConfig): image_encoder_model_id = ip_adapter_info.image_encoder_model_id image_encoder_model_name = image_encoder_model_id.split("/")[-1].strip() else: diff --git a/invokeai/backend/model_manager/config.py b/invokeai/backend/model_manager/config.py index 172045d3fc..82f88c0e81 100644 --- a/invokeai/backend/model_manager/config.py +++ b/invokeai/backend/model_manager/config.py @@ -327,7 +327,7 @@ class IPAdapterBaseConfig(ModelConfigBase): type: Literal[ModelType.IPAdapter] = ModelType.IPAdapter -class IPAdapterDiffusersConfig(IPAdapterBaseConfig): +class IPAdapterInvokeAIConfig(IPAdapterBaseConfig): """Model config for IP Adapter diffusers format models.""" image_encoder_model_id: str @@ -403,7 +403,7 @@ AnyModelConfig = Annotated[ Annotated[LoRADiffusersConfig, LoRADiffusersConfig.get_tag()], Annotated[TextualInversionFileConfig, TextualInversionFileConfig.get_tag()], Annotated[TextualInversionFolderConfig, TextualInversionFolderConfig.get_tag()], - Annotated[IPAdapterDiffusersConfig, IPAdapterDiffusersConfig.get_tag()], + Annotated[IPAdapterInvokeAIConfig, IPAdapterInvokeAIConfig.get_tag()], Annotated[IPAdapterCheckpointConfig, IPAdapterCheckpointConfig.get_tag()], Annotated[T2IAdapterConfig, T2IAdapterConfig.get_tag()], Annotated[CLIPVisionDiffusersConfig, CLIPVisionDiffusersConfig.get_tag()], diff --git a/invokeai/frontend/web/src/services/api/types.ts b/invokeai/frontend/web/src/services/api/types.ts index 1c7e816d16..39d5890e51 100644 --- a/invokeai/frontend/web/src/services/api/types.ts +++ b/invokeai/frontend/web/src/services/api/types.ts @@ -46,7 +46,7 @@ export type LoRAModelConfig = S['LoRADiffusersConfig'] | S['LoRALyCORISConfig']; // TODO(MM2): Can we rename this from Vae -> VAE export type VAEModelConfig = S['VAECheckpointConfig'] | S['VAEDiffusersConfig']; export type ControlNetModelConfig = S['ControlNetDiffusersConfig'] | S['ControlNetCheckpointConfig']; -export type IPAdapterModelConfig = S['IPAdapterDiffusersConfig'] | S['IPAdapterCheckpointConfig']; +export type IPAdapterModelConfig = S['IPAdapterInvokeAIConfig'] | S['IPAdapterCheckpointConfig']; export type T2IAdapterModelConfig = S['T2IAdapterConfig']; type TextualInversionModelConfig = S['TextualInversionFileConfig'] | S['TextualInversionFolderConfig']; type DiffusersModelConfig = S['MainDiffusersConfig']; From be1212de9afbecfee364316f1c7124a191cdeada Mon Sep 17 00:00:00 2001 From: blessedcoolant <54517381+blessedcoolant@users.noreply.github.com> Date: Fri, 29 Mar 2024 11:58:10 +0530 Subject: [PATCH 24/89] fix: Raise a better error when incorrect CLIP Vision model is used --- invokeai/backend/ip_adapter/ip_adapter.py | 18 ++++++++++++------ 1 file changed, 12 insertions(+), 6 deletions(-) diff --git a/invokeai/backend/ip_adapter/ip_adapter.py b/invokeai/backend/ip_adapter/ip_adapter.py index 5444c76c8c..1155e571ae 100644 --- a/invokeai/backend/ip_adapter/ip_adapter.py +++ b/invokeai/backend/ip_adapter/ip_adapter.py @@ -146,9 +146,12 @@ class IPAdapter(RawModel): def get_image_embeds(self, pil_image: List[Image.Image], image_encoder: CLIPVisionModelWithProjection): clip_image = self._clip_image_processor(images=pil_image, return_tensors="pt").pixel_values clip_image_embeds = image_encoder(clip_image.to(self.device, dtype=self.dtype)).image_embeds - image_prompt_embeds = self._image_proj_model(clip_image_embeds) - uncond_image_prompt_embeds = self._image_proj_model(torch.zeros_like(clip_image_embeds)) - return image_prompt_embeds, uncond_image_prompt_embeds + try: + image_prompt_embeds = self._image_proj_model(clip_image_embeds) + uncond_image_prompt_embeds = self._image_proj_model(torch.zeros_like(clip_image_embeds)) + return image_prompt_embeds, uncond_image_prompt_embeds + except RuntimeError: + raise RuntimeError("Selected CLIP Vision Model is incompatible with the current IP Adapter") class IPAdapterPlus(IPAdapter): @@ -169,12 +172,15 @@ class IPAdapterPlus(IPAdapter): clip_image = self._clip_image_processor(images=pil_image, return_tensors="pt").pixel_values clip_image = clip_image.to(self.device, dtype=self.dtype) clip_image_embeds = image_encoder(clip_image, output_hidden_states=True).hidden_states[-2] - image_prompt_embeds = self._image_proj_model(clip_image_embeds) uncond_clip_image_embeds = image_encoder(torch.zeros_like(clip_image), output_hidden_states=True).hidden_states[ -2 ] - uncond_image_prompt_embeds = self._image_proj_model(uncond_clip_image_embeds) - return image_prompt_embeds, uncond_image_prompt_embeds + try: + image_prompt_embeds = self._image_proj_model(clip_image_embeds) + uncond_image_prompt_embeds = self._image_proj_model(uncond_clip_image_embeds) + return image_prompt_embeds, uncond_image_prompt_embeds + except RuntimeError: + raise RuntimeError("Selected CLIP Vision Model is incompatible with the current IP Adapter") class IPAdapterFull(IPAdapterPlus): From dc1681a0de5a1f27ed3210fda884eca8aeec7f60 Mon Sep 17 00:00:00 2001 From: blessedcoolant <54517381+blessedcoolant@users.noreply.github.com> Date: Fri, 29 Mar 2024 12:12:16 +0530 Subject: [PATCH 25/89] fix: clip vision model auto param Setting to 'auto' works only for InvokeAI config and auto detects the SD model but will override if user explicitly sets it. If auto used with checkpoint models, we raise an error. Checkpoints will always need to set to non-auto. --- invokeai/app/invocations/ip_adapter.py | 17 ++++++++++++----- 1 file changed, 12 insertions(+), 5 deletions(-) diff --git a/invokeai/app/invocations/ip_adapter.py b/invokeai/app/invocations/ip_adapter.py index 5e24721b2f..40a667c9d0 100644 --- a/invokeai/app/invocations/ip_adapter.py +++ b/invokeai/app/invocations/ip_adapter.py @@ -65,8 +65,10 @@ class IPAdapterInvocation(BaseInvocation): ui_order=-1, ui_type=UIType.IPAdapterModel, ) - clip_vision_model: Literal["ViT-H", "ViT-G"] = InputField( - description="CLIP Vision model to use", default="ViT-H", ui_order=2 + clip_vision_model: Literal["auto", "ViT-H", "ViT-G"] = InputField( + description="CLIP Vision model to use. Overrides model settings. Mandatory for checkpoint models.", + default="auto", + ui_order=2, ) weight: Union[float, List[float]] = InputField( default=1, description="The weight given to the IP-Adapter", title="Weight" @@ -94,9 +96,14 @@ class IPAdapterInvocation(BaseInvocation): ip_adapter_info = context.models.get_config(self.ip_adapter_model.key) assert isinstance(ip_adapter_info, (IPAdapterInvokeAIConfig, IPAdapterCheckpointConfig)) - if isinstance(ip_adapter_info, IPAdapterInvokeAIConfig): - image_encoder_model_id = ip_adapter_info.image_encoder_model_id - image_encoder_model_name = image_encoder_model_id.split("/")[-1].strip() + if self.clip_vision_model == "auto": + if isinstance(ip_adapter_info, IPAdapterInvokeAIConfig): + image_encoder_model_id = ip_adapter_info.image_encoder_model_id + image_encoder_model_name = image_encoder_model_id.split("/")[-1].strip() + else: + raise RuntimeError( + "You need to set the appropriate CLIP Vision model for checkpoint IP Adapter models." + ) else: image_encoder_model_name = CLIP_VISION_MODEL_MAP[self.clip_vision_model] From 1372ef15b31da861d1eada8aaf652b63be22484a Mon Sep 17 00:00:00 2001 From: blessedcoolant <54517381+blessedcoolant@users.noreply.github.com> Date: Fri, 29 Mar 2024 12:34:56 +0530 Subject: [PATCH 26/89] fix: Fail when unexpected keys are found in IP Adapter models --- invokeai/backend/ip_adapter/ip_adapter.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/invokeai/backend/ip_adapter/ip_adapter.py b/invokeai/backend/ip_adapter/ip_adapter.py index 1155e571ae..02788c0ba6 100644 --- a/invokeai/backend/ip_adapter/ip_adapter.py +++ b/invokeai/backend/ip_adapter/ip_adapter.py @@ -214,6 +214,8 @@ def load_ip_adapter_tensors(ip_adapter_ckpt_path: str, device: str) -> IPAdapter state_dict["image_proj"][key.replace("image_proj.", "")] = model.get_tensor(key) elif key.startswith("ip_adapter."): state_dict["ip_adapter"][key.replace("ip_adapter.", "")] = model.get_tensor(key) + else: + raise RuntimeError(f"Encountered unexpected IP Adapter state dict key: '{key}'.") else: ip_adapter_diffusers_checkpoint_path = ip_adapter_ckpt_path + "/ip_adapter.bin" state_dict = torch.load(ip_adapter_diffusers_checkpoint_path, map_location="cpu") From 14a9f74b1726cbfd11b00bbc5a3cbc963e15df2f Mon Sep 17 00:00:00 2001 From: blessedcoolant <54517381+blessedcoolant@users.noreply.github.com> Date: Mon, 1 Apr 2024 06:37:38 +0530 Subject: [PATCH 27/89] cleanup: use load_file of safetensors directly for loading ip adapters --- invokeai/backend/ip_adapter/ip_adapter.py | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/invokeai/backend/ip_adapter/ip_adapter.py b/invokeai/backend/ip_adapter/ip_adapter.py index 02788c0ba6..920cb3780a 100644 --- a/invokeai/backend/ip_adapter/ip_adapter.py +++ b/invokeai/backend/ip_adapter/ip_adapter.py @@ -3,12 +3,14 @@ from typing import List, Optional, TypedDict, Union +import safetensors +import safetensors.torch import torch from PIL import Image -from safetensors import safe_open from transformers import CLIPImageProcessor, CLIPVisionModelWithProjection from invokeai.backend.ip_adapter.ip_attention_weights import IPAttentionWeights +from invokeai.backend.util.devices import choose_torch_device from ..raw_model import RawModel from .resampler import Resampler @@ -208,12 +210,12 @@ def load_ip_adapter_tensors(ip_adapter_ckpt_path: str, device: str) -> IPAdapter state_dict: IPAdapterStateDict = {"ip_adapter": {}, "image_proj": {}} if ip_adapter_ckpt_path.endswith("safetensors"): - model = safe_open(ip_adapter_ckpt_path, device=device, framework="pt") + model = safetensors.torch.load_file(ip_adapter_ckpt_path, device=device) for key in model.keys(): if key.startswith("image_proj."): - state_dict["image_proj"][key.replace("image_proj.", "")] = model.get_tensor(key) + state_dict["image_proj"][key.replace("image_proj.", "")] = model[key] elif key.startswith("ip_adapter."): - state_dict["ip_adapter"][key.replace("ip_adapter.", "")] = model.get_tensor(key) + state_dict["ip_adapter"][key.replace("ip_adapter.", "")] = model[key] else: raise RuntimeError(f"Encountered unexpected IP Adapter state dict key: '{key}'.") else: From 2dcbb7223b12e54a795dd88fc0d1c92719ea7896 Mon Sep 17 00:00:00 2001 From: blessedcoolant <54517381+blessedcoolant@users.noreply.github.com> Date: Wed, 3 Apr 2024 20:21:03 +0530 Subject: [PATCH 28/89] fix: use Path for ip_adapter_ckpt_path instead of str --- invokeai/backend/ip_adapter/ip_adapter.py | 10 +++++----- .../model_manager/load/model_loaders/ip_adapter.py | 2 +- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/invokeai/backend/ip_adapter/ip_adapter.py b/invokeai/backend/ip_adapter/ip_adapter.py index 920cb3780a..5d1a9f153f 100644 --- a/invokeai/backend/ip_adapter/ip_adapter.py +++ b/invokeai/backend/ip_adapter/ip_adapter.py @@ -1,6 +1,7 @@ # copied from https://github.com/tencent-ailab/IP-Adapter (Apache License 2.0) # and modified as needed +import pathlib from typing import List, Optional, TypedDict, Union import safetensors @@ -10,7 +11,6 @@ from PIL import Image from transformers import CLIPImageProcessor, CLIPVisionModelWithProjection from invokeai.backend.ip_adapter.ip_attention_weights import IPAttentionWeights -from invokeai.backend.util.devices import choose_torch_device from ..raw_model import RawModel from .resampler import Resampler @@ -206,10 +206,10 @@ class IPAdapterPlusXL(IPAdapterPlus): ).to(self.device, dtype=self.dtype) -def load_ip_adapter_tensors(ip_adapter_ckpt_path: str, device: str) -> IPAdapterStateDict: +def load_ip_adapter_tensors(ip_adapter_ckpt_path: pathlib.Path, device: str) -> IPAdapterStateDict: state_dict: IPAdapterStateDict = {"ip_adapter": {}, "image_proj": {}} - if ip_adapter_ckpt_path.endswith("safetensors"): + if ip_adapter_ckpt_path.stem == "safetensors": model = safetensors.torch.load_file(ip_adapter_ckpt_path, device=device) for key in model.keys(): if key.startswith("image_proj."): @@ -219,14 +219,14 @@ def load_ip_adapter_tensors(ip_adapter_ckpt_path: str, device: str) -> IPAdapter else: raise RuntimeError(f"Encountered unexpected IP Adapter state dict key: '{key}'.") else: - ip_adapter_diffusers_checkpoint_path = ip_adapter_ckpt_path + "/ip_adapter.bin" + ip_adapter_diffusers_checkpoint_path = ip_adapter_ckpt_path / "ip_adapter.bin" state_dict = torch.load(ip_adapter_diffusers_checkpoint_path, map_location="cpu") return state_dict def build_ip_adapter( - ip_adapter_ckpt_path: str, device: torch.device, dtype: torch.dtype = torch.float16 + ip_adapter_ckpt_path: pathlib.Path, device: torch.device, dtype: torch.dtype = torch.float16 ) -> Union[IPAdapter, IPAdapterPlus, IPAdapterPlusXL, IPAdapterPlus]: state_dict = load_ip_adapter_tensors(ip_adapter_ckpt_path, device.type) diff --git a/invokeai/backend/model_manager/load/model_loaders/ip_adapter.py b/invokeai/backend/model_manager/load/model_loaders/ip_adapter.py index 414d2c7247..55eed81fcd 100644 --- a/invokeai/backend/model_manager/load/model_loaders/ip_adapter.py +++ b/invokeai/backend/model_manager/load/model_loaders/ip_adapter.py @@ -26,7 +26,7 @@ class IPAdapterInvokeAILoader(ModelLoader): raise ValueError("There are no submodels in an IP-Adapter model.") model_path = Path(config.path) model: RawModel = build_ip_adapter( - ip_adapter_ckpt_path=str(model_path), + ip_adapter_ckpt_path=model_path, device=torch.device("cpu"), dtype=self._torch_dtype, ) From 414851f2f0f311f7b9c26b76731a45f2da47813a Mon Sep 17 00:00:00 2001 From: blessedcoolant <54517381+blessedcoolant@users.noreply.github.com> Date: Wed, 3 Apr 2024 20:21:50 +0530 Subject: [PATCH 29/89] fix: raise and present the runtime error from the exception --- invokeai/backend/ip_adapter/ip_adapter.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/invokeai/backend/ip_adapter/ip_adapter.py b/invokeai/backend/ip_adapter/ip_adapter.py index 5d1a9f153f..62ee81f6b2 100644 --- a/invokeai/backend/ip_adapter/ip_adapter.py +++ b/invokeai/backend/ip_adapter/ip_adapter.py @@ -152,8 +152,8 @@ class IPAdapter(RawModel): image_prompt_embeds = self._image_proj_model(clip_image_embeds) uncond_image_prompt_embeds = self._image_proj_model(torch.zeros_like(clip_image_embeds)) return image_prompt_embeds, uncond_image_prompt_embeds - except RuntimeError: - raise RuntimeError("Selected CLIP Vision Model is incompatible with the current IP Adapter") + except RuntimeError as e: + raise RuntimeError("Selected CLIP Vision Model is incompatible with the current IP Adapter") from e class IPAdapterPlus(IPAdapter): @@ -181,8 +181,8 @@ class IPAdapterPlus(IPAdapter): image_prompt_embeds = self._image_proj_model(clip_image_embeds) uncond_image_prompt_embeds = self._image_proj_model(uncond_clip_image_embeds) return image_prompt_embeds, uncond_image_prompt_embeds - except RuntimeError: - raise RuntimeError("Selected CLIP Vision Model is incompatible with the current IP Adapter") + except RuntimeError as e: + raise RuntimeError("Selected CLIP Vision Model is incompatible with the current IP Adapter") from e class IPAdapterFull(IPAdapterPlus): From e5748154137b4553ddf63c649996f381952a08de Mon Sep 17 00:00:00 2001 From: blessedcoolant <54517381+blessedcoolant@users.noreply.github.com> Date: Wed, 3 Apr 2024 20:23:25 +0530 Subject: [PATCH 30/89] chore: clean up merge conflicts --- invokeai/backend/ip_adapter/ip_adapter.py | 1 - .../backend/model_manager/load/model_loaders/ip_adapter.py | 1 - invokeai/frontend/web/src/services/api/schema.ts | 4 ++-- 3 files changed, 2 insertions(+), 4 deletions(-) diff --git a/invokeai/backend/ip_adapter/ip_adapter.py b/invokeai/backend/ip_adapter/ip_adapter.py index a317704d7c..62ee81f6b2 100644 --- a/invokeai/backend/ip_adapter/ip_adapter.py +++ b/invokeai/backend/ip_adapter/ip_adapter.py @@ -11,7 +11,6 @@ from PIL import Image from transformers import CLIPImageProcessor, CLIPVisionModelWithProjection from invokeai.backend.ip_adapter.ip_attention_weights import IPAttentionWeights -from invokeai.backend.util.devices import choose_torch_device from ..raw_model import RawModel from .resampler import Resampler diff --git a/invokeai/backend/model_manager/load/model_loaders/ip_adapter.py b/invokeai/backend/model_manager/load/model_loaders/ip_adapter.py index 8965570784..55eed81fcd 100644 --- a/invokeai/backend/model_manager/load/model_loaders/ip_adapter.py +++ b/invokeai/backend/model_manager/load/model_loaders/ip_adapter.py @@ -14,7 +14,6 @@ from invokeai.backend.raw_model import RawModel @ModelLoaderRegistry.register(base=BaseModelType.Any, type=ModelType.IPAdapter, format=ModelFormat.InvokeAI) @ModelLoaderRegistry.register(base=BaseModelType.Any, type=ModelType.IPAdapter, format=ModelFormat.Checkpoint) -@ModelLoaderRegistry.register(base=BaseModelType.Any, type=ModelType.IPAdapter, format=ModelFormat.Checkpoint) class IPAdapterInvokeAILoader(ModelLoader): """Class to load IP Adapter diffusers models.""" diff --git a/invokeai/frontend/web/src/services/api/schema.ts b/invokeai/frontend/web/src/services/api/schema.ts index 497a55fc08..6e3ddf678b 100644 --- a/invokeai/frontend/web/src/services/api/schema.ts +++ b/invokeai/frontend/web/src/services/api/schema.ts @@ -4112,7 +4112,7 @@ export type components = { * @description The nodes in this graph */ nodes: { - [key: string]: components["schemas"]["FloatInvocation"] | components["schemas"]["LatentsInvocation"] | components["schemas"]["SubtractInvocation"] | components["schemas"]["MlsdImageProcessorInvocation"] | components["schemas"]["ImageBlurInvocation"] | components["schemas"]["CvInpaintInvocation"] | components["schemas"]["LaMaInfillInvocation"] | components["schemas"]["RangeOfSizeInvocation"] | components["schemas"]["RandomFloatInvocation"] | components["schemas"]["CoreMetadataInvocation"] | components["schemas"]["PairTileImageInvocation"] | components["schemas"]["CenterPadCropInvocation"] | components["schemas"]["MidasDepthImageProcessorInvocation"] | components["schemas"]["ImageInverseLerpInvocation"] | components["schemas"]["SeamlessModeInvocation"] | components["schemas"]["TileToPropertiesInvocation"] | components["schemas"]["ResizeLatentsInvocation"] | components["schemas"]["CollectInvocation"] | components["schemas"]["MediapipeFaceProcessorInvocation"] | components["schemas"]["FaceIdentifierInvocation"] | components["schemas"]["PromptsFromFileInvocation"] | components["schemas"]["BlendLatentsInvocation"] | components["schemas"]["SegmentAnythingProcessorInvocation"] | components["schemas"]["T2IAdapterInvocation"] | components["schemas"]["ImageInvocation"] | components["schemas"]["ScaleLatentsInvocation"] | components["schemas"]["UnsharpMaskInvocation"] | components["schemas"]["StepParamEasingInvocation"] | components["schemas"]["SDXLLoRALoaderInvocation"] | components["schemas"]["ContentShuffleImageProcessorInvocation"] | components["schemas"]["StringReplaceInvocation"] | components["schemas"]["MultiplyInvocation"] | components["schemas"]["SDXLRefinerCompelPromptInvocation"] | components["schemas"]["ColorCorrectInvocation"] | components["schemas"]["ImageCollectionInvocation"] | components["schemas"]["ImageResizeInvocation"] | components["schemas"]["NormalbaeImageProcessorInvocation"] | components["schemas"]["HedImageProcessorInvocation"] | components["schemas"]["MaskFromIDInvocation"] | components["schemas"]["StringJoinThreeInvocation"] | components["schemas"]["StringCollectionInvocation"] | components["schemas"]["FaceOffInvocation"] | components["schemas"]["ImageToLatentsInvocation"] | components["schemas"]["ImagePasteInvocation"] | components["schemas"]["DivideInvocation"] | components["schemas"]["ImageMultiplyInvocation"] | components["schemas"]["VAELoaderInvocation"] | components["schemas"]["RoundInvocation"] | components["schemas"]["IdealSizeInvocation"] | components["schemas"]["ControlNetInvocation"] | components["schemas"]["ImageChannelInvocation"] | components["schemas"]["ColorMapImageProcessorInvocation"] | components["schemas"]["CV2InfillInvocation"] | components["schemas"]["LineartImageProcessorInvocation"] | components["schemas"]["ImageNSFWBlurInvocation"] | components["schemas"]["MergeTilesToImageInvocation"] | components["schemas"]["RandomRangeInvocation"] | components["schemas"]["MetadataItemInvocation"] | components["schemas"]["MaskFromAlphaInvocation"] | components["schemas"]["StringJoinInvocation"] | components["schemas"]["CropLatentsCoreInvocation"] | components["schemas"]["BlankImageInvocation"] | components["schemas"]["ImageScaleInvocation"] | components["schemas"]["ImageChannelOffsetInvocation"] | components["schemas"]["MaskEdgeInvocation"] | components["schemas"]["IterateInvocation"] | components["schemas"]["MainModelLoaderInvocation"] | components["schemas"]["StringSplitInvocation"] | components["schemas"]["PidiImageProcessorInvocation"] | components["schemas"]["FaceMaskInvocation"] | components["schemas"]["DenoiseLatentsInvocation"] | components["schemas"]["FloatCollectionInvocation"] | components["schemas"]["CalculateImageTilesEvenSplitInvocation"] | components["schemas"]["ColorInvocation"] | components["schemas"]["RandomIntInvocation"] | components["schemas"]["DepthAnythingImageProcessorInvocation"] | components["schemas"]["CompelInvocation"] | components["schemas"]["MaskCombineInvocation"] | components["schemas"]["StringSplitNegInvocation"] | components["schemas"]["BooleanInvocation"] | components["schemas"]["CreateDenoiseMaskInvocation"] | components["schemas"]["RangeInvocation"] | components["schemas"]["ZoeDepthImageProcessorInvocation"] | components["schemas"]["DWOpenposeImageProcessorInvocation"] | components["schemas"]["ImageChannelMultiplyInvocation"] | components["schemas"]["InfillPatchMatchInvocation"] | components["schemas"]["BooleanCollectionInvocation"] | components["schemas"]["ImageWatermarkInvocation"] | components["schemas"]["FreeUInvocation"] | components["schemas"]["DynamicPromptInvocation"] | components["schemas"]["IntegerCollectionInvocation"] | components["schemas"]["LeresImageProcessorInvocation"] | components["schemas"]["NoiseInvocation"] | components["schemas"]["ConditioningInvocation"] | components["schemas"]["SDXLCompelPromptInvocation"] | components["schemas"]["SchedulerInvocation"] | components["schemas"]["CalculateImageTilesInvocation"] | components["schemas"]["StringInvocation"] | components["schemas"]["AddInvocation"] | components["schemas"]["ImageConvertInvocation"] | components["schemas"]["LatentsCollectionInvocation"] | components["schemas"]["IntegerMathInvocation"] | components["schemas"]["CannyImageProcessorInvocation"] | components["schemas"]["SaveImageInvocation"] | components["schemas"]["MetadataInvocation"] | components["schemas"]["InfillTileInvocation"] | components["schemas"]["SDXLModelLoaderInvocation"] | components["schemas"]["ConditioningCollectionInvocation"] | components["schemas"]["ImageCropInvocation"] | components["schemas"]["MergeMetadataInvocation"] | components["schemas"]["ImageHueAdjustmentInvocation"] | components["schemas"]["LineartAnimeImageProcessorInvocation"] | components["schemas"]["InfillColorInvocation"] | components["schemas"]["IPAdapterInvocation"] | components["schemas"]["ESRGANInvocation"] | components["schemas"]["LoRALoaderInvocation"] | components["schemas"]["FloatMathInvocation"] | components["schemas"]["TileResamplerProcessorInvocation"] | components["schemas"]["SDXLRefinerModelLoaderInvocation"] | components["schemas"]["IntegerInvocation"] | components["schemas"]["CalculateImageTilesMinimumOverlapInvocation"] | components["schemas"]["FloatToIntegerInvocation"] | components["schemas"]["ShowImageInvocation"] | components["schemas"]["ImageLerpInvocation"] | components["schemas"]["CanvasPasteBackInvocation"] | components["schemas"]["FloatLinearRangeInvocation"] | components["schemas"]["CreateGradientMaskInvocation"] | components["schemas"]["LatentsToImageInvocation"] | components["schemas"]["CLIPSkipInvocation"]; + [key: string]: components["schemas"]["StringInvocation"] | components["schemas"]["ControlNetInvocation"] | components["schemas"]["MetadataItemInvocation"] | components["schemas"]["MergeMetadataInvocation"] | components["schemas"]["ContentShuffleImageProcessorInvocation"] | components["schemas"]["FloatLinearRangeInvocation"] | components["schemas"]["RangeOfSizeInvocation"] | components["schemas"]["SegmentAnythingProcessorInvocation"] | components["schemas"]["StringReplaceInvocation"] | components["schemas"]["ImageInvocation"] | components["schemas"]["RandomRangeInvocation"] | components["schemas"]["SDXLRefinerModelLoaderInvocation"] | components["schemas"]["LineartImageProcessorInvocation"] | components["schemas"]["ConditioningCollectionInvocation"] | components["schemas"]["ESRGANInvocation"] | components["schemas"]["ZoeDepthImageProcessorInvocation"] | components["schemas"]["MaskCombineInvocation"] | components["schemas"]["InfillColorInvocation"] | components["schemas"]["RandomIntInvocation"] | components["schemas"]["FloatMathInvocation"] | components["schemas"]["BooleanCollectionInvocation"] | components["schemas"]["CenterPadCropInvocation"] | components["schemas"]["CannyImageProcessorInvocation"] | components["schemas"]["SaveImageInvocation"] | components["schemas"]["CollectInvocation"] | components["schemas"]["ShowImageInvocation"] | components["schemas"]["TileResamplerProcessorInvocation"] | components["schemas"]["StringSplitInvocation"] | components["schemas"]["ImageMultiplyInvocation"] | components["schemas"]["StringCollectionInvocation"] | components["schemas"]["ImageHueAdjustmentInvocation"] | components["schemas"]["AddInvocation"] | components["schemas"]["NoiseInvocation"] | components["schemas"]["UnsharpMaskInvocation"] | components["schemas"]["LatentsInvocation"] | components["schemas"]["MediapipeFaceProcessorInvocation"] | components["schemas"]["InfillTileInvocation"] | components["schemas"]["ResizeLatentsInvocation"] | components["schemas"]["RoundInvocation"] | components["schemas"]["MetadataInvocation"] | components["schemas"]["ImageChannelInvocation"] | components["schemas"]["PromptsFromFileInvocation"] | components["schemas"]["ScaleLatentsInvocation"] | components["schemas"]["PidiImageProcessorInvocation"] | components["schemas"]["CLIPSkipInvocation"] | components["schemas"]["RandomFloatInvocation"] | components["schemas"]["ImageChannelOffsetInvocation"] | components["schemas"]["RangeInvocation"] | components["schemas"]["DepthAnythingImageProcessorInvocation"] | components["schemas"]["ImageScaleInvocation"] | components["schemas"]["ColorCorrectInvocation"] | components["schemas"]["ImageWatermarkInvocation"] | components["schemas"]["VAELoaderInvocation"] | components["schemas"]["CreateDenoiseMaskInvocation"] | components["schemas"]["ImageCollectionInvocation"] | components["schemas"]["LineartAnimeImageProcessorInvocation"] | components["schemas"]["ImageChannelMultiplyInvocation"] | components["schemas"]["InfillPatchMatchInvocation"] | components["schemas"]["T2IAdapterInvocation"] | components["schemas"]["FreeUInvocation"] | components["schemas"]["ImageResizeInvocation"] | components["schemas"]["TileToPropertiesInvocation"] | components["schemas"]["CreateGradientMaskInvocation"] | components["schemas"]["ImagePasteInvocation"] | components["schemas"]["DWOpenposeImageProcessorInvocation"] | components["schemas"]["FaceMaskInvocation"] | components["schemas"]["IterateInvocation"] | components["schemas"]["NormalbaeImageProcessorInvocation"] | components["schemas"]["LaMaInfillInvocation"] | components["schemas"]["SubtractInvocation"] | components["schemas"]["FloatToIntegerInvocation"] | components["schemas"]["ImageCropInvocation"] | components["schemas"]["BooleanInvocation"] | components["schemas"]["ConditioningInvocation"] | components["schemas"]["MaskFromIDInvocation"] | components["schemas"]["DenoiseLatentsInvocation"] | components["schemas"]["BlankImageInvocation"] | components["schemas"]["ImageLerpInvocation"] | components["schemas"]["FaceIdentifierInvocation"] | components["schemas"]["LatentsToImageInvocation"] | components["schemas"]["CropLatentsCoreInvocation"] | components["schemas"]["MaskFromAlphaInvocation"] | components["schemas"]["CompelInvocation"] | components["schemas"]["CV2InfillInvocation"] | components["schemas"]["MainModelLoaderInvocation"] | components["schemas"]["StringSplitNegInvocation"] | components["schemas"]["MultiplyInvocation"] | components["schemas"]["IntegerMathInvocation"] | components["schemas"]["IntegerInvocation"] | components["schemas"]["ImageConvertInvocation"] | components["schemas"]["ImageInverseLerpInvocation"] | components["schemas"]["MlsdImageProcessorInvocation"] | components["schemas"]["StringJoinInvocation"] | components["schemas"]["IdealSizeInvocation"] | components["schemas"]["ColorInvocation"] | components["schemas"]["ColorMapImageProcessorInvocation"] | components["schemas"]["LeresImageProcessorInvocation"] | components["schemas"]["LatentsCollectionInvocation"] | components["schemas"]["MergeTilesToImageInvocation"] | components["schemas"]["MidasDepthImageProcessorInvocation"] | components["schemas"]["MaskEdgeInvocation"] | components["schemas"]["FaceOffInvocation"] | components["schemas"]["CalculateImageTilesInvocation"] | components["schemas"]["SeamlessModeInvocation"] | components["schemas"]["ImageBlurInvocation"] | components["schemas"]["CalculateImageTilesEvenSplitInvocation"] | components["schemas"]["IntegerCollectionInvocation"] | components["schemas"]["ImageNSFWBlurInvocation"] | components["schemas"]["SDXLCompelPromptInvocation"] | components["schemas"]["StepParamEasingInvocation"] | components["schemas"]["StringJoinThreeInvocation"] | components["schemas"]["CoreMetadataInvocation"] | components["schemas"]["PairTileImageInvocation"] | components["schemas"]["IPAdapterInvocation"] | components["schemas"]["CalculateImageTilesMinimumOverlapInvocation"] | components["schemas"]["BlendLatentsInvocation"] | components["schemas"]["FloatCollectionInvocation"] | components["schemas"]["SDXLModelLoaderInvocation"] | components["schemas"]["DivideInvocation"] | components["schemas"]["FloatInvocation"] | components["schemas"]["SDXLLoRALoaderInvocation"] | components["schemas"]["SchedulerInvocation"] | components["schemas"]["ImageToLatentsInvocation"] | components["schemas"]["HedImageProcessorInvocation"] | components["schemas"]["CanvasPasteBackInvocation"] | components["schemas"]["CvInpaintInvocation"] | components["schemas"]["DynamicPromptInvocation"] | components["schemas"]["SDXLRefinerCompelPromptInvocation"] | components["schemas"]["LoRALoaderInvocation"]; }; /** * Edges @@ -4149,7 +4149,7 @@ export type components = { * @description The results of node executions */ results: { - [key: string]: components["schemas"]["ConditioningOutput"] | components["schemas"]["SDXLModelLoaderOutput"] | components["schemas"]["ModelLoaderOutput"] | components["schemas"]["LatentsCollectionOutput"] | components["schemas"]["FloatOutput"] | components["schemas"]["CalculateImageTilesOutput"] | components["schemas"]["ColorOutput"] | components["schemas"]["IntegerOutput"] | components["schemas"]["GradientMaskOutput"] | components["schemas"]["ImageCollectionOutput"] | components["schemas"]["IPAdapterOutput"] | components["schemas"]["MetadataItemOutput"] | components["schemas"]["SDXLRefinerModelLoaderOutput"] | components["schemas"]["ImageOutput"] | components["schemas"]["IdealSizeOutput"] | components["schemas"]["ColorCollectionOutput"] | components["schemas"]["T2IAdapterOutput"] | components["schemas"]["StringPosNegOutput"] | components["schemas"]["IterateInvocationOutput"] | components["schemas"]["UNetOutput"] | components["schemas"]["TileToPropertiesOutput"] | components["schemas"]["SeamlessModeOutput"] | components["schemas"]["StringOutput"] | components["schemas"]["LatentsOutput"] | components["schemas"]["NoiseOutput"] | components["schemas"]["CollectInvocationOutput"] | components["schemas"]["SDXLLoRALoaderOutput"] | components["schemas"]["CLIPSkipInvocationOutput"] | components["schemas"]["SchedulerOutput"] | components["schemas"]["DenoiseMaskOutput"] | components["schemas"]["FaceOffOutput"] | components["schemas"]["PairTileImageOutput"] | components["schemas"]["StringCollectionOutput"] | components["schemas"]["String2Output"] | components["schemas"]["LoRALoaderOutput"] | components["schemas"]["BooleanOutput"] | components["schemas"]["VAEOutput"] | components["schemas"]["ControlOutput"] | components["schemas"]["FaceMaskOutput"] | components["schemas"]["IntegerCollectionOutput"] | components["schemas"]["ConditioningCollectionOutput"] | components["schemas"]["CLIPOutput"] | components["schemas"]["MetadataOutput"] | components["schemas"]["BooleanCollectionOutput"] | components["schemas"]["FloatCollectionOutput"]; + [key: string]: components["schemas"]["CLIPOutput"] | components["schemas"]["StringPosNegOutput"] | components["schemas"]["IdealSizeOutput"] | components["schemas"]["SDXLLoRALoaderOutput"] | components["schemas"]["LatentsOutput"] | components["schemas"]["BooleanOutput"] | components["schemas"]["SeamlessModeOutput"] | components["schemas"]["DenoiseMaskOutput"] | components["schemas"]["SchedulerOutput"] | components["schemas"]["VAEOutput"] | components["schemas"]["IntegerCollectionOutput"] | components["schemas"]["ColorCollectionOutput"] | components["schemas"]["ConditioningOutput"] | components["schemas"]["NoiseOutput"] | components["schemas"]["TileToPropertiesOutput"] | components["schemas"]["String2Output"] | components["schemas"]["IntegerOutput"] | components["schemas"]["GradientMaskOutput"] | components["schemas"]["FloatOutput"] | components["schemas"]["FloatCollectionOutput"] | components["schemas"]["LatentsCollectionOutput"] | components["schemas"]["ControlOutput"] | components["schemas"]["FaceOffOutput"] | components["schemas"]["MetadataOutput"] | components["schemas"]["StringOutput"] | components["schemas"]["IterateInvocationOutput"] | components["schemas"]["LoRALoaderOutput"] | components["schemas"]["StringCollectionOutput"] | components["schemas"]["IPAdapterOutput"] | components["schemas"]["ConditioningCollectionOutput"] | components["schemas"]["PairTileImageOutput"] | components["schemas"]["MetadataItemOutput"] | components["schemas"]["CLIPSkipInvocationOutput"] | components["schemas"]["SDXLRefinerModelLoaderOutput"] | components["schemas"]["BooleanCollectionOutput"] | components["schemas"]["UNetOutput"] | components["schemas"]["T2IAdapterOutput"] | components["schemas"]["CollectInvocationOutput"] | components["schemas"]["SDXLModelLoaderOutput"] | components["schemas"]["FaceMaskOutput"] | components["schemas"]["ImageCollectionOutput"] | components["schemas"]["ModelLoaderOutput"] | components["schemas"]["ImageOutput"] | components["schemas"]["ColorOutput"] | components["schemas"]["CalculateImageTilesOutput"]; }; /** * Errors From 5f01de1993ea44952a1ca4aabcbb7618f7a5f5bf Mon Sep 17 00:00:00 2001 From: blessedcoolant <54517381+blessedcoolant@users.noreply.github.com> Date: Wed, 3 Apr 2024 20:38:29 +0530 Subject: [PATCH 31/89] chore: ruff and lint fixes --- invokeai/backend/model_manager/probe.py | 4 +++- invokeai/frontend/web/src/features/metadata/util/parsers.ts | 1 + 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/invokeai/backend/model_manager/probe.py b/invokeai/backend/model_manager/probe.py index 7fc8c99e29..7aa650afaa 100644 --- a/invokeai/backend/model_manager/probe.py +++ b/invokeai/backend/model_manager/probe.py @@ -707,7 +707,9 @@ class ControlNetFolderProbe(FolderProbeBase): else ( BaseModelType.StableDiffusion2 if dimension == 1024 - else BaseModelType.StableDiffusionXL if dimension == 2048 else None + else BaseModelType.StableDiffusionXL + if dimension == 2048 + else None ) ) if not base_model: diff --git a/invokeai/frontend/web/src/features/metadata/util/parsers.ts b/invokeai/frontend/web/src/features/metadata/util/parsers.ts index c7c9616bd0..635a63a8de 100644 --- a/invokeai/frontend/web/src/features/metadata/util/parsers.ts +++ b/invokeai/frontend/web/src/features/metadata/util/parsers.ts @@ -372,6 +372,7 @@ const parseIPAdapter: MetadataParseFunc = async (metada type: 'ip_adapter', isEnabled: true, model: zModelIdentifierField.parse(ipAdapterModel), + clipVisionModel: 'ViT-H', controlImage: image?.image_name ?? null, weight: weight ?? initialIPAdapter.weight, beginStepPct: begin_step_percent ?? initialIPAdapter.beginStepPct, From be574cb764e73c44b5b18144170b5b38f00f7869 Mon Sep 17 00:00:00 2001 From: blessedcoolant <54517381+blessedcoolant@users.noreply.github.com> Date: Wed, 3 Apr 2024 22:38:28 +0530 Subject: [PATCH 32/89] fix: incorrect suffix check in ip adapter checkpoint file --- invokeai/backend/ip_adapter/ip_adapter.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/invokeai/backend/ip_adapter/ip_adapter.py b/invokeai/backend/ip_adapter/ip_adapter.py index 62ee81f6b2..f3be042146 100644 --- a/invokeai/backend/ip_adapter/ip_adapter.py +++ b/invokeai/backend/ip_adapter/ip_adapter.py @@ -209,7 +209,7 @@ class IPAdapterPlusXL(IPAdapterPlus): def load_ip_adapter_tensors(ip_adapter_ckpt_path: pathlib.Path, device: str) -> IPAdapterStateDict: state_dict: IPAdapterStateDict = {"ip_adapter": {}, "image_proj": {}} - if ip_adapter_ckpt_path.stem == "safetensors": + if ip_adapter_ckpt_path.suffix == ".safetensors": model = safetensors.torch.load_file(ip_adapter_ckpt_path, device=device) for key in model.keys(): if key.startswith("image_proj."): From 85f53f94f81bdb2d4e399f1196bc9b18f35b4c9d Mon Sep 17 00:00:00 2001 From: psychedelicious <4822129+psychedelicious@users.noreply.github.com> Date: Wed, 3 Apr 2024 10:15:39 +1100 Subject: [PATCH 33/89] feat(mm): include needed vs free in OOM Gives us a bit more visibility into these errors, which seem to be popping up more frequently with the new MM. --- .../model_manager/load/model_cache/model_cache_default.py | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/invokeai/backend/model_manager/load/model_cache/model_cache_default.py b/invokeai/backend/model_manager/load/model_cache/model_cache_default.py index 6173d48abe..b8312f619a 100644 --- a/invokeai/backend/model_manager/load/model_cache/model_cache_default.py +++ b/invokeai/backend/model_manager/load/model_cache/model_cache_default.py @@ -429,4 +429,8 @@ class ModelCache(ModelCacheBase[AnyModel]): ) free_mem, _ = torch.cuda.mem_get_info(torch.device(vram_device)) if needed_size > free_mem: - raise torch.cuda.OutOfMemoryError + needed_gb = round(needed_size / GIG, 2) + free_gb = round(free_mem / GIG, 2) + raise torch.cuda.OutOfMemoryError( + f"Insufficient VRAM to load model, requested {needed_gb}GB but only had {free_gb}GB free" + ) From 4927d1b7c9f3822c0c4586de4dfd9f1a30f989d3 Mon Sep 17 00:00:00 2001 From: Mary Hipp Date: Wed, 3 Apr 2024 11:26:18 -0400 Subject: [PATCH 34/89] add some test IDs for accordion targeting --- .../AdvancedSettingsAccordion/AdvancedSettingsAccordion.tsx | 2 +- .../ControlSettingsAccordion/ControlSettingsAccordion.tsx | 2 +- .../GenerationSettingsAccordion/GenerationSettingsAccordion.tsx | 2 +- .../ImageSettingsAccordion/ImageSettingsAccordion.tsx | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) diff --git a/invokeai/frontend/web/src/features/settingsAccordions/components/AdvancedSettingsAccordion/AdvancedSettingsAccordion.tsx b/invokeai/frontend/web/src/features/settingsAccordions/components/AdvancedSettingsAccordion/AdvancedSettingsAccordion.tsx index 05ca73927c..087efba616 100644 --- a/invokeai/frontend/web/src/features/settingsAccordions/components/AdvancedSettingsAccordion/AdvancedSettingsAccordion.tsx +++ b/invokeai/frontend/web/src/features/settingsAccordions/components/AdvancedSettingsAccordion/AdvancedSettingsAccordion.tsx @@ -61,7 +61,7 @@ export const AdvancedSettingsAccordion = memo(() => { return ( - + diff --git a/invokeai/frontend/web/src/features/settingsAccordions/components/ControlSettingsAccordion/ControlSettingsAccordion.tsx b/invokeai/frontend/web/src/features/settingsAccordions/components/ControlSettingsAccordion/ControlSettingsAccordion.tsx index a02eadef36..3cddc927e9 100644 --- a/invokeai/frontend/web/src/features/settingsAccordions/components/ControlSettingsAccordion/ControlSettingsAccordion.tsx +++ b/invokeai/frontend/web/src/features/settingsAccordions/components/ControlSettingsAccordion/ControlSettingsAccordion.tsx @@ -77,7 +77,7 @@ export const ControlSettingsAccordion: React.FC = memo(() => { return ( - + @@ -116,7 +157,7 @@ export const ScanModelsResults = ({ results }: ScanModelResultsProps) => { {filteredResults.map((result) => ( - + ))} From c4a6d3ddc0f1e9ff38c859ec6f07d28135e4b7de Mon Sep 17 00:00:00 2001 From: psychedelicious <4822129+psychedelicious@users.noreply.github.com> Date: Wed, 3 Apr 2024 19:01:22 +1100 Subject: [PATCH 42/89] docs: update FAQ for missing models solution This will be fairly common in v4 updates. The root cause is models not being added to the `models.yaml` file in v3, so we don't correctly migrate the models to the db. The docs describe how to use `Scan Folder` to restore missing models. --- docs/help/FAQ.md | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/docs/help/FAQ.md b/docs/help/FAQ.md index 28401b5661..458770e41a 100644 --- a/docs/help/FAQ.md +++ b/docs/help/FAQ.md @@ -18,6 +18,22 @@ Note that any releases marked as _pre-release_ are in a beta state. You may expe The Model Manager tab in the UI provides a few ways to install models, including using your already-downloaded models. You'll see a popup directing you there on first startup. For more information, see the [model install docs]. +## Missing models after updating to v4 + +If you find some models are missing after updating to v4, it's likely they weren't correctly registered before the update and didn't get picked up in the migration. + +You can use the `Scan Folder` tab in the Model Manager UI to fix this. The models will either be in the old, now-unused `autoimport` folder, or your `models` folder. + +- Find and copy your install's old `autoimport` folder path, install the main install folder. +- Go to the Model Manager and click `Scan Folder`. +- Paste the path and scan. +- IMPORTANT: Uncheck `Inplace install`. +- Click `Install All` to install all found models, or just install the models you want. + +Next, find and copy your install's `models` folder path (this could be your custom models folder path, or the `models` folder inside the main install folder). + +Follow the same steps to scan and import the missing models. + ## Slow generation - Check the [system requirements] to ensure that your system is capable of generating images. From 9fdfd4267cbc66526450648100c36d8780995fb5 Mon Sep 17 00:00:00 2001 From: psychedelicious <4822129+psychedelicious@users.noreply.github.com> Date: Wed, 3 Apr 2024 15:15:56 +1100 Subject: [PATCH 43/89] fix(ui): fix model name overflow Closes #3897 --- .../subpanels/ModelManagerPanel/ModelListItem.tsx | 8 +++++--- .../modelManagerV2/subpanels/ModelPanel/Model.tsx | 6 +++--- .../modelManagerV2/subpanels/ModelPanel/ModelAttrView.tsx | 4 +++- 3 files changed, 11 insertions(+), 7 deletions(-) diff --git a/invokeai/frontend/web/src/features/modelManagerV2/subpanels/ModelManagerPanel/ModelListItem.tsx b/invokeai/frontend/web/src/features/modelManagerV2/subpanels/ModelManagerPanel/ModelListItem.tsx index 995a599048..c9d0c03ed8 100644 --- a/invokeai/frontend/web/src/features/modelManagerV2/subpanels/ModelManagerPanel/ModelListItem.tsx +++ b/invokeai/frontend/web/src/features/modelManagerV2/subpanels/ModelManagerPanel/ModelListItem.tsx @@ -90,11 +90,13 @@ const ModelListItem = (props: ModelListItemProps) => { cursor="pointer" onClick={handleSelectModel} > - + - + - {model.name} + + {model.name} + diff --git a/invokeai/frontend/web/src/features/modelManagerV2/subpanels/ModelPanel/Model.tsx b/invokeai/frontend/web/src/features/modelManagerV2/subpanels/ModelPanel/Model.tsx index 6c5583ade9..d95eed8d24 100644 --- a/invokeai/frontend/web/src/features/modelManagerV2/subpanels/ModelPanel/Model.tsx +++ b/invokeai/frontend/web/src/features/modelManagerV2/subpanels/ModelPanel/Model.tsx @@ -87,9 +87,9 @@ export const Model = () => { - + - + {data.name} @@ -114,7 +114,7 @@ export const Model = () => { )} {data.source && ( - + {t('modelManager.source')}: {data?.source} )} diff --git a/invokeai/frontend/web/src/features/modelManagerV2/subpanels/ModelPanel/ModelAttrView.tsx b/invokeai/frontend/web/src/features/modelManagerV2/subpanels/ModelPanel/ModelAttrView.tsx index f45bfca993..ebdedffebf 100644 --- a/invokeai/frontend/web/src/features/modelManagerV2/subpanels/ModelPanel/ModelAttrView.tsx +++ b/invokeai/frontend/web/src/features/modelManagerV2/subpanels/ModelPanel/ModelAttrView.tsx @@ -9,7 +9,9 @@ export const ModelAttrView = ({ label, value }: Props) => { return ( {label} - {value || '-'} + + {value || '-'} + ); }; From 52b58b4a80a35da539aac619174f2f34fc463baa Mon Sep 17 00:00:00 2001 From: psychedelicious <4822129+psychedelicious@users.noreply.github.com> Date: Wed, 3 Apr 2024 20:06:40 +1100 Subject: [PATCH 44/89] feat(installer): remove extra GPU options - Remove `CUDA_AND_DML`. This was for onnx, which we have since removed. - Remove `AUTODETECT`. This option causes problems for windows users, as it falls back on default pypi index resulting in a non-CUDA torch being installed. - Add more explicit settings for extra index URL, based on the torch website - Fix bug where `xformers` wasn't installed on linux and/or windows when autodetect was selected --- installer/lib/installer.py | 19 +++++++++++++------ installer/lib/messages.py | 21 +-------------------- 2 files changed, 14 insertions(+), 26 deletions(-) diff --git a/installer/lib/installer.py b/installer/lib/installer.py index 45bc764023..5297366ccb 100644 --- a/installer/lib/installer.py +++ b/installer/lib/installer.py @@ -404,22 +404,29 @@ def get_torch_source() -> Tuple[str | None, str | None]: # device can be one of: "cuda", "rocm", "cpu", "cuda_and_dml, autodetect" device = select_gpu() + # The correct extra index URLs for torch are inconsistent, see https://pytorch.org/get-started/locally/#start-locally + url = None - optional_modules = "[onnx]" + optional_modules: str | None = None if OS == "Linux": if device.value == "rocm": url = "https://download.pytorch.org/whl/rocm5.6" elif device.value == "cpu": url = "https://download.pytorch.org/whl/cpu" - + elif device.value == "cuda": + # CUDA uses the default PyPi index + optional_modules = "[xformers,onnx-cuda]" elif OS == "Windows": if device.value == "cuda": url = "https://download.pytorch.org/whl/cu121" optional_modules = "[xformers,onnx-cuda]" - if device.value == "cuda_and_dml": - url = "https://download.pytorch.org/whl/cu121" - optional_modules = "[xformers,onnx-directml]" + elif device.value == "cpu": + # CPU uses the default PyPi index, no optional modules + pass + elif OS == "Darwin": + # macOS uses the default PyPi index, no optional modules + pass - # in all other cases, Torch wheels should be coming from PyPi as of Torch 1.13 + # Fall back to defaults return (url, optional_modules) diff --git a/installer/lib/messages.py b/installer/lib/messages.py index 257a587d9c..dcd65a9813 100644 --- a/installer/lib/messages.py +++ b/installer/lib/messages.py @@ -207,10 +207,8 @@ def dest_path(dest: Optional[str | Path] = None) -> Path | None: class GpuType(Enum): CUDA = "cuda" - CUDA_AND_DML = "cuda_and_dml" ROCM = "rocm" CPU = "cpu" - AUTODETECT = "autodetect" def select_gpu() -> GpuType: @@ -226,10 +224,6 @@ def select_gpu() -> GpuType: "an [gold1 b]NVIDIA[/] GPU (using CUDA™)", GpuType.CUDA, ) - nvidia_with_dml = ( - "an [gold1 b]NVIDIA[/] GPU (using CUDA™, and DirectML™ for ONNX) -- ALPHA", - GpuType.CUDA_AND_DML, - ) amd = ( "an [gold1 b]AMD[/] GPU (using ROCm™)", GpuType.ROCM, @@ -238,27 +232,19 @@ def select_gpu() -> GpuType: "Do not install any GPU support, use CPU for generation (slow)", GpuType.CPU, ) - autodetect = ( - "I'm not sure what to choose", - GpuType.AUTODETECT, - ) options = [] if OS == "Windows": - options = [nvidia, nvidia_with_dml, cpu] + options = [nvidia, cpu] if OS == "Linux": options = [nvidia, amd, cpu] elif OS == "Darwin": options = [cpu] - # future CoreML? if len(options) == 1: print(f'Your platform [gold1]{OS}-{ARCH}[/] only supports the "{options[0][1]}" driver. Proceeding with that.') return options[0][1] - # "I don't know" is always added the last option - options.append(autodetect) # type: ignore - options = {str(i): opt for i, opt in enumerate(options, 1)} console.rule(":space_invader: GPU (Graphics Card) selection :space_invader:") @@ -292,11 +278,6 @@ def select_gpu() -> GpuType: ), ) - if options[choice][1] is GpuType.AUTODETECT: - console.print( - "No problem. We will install CUDA support first :crossed_fingers: If Invoke does not detect a GPU, please re-run the installer and select one of the other GPU types." - ) - return options[choice][1] From f887e030bbe4de332e897a9b35a351a17192f5f4 Mon Sep 17 00:00:00 2001 From: psychedelicious <4822129+psychedelicious@users.noreply.github.com> Date: Wed, 3 Apr 2024 20:10:05 +1100 Subject: [PATCH 45/89] docs: update 010_INSTALL_AUTOMATED.md Remove reference to the autodetect GPU device option. --- docs/installation/010_INSTALL_AUTOMATED.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/installation/010_INSTALL_AUTOMATED.md b/docs/installation/010_INSTALL_AUTOMATED.md index 6b96ec0fe8..5e2db65d7b 100644 --- a/docs/installation/010_INSTALL_AUTOMATED.md +++ b/docs/installation/010_INSTALL_AUTOMATED.md @@ -44,7 +44,7 @@ The installation process is simple, with a few prompts: - Select the version to install. Unless you have a specific reason to install a specific version, select the default (the latest version). - Select location for the install. Be sure you have enough space in this folder for the base application, as described in the [installation requirements]. -- Select a GPU device. If you are unsure, you can let the installer figure it out. +- Select a GPU device. !!! info "Slow Installation" From 9c51abb46e427fb78ad8571d8c29f7546cad49d1 Mon Sep 17 00:00:00 2001 From: psychedelicious <4822129+psychedelicious@users.noreply.github.com> Date: Wed, 3 Apr 2024 15:19:52 +1100 Subject: [PATCH 46/89] fix(config): get root from venv This logic was a bit wonky. It only selected the `venv` parent if there was already an `invokeai.yaml` file in it. Removed this constraint. --- invokeai/app/services/config/config_default.py | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/invokeai/app/services/config/config_default.py b/invokeai/app/services/config/config_default.py index 247835d533..79cbbc39af 100644 --- a/invokeai/app/services/config/config_default.py +++ b/invokeai/app/services/config/config_default.py @@ -317,11 +317,10 @@ class InvokeAIAppConfig(BaseSettings): @staticmethod def find_root() -> Path: """Choose the runtime root directory when not specified on command line or init file.""" - venv = Path(os.environ.get("VIRTUAL_ENV") or ".") if os.environ.get("INVOKEAI_ROOT"): root = Path(os.environ["INVOKEAI_ROOT"]) - elif any((venv.parent / x).exists() for x in [INIT_FILE, LEGACY_INIT_FILE]): - root = (venv.parent).resolve() + elif venv := os.environ.get("VIRTUAL_ENV", None): + root = Path(venv).parent.resolve() else: root = Path("~/invokeai").expanduser().resolve() return root From a0ae2f37d790fc1b266558365c7bb32b801e0de5 Mon Sep 17 00:00:00 2001 From: psychedelicious <4822129+psychedelicious@users.noreply.github.com> Date: Thu, 4 Apr 2024 09:55:18 +1100 Subject: [PATCH 47/89] docs: update 020_INSTALL_MANUAL.md Tidy verbiage around the invokeai root and how it is determined --- docs/installation/020_INSTALL_MANUAL.md | 15 ++++----------- 1 file changed, 4 insertions(+), 11 deletions(-) diff --git a/docs/installation/020_INSTALL_MANUAL.md b/docs/installation/020_INSTALL_MANUAL.md index 6370c7d5f2..70bc566a71 100644 --- a/docs/installation/020_INSTALL_MANUAL.md +++ b/docs/installation/020_INSTALL_MANUAL.md @@ -40,11 +40,11 @@ Before you start, go through the [installation requirements]. 1. Enter the root (invokeai) directory and create a virtual Python environment within it named `.venv`. - !!! info "Virtual Environment Location" + !!! warning "Virtual Environment Location" While you may create the virtual environment anywhere in the file system, we recommend that you create it within the root directory as shown here. This allows the application to automatically detect its data directories. - If you choose a different location for the venv, then you must set the `INVOKEAI_ROOT` environment variable or pass the directory using the `--root` CLI arg. + If you choose a different location for the venv, then you _must_ set the `INVOKEAI_ROOT` environment variable or specify the root directory using the `--root` CLI arg. ```terminal cd $INVOKEAI_ROOT @@ -126,16 +126,9 @@ Before you start, go through the [installation requirements]. Run `invokeai-web` to start the UI. You must activate the virtual environment before running the app. - If the virtual environment you selected is NOT inside `INVOKEAI_ROOT`, then you must specify the path to the root directory by adding - `--root_dir \path\to\invokeai`. + !!! warning - !!! tip - - You can permanently set the location of the runtime directory - by setting the environment variable `INVOKEAI_ROOT` to the - path of the directory. As mentioned previously, this is - recommended if your virtual environment is located outside of - your runtime directory. + If the virtual environment is _not_ inside the root directory, then you _must_ specify the path to the root directory with `--root_dir \path\to\invokeai` or the `INVOKEAI_ROOT` environment variable. ## Unsupported Conda Install From 98ab387e2bdab89887e67e90c7eeb6732012497e Mon Sep 17 00:00:00 2001 From: psychedelicious <4822129+psychedelicious@users.noreply.github.com> Date: Thu, 4 Apr 2024 10:14:50 +1100 Subject: [PATCH 48/89] docs: update 020_INSTALL_MANUAL.md Redo the install the package section. It was inaccurate with respect to extra index URLs. --- docs/installation/020_INSTALL_MANUAL.md | 38 +++++++++---------------- 1 file changed, 14 insertions(+), 24 deletions(-) diff --git a/docs/installation/020_INSTALL_MANUAL.md b/docs/installation/020_INSTALL_MANUAL.md index 70bc566a71..b7622c4f3e 100644 --- a/docs/installation/020_INSTALL_MANUAL.md +++ b/docs/installation/020_INSTALL_MANUAL.md @@ -6,11 +6,7 @@ ## Introduction -!!! tip "Conda" - - As of InvokeAI v2.3.0 installation using the `conda` package manager is no longer being supported. It will likely still work, but we are not testing this installation method. - -InvokeAI is distributed as a python package on PyPI, installable with `pip`. There are a few things that are handled by the installer that you'll need to manage manually, described in this guide. +InvokeAI is distributed as a python package on PyPI, installable with `pip`. There are a few things that are handled by the installer and launcher that you'll need to manage manually, described in this guide. ### Requirements @@ -81,31 +77,23 @@ Before you start, go through the [installation requirements]. python3 -m pip install --upgrade pip ``` -1. Install the InvokeAI Package. The `--extra-index-url` option is used to select the correct `torch` backend: +1. Install the InvokeAI Package. The base command is `pip install InvokeAI --use-pep517`, but you may need to change this depending on your system and the desired features. - === "CUDA (NVidia)" + - You may need to provide an [extra index URL]. Select your platform configuration using [this tool on the PyTorch website]. Copy the `--extra-index-url` string from this and append it to your install command. - ```bash - pip install "InvokeAI[xformers]" --use-pep517 --extra-index-url https://download.pytorch.org/whl/cu121 - ``` + !!! example "Install with an extra index URL" - === "ROCm (AMD)" + ```bash + pip install InvokeAI --use-pep517 --extra-index-url https://download.pytorch.org/whl/cu121 + ``` - ```bash - pip install InvokeAI --use-pep517 --extra-index-url https://download.pytorch.org/whl/rocm5.6 - ``` + - If you have a CUDA GPU and want to install with `xformers`, you need to add an option to the package name. Note that `xformers` is not necessary. PyTorch includes an implementation of the SDP attention algorithm with the same performance. - === "CPU (Intel Macs & non-GPU systems)" + !!! example "Install with `xformers`" - ```bash - pip install InvokeAI --use-pep517 --extra-index-url https://download.pytorch.org/whl/cpu - ``` - - === "MPS (Apple Silicon)" - - ```bash - pip install InvokeAI --use-pep517 - ``` + ```bash + pip install "InvokeAI[xformers]" --use-pep517 + ``` 1. Deactivate and reactivate your runtime directory so that the invokeai-specific commands become available in the environment: @@ -153,3 +141,5 @@ Note that if you run into problems with the Conda installation, the InvokeAI staff will **not** be able to help you out. Caveat Emptor! [installation requirements]: INSTALL_REQUIREMENTS.md +[this tool on the PyTorch website]: https://pytorch.org/get-started/locally/#start-locally +[extra index URL]: https://pip.pypa.io/en/stable/cli/pip_install/#cmdoption-extra-index-url From 38718d8c65ca27403951a90fae27490cafaa48de Mon Sep 17 00:00:00 2001 From: psychedelicious <4822129+psychedelicious@users.noreply.github.com> Date: Thu, 4 Apr 2024 10:58:25 +1100 Subject: [PATCH 49/89] docs: update 020_INSTALL_MANUAL.md, remove conda section --- docs/installation/020_INSTALL_MANUAL.md | 26 ------------------------- 1 file changed, 26 deletions(-) diff --git a/docs/installation/020_INSTALL_MANUAL.md b/docs/installation/020_INSTALL_MANUAL.md index b7622c4f3e..36859a5795 100644 --- a/docs/installation/020_INSTALL_MANUAL.md +++ b/docs/installation/020_INSTALL_MANUAL.md @@ -117,29 +117,3 @@ Before you start, go through the [installation requirements]. !!! warning If the virtual environment is _not_ inside the root directory, then you _must_ specify the path to the root directory with `--root_dir \path\to\invokeai` or the `INVOKEAI_ROOT` environment variable. - -## Unsupported Conda Install - -Congratulations, you found the "secret" Conda installation instructions. If you really **really** want to use Conda with InvokeAI, you can do so using this unsupported recipe: - -```sh -mkdir ~/invokeai -conda create -n invokeai python=3.11 -conda activate invokeai -# Adjust this as described above for the appropriate torch backend -pip install InvokeAI[xformers] --use-pep517 --extra-index-url https://download.pytorch.org/whl/cu121 -invokeai-web --root ~/invokeai -``` - -The `pip install` command shown in this recipe is for Linux/Windows -systems with an NVIDIA GPU. See step (6) above for the command to use -with other platforms/GPU combinations. If you don't wish to pass the -`--root` argument to `invokeai` with each launch, you may set the -environment variable `INVOKEAI_ROOT` to point to the installation directory. - -Note that if you run into problems with the Conda installation, the InvokeAI -staff will **not** be able to help you out. Caveat Emptor! - -[installation requirements]: INSTALL_REQUIREMENTS.md -[this tool on the PyTorch website]: https://pytorch.org/get-started/locally/#start-locally -[extra index URL]: https://pip.pypa.io/en/stable/cli/pip_install/#cmdoption-extra-index-url From 8c15d14099d63d0265d4e12c9a606cade75f78a1 Mon Sep 17 00:00:00 2001 From: psychedelicious <4822129+psychedelicious@users.noreply.github.com> Date: Thu, 4 Apr 2024 14:59:20 +1100 Subject: [PATCH 50/89] fix: use locale encoding We have had a few bugs with v4 related to file encodings, especially on Windows. Windows uses its own character encodings instead of `utf-8`, often `cp1252`. Some characters cannot be decoded using `utf-8`, causing `UnicodeDecodeError`. There are a couple places where this can cause problems: - In the installer bootstrap, we install or upgrade `pip` and decode the result, using `subprocess`. The input to this includes the user's home dir. In #6105, the user had one of the problematic characters in their username. `subprocess` attempts and fails to decode the username, which crashes the installer. To fix this, we need to use `locale.getpreferredencoding()` when executing the command. - Similarly, in the model install service and config class, we attempt to load a yaml config file. If a problematic character is in the path to the file (which often includes the user's home dir), we can get the same error. One example is #6129 in which the models.yaml migration fails. To fix this, we need to open the file with `locale.getpreferredencoding()`. --- installer/lib/installer.py | 5 ++++- invokeai/app/services/config/config_default.py | 3 ++- invokeai/app/services/model_install/model_install_default.py | 4 +++- 3 files changed, 9 insertions(+), 3 deletions(-) diff --git a/installer/lib/installer.py b/installer/lib/installer.py index 5297366ccb..11823b413e 100644 --- a/installer/lib/installer.py +++ b/installer/lib/installer.py @@ -3,6 +3,7 @@ InvokeAI installer script """ +import locale import os import platform import re @@ -316,7 +317,9 @@ def upgrade_pip(venv_path: Path) -> str | None: python = str(venv_path.expanduser().resolve() / python) try: - result = subprocess.check_output([python, "-m", "pip", "install", "--upgrade", "pip"]).decode() + result = subprocess.check_output([python, "-m", "pip", "install", "--upgrade", "pip"]).decode( + encoding=locale.getpreferredencoding() + ) except subprocess.CalledProcessError as e: print(e) result = None diff --git a/invokeai/app/services/config/config_default.py b/invokeai/app/services/config/config_default.py index 79cbbc39af..f453a56584 100644 --- a/invokeai/app/services/config/config_default.py +++ b/invokeai/app/services/config/config_default.py @@ -3,6 +3,7 @@ from __future__ import annotations +import locale import os import re import shutil @@ -401,7 +402,7 @@ def load_and_migrate_config(config_path: Path) -> InvokeAIAppConfig: An instance of `InvokeAIAppConfig` with the loaded and migrated settings. """ assert config_path.suffix == ".yaml" - with open(config_path) as file: + with open(config_path, "rt", encoding=locale.getpreferredencoding()) as file: loaded_config_dict = yaml.safe_load(file) assert isinstance(loaded_config_dict, dict) diff --git a/invokeai/app/services/model_install/model_install_default.py b/invokeai/app/services/model_install/model_install_default.py index b5595d18c6..20cfc1c4ff 100644 --- a/invokeai/app/services/model_install/model_install_default.py +++ b/invokeai/app/services/model_install/model_install_default.py @@ -1,5 +1,6 @@ """Model installation class.""" +import locale import os import re import signal @@ -323,7 +324,8 @@ class ModelInstallService(ModelInstallServiceBase): legacy_models_yaml_path = Path(self._app_config.root_path, legacy_models_yaml_path) if legacy_models_yaml_path.exists(): - legacy_models_yaml = yaml.safe_load(legacy_models_yaml_path.read_text()) + with open(legacy_models_yaml_path, "rt", encoding=locale.getpreferredencoding()) as file: + legacy_models_yaml = yaml.safe_load(file) yaml_metadata = legacy_models_yaml.pop("__metadata__") yaml_version = yaml_metadata.get("version") From 8a32baf2dc3531e034d965de30fb42782e3cb075 Mon Sep 17 00:00:00 2001 From: psychedelicious <4822129+psychedelicious@users.noreply.github.com> Date: Thu, 4 Apr 2024 15:18:28 +1100 Subject: [PATCH 51/89] chore: v4.0.2 --- invokeai/version/invokeai_version.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/invokeai/version/invokeai_version.py b/invokeai/version/invokeai_version.py index 76ad18b89a..064c0b35b2 100644 --- a/invokeai/version/invokeai_version.py +++ b/invokeai/version/invokeai_version.py @@ -1 +1 @@ -__version__ = "4.0.1" +__version__ = "4.0.2" From 13027891d949a04a15be9dbb61a256359b50ad86 Mon Sep 17 00:00:00 2001 From: psychedelicious <4822129+psychedelicious@users.noreply.github.com> Date: Thu, 4 Apr 2024 20:41:23 +1100 Subject: [PATCH 52/89] fix(ui): discarding last single canvas image breaks canvas We need to reset the staging area if we are discarding the last image. --- .../web/src/features/canvas/store/canvasSlice.ts | 15 +++++++++++---- 1 file changed, 11 insertions(+), 4 deletions(-) diff --git a/invokeai/frontend/web/src/features/canvas/store/canvasSlice.ts b/invokeai/frontend/web/src/features/canvas/store/canvasSlice.ts index c5a60bdd26..68eba694ab 100644 --- a/invokeai/frontend/web/src/features/canvas/store/canvasSlice.ts +++ b/invokeai/frontend/web/src/features/canvas/store/canvasSlice.ts @@ -289,12 +289,19 @@ export const canvasSlice = createSlice({ const { images, selectedImageIndex } = state.layerState.stagingArea; pushToPrevLayerStates(state); - if (!images.length) { - return; - } - images.splice(selectedImageIndex, 1); + if (images.length === 0) { + pushToPrevLayerStates(state); + + state.layerState.stagingArea = deepClone(initialLayerState.stagingArea); + + state.futureLayerStates = []; + state.shouldShowStagingOutline = true; + state.shouldShowStagingImage = true; + state.batchIds = []; + } + if (selectedImageIndex >= images.length) { state.layerState.stagingArea.selectedImageIndex = images.length - 1; } From d284e0567ab5603a9759df6fb7dfd7a624396e77 Mon Sep 17 00:00:00 2001 From: blessedcoolant <54517381+blessedcoolant@users.noreply.github.com> Date: Thu, 4 Apr 2024 17:35:17 +0530 Subject: [PATCH 53/89] fix: ip adapter clip selection being broken --- invokeai/app/invocations/ip_adapter.py | 13 ++++--------- invokeai/frontend/web/src/services/api/schema.ts | 6 +++--- 2 files changed, 7 insertions(+), 12 deletions(-) diff --git a/invokeai/app/invocations/ip_adapter.py b/invokeai/app/invocations/ip_adapter.py index 40a667c9d0..0ac40e97fb 100644 --- a/invokeai/app/invocations/ip_adapter.py +++ b/invokeai/app/invocations/ip_adapter.py @@ -65,7 +65,7 @@ class IPAdapterInvocation(BaseInvocation): ui_order=-1, ui_type=UIType.IPAdapterModel, ) - clip_vision_model: Literal["auto", "ViT-H", "ViT-G"] = InputField( + clip_vision_model: Literal["ViT-H", "ViT-G"] = InputField( description="CLIP Vision model to use. Overrides model settings. Mandatory for checkpoint models.", default="auto", ui_order=2, @@ -96,14 +96,9 @@ class IPAdapterInvocation(BaseInvocation): ip_adapter_info = context.models.get_config(self.ip_adapter_model.key) assert isinstance(ip_adapter_info, (IPAdapterInvokeAIConfig, IPAdapterCheckpointConfig)) - if self.clip_vision_model == "auto": - if isinstance(ip_adapter_info, IPAdapterInvokeAIConfig): - image_encoder_model_id = ip_adapter_info.image_encoder_model_id - image_encoder_model_name = image_encoder_model_id.split("/")[-1].strip() - else: - raise RuntimeError( - "You need to set the appropriate CLIP Vision model for checkpoint IP Adapter models." - ) + if isinstance(ip_adapter_info, IPAdapterInvokeAIConfig): + image_encoder_model_id = ip_adapter_info.image_encoder_model_id + image_encoder_model_name = image_encoder_model_id.split("/")[-1].strip() else: image_encoder_model_name = CLIP_VISION_MODEL_MAP[self.clip_vision_model] diff --git a/invokeai/frontend/web/src/services/api/schema.ts b/invokeai/frontend/web/src/services/api/schema.ts index 6e3ddf678b..41e60fa029 100644 --- a/invokeai/frontend/web/src/services/api/schema.ts +++ b/invokeai/frontend/web/src/services/api/schema.ts @@ -4112,7 +4112,7 @@ export type components = { * @description The nodes in this graph */ nodes: { - [key: string]: components["schemas"]["StringInvocation"] | components["schemas"]["ControlNetInvocation"] | components["schemas"]["MetadataItemInvocation"] | components["schemas"]["MergeMetadataInvocation"] | components["schemas"]["ContentShuffleImageProcessorInvocation"] | components["schemas"]["FloatLinearRangeInvocation"] | components["schemas"]["RangeOfSizeInvocation"] | components["schemas"]["SegmentAnythingProcessorInvocation"] | components["schemas"]["StringReplaceInvocation"] | components["schemas"]["ImageInvocation"] | components["schemas"]["RandomRangeInvocation"] | components["schemas"]["SDXLRefinerModelLoaderInvocation"] | components["schemas"]["LineartImageProcessorInvocation"] | components["schemas"]["ConditioningCollectionInvocation"] | components["schemas"]["ESRGANInvocation"] | components["schemas"]["ZoeDepthImageProcessorInvocation"] | components["schemas"]["MaskCombineInvocation"] | components["schemas"]["InfillColorInvocation"] | components["schemas"]["RandomIntInvocation"] | components["schemas"]["FloatMathInvocation"] | components["schemas"]["BooleanCollectionInvocation"] | components["schemas"]["CenterPadCropInvocation"] | components["schemas"]["CannyImageProcessorInvocation"] | components["schemas"]["SaveImageInvocation"] | components["schemas"]["CollectInvocation"] | components["schemas"]["ShowImageInvocation"] | components["schemas"]["TileResamplerProcessorInvocation"] | components["schemas"]["StringSplitInvocation"] | components["schemas"]["ImageMultiplyInvocation"] | components["schemas"]["StringCollectionInvocation"] | components["schemas"]["ImageHueAdjustmentInvocation"] | components["schemas"]["AddInvocation"] | components["schemas"]["NoiseInvocation"] | components["schemas"]["UnsharpMaskInvocation"] | components["schemas"]["LatentsInvocation"] | components["schemas"]["MediapipeFaceProcessorInvocation"] | components["schemas"]["InfillTileInvocation"] | components["schemas"]["ResizeLatentsInvocation"] | components["schemas"]["RoundInvocation"] | components["schemas"]["MetadataInvocation"] | components["schemas"]["ImageChannelInvocation"] | components["schemas"]["PromptsFromFileInvocation"] | components["schemas"]["ScaleLatentsInvocation"] | components["schemas"]["PidiImageProcessorInvocation"] | components["schemas"]["CLIPSkipInvocation"] | components["schemas"]["RandomFloatInvocation"] | components["schemas"]["ImageChannelOffsetInvocation"] | components["schemas"]["RangeInvocation"] | components["schemas"]["DepthAnythingImageProcessorInvocation"] | components["schemas"]["ImageScaleInvocation"] | components["schemas"]["ColorCorrectInvocation"] | components["schemas"]["ImageWatermarkInvocation"] | components["schemas"]["VAELoaderInvocation"] | components["schemas"]["CreateDenoiseMaskInvocation"] | components["schemas"]["ImageCollectionInvocation"] | components["schemas"]["LineartAnimeImageProcessorInvocation"] | components["schemas"]["ImageChannelMultiplyInvocation"] | components["schemas"]["InfillPatchMatchInvocation"] | components["schemas"]["T2IAdapterInvocation"] | components["schemas"]["FreeUInvocation"] | components["schemas"]["ImageResizeInvocation"] | components["schemas"]["TileToPropertiesInvocation"] | components["schemas"]["CreateGradientMaskInvocation"] | components["schemas"]["ImagePasteInvocation"] | components["schemas"]["DWOpenposeImageProcessorInvocation"] | components["schemas"]["FaceMaskInvocation"] | components["schemas"]["IterateInvocation"] | components["schemas"]["NormalbaeImageProcessorInvocation"] | components["schemas"]["LaMaInfillInvocation"] | components["schemas"]["SubtractInvocation"] | components["schemas"]["FloatToIntegerInvocation"] | components["schemas"]["ImageCropInvocation"] | components["schemas"]["BooleanInvocation"] | components["schemas"]["ConditioningInvocation"] | components["schemas"]["MaskFromIDInvocation"] | components["schemas"]["DenoiseLatentsInvocation"] | components["schemas"]["BlankImageInvocation"] | components["schemas"]["ImageLerpInvocation"] | components["schemas"]["FaceIdentifierInvocation"] | components["schemas"]["LatentsToImageInvocation"] | components["schemas"]["CropLatentsCoreInvocation"] | components["schemas"]["MaskFromAlphaInvocation"] | components["schemas"]["CompelInvocation"] | components["schemas"]["CV2InfillInvocation"] | components["schemas"]["MainModelLoaderInvocation"] | components["schemas"]["StringSplitNegInvocation"] | components["schemas"]["MultiplyInvocation"] | components["schemas"]["IntegerMathInvocation"] | components["schemas"]["IntegerInvocation"] | components["schemas"]["ImageConvertInvocation"] | components["schemas"]["ImageInverseLerpInvocation"] | components["schemas"]["MlsdImageProcessorInvocation"] | components["schemas"]["StringJoinInvocation"] | components["schemas"]["IdealSizeInvocation"] | components["schemas"]["ColorInvocation"] | components["schemas"]["ColorMapImageProcessorInvocation"] | components["schemas"]["LeresImageProcessorInvocation"] | components["schemas"]["LatentsCollectionInvocation"] | components["schemas"]["MergeTilesToImageInvocation"] | components["schemas"]["MidasDepthImageProcessorInvocation"] | components["schemas"]["MaskEdgeInvocation"] | components["schemas"]["FaceOffInvocation"] | components["schemas"]["CalculateImageTilesInvocation"] | components["schemas"]["SeamlessModeInvocation"] | components["schemas"]["ImageBlurInvocation"] | components["schemas"]["CalculateImageTilesEvenSplitInvocation"] | components["schemas"]["IntegerCollectionInvocation"] | components["schemas"]["ImageNSFWBlurInvocation"] | components["schemas"]["SDXLCompelPromptInvocation"] | components["schemas"]["StepParamEasingInvocation"] | components["schemas"]["StringJoinThreeInvocation"] | components["schemas"]["CoreMetadataInvocation"] | components["schemas"]["PairTileImageInvocation"] | components["schemas"]["IPAdapterInvocation"] | components["schemas"]["CalculateImageTilesMinimumOverlapInvocation"] | components["schemas"]["BlendLatentsInvocation"] | components["schemas"]["FloatCollectionInvocation"] | components["schemas"]["SDXLModelLoaderInvocation"] | components["schemas"]["DivideInvocation"] | components["schemas"]["FloatInvocation"] | components["schemas"]["SDXLLoRALoaderInvocation"] | components["schemas"]["SchedulerInvocation"] | components["schemas"]["ImageToLatentsInvocation"] | components["schemas"]["HedImageProcessorInvocation"] | components["schemas"]["CanvasPasteBackInvocation"] | components["schemas"]["CvInpaintInvocation"] | components["schemas"]["DynamicPromptInvocation"] | components["schemas"]["SDXLRefinerCompelPromptInvocation"] | components["schemas"]["LoRALoaderInvocation"]; + [key: string]: components["schemas"]["AddInvocation"] | components["schemas"]["MaskCombineInvocation"] | components["schemas"]["BooleanInvocation"] | components["schemas"]["FloatCollectionInvocation"] | components["schemas"]["CreateGradientMaskInvocation"] | components["schemas"]["ImageMultiplyInvocation"] | components["schemas"]["ImageCropInvocation"] | components["schemas"]["MaskEdgeInvocation"] | components["schemas"]["IPAdapterInvocation"] | components["schemas"]["DenoiseLatentsInvocation"] | components["schemas"]["VAELoaderInvocation"] | components["schemas"]["LeresImageProcessorInvocation"] | components["schemas"]["ImageLerpInvocation"] | components["schemas"]["FloatLinearRangeInvocation"] | components["schemas"]["TileToPropertiesInvocation"] | components["schemas"]["CannyImageProcessorInvocation"] | components["schemas"]["ImageResizeInvocation"] | components["schemas"]["IntegerMathInvocation"] | components["schemas"]["FloatInvocation"] | components["schemas"]["ConditioningInvocation"] | components["schemas"]["SchedulerInvocation"] | components["schemas"]["ScaleLatentsInvocation"] | components["schemas"]["CollectInvocation"] | components["schemas"]["ImageInvocation"] | components["schemas"]["ImageConvertInvocation"] | components["schemas"]["NormalbaeImageProcessorInvocation"] | components["schemas"]["RoundInvocation"] | components["schemas"]["RandomRangeInvocation"] | components["schemas"]["FaceOffInvocation"] | components["schemas"]["FreeUInvocation"] | components["schemas"]["StringSplitInvocation"] | components["schemas"]["MlsdImageProcessorInvocation"] | components["schemas"]["InfillPatchMatchInvocation"] | components["schemas"]["PairTileImageInvocation"] | components["schemas"]["FaceMaskInvocation"] | components["schemas"]["MidasDepthImageProcessorInvocation"] | components["schemas"]["ImageHueAdjustmentInvocation"] | components["schemas"]["IdealSizeInvocation"] | components["schemas"]["LineartAnimeImageProcessorInvocation"] | components["schemas"]["ESRGANInvocation"] | components["schemas"]["UnsharpMaskInvocation"] | components["schemas"]["ImageChannelOffsetInvocation"] | components["schemas"]["ResizeLatentsInvocation"] | components["schemas"]["CalculateImageTilesMinimumOverlapInvocation"] | components["schemas"]["CreateDenoiseMaskInvocation"] | components["schemas"]["LatentsToImageInvocation"] | components["schemas"]["ZoeDepthImageProcessorInvocation"] | components["schemas"]["SubtractInvocation"] | components["schemas"]["T2IAdapterInvocation"] | components["schemas"]["StringSplitNegInvocation"] | components["schemas"]["SDXLRefinerCompelPromptInvocation"] | components["schemas"]["SeamlessModeInvocation"] | components["schemas"]["TileResamplerProcessorInvocation"] | components["schemas"]["ImageChannelMultiplyInvocation"] | components["schemas"]["StepParamEasingInvocation"] | components["schemas"]["MainModelLoaderInvocation"] | components["schemas"]["MetadataItemInvocation"] | components["schemas"]["CalculateImageTilesEvenSplitInvocation"] | components["schemas"]["ControlNetInvocation"] | components["schemas"]["DWOpenposeImageProcessorInvocation"] | components["schemas"]["ContentShuffleImageProcessorInvocation"] | components["schemas"]["FloatMathInvocation"] | components["schemas"]["NoiseInvocation"] | components["schemas"]["IntegerCollectionInvocation"] | components["schemas"]["DynamicPromptInvocation"] | components["schemas"]["CvInpaintInvocation"] | components["schemas"]["SDXLCompelPromptInvocation"] | components["schemas"]["CenterPadCropInvocation"] | components["schemas"]["CanvasPasteBackInvocation"] | components["schemas"]["MetadataInvocation"] | components["schemas"]["StringReplaceInvocation"] | components["schemas"]["DivideInvocation"] | components["schemas"]["ImageCollectionInvocation"] | components["schemas"]["ImageInverseLerpInvocation"] | components["schemas"]["RandomIntInvocation"] | components["schemas"]["LaMaInfillInvocation"] | components["schemas"]["MergeTilesToImageInvocation"] | components["schemas"]["ImageChannelInvocation"] | components["schemas"]["ImageWatermarkInvocation"] | components["schemas"]["BooleanCollectionInvocation"] | components["schemas"]["CalculateImageTilesInvocation"] | components["schemas"]["RangeInvocation"] | components["schemas"]["ImageToLatentsInvocation"] | components["schemas"]["FloatToIntegerInvocation"] | components["schemas"]["IntegerInvocation"] | components["schemas"]["InfillColorInvocation"] | components["schemas"]["StringInvocation"] | components["schemas"]["SegmentAnythingProcessorInvocation"] | components["schemas"]["ShowImageInvocation"] | components["schemas"]["RangeOfSizeInvocation"] | components["schemas"]["LineartImageProcessorInvocation"] | components["schemas"]["ImagePasteInvocation"] | components["schemas"]["PromptsFromFileInvocation"] | components["schemas"]["DepthAnythingImageProcessorInvocation"] | components["schemas"]["CV2InfillInvocation"] | components["schemas"]["CompelInvocation"] | components["schemas"]["HedImageProcessorInvocation"] | components["schemas"]["ImageBlurInvocation"] | components["schemas"]["CoreMetadataInvocation"] | components["schemas"]["PidiImageProcessorInvocation"] | components["schemas"]["MaskFromAlphaInvocation"] | components["schemas"]["SaveImageInvocation"] | components["schemas"]["ColorInvocation"] | components["schemas"]["StringJoinThreeInvocation"] | components["schemas"]["ColorMapImageProcessorInvocation"] | components["schemas"]["LatentsInvocation"] | components["schemas"]["CropLatentsCoreInvocation"] | components["schemas"]["MultiplyInvocation"] | components["schemas"]["ColorCorrectInvocation"] | components["schemas"]["LatentsCollectionInvocation"] | components["schemas"]["StringCollectionInvocation"] | components["schemas"]["ConditioningCollectionInvocation"] | components["schemas"]["BlendLatentsInvocation"] | components["schemas"]["ImageScaleInvocation"] | components["schemas"]["SDXLModelLoaderInvocation"] | components["schemas"]["FaceIdentifierInvocation"] | components["schemas"]["MergeMetadataInvocation"] | components["schemas"]["InfillTileInvocation"] | components["schemas"]["LoRALoaderInvocation"] | components["schemas"]["BlankImageInvocation"] | components["schemas"]["ImageNSFWBlurInvocation"] | components["schemas"]["MaskFromIDInvocation"] | components["schemas"]["IterateInvocation"] | components["schemas"]["SDXLRefinerModelLoaderInvocation"] | components["schemas"]["CLIPSkipInvocation"] | components["schemas"]["SDXLLoRALoaderInvocation"] | components["schemas"]["MediapipeFaceProcessorInvocation"] | components["schemas"]["RandomFloatInvocation"] | components["schemas"]["StringJoinInvocation"]; }; /** * Edges @@ -4149,7 +4149,7 @@ export type components = { * @description The results of node executions */ results: { - [key: string]: components["schemas"]["CLIPOutput"] | components["schemas"]["StringPosNegOutput"] | components["schemas"]["IdealSizeOutput"] | components["schemas"]["SDXLLoRALoaderOutput"] | components["schemas"]["LatentsOutput"] | components["schemas"]["BooleanOutput"] | components["schemas"]["SeamlessModeOutput"] | components["schemas"]["DenoiseMaskOutput"] | components["schemas"]["SchedulerOutput"] | components["schemas"]["VAEOutput"] | components["schemas"]["IntegerCollectionOutput"] | components["schemas"]["ColorCollectionOutput"] | components["schemas"]["ConditioningOutput"] | components["schemas"]["NoiseOutput"] | components["schemas"]["TileToPropertiesOutput"] | components["schemas"]["String2Output"] | components["schemas"]["IntegerOutput"] | components["schemas"]["GradientMaskOutput"] | components["schemas"]["FloatOutput"] | components["schemas"]["FloatCollectionOutput"] | components["schemas"]["LatentsCollectionOutput"] | components["schemas"]["ControlOutput"] | components["schemas"]["FaceOffOutput"] | components["schemas"]["MetadataOutput"] | components["schemas"]["StringOutput"] | components["schemas"]["IterateInvocationOutput"] | components["schemas"]["LoRALoaderOutput"] | components["schemas"]["StringCollectionOutput"] | components["schemas"]["IPAdapterOutput"] | components["schemas"]["ConditioningCollectionOutput"] | components["schemas"]["PairTileImageOutput"] | components["schemas"]["MetadataItemOutput"] | components["schemas"]["CLIPSkipInvocationOutput"] | components["schemas"]["SDXLRefinerModelLoaderOutput"] | components["schemas"]["BooleanCollectionOutput"] | components["schemas"]["UNetOutput"] | components["schemas"]["T2IAdapterOutput"] | components["schemas"]["CollectInvocationOutput"] | components["schemas"]["SDXLModelLoaderOutput"] | components["schemas"]["FaceMaskOutput"] | components["schemas"]["ImageCollectionOutput"] | components["schemas"]["ModelLoaderOutput"] | components["schemas"]["ImageOutput"] | components["schemas"]["ColorOutput"] | components["schemas"]["CalculateImageTilesOutput"]; + [key: string]: components["schemas"]["UNetOutput"] | components["schemas"]["ImageCollectionOutput"] | components["schemas"]["ColorCollectionOutput"] | components["schemas"]["String2Output"] | components["schemas"]["ControlOutput"] | components["schemas"]["StringCollectionOutput"] | components["schemas"]["DenoiseMaskOutput"] | components["schemas"]["SDXLRefinerModelLoaderOutput"] | components["schemas"]["IntegerOutput"] | components["schemas"]["SeamlessModeOutput"] | components["schemas"]["ImageOutput"] | components["schemas"]["SchedulerOutput"] | components["schemas"]["TileToPropertiesOutput"] | components["schemas"]["FaceOffOutput"] | components["schemas"]["BooleanCollectionOutput"] | components["schemas"]["IterateInvocationOutput"] | components["schemas"]["StringOutput"] | components["schemas"]["ConditioningCollectionOutput"] | components["schemas"]["CLIPSkipInvocationOutput"] | components["schemas"]["NoiseOutput"] | components["schemas"]["LoRALoaderOutput"] | components["schemas"]["StringPosNegOutput"] | components["schemas"]["SDXLLoRALoaderOutput"] | components["schemas"]["CollectInvocationOutput"] | components["schemas"]["IntegerCollectionOutput"] | components["schemas"]["LatentsCollectionOutput"] | components["schemas"]["SDXLModelLoaderOutput"] | components["schemas"]["MetadataOutput"] | components["schemas"]["PairTileImageOutput"] | components["schemas"]["ConditioningOutput"] | components["schemas"]["IdealSizeOutput"] | components["schemas"]["ColorOutput"] | components["schemas"]["ModelLoaderOutput"] | components["schemas"]["FloatCollectionOutput"] | components["schemas"]["MetadataItemOutput"] | components["schemas"]["CLIPOutput"] | components["schemas"]["BooleanOutput"] | components["schemas"]["FaceMaskOutput"] | components["schemas"]["T2IAdapterOutput"] | components["schemas"]["VAEOutput"] | components["schemas"]["FloatOutput"] | components["schemas"]["IPAdapterOutput"] | components["schemas"]["GradientMaskOutput"] | components["schemas"]["CalculateImageTilesOutput"] | components["schemas"]["LatentsOutput"]; }; /** * Errors @@ -4438,7 +4438,7 @@ export type components = { * @default auto * @enum {string} */ - clip_vision_model?: "auto" | "ViT-H" | "ViT-G"; + clip_vision_model?: "ViT-H" | "ViT-G"; /** * Weight * @description The weight given to the IP-Adapter From 3659219f46b2efc16a1e4cede6a52b6626178f78 Mon Sep 17 00:00:00 2001 From: Jonathan <34005131+JPPhoto@users.noreply.github.com> Date: Thu, 4 Apr 2024 16:38:40 -0500 Subject: [PATCH 54/89] Fix IdealSizeInvocation (#6145) --- invokeai/app/invocations/latent.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/invokeai/app/invocations/latent.py b/invokeai/app/invocations/latent.py index 3c66b7014f..449d013504 100644 --- a/invokeai/app/invocations/latent.py +++ b/invokeai/app/invocations/latent.py @@ -1254,7 +1254,7 @@ class IdealSizeInvocation(BaseInvocation): return tuple((x - x % multiple_of) for x in args) def invoke(self, context: InvocationContext) -> IdealSizeOutput: - unet_config = context.models.get_config(**self.unet.unet.model_dump()) + unet_config = context.models.get_config(self.unet.unet.key) aspect = self.width / self.height dimension: float = 512 if unet_config.base == BaseModelType.StableDiffusion2: From 32a6b758cd1ff44f598423f8a8f43259da7876b2 Mon Sep 17 00:00:00 2001 From: blessedcoolant <54517381+blessedcoolant@users.noreply.github.com> Date: Wed, 20 Mar 2024 08:47:16 +0530 Subject: [PATCH 55/89] wip: Initial Infill Methods Refactor --- invokeai/app/api/routers/app_info.py | 4 +- invokeai/app/invocations/infill.py | 237 ++++++------------ invokeai/backend/image_util/__init__.py | 2 +- .../{ => infill_methods}/cv2_inpaint.py | 0 .../image_util/{ => infill_methods}/lama.py | 9 + .../image_util/infill_methods/mosaic.py | 56 +++++ .../image_util/infill_methods/patchmatch.py | 67 +++++ .../backend/image_util/infill_methods/tile.py | 72 ++++++ invokeai/backend/image_util/patchmatch.py | 49 ---- .../util/graph/buildCanvasOutpaintGraph.ts | 16 ++ .../graph/buildCanvasSDXLOutpaintGraph.ts | 16 ++ 11 files changed, 318 insertions(+), 210 deletions(-) rename invokeai/backend/image_util/{ => infill_methods}/cv2_inpaint.py (100%) rename invokeai/backend/image_util/{ => infill_methods}/lama.py (82%) create mode 100644 invokeai/backend/image_util/infill_methods/mosaic.py create mode 100644 invokeai/backend/image_util/infill_methods/patchmatch.py create mode 100644 invokeai/backend/image_util/infill_methods/tile.py delete mode 100644 invokeai/backend/image_util/patchmatch.py diff --git a/invokeai/app/api/routers/app_info.py b/invokeai/app/api/routers/app_info.py index 4cbdc81b28..fadc749c3b 100644 --- a/invokeai/app/api/routers/app_info.py +++ b/invokeai/app/api/routers/app_info.py @@ -12,7 +12,7 @@ from pydantic import BaseModel, Field from invokeai.app.invocations.upscale import ESRGAN_MODELS from invokeai.app.services.invocation_cache.invocation_cache_common import InvocationCacheStatus -from invokeai.backend.image_util.patchmatch import PatchMatch +from invokeai.backend.image_util.infill_methods.patchmatch import PatchMatch from invokeai.backend.image_util.safety_checker import SafetyChecker from invokeai.backend.util.logging import logging from invokeai.version import __version__ @@ -100,7 +100,7 @@ async def get_app_deps() -> AppDependencyVersions: @app_router.get("/config", operation_id="get_config", status_code=200, response_model=AppConfig) async def get_config() -> AppConfig: - infill_methods = ["tile", "lama", "cv2"] + infill_methods = ["tile", "lama", "cv2", "color", "mosaic"] if PatchMatch.patchmatch_available(): infill_methods.append("patchmatch") diff --git a/invokeai/app/invocations/infill.py b/invokeai/app/invocations/infill.py index 8d14c0a8fe..325bedaba1 100644 --- a/invokeai/app/invocations/infill.py +++ b/invokeai/app/invocations/infill.py @@ -1,154 +1,91 @@ -# Copyright (c) 2022 Kyle Schouviller (https://github.com/kyle0654) and the InvokeAI Team +from abc import abstractmethod +from typing import Literal, get_args -import math -from typing import Literal, Optional, get_args - -import numpy as np -from PIL import Image, ImageOps +from PIL import Image from invokeai.app.invocations.fields import ColorField, ImageField from invokeai.app.invocations.primitives import ImageOutput from invokeai.app.services.shared.invocation_context import InvocationContext -from invokeai.app.util.download_with_progress import download_with_progress_bar from invokeai.app.util.misc import SEED_MAX -from invokeai.backend.image_util.cv2_inpaint import cv2_inpaint -from invokeai.backend.image_util.lama import LaMA -from invokeai.backend.image_util.patchmatch import PatchMatch +from invokeai.backend.image_util.infill_methods.cv2_inpaint import cv2_inpaint +from invokeai.backend.image_util.infill_methods.lama import LaMA +from invokeai.backend.image_util.infill_methods.mosaic import infill_mosaic +from invokeai.backend.image_util.infill_methods.patchmatch import PatchMatch, infill_patchmatch +from invokeai.backend.image_util.infill_methods.tile import infill_tile +from invokeai.backend.util.logging import InvokeAILogger from .baseinvocation import BaseInvocation, invocation from .fields import InputField, WithBoard, WithMetadata from .image import PIL_RESAMPLING_MAP, PIL_RESAMPLING_MODES +logger = InvokeAILogger.get_logger() -def infill_methods() -> list[str]: - methods = ["tile", "solid", "lama", "cv2"] + +def get_infill_methods(): + methods = Literal["tile", "color", "lama", "cv2", "mosaic"] if PatchMatch.patchmatch_available(): - methods.insert(0, "patchmatch") + methods = Literal["patchmatch", "tile", "color", "lama", "cv2", "mosaic"] return methods -INFILL_METHODS = Literal[tuple(infill_methods())] +INFILL_METHODS = get_infill_methods() DEFAULT_INFILL_METHOD = "patchmatch" if "patchmatch" in get_args(INFILL_METHODS) else "tile" -def infill_lama(im: Image.Image) -> Image.Image: - lama = LaMA() - return lama(im) +class InfillImageProcessorInvocation(BaseInvocation, WithMetadata, WithBoard): + """Base class for invocations that preprocess images for Infilling""" + image: ImageField = InputField(description="The image to process") -def infill_patchmatch(im: Image.Image) -> Image.Image: - if im.mode != "RGBA": - return im + @abstractmethod + def infill(self, image: Image.Image) -> Image.Image: + """Abstract to perform various infilling techniques""" + return image - # Skip patchmatch if patchmatch isn't available - if not PatchMatch.patchmatch_available(): - return im + def load_image(self, context: InvocationContext) -> tuple[Image.Image, bool]: + """Process the image to have an alpha channel before being infilled""" + image = context.images.get_pil(self.image.image_name) + has_alpha = True if image.mode == "RGBA" else False + return image, has_alpha - # Patchmatch (note, we may want to expose patch_size? Increasing it significantly impacts performance though) - im_patched_np = PatchMatch.inpaint(im.convert("RGB"), ImageOps.invert(im.split()[-1]), patch_size=3) - im_patched = Image.fromarray(im_patched_np, mode="RGB") - return im_patched + def invoke(self, context: InvocationContext) -> ImageOutput: + # Retrieve and process image to be infilled + input_image, has_alpha = self.load_image(context) + # If the input image has no alpha channel, return it + if has_alpha is False: + return ImageOutput.build(context.images.get_dto(self.image.image_name)) -def infill_cv2(im: Image.Image) -> Image.Image: - return cv2_inpaint(im) + # Perform Infill action + infilled_image = self.infill(input_image) + # Create ImageDTO for Infilled Image + infilled_image_dto = context.images.save(image=infilled_image) -def get_tile_images(image: np.ndarray, width=8, height=8): - _nrows, _ncols, depth = image.shape - _strides = image.strides - - nrows, _m = divmod(_nrows, height) - ncols, _n = divmod(_ncols, width) - if _m != 0 or _n != 0: - return None - - return np.lib.stride_tricks.as_strided( - np.ravel(image), - shape=(nrows, ncols, height, width, depth), - strides=(height * _strides[0], width * _strides[1], *_strides), - writeable=False, - ) - - -def tile_fill_missing(im: Image.Image, tile_size: int = 16, seed: Optional[int] = None) -> Image.Image: - # Only fill if there's an alpha layer - if im.mode != "RGBA": - return im - - a = np.asarray(im, dtype=np.uint8) - - tile_size_tuple = (tile_size, tile_size) - - # Get the image as tiles of a specified size - tiles = get_tile_images(a, *tile_size_tuple).copy() - - # Get the mask as tiles - tiles_mask = tiles[:, :, :, :, 3] - - # Find any mask tiles with any fully transparent pixels (we will be replacing these later) - tmask_shape = tiles_mask.shape - tiles_mask = tiles_mask.reshape(math.prod(tiles_mask.shape)) - n, ny = (math.prod(tmask_shape[0:2])), math.prod(tmask_shape[2:]) - tiles_mask = tiles_mask > 0 - tiles_mask = tiles_mask.reshape((n, ny)).all(axis=1) - - # Get RGB tiles in single array and filter by the mask - tshape = tiles.shape - tiles_all = tiles.reshape((math.prod(tiles.shape[0:2]), *tiles.shape[2:])) - filtered_tiles = tiles_all[tiles_mask] - - if len(filtered_tiles) == 0: - return im - - # Find all invalid tiles and replace with a random valid tile - replace_count = (tiles_mask == False).sum() # noqa: E712 - rng = np.random.default_rng(seed=seed) - tiles_all[np.logical_not(tiles_mask)] = filtered_tiles[rng.choice(filtered_tiles.shape[0], replace_count), :, :, :] - - # Convert back to an image - tiles_all = tiles_all.reshape(tshape) - tiles_all = tiles_all.swapaxes(1, 2) - st = tiles_all.reshape( - ( - math.prod(tiles_all.shape[0:2]), - math.prod(tiles_all.shape[2:4]), - tiles_all.shape[4], - ) - ) - si = Image.fromarray(st, mode="RGBA") - - return si + # Return Infilled Image + return ImageOutput.build(infilled_image_dto) @invocation("infill_rgba", title="Solid Color Infill", tags=["image", "inpaint"], category="inpaint", version="1.2.2") -class InfillColorInvocation(BaseInvocation, WithMetadata, WithBoard): +class InfillColorInvocation(InfillImageProcessorInvocation): """Infills transparent areas of an image with a solid color""" - image: ImageField = InputField(description="The image to infill") color: ColorField = InputField( default=ColorField(r=127, g=127, b=127, a=255), description="The color to use to infill", ) - def invoke(self, context: InvocationContext) -> ImageOutput: - image = context.images.get_pil(self.image.image_name) - + def infill(self, image: Image.Image): solid_bg = Image.new("RGBA", image.size, self.color.tuple()) infilled = Image.alpha_composite(solid_bg, image.convert("RGBA")) - infilled.paste(image, (0, 0), image.split()[-1]) - - image_dto = context.images.save(image=infilled) - - return ImageOutput.build(image_dto) + return infilled @invocation("infill_tile", title="Tile Infill", tags=["image", "inpaint"], category="inpaint", version="1.2.3") -class InfillTileInvocation(BaseInvocation, WithMetadata, WithBoard): +class InfillTileInvocation(InfillImageProcessorInvocation): """Infills transparent areas of an image with tiles of the image""" - image: ImageField = InputField(description="The image to infill") tile_size: int = InputField(default=32, ge=1, description="The tile size (px)") seed: int = InputField( default=0, @@ -157,92 +94,76 @@ class InfillTileInvocation(BaseInvocation, WithMetadata, WithBoard): description="The seed to use for tile generation (omit for random)", ) - def invoke(self, context: InvocationContext) -> ImageOutput: - image = context.images.get_pil(self.image.image_name) - - infilled = tile_fill_missing(image.copy(), seed=self.seed, tile_size=self.tile_size) + def infill(self, image: Image.Image): + infilled = infill_tile(image, seed=self.seed, tile_size=self.tile_size) infilled.paste(image, (0, 0), image.split()[-1]) - - image_dto = context.images.save(image=infilled) - - return ImageOutput.build(image_dto) + return infilled @invocation( "infill_patchmatch", title="PatchMatch Infill", tags=["image", "inpaint"], category="inpaint", version="1.2.2" ) -class InfillPatchMatchInvocation(BaseInvocation, WithMetadata, WithBoard): +class InfillPatchMatchInvocation(InfillImageProcessorInvocation): """Infills transparent areas of an image using the PatchMatch algorithm""" - image: ImageField = InputField(description="The image to infill") downscale: float = InputField(default=2.0, gt=0, description="Run patchmatch on downscaled image to speedup infill") resample_mode: PIL_RESAMPLING_MODES = InputField(default="bicubic", description="The resampling mode") - def invoke(self, context: InvocationContext) -> ImageOutput: - image = context.images.get_pil(self.image.image_name).convert("RGBA") + def infill(self, image: Image.Image): resample_mode = PIL_RESAMPLING_MAP[self.resample_mode] - infill_image = image.copy() width = int(image.width / self.downscale) height = int(image.height / self.downscale) - infill_image = infill_image.resize( + + infilled = image.resize( (width, height), resample=resample_mode, ) - - if PatchMatch.patchmatch_available(): - infilled = infill_patchmatch(infill_image) - else: - raise ValueError("PatchMatch is not available on this system") - + infilled = infill_patchmatch(image) infilled = infilled.resize( (image.width, image.height), resample=resample_mode, ) - infilled.paste(image, (0, 0), mask=image.split()[-1]) - # image.paste(infilled, (0, 0), mask=image.split()[-1]) - image_dto = context.images.save(image=infilled) - - return ImageOutput.build(image_dto) + return infilled @invocation("infill_lama", title="LaMa Infill", tags=["image", "inpaint"], category="inpaint", version="1.2.2") -class LaMaInfillInvocation(BaseInvocation, WithMetadata, WithBoard): +class LaMaInfillInvocation(InfillImageProcessorInvocation): """Infills transparent areas of an image using the LaMa model""" - image: ImageField = InputField(description="The image to infill") - - def invoke(self, context: InvocationContext) -> ImageOutput: - image = context.images.get_pil(self.image.image_name) - - # Downloads the LaMa model if it doesn't already exist - download_with_progress_bar( - name="LaMa Inpainting Model", - url="https://github.com/Sanster/models/releases/download/add_big_lama/big-lama.pt", - dest_path=context.config.get().models_path / "core/misc/lama/lama.pt", - ) - - infilled = infill_lama(image.copy()) - - image_dto = context.images.save(image=infilled) - - return ImageOutput.build(image_dto) + def infill(self, image: Image.Image): + lama = LaMA() + return lama(image) @invocation("infill_cv2", title="CV2 Infill", tags=["image", "inpaint"], category="inpaint", version="1.2.2") -class CV2InfillInvocation(BaseInvocation, WithMetadata, WithBoard): +class CV2InfillInvocation(InfillImageProcessorInvocation): """Infills transparent areas of an image using OpenCV Inpainting""" + def infill(self, image: Image.Image): + return cv2_inpaint(image) + + +@invocation( + "infill_mosaic", title="Mosaic Infill", tags=["image", "inpaint", "outpaint"], category="inpaint", version="1.0.0" +) +class MosaicInfillInvocation(InfillImageProcessorInvocation): + """Infills transparent areas of an image with a mosaic pattern drawing colors from the rest of the image""" + image: ImageField = InputField(description="The image to infill") + tile_width: int = InputField(default=64, description="Width of the tile") + tile_height: int = InputField(default=64, description="Height of the tile") + min_color: ColorField = InputField( + default=ColorField(r=0, g=0, b=0, a=255), + description="The min threshold for color", + ) + max_color: ColorField = InputField( + default=ColorField(r=255, g=255, b=255, a=255), + description="The max threshold for color", + ) - def invoke(self, context: InvocationContext) -> ImageOutput: - image = context.images.get_pil(self.image.image_name) - - infilled = infill_cv2(image.copy()) - - image_dto = context.images.save(image=infilled) - - return ImageOutput.build(image_dto) + def infill(self, image: Image.Image): + return infill_mosaic(image, (self.tile_width, self.tile_height), self.min_color.tuple(), self.max_color.tuple()) diff --git a/invokeai/backend/image_util/__init__.py b/invokeai/backend/image_util/__init__.py index 473ecc4c87..dec2a92150 100644 --- a/invokeai/backend/image_util/__init__.py +++ b/invokeai/backend/image_util/__init__.py @@ -2,7 +2,7 @@ Initialization file for invokeai.backend.image_util methods. """ -from .patchmatch import PatchMatch # noqa: F401 +from .infill_methods.patchmatch import PatchMatch # noqa: F401 from .pngwriter import PngWriter, PromptFormatter, retrieve_metadata, write_metadata # noqa: F401 from .seamless import configure_model_padding # noqa: F401 from .util import InitImageResizer, make_grid # noqa: F401 diff --git a/invokeai/backend/image_util/cv2_inpaint.py b/invokeai/backend/image_util/infill_methods/cv2_inpaint.py similarity index 100% rename from invokeai/backend/image_util/cv2_inpaint.py rename to invokeai/backend/image_util/infill_methods/cv2_inpaint.py diff --git a/invokeai/backend/image_util/lama.py b/invokeai/backend/image_util/infill_methods/lama.py similarity index 82% rename from invokeai/backend/image_util/lama.py rename to invokeai/backend/image_util/infill_methods/lama.py index 5b3fc3a9c7..fa354aeed1 100644 --- a/invokeai/backend/image_util/lama.py +++ b/invokeai/backend/image_util/infill_methods/lama.py @@ -7,6 +7,7 @@ from PIL import Image import invokeai.backend.util.logging as logger from invokeai.app.services.config.config_default import get_config +from invokeai.app.util.download_with_progress import download_with_progress_bar from invokeai.backend.util.devices import choose_torch_device @@ -30,6 +31,14 @@ class LaMA: def __call__(self, input_image: Image.Image, *args: Any, **kwds: Any) -> Any: device = choose_torch_device() model_location = get_config().models_path / "core/misc/lama/lama.pt" + + if not model_location.exists(): + download_with_progress_bar( + name="LaMa Inpainting Model", + url="https://github.com/Sanster/models/releases/download/add_big_lama/big-lama.pt", + dest_path=model_location, + ) + model = load_jit_model(model_location, device) image = np.asarray(input_image.convert("RGB")) diff --git a/invokeai/backend/image_util/infill_methods/mosaic.py b/invokeai/backend/image_util/infill_methods/mosaic.py new file mode 100644 index 0000000000..3a29fc3a17 --- /dev/null +++ b/invokeai/backend/image_util/infill_methods/mosaic.py @@ -0,0 +1,56 @@ +from typing import Tuple + +import numpy as np +from PIL import Image + + +def infill_mosaic( + image: Image.Image, + tile_shape: Tuple[int, int] = (64, 16), + min_color: Tuple[int, int, int, int] = (0, 0, 0, 0), + max_color: Tuple[int, int, int, int] = (255, 255, 255, 0), +) -> Image.Image: + """ + image:PIL - A PIL Image + tile_shape: Tuple[int,int] - Tile width & Tile Height + min_color: Tuple[int,int,int] - RGB values for the lowest color to clip to (0-255) + max_color: Tuple[int,int,int] - RGB values for the highest color to clip to (0-255) + """ + + np_image = np.array(image) # Convert image to np array + alpha = np_image[:, :, 3] # Get the mask from the alpha channel of the image + non_transparent_pixels = np_image[alpha != 0, :3] # List of non-transparent pixels + + # Create color tiles to paste in the empty areas of the image + tile_width, tile_height = tile_shape + + # Clip the range of colors in the image to a particular spectrum only + r_min, g_min, b_min, _ = min_color + r_max, g_max, b_max, _ = max_color + non_transparent_pixels[:, 0] = np.clip(non_transparent_pixels[:, 0], r_min, r_max) + non_transparent_pixels[:, 1] = np.clip(non_transparent_pixels[:, 1], g_min, g_max) + non_transparent_pixels[:, 2] = np.clip(non_transparent_pixels[:, 2], b_min, b_max) + + tiles = [] + for _ in range(256): + color = non_transparent_pixels[np.random.randint(len(non_transparent_pixels))] + + tile = np.zeros((tile_height, tile_width, 3), dtype=np.uint8) + tile[:, :] = color + tiles.append(tile) + + # Fill the transparent area with tiles + filled_image = np.zeros((image.height, image.width, 3), dtype=np.uint8) + for x in range(image.width): + for y in range(image.height): + tile = tiles[np.random.randint(len(tiles))] + filled_image[ + y - (y % tile_height) : y - (y % tile_height) + tile_height, + x - (x % tile_width) : x - (x % tile_width) + tile_width, + ] = tile + + filled_image = Image.fromarray(filled_image) # Convert the filled tiles image to PIL + image = Image.composite( + image, filled_image, image.split()[-1] + ) # Composite the original image on top of the filled tiles + return image diff --git a/invokeai/backend/image_util/infill_methods/patchmatch.py b/invokeai/backend/image_util/infill_methods/patchmatch.py new file mode 100644 index 0000000000..7e9cdf8fa4 --- /dev/null +++ b/invokeai/backend/image_util/infill_methods/patchmatch.py @@ -0,0 +1,67 @@ +""" +This module defines a singleton object, "patchmatch" that +wraps the actual patchmatch object. It respects the global +"try_patchmatch" attribute, so that patchmatch loading can +be suppressed or deferred +""" + +import numpy as np +from PIL import Image + +import invokeai.backend.util.logging as logger +from invokeai.app.services.config.config_default import get_config + + +class PatchMatch: + """ + Thin class wrapper around the patchmatch function. + """ + + patch_match = None + tried_load: bool = False + + def __init__(self): + super().__init__() + + @classmethod + def _load_patch_match(cls): + if cls.tried_load: + return + if get_config().patchmatch: + from patchmatch import patch_match as pm + + if pm.patchmatch_available: + logger.info("Patchmatch initialized") + cls.patch_match = pm + else: + logger.info("Patchmatch not loaded (nonfatal)") + else: + logger.info("Patchmatch loading disabled") + cls.tried_load = True + + @classmethod + def patchmatch_available(cls) -> bool: + cls._load_patch_match() + if not cls.patch_match: + return False + return cls.patch_match.patchmatch_available + + @classmethod + def inpaint(cls, image: Image.Image) -> Image.Image: + if cls.patch_match is None or not cls.patchmatch_available(): + return image + + np_image = np.array(image) + mask = 255 - np_image[:, :, 3] + infilled = cls.patch_match.inpaint(np_image[:, :, :3], mask, patch_size=3) + return Image.fromarray(infilled, mode="RGB") + + +def infill_patchmatch(image: Image.Image) -> Image.Image: + IS_PATCHMATCH_AVAILABLE = PatchMatch.patchmatch_available() + + if not IS_PATCHMATCH_AVAILABLE: + logger.warning("PatchMatch is not available on this system") + return image + + return PatchMatch.inpaint(image) diff --git a/invokeai/backend/image_util/infill_methods/tile.py b/invokeai/backend/image_util/infill_methods/tile.py new file mode 100644 index 0000000000..d99d93fb95 --- /dev/null +++ b/invokeai/backend/image_util/infill_methods/tile.py @@ -0,0 +1,72 @@ +import math +from typing import Optional + +import numpy as np +from PIL import Image + + +def get_tile_images(image: np.ndarray, width: int = 8, height: int = 8): + _nrows, _ncols, depth = image.shape + _strides = image.strides + + nrows, _m = divmod(_nrows, height) + ncols, _n = divmod(_ncols, width) + if _m != 0 or _n != 0: + return None + + return np.lib.stride_tricks.as_strided( + np.ravel(image), + shape=(nrows, ncols, height, width, depth), + strides=(height * _strides[0], width * _strides[1], *_strides), + writeable=False, + ) + + +def infill_tile(im: Image.Image, tile_size: int = 16, seed: Optional[int] = None) -> Image.Image: + # Only fill if there's an alpha layer + if im.mode != "RGBA": + return im + + a = np.asarray(im, dtype=np.uint8) + + tile_size_tuple = (tile_size, tile_size) + + # Get the image as tiles of a specified size + tiles = get_tile_images(a, *tile_size_tuple).copy() + + # Get the mask as tiles + tiles_mask = tiles[:, :, :, :, 3] + + # Find any mask tiles with any fully transparent pixels (we will be replacing these later) + tmask_shape = tiles_mask.shape + tiles_mask = tiles_mask.reshape(math.prod(tiles_mask.shape)) + n, ny = (math.prod(tmask_shape[0:2])), math.prod(tmask_shape[2:]) + tiles_mask = tiles_mask > 0 + tiles_mask = tiles_mask.reshape((n, ny)).all(axis=1) + + # Get RGB tiles in single array and filter by the mask + tshape = tiles.shape + tiles_all = tiles.reshape((math.prod(tiles.shape[0:2]), *tiles.shape[2:])) + filtered_tiles = tiles_all[tiles_mask] + + if len(filtered_tiles) == 0: + return im + + # Find all invalid tiles and replace with a random valid tile + replace_count = (tiles_mask == False).sum() # noqa: E712 + rng = np.random.default_rng(seed=seed) + tiles_all[np.logical_not(tiles_mask)] = filtered_tiles[rng.choice(filtered_tiles.shape[0], replace_count), :, :, :] + + # Convert back to an image + tiles_all = tiles_all.reshape(tshape) + tiles_all = tiles_all.swapaxes(1, 2) + st = tiles_all.reshape( + ( + math.prod(tiles_all.shape[0:2]), + math.prod(tiles_all.shape[2:4]), + tiles_all.shape[4], + ) + ) + si = Image.fromarray(st, mode="RGBA") + + return si diff --git a/invokeai/backend/image_util/patchmatch.py b/invokeai/backend/image_util/patchmatch.py deleted file mode 100644 index 8b7b468397..0000000000 --- a/invokeai/backend/image_util/patchmatch.py +++ /dev/null @@ -1,49 +0,0 @@ -""" -This module defines a singleton object, "patchmatch" that -wraps the actual patchmatch object. It respects the global -"try_patchmatch" attribute, so that patchmatch loading can -be suppressed or deferred -""" - -import numpy as np - -import invokeai.backend.util.logging as logger -from invokeai.app.services.config.config_default import get_config - - -class PatchMatch: - """ - Thin class wrapper around the patchmatch function. - """ - - patch_match = None - tried_load: bool = False - - def __init__(self): - super().__init__() - - @classmethod - def _load_patch_match(self): - if self.tried_load: - return - if get_config().patchmatch: - from patchmatch import patch_match as pm - - if pm.patchmatch_available: - logger.info("Patchmatch initialized") - else: - logger.info("Patchmatch not loaded (nonfatal)") - self.patch_match = pm - else: - logger.info("Patchmatch loading disabled") - self.tried_load = True - - @classmethod - def patchmatch_available(self) -> bool: - self._load_patch_match() - return self.patch_match and self.patch_match.patchmatch_available - - @classmethod - def inpaint(self, *args, **kwargs) -> np.ndarray: - if self.patchmatch_available(): - return self.patch_match.inpaint(*args, **kwargs) diff --git a/invokeai/frontend/web/src/features/nodes/util/graph/buildCanvasOutpaintGraph.ts b/invokeai/frontend/web/src/features/nodes/util/graph/buildCanvasOutpaintGraph.ts index d847ccbfb5..520005ee5b 100644 --- a/invokeai/frontend/web/src/features/nodes/util/graph/buildCanvasOutpaintGraph.ts +++ b/invokeai/frontend/web/src/features/nodes/util/graph/buildCanvasOutpaintGraph.ts @@ -356,6 +356,22 @@ export const buildCanvasOutpaintGraph = async ( }; } + if (infillMethod === 'mosaic') { + graph.nodes[INPAINT_INFILL] = { + type: 'infill_mosaic', + id: INPAINT_INFILL, + is_intermediate, + }; + } + + if (infillMethod === 'color') { + graph.nodes[INPAINT_INFILL] = { + type: 'infill_rgba', + id: INPAINT_INFILL, + is_intermediate, + }; + } + // Handle Scale Before Processing if (isUsingScaledDimensions) { const scaledWidth: number = scaledBoundingBoxDimensions.width; diff --git a/invokeai/frontend/web/src/features/nodes/util/graph/buildCanvasSDXLOutpaintGraph.ts b/invokeai/frontend/web/src/features/nodes/util/graph/buildCanvasSDXLOutpaintGraph.ts index 39a54fd9d1..751eb52b9a 100644 --- a/invokeai/frontend/web/src/features/nodes/util/graph/buildCanvasSDXLOutpaintGraph.ts +++ b/invokeai/frontend/web/src/features/nodes/util/graph/buildCanvasSDXLOutpaintGraph.ts @@ -365,6 +365,22 @@ export const buildCanvasSDXLOutpaintGraph = async ( }; } + if (infillMethod === 'mosaic') { + graph.nodes[INPAINT_INFILL] = { + type: 'infill_mosaic', + id: INPAINT_INFILL, + is_intermediate, + }; + } + + if (infillMethod === 'color') { + graph.nodes[INPAINT_INFILL] = { + type: 'infill_rgba', + id: INPAINT_INFILL, + is_intermediate, + }; + } + // Handle Scale Before Processing if (isUsingScaledDimensions) { const scaledWidth: number = scaledBoundingBoxDimensions.width; From 3c195d74a54e663a2d19c0d4ac037371e7ab93b7 Mon Sep 17 00:00:00 2001 From: blessedcoolant <54517381+blessedcoolant@users.noreply.github.com> Date: Wed, 20 Mar 2024 13:17:45 +0530 Subject: [PATCH 56/89] fix: bypass edge pixels which cannot transform to tile size Still need to fix this somehow --- .../backend/image_util/infill_methods/mosaic.py | 16 ++++++++++------ 1 file changed, 10 insertions(+), 6 deletions(-) diff --git a/invokeai/backend/image_util/infill_methods/mosaic.py b/invokeai/backend/image_util/infill_methods/mosaic.py index 3a29fc3a17..2715a100d2 100644 --- a/invokeai/backend/image_util/infill_methods/mosaic.py +++ b/invokeai/backend/image_util/infill_methods/mosaic.py @@ -6,7 +6,7 @@ from PIL import Image def infill_mosaic( image: Image.Image, - tile_shape: Tuple[int, int] = (64, 16), + tile_shape: Tuple[int, int] = (64, 64), min_color: Tuple[int, int, int, int] = (0, 0, 0, 0), max_color: Tuple[int, int, int, int] = (255, 255, 255, 0), ) -> Image.Image: @@ -34,20 +34,24 @@ def infill_mosaic( tiles = [] for _ in range(256): color = non_transparent_pixels[np.random.randint(len(non_transparent_pixels))] - tile = np.zeros((tile_height, tile_width, 3), dtype=np.uint8) tile[:, :] = color tiles.append(tile) # Fill the transparent area with tiles filled_image = np.zeros((image.height, image.width, 3), dtype=np.uint8) + for x in range(image.width): for y in range(image.height): tile = tiles[np.random.randint(len(tiles))] - filled_image[ - y - (y % tile_height) : y - (y % tile_height) + tile_height, - x - (x % tile_width) : x - (x % tile_width) + tile_width, - ] = tile + try: + filled_image[ + y - (y % tile_height) : y - (y % tile_height) + tile_height, + x - (x % tile_width) : x - (x % tile_width) + tile_width, + ] = tile + except ValueError: + # Need to handle edge cases - literally + pass filled_image = Image.fromarray(filled_image) # Convert the filled tiles image to PIL image = Image.composite( From adb7966bb3281b465281369caab8029b776eb317 Mon Sep 17 00:00:00 2001 From: blessedcoolant <54517381+blessedcoolant@users.noreply.github.com> Date: Wed, 20 Mar 2024 13:36:55 +0530 Subject: [PATCH 57/89] ui: intial mosaic infill ui Need to change color picking. --- invokeai/frontend/web/public/locales/en.json | 4 + .../util/graph/buildCanvasOutpaintGraph.ts | 8 ++ .../graph/buildCanvasSDXLOutpaintGraph.ts | 8 ++ .../InfillAndScaling/ParamInfillOptions.tsx | 5 + .../ParamMosaicInfillOptions.tsx | 127 ++++++++++++++++++ .../parameters/store/generationSlice.ts | 21 +++ .../src/features/parameters/store/types.ts | 5 + .../frontend/web/src/services/api/schema.ts | 81 ++++++++++- 8 files changed, 252 insertions(+), 7 deletions(-) create mode 100644 invokeai/frontend/web/src/features/parameters/components/Canvas/InfillAndScaling/ParamMosaicInfillOptions.tsx diff --git a/invokeai/frontend/web/public/locales/en.json b/invokeai/frontend/web/public/locales/en.json index 5872d22dfe..7f31da089e 100644 --- a/invokeai/frontend/web/public/locales/en.json +++ b/invokeai/frontend/web/public/locales/en.json @@ -887,6 +887,10 @@ "imageFit": "Fit Initial Image To Output Size", "images": "Images", "infillMethod": "Infill Method", + "infillMosaicTileWidth": "Tile Width", + "infillMosaicTileHeight": "Tile Height", + "infillMosaicMinColor": "Min Color", + "infillMosaicMaxColor": "Max Color", "info": "Info", "invoke": { "addingImagesTo": "Adding images to", diff --git a/invokeai/frontend/web/src/features/nodes/util/graph/buildCanvasOutpaintGraph.ts b/invokeai/frontend/web/src/features/nodes/util/graph/buildCanvasOutpaintGraph.ts index 520005ee5b..faad4189a9 100644 --- a/invokeai/frontend/web/src/features/nodes/util/graph/buildCanvasOutpaintGraph.ts +++ b/invokeai/frontend/web/src/features/nodes/util/graph/buildCanvasOutpaintGraph.ts @@ -65,6 +65,10 @@ export const buildCanvasOutpaintGraph = async ( infillTileSize, infillPatchmatchDownscaleSize, infillMethod, + infillMosaicTileWidth, + infillMosaicTileHeight, + infillMosaicMinColor, + infillMosaicMaxColor, clipSkip, seamlessXAxis, seamlessYAxis, @@ -361,6 +365,10 @@ export const buildCanvasOutpaintGraph = async ( type: 'infill_mosaic', id: INPAINT_INFILL, is_intermediate, + tile_width: infillMosaicTileWidth, + tile_height: infillMosaicTileHeight, + min_color: infillMosaicMinColor, + max_color: infillMosaicMaxColor, }; } diff --git a/invokeai/frontend/web/src/features/nodes/util/graph/buildCanvasSDXLOutpaintGraph.ts b/invokeai/frontend/web/src/features/nodes/util/graph/buildCanvasSDXLOutpaintGraph.ts index 751eb52b9a..efe9d51990 100644 --- a/invokeai/frontend/web/src/features/nodes/util/graph/buildCanvasSDXLOutpaintGraph.ts +++ b/invokeai/frontend/web/src/features/nodes/util/graph/buildCanvasSDXLOutpaintGraph.ts @@ -66,6 +66,10 @@ export const buildCanvasSDXLOutpaintGraph = async ( infillTileSize, infillPatchmatchDownscaleSize, infillMethod, + infillMosaicTileWidth, + infillMosaicTileHeight, + infillMosaicMinColor, + infillMosaicMaxColor, seamlessXAxis, seamlessYAxis, canvasCoherenceMode, @@ -370,6 +374,10 @@ export const buildCanvasSDXLOutpaintGraph = async ( type: 'infill_mosaic', id: INPAINT_INFILL, is_intermediate, + tile_width: infillMosaicTileWidth, + tile_height: infillMosaicTileHeight, + min_color: infillMosaicMinColor, + max_color: infillMosaicMaxColor, }; } diff --git a/invokeai/frontend/web/src/features/parameters/components/Canvas/InfillAndScaling/ParamInfillOptions.tsx b/invokeai/frontend/web/src/features/parameters/components/Canvas/InfillAndScaling/ParamInfillOptions.tsx index 16cbffe56a..607a2ed809 100644 --- a/invokeai/frontend/web/src/features/parameters/components/Canvas/InfillAndScaling/ParamInfillOptions.tsx +++ b/invokeai/frontend/web/src/features/parameters/components/Canvas/InfillAndScaling/ParamInfillOptions.tsx @@ -3,6 +3,7 @@ import { memo } from 'react'; import ParamInfillPatchmatchDownscaleSize from './ParamInfillPatchmatchDownscaleSize'; import ParamInfillTilesize from './ParamInfillTilesize'; +import ParamMosaicInfillOptions from './ParamMosaicInfillOptions'; const ParamInfillOptions = () => { const infillMethod = useAppSelector((s) => s.generation.infillMethod); @@ -14,6 +15,10 @@ const ParamInfillOptions = () => { return ; } + if (infillMethod === 'mosaic') { + return ; + } + return null; }; diff --git a/invokeai/frontend/web/src/features/parameters/components/Canvas/InfillAndScaling/ParamMosaicInfillOptions.tsx b/invokeai/frontend/web/src/features/parameters/components/Canvas/InfillAndScaling/ParamMosaicInfillOptions.tsx new file mode 100644 index 0000000000..47c11d5669 --- /dev/null +++ b/invokeai/frontend/web/src/features/parameters/components/Canvas/InfillAndScaling/ParamMosaicInfillOptions.tsx @@ -0,0 +1,127 @@ +import { Box, CompositeNumberInput, CompositeSlider, Flex, FormControl, FormLabel } from '@invoke-ai/ui-library'; +import { createSelector } from '@reduxjs/toolkit'; +import { useAppDispatch, useAppSelector } from 'app/store/storeHooks'; +import IAIColorPicker from 'common/components/IAIColorPicker'; +import { + selectGenerationSlice, + setInfillMosaicMaxColor, + setInfillMosaicMinColor, + setInfillMosaicTileHeight, + setInfillMosaicTileWidth, +} from 'features/parameters/store/generationSlice'; +import { memo, useCallback, useMemo } from 'react'; +import type { RgbaColor } from 'react-colorful'; +import { useTranslation } from 'react-i18next'; + +const ParamMosaicInfillTileSize = () => { + const dispatch = useAppDispatch(); + + const selector = useMemo( + () => + createSelector(selectGenerationSlice, (generation) => ({ + infillMosaicTileWidth: generation.infillMosaicTileWidth, + infillMosaicTileHeight: generation.infillMosaicTileHeight, + infillMosaicMinColor: generation.infillMosaicMinColor, + infillMosaicMaxColor: generation.infillMosaicMaxColor, + })), + [] + ); + + const { infillMosaicTileWidth, infillMosaicTileHeight, infillMosaicMinColor, infillMosaicMaxColor } = + useAppSelector(selector); + + const infillMethod = useAppSelector((s) => s.generation.infillMethod); + + const { t } = useTranslation(); + + const handleInfillMosaicTileWidthChange = useCallback( + (v: number) => { + dispatch(setInfillMosaicTileWidth(v)); + }, + [dispatch] + ); + + const handleInfillMosaicTileHeightChange = useCallback( + (v: number) => { + dispatch(setInfillMosaicTileHeight(v)); + }, + [dispatch] + ); + + const handleInfillMosaicMinColor = useCallback( + (v: RgbaColor) => { + dispatch(setInfillMosaicMinColor(v)); + }, + [dispatch] + ); + + const handleInfillMosaicMaxColor = useCallback( + (v: RgbaColor) => { + dispatch(setInfillMosaicMaxColor(v)); + }, + [dispatch] + ); + + return ( + + + {t('parameters.infillMosaicTileWidth')} + + + + + {t('parameters.infillMosaicTileHeight')} + + + + + {t('parameters.infillMosaicMinColor')} + + + + + + {t('parameters.infillMosaicMaxColor')} + + + + + + ); +}; + +export default memo(ParamMosaicInfillTileSize); diff --git a/invokeai/frontend/web/src/features/parameters/store/generationSlice.ts b/invokeai/frontend/web/src/features/parameters/store/generationSlice.ts index e272cd278e..7572ffeb90 100644 --- a/invokeai/frontend/web/src/features/parameters/store/generationSlice.ts +++ b/invokeai/frontend/web/src/features/parameters/store/generationSlice.ts @@ -19,6 +19,7 @@ import type { import { getIsSizeOptimal, getOptimalDimension } from 'features/parameters/util/optimalDimension'; import { configChanged } from 'features/system/store/configSlice'; import { clamp } from 'lodash-es'; +import type { RgbaColor } from 'react-colorful'; import type { ImageDTO } from 'services/api/types'; import type { GenerationState } from './types'; @@ -55,6 +56,10 @@ const initialGenerationState: GenerationState = { shouldUseCpuNoise: true, shouldShowAdvancedOptions: false, aspectRatio: { ...initialAspectRatioState }, + infillMosaicTileWidth: 64, + infillMosaicTileHeight: 64, + infillMosaicMinColor: { r: 0, g: 0, b: 0, a: 1 }, + infillMosaicMaxColor: { r: 255, g: 255, b: 255, a: 1 }, }; export const generationSlice = createSlice({ @@ -206,6 +211,18 @@ export const generationSlice = createSlice({ aspectRatioChanged: (state, action: PayloadAction) => { state.aspectRatio = action.payload; }, + setInfillMosaicTileWidth: (state, action: PayloadAction) => { + state.infillMosaicTileWidth = action.payload; + }, + setInfillMosaicTileHeight: (state, action: PayloadAction) => { + state.infillMosaicTileHeight = action.payload; + }, + setInfillMosaicMinColor: (state, action: PayloadAction) => { + state.infillMosaicMinColor = action.payload; + }, + setInfillMosaicMaxColor: (state, action: PayloadAction) => { + state.infillMosaicMaxColor = action.payload; + }, }, extraReducers: (builder) => { builder.addCase(configChanged, (state, action) => { @@ -264,6 +281,10 @@ export const { heightChanged, widthRecalled, heightRecalled, + setInfillMosaicTileWidth, + setInfillMosaicTileHeight, + setInfillMosaicMinColor, + setInfillMosaicMaxColor, } = generationSlice.actions; export const { selectOptimalDimension } = generationSlice.selectors; diff --git a/invokeai/frontend/web/src/features/parameters/store/types.ts b/invokeai/frontend/web/src/features/parameters/store/types.ts index 73185754ee..1e69bf52d5 100644 --- a/invokeai/frontend/web/src/features/parameters/store/types.ts +++ b/invokeai/frontend/web/src/features/parameters/store/types.ts @@ -17,6 +17,7 @@ import type { ParameterVAEModel, ParameterWidth, } from 'features/parameters/types/parameterSchemas'; +import type { RgbaColor } from 'react-colorful'; export interface GenerationState { _version: 2; @@ -51,6 +52,10 @@ export interface GenerationState { shouldUseCpuNoise: boolean; shouldShowAdvancedOptions: boolean; aspectRatio: AspectRatioState; + infillMosaicTileWidth: number; + infillMosaicTileHeight: number; + infillMosaicMinColor: RgbaColor; + infillMosaicMaxColor: RgbaColor; } export type PayloadActionWithOptimalDimension = PayloadAction; diff --git a/invokeai/frontend/web/src/services/api/schema.ts b/invokeai/frontend/web/src/services/api/schema.ts index 41e60fa029..c42078d414 100644 --- a/invokeai/frontend/web/src/services/api/schema.ts +++ b/invokeai/frontend/web/src/services/api/schema.ts @@ -1419,7 +1419,7 @@ export type components = { * @default true */ use_cache?: boolean; - /** @description The image to infill */ + /** @description The image to process */ image?: components["schemas"]["ImageField"]; /** * type @@ -4112,7 +4112,7 @@ export type components = { * @description The nodes in this graph */ nodes: { - [key: string]: components["schemas"]["AddInvocation"] | components["schemas"]["MaskCombineInvocation"] | components["schemas"]["BooleanInvocation"] | components["schemas"]["FloatCollectionInvocation"] | components["schemas"]["CreateGradientMaskInvocation"] | components["schemas"]["ImageMultiplyInvocation"] | components["schemas"]["ImageCropInvocation"] | components["schemas"]["MaskEdgeInvocation"] | components["schemas"]["IPAdapterInvocation"] | components["schemas"]["DenoiseLatentsInvocation"] | components["schemas"]["VAELoaderInvocation"] | components["schemas"]["LeresImageProcessorInvocation"] | components["schemas"]["ImageLerpInvocation"] | components["schemas"]["FloatLinearRangeInvocation"] | components["schemas"]["TileToPropertiesInvocation"] | components["schemas"]["CannyImageProcessorInvocation"] | components["schemas"]["ImageResizeInvocation"] | components["schemas"]["IntegerMathInvocation"] | components["schemas"]["FloatInvocation"] | components["schemas"]["ConditioningInvocation"] | components["schemas"]["SchedulerInvocation"] | components["schemas"]["ScaleLatentsInvocation"] | components["schemas"]["CollectInvocation"] | components["schemas"]["ImageInvocation"] | components["schemas"]["ImageConvertInvocation"] | components["schemas"]["NormalbaeImageProcessorInvocation"] | components["schemas"]["RoundInvocation"] | components["schemas"]["RandomRangeInvocation"] | components["schemas"]["FaceOffInvocation"] | components["schemas"]["FreeUInvocation"] | components["schemas"]["StringSplitInvocation"] | components["schemas"]["MlsdImageProcessorInvocation"] | components["schemas"]["InfillPatchMatchInvocation"] | components["schemas"]["PairTileImageInvocation"] | components["schemas"]["FaceMaskInvocation"] | components["schemas"]["MidasDepthImageProcessorInvocation"] | components["schemas"]["ImageHueAdjustmentInvocation"] | components["schemas"]["IdealSizeInvocation"] | components["schemas"]["LineartAnimeImageProcessorInvocation"] | components["schemas"]["ESRGANInvocation"] | components["schemas"]["UnsharpMaskInvocation"] | components["schemas"]["ImageChannelOffsetInvocation"] | components["schemas"]["ResizeLatentsInvocation"] | components["schemas"]["CalculateImageTilesMinimumOverlapInvocation"] | components["schemas"]["CreateDenoiseMaskInvocation"] | components["schemas"]["LatentsToImageInvocation"] | components["schemas"]["ZoeDepthImageProcessorInvocation"] | components["schemas"]["SubtractInvocation"] | components["schemas"]["T2IAdapterInvocation"] | components["schemas"]["StringSplitNegInvocation"] | components["schemas"]["SDXLRefinerCompelPromptInvocation"] | components["schemas"]["SeamlessModeInvocation"] | components["schemas"]["TileResamplerProcessorInvocation"] | components["schemas"]["ImageChannelMultiplyInvocation"] | components["schemas"]["StepParamEasingInvocation"] | components["schemas"]["MainModelLoaderInvocation"] | components["schemas"]["MetadataItemInvocation"] | components["schemas"]["CalculateImageTilesEvenSplitInvocation"] | components["schemas"]["ControlNetInvocation"] | components["schemas"]["DWOpenposeImageProcessorInvocation"] | components["schemas"]["ContentShuffleImageProcessorInvocation"] | components["schemas"]["FloatMathInvocation"] | components["schemas"]["NoiseInvocation"] | components["schemas"]["IntegerCollectionInvocation"] | components["schemas"]["DynamicPromptInvocation"] | components["schemas"]["CvInpaintInvocation"] | components["schemas"]["SDXLCompelPromptInvocation"] | components["schemas"]["CenterPadCropInvocation"] | components["schemas"]["CanvasPasteBackInvocation"] | components["schemas"]["MetadataInvocation"] | components["schemas"]["StringReplaceInvocation"] | components["schemas"]["DivideInvocation"] | components["schemas"]["ImageCollectionInvocation"] | components["schemas"]["ImageInverseLerpInvocation"] | components["schemas"]["RandomIntInvocation"] | components["schemas"]["LaMaInfillInvocation"] | components["schemas"]["MergeTilesToImageInvocation"] | components["schemas"]["ImageChannelInvocation"] | components["schemas"]["ImageWatermarkInvocation"] | components["schemas"]["BooleanCollectionInvocation"] | components["schemas"]["CalculateImageTilesInvocation"] | components["schemas"]["RangeInvocation"] | components["schemas"]["ImageToLatentsInvocation"] | components["schemas"]["FloatToIntegerInvocation"] | components["schemas"]["IntegerInvocation"] | components["schemas"]["InfillColorInvocation"] | components["schemas"]["StringInvocation"] | components["schemas"]["SegmentAnythingProcessorInvocation"] | components["schemas"]["ShowImageInvocation"] | components["schemas"]["RangeOfSizeInvocation"] | components["schemas"]["LineartImageProcessorInvocation"] | components["schemas"]["ImagePasteInvocation"] | components["schemas"]["PromptsFromFileInvocation"] | components["schemas"]["DepthAnythingImageProcessorInvocation"] | components["schemas"]["CV2InfillInvocation"] | components["schemas"]["CompelInvocation"] | components["schemas"]["HedImageProcessorInvocation"] | components["schemas"]["ImageBlurInvocation"] | components["schemas"]["CoreMetadataInvocation"] | components["schemas"]["PidiImageProcessorInvocation"] | components["schemas"]["MaskFromAlphaInvocation"] | components["schemas"]["SaveImageInvocation"] | components["schemas"]["ColorInvocation"] | components["schemas"]["StringJoinThreeInvocation"] | components["schemas"]["ColorMapImageProcessorInvocation"] | components["schemas"]["LatentsInvocation"] | components["schemas"]["CropLatentsCoreInvocation"] | components["schemas"]["MultiplyInvocation"] | components["schemas"]["ColorCorrectInvocation"] | components["schemas"]["LatentsCollectionInvocation"] | components["schemas"]["StringCollectionInvocation"] | components["schemas"]["ConditioningCollectionInvocation"] | components["schemas"]["BlendLatentsInvocation"] | components["schemas"]["ImageScaleInvocation"] | components["schemas"]["SDXLModelLoaderInvocation"] | components["schemas"]["FaceIdentifierInvocation"] | components["schemas"]["MergeMetadataInvocation"] | components["schemas"]["InfillTileInvocation"] | components["schemas"]["LoRALoaderInvocation"] | components["schemas"]["BlankImageInvocation"] | components["schemas"]["ImageNSFWBlurInvocation"] | components["schemas"]["MaskFromIDInvocation"] | components["schemas"]["IterateInvocation"] | components["schemas"]["SDXLRefinerModelLoaderInvocation"] | components["schemas"]["CLIPSkipInvocation"] | components["schemas"]["SDXLLoRALoaderInvocation"] | components["schemas"]["MediapipeFaceProcessorInvocation"] | components["schemas"]["RandomFloatInvocation"] | components["schemas"]["StringJoinInvocation"]; + [key: string]: components["schemas"]["FaceOffInvocation"] | components["schemas"]["ImageHueAdjustmentInvocation"] | components["schemas"]["AddInvocation"] | components["schemas"]["DWOpenposeImageProcessorInvocation"] | components["schemas"]["ImageInvocation"] | components["schemas"]["FloatLinearRangeInvocation"] | components["schemas"]["TileToPropertiesInvocation"] | components["schemas"]["SegmentAnythingProcessorInvocation"] | components["schemas"]["ImageLerpInvocation"] | components["schemas"]["SDXLRefinerCompelPromptInvocation"] | components["schemas"]["RandomFloatInvocation"] | components["schemas"]["SDXLModelLoaderInvocation"] | components["schemas"]["LoRALoaderInvocation"] | components["schemas"]["StringCollectionInvocation"] | components["schemas"]["MaskFromAlphaInvocation"] | components["schemas"]["MergeTilesToImageInvocation"] | components["schemas"]["TileResamplerProcessorInvocation"] | components["schemas"]["IPAdapterInvocation"] | components["schemas"]["CV2InfillInvocation"] | components["schemas"]["ZoeDepthImageProcessorInvocation"] | components["schemas"]["IntegerMathInvocation"] | components["schemas"]["ImageBlurInvocation"] | components["schemas"]["StepParamEasingInvocation"] | components["schemas"]["MlsdImageProcessorInvocation"] | components["schemas"]["MidasDepthImageProcessorInvocation"] | components["schemas"]["RandomIntInvocation"] | components["schemas"]["MetadataInvocation"] | components["schemas"]["FloatInvocation"] | components["schemas"]["CannyImageProcessorInvocation"] | components["schemas"]["ResizeLatentsInvocation"] | components["schemas"]["MergeMetadataInvocation"] | components["schemas"]["SDXLRefinerModelLoaderInvocation"] | components["schemas"]["FaceIdentifierInvocation"] | components["schemas"]["VAELoaderInvocation"] | components["schemas"]["LeresImageProcessorInvocation"] | components["schemas"]["DivideInvocation"] | components["schemas"]["FloatToIntegerInvocation"] | components["schemas"]["ImageScaleInvocation"] | components["schemas"]["ColorInvocation"] | components["schemas"]["LatentsToImageInvocation"] | components["schemas"]["IntegerInvocation"] | components["schemas"]["StringReplaceInvocation"] | components["schemas"]["ImageToLatentsInvocation"] | components["schemas"]["BlendLatentsInvocation"] | components["schemas"]["ImageNSFWBlurInvocation"] | components["schemas"]["ConditioningInvocation"] | components["schemas"]["ImageCollectionInvocation"] | components["schemas"]["ImageChannelOffsetInvocation"] | components["schemas"]["PairTileImageInvocation"] | components["schemas"]["ImageCropInvocation"] | components["schemas"]["ESRGANInvocation"] | components["schemas"]["IdealSizeInvocation"] | components["schemas"]["CenterPadCropInvocation"] | components["schemas"]["ImagePasteInvocation"] | components["schemas"]["CalculateImageTilesInvocation"] | components["schemas"]["ImageChannelMultiplyInvocation"] | components["schemas"]["FloatMathInvocation"] | components["schemas"]["NoiseInvocation"] | components["schemas"]["ControlNetInvocation"] | components["schemas"]["MainModelLoaderInvocation"] | components["schemas"]["MaskCombineInvocation"] | components["schemas"]["ImageResizeInvocation"] | components["schemas"]["LineartImageProcessorInvocation"] | components["schemas"]["UnsharpMaskInvocation"] | components["schemas"]["MetadataItemInvocation"] | components["schemas"]["MaskFromIDInvocation"] | components["schemas"]["CoreMetadataInvocation"] | components["schemas"]["PromptsFromFileInvocation"] | components["schemas"]["LatentsInvocation"] | components["schemas"]["LatentsCollectionInvocation"] | components["schemas"]["ImageWatermarkInvocation"] | components["schemas"]["BooleanInvocation"] | components["schemas"]["SaveImageInvocation"] | components["schemas"]["PidiImageProcessorInvocation"] | components["schemas"]["DenoiseLatentsInvocation"] | components["schemas"]["InfillTileInvocation"] | components["schemas"]["CvInpaintInvocation"] | components["schemas"]["HedImageProcessorInvocation"] | components["schemas"]["ConditioningCollectionInvocation"] | components["schemas"]["StringSplitNegInvocation"] | components["schemas"]["ColorMapImageProcessorInvocation"] | components["schemas"]["MultiplyInvocation"] | components["schemas"]["CanvasPasteBackInvocation"] | components["schemas"]["T2IAdapterInvocation"] | components["schemas"]["CreateDenoiseMaskInvocation"] | components["schemas"]["StringInvocation"] | components["schemas"]["SeamlessModeInvocation"] | components["schemas"]["CompelInvocation"] | components["schemas"]["DepthAnythingImageProcessorInvocation"] | components["schemas"]["SchedulerInvocation"] | components["schemas"]["SDXLLoRALoaderInvocation"] | components["schemas"]["CollectInvocation"] | components["schemas"]["FreeUInvocation"] | components["schemas"]["StringSplitInvocation"] | components["schemas"]["ScaleLatentsInvocation"] | components["schemas"]["ImageConvertInvocation"] | components["schemas"]["MediapipeFaceProcessorInvocation"] | components["schemas"]["BlankImageInvocation"] | components["schemas"]["ShowImageInvocation"] | components["schemas"]["RoundInvocation"] | components["schemas"]["IntegerCollectionInvocation"] | components["schemas"]["NormalbaeImageProcessorInvocation"] | components["schemas"]["LineartAnimeImageProcessorInvocation"] | components["schemas"]["SubtractInvocation"] | components["schemas"]["StringJoinInvocation"] | components["schemas"]["DynamicPromptInvocation"] | components["schemas"]["MosaicInfillInvocation"] | components["schemas"]["IterateInvocation"] | components["schemas"]["LaMaInfillInvocation"] | components["schemas"]["MaskEdgeInvocation"] | components["schemas"]["RangeOfSizeInvocation"] | components["schemas"]["CreateGradientMaskInvocation"] | components["schemas"]["ImageChannelInvocation"] | components["schemas"]["RangeInvocation"] | components["schemas"]["ColorCorrectInvocation"] | components["schemas"]["ContentShuffleImageProcessorInvocation"] | components["schemas"]["RandomRangeInvocation"] | components["schemas"]["FloatCollectionInvocation"] | components["schemas"]["CLIPSkipInvocation"] | components["schemas"]["InfillPatchMatchInvocation"] | components["schemas"]["CalculateImageTilesEvenSplitInvocation"] | components["schemas"]["ImageMultiplyInvocation"] | components["schemas"]["CropLatentsCoreInvocation"] | components["schemas"]["SDXLCompelPromptInvocation"] | components["schemas"]["StringJoinThreeInvocation"] | components["schemas"]["CalculateImageTilesMinimumOverlapInvocation"] | components["schemas"]["InfillColorInvocation"] | components["schemas"]["FaceMaskInvocation"] | components["schemas"]["BooleanCollectionInvocation"] | components["schemas"]["ImageInverseLerpInvocation"]; }; /** * Edges @@ -4149,7 +4149,7 @@ export type components = { * @description The results of node executions */ results: { - [key: string]: components["schemas"]["UNetOutput"] | components["schemas"]["ImageCollectionOutput"] | components["schemas"]["ColorCollectionOutput"] | components["schemas"]["String2Output"] | components["schemas"]["ControlOutput"] | components["schemas"]["StringCollectionOutput"] | components["schemas"]["DenoiseMaskOutput"] | components["schemas"]["SDXLRefinerModelLoaderOutput"] | components["schemas"]["IntegerOutput"] | components["schemas"]["SeamlessModeOutput"] | components["schemas"]["ImageOutput"] | components["schemas"]["SchedulerOutput"] | components["schemas"]["TileToPropertiesOutput"] | components["schemas"]["FaceOffOutput"] | components["schemas"]["BooleanCollectionOutput"] | components["schemas"]["IterateInvocationOutput"] | components["schemas"]["StringOutput"] | components["schemas"]["ConditioningCollectionOutput"] | components["schemas"]["CLIPSkipInvocationOutput"] | components["schemas"]["NoiseOutput"] | components["schemas"]["LoRALoaderOutput"] | components["schemas"]["StringPosNegOutput"] | components["schemas"]["SDXLLoRALoaderOutput"] | components["schemas"]["CollectInvocationOutput"] | components["schemas"]["IntegerCollectionOutput"] | components["schemas"]["LatentsCollectionOutput"] | components["schemas"]["SDXLModelLoaderOutput"] | components["schemas"]["MetadataOutput"] | components["schemas"]["PairTileImageOutput"] | components["schemas"]["ConditioningOutput"] | components["schemas"]["IdealSizeOutput"] | components["schemas"]["ColorOutput"] | components["schemas"]["ModelLoaderOutput"] | components["schemas"]["FloatCollectionOutput"] | components["schemas"]["MetadataItemOutput"] | components["schemas"]["CLIPOutput"] | components["schemas"]["BooleanOutput"] | components["schemas"]["FaceMaskOutput"] | components["schemas"]["T2IAdapterOutput"] | components["schemas"]["VAEOutput"] | components["schemas"]["FloatOutput"] | components["schemas"]["IPAdapterOutput"] | components["schemas"]["GradientMaskOutput"] | components["schemas"]["CalculateImageTilesOutput"] | components["schemas"]["LatentsOutput"]; + [key: string]: components["schemas"]["ControlOutput"] | components["schemas"]["CLIPOutput"] | components["schemas"]["CollectInvocationOutput"] | components["schemas"]["NoiseOutput"] | components["schemas"]["TileToPropertiesOutput"] | components["schemas"]["FloatOutput"] | components["schemas"]["GradientMaskOutput"] | components["schemas"]["ConditioningOutput"] | components["schemas"]["FaceOffOutput"] | components["schemas"]["String2Output"] | components["schemas"]["StringCollectionOutput"] | components["schemas"]["DenoiseMaskOutput"] | components["schemas"]["FloatCollectionOutput"] | components["schemas"]["SDXLLoRALoaderOutput"] | components["schemas"]["StringOutput"] | components["schemas"]["StringPosNegOutput"] | components["schemas"]["LatentsCollectionOutput"] | components["schemas"]["LoRALoaderOutput"] | components["schemas"]["CalculateImageTilesOutput"] | components["schemas"]["MetadataItemOutput"] | components["schemas"]["ConditioningCollectionOutput"] | components["schemas"]["IntegerOutput"] | components["schemas"]["FaceMaskOutput"] | components["schemas"]["MetadataOutput"] | components["schemas"]["BooleanOutput"] | components["schemas"]["ColorCollectionOutput"] | components["schemas"]["IPAdapterOutput"] | components["schemas"]["BooleanCollectionOutput"] | components["schemas"]["SeamlessModeOutput"] | components["schemas"]["PairTileImageOutput"] | components["schemas"]["IdealSizeOutput"] | components["schemas"]["SchedulerOutput"] | components["schemas"]["LatentsOutput"] | components["schemas"]["SDXLModelLoaderOutput"] | components["schemas"]["SDXLRefinerModelLoaderOutput"] | components["schemas"]["ModelLoaderOutput"] | components["schemas"]["IterateInvocationOutput"] | components["schemas"]["IntegerCollectionOutput"] | components["schemas"]["CLIPSkipInvocationOutput"] | components["schemas"]["T2IAdapterOutput"] | components["schemas"]["ImageCollectionOutput"] | components["schemas"]["UNetOutput"] | components["schemas"]["ImageOutput"] | components["schemas"]["ColorOutput"] | components["schemas"]["VAEOutput"]; }; /** * Errors @@ -5693,7 +5693,7 @@ export type components = { * @default true */ use_cache?: boolean; - /** @description The image to infill */ + /** @description The image to process */ image?: components["schemas"]["ImageField"]; /** * @description The color to use to infill @@ -5738,7 +5738,7 @@ export type components = { * @default true */ use_cache?: boolean; - /** @description The image to infill */ + /** @description The image to process */ image?: components["schemas"]["ImageField"]; /** * Downscale @@ -5786,7 +5786,7 @@ export type components = { * @default true */ use_cache?: boolean; - /** @description The image to infill */ + /** @description The image to process */ image?: components["schemas"]["ImageField"]; /** * Tile Size @@ -6088,7 +6088,7 @@ export type components = { * @default true */ use_cache?: boolean; - /** @description The image to infill */ + /** @description The image to process */ image?: components["schemas"]["ImageField"]; /** * type @@ -7702,6 +7702,73 @@ export type components = { /** Models */ models: (components["schemas"]["MainDiffusersConfig"] | components["schemas"]["MainCheckpointConfig"] | components["schemas"]["VAEDiffusersConfig"] | components["schemas"]["VAECheckpointConfig"] | components["schemas"]["ControlNetDiffusersConfig"] | components["schemas"]["ControlNetCheckpointConfig"] | components["schemas"]["LoRALyCORISConfig"] | components["schemas"]["LoRADiffusersConfig"] | components["schemas"]["TextualInversionFileConfig"] | components["schemas"]["TextualInversionFolderConfig"] | components["schemas"]["IPAdapterInvokeAIConfig"] | components["schemas"]["IPAdapterCheckpointConfig"] | components["schemas"]["T2IAdapterConfig"] | components["schemas"]["CLIPVisionDiffusersConfig"])[]; }; + /** + * Mosaic Infill + * @description Infills transparent areas of an image with a mosaic pattern drawing colors from the rest of the image + */ + MosaicInfillInvocation: { + /** @description The board to save the image to */ + board?: components["schemas"]["BoardField"] | null; + /** @description Optional metadata to be saved with the image */ + metadata?: components["schemas"]["MetadataField"] | null; + /** + * Id + * @description The id of this instance of an invocation. Must be unique among all instances of invocations. + */ + id: string; + /** + * Is Intermediate + * @description Whether or not this is an intermediate invocation. + * @default false + */ + is_intermediate?: boolean; + /** + * Use Cache + * @description Whether or not to use the cache + * @default true + */ + use_cache?: boolean; + /** @description The image to infill */ + image?: components["schemas"]["ImageField"]; + /** + * Tile Width + * @description Width of the tile + * @default 64 + */ + tile_width?: number; + /** + * Tile Height + * @description Height of the tile + * @default 64 + */ + tile_height?: number; + /** + * @description The min threshold for color + * @default { + * "r": 0, + * "g": 0, + * "b": 0, + * "a": 255 + * } + */ + min_color?: components["schemas"]["ColorField"]; + /** + * @description The max threshold for color + * @default { + * "r": 255, + * "g": 255, + * "b": 255, + * "a": 255 + * } + */ + max_color?: components["schemas"]["ColorField"]; + /** + * type + * @default infill_mosaic + * @constant + */ + type: "infill_mosaic"; + }; /** * Multiply Integers * @description Multiplies two numbers From e55ab5b3a1e50d33410fb446d54329c649443bca Mon Sep 17 00:00:00 2001 From: blessedcoolant <54517381+blessedcoolant@users.noreply.github.com> Date: Sat, 30 Mar 2024 15:02:51 +0530 Subject: [PATCH 58/89] ui: Color Infill UI --- invokeai/frontend/web/public/locales/en.json | 1 + .../util/graph/buildCanvasOutpaintGraph.ts | 2 + .../graph/buildCanvasSDXLOutpaintGraph.ts | 2 + .../ParamInfillColorOptions.tsx | 46 +++++++++++++++++++ ...tions.tsx => ParamInfillMosaicOptions.tsx} | 4 +- .../InfillAndScaling/ParamInfillOptions.tsx | 9 +++- .../parameters/store/generationSlice.ts | 31 +++++++------ .../src/features/parameters/store/types.ts | 5 +- 8 files changed, 81 insertions(+), 19 deletions(-) create mode 100644 invokeai/frontend/web/src/features/parameters/components/Canvas/InfillAndScaling/ParamInfillColorOptions.tsx rename invokeai/frontend/web/src/features/parameters/components/Canvas/InfillAndScaling/{ParamMosaicInfillOptions.tsx => ParamInfillMosaicOptions.tsx} (97%) diff --git a/invokeai/frontend/web/public/locales/en.json b/invokeai/frontend/web/public/locales/en.json index 7f31da089e..96fb8e5748 100644 --- a/invokeai/frontend/web/public/locales/en.json +++ b/invokeai/frontend/web/public/locales/en.json @@ -891,6 +891,7 @@ "infillMosaicTileHeight": "Tile Height", "infillMosaicMinColor": "Min Color", "infillMosaicMaxColor": "Max Color", + "infillColorValue": "Fill Color", "info": "Info", "invoke": { "addingImagesTo": "Adding images to", diff --git a/invokeai/frontend/web/src/features/nodes/util/graph/buildCanvasOutpaintGraph.ts b/invokeai/frontend/web/src/features/nodes/util/graph/buildCanvasOutpaintGraph.ts index faad4189a9..30dd44aa18 100644 --- a/invokeai/frontend/web/src/features/nodes/util/graph/buildCanvasOutpaintGraph.ts +++ b/invokeai/frontend/web/src/features/nodes/util/graph/buildCanvasOutpaintGraph.ts @@ -69,6 +69,7 @@ export const buildCanvasOutpaintGraph = async ( infillMosaicTileHeight, infillMosaicMinColor, infillMosaicMaxColor, + infillColorValue, clipSkip, seamlessXAxis, seamlessYAxis, @@ -376,6 +377,7 @@ export const buildCanvasOutpaintGraph = async ( graph.nodes[INPAINT_INFILL] = { type: 'infill_rgba', id: INPAINT_INFILL, + color: infillColorValue, is_intermediate, }; } diff --git a/invokeai/frontend/web/src/features/nodes/util/graph/buildCanvasSDXLOutpaintGraph.ts b/invokeai/frontend/web/src/features/nodes/util/graph/buildCanvasSDXLOutpaintGraph.ts index efe9d51990..4ec00a2cd6 100644 --- a/invokeai/frontend/web/src/features/nodes/util/graph/buildCanvasSDXLOutpaintGraph.ts +++ b/invokeai/frontend/web/src/features/nodes/util/graph/buildCanvasSDXLOutpaintGraph.ts @@ -70,6 +70,7 @@ export const buildCanvasSDXLOutpaintGraph = async ( infillMosaicTileHeight, infillMosaicMinColor, infillMosaicMaxColor, + infillColorValue, seamlessXAxis, seamlessYAxis, canvasCoherenceMode, @@ -386,6 +387,7 @@ export const buildCanvasSDXLOutpaintGraph = async ( type: 'infill_rgba', id: INPAINT_INFILL, is_intermediate, + color: infillColorValue, }; } diff --git a/invokeai/frontend/web/src/features/parameters/components/Canvas/InfillAndScaling/ParamInfillColorOptions.tsx b/invokeai/frontend/web/src/features/parameters/components/Canvas/InfillAndScaling/ParamInfillColorOptions.tsx new file mode 100644 index 0000000000..1cafe4310e --- /dev/null +++ b/invokeai/frontend/web/src/features/parameters/components/Canvas/InfillAndScaling/ParamInfillColorOptions.tsx @@ -0,0 +1,46 @@ +import { Box, Flex, FormControl, FormLabel } from '@invoke-ai/ui-library'; +import { createSelector } from '@reduxjs/toolkit'; +import { useAppDispatch, useAppSelector } from 'app/store/storeHooks'; +import IAIColorPicker from 'common/components/IAIColorPicker'; +import { selectGenerationSlice, setInfillColorValue } from 'features/parameters/store/generationSlice'; +import { memo, useCallback, useMemo } from 'react'; +import type { RgbaColor } from 'react-colorful'; +import { useTranslation } from 'react-i18next'; + +const ParamInfillColorOptions = () => { + const dispatch = useAppDispatch(); + + const selector = useMemo( + () => + createSelector(selectGenerationSlice, (generation) => ({ + infillColor: generation.infillColorValue, + })), + [] + ); + + const { infillColor } = useAppSelector(selector); + + const infillMethod = useAppSelector((s) => s.generation.infillMethod); + + const { t } = useTranslation(); + + const handleInfillColor = useCallback( + (v: RgbaColor) => { + dispatch(setInfillColorValue(v)); + }, + [dispatch] + ); + + return ( + + + {t('parameters.infillColorValue')} + + + + + + ); +}; + +export default memo(ParamInfillColorOptions); diff --git a/invokeai/frontend/web/src/features/parameters/components/Canvas/InfillAndScaling/ParamMosaicInfillOptions.tsx b/invokeai/frontend/web/src/features/parameters/components/Canvas/InfillAndScaling/ParamInfillMosaicOptions.tsx similarity index 97% rename from invokeai/frontend/web/src/features/parameters/components/Canvas/InfillAndScaling/ParamMosaicInfillOptions.tsx rename to invokeai/frontend/web/src/features/parameters/components/Canvas/InfillAndScaling/ParamInfillMosaicOptions.tsx index 47c11d5669..f164bb903e 100644 --- a/invokeai/frontend/web/src/features/parameters/components/Canvas/InfillAndScaling/ParamMosaicInfillOptions.tsx +++ b/invokeai/frontend/web/src/features/parameters/components/Canvas/InfillAndScaling/ParamInfillMosaicOptions.tsx @@ -13,7 +13,7 @@ import { memo, useCallback, useMemo } from 'react'; import type { RgbaColor } from 'react-colorful'; import { useTranslation } from 'react-i18next'; -const ParamMosaicInfillTileSize = () => { +const ParamInfillMosaicTileSize = () => { const dispatch = useAppDispatch(); const selector = useMemo( @@ -124,4 +124,4 @@ const ParamMosaicInfillTileSize = () => { ); }; -export default memo(ParamMosaicInfillTileSize); +export default memo(ParamInfillMosaicTileSize); diff --git a/invokeai/frontend/web/src/features/parameters/components/Canvas/InfillAndScaling/ParamInfillOptions.tsx b/invokeai/frontend/web/src/features/parameters/components/Canvas/InfillAndScaling/ParamInfillOptions.tsx index 607a2ed809..04e4727885 100644 --- a/invokeai/frontend/web/src/features/parameters/components/Canvas/InfillAndScaling/ParamInfillOptions.tsx +++ b/invokeai/frontend/web/src/features/parameters/components/Canvas/InfillAndScaling/ParamInfillOptions.tsx @@ -1,9 +1,10 @@ import { useAppSelector } from 'app/store/storeHooks'; import { memo } from 'react'; +import ParamInfillColorOptions from './ParamInfillColorOptions'; +import ParamInfillMosaicOptions from './ParamInfillMosaicOptions'; import ParamInfillPatchmatchDownscaleSize from './ParamInfillPatchmatchDownscaleSize'; import ParamInfillTilesize from './ParamInfillTilesize'; -import ParamMosaicInfillOptions from './ParamMosaicInfillOptions'; const ParamInfillOptions = () => { const infillMethod = useAppSelector((s) => s.generation.infillMethod); @@ -16,7 +17,11 @@ const ParamInfillOptions = () => { } if (infillMethod === 'mosaic') { - return ; + return ; + } + + if (infillMethod === 'color') { + return ; } return null; diff --git a/invokeai/frontend/web/src/features/parameters/store/generationSlice.ts b/invokeai/frontend/web/src/features/parameters/store/generationSlice.ts index 7572ffeb90..0da6e21d9f 100644 --- a/invokeai/frontend/web/src/features/parameters/store/generationSlice.ts +++ b/invokeai/frontend/web/src/features/parameters/store/generationSlice.ts @@ -44,8 +44,6 @@ const initialGenerationState: GenerationState = { shouldFitToWidthHeight: true, shouldRandomizeSeed: true, steps: 50, - infillTileSize: 32, - infillPatchmatchDownscaleSize: 1, width: 512, model: null, vae: null, @@ -56,10 +54,13 @@ const initialGenerationState: GenerationState = { shouldUseCpuNoise: true, shouldShowAdvancedOptions: false, aspectRatio: { ...initialAspectRatioState }, + infillTileSize: 32, + infillPatchmatchDownscaleSize: 1, infillMosaicTileWidth: 64, infillMosaicTileHeight: 64, infillMosaicMinColor: { r: 0, g: 0, b: 0, a: 1 }, infillMosaicMaxColor: { r: 255, g: 255, b: 255, a: 1 }, + infillColorValue: { r: 0, g: 0, b: 0, a: 1 }, }; export const generationSlice = createSlice({ @@ -121,15 +122,6 @@ export const generationSlice = createSlice({ setCanvasCoherenceMinDenoise: (state, action: PayloadAction) => { state.canvasCoherenceMinDenoise = action.payload; }, - setInfillMethod: (state, action: PayloadAction) => { - state.infillMethod = action.payload; - }, - setInfillTileSize: (state, action: PayloadAction) => { - state.infillTileSize = action.payload; - }, - setInfillPatchmatchDownscaleSize: (state, action: PayloadAction) => { - state.infillPatchmatchDownscaleSize = action.payload; - }, initialImageChanged: (state, action: PayloadAction) => { const { image_name, width, height } = action.payload; state.initialImage = { imageName: image_name, width, height }; @@ -211,6 +203,15 @@ export const generationSlice = createSlice({ aspectRatioChanged: (state, action: PayloadAction) => { state.aspectRatio = action.payload; }, + setInfillMethod: (state, action: PayloadAction) => { + state.infillMethod = action.payload; + }, + setInfillTileSize: (state, action: PayloadAction) => { + state.infillTileSize = action.payload; + }, + setInfillPatchmatchDownscaleSize: (state, action: PayloadAction) => { + state.infillPatchmatchDownscaleSize = action.payload; + }, setInfillMosaicTileWidth: (state, action: PayloadAction) => { state.infillMosaicTileWidth = action.payload; }, @@ -223,6 +224,9 @@ export const generationSlice = createSlice({ setInfillMosaicMaxColor: (state, action: PayloadAction) => { state.infillMosaicMaxColor = action.payload; }, + setInfillColorValue: (state, action: PayloadAction) => { + state.infillColorValue = action.payload; + }, }, extraReducers: (builder) => { builder.addCase(configChanged, (state, action) => { @@ -266,8 +270,6 @@ export const { setShouldFitToWidthHeight, setShouldRandomizeSeed, setSteps, - setInfillTileSize, - setInfillPatchmatchDownscaleSize, initialImageChanged, modelChanged, vaeSelected, @@ -281,10 +283,13 @@ export const { heightChanged, widthRecalled, heightRecalled, + setInfillTileSize, + setInfillPatchmatchDownscaleSize, setInfillMosaicTileWidth, setInfillMosaicTileHeight, setInfillMosaicMinColor, setInfillMosaicMaxColor, + setInfillColorValue, } = generationSlice.actions; export const { selectOptimalDimension } = generationSlice.selectors; diff --git a/invokeai/frontend/web/src/features/parameters/store/types.ts b/invokeai/frontend/web/src/features/parameters/store/types.ts index 1e69bf52d5..773cfbf925 100644 --- a/invokeai/frontend/web/src/features/parameters/store/types.ts +++ b/invokeai/frontend/web/src/features/parameters/store/types.ts @@ -40,8 +40,6 @@ export interface GenerationState { shouldFitToWidthHeight: boolean; shouldRandomizeSeed: boolean; steps: ParameterSteps; - infillTileSize: number; - infillPatchmatchDownscaleSize: number; width: ParameterWidth; model: ParameterModel | null; vae: ParameterVAEModel | null; @@ -52,10 +50,13 @@ export interface GenerationState { shouldUseCpuNoise: boolean; shouldShowAdvancedOptions: boolean; aspectRatio: AspectRatioState; + infillTileSize: number; + infillPatchmatchDownscaleSize: number; infillMosaicTileWidth: number; infillMosaicTileHeight: number; infillMosaicMinColor: RgbaColor; infillMosaicMaxColor: RgbaColor; + infillColorValue: RgbaColor; } export type PayloadActionWithOptimalDimension = PayloadAction; From b061db414f11f643788d68a11a3b1e01d248203e Mon Sep 17 00:00:00 2001 From: psychedelicious <4822129+psychedelicious@users.noreply.github.com> Date: Mon, 1 Apr 2024 19:30:55 +1100 Subject: [PATCH 59/89] tidy(nodes): abstractmethod is noop --- invokeai/app/invocations/infill.py | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/invokeai/app/invocations/infill.py b/invokeai/app/invocations/infill.py index 325bedaba1..ad41a81813 100644 --- a/invokeai/app/invocations/infill.py +++ b/invokeai/app/invocations/infill.py @@ -39,8 +39,8 @@ class InfillImageProcessorInvocation(BaseInvocation, WithMetadata, WithBoard): @abstractmethod def infill(self, image: Image.Image) -> Image.Image: - """Abstract to perform various infilling techniques""" - return image + """Infill the image with the specified method""" + pass def load_image(self, context: InvocationContext) -> tuple[Image.Image, bool]: """Process the image to have an alpha channel before being infilled""" @@ -110,7 +110,6 @@ class InfillPatchMatchInvocation(InfillImageProcessorInvocation): resample_mode: PIL_RESAMPLING_MODES = InputField(default="bicubic", description="The resampling mode") def infill(self, image: Image.Image): - resample_mode = PIL_RESAMPLING_MAP[self.resample_mode] width = int(image.width / self.downscale) From f0b1bb03270cd59d2b0bbbf04e8fc2ef143b6e57 Mon Sep 17 00:00:00 2001 From: psychedelicious <4822129+psychedelicious@users.noreply.github.com> Date: Thu, 4 Apr 2024 21:45:05 +1100 Subject: [PATCH 60/89] feat(nodes): redo tile infill The previous algorithm errored if the image wasn't divisible by the tile size. I've reimplemented it from scratch to mitigate this issue. The new algorithm is simpler. We create a pool of tiles, then use them to create an image composed completely of tiles. If there is any awkwardly sized space on the edge of the image, the tiles are cropped to fit. Finally, paste the original image over the tile image. I've added a jupyter notebook to do a smoke test of infilling methods, and 10 test images. The other infill algorithms can be easily tested with the notebook on the same images, though I didn't set that up yet. Tested and confirmed this gives results just as good as the earlier infill, though of course they aren't the same due to the change in the algorithm. --- invokeai/app/invocations/infill.py | 5 +- .../infill_methods/test_images/source1.webp | Bin 0 -> 45948 bytes .../infill_methods/test_images/source10.webp | Bin 0 -> 2262 bytes .../infill_methods/test_images/source2.webp | Bin 0 -> 36752 bytes .../infill_methods/test_images/source3.webp | Bin 0 -> 33716 bytes .../infill_methods/test_images/source4.webp | Bin 0 -> 21708 bytes .../infill_methods/test_images/source5.webp | Bin 0 -> 39852 bytes .../infill_methods/test_images/source6.webp | Bin 0 -> 42794 bytes .../infill_methods/test_images/source7.webp | Bin 0 -> 49582 bytes .../infill_methods/test_images/source8.webp | Bin 0 -> 50096 bytes .../infill_methods/test_images/source9.webp | Bin 0 -> 60934 bytes .../image_util/infill_methods/tile.ipynb | 95 ++++++++++ .../backend/image_util/infill_methods/tile.py | 170 +++++++++++------- 13 files changed, 207 insertions(+), 63 deletions(-) create mode 100644 invokeai/backend/image_util/infill_methods/test_images/source1.webp create mode 100644 invokeai/backend/image_util/infill_methods/test_images/source10.webp create mode 100644 invokeai/backend/image_util/infill_methods/test_images/source2.webp create mode 100644 invokeai/backend/image_util/infill_methods/test_images/source3.webp create mode 100644 invokeai/backend/image_util/infill_methods/test_images/source4.webp create mode 100644 invokeai/backend/image_util/infill_methods/test_images/source5.webp create mode 100644 invokeai/backend/image_util/infill_methods/test_images/source6.webp create mode 100644 invokeai/backend/image_util/infill_methods/test_images/source7.webp create mode 100644 invokeai/backend/image_util/infill_methods/test_images/source8.webp create mode 100644 invokeai/backend/image_util/infill_methods/test_images/source9.webp create mode 100644 invokeai/backend/image_util/infill_methods/tile.ipynb diff --git a/invokeai/app/invocations/infill.py b/invokeai/app/invocations/infill.py index ad41a81813..742ea6a121 100644 --- a/invokeai/app/invocations/infill.py +++ b/invokeai/app/invocations/infill.py @@ -95,9 +95,8 @@ class InfillTileInvocation(InfillImageProcessorInvocation): ) def infill(self, image: Image.Image): - infilled = infill_tile(image, seed=self.seed, tile_size=self.tile_size) - infilled.paste(image, (0, 0), image.split()[-1]) - return infilled + output = infill_tile(image, seed=self.seed, tile_size=self.tile_size) + return output.infilled @invocation( diff --git a/invokeai/backend/image_util/infill_methods/test_images/source1.webp b/invokeai/backend/image_util/infill_methods/test_images/source1.webp new file mode 100644 index 0000000000000000000000000000000000000000..7057eefa85f27b7f043838f5b14ca3048a14a633 GIT binary patch literal 45948 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 literal 0 HcmV?d00001 diff --git a/invokeai/backend/image_util/infill_methods/test_images/source2.webp b/invokeai/backend/image_util/infill_methods/test_images/source2.webp new file mode 100644 index 0000000000000000000000000000000000000000..b25060024a7164a431c21570902daec0d8b70c54 GIT binary patch literal 36752 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%0A7-~WfQ-*76D#-Pd^Nc*Pax7 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 literal 0 HcmV?d00001 diff --git a/invokeai/backend/image_util/infill_methods/test_images/source6.webp b/invokeai/backend/image_util/infill_methods/test_images/source6.webp new file mode 100644 index 0000000000000000000000000000000000000000..e16e132004938e11cfb920fc1bc859bfc33864e8 GIT binary patch literal 42794 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 literal 0 HcmV?d00001 diff --git a/invokeai/backend/image_util/infill_methods/test_images/source9.webp b/invokeai/backend/image_util/infill_methods/test_images/source9.webp new file mode 100644 index 0000000000000000000000000000000000000000..062f25ed8d559621a20beac4b9e4dd2d64b5bb79 GIT binary patch literal 60934 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 literal 0 HcmV?d00001 diff --git a/invokeai/backend/image_util/infill_methods/tile.ipynb b/invokeai/backend/image_util/infill_methods/tile.ipynb new file mode 100644 index 0000000000..eac7a43657 --- /dev/null +++ b/invokeai/backend/image_util/infill_methods/tile.ipynb @@ -0,0 +1,95 @@ +{ + "cells": [ + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "\"\"\"Smoke test for the tile infill\"\"\"\n", + "\n", + "from pathlib import Path\n", + "from typing import Optional\n", + "from PIL import Image\n", + "from invokeai.backend.image_util.infill_methods.tile import infill_tile\n", + "\n", + "images: list[tuple[str, Image.Image]] = []\n", + "\n", + "for i in sorted(Path(\"./test_images/\").glob(\"*.webp\")):\n", + " images.append((i.name, Image.open(i)))\n", + " images.append((i.name, Image.open(i).transpose(Image.FLIP_LEFT_RIGHT)))\n", + " images.append((i.name, Image.open(i).transpose(Image.FLIP_TOP_BOTTOM)))\n", + " images.append((i.name, Image.open(i).resize((512, 512))))\n", + " images.append((i.name, Image.open(i).resize((1234, 461))))\n", + "\n", + "outputs: list[tuple[str, Image.Image, Image.Image, Optional[Image.Image]]] = []\n", + "\n", + "for name, image in images:\n", + " try:\n", + " output = infill_tile(image, seed=0, tile_size=32)\n", + " outputs.append((name, image, output.infilled, output.tile_image))\n", + " except ValueError as e:\n", + " print(f\"Skipping image {name}: {e}\")" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "# Display the images in jupyter notebook\n", + "import matplotlib.pyplot as plt\n", + "from PIL import ImageOps\n", + "\n", + "fig, axes = plt.subplots(len(outputs), 3, figsize=(10, 3 * len(outputs)))\n", + "plt.subplots_adjust(hspace=0)\n", + "\n", + "for i, (name, original, infilled, tile_image) in enumerate(outputs):\n", + " # Add a border to each image, helps to see the edges\n", + " size = original.size\n", + " original = ImageOps.expand(original, border=5, fill=\"red\")\n", + " filled = ImageOps.expand(infilled, border=5, fill=\"red\")\n", + " if tile_image:\n", + " tile_image = ImageOps.expand(tile_image, border=5, fill=\"red\")\n", + "\n", + " axes[i, 0].imshow(original)\n", + " axes[i, 0].axis(\"off\")\n", + " axes[i, 0].set_title(f\"Original ({name} - {size})\")\n", + "\n", + " if tile_image:\n", + " axes[i, 1].imshow(tile_image)\n", + " axes[i, 1].axis(\"off\")\n", + " axes[i, 1].set_title(\"Tile Image\")\n", + " else:\n", + " axes[i, 1].axis(\"off\")\n", + " axes[i, 1].set_title(\"NO TILES GENERATED (NO TRANSPARENCY)\")\n", + "\n", + " axes[i, 2].imshow(filled)\n", + " axes[i, 2].axis(\"off\")\n", + " axes[i, 2].set_title(\"Filled\")" + ] + } + ], + "metadata": { + "kernelspec": { + "display_name": ".invokeai", + "language": "python", + "name": "python3" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.10.12" + } + }, + "nbformat": 4, + "nbformat_minor": 2 +} diff --git a/invokeai/backend/image_util/infill_methods/tile.py b/invokeai/backend/image_util/infill_methods/tile.py index d99d93fb95..03cb6c1a8c 100644 --- a/invokeai/backend/image_util/infill_methods/tile.py +++ b/invokeai/backend/image_util/infill_methods/tile.py @@ -1,72 +1,122 @@ -import math +from dataclasses import dataclass from typing import Optional import numpy as np from PIL import Image -def get_tile_images(image: np.ndarray, width: int = 8, height: int = 8): - _nrows, _ncols, depth = image.shape - _strides = image.strides +def create_tile_pool(img_array: np.ndarray, tile_size: tuple[int, int]) -> list[np.ndarray]: + """ + Create a pool of tiles from non-transparent areas of the image by systematically walking through the image. - nrows, _m = divmod(_nrows, height) - ncols, _n = divmod(_ncols, width) - if _m != 0 or _n != 0: - return None + Args: + img_array: numpy array of the image. + tile_size: tuple (tile_width, tile_height) specifying the size of each tile. - return np.lib.stride_tricks.as_strided( - np.ravel(image), - shape=(nrows, ncols, height, width, depth), - strides=(height * _strides[0], width * _strides[1], *_strides), - writeable=False, - ) + Returns: + A list of numpy arrays, each representing a tile. + """ + tiles: list[np.ndarray] = [] + rows, cols = img_array.shape[:2] + tile_width, tile_height = tile_size + for y in range(0, rows - tile_height + 1, tile_height): + for x in range(0, cols - tile_width + 1, tile_width): + tile = img_array[y : y + tile_height, x : x + tile_width] + # Check if the image has an alpha channel and the tile is completely opaque + if img_array.shape[2] == 4 and np.all(tile[:, :, 3] == 255): + tiles.append(tile) + elif img_array.shape[2] == 3: # If no alpha channel, append the tile + tiles.append(tile) -def infill_tile(im: Image.Image, tile_size: int = 16, seed: Optional[int] = None) -> Image.Image: - # Only fill if there's an alpha layer - if im.mode != "RGBA": - return im - - a = np.asarray(im, dtype=np.uint8) - - tile_size_tuple = (tile_size, tile_size) - - # Get the image as tiles of a specified size - tiles = get_tile_images(a, *tile_size_tuple).copy() - - # Get the mask as tiles - tiles_mask = tiles[:, :, :, :, 3] - - # Find any mask tiles with any fully transparent pixels (we will be replacing these later) - tmask_shape = tiles_mask.shape - tiles_mask = tiles_mask.reshape(math.prod(tiles_mask.shape)) - n, ny = (math.prod(tmask_shape[0:2])), math.prod(tmask_shape[2:]) - tiles_mask = tiles_mask > 0 - tiles_mask = tiles_mask.reshape((n, ny)).all(axis=1) - - # Get RGB tiles in single array and filter by the mask - tshape = tiles.shape - tiles_all = tiles.reshape((math.prod(tiles.shape[0:2]), *tiles.shape[2:])) - filtered_tiles = tiles_all[tiles_mask] - - if len(filtered_tiles) == 0: - return im - - # Find all invalid tiles and replace with a random valid tile - replace_count = (tiles_mask == False).sum() # noqa: E712 - rng = np.random.default_rng(seed=seed) - tiles_all[np.logical_not(tiles_mask)] = filtered_tiles[rng.choice(filtered_tiles.shape[0], replace_count), :, :, :] - - # Convert back to an image - tiles_all = tiles_all.reshape(tshape) - tiles_all = tiles_all.swapaxes(1, 2) - st = tiles_all.reshape( - ( - math.prod(tiles_all.shape[0:2]), - math.prod(tiles_all.shape[2:4]), - tiles_all.shape[4], + if not tiles: + raise ValueError( + "Not enough opaque pixels to generate any tiles. Use a smaller tile size or a different image." ) - ) - si = Image.fromarray(st, mode="RGBA") - return si + return tiles + + +def create_filled_image( + img_array: np.ndarray, tile_pool: list[np.ndarray], tile_size: tuple[int, int], seed: int +) -> np.ndarray: + """ + Create an image of the same dimensions as the original, filled entirely with tiles from the pool. + + Args: + img_array: numpy array of the original image. + tile_pool: A list of numpy arrays, each representing a tile. + tile_size: tuple (tile_width, tile_height) specifying the size of each tile. + + Returns: + A numpy array representing the filled image. + """ + + rows, cols, _ = img_array.shape + tile_width, tile_height = tile_size + + # Prep an empty RGB image + filled_img_array = np.zeros((rows, cols, 3), dtype=img_array.dtype) + + # Make the random tile selection reproducible + rng = np.random.default_rng(seed) + + for y in range(0, rows, tile_height): + for x in range(0, cols, tile_width): + # Pick a random tile from the pool + tile = tile_pool[rng.integers(len(tile_pool))] + + # Calculate the space available (may be less than tile size near the edges) + space_y = min(tile_height, rows - y) + space_x = min(tile_width, cols - x) + + # Crop the tile if necessary to fit into the available space + cropped_tile = tile[:space_y, :space_x, :3] + + # Fill the available space with the (possibly cropped) tile + filled_img_array[y : y + space_y, x : x + space_x, :3] = cropped_tile + + return filled_img_array + + +@dataclass +class InfillTileOutput: + infilled: Image.Image + tile_image: Optional[Image.Image] = None + + +def infill_tile(image_to_infill: Image.Image, seed: int, tile_size: int) -> InfillTileOutput: + """Infills an image with random tiles from the image itself. + + If the image is not an RGBA image, it is returned untouched. + + Args: + image: The image to infill. + tile_size: The size of the tiles to use for infilling. + + Raises: + ValueError: If there are not enough opaque pixels to generate any tiles. + """ + + if image_to_infill.mode != "RGBA": + return InfillTileOutput(infilled=image_to_infill) + + # Internally, we want a tuple of (tile_width, tile_height). In the future, the tile size can be any rectangle. + _tile_size = (tile_size, tile_size) + np_image = np.array(image_to_infill, dtype=np.uint8) + + # Create the pool of tiles that we will use to infill + tile_pool = create_tile_pool(np_image, _tile_size) + + # Create an image from the tiles, same size as the original + tile_np_image = create_filled_image(np_image, tile_pool, _tile_size, seed) + + # Paste the OG image over the tile image, effectively infilling the area + tile_image = Image.fromarray(tile_np_image, "RGB") + infilled = tile_image.copy() + infilled.paste(image_to_infill, (0, 0), image_to_infill.split()[-1]) + + # I think we want this to be "RGBA"? + infilled.convert("RGBA") + + return InfillTileOutput(infilled=infilled, tile_image=tile_image) From d6ccd5bc8179ad05804301e587c4760dbf52c139 Mon Sep 17 00:00:00 2001 From: psychedelicious <4822129+psychedelicious@users.noreply.github.com> Date: Fri, 5 Apr 2024 07:58:05 +1100 Subject: [PATCH 61/89] feat(nodes): disable mosaic fill Needs a bit of tweaking, leaving the code in just disabled/commented it out. --- invokeai/app/api/routers/app_info.py | 2 +- invokeai/app/invocations/infill.py | 10 +++++----- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/invokeai/app/api/routers/app_info.py b/invokeai/app/api/routers/app_info.py index fadc749c3b..21286ac2b0 100644 --- a/invokeai/app/api/routers/app_info.py +++ b/invokeai/app/api/routers/app_info.py @@ -100,7 +100,7 @@ async def get_app_deps() -> AppDependencyVersions: @app_router.get("/config", operation_id="get_config", status_code=200, response_model=AppConfig) async def get_config() -> AppConfig: - infill_methods = ["tile", "lama", "cv2", "color", "mosaic"] + infill_methods = ["tile", "lama", "cv2", "color"] # TODO: add mosaic back if PatchMatch.patchmatch_available(): infill_methods.append("patchmatch") diff --git a/invokeai/app/invocations/infill.py b/invokeai/app/invocations/infill.py index 742ea6a121..418bc62fdc 100644 --- a/invokeai/app/invocations/infill.py +++ b/invokeai/app/invocations/infill.py @@ -22,9 +22,9 @@ logger = InvokeAILogger.get_logger() def get_infill_methods(): - methods = Literal["tile", "color", "lama", "cv2", "mosaic"] + methods = Literal["tile", "color", "lama", "cv2"] # TODO: add mosaic back if PatchMatch.patchmatch_available(): - methods = Literal["patchmatch", "tile", "color", "lama", "cv2", "mosaic"] + methods = Literal["patchmatch", "tile", "color", "lama", "cv2"] # TODO: add mosaic back return methods @@ -145,9 +145,9 @@ class CV2InfillInvocation(InfillImageProcessorInvocation): return cv2_inpaint(image) -@invocation( - "infill_mosaic", title="Mosaic Infill", tags=["image", "inpaint", "outpaint"], category="inpaint", version="1.0.0" -) +# @invocation( +# "infill_mosaic", title="Mosaic Infill", tags=["image", "inpaint", "outpaint"], category="inpaint", version="1.0.0" +# ) class MosaicInfillInvocation(InfillImageProcessorInvocation): """Infills transparent areas of an image with a mosaic pattern drawing colors from the rest of the image""" From 90bdd74f307919edc43e12b1f3daddd30379bf5a Mon Sep 17 00:00:00 2001 From: psychedelicious <4822129+psychedelicious@users.noreply.github.com> Date: Fri, 5 Apr 2024 07:58:15 +1100 Subject: [PATCH 62/89] chore(ui): typegen --- .../frontend/web/src/services/api/schema.ts | 71 +------------------ 1 file changed, 2 insertions(+), 69 deletions(-) diff --git a/invokeai/frontend/web/src/services/api/schema.ts b/invokeai/frontend/web/src/services/api/schema.ts index c42078d414..72da0f1f8c 100644 --- a/invokeai/frontend/web/src/services/api/schema.ts +++ b/invokeai/frontend/web/src/services/api/schema.ts @@ -4112,7 +4112,7 @@ export type components = { * @description The nodes in this graph */ nodes: { - [key: string]: components["schemas"]["FaceOffInvocation"] | components["schemas"]["ImageHueAdjustmentInvocation"] | components["schemas"]["AddInvocation"] | components["schemas"]["DWOpenposeImageProcessorInvocation"] | components["schemas"]["ImageInvocation"] | components["schemas"]["FloatLinearRangeInvocation"] | components["schemas"]["TileToPropertiesInvocation"] | components["schemas"]["SegmentAnythingProcessorInvocation"] | components["schemas"]["ImageLerpInvocation"] | components["schemas"]["SDXLRefinerCompelPromptInvocation"] | components["schemas"]["RandomFloatInvocation"] | components["schemas"]["SDXLModelLoaderInvocation"] | components["schemas"]["LoRALoaderInvocation"] | components["schemas"]["StringCollectionInvocation"] | components["schemas"]["MaskFromAlphaInvocation"] | components["schemas"]["MergeTilesToImageInvocation"] | components["schemas"]["TileResamplerProcessorInvocation"] | components["schemas"]["IPAdapterInvocation"] | components["schemas"]["CV2InfillInvocation"] | components["schemas"]["ZoeDepthImageProcessorInvocation"] | components["schemas"]["IntegerMathInvocation"] | components["schemas"]["ImageBlurInvocation"] | components["schemas"]["StepParamEasingInvocation"] | components["schemas"]["MlsdImageProcessorInvocation"] | components["schemas"]["MidasDepthImageProcessorInvocation"] | components["schemas"]["RandomIntInvocation"] | components["schemas"]["MetadataInvocation"] | components["schemas"]["FloatInvocation"] | components["schemas"]["CannyImageProcessorInvocation"] | components["schemas"]["ResizeLatentsInvocation"] | components["schemas"]["MergeMetadataInvocation"] | components["schemas"]["SDXLRefinerModelLoaderInvocation"] | components["schemas"]["FaceIdentifierInvocation"] | components["schemas"]["VAELoaderInvocation"] | components["schemas"]["LeresImageProcessorInvocation"] | components["schemas"]["DivideInvocation"] | components["schemas"]["FloatToIntegerInvocation"] | components["schemas"]["ImageScaleInvocation"] | components["schemas"]["ColorInvocation"] | components["schemas"]["LatentsToImageInvocation"] | components["schemas"]["IntegerInvocation"] | components["schemas"]["StringReplaceInvocation"] | components["schemas"]["ImageToLatentsInvocation"] | components["schemas"]["BlendLatentsInvocation"] | components["schemas"]["ImageNSFWBlurInvocation"] | components["schemas"]["ConditioningInvocation"] | components["schemas"]["ImageCollectionInvocation"] | components["schemas"]["ImageChannelOffsetInvocation"] | components["schemas"]["PairTileImageInvocation"] | components["schemas"]["ImageCropInvocation"] | components["schemas"]["ESRGANInvocation"] | components["schemas"]["IdealSizeInvocation"] | components["schemas"]["CenterPadCropInvocation"] | components["schemas"]["ImagePasteInvocation"] | components["schemas"]["CalculateImageTilesInvocation"] | components["schemas"]["ImageChannelMultiplyInvocation"] | components["schemas"]["FloatMathInvocation"] | components["schemas"]["NoiseInvocation"] | components["schemas"]["ControlNetInvocation"] | components["schemas"]["MainModelLoaderInvocation"] | components["schemas"]["MaskCombineInvocation"] | components["schemas"]["ImageResizeInvocation"] | components["schemas"]["LineartImageProcessorInvocation"] | components["schemas"]["UnsharpMaskInvocation"] | components["schemas"]["MetadataItemInvocation"] | components["schemas"]["MaskFromIDInvocation"] | components["schemas"]["CoreMetadataInvocation"] | components["schemas"]["PromptsFromFileInvocation"] | components["schemas"]["LatentsInvocation"] | components["schemas"]["LatentsCollectionInvocation"] | components["schemas"]["ImageWatermarkInvocation"] | components["schemas"]["BooleanInvocation"] | components["schemas"]["SaveImageInvocation"] | components["schemas"]["PidiImageProcessorInvocation"] | components["schemas"]["DenoiseLatentsInvocation"] | components["schemas"]["InfillTileInvocation"] | components["schemas"]["CvInpaintInvocation"] | components["schemas"]["HedImageProcessorInvocation"] | components["schemas"]["ConditioningCollectionInvocation"] | components["schemas"]["StringSplitNegInvocation"] | components["schemas"]["ColorMapImageProcessorInvocation"] | components["schemas"]["MultiplyInvocation"] | components["schemas"]["CanvasPasteBackInvocation"] | components["schemas"]["T2IAdapterInvocation"] | components["schemas"]["CreateDenoiseMaskInvocation"] | components["schemas"]["StringInvocation"] | components["schemas"]["SeamlessModeInvocation"] | components["schemas"]["CompelInvocation"] | components["schemas"]["DepthAnythingImageProcessorInvocation"] | components["schemas"]["SchedulerInvocation"] | components["schemas"]["SDXLLoRALoaderInvocation"] | components["schemas"]["CollectInvocation"] | components["schemas"]["FreeUInvocation"] | components["schemas"]["StringSplitInvocation"] | components["schemas"]["ScaleLatentsInvocation"] | components["schemas"]["ImageConvertInvocation"] | components["schemas"]["MediapipeFaceProcessorInvocation"] | components["schemas"]["BlankImageInvocation"] | components["schemas"]["ShowImageInvocation"] | components["schemas"]["RoundInvocation"] | components["schemas"]["IntegerCollectionInvocation"] | components["schemas"]["NormalbaeImageProcessorInvocation"] | components["schemas"]["LineartAnimeImageProcessorInvocation"] | components["schemas"]["SubtractInvocation"] | components["schemas"]["StringJoinInvocation"] | components["schemas"]["DynamicPromptInvocation"] | components["schemas"]["MosaicInfillInvocation"] | components["schemas"]["IterateInvocation"] | components["schemas"]["LaMaInfillInvocation"] | components["schemas"]["MaskEdgeInvocation"] | components["schemas"]["RangeOfSizeInvocation"] | components["schemas"]["CreateGradientMaskInvocation"] | components["schemas"]["ImageChannelInvocation"] | components["schemas"]["RangeInvocation"] | components["schemas"]["ColorCorrectInvocation"] | components["schemas"]["ContentShuffleImageProcessorInvocation"] | components["schemas"]["RandomRangeInvocation"] | components["schemas"]["FloatCollectionInvocation"] | components["schemas"]["CLIPSkipInvocation"] | components["schemas"]["InfillPatchMatchInvocation"] | components["schemas"]["CalculateImageTilesEvenSplitInvocation"] | components["schemas"]["ImageMultiplyInvocation"] | components["schemas"]["CropLatentsCoreInvocation"] | components["schemas"]["SDXLCompelPromptInvocation"] | components["schemas"]["StringJoinThreeInvocation"] | components["schemas"]["CalculateImageTilesMinimumOverlapInvocation"] | components["schemas"]["InfillColorInvocation"] | components["schemas"]["FaceMaskInvocation"] | components["schemas"]["BooleanCollectionInvocation"] | components["schemas"]["ImageInverseLerpInvocation"]; + [key: string]: components["schemas"]["ColorInvocation"] | components["schemas"]["SubtractInvocation"] | components["schemas"]["PidiImageProcessorInvocation"] | components["schemas"]["StringSplitInvocation"] | components["schemas"]["BlankImageInvocation"] | components["schemas"]["ShowImageInvocation"] | components["schemas"]["ImageChannelMultiplyInvocation"] | components["schemas"]["LoRALoaderInvocation"] | components["schemas"]["CreateDenoiseMaskInvocation"] | components["schemas"]["SDXLCompelPromptInvocation"] | components["schemas"]["ColorMapImageProcessorInvocation"] | components["schemas"]["RandomIntInvocation"] | components["schemas"]["ConditioningInvocation"] | components["schemas"]["SeamlessModeInvocation"] | components["schemas"]["ImageCollectionInvocation"] | components["schemas"]["MidasDepthImageProcessorInvocation"] | components["schemas"]["MaskCombineInvocation"] | components["schemas"]["ImageResizeInvocation"] | components["schemas"]["ResizeLatentsInvocation"] | components["schemas"]["StringJoinInvocation"] | components["schemas"]["TileToPropertiesInvocation"] | components["schemas"]["MaskFromIDInvocation"] | components["schemas"]["UnsharpMaskInvocation"] | components["schemas"]["FaceIdentifierInvocation"] | components["schemas"]["SDXLLoRALoaderInvocation"] | components["schemas"]["FloatToIntegerInvocation"] | components["schemas"]["FreeUInvocation"] | components["schemas"]["ImageWatermarkInvocation"] | components["schemas"]["MetadataItemInvocation"] | components["schemas"]["SaveImageInvocation"] | components["schemas"]["DivideInvocation"] | components["schemas"]["DepthAnythingImageProcessorInvocation"] | components["schemas"]["IntegerCollectionInvocation"] | components["schemas"]["InfillColorInvocation"] | components["schemas"]["SchedulerInvocation"] | components["schemas"]["CanvasPasteBackInvocation"] | components["schemas"]["IdealSizeInvocation"] | components["schemas"]["TileResamplerProcessorInvocation"] | components["schemas"]["NoiseInvocation"] | components["schemas"]["LatentsInvocation"] | components["schemas"]["LatentsCollectionInvocation"] | components["schemas"]["ControlNetInvocation"] | components["schemas"]["MlsdImageProcessorInvocation"] | components["schemas"]["StringJoinThreeInvocation"] | components["schemas"]["FloatMathInvocation"] | components["schemas"]["AddInvocation"] | components["schemas"]["CalculateImageTilesMinimumOverlapInvocation"] | components["schemas"]["BooleanCollectionInvocation"] | components["schemas"]["InfillTileInvocation"] | components["schemas"]["IterateInvocation"] | components["schemas"]["ConditioningCollectionInvocation"] | components["schemas"]["CalculateImageTilesInvocation"] | components["schemas"]["HedImageProcessorInvocation"] | components["schemas"]["LaMaInfillInvocation"] | components["schemas"]["ScaleLatentsInvocation"] | components["schemas"]["ImageConvertInvocation"] | components["schemas"]["StringInvocation"] | components["schemas"]["DWOpenposeImageProcessorInvocation"] | components["schemas"]["T2IAdapterInvocation"] | components["schemas"]["MediapipeFaceProcessorInvocation"] | components["schemas"]["CvInpaintInvocation"] | components["schemas"]["NormalbaeImageProcessorInvocation"] | components["schemas"]["ImageChannelInvocation"] | components["schemas"]["MaskEdgeInvocation"] | components["schemas"]["MultiplyInvocation"] | components["schemas"]["CollectInvocation"] | components["schemas"]["MergeTilesToImageInvocation"] | components["schemas"]["ColorCorrectInvocation"] | components["schemas"]["ImageMultiplyInvocation"] | components["schemas"]["MetadataInvocation"] | components["schemas"]["SegmentAnythingProcessorInvocation"] | components["schemas"]["ZoeDepthImageProcessorInvocation"] | components["schemas"]["FloatCollectionInvocation"] | components["schemas"]["CLIPSkipInvocation"] | components["schemas"]["MergeMetadataInvocation"] | components["schemas"]["ImageInverseLerpInvocation"] | components["schemas"]["DenoiseLatentsInvocation"] | components["schemas"]["RoundInvocation"] | components["schemas"]["PairTileImageInvocation"] | components["schemas"]["PromptsFromFileInvocation"] | components["schemas"]["CropLatentsCoreInvocation"] | components["schemas"]["SDXLRefinerCompelPromptInvocation"] | components["schemas"]["StringReplaceInvocation"] | components["schemas"]["ImageHueAdjustmentInvocation"] | components["schemas"]["RangeInvocation"] | components["schemas"]["InfillPatchMatchInvocation"] | components["schemas"]["LatentsToImageInvocation"] | components["schemas"]["ImageLerpInvocation"] | components["schemas"]["VAELoaderInvocation"] | components["schemas"]["RangeOfSizeInvocation"] | components["schemas"]["MaskFromAlphaInvocation"] | components["schemas"]["IntegerInvocation"] | components["schemas"]["CV2InfillInvocation"] | components["schemas"]["SDXLRefinerModelLoaderInvocation"] | components["schemas"]["ImageInvocation"] | components["schemas"]["DynamicPromptInvocation"] | components["schemas"]["ContentShuffleImageProcessorInvocation"] | components["schemas"]["LineartAnimeImageProcessorInvocation"] | components["schemas"]["IPAdapterInvocation"] | components["schemas"]["CalculateImageTilesEvenSplitInvocation"] | components["schemas"]["ImageBlurInvocation"] | components["schemas"]["LineartImageProcessorInvocation"] | components["schemas"]["CompelInvocation"] | components["schemas"]["FaceMaskInvocation"] | components["schemas"]["CreateGradientMaskInvocation"] | components["schemas"]["StringCollectionInvocation"] | components["schemas"]["FloatLinearRangeInvocation"] | components["schemas"]["CoreMetadataInvocation"] | components["schemas"]["StepParamEasingInvocation"] | components["schemas"]["FaceOffInvocation"] | components["schemas"]["RandomRangeInvocation"] | components["schemas"]["ESRGANInvocation"] | components["schemas"]["StringSplitNegInvocation"] | components["schemas"]["BooleanInvocation"] | components["schemas"]["LeresImageProcessorInvocation"] | components["schemas"]["SDXLModelLoaderInvocation"] | components["schemas"]["ImageScaleInvocation"] | components["schemas"]["RandomFloatInvocation"] | components["schemas"]["IntegerMathInvocation"] | components["schemas"]["ImageToLatentsInvocation"] | components["schemas"]["BlendLatentsInvocation"] | components["schemas"]["MainModelLoaderInvocation"] | components["schemas"]["ImageChannelOffsetInvocation"] | components["schemas"]["ImageNSFWBlurInvocation"] | components["schemas"]["FloatInvocation"] | components["schemas"]["ImageCropInvocation"] | components["schemas"]["CenterPadCropInvocation"] | components["schemas"]["CannyImageProcessorInvocation"] | components["schemas"]["ImagePasteInvocation"]; }; /** * Edges @@ -4149,7 +4149,7 @@ export type components = { * @description The results of node executions */ results: { - [key: string]: components["schemas"]["ControlOutput"] | components["schemas"]["CLIPOutput"] | components["schemas"]["CollectInvocationOutput"] | components["schemas"]["NoiseOutput"] | components["schemas"]["TileToPropertiesOutput"] | components["schemas"]["FloatOutput"] | components["schemas"]["GradientMaskOutput"] | components["schemas"]["ConditioningOutput"] | components["schemas"]["FaceOffOutput"] | components["schemas"]["String2Output"] | components["schemas"]["StringCollectionOutput"] | components["schemas"]["DenoiseMaskOutput"] | components["schemas"]["FloatCollectionOutput"] | components["schemas"]["SDXLLoRALoaderOutput"] | components["schemas"]["StringOutput"] | components["schemas"]["StringPosNegOutput"] | components["schemas"]["LatentsCollectionOutput"] | components["schemas"]["LoRALoaderOutput"] | components["schemas"]["CalculateImageTilesOutput"] | components["schemas"]["MetadataItemOutput"] | components["schemas"]["ConditioningCollectionOutput"] | components["schemas"]["IntegerOutput"] | components["schemas"]["FaceMaskOutput"] | components["schemas"]["MetadataOutput"] | components["schemas"]["BooleanOutput"] | components["schemas"]["ColorCollectionOutput"] | components["schemas"]["IPAdapterOutput"] | components["schemas"]["BooleanCollectionOutput"] | components["schemas"]["SeamlessModeOutput"] | components["schemas"]["PairTileImageOutput"] | components["schemas"]["IdealSizeOutput"] | components["schemas"]["SchedulerOutput"] | components["schemas"]["LatentsOutput"] | components["schemas"]["SDXLModelLoaderOutput"] | components["schemas"]["SDXLRefinerModelLoaderOutput"] | components["schemas"]["ModelLoaderOutput"] | components["schemas"]["IterateInvocationOutput"] | components["schemas"]["IntegerCollectionOutput"] | components["schemas"]["CLIPSkipInvocationOutput"] | components["schemas"]["T2IAdapterOutput"] | components["schemas"]["ImageCollectionOutput"] | components["schemas"]["UNetOutput"] | components["schemas"]["ImageOutput"] | components["schemas"]["ColorOutput"] | components["schemas"]["VAEOutput"]; + [key: string]: components["schemas"]["LatentsCollectionOutput"] | components["schemas"]["GradientMaskOutput"] | components["schemas"]["SDXLModelLoaderOutput"] | components["schemas"]["ConditioningCollectionOutput"] | components["schemas"]["CLIPOutput"] | components["schemas"]["SchedulerOutput"] | components["schemas"]["VAEOutput"] | components["schemas"]["FaceMaskOutput"] | components["schemas"]["BooleanOutput"] | components["schemas"]["IntegerCollectionOutput"] | components["schemas"]["SeamlessModeOutput"] | components["schemas"]["PairTileImageOutput"] | components["schemas"]["LoRALoaderOutput"] | components["schemas"]["MetadataOutput"] | components["schemas"]["IPAdapterOutput"] | components["schemas"]["NoiseOutput"] | components["schemas"]["IdealSizeOutput"] | components["schemas"]["FloatOutput"] | components["schemas"]["StringPosNegOutput"] | components["schemas"]["LatentsOutput"] | components["schemas"]["BooleanCollectionOutput"] | components["schemas"]["TileToPropertiesOutput"] | components["schemas"]["CalculateImageTilesOutput"] | components["schemas"]["FloatCollectionOutput"] | components["schemas"]["IntegerOutput"] | components["schemas"]["CLIPSkipInvocationOutput"] | components["schemas"]["FaceOffOutput"] | components["schemas"]["ImageCollectionOutput"] | components["schemas"]["UNetOutput"] | components["schemas"]["T2IAdapterOutput"] | components["schemas"]["ImageOutput"] | components["schemas"]["ColorOutput"] | components["schemas"]["ColorCollectionOutput"] | components["schemas"]["SDXLRefinerModelLoaderOutput"] | components["schemas"]["StringCollectionOutput"] | components["schemas"]["SDXLLoRALoaderOutput"] | components["schemas"]["ConditioningOutput"] | components["schemas"]["ModelLoaderOutput"] | components["schemas"]["DenoiseMaskOutput"] | components["schemas"]["MetadataItemOutput"] | components["schemas"]["IterateInvocationOutput"] | components["schemas"]["CollectInvocationOutput"] | components["schemas"]["ControlOutput"] | components["schemas"]["String2Output"] | components["schemas"]["StringOutput"]; }; /** * Errors @@ -7702,73 +7702,6 @@ export type components = { /** Models */ models: (components["schemas"]["MainDiffusersConfig"] | components["schemas"]["MainCheckpointConfig"] | components["schemas"]["VAEDiffusersConfig"] | components["schemas"]["VAECheckpointConfig"] | components["schemas"]["ControlNetDiffusersConfig"] | components["schemas"]["ControlNetCheckpointConfig"] | components["schemas"]["LoRALyCORISConfig"] | components["schemas"]["LoRADiffusersConfig"] | components["schemas"]["TextualInversionFileConfig"] | components["schemas"]["TextualInversionFolderConfig"] | components["schemas"]["IPAdapterInvokeAIConfig"] | components["schemas"]["IPAdapterCheckpointConfig"] | components["schemas"]["T2IAdapterConfig"] | components["schemas"]["CLIPVisionDiffusersConfig"])[]; }; - /** - * Mosaic Infill - * @description Infills transparent areas of an image with a mosaic pattern drawing colors from the rest of the image - */ - MosaicInfillInvocation: { - /** @description The board to save the image to */ - board?: components["schemas"]["BoardField"] | null; - /** @description Optional metadata to be saved with the image */ - metadata?: components["schemas"]["MetadataField"] | null; - /** - * Id - * @description The id of this instance of an invocation. Must be unique among all instances of invocations. - */ - id: string; - /** - * Is Intermediate - * @description Whether or not this is an intermediate invocation. - * @default false - */ - is_intermediate?: boolean; - /** - * Use Cache - * @description Whether or not to use the cache - * @default true - */ - use_cache?: boolean; - /** @description The image to infill */ - image?: components["schemas"]["ImageField"]; - /** - * Tile Width - * @description Width of the tile - * @default 64 - */ - tile_width?: number; - /** - * Tile Height - * @description Height of the tile - * @default 64 - */ - tile_height?: number; - /** - * @description The min threshold for color - * @default { - * "r": 0, - * "g": 0, - * "b": 0, - * "a": 255 - * } - */ - min_color?: components["schemas"]["ColorField"]; - /** - * @description The max threshold for color - * @default { - * "r": 255, - * "g": 255, - * "b": 255, - * "a": 255 - * } - */ - max_color?: components["schemas"]["ColorField"]; - /** - * type - * @default infill_mosaic - * @constant - */ - type: "infill_mosaic"; - }; /** * Multiply Integers * @description Multiplies two numbers From 5d4a57177819c7ea384e04c2d8b4b40194bdb029 Mon Sep 17 00:00:00 2001 From: psychedelicious <4822129+psychedelicious@users.noreply.github.com> Date: Fri, 5 Apr 2024 07:58:34 +1100 Subject: [PATCH 63/89] feat(ui): disable mosaic infill in graph builders --- .../util/graph/buildCanvasOutpaintGraph.ts | 31 ++++++++++--------- .../graph/buildCanvasSDXLOutpaintGraph.ts | 31 ++++++++++--------- 2 files changed, 32 insertions(+), 30 deletions(-) diff --git a/invokeai/frontend/web/src/features/nodes/util/graph/buildCanvasOutpaintGraph.ts b/invokeai/frontend/web/src/features/nodes/util/graph/buildCanvasOutpaintGraph.ts index 30dd44aa18..6a59c51872 100644 --- a/invokeai/frontend/web/src/features/nodes/util/graph/buildCanvasOutpaintGraph.ts +++ b/invokeai/frontend/web/src/features/nodes/util/graph/buildCanvasOutpaintGraph.ts @@ -65,10 +65,10 @@ export const buildCanvasOutpaintGraph = async ( infillTileSize, infillPatchmatchDownscaleSize, infillMethod, - infillMosaicTileWidth, - infillMosaicTileHeight, - infillMosaicMinColor, - infillMosaicMaxColor, + // infillMosaicTileWidth, + // infillMosaicTileHeight, + // infillMosaicMinColor, + // infillMosaicMaxColor, infillColorValue, clipSkip, seamlessXAxis, @@ -361,17 +361,18 @@ export const buildCanvasOutpaintGraph = async ( }; } - if (infillMethod === 'mosaic') { - graph.nodes[INPAINT_INFILL] = { - type: 'infill_mosaic', - id: INPAINT_INFILL, - is_intermediate, - tile_width: infillMosaicTileWidth, - tile_height: infillMosaicTileHeight, - min_color: infillMosaicMinColor, - max_color: infillMosaicMaxColor, - }; - } + // TODO: add mosaic back + // if (infillMethod === 'mosaic') { + // graph.nodes[INPAINT_INFILL] = { + // type: 'infill_mosaic', + // id: INPAINT_INFILL, + // is_intermediate, + // tile_width: infillMosaicTileWidth, + // tile_height: infillMosaicTileHeight, + // min_color: infillMosaicMinColor, + // max_color: infillMosaicMaxColor, + // }; + // } if (infillMethod === 'color') { graph.nodes[INPAINT_INFILL] = { diff --git a/invokeai/frontend/web/src/features/nodes/util/graph/buildCanvasSDXLOutpaintGraph.ts b/invokeai/frontend/web/src/features/nodes/util/graph/buildCanvasSDXLOutpaintGraph.ts index 4ec00a2cd6..b932b26660 100644 --- a/invokeai/frontend/web/src/features/nodes/util/graph/buildCanvasSDXLOutpaintGraph.ts +++ b/invokeai/frontend/web/src/features/nodes/util/graph/buildCanvasSDXLOutpaintGraph.ts @@ -66,10 +66,10 @@ export const buildCanvasSDXLOutpaintGraph = async ( infillTileSize, infillPatchmatchDownscaleSize, infillMethod, - infillMosaicTileWidth, - infillMosaicTileHeight, - infillMosaicMinColor, - infillMosaicMaxColor, + // infillMosaicTileWidth, + // infillMosaicTileHeight, + // infillMosaicMinColor, + // infillMosaicMaxColor, infillColorValue, seamlessXAxis, seamlessYAxis, @@ -370,17 +370,18 @@ export const buildCanvasSDXLOutpaintGraph = async ( }; } - if (infillMethod === 'mosaic') { - graph.nodes[INPAINT_INFILL] = { - type: 'infill_mosaic', - id: INPAINT_INFILL, - is_intermediate, - tile_width: infillMosaicTileWidth, - tile_height: infillMosaicTileHeight, - min_color: infillMosaicMinColor, - max_color: infillMosaicMaxColor, - }; - } + // TODO: add mosaic back + // if (infillMethod === 'mosaic') { + // graph.nodes[INPAINT_INFILL] = { + // type: 'infill_mosaic', + // id: INPAINT_INFILL, + // is_intermediate, + // tile_width: infillMosaicTileWidth, + // tile_height: infillMosaicTileHeight, + // min_color: infillMosaicMinColor, + // max_color: infillMosaicMaxColor, + // }; + // } if (infillMethod === 'color') { graph.nodes[INPAINT_INFILL] = { From 3006285d1358bbb97325b7d76ffdd332484263c6 Mon Sep 17 00:00:00 2001 From: psychedelicious <4822129+psychedelicious@users.noreply.github.com> Date: Fri, 5 Apr 2024 09:21:13 +1100 Subject: [PATCH 64/89] fix(ui): display refiner models in mm --- invokeai/frontend/web/public/locales/en.json | 1 + .../store/modelManagerV2Slice.ts | 2 +- .../subpanels/ModelManagerPanel/ModelList.tsx | 60 +++++++++++++++++-- .../ModelManagerPanel/ModelTypeFilter.tsx | 1 + 4 files changed, 59 insertions(+), 5 deletions(-) diff --git a/invokeai/frontend/web/public/locales/en.json b/invokeai/frontend/web/public/locales/en.json index 96fb8e5748..0cf98289e4 100644 --- a/invokeai/frontend/web/public/locales/en.json +++ b/invokeai/frontend/web/public/locales/en.json @@ -684,6 +684,7 @@ "noModelsInstalled": "No Models Installed", "noModelsInstalledDesc1": "Install models with the", "noModelSelected": "No Model Selected", + "noMatchingModels": "No matching Models", "none": "none", "path": "Path", "pathToConfig": "Path To Config", diff --git a/invokeai/frontend/web/src/features/modelManagerV2/store/modelManagerV2Slice.ts b/invokeai/frontend/web/src/features/modelManagerV2/store/modelManagerV2Slice.ts index 6bdd829bb1..c637d30fd8 100644 --- a/invokeai/frontend/web/src/features/modelManagerV2/store/modelManagerV2Slice.ts +++ b/invokeai/frontend/web/src/features/modelManagerV2/store/modelManagerV2Slice.ts @@ -3,7 +3,7 @@ import { createSlice } from '@reduxjs/toolkit'; import type { PersistConfig } from 'app/store/store'; import type { ModelType } from 'services/api/types'; -export type FilterableModelType = Exclude; +export type FilterableModelType = Exclude | 'refiner'; type ModelManagerState = { _version: 1; diff --git a/invokeai/frontend/web/src/features/modelManagerV2/subpanels/ModelManagerPanel/ModelList.tsx b/invokeai/frontend/web/src/features/modelManagerV2/subpanels/ModelManagerPanel/ModelList.tsx index 033841ec79..67e65dbfb6 100644 --- a/invokeai/frontend/web/src/features/modelManagerV2/subpanels/ModelManagerPanel/ModelList.tsx +++ b/invokeai/frontend/web/src/features/modelManagerV2/subpanels/ModelManagerPanel/ModelList.tsx @@ -1,6 +1,7 @@ -import { Flex } from '@invoke-ai/ui-library'; +import { Flex, Text } from '@invoke-ai/ui-library'; import { useAppSelector } from 'app/store/storeHooks'; import ScrollableContent from 'common/components/OverlayScrollbars/ScrollableContent'; +import type { FilterableModelType } from 'features/modelManagerV2/store/modelManagerV2Slice'; import { memo, useMemo } from 'react'; import { useTranslation } from 'react-i18next'; import { @@ -9,10 +10,11 @@ import { useIPAdapterModels, useLoRAModels, useMainModels, + useRefinerModels, useT2IAdapterModels, useVAEModels, } from 'services/api/hooks/modelsByType'; -import type { AnyModelConfig, ModelType } from 'services/api/types'; +import type { AnyModelConfig } from 'services/api/types'; import { FetchingModelsLoader } from './FetchingModelsLoader'; import { ModelListWrapper } from './ModelListWrapper'; @@ -27,6 +29,12 @@ const ModelList = () => { [mainModels, searchTerm, filteredModelType] ); + const [refinerModels, { isLoading: isLoadingRefinerModels }] = useRefinerModels(); + const filteredRefinerModels = useMemo( + () => modelsFilter(refinerModels, searchTerm, filteredModelType), + [refinerModels, searchTerm, filteredModelType] + ); + const [loraModels, { isLoading: isLoadingLoRAModels }] = useLoRAModels(); const filteredLoRAModels = useMemo( () => modelsFilter(loraModels, searchTerm, filteredModelType), @@ -63,6 +71,28 @@ const ModelList = () => { [vaeModels, searchTerm, filteredModelType] ); + const totalFilteredModels = useMemo(() => { + return ( + filteredMainModels.length + + filteredRefinerModels.length + + filteredLoRAModels.length + + filteredEmbeddingModels.length + + filteredControlNetModels.length + + filteredT2IAdapterModels.length + + filteredIPAdapterModels.length + + filteredVAEModels.length + ); + }, [ + filteredControlNetModels.length, + filteredEmbeddingModels.length, + filteredIPAdapterModels.length, + filteredLoRAModels.length, + filteredMainModels.length, + filteredRefinerModels.length, + filteredT2IAdapterModels.length, + filteredVAEModels.length, + ]); + return ( @@ -71,6 +101,11 @@ const ModelList = () => { {!isLoadingMainModels && filteredMainModels.length > 0 && ( )} + {/* Refiner Model List */} + {isLoadingRefinerModels && } + {!isLoadingRefinerModels && filteredRefinerModels.length > 0 && ( + + )} {/* LoRAs List */} {isLoadingLoRAModels && } {!isLoadingLoRAModels && filteredLoRAModels.length > 0 && ( @@ -108,6 +143,11 @@ const ModelList = () => { {!isLoadingT2IAdapterModels && filteredT2IAdapterModels.length > 0 && ( )} + {totalFilteredModels === 0 && ( + + {t('modelManager.noMatchingModels')} + + )} ); @@ -118,12 +158,24 @@ export default memo(ModelList); const modelsFilter = ( data: T[], nameFilter: string, - filteredModelType: ModelType | null + filteredModelType: FilterableModelType | null ): T[] => { return data.filter((model) => { const matchesFilter = model.name.toLowerCase().includes(nameFilter.toLowerCase()); - const matchesType = filteredModelType ? model.type === filteredModelType : true; + const matchesType = getMatchesType(model, filteredModelType); return matchesFilter && matchesType; }); }; + +const getMatchesType = (modelConfig: AnyModelConfig, filteredModelType: FilterableModelType | null): boolean => { + if (filteredModelType === 'refiner') { + return modelConfig.base === 'sdxl-refiner'; + } + + if (filteredModelType === 'main' && modelConfig.base === 'sdxl-refiner') { + return false; + } + + return filteredModelType ? modelConfig.type === filteredModelType : true; +}; diff --git a/invokeai/frontend/web/src/features/modelManagerV2/subpanels/ModelManagerPanel/ModelTypeFilter.tsx b/invokeai/frontend/web/src/features/modelManagerV2/subpanels/ModelManagerPanel/ModelTypeFilter.tsx index 0b8ad3f600..76802b36e7 100644 --- a/invokeai/frontend/web/src/features/modelManagerV2/subpanels/ModelManagerPanel/ModelTypeFilter.tsx +++ b/invokeai/frontend/web/src/features/modelManagerV2/subpanels/ModelManagerPanel/ModelTypeFilter.tsx @@ -13,6 +13,7 @@ export const ModelTypeFilter = () => { const MODEL_TYPE_LABELS: Record = useMemo( () => ({ main: t('modelManager.main'), + refiner: t('sdxl.refiner'), lora: 'LoRA', embedding: t('modelManager.textualInversions'), controlnet: 'ControlNet', From 812f10730f4afb2a748d017a023da0b7ff8fde23 Mon Sep 17 00:00:00 2001 From: Lincoln Stein Date: Thu, 4 Apr 2024 22:51:12 -0400 Subject: [PATCH 65/89] adjust free vram calculation for models that will be removed by lazy offloading (#6150) Co-authored-by: Lincoln Stein --- .../model_manager/model_manager_default.py | 1 + .../load/model_cache/model_cache_default.py | 15 +++++++++++---- .../load/model_cache/model_locker.py | 4 ++-- 3 files changed, 14 insertions(+), 6 deletions(-) diff --git a/invokeai/app/services/model_manager/model_manager_default.py b/invokeai/app/services/model_manager/model_manager_default.py index b160ff6fed..de6e5f09d8 100644 --- a/invokeai/app/services/model_manager/model_manager_default.py +++ b/invokeai/app/services/model_manager/model_manager_default.py @@ -80,6 +80,7 @@ class ModelManagerService(ModelManagerServiceBase): ram_cache = ModelCache( max_cache_size=app_config.ram, max_vram_cache_size=app_config.vram, + lazy_offloading=app_config.lazy_offload, logger=logger, execution_device=execution_device, ) diff --git a/invokeai/backend/model_manager/load/model_cache/model_cache_default.py b/invokeai/backend/model_manager/load/model_cache/model_cache_default.py index b8312f619a..4d5d09864e 100644 --- a/invokeai/backend/model_manager/load/model_cache/model_cache_default.py +++ b/invokeai/backend/model_manager/load/model_cache/model_cache_default.py @@ -421,13 +421,20 @@ class ModelCache(ModelCacheBase[AnyModel]): self.logger.debug(f"After making room: cached_models={len(self._cached_models)}") + def _free_vram(self, device: torch.device) -> int: + vram_device = ( # mem_get_info() needs an indexed device + device if device.index is not None else torch.device(str(device), index=0) + ) + free_mem, _ = torch.cuda.mem_get_info(vram_device) + for _, cache_entry in self._cached_models.items(): + if cache_entry.loaded and not cache_entry.locked: + free_mem += cache_entry.size + return free_mem + def _check_free_vram(self, target_device: torch.device, needed_size: int) -> None: if target_device.type != "cuda": return - vram_device = ( # mem_get_info() needs an indexed device - target_device if target_device.index is not None else torch.device(str(target_device), index=0) - ) - free_mem, _ = torch.cuda.mem_get_info(torch.device(vram_device)) + free_mem = self._free_vram(target_device) if needed_size > free_mem: needed_gb = round(needed_size / GIG, 2) free_gb = round(free_mem / GIG, 2) diff --git a/invokeai/backend/model_manager/load/model_cache/model_locker.py b/invokeai/backend/model_manager/load/model_cache/model_locker.py index 81dca346e5..ea38d3773c 100644 --- a/invokeai/backend/model_manager/load/model_cache/model_locker.py +++ b/invokeai/backend/model_manager/load/model_cache/model_locker.py @@ -33,14 +33,13 @@ class ModelLocker(ModelLockerBase): return self.model # NOTE that the model has to have the to() method in order for this code to move it into GPU! - self._cache_entry.lock() - try: if self._cache.lazy_offloading: self._cache.offload_unlocked_models(self._cache_entry.size) self._cache.move_model_to_device(self._cache_entry, self._cache.execution_device) self._cache_entry.loaded = True + self._cache_entry.lock() self._cache.logger.debug(f"Locking {self._cache_entry.key} in {self._cache.execution_device}") self._cache.print_cuda_stats() @@ -51,6 +50,7 @@ class ModelLocker(ModelLockerBase): except Exception: self._cache_entry.unlock() raise + return self.model def unlock(self) -> None: From 577469be557bdcb49f252579997211b3495391bc Mon Sep 17 00:00:00 2001 From: Alexander Eichhorn Date: Thu, 4 Apr 2024 20:01:51 +0200 Subject: [PATCH 66/89] translationBot(ui): update translation (German) Currently translated at 73.3% (826 of 1126 strings) Co-authored-by: Alexander Eichhorn Translate-URL: https://hosted.weblate.org/projects/invokeai/web-ui/de/ Translation: InvokeAI/Web UI --- invokeai/frontend/web/public/locales/de.json | 23 +++++++++++++++----- 1 file changed, 18 insertions(+), 5 deletions(-) diff --git a/invokeai/frontend/web/public/locales/de.json b/invokeai/frontend/web/public/locales/de.json index 48a8c5127e..4f18cd0050 100644 --- a/invokeai/frontend/web/public/locales/de.json +++ b/invokeai/frontend/web/public/locales/de.json @@ -75,7 +75,8 @@ "copy": "Kopieren", "aboutHeading": "Nutzen Sie Ihre kreative Energie", "toResolve": "Lösen", - "add": "Hinzufügen" + "add": "Hinzufügen", + "loglevel": "Protokoll Stufe" }, "gallery": { "galleryImageSize": "Bildgröße", @@ -388,7 +389,14 @@ "vaePrecision": "VAE-Präzision", "variant": "Variante", "modelDeleteFailed": "Modell konnte nicht gelöscht werden", - "noModelSelected": "Kein Modell ausgewählt" + "noModelSelected": "Kein Modell ausgewählt", + "huggingFace": "HuggingFace", + "defaultSettings": "Standardeinstellungen", + "edit": "Bearbeiten", + "cancel": "Stornieren", + "defaultSettingsSaved": "Standardeinstellungen gespeichert", + "addModels": "Model hinzufügen", + "deleteModelImage": "Lösche Model Bild" }, "parameters": { "images": "Bilder", @@ -677,7 +685,8 @@ "body": "Körper", "hands": "Hände", "dwOpenpose": "DW Openpose", - "dwOpenposeDescription": "Posenschätzung mit DW Openpose" + "dwOpenposeDescription": "Posenschätzung mit DW Openpose", + "selectCLIPVisionModel": "Wähle ein CLIP Vision Model aus" }, "queue": { "status": "Status", @@ -765,7 +774,10 @@ "recallParameters": "Parameter wiederherstellen", "cfgRescaleMultiplier": "$t(parameters.cfgRescaleMultiplier)", "allPrompts": "Alle Prompts", - "imageDimensions": "Bilder Auslösungen" + "imageDimensions": "Bilder Auslösungen", + "parameterSet": "Parameter {{parameter}} setzen", + "recallParameter": "{{label}} Abrufen", + "parsingFailed": "Parsing Fehlgeschlagen" }, "popovers": { "noiseUseCPU": { @@ -1030,7 +1042,8 @@ "title": "Bild" }, "advanced": { - "title": "Erweitert" + "title": "Erweitert", + "options": "$t(accordions.advanced.title) Optionen" }, "control": { "title": "Kontrolle" From b5c048d8bfb4a543055d82d8bb305e55a2e143d6 Mon Sep 17 00:00:00 2001 From: Riccardo Giovanetti Date: Thu, 4 Apr 2024 20:01:51 +0200 Subject: [PATCH 67/89] translationBot(ui): update translation (Italian) Currently translated at 98.4% (1108 of 1126 strings) Co-authored-by: Riccardo Giovanetti Translate-URL: https://hosted.weblate.org/projects/invokeai/web-ui/it/ Translation: InvokeAI/Web UI --- invokeai/frontend/web/public/locales/it.json | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/invokeai/frontend/web/public/locales/it.json b/invokeai/frontend/web/public/locales/it.json index a9eb75c0f5..2b211484aa 100644 --- a/invokeai/frontend/web/public/locales/it.json +++ b/invokeai/frontend/web/public/locales/it.json @@ -366,7 +366,7 @@ "modelConverted": "Modello convertito", "alpha": "Alpha", "convertToDiffusersHelpText1": "Questo modello verrà convertito nel formato 🧨 Diffusori.", - "convertToDiffusersHelpText3": "Il file Checkpoint su disco verrà eliminato se si trova nella cartella principale di InvokeAI. Se si trova invece in una posizione personalizzata, NON verrà eliminato.", + "convertToDiffusersHelpText3": "Il file del modello su disco verrà eliminato se si trova nella cartella principale di InvokeAI. Se si trova invece in una posizione personalizzata, NON verrà eliminato.", "v2_base": "v2 (512px)", "v2_768": "v2 (768px)", "none": "nessuno", @@ -443,7 +443,8 @@ "noModelsInstalled": "Nessun modello installato", "hfTokenInvalidErrorMessage2": "Aggiornalo in ", "main": "Principali", - "noModelsInstalledDesc1": "Installa i modelli con" + "noModelsInstalledDesc1": "Installa i modelli con", + "ipAdapters": "Adattatori IP" }, "parameters": { "images": "Immagini", @@ -937,7 +938,8 @@ "controlnet": "$t(controlnet.controlAdapter_one) #{{number}} ($t(common.controlNet))", "mediapipeFace": "Mediapipe Volto", "ip_adapter": "$t(controlnet.controlAdapter_one) #{{number}} ($t(common.ipAdapter))", - "t2i_adapter": "$t(controlnet.controlAdapter_one) #{{number}} ($t(common.t2iAdapter))" + "t2i_adapter": "$t(controlnet.controlAdapter_one) #{{number}} ($t(common.t2iAdapter))", + "selectCLIPVisionModel": "Seleziona un modello CLIP Vision" }, "queue": { "queueFront": "Aggiungi all'inizio della coda", From fec989f015798b7e087d3571a9eac49b33cabee9 Mon Sep 17 00:00:00 2001 From: Jennifer Player Date: Thu, 4 Apr 2024 21:49:03 -0400 Subject: [PATCH 68/89] navigate to workflow tab when clicking load workflow --- invokeai/frontend/web/src/features/ui/store/uiSlice.ts | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/invokeai/frontend/web/src/features/ui/store/uiSlice.ts b/invokeai/frontend/web/src/features/ui/store/uiSlice.ts index f9a4b94805..69c8eaf7cf 100644 --- a/invokeai/frontend/web/src/features/ui/store/uiSlice.ts +++ b/invokeai/frontend/web/src/features/ui/store/uiSlice.ts @@ -1,6 +1,7 @@ import type { PayloadAction } from '@reduxjs/toolkit'; import { createSlice } from '@reduxjs/toolkit'; import type { PersistConfig, RootState } from 'app/store/store'; +import { workflowLoadRequested } from 'features/nodes/store/actions'; import { initialImageChanged } from 'features/parameters/store/generationSlice'; import type { InvokeTabName } from './tabMap'; @@ -45,6 +46,9 @@ export const uiSlice = createSlice({ builder.addCase(initialImageChanged, (state) => { state.activeTab = 'img2img'; }); + builder.addCase(workflowLoadRequested, (state) => { + state.activeTab = 'nodes'; + }); }, }); From 4571986c635fcd3ec1f0a1191e5b88e3835891e4 Mon Sep 17 00:00:00 2001 From: Lincoln Stein Date: Thu, 4 Apr 2024 23:12:49 -0400 Subject: [PATCH 69/89] fix misplaced lock call --- invokeai/backend/model_manager/load/model_cache/model_locker.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/invokeai/backend/model_manager/load/model_cache/model_locker.py b/invokeai/backend/model_manager/load/model_cache/model_locker.py index ea38d3773c..a275987773 100644 --- a/invokeai/backend/model_manager/load/model_cache/model_locker.py +++ b/invokeai/backend/model_manager/load/model_cache/model_locker.py @@ -33,13 +33,13 @@ class ModelLocker(ModelLockerBase): return self.model # NOTE that the model has to have the to() method in order for this code to move it into GPU! + self._cache_entry.lock() try: if self._cache.lazy_offloading: self._cache.offload_unlocked_models(self._cache_entry.size) self._cache.move_model_to_device(self._cache_entry, self._cache.execution_device) self._cache_entry.loaded = True - self._cache_entry.lock() self._cache.logger.debug(f"Locking {self._cache_entry.key} in {self._cache.execution_device}") self._cache.print_cuda_stats() From 90686c7f9c2e48398da9548957e84693b59692f3 Mon Sep 17 00:00:00 2001 From: symant233 Date: Thu, 4 Apr 2024 18:41:22 +0800 Subject: [PATCH 70/89] feat: Unified Canvas Fit Image Size on Drop --- invokeai/frontend/web/public/locales/en.json | 1 + invokeai/frontend/web/public/locales/zh_CN.json | 3 ++- .../IAICanvasSettingsButtonPopover.tsx | 10 ++++++++++ .../src/features/canvas/store/canvasSlice.ts | 17 ++++++++++++----- .../src/features/canvas/store/canvasTypes.ts | 1 + 5 files changed, 26 insertions(+), 6 deletions(-) diff --git a/invokeai/frontend/web/public/locales/en.json b/invokeai/frontend/web/public/locales/en.json index 0cf98289e4..623cea64ee 100644 --- a/invokeai/frontend/web/public/locales/en.json +++ b/invokeai/frontend/web/public/locales/en.json @@ -1423,6 +1423,7 @@ "eraseBoundingBox": "Erase Bounding Box", "eraser": "Eraser", "fillBoundingBox": "Fill Bounding Box", + "initialFitImageSize": "Fit Image Size on Drop", "invertBrushSizeScrollDirection": "Invert Scroll for Brush Size", "layer": "Layer", "limitStrokesToBox": "Limit Strokes to Box", diff --git a/invokeai/frontend/web/public/locales/zh_CN.json b/invokeai/frontend/web/public/locales/zh_CN.json index 77a06ea77b..e40b04ed10 100644 --- a/invokeai/frontend/web/public/locales/zh_CN.json +++ b/invokeai/frontend/web/public/locales/zh_CN.json @@ -583,7 +583,8 @@ "next": "下一张", "accept": "接受", "discardAll": "放弃所有", - "antialiasing": "抗锯齿", + "antialiasing": "抗锯齿", + "initialFitImageSize": "初始适应图片大小", "showResultsOn": "显示结果 (开)", "showResultsOff": "显示结果 (关)", "saveMask": "保存 $t(unifiedCanvas.mask)" diff --git a/invokeai/frontend/web/src/features/canvas/components/IAICanvasToolbar/IAICanvasSettingsButtonPopover.tsx b/invokeai/frontend/web/src/features/canvas/components/IAICanvasToolbar/IAICanvasSettingsButtonPopover.tsx index 0228b158dd..80be585aa2 100644 --- a/invokeai/frontend/web/src/features/canvas/components/IAICanvasToolbar/IAICanvasSettingsButtonPopover.tsx +++ b/invokeai/frontend/web/src/features/canvas/components/IAICanvasToolbar/IAICanvasSettingsButtonPopover.tsx @@ -18,6 +18,7 @@ import { setShouldAutoSave, setShouldCropToBoundingBoxOnSave, setShouldDarkenOutsideBoundingBox, + setShouldFitImageSize, setShouldInvertBrushSizeScrollDirection, setShouldRestrictStrokesToBox, setShouldShowCanvasDebugInfo, @@ -48,6 +49,7 @@ const IAICanvasSettingsButtonPopover = () => { const shouldSnapToGrid = useAppSelector((s) => s.canvas.shouldSnapToGrid); const shouldRestrictStrokesToBox = useAppSelector((s) => s.canvas.shouldRestrictStrokesToBox); const shouldAntialias = useAppSelector((s) => s.canvas.shouldAntialias); + const sholdFitImageSize = useAppSelector((s) => s.canvas.shouldFitImageSize); useHotkeys( ['n'], @@ -102,6 +104,10 @@ const IAICanvasSettingsButtonPopover = () => { (e: ChangeEvent) => dispatch(setShouldAntialias(e.target.checked)), [dispatch] ); + const handleChangeSholdFitImageSize = useCallback( + (e: ChangeEvent) => dispatch(setShouldFitImageSize(e.target.checked)), + [dispatch] + ); return ( @@ -165,6 +171,10 @@ const IAICanvasSettingsButtonPopover = () => { {t('unifiedCanvas.antialiasing')} + + {t('unifiedCanvas.initialFitImageSize')} + + diff --git a/invokeai/frontend/web/src/features/canvas/store/canvasSlice.ts b/invokeai/frontend/web/src/features/canvas/store/canvasSlice.ts index 68eba694ab..b9f68cf47d 100644 --- a/invokeai/frontend/web/src/features/canvas/store/canvasSlice.ts +++ b/invokeai/frontend/web/src/features/canvas/store/canvasSlice.ts @@ -66,6 +66,7 @@ const initialCanvasState: CanvasState = { shouldAutoSave: false, shouldCropToBoundingBoxOnSave: false, shouldDarkenOutsideBoundingBox: false, + shouldFitImageSize: false, shouldInvertBrushSizeScrollDirection: false, shouldLockBoundingBox: false, shouldPreserveMaskedArea: false, @@ -144,12 +145,14 @@ export const canvasSlice = createSlice({ reducer: (state, action: PayloadActionWithOptimalDimension) => { const { width, height, image_name } = action.payload; const { optimalDimension } = action.meta; - const { stageDimensions } = state; + const { stageDimensions, shouldFitImageSize } = state; - const newBoundingBoxDimensions = { - width: roundDownToMultiple(clamp(width, CANVAS_GRID_SIZE_FINE, optimalDimension), CANVAS_GRID_SIZE_FINE), - height: roundDownToMultiple(clamp(height, CANVAS_GRID_SIZE_FINE, optimalDimension), CANVAS_GRID_SIZE_FINE), - }; + const newBoundingBoxDimensions = shouldFitImageSize + ? { width, height } + : { + width: roundDownToMultiple(clamp(width, CANVAS_GRID_SIZE_FINE, optimalDimension), CANVAS_GRID_SIZE_FINE), + height: roundDownToMultiple(clamp(height, CANVAS_GRID_SIZE_FINE, optimalDimension), CANVAS_GRID_SIZE_FINE), + }; const newBoundingBoxCoordinates = { x: roundToMultiple(width / 2 - newBoundingBoxDimensions.width / 2, CANVAS_GRID_SIZE_FINE), @@ -582,6 +585,9 @@ export const canvasSlice = createSlice({ setShouldAntialias: (state, action: PayloadAction) => { state.shouldAntialias = action.payload; }, + setShouldFitImageSize: (state, action: PayloadAction) => { + state.shouldFitImageSize = action.payload; + }, setShouldCropToBoundingBoxOnSave: (state, action: PayloadAction) => { state.shouldCropToBoundingBoxOnSave = action.payload; }, @@ -692,6 +698,7 @@ export const { setShouldRestrictStrokesToBox, stagingAreaInitialized, setShouldAntialias, + setShouldFitImageSize, canvasResized, canvasBatchIdAdded, canvasBatchIdsReset, diff --git a/invokeai/frontend/web/src/features/canvas/store/canvasTypes.ts b/invokeai/frontend/web/src/features/canvas/store/canvasTypes.ts index 7fc39fde1f..2d30e18760 100644 --- a/invokeai/frontend/web/src/features/canvas/store/canvasTypes.ts +++ b/invokeai/frontend/web/src/features/canvas/store/canvasTypes.ts @@ -120,6 +120,7 @@ export interface CanvasState { shouldAutoSave: boolean; shouldCropToBoundingBoxOnSave: boolean; shouldDarkenOutsideBoundingBox: boolean; + shouldFitImageSize: boolean; shouldInvertBrushSizeScrollDirection: boolean; shouldLockBoundingBox: boolean; shouldPreserveMaskedArea: boolean; From d32e557e503b4a4f71f5069379ebd497812443bb Mon Sep 17 00:00:00 2001 From: symant233 Date: Thu, 4 Apr 2024 19:54:56 +0800 Subject: [PATCH 71/89] fix: add roundDownToMultiple --- invokeai/frontend/web/public/locales/zh_CN.json | 3 +-- .../web/src/features/canvas/store/canvasSlice.ts | 10 ++++++++-- 2 files changed, 9 insertions(+), 4 deletions(-) diff --git a/invokeai/frontend/web/public/locales/zh_CN.json b/invokeai/frontend/web/public/locales/zh_CN.json index e40b04ed10..77a06ea77b 100644 --- a/invokeai/frontend/web/public/locales/zh_CN.json +++ b/invokeai/frontend/web/public/locales/zh_CN.json @@ -583,8 +583,7 @@ "next": "下一张", "accept": "接受", "discardAll": "放弃所有", - "antialiasing": "抗锯齿", - "initialFitImageSize": "初始适应图片大小", + "antialiasing": "抗锯齿", "showResultsOn": "显示结果 (开)", "showResultsOff": "显示结果 (关)", "saveMask": "保存 $t(unifiedCanvas.mask)" diff --git a/invokeai/frontend/web/src/features/canvas/store/canvasSlice.ts b/invokeai/frontend/web/src/features/canvas/store/canvasSlice.ts index b9f68cf47d..d62f8f3f8d 100644 --- a/invokeai/frontend/web/src/features/canvas/store/canvasSlice.ts +++ b/invokeai/frontend/web/src/features/canvas/store/canvasSlice.ts @@ -148,10 +148,16 @@ export const canvasSlice = createSlice({ const { stageDimensions, shouldFitImageSize } = state; const newBoundingBoxDimensions = shouldFitImageSize - ? { width, height } + ? { + width: roundDownToMultiple(width, CANVAS_GRID_SIZE_FINE), + height: roundDownToMultiple(height, CANVAS_GRID_SIZE_FINE), + } : { width: roundDownToMultiple(clamp(width, CANVAS_GRID_SIZE_FINE, optimalDimension), CANVAS_GRID_SIZE_FINE), - height: roundDownToMultiple(clamp(height, CANVAS_GRID_SIZE_FINE, optimalDimension), CANVAS_GRID_SIZE_FINE), + height: roundDownToMultiple( + clamp(height, CANVAS_GRID_SIZE_FINE, optimalDimension), + CANVAS_GRID_SIZE_FINE + ), }; const newBoundingBoxCoordinates = { From b783679b9ff1bd3a5b57cea960e40b986459794a Mon Sep 17 00:00:00 2001 From: symant233 Date: Thu, 4 Apr 2024 20:17:48 +0800 Subject: [PATCH 72/89] fix: typo, change shouldFitImageSize default value --- .../IAICanvasToolbar/IAICanvasSettingsButtonPopover.tsx | 6 +++--- .../frontend/web/src/features/canvas/store/canvasSlice.ts | 2 +- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/invokeai/frontend/web/src/features/canvas/components/IAICanvasToolbar/IAICanvasSettingsButtonPopover.tsx b/invokeai/frontend/web/src/features/canvas/components/IAICanvasToolbar/IAICanvasSettingsButtonPopover.tsx index 80be585aa2..83ee900a43 100644 --- a/invokeai/frontend/web/src/features/canvas/components/IAICanvasToolbar/IAICanvasSettingsButtonPopover.tsx +++ b/invokeai/frontend/web/src/features/canvas/components/IAICanvasToolbar/IAICanvasSettingsButtonPopover.tsx @@ -49,7 +49,7 @@ const IAICanvasSettingsButtonPopover = () => { const shouldSnapToGrid = useAppSelector((s) => s.canvas.shouldSnapToGrid); const shouldRestrictStrokesToBox = useAppSelector((s) => s.canvas.shouldRestrictStrokesToBox); const shouldAntialias = useAppSelector((s) => s.canvas.shouldAntialias); - const sholdFitImageSize = useAppSelector((s) => s.canvas.shouldFitImageSize); + const shouldFitImageSize = useAppSelector((s) => s.canvas.shouldFitImageSize); useHotkeys( ['n'], @@ -104,7 +104,7 @@ const IAICanvasSettingsButtonPopover = () => { (e: ChangeEvent) => dispatch(setShouldAntialias(e.target.checked)), [dispatch] ); - const handleChangeSholdFitImageSize = useCallback( + const handleChangeShouldFitImageSize = useCallback( (e: ChangeEvent) => dispatch(setShouldFitImageSize(e.target.checked)), [dispatch] ); @@ -173,7 +173,7 @@ const IAICanvasSettingsButtonPopover = () => { {t('unifiedCanvas.initialFitImageSize')} - + diff --git a/invokeai/frontend/web/src/features/canvas/store/canvasSlice.ts b/invokeai/frontend/web/src/features/canvas/store/canvasSlice.ts index d62f8f3f8d..bb469c67f0 100644 --- a/invokeai/frontend/web/src/features/canvas/store/canvasSlice.ts +++ b/invokeai/frontend/web/src/features/canvas/store/canvasSlice.ts @@ -66,7 +66,7 @@ const initialCanvasState: CanvasState = { shouldAutoSave: false, shouldCropToBoundingBoxOnSave: false, shouldDarkenOutsideBoundingBox: false, - shouldFitImageSize: false, + shouldFitImageSize: true, shouldInvertBrushSizeScrollDirection: false, shouldLockBoundingBox: false, shouldPreserveMaskedArea: false, From e33096602002ed8c3433efe83d9628dd26822cf1 Mon Sep 17 00:00:00 2001 From: psychedelicious <4822129+psychedelicious@users.noreply.github.com> Date: Fri, 5 Apr 2024 14:14:17 +1100 Subject: [PATCH 73/89] chore: v4.0.3 --- invokeai/version/invokeai_version.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/invokeai/version/invokeai_version.py b/invokeai/version/invokeai_version.py index 064c0b35b2..c7a18d13e8 100644 --- a/invokeai/version/invokeai_version.py +++ b/invokeai/version/invokeai_version.py @@ -1 +1 @@ -__version__ = "4.0.2" +__version__ = "4.0.3" From 540d506ec9e4d96a55ed3760858f5f2cee7b972d Mon Sep 17 00:00:00 2001 From: blessedcoolant <54517381+blessedcoolant@users.noreply.github.com> Date: Sat, 6 Apr 2024 00:05:27 +0530 Subject: [PATCH 74/89] fix: Incorrect default clip vision opt in the node --- invokeai/app/invocations/ip_adapter.py | 2 +- invokeai/frontend/web/src/services/api/schema.ts | 6 +++--- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/invokeai/app/invocations/ip_adapter.py b/invokeai/app/invocations/ip_adapter.py index 0ac40e97fb..485414d263 100644 --- a/invokeai/app/invocations/ip_adapter.py +++ b/invokeai/app/invocations/ip_adapter.py @@ -67,7 +67,7 @@ class IPAdapterInvocation(BaseInvocation): ) clip_vision_model: Literal["ViT-H", "ViT-G"] = InputField( description="CLIP Vision model to use. Overrides model settings. Mandatory for checkpoint models.", - default="auto", + default="ViT-H", ui_order=2, ) weight: Union[float, List[float]] = InputField( diff --git a/invokeai/frontend/web/src/services/api/schema.ts b/invokeai/frontend/web/src/services/api/schema.ts index 72da0f1f8c..cb222bd497 100644 --- a/invokeai/frontend/web/src/services/api/schema.ts +++ b/invokeai/frontend/web/src/services/api/schema.ts @@ -4112,7 +4112,7 @@ export type components = { * @description The nodes in this graph */ nodes: { - [key: string]: components["schemas"]["ColorInvocation"] | components["schemas"]["SubtractInvocation"] | components["schemas"]["PidiImageProcessorInvocation"] | components["schemas"]["StringSplitInvocation"] | components["schemas"]["BlankImageInvocation"] | components["schemas"]["ShowImageInvocation"] | components["schemas"]["ImageChannelMultiplyInvocation"] | components["schemas"]["LoRALoaderInvocation"] | components["schemas"]["CreateDenoiseMaskInvocation"] | components["schemas"]["SDXLCompelPromptInvocation"] | components["schemas"]["ColorMapImageProcessorInvocation"] | components["schemas"]["RandomIntInvocation"] | components["schemas"]["ConditioningInvocation"] | components["schemas"]["SeamlessModeInvocation"] | components["schemas"]["ImageCollectionInvocation"] | components["schemas"]["MidasDepthImageProcessorInvocation"] | components["schemas"]["MaskCombineInvocation"] | components["schemas"]["ImageResizeInvocation"] | components["schemas"]["ResizeLatentsInvocation"] | components["schemas"]["StringJoinInvocation"] | components["schemas"]["TileToPropertiesInvocation"] | components["schemas"]["MaskFromIDInvocation"] | components["schemas"]["UnsharpMaskInvocation"] | components["schemas"]["FaceIdentifierInvocation"] | components["schemas"]["SDXLLoRALoaderInvocation"] | components["schemas"]["FloatToIntegerInvocation"] | components["schemas"]["FreeUInvocation"] | components["schemas"]["ImageWatermarkInvocation"] | components["schemas"]["MetadataItemInvocation"] | components["schemas"]["SaveImageInvocation"] | components["schemas"]["DivideInvocation"] | components["schemas"]["DepthAnythingImageProcessorInvocation"] | components["schemas"]["IntegerCollectionInvocation"] | components["schemas"]["InfillColorInvocation"] | components["schemas"]["SchedulerInvocation"] | components["schemas"]["CanvasPasteBackInvocation"] | components["schemas"]["IdealSizeInvocation"] | components["schemas"]["TileResamplerProcessorInvocation"] | components["schemas"]["NoiseInvocation"] | components["schemas"]["LatentsInvocation"] | components["schemas"]["LatentsCollectionInvocation"] | components["schemas"]["ControlNetInvocation"] | components["schemas"]["MlsdImageProcessorInvocation"] | components["schemas"]["StringJoinThreeInvocation"] | components["schemas"]["FloatMathInvocation"] | components["schemas"]["AddInvocation"] | components["schemas"]["CalculateImageTilesMinimumOverlapInvocation"] | components["schemas"]["BooleanCollectionInvocation"] | components["schemas"]["InfillTileInvocation"] | components["schemas"]["IterateInvocation"] | components["schemas"]["ConditioningCollectionInvocation"] | components["schemas"]["CalculateImageTilesInvocation"] | components["schemas"]["HedImageProcessorInvocation"] | components["schemas"]["LaMaInfillInvocation"] | components["schemas"]["ScaleLatentsInvocation"] | components["schemas"]["ImageConvertInvocation"] | components["schemas"]["StringInvocation"] | components["schemas"]["DWOpenposeImageProcessorInvocation"] | components["schemas"]["T2IAdapterInvocation"] | components["schemas"]["MediapipeFaceProcessorInvocation"] | components["schemas"]["CvInpaintInvocation"] | components["schemas"]["NormalbaeImageProcessorInvocation"] | components["schemas"]["ImageChannelInvocation"] | components["schemas"]["MaskEdgeInvocation"] | components["schemas"]["MultiplyInvocation"] | components["schemas"]["CollectInvocation"] | components["schemas"]["MergeTilesToImageInvocation"] | components["schemas"]["ColorCorrectInvocation"] | components["schemas"]["ImageMultiplyInvocation"] | components["schemas"]["MetadataInvocation"] | components["schemas"]["SegmentAnythingProcessorInvocation"] | components["schemas"]["ZoeDepthImageProcessorInvocation"] | components["schemas"]["FloatCollectionInvocation"] | components["schemas"]["CLIPSkipInvocation"] | components["schemas"]["MergeMetadataInvocation"] | components["schemas"]["ImageInverseLerpInvocation"] | components["schemas"]["DenoiseLatentsInvocation"] | components["schemas"]["RoundInvocation"] | components["schemas"]["PairTileImageInvocation"] | components["schemas"]["PromptsFromFileInvocation"] | components["schemas"]["CropLatentsCoreInvocation"] | components["schemas"]["SDXLRefinerCompelPromptInvocation"] | components["schemas"]["StringReplaceInvocation"] | components["schemas"]["ImageHueAdjustmentInvocation"] | components["schemas"]["RangeInvocation"] | components["schemas"]["InfillPatchMatchInvocation"] | components["schemas"]["LatentsToImageInvocation"] | components["schemas"]["ImageLerpInvocation"] | components["schemas"]["VAELoaderInvocation"] | components["schemas"]["RangeOfSizeInvocation"] | components["schemas"]["MaskFromAlphaInvocation"] | components["schemas"]["IntegerInvocation"] | components["schemas"]["CV2InfillInvocation"] | components["schemas"]["SDXLRefinerModelLoaderInvocation"] | components["schemas"]["ImageInvocation"] | components["schemas"]["DynamicPromptInvocation"] | components["schemas"]["ContentShuffleImageProcessorInvocation"] | components["schemas"]["LineartAnimeImageProcessorInvocation"] | components["schemas"]["IPAdapterInvocation"] | components["schemas"]["CalculateImageTilesEvenSplitInvocation"] | components["schemas"]["ImageBlurInvocation"] | components["schemas"]["LineartImageProcessorInvocation"] | components["schemas"]["CompelInvocation"] | components["schemas"]["FaceMaskInvocation"] | components["schemas"]["CreateGradientMaskInvocation"] | components["schemas"]["StringCollectionInvocation"] | components["schemas"]["FloatLinearRangeInvocation"] | components["schemas"]["CoreMetadataInvocation"] | components["schemas"]["StepParamEasingInvocation"] | components["schemas"]["FaceOffInvocation"] | components["schemas"]["RandomRangeInvocation"] | components["schemas"]["ESRGANInvocation"] | components["schemas"]["StringSplitNegInvocation"] | components["schemas"]["BooleanInvocation"] | components["schemas"]["LeresImageProcessorInvocation"] | components["schemas"]["SDXLModelLoaderInvocation"] | components["schemas"]["ImageScaleInvocation"] | components["schemas"]["RandomFloatInvocation"] | components["schemas"]["IntegerMathInvocation"] | components["schemas"]["ImageToLatentsInvocation"] | components["schemas"]["BlendLatentsInvocation"] | components["schemas"]["MainModelLoaderInvocation"] | components["schemas"]["ImageChannelOffsetInvocation"] | components["schemas"]["ImageNSFWBlurInvocation"] | components["schemas"]["FloatInvocation"] | components["schemas"]["ImageCropInvocation"] | components["schemas"]["CenterPadCropInvocation"] | components["schemas"]["CannyImageProcessorInvocation"] | components["schemas"]["ImagePasteInvocation"]; + [key: string]: components["schemas"]["IntegerMathInvocation"] | components["schemas"]["RandomRangeInvocation"] | components["schemas"]["NormalbaeImageProcessorInvocation"] | components["schemas"]["CoreMetadataInvocation"] | components["schemas"]["ImageWatermarkInvocation"] | components["schemas"]["ColorInvocation"] | components["schemas"]["ImageChannelOffsetInvocation"] | components["schemas"]["PromptsFromFileInvocation"] | components["schemas"]["CompelInvocation"] | components["schemas"]["ImageScaleInvocation"] | components["schemas"]["ColorCorrectInvocation"] | components["schemas"]["FloatInvocation"] | components["schemas"]["ImageChannelMultiplyInvocation"] | components["schemas"]["IntegerCollectionInvocation"] | components["schemas"]["DepthAnythingImageProcessorInvocation"] | components["schemas"]["FreeUInvocation"] | components["schemas"]["ControlNetInvocation"] | components["schemas"]["NoiseInvocation"] | components["schemas"]["ShowImageInvocation"] | components["schemas"]["RangeOfSizeInvocation"] | components["schemas"]["StringSplitNegInvocation"] | components["schemas"]["MultiplyInvocation"] | components["schemas"]["RandomFloatInvocation"] | components["schemas"]["MetadataInvocation"] | components["schemas"]["SDXLModelLoaderInvocation"] | components["schemas"]["MergeTilesToImageInvocation"] | components["schemas"]["DenoiseLatentsInvocation"] | components["schemas"]["MaskEdgeInvocation"] | components["schemas"]["LoRALoaderInvocation"] | components["schemas"]["ImageHueAdjustmentInvocation"] | components["schemas"]["CvInpaintInvocation"] | components["schemas"]["BooleanCollectionInvocation"] | components["schemas"]["FaceMaskInvocation"] | components["schemas"]["FloatToIntegerInvocation"] | components["schemas"]["BlankImageInvocation"] | components["schemas"]["MaskFromIDInvocation"] | components["schemas"]["CalculateImageTilesMinimumOverlapInvocation"] | components["schemas"]["FloatCollectionInvocation"] | components["schemas"]["MetadataItemInvocation"] | components["schemas"]["ResizeLatentsInvocation"] | components["schemas"]["IPAdapterInvocation"] | components["schemas"]["AddInvocation"] | components["schemas"]["StringSplitInvocation"] | components["schemas"]["CalculateImageTilesInvocation"] | components["schemas"]["TileToPropertiesInvocation"] | components["schemas"]["CreateGradientMaskInvocation"] | components["schemas"]["CropLatentsCoreInvocation"] | components["schemas"]["MediapipeFaceProcessorInvocation"] | components["schemas"]["UnsharpMaskInvocation"] | components["schemas"]["StringJoinThreeInvocation"] | components["schemas"]["ESRGANInvocation"] | components["schemas"]["ZoeDepthImageProcessorInvocation"] | components["schemas"]["FaceIdentifierInvocation"] | components["schemas"]["ImageNSFWBlurInvocation"] | components["schemas"]["StringJoinInvocation"] | components["schemas"]["ScaleLatentsInvocation"] | components["schemas"]["ContentShuffleImageProcessorInvocation"] | components["schemas"]["StringInvocation"] | components["schemas"]["FloatMathInvocation"] | components["schemas"]["CollectInvocation"] | components["schemas"]["SDXLCompelPromptInvocation"] | components["schemas"]["LatentsInvocation"] | components["schemas"]["ImageLerpInvocation"] | components["schemas"]["ImageInverseLerpInvocation"] | components["schemas"]["CannyImageProcessorInvocation"] | components["schemas"]["PidiImageProcessorInvocation"] | components["schemas"]["ImageCropInvocation"] | components["schemas"]["SegmentAnythingProcessorInvocation"] | components["schemas"]["MaskFromAlphaInvocation"] | components["schemas"]["MainModelLoaderInvocation"] | components["schemas"]["ImageToLatentsInvocation"] | components["schemas"]["LineartAnimeImageProcessorInvocation"] | components["schemas"]["LeresImageProcessorInvocation"] | components["schemas"]["CanvasPasteBackInvocation"] | components["schemas"]["CreateDenoiseMaskInvocation"] | components["schemas"]["StringCollectionInvocation"] | components["schemas"]["ImagePasteInvocation"] | components["schemas"]["T2IAdapterInvocation"] | components["schemas"]["DWOpenposeImageProcessorInvocation"] | components["schemas"]["SaveImageInvocation"] | components["schemas"]["StringReplaceInvocation"] | components["schemas"]["RangeInvocation"] | components["schemas"]["MlsdImageProcessorInvocation"] | components["schemas"]["InfillPatchMatchInvocation"] | components["schemas"]["SchedulerInvocation"] | components["schemas"]["RoundInvocation"] | components["schemas"]["ImageBlurInvocation"] | components["schemas"]["PairTileImageInvocation"] | components["schemas"]["ColorMapImageProcessorInvocation"] | components["schemas"]["ImageResizeInvocation"] | components["schemas"]["IterateInvocation"] | components["schemas"]["SeamlessModeInvocation"] | components["schemas"]["SDXLRefinerCompelPromptInvocation"] | components["schemas"]["IntegerInvocation"] | components["schemas"]["DivideInvocation"] | components["schemas"]["MergeMetadataInvocation"] | components["schemas"]["MaskCombineInvocation"] | components["schemas"]["InfillColorInvocation"] | components["schemas"]["HedImageProcessorInvocation"] | components["schemas"]["CLIPSkipInvocation"] | components["schemas"]["StepParamEasingInvocation"] | components["schemas"]["VAELoaderInvocation"] | components["schemas"]["ConditioningInvocation"] | components["schemas"]["LatentsCollectionInvocation"] | components["schemas"]["DynamicPromptInvocation"] | components["schemas"]["BooleanInvocation"] | components["schemas"]["BlendLatentsInvocation"] | components["schemas"]["ImageCollectionInvocation"] | components["schemas"]["RandomIntInvocation"] | components["schemas"]["SubtractInvocation"] | components["schemas"]["ImageConvertInvocation"] | components["schemas"]["SDXLRefinerModelLoaderInvocation"] | components["schemas"]["CalculateImageTilesEvenSplitInvocation"] | components["schemas"]["CV2InfillInvocation"] | components["schemas"]["ImageChannelInvocation"] | components["schemas"]["SDXLLoRALoaderInvocation"] | components["schemas"]["LaMaInfillInvocation"] | components["schemas"]["CenterPadCropInvocation"] | components["schemas"]["ConditioningCollectionInvocation"] | components["schemas"]["MidasDepthImageProcessorInvocation"] | components["schemas"]["TileResamplerProcessorInvocation"] | components["schemas"]["IdealSizeInvocation"] | components["schemas"]["FaceOffInvocation"] | components["schemas"]["ImageInvocation"] | components["schemas"]["ImageMultiplyInvocation"] | components["schemas"]["FloatLinearRangeInvocation"] | components["schemas"]["InfillTileInvocation"] | components["schemas"]["LatentsToImageInvocation"] | components["schemas"]["LineartImageProcessorInvocation"]; }; /** * Edges @@ -4149,7 +4149,7 @@ export type components = { * @description The results of node executions */ results: { - [key: string]: components["schemas"]["LatentsCollectionOutput"] | components["schemas"]["GradientMaskOutput"] | components["schemas"]["SDXLModelLoaderOutput"] | components["schemas"]["ConditioningCollectionOutput"] | components["schemas"]["CLIPOutput"] | components["schemas"]["SchedulerOutput"] | components["schemas"]["VAEOutput"] | components["schemas"]["FaceMaskOutput"] | components["schemas"]["BooleanOutput"] | components["schemas"]["IntegerCollectionOutput"] | components["schemas"]["SeamlessModeOutput"] | components["schemas"]["PairTileImageOutput"] | components["schemas"]["LoRALoaderOutput"] | components["schemas"]["MetadataOutput"] | components["schemas"]["IPAdapterOutput"] | components["schemas"]["NoiseOutput"] | components["schemas"]["IdealSizeOutput"] | components["schemas"]["FloatOutput"] | components["schemas"]["StringPosNegOutput"] | components["schemas"]["LatentsOutput"] | components["schemas"]["BooleanCollectionOutput"] | components["schemas"]["TileToPropertiesOutput"] | components["schemas"]["CalculateImageTilesOutput"] | components["schemas"]["FloatCollectionOutput"] | components["schemas"]["IntegerOutput"] | components["schemas"]["CLIPSkipInvocationOutput"] | components["schemas"]["FaceOffOutput"] | components["schemas"]["ImageCollectionOutput"] | components["schemas"]["UNetOutput"] | components["schemas"]["T2IAdapterOutput"] | components["schemas"]["ImageOutput"] | components["schemas"]["ColorOutput"] | components["schemas"]["ColorCollectionOutput"] | components["schemas"]["SDXLRefinerModelLoaderOutput"] | components["schemas"]["StringCollectionOutput"] | components["schemas"]["SDXLLoRALoaderOutput"] | components["schemas"]["ConditioningOutput"] | components["schemas"]["ModelLoaderOutput"] | components["schemas"]["DenoiseMaskOutput"] | components["schemas"]["MetadataItemOutput"] | components["schemas"]["IterateInvocationOutput"] | components["schemas"]["CollectInvocationOutput"] | components["schemas"]["ControlOutput"] | components["schemas"]["String2Output"] | components["schemas"]["StringOutput"]; + [key: string]: components["schemas"]["FloatCollectionOutput"] | components["schemas"]["VAEOutput"] | components["schemas"]["SDXLModelLoaderOutput"] | components["schemas"]["ConditioningCollectionOutput"] | components["schemas"]["FaceOffOutput"] | components["schemas"]["FaceMaskOutput"] | components["schemas"]["ImageOutput"] | components["schemas"]["ColorOutput"] | components["schemas"]["SDXLLoRALoaderOutput"] | components["schemas"]["T2IAdapterOutput"] | components["schemas"]["String2Output"] | components["schemas"]["CLIPSkipInvocationOutput"] | components["schemas"]["ConditioningOutput"] | components["schemas"]["StringOutput"] | components["schemas"]["ImageCollectionOutput"] | components["schemas"]["TileToPropertiesOutput"] | components["schemas"]["ControlOutput"] | components["schemas"]["NoiseOutput"] | components["schemas"]["MetadataItemOutput"] | components["schemas"]["LatentsCollectionOutput"] | components["schemas"]["IntegerOutput"] | components["schemas"]["ColorCollectionOutput"] | components["schemas"]["SchedulerOutput"] | components["schemas"]["CalculateImageTilesOutput"] | components["schemas"]["IdealSizeOutput"] | components["schemas"]["BooleanCollectionOutput"] | components["schemas"]["CollectInvocationOutput"] | components["schemas"]["DenoiseMaskOutput"] | components["schemas"]["MetadataOutput"] | components["schemas"]["StringCollectionOutput"] | components["schemas"]["PairTileImageOutput"] | components["schemas"]["SeamlessModeOutput"] | components["schemas"]["UNetOutput"] | components["schemas"]["CLIPOutput"] | components["schemas"]["FloatOutput"] | components["schemas"]["GradientMaskOutput"] | components["schemas"]["BooleanOutput"] | components["schemas"]["LatentsOutput"] | components["schemas"]["StringPosNegOutput"] | components["schemas"]["ModelLoaderOutput"] | components["schemas"]["IterateInvocationOutput"] | components["schemas"]["LoRALoaderOutput"] | components["schemas"]["IntegerCollectionOutput"] | components["schemas"]["IPAdapterOutput"] | components["schemas"]["SDXLRefinerModelLoaderOutput"]; }; /** * Errors @@ -4435,7 +4435,7 @@ export type components = { /** * Clip Vision Model * @description CLIP Vision model to use. Overrides model settings. Mandatory for checkpoint models. - * @default auto + * @default ViT-H * @enum {string} */ clip_vision_model?: "ViT-H" | "ViT-G"; From a09d705e4cfce4b45bc9bd603f89298968cd62c0 Mon Sep 17 00:00:00 2001 From: psychedelicious <4822129+psychedelicious@users.noreply.github.com> Date: Fri, 5 Apr 2024 18:40:57 +1100 Subject: [PATCH 75/89] fix(mm): remove vram check This check prematurely reports insufficient VRAM on Windows. See #6106 for details. --- .../load/model_cache/model_cache_default.py | 24 ------------------- 1 file changed, 24 deletions(-) diff --git a/invokeai/backend/model_manager/load/model_cache/model_cache_default.py b/invokeai/backend/model_manager/load/model_cache/model_cache_default.py index 4d5d09864e..49b48f20ef 100644 --- a/invokeai/backend/model_manager/load/model_cache/model_cache_default.py +++ b/invokeai/backend/model_manager/load/model_cache/model_cache_default.py @@ -269,9 +269,6 @@ class ModelCache(ModelCacheBase[AnyModel]): if torch.device(source_device).type == torch.device(target_device).type: return - # may raise an exception here if insufficient GPU VRAM - self._check_free_vram(target_device, cache_entry.size) - start_model_to_time = time.time() snapshot_before = self._capture_memory_snapshot() cache_entry.model.to(target_device) @@ -420,24 +417,3 @@ class ModelCache(ModelCacheBase[AnyModel]): mps.empty_cache() self.logger.debug(f"After making room: cached_models={len(self._cached_models)}") - - def _free_vram(self, device: torch.device) -> int: - vram_device = ( # mem_get_info() needs an indexed device - device if device.index is not None else torch.device(str(device), index=0) - ) - free_mem, _ = torch.cuda.mem_get_info(vram_device) - for _, cache_entry in self._cached_models.items(): - if cache_entry.loaded and not cache_entry.locked: - free_mem += cache_entry.size - return free_mem - - def _check_free_vram(self, target_device: torch.device, needed_size: int) -> None: - if target_device.type != "cuda": - return - free_mem = self._free_vram(target_device) - if needed_size > free_mem: - needed_gb = round(needed_size / GIG, 2) - free_gb = round(free_mem / GIG, 2) - raise torch.cuda.OutOfMemoryError( - f"Insufficient VRAM to load model, requested {needed_gb}GB but only had {free_gb}GB free" - ) From 4068e817d6eae4689362b0e74c79f3b1b05a5fd5 Mon Sep 17 00:00:00 2001 From: psychedelicious <4822129+psychedelicious@users.noreply.github.com> Date: Fri, 5 Apr 2024 18:41:10 +1100 Subject: [PATCH 76/89] fix(mm): typing issues in model cache --- .../model_manager/load/model_cache/model_cache_base.py | 2 +- .../model_manager/load/model_cache/model_cache_default.py | 6 +++--- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/invokeai/backend/model_manager/load/model_cache/model_cache_base.py b/invokeai/backend/model_manager/load/model_cache/model_cache_base.py index eb82f87cb2..a8c2dd3e92 100644 --- a/invokeai/backend/model_manager/load/model_cache/model_cache_base.py +++ b/invokeai/backend/model_manager/load/model_cache/model_cache_base.py @@ -117,7 +117,7 @@ class ModelCacheBase(ABC, Generic[T]): @property @abstractmethod - def stats(self) -> CacheStats: + def stats(self) -> Optional[CacheStats]: """Return collected CacheStats object.""" pass diff --git a/invokeai/backend/model_manager/load/model_cache/model_cache_default.py b/invokeai/backend/model_manager/load/model_cache/model_cache_default.py index 49b48f20ef..f2e0c01a94 100644 --- a/invokeai/backend/model_manager/load/model_cache/model_cache_default.py +++ b/invokeai/backend/model_manager/load/model_cache/model_cache_default.py @@ -326,11 +326,11 @@ class ModelCache(ModelCacheBase[AnyModel]): f" {in_ram_models}/{in_vram_models}({locked_in_vram_models})" ) - def make_room(self, model_size: int) -> None: + def make_room(self, size: int) -> None: """Make enough room in the cache to accommodate a new model of indicated size.""" # calculate how much memory this model will require # multiplier = 2 if self.precision==torch.float32 else 1 - bytes_needed = model_size + bytes_needed = size maximum_size = self.max_cache_size * GIG # stored in GB, convert to bytes current_size = self.cache_size() @@ -385,7 +385,7 @@ class ModelCache(ModelCacheBase[AnyModel]): # 1 from onnx runtime object if not cache_entry.locked and refs <= (3 if "onnx" in model_key else 2): self.logger.debug( - f"Removing {model_key} from RAM cache to free at least {(model_size/GIG):.2f} GB (-{(cache_entry.size/GIG):.2f} GB)" + f"Removing {model_key} from RAM cache to free at least {(size/GIG):.2f} GB (-{(cache_entry.size/GIG):.2f} GB)" ) current_size -= cache_entry.size models_cleared += 1 From a95756f3edd97b03d54a4cc7b6c150ce1f2e8c47 Mon Sep 17 00:00:00 2001 From: psychedelicious <4822129+psychedelicious@users.noreply.github.com> Date: Fri, 5 Apr 2024 18:58:08 +1100 Subject: [PATCH 77/89] docs: update FAQ.md (shared GPU memory) --- docs/help/FAQ.md | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) diff --git a/docs/help/FAQ.md b/docs/help/FAQ.md index 458770e41a..4c297f442a 100644 --- a/docs/help/FAQ.md +++ b/docs/help/FAQ.md @@ -40,6 +40,25 @@ Follow the same steps to scan and import the missing models. - Check the `ram` setting in `invokeai.yaml`. This setting tells Invoke how much of your system RAM can be used to cache models. Having this too high or too low can slow things down. That said, it's generally safest to not set this at all and instead let Invoke manage it. - Check the `vram` setting in `invokeai.yaml`. This setting tells Invoke how much of your GPU VRAM can be used to cache models. Counter-intuitively, if this setting is too high, Invoke will need to do a lot of shuffling of models as it juggles the VRAM cache and the currently-loaded model. The default value of 0.25 is generally works well for GPUs without 16GB or more VRAM. Even on a 24GB card, the default works well. - Check that your generations are happening on your GPU (if you have one). InvokeAI will log what is being used for generation upon startup. If your GPU isn't used, re-install to ensure the correct versions of torch get installed. +- If you are on Windows, you may have exceeded your GPU's VRAM capacity and are using slower [shared GPU memory](#shared-gpu-memory-windows). There's a guide to opt out of this behaviour in the linked FAQ entry. + +## Shared GPU Memory (Windows) + +!!! tip "Nvidia GPUs with driver 536.40" + + This only applies to current Nvidia cards with driver 536.40 or later, released in June 2023. + +When the GPU doesn't have enough VRAM for a task, Windows is able to allocate some of its CPU RAM to the GPU. This is much slower than VRAM, but it does allow the system to generate when it otherwise might no have enough VRAM. + +When shared GPU memory is used, generation slows down dramatically - but at least it doesn't crash. + +If you'd like to opt out of this behavior and instead get an error when you exceed your GPU's VRAM, follow [this guide from Nvidia](https://nvidia.custhelp.com/app/answers/detail/a_id/5490). + +Here's how to get the python path required in the linked guide: + +- Run `invoke.bat`. +- Select option 2 for developer console. +- At least one python path will be printed. Copy the path that includes your invoke installation directory (typically the first). ## Installer cannot find python (Windows) From f56b9537cde6e4b50c54b3fbc08f38befafa207c Mon Sep 17 00:00:00 2001 From: Jennifer Player Date: Thu, 4 Apr 2024 10:54:02 -0400 Subject: [PATCH 78/89] added initial image to metadata viewer --- .../components/ImageMetadataViewer/ImageMetadataActions.tsx | 1 + 1 file changed, 1 insertion(+) diff --git a/invokeai/frontend/web/src/features/gallery/components/ImageMetadataViewer/ImageMetadataActions.tsx b/invokeai/frontend/web/src/features/gallery/components/ImageMetadataViewer/ImageMetadataActions.tsx index 5b9f15c21a..ce75ea62e0 100644 --- a/invokeai/frontend/web/src/features/gallery/components/ImageMetadataViewer/ImageMetadataActions.tsx +++ b/invokeai/frontend/web/src/features/gallery/components/ImageMetadataViewer/ImageMetadataActions.tsx @@ -33,6 +33,7 @@ const ImageMetadataActions = (props: Props) => { + From 8a17616bf456853d3194fe5578b4a515ba104b8d Mon Sep 17 00:00:00 2001 From: Jennifer Player Date: Thu, 4 Apr 2024 10:56:25 -0400 Subject: [PATCH 79/89] recall initial image from metadata and set to image2image --- .../web/src/features/metadata/util/handlers.ts | 5 +++++ .../web/src/features/metadata/util/parsers.ts | 5 +++++ .../web/src/features/metadata/util/recallers.ts | 11 +++++++++++ .../src/features/parameters/types/parameterSchemas.ts | 7 +++++++ 4 files changed, 28 insertions(+) diff --git a/invokeai/frontend/web/src/features/metadata/util/handlers.ts b/invokeai/frontend/web/src/features/metadata/util/handlers.ts index b64426b422..4bf717f638 100644 --- a/invokeai/frontend/web/src/features/metadata/util/handlers.ts +++ b/invokeai/frontend/web/src/features/metadata/util/handlers.ts @@ -189,6 +189,11 @@ export const handlers = { recaller: recallers.cfgScale, }), height: buildHandlers({ getLabel: () => t('metadata.height'), parser: parsers.height, recaller: recallers.height }), + initialImage: buildHandlers({ + getLabel: () => t('metadata.initImage'), + parser: parsers.initialImage, + recaller: recallers.initialImage, + }), negativePrompt: buildHandlers({ getLabel: () => t('metadata.negativePrompt'), parser: parsers.negativePrompt, diff --git a/invokeai/frontend/web/src/features/metadata/util/parsers.ts b/invokeai/frontend/web/src/features/metadata/util/parsers.ts index 635a63a8de..26a0c3c5b1 100644 --- a/invokeai/frontend/web/src/features/metadata/util/parsers.ts +++ b/invokeai/frontend/web/src/features/metadata/util/parsers.ts @@ -20,6 +20,7 @@ import type { ParameterHeight, ParameterHRFEnabled, ParameterHRFMethod, + ParameterInitialImage, ParameterModel, ParameterNegativePrompt, ParameterNegativeStylePromptSDXL, @@ -135,6 +136,9 @@ const parseCFGRescaleMultiplier: MetadataParseFunc = (metadata) => getProperty(metadata, 'scheduler', isParameterScheduler); +const parseInitialImage: MetadataParseFunc = (metadata) => + getProperty(metadata, 'init_image', isString); + const parseWidth: MetadataParseFunc = (metadata) => getProperty(metadata, 'width', isParameterWidth); const parseHeight: MetadataParseFunc = (metadata) => @@ -402,6 +406,7 @@ export const parsers = { cfgScale: parseCFGScale, cfgRescaleMultiplier: parseCFGRescaleMultiplier, scheduler: parseScheduler, + initialImage: parseInitialImage, width: parseWidth, height: parseHeight, steps: parseSteps, diff --git a/invokeai/frontend/web/src/features/metadata/util/recallers.ts b/invokeai/frontend/web/src/features/metadata/util/recallers.ts index f35399c139..617e5b2377 100644 --- a/invokeai/frontend/web/src/features/metadata/util/recallers.ts +++ b/invokeai/frontend/web/src/features/metadata/util/recallers.ts @@ -17,6 +17,7 @@ import type { import { modelSelected } from 'features/parameters/store/actions'; import { heightRecalled, + initialImageChanged, setCfgRescaleMultiplier, setCfgScale, setImg2imgStrength, @@ -34,6 +35,7 @@ import type { ParameterHeight, ParameterHRFEnabled, ParameterHRFMethod, + ParameterInitialImage, ParameterModel, ParameterNegativePrompt, ParameterNegativeStylePromptSDXL, @@ -61,6 +63,7 @@ import { setRefinerStart, setRefinerSteps, } from 'features/sdxl/store/sdxlSlice'; +import type { ImageDTO } from 'services/api/types'; const recallPositivePrompt: MetadataRecallFunc = (positivePrompt) => { getStore().dispatch(setPositivePrompt(positivePrompt)); @@ -94,6 +97,13 @@ const recallScheduler: MetadataRecallFunc = (scheduler) => { getStore().dispatch(setScheduler(scheduler)); }; +const recallInitialImage: MetadataRecallFunc = (initialImage) => { + const image = { + image_name: initialImage, + }; + getStore().dispatch(initialImageChanged(image as ImageDTO)); +}; + const recallWidth: MetadataRecallFunc = (width) => { getStore().dispatch(widthRecalled(width)); }; @@ -235,6 +245,7 @@ export const recallers = { cfgScale: recallCFGScale, cfgRescaleMultiplier: recallCFGRescaleMultiplier, scheduler: recallScheduler, + initialImage: recallInitialImage, width: recallWidth, height: recallHeight, steps: recallSteps, diff --git a/invokeai/frontend/web/src/features/parameters/types/parameterSchemas.ts b/invokeai/frontend/web/src/features/parameters/types/parameterSchemas.ts index 75693cd47f..b3c403488a 100644 --- a/invokeai/frontend/web/src/features/parameters/types/parameterSchemas.ts +++ b/invokeai/frontend/web/src/features/parameters/types/parameterSchemas.ts @@ -126,6 +126,13 @@ const zParameterT2IAdapterModel = zModelIdentifierField; export type ParameterT2IAdapterModel = z.infer; // #endregion +// #region I2I Initial Image +const zParameterInitialImage = z.string(); +export type ParameterInitialImage = z.infer; +export const isParameterInitialImage = (val: unknown): val is ParameterInitialImage => + zParameterInitialImage.safeParse(val).success; +// #endregion + // #region Strength (l2l strength) const zParameterStrength = z.number().min(0).max(1); export type ParameterStrength = z.infer; From 9ba5ec4b67d16451e49e6080c93f5d3069fe53a4 Mon Sep 17 00:00:00 2001 From: Jennifer Player Date: Thu, 4 Apr 2024 10:56:54 -0400 Subject: [PATCH 80/89] fix typo Params set set --- invokeai/frontend/web/public/locales/en.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/invokeai/frontend/web/public/locales/en.json b/invokeai/frontend/web/public/locales/en.json index 623cea64ee..e2ef66b9de 100644 --- a/invokeai/frontend/web/public/locales/en.json +++ b/invokeai/frontend/web/public/locales/en.json @@ -1044,7 +1044,7 @@ "parameterNotSet": "{{parameter}} not set", "parameterSet": "{{parameter}} set", "parametersNotSet": "Parameters Not Set", - "parametersSet": "Parameters Set", + "parametersSet": "Parameters", "problemCopyingCanvas": "Problem Copying Canvas", "problemCopyingCanvasDesc": "Unable to export base layer", "problemCopyingImage": "Unable to Copy Image", From fdd0e5797684f2e105770517508bc97097eb4fee Mon Sep 17 00:00:00 2001 From: Jennifer Player Date: Thu, 4 Apr 2024 11:06:30 -0400 Subject: [PATCH 81/89] actually use the schema --- invokeai/frontend/web/src/features/metadata/util/parsers.ts | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/invokeai/frontend/web/src/features/metadata/util/parsers.ts b/invokeai/frontend/web/src/features/metadata/util/parsers.ts index 26a0c3c5b1..5d844c4663 100644 --- a/invokeai/frontend/web/src/features/metadata/util/parsers.ts +++ b/invokeai/frontend/web/src/features/metadata/util/parsers.ts @@ -43,6 +43,7 @@ import { isParameterHeight, isParameterHRFEnabled, isParameterHRFMethod, + isParameterInitialImage, isParameterLoRAWeight, isParameterNegativePrompt, isParameterNegativeStylePromptSDXL, @@ -137,7 +138,7 @@ const parseScheduler: MetadataParseFunc = (metadata) => getProperty(metadata, 'scheduler', isParameterScheduler); const parseInitialImage: MetadataParseFunc = (metadata) => - getProperty(metadata, 'init_image', isString); + getProperty(metadata, 'init_image', isParameterInitialImage); const parseWidth: MetadataParseFunc = (metadata) => getProperty(metadata, 'width', isParameterWidth); From adc30045a6ec268afcce3ba2fd76db3e02159273 Mon Sep 17 00:00:00 2001 From: Jennifer Player Date: Fri, 5 Apr 2024 13:30:31 -0400 Subject: [PATCH 82/89] addressed pr feedback --- invokeai/frontend/web/public/locales/en.json | 2 +- .../web/src/features/metadata/util/handlers.ts | 2 +- .../web/src/features/metadata/util/recallers.ts | 12 ++++++------ 3 files changed, 8 insertions(+), 8 deletions(-) diff --git a/invokeai/frontend/web/public/locales/en.json b/invokeai/frontend/web/public/locales/en.json index e2ef66b9de..9686f8a02a 100644 --- a/invokeai/frontend/web/public/locales/en.json +++ b/invokeai/frontend/web/public/locales/en.json @@ -1041,10 +1041,10 @@ "metadataLoadFailed": "Failed to load metadata", "modelAddedSimple": "Model Added to Queue", "modelImportCanceled": "Model Import Canceled", + "parameters": "Parameters", "parameterNotSet": "{{parameter}} not set", "parameterSet": "{{parameter}} set", "parametersNotSet": "Parameters Not Set", - "parametersSet": "Parameters", "problemCopyingCanvas": "Problem Copying Canvas", "problemCopyingCanvasDesc": "Unable to export base layer", "problemCopyingImage": "Unable to Copy Image", diff --git a/invokeai/frontend/web/src/features/metadata/util/handlers.ts b/invokeai/frontend/web/src/features/metadata/util/handlers.ts index 4bf717f638..af089a3177 100644 --- a/invokeai/frontend/web/src/features/metadata/util/handlers.ts +++ b/invokeai/frontend/web/src/features/metadata/util/handlers.ts @@ -410,6 +410,6 @@ export const parseAndRecallAllMetadata = async (metadata: unknown, skip: (keyof }) ); if (results.some((result) => result.status === 'fulfilled')) { - parameterSetToast(t('toast.parametersSet')); + parameterSetToast(t('toast.parameters')); } }; diff --git a/invokeai/frontend/web/src/features/metadata/util/recallers.ts b/invokeai/frontend/web/src/features/metadata/util/recallers.ts index 617e5b2377..50d814b859 100644 --- a/invokeai/frontend/web/src/features/metadata/util/recallers.ts +++ b/invokeai/frontend/web/src/features/metadata/util/recallers.ts @@ -63,7 +63,7 @@ import { setRefinerStart, setRefinerSteps, } from 'features/sdxl/store/sdxlSlice'; -import type { ImageDTO } from 'services/api/types'; +import { imagesApi } from 'services/api/endpoints/images'; const recallPositivePrompt: MetadataRecallFunc = (positivePrompt) => { getStore().dispatch(setPositivePrompt(positivePrompt)); @@ -97,11 +97,11 @@ const recallScheduler: MetadataRecallFunc = (scheduler) => { getStore().dispatch(setScheduler(scheduler)); }; -const recallInitialImage: MetadataRecallFunc = (initialImage) => { - const image = { - image_name: initialImage, - }; - getStore().dispatch(initialImageChanged(image as ImageDTO)); +const recallInitialImage: MetadataRecallFunc = async (initialImage) => { + const imageDTORequest = getStore().dispatch(imagesApi.endpoints.getImageDTO.initiate(initialImage)); + const imageDTO = await imageDTORequest.unwrap(); + imageDTORequest.unsubscribe(); + getStore().dispatch(initialImageChanged(imageDTO)); }; const recallWidth: MetadataRecallFunc = (width) => { From 55f7a7737a271dba902d2dba6cf523c074c177ed Mon Sep 17 00:00:00 2001 From: psychedelicious <4822129+psychedelicious@users.noreply.github.com> Date: Sat, 6 Apr 2024 14:47:24 +1100 Subject: [PATCH 83/89] feat(ui): shift around init image recall logic Retrieving the DTO happens as part of the metadata parsing, not recall. This way, we don't show the option to recall a nonexistent image. This matches the flow for other metadata entities like models - we don't show the model recall button if the model isn't available. --- .../web/src/features/metadata/util/handlers.ts | 1 + .../web/src/features/metadata/util/parsers.ts | 13 ++++++++++--- .../web/src/features/metadata/util/recallers.ts | 8 ++------ 3 files changed, 13 insertions(+), 9 deletions(-) diff --git a/invokeai/frontend/web/src/features/metadata/util/handlers.ts b/invokeai/frontend/web/src/features/metadata/util/handlers.ts index af089a3177..2fb840afcb 100644 --- a/invokeai/frontend/web/src/features/metadata/util/handlers.ts +++ b/invokeai/frontend/web/src/features/metadata/util/handlers.ts @@ -193,6 +193,7 @@ export const handlers = { getLabel: () => t('metadata.initImage'), parser: parsers.initialImage, recaller: recallers.initialImage, + renderValue: async (imageDTO) => imageDTO.image_name, }), negativePrompt: buildHandlers({ getLabel: () => t('metadata.negativePrompt'), diff --git a/invokeai/frontend/web/src/features/metadata/util/parsers.ts b/invokeai/frontend/web/src/features/metadata/util/parsers.ts index 5d844c4663..55a170745d 100644 --- a/invokeai/frontend/web/src/features/metadata/util/parsers.ts +++ b/invokeai/frontend/web/src/features/metadata/util/parsers.ts @@ -1,3 +1,4 @@ +import { getStore } from 'app/store/nanostores/store'; import { initialControlNet, initialIPAdapter, @@ -20,7 +21,6 @@ import type { ParameterHeight, ParameterHRFEnabled, ParameterHRFMethod, - ParameterInitialImage, ParameterModel, ParameterNegativePrompt, ParameterNegativeStylePromptSDXL, @@ -59,6 +59,8 @@ import { isParameterWidth, } from 'features/parameters/types/parameterSchemas'; import { get, isArray, isString } from 'lodash-es'; +import { imagesApi } from 'services/api/endpoints/images'; +import type { ImageDTO } from 'services/api/types'; import { isControlNetModelConfig, isIPAdapterModelConfig, @@ -137,8 +139,13 @@ const parseCFGRescaleMultiplier: MetadataParseFunc = (metadata) => getProperty(metadata, 'scheduler', isParameterScheduler); -const parseInitialImage: MetadataParseFunc = (metadata) => - getProperty(metadata, 'init_image', isParameterInitialImage); +const parseInitialImage: MetadataParseFunc = async (metadata) => { + const imageName = await getProperty(metadata, 'init_image', isParameterInitialImage); + const imageDTORequest = getStore().dispatch(imagesApi.endpoints.getImageDTO.initiate(imageName)); + const imageDTO = await imageDTORequest.unwrap(); + imageDTORequest.unsubscribe(); + return imageDTO; +}; const parseWidth: MetadataParseFunc = (metadata) => getProperty(metadata, 'width', isParameterWidth); diff --git a/invokeai/frontend/web/src/features/metadata/util/recallers.ts b/invokeai/frontend/web/src/features/metadata/util/recallers.ts index 50d814b859..88af390a20 100644 --- a/invokeai/frontend/web/src/features/metadata/util/recallers.ts +++ b/invokeai/frontend/web/src/features/metadata/util/recallers.ts @@ -35,7 +35,6 @@ import type { ParameterHeight, ParameterHRFEnabled, ParameterHRFMethod, - ParameterInitialImage, ParameterModel, ParameterNegativePrompt, ParameterNegativeStylePromptSDXL, @@ -63,7 +62,7 @@ import { setRefinerStart, setRefinerSteps, } from 'features/sdxl/store/sdxlSlice'; -import { imagesApi } from 'services/api/endpoints/images'; +import type { ImageDTO } from 'services/api/types'; const recallPositivePrompt: MetadataRecallFunc = (positivePrompt) => { getStore().dispatch(setPositivePrompt(positivePrompt)); @@ -97,10 +96,7 @@ const recallScheduler: MetadataRecallFunc = (scheduler) => { getStore().dispatch(setScheduler(scheduler)); }; -const recallInitialImage: MetadataRecallFunc = async (initialImage) => { - const imageDTORequest = getStore().dispatch(imagesApi.endpoints.getImageDTO.initiate(initialImage)); - const imageDTO = await imageDTORequest.unwrap(); - imageDTORequest.unsubscribe(); +const recallInitialImage: MetadataRecallFunc = async (imageDTO) => { getStore().dispatch(initialImageChanged(imageDTO)); }; From 4d0a49298cbf813dddb27b852ba7fc83d5af7079 Mon Sep 17 00:00:00 2001 From: psychedelicious <4822129+psychedelicious@users.noreply.github.com> Date: Sat, 6 Apr 2024 14:48:51 +1100 Subject: [PATCH 84/89] tidy(ui): remove extraneous zod schema --- .../frontend/web/src/features/metadata/util/parsers.ts | 3 +-- .../web/src/features/parameters/types/parameterSchemas.ts | 7 ------- 2 files changed, 1 insertion(+), 9 deletions(-) diff --git a/invokeai/frontend/web/src/features/metadata/util/parsers.ts b/invokeai/frontend/web/src/features/metadata/util/parsers.ts index 55a170745d..9f5c14d94e 100644 --- a/invokeai/frontend/web/src/features/metadata/util/parsers.ts +++ b/invokeai/frontend/web/src/features/metadata/util/parsers.ts @@ -43,7 +43,6 @@ import { isParameterHeight, isParameterHRFEnabled, isParameterHRFMethod, - isParameterInitialImage, isParameterLoRAWeight, isParameterNegativePrompt, isParameterNegativeStylePromptSDXL, @@ -140,7 +139,7 @@ const parseScheduler: MetadataParseFunc = (metadata) => getProperty(metadata, 'scheduler', isParameterScheduler); const parseInitialImage: MetadataParseFunc = async (metadata) => { - const imageName = await getProperty(metadata, 'init_image', isParameterInitialImage); + const imageName = await getProperty(metadata, 'init_image', isString); const imageDTORequest = getStore().dispatch(imagesApi.endpoints.getImageDTO.initiate(imageName)); const imageDTO = await imageDTORequest.unwrap(); imageDTORequest.unsubscribe(); diff --git a/invokeai/frontend/web/src/features/parameters/types/parameterSchemas.ts b/invokeai/frontend/web/src/features/parameters/types/parameterSchemas.ts index b3c403488a..75693cd47f 100644 --- a/invokeai/frontend/web/src/features/parameters/types/parameterSchemas.ts +++ b/invokeai/frontend/web/src/features/parameters/types/parameterSchemas.ts @@ -126,13 +126,6 @@ const zParameterT2IAdapterModel = zModelIdentifierField; export type ParameterT2IAdapterModel = z.infer; // #endregion -// #region I2I Initial Image -const zParameterInitialImage = z.string(); -export type ParameterInitialImage = z.infer; -export const isParameterInitialImage = (val: unknown): val is ParameterInitialImage => - zParameterInitialImage.safeParse(val).success; -// #endregion - // #region Strength (l2l strength) const zParameterStrength = z.number().min(0).max(1); export type ParameterStrength = z.infer; From 69f17da1a21e5cc2768de36f18664242d2fe97bc Mon Sep 17 00:00:00 2001 From: psychedelicious <4822129+psychedelicious@users.noreply.github.com> Date: Sat, 6 Apr 2024 08:50:52 +1100 Subject: [PATCH 85/89] fix(nodes): add WithBoard to public API --- invokeai/invocation_api/__init__.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/invokeai/invocation_api/__init__.py b/invokeai/invocation_api/__init__.py index 300ecd751b..11f334e24e 100644 --- a/invokeai/invocation_api/__init__.py +++ b/invokeai/invocation_api/__init__.py @@ -27,6 +27,7 @@ from invokeai.app.invocations.fields import ( OutputField, UIComponent, UIType, + WithBoard, WithMetadata, WithWorkflow, ) @@ -105,6 +106,7 @@ __all__ = [ "OutputField", "UIComponent", "UIType", + "WithBoard", "WithMetadata", "WithWorkflow", # invokeai.app.invocations.latent From 9a0a90e2a293b58d2a7b0714505f6f192165ffc5 Mon Sep 17 00:00:00 2001 From: psychedelicious <4822129+psychedelicious@users.noreply.github.com> Date: Sat, 6 Apr 2024 14:25:44 +1100 Subject: [PATCH 86/89] chore: v4.0.4 --- invokeai/version/invokeai_version.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/invokeai/version/invokeai_version.py b/invokeai/version/invokeai_version.py index c7a18d13e8..4b56dfc53e 100644 --- a/invokeai/version/invokeai_version.py +++ b/invokeai/version/invokeai_version.py @@ -1 +1 @@ -__version__ = "4.0.3" +__version__ = "4.0.4" From 2c45697f3d50fefbdd8c4a8b68694a2043d5d2ad Mon Sep 17 00:00:00 2001 From: Hosted Weblate Date: Sat, 6 Apr 2024 05:54:19 +0200 Subject: [PATCH 87/89] translationBot(ui): update translation files Updated by "Cleanup translation files" hook in Weblate. Co-authored-by: Hosted Weblate Translate-URL: https://hosted.weblate.org/projects/invokeai/web-ui/ Translation: InvokeAI/Web UI --- invokeai/frontend/web/public/locales/ar.json | 1 - invokeai/frontend/web/public/locales/de.json | 1 - invokeai/frontend/web/public/locales/es.json | 1 - invokeai/frontend/web/public/locales/fr.json | 1 - invokeai/frontend/web/public/locales/he.json | 1 - invokeai/frontend/web/public/locales/it.json | 1 - invokeai/frontend/web/public/locales/nl.json | 1 - invokeai/frontend/web/public/locales/pl.json | 1 - invokeai/frontend/web/public/locales/pt.json | 1 - invokeai/frontend/web/public/locales/pt_BR.json | 1 - invokeai/frontend/web/public/locales/ru.json | 1 - invokeai/frontend/web/public/locales/uk.json | 1 - invokeai/frontend/web/public/locales/zh_CN.json | 1 - 13 files changed, 13 deletions(-) diff --git a/invokeai/frontend/web/public/locales/ar.json b/invokeai/frontend/web/public/locales/ar.json index d5be1b1fce..ee370d1e42 100644 --- a/invokeai/frontend/web/public/locales/ar.json +++ b/invokeai/frontend/web/public/locales/ar.json @@ -291,7 +291,6 @@ "canvasMerged": "تم دمج الخط", "sentToImageToImage": "تم إرسال إلى صورة إلى صورة", "sentToUnifiedCanvas": "تم إرسال إلى لوحة موحدة", - "parametersSet": "تم تعيين المعلمات", "parametersNotSet": "لم يتم تعيين المعلمات", "metadataLoadFailed": "فشل تحميل البيانات الوصفية" }, diff --git a/invokeai/frontend/web/public/locales/de.json b/invokeai/frontend/web/public/locales/de.json index 4f18cd0050..033dffdc44 100644 --- a/invokeai/frontend/web/public/locales/de.json +++ b/invokeai/frontend/web/public/locales/de.json @@ -480,7 +480,6 @@ "canvasMerged": "Leinwand zusammengeführt", "sentToImageToImage": "Gesendet an Bild zu Bild", "sentToUnifiedCanvas": "Gesendet an Leinwand", - "parametersSet": "Parameter festlegen", "parametersNotSet": "Parameter nicht festgelegt", "metadataLoadFailed": "Metadaten konnten nicht geladen werden", "setCanvasInitialImage": "Ausgangsbild setzen", diff --git a/invokeai/frontend/web/public/locales/es.json b/invokeai/frontend/web/public/locales/es.json index c7af596556..3037045db5 100644 --- a/invokeai/frontend/web/public/locales/es.json +++ b/invokeai/frontend/web/public/locales/es.json @@ -363,7 +363,6 @@ "canvasMerged": "Lienzo consolidado", "sentToImageToImage": "Enviar hacia Imagen a Imagen", "sentToUnifiedCanvas": "Enviar hacia Lienzo Consolidado", - "parametersSet": "Parámetros establecidos", "parametersNotSet": "Parámetros no establecidos", "metadataLoadFailed": "Error al cargar metadatos", "serverError": "Error en el servidor", diff --git a/invokeai/frontend/web/public/locales/fr.json b/invokeai/frontend/web/public/locales/fr.json index 095ee5d0d5..b8f560e265 100644 --- a/invokeai/frontend/web/public/locales/fr.json +++ b/invokeai/frontend/web/public/locales/fr.json @@ -298,7 +298,6 @@ "canvasMerged": "Canvas fusionné", "sentToImageToImage": "Envoyé à Image à Image", "sentToUnifiedCanvas": "Envoyé à Canvas unifié", - "parametersSet": "Paramètres définis", "parametersNotSet": "Paramètres non définis", "metadataLoadFailed": "Échec du chargement des métadonnées" }, diff --git a/invokeai/frontend/web/public/locales/he.json b/invokeai/frontend/web/public/locales/he.json index efb90f61c7..dbbb3cbec4 100644 --- a/invokeai/frontend/web/public/locales/he.json +++ b/invokeai/frontend/web/public/locales/he.json @@ -306,7 +306,6 @@ "canvasMerged": "קנבס מוזג", "sentToImageToImage": "נשלח לתמונה לתמונה", "sentToUnifiedCanvas": "נשלח אל קנבס מאוחד", - "parametersSet": "הגדרת פרמטרים", "parametersNotSet": "פרמטרים לא הוגדרו", "metadataLoadFailed": "טעינת מטא-נתונים נכשלה" }, diff --git a/invokeai/frontend/web/public/locales/it.json b/invokeai/frontend/web/public/locales/it.json index 2b211484aa..ff4e44c487 100644 --- a/invokeai/frontend/web/public/locales/it.json +++ b/invokeai/frontend/web/public/locales/it.json @@ -569,7 +569,6 @@ "canvasMerged": "Tela unita", "sentToImageToImage": "Inviato a Immagine a Immagine", "sentToUnifiedCanvas": "Inviato a Tela Unificata", - "parametersSet": "Parametri impostati", "parametersNotSet": "Parametri non impostati", "metadataLoadFailed": "Impossibile caricare i metadati", "serverError": "Errore del Server", diff --git a/invokeai/frontend/web/public/locales/nl.json b/invokeai/frontend/web/public/locales/nl.json index 8fd8c96ee4..70adbb371d 100644 --- a/invokeai/frontend/web/public/locales/nl.json +++ b/invokeai/frontend/web/public/locales/nl.json @@ -420,7 +420,6 @@ "canvasMerged": "Canvas samengevoegd", "sentToImageToImage": "Gestuurd naar Afbeelding naar afbeelding", "sentToUnifiedCanvas": "Gestuurd naar Centraal canvas", - "parametersSet": "Parameters ingesteld", "parametersNotSet": "Parameters niet ingesteld", "metadataLoadFailed": "Fout bij laden metagegevens", "serverError": "Serverfout", diff --git a/invokeai/frontend/web/public/locales/pl.json b/invokeai/frontend/web/public/locales/pl.json index 399417db58..b7592c3fae 100644 --- a/invokeai/frontend/web/public/locales/pl.json +++ b/invokeai/frontend/web/public/locales/pl.json @@ -267,7 +267,6 @@ "canvasMerged": "Scalono widoczne warstwy", "sentToImageToImage": "Wysłano do Obraz na obraz", "sentToUnifiedCanvas": "Wysłano do trybu uniwersalnego", - "parametersSet": "Ustawiono parametry", "parametersNotSet": "Nie ustawiono parametrów", "metadataLoadFailed": "Błąd wczytywania metadanych" }, diff --git a/invokeai/frontend/web/public/locales/pt.json b/invokeai/frontend/web/public/locales/pt.json index 34f99b7075..3003a1732b 100644 --- a/invokeai/frontend/web/public/locales/pt.json +++ b/invokeai/frontend/web/public/locales/pt.json @@ -310,7 +310,6 @@ "canvasMerged": "Tela Fundida", "sentToImageToImage": "Mandar Para Imagem Para Imagem", "sentToUnifiedCanvas": "Enviada para a Tela Unificada", - "parametersSet": "Parâmetros Definidos", "parametersNotSet": "Parâmetros Não Definidos", "metadataLoadFailed": "Falha ao tentar carregar metadados" }, diff --git a/invokeai/frontend/web/public/locales/pt_BR.json b/invokeai/frontend/web/public/locales/pt_BR.json index 2859eb31db..c966c6db50 100644 --- a/invokeai/frontend/web/public/locales/pt_BR.json +++ b/invokeai/frontend/web/public/locales/pt_BR.json @@ -307,7 +307,6 @@ "canvasMerged": "Tela Fundida", "sentToImageToImage": "Mandar Para Imagem Para Imagem", "sentToUnifiedCanvas": "Enviada para a Tela Unificada", - "parametersSet": "Parâmetros Definidos", "parametersNotSet": "Parâmetros Não Definidos", "metadataLoadFailed": "Falha ao tentar carregar metadados" }, diff --git a/invokeai/frontend/web/public/locales/ru.json b/invokeai/frontend/web/public/locales/ru.json index 258bceeb05..4dd2ad895a 100644 --- a/invokeai/frontend/web/public/locales/ru.json +++ b/invokeai/frontend/web/public/locales/ru.json @@ -575,7 +575,6 @@ "canvasMerged": "Холст объединен", "sentToImageToImage": "Отправить в img2img", "sentToUnifiedCanvas": "Отправлено на Единый холст", - "parametersSet": "Параметры заданы", "parametersNotSet": "Параметры не заданы", "metadataLoadFailed": "Не удалось загрузить метаданные", "serverError": "Ошибка сервера", diff --git a/invokeai/frontend/web/public/locales/uk.json b/invokeai/frontend/web/public/locales/uk.json index f97909525c..9bb38c21b3 100644 --- a/invokeai/frontend/web/public/locales/uk.json +++ b/invokeai/frontend/web/public/locales/uk.json @@ -315,7 +315,6 @@ "canvasMerged": "Полотно об'єднане", "sentToImageToImage": "Надіслати до img2img", "sentToUnifiedCanvas": "Надіслати на полотно", - "parametersSet": "Параметри задані", "parametersNotSet": "Параметри не задані", "metadataLoadFailed": "Не вдалося завантажити метадані", "serverError": "Помилка сервера", diff --git a/invokeai/frontend/web/public/locales/zh_CN.json b/invokeai/frontend/web/public/locales/zh_CN.json index 77a06ea77b..a88f540990 100644 --- a/invokeai/frontend/web/public/locales/zh_CN.json +++ b/invokeai/frontend/web/public/locales/zh_CN.json @@ -487,7 +487,6 @@ "canvasMerged": "画布已合并", "sentToImageToImage": "已发送到图生图", "sentToUnifiedCanvas": "已发送到统一画布", - "parametersSet": "参数已设定", "parametersNotSet": "参数未设定", "metadataLoadFailed": "加载元数据失败", "uploadFailedInvalidUploadDesc": "必须是单张的 PNG 或 JPEG 图片", From 29cfe5a2745c233a6557256271688360995905ca Mon Sep 17 00:00:00 2001 From: psychedelicious <4822129+psychedelicious@users.noreply.github.com> Date: Sat, 6 Apr 2024 15:30:30 +1100 Subject: [PATCH 88/89] fix(ui): handle multipleOf on number fields This data is already in the template but it wasn't ever used. One big place where this improves UX is the noise node. Previously, the UI let you change width and height in increments of 1, despite the template requiring a multiple of 8. It now works in multiples of 8. --- .../inputs/NumberFieldInputComponent.tsx | 36 +++++++++++++------ 1 file changed, 26 insertions(+), 10 deletions(-) diff --git a/invokeai/frontend/web/src/features/nodes/components/flow/nodes/Invocation/fields/inputs/NumberFieldInputComponent.tsx b/invokeai/frontend/web/src/features/nodes/components/flow/nodes/Invocation/fields/inputs/NumberFieldInputComponent.tsx index 0cb250bb22..e3f33d8a45 100644 --- a/invokeai/frontend/web/src/features/nodes/components/flow/nodes/Invocation/fields/inputs/NumberFieldInputComponent.tsx +++ b/invokeai/frontend/web/src/features/nodes/components/flow/nodes/Invocation/fields/inputs/NumberFieldInputComponent.tsx @@ -37,34 +37,50 @@ const NumberFieldInputComponent = ( ); const min = useMemo(() => { + let min = -NUMPY_RAND_MAX; if (!isNil(fieldTemplate.minimum)) { - return fieldTemplate.minimum; + min = fieldTemplate.minimum; } if (!isNil(fieldTemplate.exclusiveMinimum)) { - return fieldTemplate.exclusiveMinimum + 0.01; + min = fieldTemplate.exclusiveMinimum + 0.01; } - return; + return min; }, [fieldTemplate.exclusiveMinimum, fieldTemplate.minimum]); const max = useMemo(() => { + let max = NUMPY_RAND_MAX; if (!isNil(fieldTemplate.maximum)) { - return fieldTemplate.maximum; + max = fieldTemplate.maximum; } if (!isNil(fieldTemplate.exclusiveMaximum)) { - return fieldTemplate.exclusiveMaximum - 0.01; + max = fieldTemplate.exclusiveMaximum - 0.01; } - return; + return max; }, [fieldTemplate.exclusiveMaximum, fieldTemplate.maximum]); + const step = useMemo(() => { + if (isNil(fieldTemplate.multipleOf)) { + return isIntegerField ? 1 : 0.1; + } + return fieldTemplate.multipleOf; + }, [fieldTemplate.multipleOf, isIntegerField]); + + const fineStep = useMemo(() => { + if (isNil(fieldTemplate.multipleOf)) { + return isIntegerField ? 1 : 0.01; + } + return fieldTemplate.multipleOf; + }, [fieldTemplate.multipleOf, isIntegerField]); + return ( ); From 9ab66554914cea5c9cc5879d5252c3e95724ae44 Mon Sep 17 00:00:00 2001 From: psychedelicious <4822129+psychedelicious@users.noreply.github.com> Date: Sun, 7 Apr 2024 14:28:29 +1000 Subject: [PATCH 89/89] feat(backend): clean up choose_precision - Allow user-defined precision on MPS. - Use more explicit logic to handle all possible cases. - Add comments. - Remove the app_config args (they were effectively unused, just get the config using the singleton getter util) --- .../model_manager/load/load_default.py | 2 +- invokeai/backend/util/devices.py | 46 +++++++++---------- 2 files changed, 23 insertions(+), 25 deletions(-) diff --git a/invokeai/backend/model_manager/load/load_default.py b/invokeai/backend/model_manager/load/load_default.py index 60cc1f5e6c..6774fc2989 100644 --- a/invokeai/backend/model_manager/load/load_default.py +++ b/invokeai/backend/model_manager/load/load_default.py @@ -37,7 +37,7 @@ class ModelLoader(ModelLoaderBase): self._logger = logger self._ram_cache = ram_cache self._convert_cache = convert_cache - self._torch_dtype = torch_dtype(choose_torch_device(), app_config) + self._torch_dtype = torch_dtype(choose_torch_device()) def load_model(self, model_config: AnyModelConfig, submodel_type: Optional[SubModelType] = None) -> LoadedModel: """ diff --git a/invokeai/backend/util/devices.py b/invokeai/backend/util/devices.py index 0be53c842a..cb6b93eaac 100644 --- a/invokeai/backend/util/devices.py +++ b/invokeai/backend/util/devices.py @@ -6,8 +6,7 @@ from typing import Literal, Optional, Union import torch from torch import autocast -from invokeai.app.services.config import InvokeAIAppConfig -from invokeai.app.services.config.config_default import get_config +from invokeai.app.services.config.config_default import PRECISION, get_config CPU_DEVICE = torch.device("cpu") CUDA_DEVICE = torch.device("cuda") @@ -33,35 +32,34 @@ def get_torch_device_name() -> str: return torch.cuda.get_device_name(device) if device.type == "cuda" else device.type.upper() -# We are in transition here from using a single global AppConfig to allowing multiple -# configurations. It is strongly recommended to pass the app_config to this function. -def choose_precision( - device: torch.device, app_config: Optional[InvokeAIAppConfig] = None -) -> Literal["float32", "float16", "bfloat16"]: +def choose_precision(device: torch.device) -> Literal["float32", "float16", "bfloat16"]: """Return an appropriate precision for the given torch device.""" - app_config = app_config or get_config() + app_config = get_config() if device.type == "cuda": device_name = torch.cuda.get_device_name(device) - if not ("GeForce GTX 1660" in device_name or "GeForce GTX 1650" in device_name): - if app_config.precision == "float32": - return "float32" - elif app_config.precision == "bfloat16": - return "bfloat16" - else: - return "float16" + if "GeForce GTX 1660" in device_name or "GeForce GTX 1650" in device_name: + # These GPUs have limited support for float16 + return "float32" + elif app_config.precision == "auto" or app_config.precision == "autocast": + # Default to float16 for CUDA devices + return "float16" + else: + # Use the user-defined precision + return app_config.precision elif device.type == "mps": - return "float16" + if app_config.precision == "auto" or app_config.precision == "autocast": + # Default to float16 for MPS devices + return "float16" + else: + # Use the user-defined precision + return app_config.precision + # CPU / safe fallback return "float32" -# We are in transition here from using a single global AppConfig to allowing multiple -# configurations. It is strongly recommended to pass the app_config to this function. -def torch_dtype( - device: Optional[torch.device] = None, - app_config: Optional[InvokeAIAppConfig] = None, -) -> torch.dtype: +def torch_dtype(device: Optional[torch.device] = None) -> torch.dtype: device = device or choose_torch_device() - precision = choose_precision(device, app_config) + precision = choose_precision(device) if precision == "float16": return torch.float16 if precision == "bfloat16": @@ -71,7 +69,7 @@ def torch_dtype( return torch.float32 -def choose_autocast(precision): +def choose_autocast(precision: PRECISION): """Returns an autocast context or nullcontext for the given precision string""" # float16 currently requires autocast to avoid errors like: # 'expected scalar type Half but found Float'