mirror of
https://github.com/invoke-ai/InvokeAI
synced 2024-08-30 20:32:17 +00:00
Compare commits
46 Commits
psyche/fea
...
lstein/doc
Author | SHA1 | Date | |
---|---|---|---|
be48323a06 | |||
8ecf72838d | |||
c3ab8a6aa8 | |||
1931aa3e70 | |||
d3d8055055 | |||
476b0a0403 | |||
f66584713c | |||
33624fc2fa | |||
09d1e190e7 | |||
17ff8196cb | |||
68f993998a | |||
7da6120b39 | |||
6cd40965c4 | |||
408a1d6dbb | |||
140670d00e | |||
70233fae5d | |||
6f457a6c4c | |||
5c319f5356 | |||
991a04f090 | |||
c39fa75113 | |||
f7863e17ce | |||
7c526390ed | |||
2cff20f87a | |||
90ec757802 | |||
4b85dfcefe | |||
21deefdc41 | |||
4d4f921a4e | |||
98db8f395b | |||
f465a956a3 | |||
9edb02d7ef | |||
6c4cf58a31 | |||
08993c0d29 | |||
4f8a4b0f22 | |||
a743f3c9b5 | |||
332bc9da5b | |||
08def3da95 | |||
daf899f9c4 | |||
13fb2d1f49 | |||
95dde802ea | |||
b4cf78a95d | |||
18f89ed5ed | |||
f170697ebe | |||
556c6a1d84 | |||
e5d9ca013e | |||
4166c756ce | |||
4f0dfbd34d |
@ -196,6 +196,22 @@ tips to reduce the problem:
|
||||
=== "12GB VRAM GPU"
|
||||
|
||||
This should be sufficient to generate larger images up to about 1280x1280.
|
||||
|
||||
## Checkpoint Models Load Slowly or Use Too Much RAM
|
||||
|
||||
The difference between diffusers models (a folder containing multiple
|
||||
subfolders) and checkpoint models (a file ending with .safetensors or
|
||||
.ckpt) is that InvokeAI is able to load diffusers models into memory
|
||||
incrementally, while checkpoint models must be loaded all at
|
||||
once. With very large models, or systems with limited RAM, you may
|
||||
experience slowdowns and other memory-related issues when loading
|
||||
checkpoint models.
|
||||
|
||||
To solve this, go to the Model Manager tab (the cube), select the
|
||||
checkpoint model that's giving you trouble, and press the "Convert"
|
||||
button in the upper right of your browser window. This will conver the
|
||||
checkpoint into a diffusers model, after which loading should be
|
||||
faster and less memory-intensive.
|
||||
|
||||
## Memory Leak (Linux)
|
||||
|
||||
|
@ -218,9 +218,8 @@ async def get_image_workflow(
|
||||
raise HTTPException(status_code=404)
|
||||
|
||||
|
||||
@images_router.api_route(
|
||||
@images_router.get(
|
||||
"/i/{image_name}/full",
|
||||
methods=["GET", "HEAD"],
|
||||
operation_id="get_image_full",
|
||||
response_class=Response,
|
||||
responses={
|
||||
@ -231,6 +230,18 @@ async def get_image_workflow(
|
||||
404: {"description": "Image not found"},
|
||||
},
|
||||
)
|
||||
@images_router.head(
|
||||
"/i/{image_name}/full",
|
||||
operation_id="get_image_full_head",
|
||||
response_class=Response,
|
||||
responses={
|
||||
200: {
|
||||
"description": "Return the full-resolution image",
|
||||
"content": {"image/png": {}},
|
||||
},
|
||||
404: {"description": "Image not found"},
|
||||
},
|
||||
)
|
||||
async def get_image_full(
|
||||
image_name: str = Path(description="The name of full-resolution image file to get"),
|
||||
) -> Response:
|
||||
@ -242,6 +253,7 @@ async def get_image_full(
|
||||
content = f.read()
|
||||
response = Response(content, media_type="image/png")
|
||||
response.headers["Cache-Control"] = f"max-age={IMAGE_MAX_AGE}"
|
||||
response.headers["Content-Disposition"] = f'inline; filename="{image_name}"'
|
||||
return response
|
||||
except Exception:
|
||||
raise HTTPException(status_code=404)
|
||||
|
@ -20,8 +20,8 @@ from invokeai.app.services.events.events_common import (
|
||||
DownloadStartedEvent,
|
||||
FastAPIEvent,
|
||||
InvocationCompleteEvent,
|
||||
InvocationDenoiseProgressEvent,
|
||||
InvocationErrorEvent,
|
||||
InvocationProgressEvent,
|
||||
InvocationStartedEvent,
|
||||
ModelEventBase,
|
||||
ModelInstallCancelledEvent,
|
||||
@ -55,7 +55,7 @@ class BulkDownloadSubscriptionEvent(BaseModel):
|
||||
|
||||
QUEUE_EVENTS = {
|
||||
InvocationStartedEvent,
|
||||
InvocationProgressEvent,
|
||||
InvocationDenoiseProgressEvent,
|
||||
InvocationCompleteEvent,
|
||||
InvocationErrorEvent,
|
||||
QueueItemStatusChangedEvent,
|
||||
|
@ -21,6 +21,8 @@ from controlnet_aux import (
|
||||
from controlnet_aux.util import HWC3, ade_palette
|
||||
from PIL import Image
|
||||
from pydantic import BaseModel, Field, field_validator, model_validator
|
||||
from transformers import pipeline
|
||||
from transformers.pipelines import DepthEstimationPipeline
|
||||
|
||||
from invokeai.app.invocations.baseinvocation import (
|
||||
BaseInvocation,
|
||||
@ -44,13 +46,12 @@ from invokeai.app.invocations.util import validate_begin_end_step, validate_weig
|
||||
from invokeai.app.services.shared.invocation_context import InvocationContext
|
||||
from invokeai.app.util.controlnet_utils import CONTROLNET_MODE_VALUES, CONTROLNET_RESIZE_VALUES, heuristic_resize
|
||||
from invokeai.backend.image_util.canny import get_canny_edges
|
||||
from invokeai.backend.image_util.depth_anything import DEPTH_ANYTHING_MODELS, DepthAnythingDetector
|
||||
from invokeai.backend.image_util.depth_anything.depth_anything_pipeline import DepthAnythingPipeline
|
||||
from invokeai.backend.image_util.dw_openpose import DWPOSE_MODELS, DWOpenposeDetector
|
||||
from invokeai.backend.image_util.hed import HEDProcessor
|
||||
from invokeai.backend.image_util.lineart import LineartProcessor
|
||||
from invokeai.backend.image_util.lineart_anime import LineartAnimeProcessor
|
||||
from invokeai.backend.image_util.util import np_to_pil, pil_to_np
|
||||
from invokeai.backend.util.devices import TorchDevice
|
||||
|
||||
|
||||
class ControlField(BaseModel):
|
||||
@ -592,7 +593,14 @@ class ColorMapImageProcessorInvocation(ImageProcessorInvocation):
|
||||
return color_map
|
||||
|
||||
|
||||
DEPTH_ANYTHING_MODEL_SIZES = Literal["large", "base", "small"]
|
||||
DEPTH_ANYTHING_MODEL_SIZES = Literal["large", "base", "small", "small_v2"]
|
||||
# DepthAnything V2 Small model is licensed under Apache 2.0 but not the base and large models.
|
||||
DEPTH_ANYTHING_MODELS = {
|
||||
"large": "LiheYoung/depth-anything-large-hf",
|
||||
"base": "LiheYoung/depth-anything-base-hf",
|
||||
"small": "LiheYoung/depth-anything-small-hf",
|
||||
"small_v2": "depth-anything/Depth-Anything-V2-Small-hf",
|
||||
}
|
||||
|
||||
|
||||
@invocation(
|
||||
@ -600,28 +608,33 @@ DEPTH_ANYTHING_MODEL_SIZES = Literal["large", "base", "small"]
|
||||
title="Depth Anything Processor",
|
||||
tags=["controlnet", "depth", "depth anything"],
|
||||
category="controlnet",
|
||||
version="1.1.2",
|
||||
version="1.1.3",
|
||||
)
|
||||
class DepthAnythingImageProcessorInvocation(ImageProcessorInvocation):
|
||||
"""Generates a depth map based on the Depth Anything algorithm"""
|
||||
|
||||
model_size: DEPTH_ANYTHING_MODEL_SIZES = InputField(
|
||||
default="small", description="The size of the depth model to use"
|
||||
default="small_v2", description="The size of the depth model to use"
|
||||
)
|
||||
resolution: int = InputField(default=512, ge=1, description=FieldDescriptions.image_res)
|
||||
|
||||
def run_processor(self, image: Image.Image) -> Image.Image:
|
||||
def loader(model_path: Path):
|
||||
return DepthAnythingDetector.load_model(
|
||||
model_path, model_size=self.model_size, device=TorchDevice.choose_torch_device()
|
||||
)
|
||||
def load_depth_anything(model_path: Path):
|
||||
depth_anything_pipeline = pipeline(model=str(model_path), task="depth-estimation", local_files_only=True)
|
||||
assert isinstance(depth_anything_pipeline, DepthEstimationPipeline)
|
||||
return DepthAnythingPipeline(depth_anything_pipeline)
|
||||
|
||||
with self._context.models.load_remote_model(
|
||||
source=DEPTH_ANYTHING_MODELS[self.model_size], loader=loader
|
||||
) as model:
|
||||
depth_anything_detector = DepthAnythingDetector(model, TorchDevice.choose_torch_device())
|
||||
processed_image = depth_anything_detector(image=image, resolution=self.resolution)
|
||||
return processed_image
|
||||
source=DEPTH_ANYTHING_MODELS[self.model_size], loader=load_depth_anything
|
||||
) as depth_anything_detector:
|
||||
assert isinstance(depth_anything_detector, DepthAnythingPipeline)
|
||||
depth_map = depth_anything_detector.generate_depth(image)
|
||||
|
||||
# Resizing to user target specified size
|
||||
new_height = int(image.size[1] * (self.resolution / image.size[0]))
|
||||
depth_map = depth_map.resize((self.resolution, new_height))
|
||||
|
||||
return depth_map
|
||||
|
||||
|
||||
@invocation(
|
||||
|
@ -1,4 +1,3 @@
|
||||
import functools
|
||||
from typing import Callable
|
||||
|
||||
import numpy as np
|
||||
@ -62,7 +61,6 @@ class SpandrelImageToImageInvocation(BaseInvocation, WithMetadata, WithBoard):
|
||||
tile_size: int,
|
||||
spandrel_model: SpandrelImageToImageModel,
|
||||
is_canceled: Callable[[], bool],
|
||||
step_callback: Callable[[int, int], None],
|
||||
) -> Image.Image:
|
||||
# Compute the image tiles.
|
||||
if tile_size > 0:
|
||||
@ -105,12 +103,7 @@ class SpandrelImageToImageInvocation(BaseInvocation, WithMetadata, WithBoard):
|
||||
image_tensor = image_tensor.to(device=spandrel_model.device, dtype=spandrel_model.dtype)
|
||||
|
||||
# Run the model on each tile.
|
||||
pbar = tqdm(list(zip(tiles, scaled_tiles, strict=True)), desc="Upscaling Tiles")
|
||||
|
||||
# Update progress, starting with 0.
|
||||
step_callback(0, pbar.total)
|
||||
|
||||
for tile, scaled_tile in pbar:
|
||||
for tile, scaled_tile in tqdm(list(zip(tiles, scaled_tiles, strict=True)), desc="Upscaling Tiles"):
|
||||
# Exit early if the invocation has been canceled.
|
||||
if is_canceled():
|
||||
raise CanceledException
|
||||
@ -143,8 +136,6 @@ class SpandrelImageToImageInvocation(BaseInvocation, WithMetadata, WithBoard):
|
||||
:,
|
||||
] = output_tile[top_overlap:, left_overlap:, :]
|
||||
|
||||
step_callback(pbar.n + 1, pbar.total)
|
||||
|
||||
# Convert the output tensor to a PIL image.
|
||||
np_image = output_tensor.detach().numpy().astype(np.uint8)
|
||||
pil_image = Image.fromarray(np_image)
|
||||
@ -160,20 +151,12 @@ class SpandrelImageToImageInvocation(BaseInvocation, WithMetadata, WithBoard):
|
||||
# Load the model.
|
||||
spandrel_model_info = context.models.load(self.image_to_image_model)
|
||||
|
||||
def step_callback(step: int, total_steps: int) -> None:
|
||||
context.util.signal_progress(
|
||||
message=f"Processing image (tile {step}/{total_steps})",
|
||||
percentage=step / total_steps,
|
||||
)
|
||||
|
||||
# Do the upscaling.
|
||||
with spandrel_model_info as spandrel_model:
|
||||
assert isinstance(spandrel_model, SpandrelImageToImageModel)
|
||||
|
||||
# Upscale the image
|
||||
pil_image = self.upscale_image(
|
||||
image, self.tile_size, spandrel_model, context.util.is_canceled, step_callback
|
||||
)
|
||||
pil_image = self.upscale_image(image, self.tile_size, spandrel_model, context.util.is_canceled)
|
||||
|
||||
image_dto = context.images.save(image=pil_image)
|
||||
return ImageOutput.build(image_dto)
|
||||
@ -214,27 +197,12 @@ class SpandrelImageToImageAutoscaleInvocation(SpandrelImageToImageInvocation):
|
||||
target_width = int(image.width * self.scale)
|
||||
target_height = int(image.height * self.scale)
|
||||
|
||||
def step_callback(iteration: int, step: int, total_steps: int) -> None:
|
||||
context.util.signal_progress(
|
||||
message=self._get_progress_message(iteration, step, total_steps),
|
||||
percentage=step / total_steps,
|
||||
)
|
||||
|
||||
# Do the upscaling.
|
||||
with spandrel_model_info as spandrel_model:
|
||||
assert isinstance(spandrel_model, SpandrelImageToImageModel)
|
||||
|
||||
iteration = 1
|
||||
context.util.signal_progress(self._get_progress_message(iteration))
|
||||
|
||||
# First pass of upscaling. Note: `pil_image` will be mutated.
|
||||
pil_image = self.upscale_image(
|
||||
image,
|
||||
self.tile_size,
|
||||
spandrel_model,
|
||||
context.util.is_canceled,
|
||||
functools.partial(step_callback, iteration),
|
||||
)
|
||||
pil_image = self.upscale_image(image, self.tile_size, spandrel_model, context.util.is_canceled)
|
||||
|
||||
# Some models don't upscale the image, but we have no way to know this in advance. We'll check if the model
|
||||
# upscaled the image and run the loop below if it did. We'll require the model to upscale both dimensions
|
||||
@ -243,22 +211,16 @@ class SpandrelImageToImageAutoscaleInvocation(SpandrelImageToImageInvocation):
|
||||
|
||||
if is_upscale_model:
|
||||
# This is an upscale model, so we should keep upscaling until we reach the target size.
|
||||
iterations = 1
|
||||
while pil_image.width < target_width or pil_image.height < target_height:
|
||||
iteration += 1
|
||||
context.util.signal_progress(self._get_progress_message(iteration))
|
||||
pil_image = self.upscale_image(
|
||||
pil_image,
|
||||
self.tile_size,
|
||||
spandrel_model,
|
||||
context.util.is_canceled,
|
||||
functools.partial(step_callback, iteration),
|
||||
)
|
||||
pil_image = self.upscale_image(pil_image, self.tile_size, spandrel_model, context.util.is_canceled)
|
||||
iterations += 1
|
||||
|
||||
# Sanity check to prevent excessive or infinite loops. All known upscaling models are at least 2x.
|
||||
# Our max scale is 16x, so with a 2x model, we should never exceed 16x == 2^4 -> 4 iterations.
|
||||
# We'll allow one extra iteration "just in case" and bail at 5 upscaling iterations. In practice,
|
||||
# we should never reach this limit.
|
||||
if iteration >= 5:
|
||||
if iterations >= 5:
|
||||
context.logger.warning(
|
||||
"Upscale loop reached maximum iteration count of 5, stopping upscaling early."
|
||||
)
|
||||
@ -289,10 +251,3 @@ class SpandrelImageToImageAutoscaleInvocation(SpandrelImageToImageInvocation):
|
||||
|
||||
image_dto = context.images.save(image=pil_image)
|
||||
return ImageOutput.build(image_dto)
|
||||
|
||||
@classmethod
|
||||
def _get_progress_message(cls, iteration: int, step: int | None = None, total_steps: int | None = None) -> str:
|
||||
if step is not None and total_steps is not None:
|
||||
return f"Processing image (iteration {iteration}, tile {step}/{total_steps})"
|
||||
|
||||
return f"Processing image (iteration {iteration})"
|
||||
|
@ -15,8 +15,8 @@ from invokeai.app.services.events.events_common import (
|
||||
DownloadStartedEvent,
|
||||
EventBase,
|
||||
InvocationCompleteEvent,
|
||||
InvocationDenoiseProgressEvent,
|
||||
InvocationErrorEvent,
|
||||
InvocationProgressEvent,
|
||||
InvocationStartedEvent,
|
||||
ModelInstallCancelledEvent,
|
||||
ModelInstallCompleteEvent,
|
||||
@ -30,12 +30,13 @@ from invokeai.app.services.events.events_common import (
|
||||
QueueClearedEvent,
|
||||
QueueItemStatusChangedEvent,
|
||||
)
|
||||
from invokeai.app.services.session_processor.session_processor_common import ProgressImage
|
||||
from invokeai.backend.stable_diffusion.diffusers_pipeline import PipelineIntermediateState
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from invokeai.app.invocations.baseinvocation import BaseInvocation, BaseInvocationOutput
|
||||
from invokeai.app.services.download.download_base import DownloadJob
|
||||
from invokeai.app.services.model_install.model_install_common import ModelInstallJob
|
||||
from invokeai.app.services.session_processor.session_processor_common import ProgressImage
|
||||
from invokeai.app.services.session_queue.session_queue_common import (
|
||||
BatchStatus,
|
||||
EnqueueBatchResult,
|
||||
@ -57,16 +58,15 @@ class EventServiceBase:
|
||||
"""Emitted when an invocation is started"""
|
||||
self.dispatch(InvocationStartedEvent.build(queue_item, invocation))
|
||||
|
||||
def emit_invocation_progress(
|
||||
def emit_invocation_denoise_progress(
|
||||
self,
|
||||
queue_item: "SessionQueueItem",
|
||||
invocation: "BaseInvocation",
|
||||
message: str,
|
||||
percentage: float | None = None,
|
||||
image: ProgressImage | None = None,
|
||||
intermediate_state: PipelineIntermediateState,
|
||||
progress_image: "ProgressImage",
|
||||
) -> None:
|
||||
"""Emitted at each step during an invocation"""
|
||||
self.dispatch(InvocationProgressEvent.build(queue_item, invocation, message, percentage, image))
|
||||
"""Emitted at each step during denoising of an invocation."""
|
||||
self.dispatch(InvocationDenoiseProgressEvent.build(queue_item, invocation, intermediate_state, progress_image))
|
||||
|
||||
def emit_invocation_complete(
|
||||
self, queue_item: "SessionQueueItem", invocation: "BaseInvocation", output: "BaseInvocationOutput"
|
||||
|
@ -1,3 +1,4 @@
|
||||
from math import floor
|
||||
from typing import TYPE_CHECKING, Any, ClassVar, Coroutine, Generic, Optional, Protocol, TypeAlias, TypeVar
|
||||
|
||||
from fastapi_events.handlers.local import local_handler
|
||||
@ -15,6 +16,7 @@ from invokeai.app.services.session_queue.session_queue_common import (
|
||||
from invokeai.app.services.shared.graph import AnyInvocation, AnyInvocationOutput
|
||||
from invokeai.app.util.misc import get_timestamp
|
||||
from invokeai.backend.model_manager.config import AnyModelConfig, SubModelType
|
||||
from invokeai.backend.stable_diffusion.diffusers_pipeline import PipelineIntermediateState
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from invokeai.app.services.download.download_base import DownloadJob
|
||||
@ -119,28 +121,28 @@ class InvocationStartedEvent(InvocationEventBase):
|
||||
|
||||
|
||||
@payload_schema.register
|
||||
class InvocationProgressEvent(InvocationEventBase):
|
||||
"""Event model for invocation_progress"""
|
||||
class InvocationDenoiseProgressEvent(InvocationEventBase):
|
||||
"""Event model for invocation_denoise_progress"""
|
||||
|
||||
__event_name__ = "invocation_progress"
|
||||
__event_name__ = "invocation_denoise_progress"
|
||||
|
||||
message: str = Field(description="A message to display")
|
||||
percentage: float | None = Field(
|
||||
default=None, ge=0, le=1, description="The percentage of the progress (omit to indicate indeterminate progress)"
|
||||
)
|
||||
image: ProgressImage | None = Field(
|
||||
default=None, description="An image representing the current state of the progress"
|
||||
)
|
||||
progress_image: ProgressImage = Field(description="The progress image sent at each step during processing")
|
||||
step: int = Field(description="The current step of the invocation")
|
||||
total_steps: int = Field(description="The total number of steps in the invocation")
|
||||
order: int = Field(description="The order of the invocation in the session")
|
||||
percentage: float = Field(description="The percentage of completion of the invocation")
|
||||
|
||||
@classmethod
|
||||
def build(
|
||||
cls,
|
||||
queue_item: SessionQueueItem,
|
||||
invocation: AnyInvocation,
|
||||
message: str,
|
||||
percentage: float | None = None,
|
||||
image: ProgressImage | None = None,
|
||||
) -> "InvocationProgressEvent":
|
||||
intermediate_state: PipelineIntermediateState,
|
||||
progress_image: ProgressImage,
|
||||
) -> "InvocationDenoiseProgressEvent":
|
||||
step = intermediate_state.step
|
||||
total_steps = intermediate_state.total_steps
|
||||
order = intermediate_state.order
|
||||
return cls(
|
||||
queue_id=queue_item.queue_id,
|
||||
item_id=queue_item.item_id,
|
||||
@ -148,11 +150,23 @@ class InvocationProgressEvent(InvocationEventBase):
|
||||
session_id=queue_item.session_id,
|
||||
invocation=invocation,
|
||||
invocation_source_id=queue_item.session.prepared_source_mapping[invocation.id],
|
||||
percentage=percentage,
|
||||
image=image,
|
||||
message=message,
|
||||
progress_image=progress_image,
|
||||
step=step,
|
||||
total_steps=total_steps,
|
||||
order=order,
|
||||
percentage=cls.calc_percentage(step, total_steps, order),
|
||||
)
|
||||
|
||||
@staticmethod
|
||||
def calc_percentage(step: int, total_steps: int, scheduler_order: float) -> float:
|
||||
"""Calculate the percentage of completion of denoising."""
|
||||
if total_steps == 0:
|
||||
return 0.0
|
||||
if scheduler_order == 2:
|
||||
return floor((step + 1 + 1) / 2) / floor((total_steps + 1) / 2)
|
||||
# order == 1
|
||||
return (step + 1 + 1) / (total_steps + 1)
|
||||
|
||||
|
||||
@payload_schema.register
|
||||
class InvocationCompleteEvent(InvocationEventBase):
|
||||
|
@ -1,11 +1,10 @@
|
||||
# Copyright (c) 2022 Kyle Schouviller (https://github.com/kyle0654) and the InvokeAI Team
|
||||
from pathlib import Path
|
||||
from queue import Queue
|
||||
from typing import Dict, Optional, Union
|
||||
from typing import Optional, Union
|
||||
|
||||
from PIL import Image, PngImagePlugin
|
||||
from PIL.Image import Image as PILImageType
|
||||
from send2trash import send2trash
|
||||
|
||||
from invokeai.app.services.image_files.image_files_base import ImageFileStorageBase
|
||||
from invokeai.app.services.image_files.image_files_common import (
|
||||
@ -20,18 +19,12 @@ from invokeai.app.util.thumbnails import get_thumbnail_name, make_thumbnail
|
||||
class DiskImageFileStorage(ImageFileStorageBase):
|
||||
"""Stores images on disk"""
|
||||
|
||||
__output_folder: Path
|
||||
__cache_ids: Queue # TODO: this is an incredibly naive cache
|
||||
__cache: Dict[Path, PILImageType]
|
||||
__max_cache_size: int
|
||||
__invoker: Invoker
|
||||
|
||||
def __init__(self, output_folder: Union[str, Path]):
|
||||
self.__cache = {}
|
||||
self.__cache_ids = Queue()
|
||||
self.__cache: dict[Path, PILImageType] = {}
|
||||
self.__cache_ids = Queue[Path]()
|
||||
self.__max_cache_size = 10 # TODO: get this from config
|
||||
|
||||
self.__output_folder: Path = output_folder if isinstance(output_folder, Path) else Path(output_folder)
|
||||
self.__output_folder = output_folder if isinstance(output_folder, Path) else Path(output_folder)
|
||||
self.__thumbnails_folder = self.__output_folder / "thumbnails"
|
||||
# Validate required output folders at launch
|
||||
self.__validate_storage_folders()
|
||||
@ -103,7 +96,7 @@ class DiskImageFileStorage(ImageFileStorageBase):
|
||||
image_path = self.get_path(image_name)
|
||||
|
||||
if image_path.exists():
|
||||
send2trash(image_path)
|
||||
image_path.unlink()
|
||||
if image_path in self.__cache:
|
||||
del self.__cache[image_path]
|
||||
|
||||
@ -111,7 +104,7 @@ class DiskImageFileStorage(ImageFileStorageBase):
|
||||
thumbnail_path = self.get_path(thumbnail_name, True)
|
||||
|
||||
if thumbnail_path.exists():
|
||||
send2trash(thumbnail_path)
|
||||
thumbnail_path.unlink()
|
||||
if thumbnail_path in self.__cache:
|
||||
del self.__cache[thumbnail_path]
|
||||
except Exception as e:
|
||||
|
@ -2,7 +2,6 @@ from pathlib import Path
|
||||
|
||||
from PIL import Image
|
||||
from PIL.Image import Image as PILImageType
|
||||
from send2trash import send2trash
|
||||
|
||||
from invokeai.app.services.invoker import Invoker
|
||||
from invokeai.app.services.model_images.model_images_base import ModelImageFileStorageBase
|
||||
@ -70,7 +69,7 @@ class ModelImageFileStorageDisk(ModelImageFileStorageBase):
|
||||
if not self._validate_path(path):
|
||||
raise ModelImageFileNotFoundException
|
||||
|
||||
send2trash(path)
|
||||
path.unlink()
|
||||
|
||||
except Exception as e:
|
||||
raise ModelImageFileDeleteException from e
|
||||
|
@ -1,8 +1,5 @@
|
||||
from PIL.Image import Image as PILImageType
|
||||
from pydantic import BaseModel, Field
|
||||
|
||||
from invokeai.backend.util.util import image_to_dataURL
|
||||
|
||||
|
||||
class SessionProcessorStatus(BaseModel):
|
||||
is_started: bool = Field(description="Whether the session processor is started")
|
||||
@ -18,16 +15,6 @@ class CanceledException(Exception):
|
||||
class ProgressImage(BaseModel):
|
||||
"""The progress image sent intermittently during processing"""
|
||||
|
||||
width: int = Field(ge=1, description="The effective width of the image in pixels")
|
||||
height: int = Field(ge=1, description="The effective height of the image in pixels")
|
||||
width: int = Field(description="The effective width of the image in pixels")
|
||||
height: int = Field(description="The effective height of the image in pixels")
|
||||
dataURL: str = Field(description="The image data as a b64 data URL")
|
||||
|
||||
@classmethod
|
||||
def build(cls, image: PILImageType, size: tuple[int, int] | None = None) -> "ProgressImage":
|
||||
"""Build a ProgressImage from a PIL image"""
|
||||
|
||||
return cls(
|
||||
width=size[0] if size else image.width,
|
||||
height=size[1] if size else image.height,
|
||||
dataURL=image_to_dataURL(image, image_format="JPEG"),
|
||||
)
|
||||
|
@ -14,7 +14,6 @@ from invokeai.app.services.image_records.image_records_common import ImageCatego
|
||||
from invokeai.app.services.images.images_common import ImageDTO
|
||||
from invokeai.app.services.invocation_services import InvocationServices
|
||||
from invokeai.app.services.model_records.model_records_base import UnknownModelException
|
||||
from invokeai.app.services.session_processor.session_processor_common import ProgressImage
|
||||
from invokeai.app.util.step_callback import stable_diffusion_step_callback
|
||||
from invokeai.backend.model_manager.config import (
|
||||
AnyModel,
|
||||
@ -551,64 +550,13 @@ class UtilInterface(InvocationContextInterface):
|
||||
"""
|
||||
|
||||
stable_diffusion_step_callback(
|
||||
signal_progress=self.signal_progress,
|
||||
context_data=self._data,
|
||||
intermediate_state=intermediate_state,
|
||||
base_model=base_model,
|
||||
events=self._services.events,
|
||||
is_canceled=self.is_canceled,
|
||||
)
|
||||
|
||||
def signal_progress(
|
||||
self, message: str, percentage: float | None = None, image: ProgressImage | None = None
|
||||
) -> None:
|
||||
"""Signals the progress of some long-running invocation. The progress is displayed in the UI.
|
||||
|
||||
If you have an image to display, use `ProgressImage.build` to create the object.
|
||||
|
||||
If your progress image should be displayed at a different size, provide a tuple of `(width, height)` when
|
||||
building the progress image.
|
||||
|
||||
For example, SD denoising progress images are 1/8 the size of the original image. In this case, the progress
|
||||
image should be built like this to ensure it displays at the correct size:
|
||||
```py
|
||||
progress_image = ProgressImage.build(image, (width * 8, height * 8))
|
||||
```
|
||||
|
||||
If your progress image is very large, consider downscaling it to reduce the payload size.
|
||||
|
||||
Example:
|
||||
```py
|
||||
total_steps = 10
|
||||
for i in range(total_steps):
|
||||
# Do some iterative progressing
|
||||
image = do_iterative_processing(image)
|
||||
|
||||
# Calculate the percentage
|
||||
step = i + 1
|
||||
percentage = step / total_steps
|
||||
|
||||
# Create a short, friendly message
|
||||
message = f"Processing (step {step}/{total_steps})"
|
||||
|
||||
# Build the progress image
|
||||
progress_image = ProgressImage.build(image)
|
||||
|
||||
# Send progress to the UI
|
||||
context.util.signal_progress(message, percentage, progress_image)
|
||||
```
|
||||
|
||||
Args:
|
||||
message: A message describing the current status.
|
||||
percentage: The current percentage completion for the process. Omit for indeterminate progress.
|
||||
image: An optional progress image to display.
|
||||
"""
|
||||
self._services.events.emit_invocation_progress(
|
||||
queue_item=self._data.queue_item,
|
||||
invocation=self._data.invocation,
|
||||
message=message,
|
||||
percentage=percentage,
|
||||
image=image,
|
||||
)
|
||||
|
||||
|
||||
class InvocationContext:
|
||||
"""Provides access to various services and data for the current invocation.
|
||||
|
@ -81,7 +81,7 @@ def get_openapi_func(
|
||||
# Add the output map to the schema
|
||||
openapi_schema["components"]["schemas"]["InvocationOutputMap"] = {
|
||||
"type": "object",
|
||||
"properties": invocation_output_map_properties,
|
||||
"properties": dict(sorted(invocation_output_map_properties.items())),
|
||||
"required": invocation_output_map_required,
|
||||
}
|
||||
|
||||
|
@ -1,5 +1,4 @@
|
||||
from math import floor
|
||||
from typing import Callable, Optional
|
||||
from typing import TYPE_CHECKING, Callable, Optional
|
||||
|
||||
import torch
|
||||
from PIL import Image
|
||||
@ -7,6 +6,11 @@ from PIL import Image
|
||||
from invokeai.app.services.session_processor.session_processor_common import CanceledException, ProgressImage
|
||||
from invokeai.backend.model_manager.config import BaseModelType
|
||||
from invokeai.backend.stable_diffusion.diffusers_pipeline import PipelineIntermediateState
|
||||
from invokeai.backend.util.util import image_to_dataURL
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from invokeai.app.services.events.events_base import EventServiceBase
|
||||
from invokeai.app.services.shared.invocation_context import InvocationContextData
|
||||
|
||||
# fast latents preview matrix for sdxl
|
||||
# generated by @StAlKeR7779
|
||||
@ -52,25 +56,11 @@ def sample_to_lowres_estimated_image(
|
||||
return Image.fromarray(latents_ubyte.numpy())
|
||||
|
||||
|
||||
def calc_percentage(intermediate_state: PipelineIntermediateState) -> float:
|
||||
"""Calculate the percentage of completion of denoising."""
|
||||
|
||||
step = intermediate_state.step
|
||||
total_steps = intermediate_state.total_steps
|
||||
order = intermediate_state.order
|
||||
|
||||
if total_steps == 0:
|
||||
return 0.0
|
||||
if order == 2:
|
||||
return floor((step + 1 + 1) / 2) / floor((total_steps + 1) / 2)
|
||||
# order == 1
|
||||
return (step + 1 + 1) / (total_steps + 1)
|
||||
|
||||
|
||||
def stable_diffusion_step_callback(
|
||||
signal_progress: Callable[[str, float | None, ProgressImage | None], None],
|
||||
context_data: "InvocationContextData",
|
||||
intermediate_state: PipelineIntermediateState,
|
||||
base_model: BaseModelType,
|
||||
events: "EventServiceBase",
|
||||
is_canceled: Callable[[], bool],
|
||||
) -> None:
|
||||
if is_canceled():
|
||||
@ -96,10 +86,11 @@ def stable_diffusion_step_callback(
|
||||
width *= 8
|
||||
height *= 8
|
||||
|
||||
percentage = calc_percentage(intermediate_state)
|
||||
dataURL = image_to_dataURL(image, image_format="JPEG")
|
||||
|
||||
signal_progress(
|
||||
"Denoising",
|
||||
percentage,
|
||||
ProgressImage.build(image=image, size=(width, height)),
|
||||
events.emit_invocation_denoise_progress(
|
||||
context_data.queue_item,
|
||||
context_data.invocation,
|
||||
intermediate_state,
|
||||
ProgressImage(dataURL=dataURL, width=width, height=height),
|
||||
)
|
||||
|
@ -1,90 +0,0 @@
|
||||
from pathlib import Path
|
||||
from typing import Literal
|
||||
|
||||
import cv2
|
||||
import numpy as np
|
||||
import torch
|
||||
import torch.nn.functional as F
|
||||
from einops import repeat
|
||||
from PIL import Image
|
||||
from torchvision.transforms import Compose
|
||||
|
||||
from invokeai.app.services.config.config_default import get_config
|
||||
from invokeai.backend.image_util.depth_anything.model.dpt import DPT_DINOv2
|
||||
from invokeai.backend.image_util.depth_anything.utilities.util import NormalizeImage, PrepareForNet, Resize
|
||||
from invokeai.backend.util.logging import InvokeAILogger
|
||||
|
||||
config = get_config()
|
||||
logger = InvokeAILogger.get_logger(config=config)
|
||||
|
||||
DEPTH_ANYTHING_MODELS = {
|
||||
"large": "https://huggingface.co/spaces/LiheYoung/Depth-Anything/resolve/main/checkpoints/depth_anything_vitl14.pth?download=true",
|
||||
"base": "https://huggingface.co/spaces/LiheYoung/Depth-Anything/resolve/main/checkpoints/depth_anything_vitb14.pth?download=true",
|
||||
"small": "https://huggingface.co/spaces/LiheYoung/Depth-Anything/resolve/main/checkpoints/depth_anything_vits14.pth?download=true",
|
||||
}
|
||||
|
||||
|
||||
transform = Compose(
|
||||
[
|
||||
Resize(
|
||||
width=518,
|
||||
height=518,
|
||||
resize_target=False,
|
||||
keep_aspect_ratio=True,
|
||||
ensure_multiple_of=14,
|
||||
resize_method="lower_bound",
|
||||
image_interpolation_method=cv2.INTER_CUBIC,
|
||||
),
|
||||
NormalizeImage(mean=[0.485, 0.456, 0.406], std=[0.229, 0.224, 0.225]),
|
||||
PrepareForNet(),
|
||||
]
|
||||
)
|
||||
|
||||
|
||||
class DepthAnythingDetector:
|
||||
def __init__(self, model: DPT_DINOv2, device: torch.device) -> None:
|
||||
self.model = model
|
||||
self.device = device
|
||||
|
||||
@staticmethod
|
||||
def load_model(
|
||||
model_path: Path, device: torch.device, model_size: Literal["large", "base", "small"] = "small"
|
||||
) -> DPT_DINOv2:
|
||||
match model_size:
|
||||
case "small":
|
||||
model = DPT_DINOv2(encoder="vits", features=64, out_channels=[48, 96, 192, 384])
|
||||
case "base":
|
||||
model = DPT_DINOv2(encoder="vitb", features=128, out_channels=[96, 192, 384, 768])
|
||||
case "large":
|
||||
model = DPT_DINOv2(encoder="vitl", features=256, out_channels=[256, 512, 1024, 1024])
|
||||
|
||||
model.load_state_dict(torch.load(model_path.as_posix(), map_location="cpu"))
|
||||
model.eval()
|
||||
|
||||
model.to(device)
|
||||
return model
|
||||
|
||||
def __call__(self, image: Image.Image, resolution: int = 512) -> Image.Image:
|
||||
if not self.model:
|
||||
logger.warn("DepthAnything model was not loaded. Returning original image")
|
||||
return image
|
||||
|
||||
np_image = np.array(image, dtype=np.uint8)
|
||||
np_image = np_image[:, :, ::-1] / 255.0
|
||||
|
||||
image_height, image_width = np_image.shape[:2]
|
||||
np_image = transform({"image": np_image})["image"]
|
||||
tensor_image = torch.from_numpy(np_image).unsqueeze(0).to(self.device)
|
||||
|
||||
with torch.no_grad():
|
||||
depth = self.model(tensor_image)
|
||||
depth = F.interpolate(depth[None], (image_height, image_width), mode="bilinear", align_corners=False)[0, 0]
|
||||
depth = (depth - depth.min()) / (depth.max() - depth.min()) * 255.0
|
||||
|
||||
depth_map = repeat(depth, "h w -> h w 3").cpu().numpy().astype(np.uint8)
|
||||
depth_map = Image.fromarray(depth_map)
|
||||
|
||||
new_height = int(image_height * (resolution / image_width))
|
||||
depth_map = depth_map.resize((resolution, new_height))
|
||||
|
||||
return depth_map
|
@ -0,0 +1,31 @@
|
||||
from typing import Optional
|
||||
|
||||
import torch
|
||||
from PIL import Image
|
||||
from transformers.pipelines import DepthEstimationPipeline
|
||||
|
||||
from invokeai.backend.raw_model import RawModel
|
||||
|
||||
|
||||
class DepthAnythingPipeline(RawModel):
|
||||
"""Custom wrapper for the Depth Estimation pipeline from transformers adding compatibility
|
||||
for Invoke's Model Management System"""
|
||||
|
||||
def __init__(self, pipeline: DepthEstimationPipeline) -> None:
|
||||
self._pipeline = pipeline
|
||||
|
||||
def generate_depth(self, image: Image.Image) -> Image.Image:
|
||||
depth_map = self._pipeline(image)["depth"]
|
||||
assert isinstance(depth_map, Image.Image)
|
||||
return depth_map
|
||||
|
||||
def to(self, device: Optional[torch.device] = None, dtype: Optional[torch.dtype] = None):
|
||||
if device is not None and device.type not in {"cpu", "cuda"}:
|
||||
device = None
|
||||
self._pipeline.model.to(device=device, dtype=dtype)
|
||||
self._pipeline.device = self._pipeline.model.device
|
||||
|
||||
def calc_size(self) -> int:
|
||||
from invokeai.backend.model_manager.load.model_util import calc_module_size
|
||||
|
||||
return calc_module_size(self._pipeline.model)
|
@ -1,145 +0,0 @@
|
||||
import torch.nn as nn
|
||||
|
||||
|
||||
def _make_scratch(in_shape, out_shape, groups=1, expand=False):
|
||||
scratch = nn.Module()
|
||||
|
||||
out_shape1 = out_shape
|
||||
out_shape2 = out_shape
|
||||
out_shape3 = out_shape
|
||||
if len(in_shape) >= 4:
|
||||
out_shape4 = out_shape
|
||||
|
||||
if expand:
|
||||
out_shape1 = out_shape
|
||||
out_shape2 = out_shape * 2
|
||||
out_shape3 = out_shape * 4
|
||||
if len(in_shape) >= 4:
|
||||
out_shape4 = out_shape * 8
|
||||
|
||||
scratch.layer1_rn = nn.Conv2d(
|
||||
in_shape[0], out_shape1, kernel_size=3, stride=1, padding=1, bias=False, groups=groups
|
||||
)
|
||||
scratch.layer2_rn = nn.Conv2d(
|
||||
in_shape[1], out_shape2, kernel_size=3, stride=1, padding=1, bias=False, groups=groups
|
||||
)
|
||||
scratch.layer3_rn = nn.Conv2d(
|
||||
in_shape[2], out_shape3, kernel_size=3, stride=1, padding=1, bias=False, groups=groups
|
||||
)
|
||||
if len(in_shape) >= 4:
|
||||
scratch.layer4_rn = nn.Conv2d(
|
||||
in_shape[3], out_shape4, kernel_size=3, stride=1, padding=1, bias=False, groups=groups
|
||||
)
|
||||
|
||||
return scratch
|
||||
|
||||
|
||||
class ResidualConvUnit(nn.Module):
|
||||
"""Residual convolution module."""
|
||||
|
||||
def __init__(self, features, activation, bn):
|
||||
"""Init.
|
||||
|
||||
Args:
|
||||
features (int): number of features
|
||||
"""
|
||||
super().__init__()
|
||||
|
||||
self.bn = bn
|
||||
|
||||
self.groups = 1
|
||||
|
||||
self.conv1 = nn.Conv2d(features, features, kernel_size=3, stride=1, padding=1, bias=True, groups=self.groups)
|
||||
|
||||
self.conv2 = nn.Conv2d(features, features, kernel_size=3, stride=1, padding=1, bias=True, groups=self.groups)
|
||||
|
||||
if self.bn:
|
||||
self.bn1 = nn.BatchNorm2d(features)
|
||||
self.bn2 = nn.BatchNorm2d(features)
|
||||
|
||||
self.activation = activation
|
||||
|
||||
self.skip_add = nn.quantized.FloatFunctional()
|
||||
|
||||
def forward(self, x):
|
||||
"""Forward pass.
|
||||
|
||||
Args:
|
||||
x (tensor): input
|
||||
|
||||
Returns:
|
||||
tensor: output
|
||||
"""
|
||||
|
||||
out = self.activation(x)
|
||||
out = self.conv1(out)
|
||||
if self.bn:
|
||||
out = self.bn1(out)
|
||||
|
||||
out = self.activation(out)
|
||||
out = self.conv2(out)
|
||||
if self.bn:
|
||||
out = self.bn2(out)
|
||||
|
||||
if self.groups > 1:
|
||||
out = self.conv_merge(out)
|
||||
|
||||
return self.skip_add.add(out, x)
|
||||
|
||||
|
||||
class FeatureFusionBlock(nn.Module):
|
||||
"""Feature fusion block."""
|
||||
|
||||
def __init__(self, features, activation, deconv=False, bn=False, expand=False, align_corners=True, size=None):
|
||||
"""Init.
|
||||
|
||||
Args:
|
||||
features (int): number of features
|
||||
"""
|
||||
super(FeatureFusionBlock, self).__init__()
|
||||
|
||||
self.deconv = deconv
|
||||
self.align_corners = align_corners
|
||||
|
||||
self.groups = 1
|
||||
|
||||
self.expand = expand
|
||||
out_features = features
|
||||
if self.expand:
|
||||
out_features = features // 2
|
||||
|
||||
self.out_conv = nn.Conv2d(features, out_features, kernel_size=1, stride=1, padding=0, bias=True, groups=1)
|
||||
|
||||
self.resConfUnit1 = ResidualConvUnit(features, activation, bn)
|
||||
self.resConfUnit2 = ResidualConvUnit(features, activation, bn)
|
||||
|
||||
self.skip_add = nn.quantized.FloatFunctional()
|
||||
|
||||
self.size = size
|
||||
|
||||
def forward(self, *xs, size=None):
|
||||
"""Forward pass.
|
||||
|
||||
Returns:
|
||||
tensor: output
|
||||
"""
|
||||
output = xs[0]
|
||||
|
||||
if len(xs) == 2:
|
||||
res = self.resConfUnit1(xs[1])
|
||||
output = self.skip_add.add(output, res)
|
||||
|
||||
output = self.resConfUnit2(output)
|
||||
|
||||
if (size is None) and (self.size is None):
|
||||
modifier = {"scale_factor": 2}
|
||||
elif size is None:
|
||||
modifier = {"size": self.size}
|
||||
else:
|
||||
modifier = {"size": size}
|
||||
|
||||
output = nn.functional.interpolate(output, **modifier, mode="bilinear", align_corners=self.align_corners)
|
||||
|
||||
output = self.out_conv(output)
|
||||
|
||||
return output
|
@ -1,183 +0,0 @@
|
||||
from pathlib import Path
|
||||
|
||||
import torch
|
||||
import torch.nn as nn
|
||||
import torch.nn.functional as F
|
||||
|
||||
from invokeai.backend.image_util.depth_anything.model.blocks import FeatureFusionBlock, _make_scratch
|
||||
|
||||
torchhub_path = Path(__file__).parent.parent / "torchhub"
|
||||
|
||||
|
||||
def _make_fusion_block(features, use_bn, size=None):
|
||||
return FeatureFusionBlock(
|
||||
features,
|
||||
nn.ReLU(False),
|
||||
deconv=False,
|
||||
bn=use_bn,
|
||||
expand=False,
|
||||
align_corners=True,
|
||||
size=size,
|
||||
)
|
||||
|
||||
|
||||
class DPTHead(nn.Module):
|
||||
def __init__(self, nclass, in_channels, features, out_channels, use_bn=False, use_clstoken=False):
|
||||
super(DPTHead, self).__init__()
|
||||
|
||||
self.nclass = nclass
|
||||
self.use_clstoken = use_clstoken
|
||||
|
||||
self.projects = nn.ModuleList(
|
||||
[
|
||||
nn.Conv2d(
|
||||
in_channels=in_channels,
|
||||
out_channels=out_channel,
|
||||
kernel_size=1,
|
||||
stride=1,
|
||||
padding=0,
|
||||
)
|
||||
for out_channel in out_channels
|
||||
]
|
||||
)
|
||||
|
||||
self.resize_layers = nn.ModuleList(
|
||||
[
|
||||
nn.ConvTranspose2d(
|
||||
in_channels=out_channels[0], out_channels=out_channels[0], kernel_size=4, stride=4, padding=0
|
||||
),
|
||||
nn.ConvTranspose2d(
|
||||
in_channels=out_channels[1], out_channels=out_channels[1], kernel_size=2, stride=2, padding=0
|
||||
),
|
||||
nn.Identity(),
|
||||
nn.Conv2d(
|
||||
in_channels=out_channels[3], out_channels=out_channels[3], kernel_size=3, stride=2, padding=1
|
||||
),
|
||||
]
|
||||
)
|
||||
|
||||
if use_clstoken:
|
||||
self.readout_projects = nn.ModuleList()
|
||||
for _ in range(len(self.projects)):
|
||||
self.readout_projects.append(nn.Sequential(nn.Linear(2 * in_channels, in_channels), nn.GELU()))
|
||||
|
||||
self.scratch = _make_scratch(
|
||||
out_channels,
|
||||
features,
|
||||
groups=1,
|
||||
expand=False,
|
||||
)
|
||||
|
||||
self.scratch.stem_transpose = None
|
||||
|
||||
self.scratch.refinenet1 = _make_fusion_block(features, use_bn)
|
||||
self.scratch.refinenet2 = _make_fusion_block(features, use_bn)
|
||||
self.scratch.refinenet3 = _make_fusion_block(features, use_bn)
|
||||
self.scratch.refinenet4 = _make_fusion_block(features, use_bn)
|
||||
|
||||
head_features_1 = features
|
||||
head_features_2 = 32
|
||||
|
||||
if nclass > 1:
|
||||
self.scratch.output_conv = nn.Sequential(
|
||||
nn.Conv2d(head_features_1, head_features_1, kernel_size=3, stride=1, padding=1),
|
||||
nn.ReLU(True),
|
||||
nn.Conv2d(head_features_1, nclass, kernel_size=1, stride=1, padding=0),
|
||||
)
|
||||
else:
|
||||
self.scratch.output_conv1 = nn.Conv2d(
|
||||
head_features_1, head_features_1 // 2, kernel_size=3, stride=1, padding=1
|
||||
)
|
||||
|
||||
self.scratch.output_conv2 = nn.Sequential(
|
||||
nn.Conv2d(head_features_1 // 2, head_features_2, kernel_size=3, stride=1, padding=1),
|
||||
nn.ReLU(True),
|
||||
nn.Conv2d(head_features_2, 1, kernel_size=1, stride=1, padding=0),
|
||||
nn.ReLU(True),
|
||||
nn.Identity(),
|
||||
)
|
||||
|
||||
def forward(self, out_features, patch_h, patch_w):
|
||||
out = []
|
||||
for i, x in enumerate(out_features):
|
||||
if self.use_clstoken:
|
||||
x, cls_token = x[0], x[1]
|
||||
readout = cls_token.unsqueeze(1).expand_as(x)
|
||||
x = self.readout_projects[i](torch.cat((x, readout), -1))
|
||||
else:
|
||||
x = x[0]
|
||||
|
||||
x = x.permute(0, 2, 1).reshape((x.shape[0], x.shape[-1], patch_h, patch_w))
|
||||
|
||||
x = self.projects[i](x)
|
||||
x = self.resize_layers[i](x)
|
||||
|
||||
out.append(x)
|
||||
|
||||
layer_1, layer_2, layer_3, layer_4 = out
|
||||
|
||||
layer_1_rn = self.scratch.layer1_rn(layer_1)
|
||||
layer_2_rn = self.scratch.layer2_rn(layer_2)
|
||||
layer_3_rn = self.scratch.layer3_rn(layer_3)
|
||||
layer_4_rn = self.scratch.layer4_rn(layer_4)
|
||||
|
||||
path_4 = self.scratch.refinenet4(layer_4_rn, size=layer_3_rn.shape[2:])
|
||||
path_3 = self.scratch.refinenet3(path_4, layer_3_rn, size=layer_2_rn.shape[2:])
|
||||
path_2 = self.scratch.refinenet2(path_3, layer_2_rn, size=layer_1_rn.shape[2:])
|
||||
path_1 = self.scratch.refinenet1(path_2, layer_1_rn)
|
||||
|
||||
out = self.scratch.output_conv1(path_1)
|
||||
out = F.interpolate(out, (int(patch_h * 14), int(patch_w * 14)), mode="bilinear", align_corners=True)
|
||||
out = self.scratch.output_conv2(out)
|
||||
|
||||
return out
|
||||
|
||||
|
||||
class DPT_DINOv2(nn.Module):
|
||||
def __init__(
|
||||
self,
|
||||
features,
|
||||
out_channels,
|
||||
encoder="vitl",
|
||||
use_bn=False,
|
||||
use_clstoken=False,
|
||||
):
|
||||
super(DPT_DINOv2, self).__init__()
|
||||
|
||||
assert encoder in ["vits", "vitb", "vitl"]
|
||||
|
||||
# # in case the Internet connection is not stable, please load the DINOv2 locally
|
||||
# if use_local:
|
||||
# self.pretrained = torch.hub.load(
|
||||
# torchhub_path / "facebookresearch_dinov2_main",
|
||||
# "dinov2_{:}14".format(encoder),
|
||||
# source="local",
|
||||
# pretrained=False,
|
||||
# )
|
||||
# else:
|
||||
# self.pretrained = torch.hub.load(
|
||||
# "facebookresearch/dinov2",
|
||||
# "dinov2_{:}14".format(encoder),
|
||||
# )
|
||||
|
||||
self.pretrained = torch.hub.load(
|
||||
"facebookresearch/dinov2",
|
||||
"dinov2_{:}14".format(encoder),
|
||||
)
|
||||
|
||||
dim = self.pretrained.blocks[0].attn.qkv.in_features
|
||||
|
||||
self.depth_head = DPTHead(1, dim, features, out_channels=out_channels, use_bn=use_bn, use_clstoken=use_clstoken)
|
||||
|
||||
def forward(self, x):
|
||||
h, w = x.shape[-2:]
|
||||
|
||||
features = self.pretrained.get_intermediate_layers(x, 4, return_class_token=True)
|
||||
|
||||
patch_h, patch_w = h // 14, w // 14
|
||||
|
||||
depth = self.depth_head(features, patch_h, patch_w)
|
||||
depth = F.interpolate(depth, size=(h, w), mode="bilinear", align_corners=True)
|
||||
depth = F.relu(depth)
|
||||
|
||||
return depth.squeeze(1)
|
@ -1,227 +0,0 @@
|
||||
import math
|
||||
|
||||
import cv2
|
||||
import numpy as np
|
||||
import torch
|
||||
import torch.nn.functional as F
|
||||
|
||||
|
||||
def apply_min_size(sample, size, image_interpolation_method=cv2.INTER_AREA):
|
||||
"""Rezise the sample to ensure the given size. Keeps aspect ratio.
|
||||
|
||||
Args:
|
||||
sample (dict): sample
|
||||
size (tuple): image size
|
||||
|
||||
Returns:
|
||||
tuple: new size
|
||||
"""
|
||||
shape = list(sample["disparity"].shape)
|
||||
|
||||
if shape[0] >= size[0] and shape[1] >= size[1]:
|
||||
return sample
|
||||
|
||||
scale = [0, 0]
|
||||
scale[0] = size[0] / shape[0]
|
||||
scale[1] = size[1] / shape[1]
|
||||
|
||||
scale = max(scale)
|
||||
|
||||
shape[0] = math.ceil(scale * shape[0])
|
||||
shape[1] = math.ceil(scale * shape[1])
|
||||
|
||||
# resize
|
||||
sample["image"] = cv2.resize(sample["image"], tuple(shape[::-1]), interpolation=image_interpolation_method)
|
||||
|
||||
sample["disparity"] = cv2.resize(sample["disparity"], tuple(shape[::-1]), interpolation=cv2.INTER_NEAREST)
|
||||
sample["mask"] = cv2.resize(
|
||||
sample["mask"].astype(np.float32),
|
||||
tuple(shape[::-1]),
|
||||
interpolation=cv2.INTER_NEAREST,
|
||||
)
|
||||
sample["mask"] = sample["mask"].astype(bool)
|
||||
|
||||
return tuple(shape)
|
||||
|
||||
|
||||
class Resize(object):
|
||||
"""Resize sample to given size (width, height)."""
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
width,
|
||||
height,
|
||||
resize_target=True,
|
||||
keep_aspect_ratio=False,
|
||||
ensure_multiple_of=1,
|
||||
resize_method="lower_bound",
|
||||
image_interpolation_method=cv2.INTER_AREA,
|
||||
):
|
||||
"""Init.
|
||||
|
||||
Args:
|
||||
width (int): desired output width
|
||||
height (int): desired output height
|
||||
resize_target (bool, optional):
|
||||
True: Resize the full sample (image, mask, target).
|
||||
False: Resize image only.
|
||||
Defaults to True.
|
||||
keep_aspect_ratio (bool, optional):
|
||||
True: Keep the aspect ratio of the input sample.
|
||||
Output sample might not have the given width and height, and
|
||||
resize behaviour depends on the parameter 'resize_method'.
|
||||
Defaults to False.
|
||||
ensure_multiple_of (int, optional):
|
||||
Output width and height is constrained to be multiple of this parameter.
|
||||
Defaults to 1.
|
||||
resize_method (str, optional):
|
||||
"lower_bound": Output will be at least as large as the given size.
|
||||
"upper_bound": Output will be at max as large as the given size. (Output size might be smaller
|
||||
than given size.)
|
||||
"minimal": Scale as least as possible. (Output size might be smaller than given size.)
|
||||
Defaults to "lower_bound".
|
||||
"""
|
||||
self.__width = width
|
||||
self.__height = height
|
||||
|
||||
self.__resize_target = resize_target
|
||||
self.__keep_aspect_ratio = keep_aspect_ratio
|
||||
self.__multiple_of = ensure_multiple_of
|
||||
self.__resize_method = resize_method
|
||||
self.__image_interpolation_method = image_interpolation_method
|
||||
|
||||
def constrain_to_multiple_of(self, x, min_val=0, max_val=None):
|
||||
y = (np.round(x / self.__multiple_of) * self.__multiple_of).astype(int)
|
||||
|
||||
if max_val is not None and y > max_val:
|
||||
y = (np.floor(x / self.__multiple_of) * self.__multiple_of).astype(int)
|
||||
|
||||
if y < min_val:
|
||||
y = (np.ceil(x / self.__multiple_of) * self.__multiple_of).astype(int)
|
||||
|
||||
return y
|
||||
|
||||
def get_size(self, width, height):
|
||||
# determine new height and width
|
||||
scale_height = self.__height / height
|
||||
scale_width = self.__width / width
|
||||
|
||||
if self.__keep_aspect_ratio:
|
||||
if self.__resize_method == "lower_bound":
|
||||
# scale such that output size is lower bound
|
||||
if scale_width > scale_height:
|
||||
# fit width
|
||||
scale_height = scale_width
|
||||
else:
|
||||
# fit height
|
||||
scale_width = scale_height
|
||||
elif self.__resize_method == "upper_bound":
|
||||
# scale such that output size is upper bound
|
||||
if scale_width < scale_height:
|
||||
# fit width
|
||||
scale_height = scale_width
|
||||
else:
|
||||
# fit height
|
||||
scale_width = scale_height
|
||||
elif self.__resize_method == "minimal":
|
||||
# scale as least as possbile
|
||||
if abs(1 - scale_width) < abs(1 - scale_height):
|
||||
# fit width
|
||||
scale_height = scale_width
|
||||
else:
|
||||
# fit height
|
||||
scale_width = scale_height
|
||||
else:
|
||||
raise ValueError(f"resize_method {self.__resize_method} not implemented")
|
||||
|
||||
if self.__resize_method == "lower_bound":
|
||||
new_height = self.constrain_to_multiple_of(scale_height * height, min_val=self.__height)
|
||||
new_width = self.constrain_to_multiple_of(scale_width * width, min_val=self.__width)
|
||||
elif self.__resize_method == "upper_bound":
|
||||
new_height = self.constrain_to_multiple_of(scale_height * height, max_val=self.__height)
|
||||
new_width = self.constrain_to_multiple_of(scale_width * width, max_val=self.__width)
|
||||
elif self.__resize_method == "minimal":
|
||||
new_height = self.constrain_to_multiple_of(scale_height * height)
|
||||
new_width = self.constrain_to_multiple_of(scale_width * width)
|
||||
else:
|
||||
raise ValueError(f"resize_method {self.__resize_method} not implemented")
|
||||
|
||||
return (new_width, new_height)
|
||||
|
||||
def __call__(self, sample):
|
||||
width, height = self.get_size(sample["image"].shape[1], sample["image"].shape[0])
|
||||
|
||||
# resize sample
|
||||
sample["image"] = cv2.resize(
|
||||
sample["image"],
|
||||
(width, height),
|
||||
interpolation=self.__image_interpolation_method,
|
||||
)
|
||||
|
||||
if self.__resize_target:
|
||||
if "disparity" in sample:
|
||||
sample["disparity"] = cv2.resize(
|
||||
sample["disparity"],
|
||||
(width, height),
|
||||
interpolation=cv2.INTER_NEAREST,
|
||||
)
|
||||
|
||||
if "depth" in sample:
|
||||
sample["depth"] = cv2.resize(sample["depth"], (width, height), interpolation=cv2.INTER_NEAREST)
|
||||
|
||||
if "semseg_mask" in sample:
|
||||
# sample["semseg_mask"] = cv2.resize(
|
||||
# sample["semseg_mask"], (width, height), interpolation=cv2.INTER_NEAREST
|
||||
# )
|
||||
sample["semseg_mask"] = F.interpolate(
|
||||
torch.from_numpy(sample["semseg_mask"]).float()[None, None, ...], (height, width), mode="nearest"
|
||||
).numpy()[0, 0]
|
||||
|
||||
if "mask" in sample:
|
||||
sample["mask"] = cv2.resize(
|
||||
sample["mask"].astype(np.float32),
|
||||
(width, height),
|
||||
interpolation=cv2.INTER_NEAREST,
|
||||
)
|
||||
# sample["mask"] = sample["mask"].astype(bool)
|
||||
|
||||
# print(sample['image'].shape, sample['depth'].shape)
|
||||
return sample
|
||||
|
||||
|
||||
class NormalizeImage(object):
|
||||
"""Normlize image by given mean and std."""
|
||||
|
||||
def __init__(self, mean, std):
|
||||
self.__mean = mean
|
||||
self.__std = std
|
||||
|
||||
def __call__(self, sample):
|
||||
sample["image"] = (sample["image"] - self.__mean) / self.__std
|
||||
|
||||
return sample
|
||||
|
||||
|
||||
class PrepareForNet(object):
|
||||
"""Prepare sample for usage as network input."""
|
||||
|
||||
def __init__(self):
|
||||
pass
|
||||
|
||||
def __call__(self, sample):
|
||||
image = np.transpose(sample["image"], (2, 0, 1))
|
||||
sample["image"] = np.ascontiguousarray(image).astype(np.float32)
|
||||
|
||||
if "mask" in sample:
|
||||
sample["mask"] = sample["mask"].astype(np.float32)
|
||||
sample["mask"] = np.ascontiguousarray(sample["mask"])
|
||||
|
||||
if "depth" in sample:
|
||||
depth = sample["depth"].astype(np.float32)
|
||||
sample["depth"] = np.ascontiguousarray(depth)
|
||||
|
||||
if "semseg_mask" in sample:
|
||||
sample["semseg_mask"] = sample["semseg_mask"].astype(np.float32)
|
||||
sample["semseg_mask"] = np.ascontiguousarray(sample["semseg_mask"])
|
||||
|
||||
return sample
|
@ -220,11 +220,17 @@ class LoKRLayer(LoRALayerBase):
|
||||
if self.w1 is None:
|
||||
self.w1_a = values["lokr_w1_a"]
|
||||
self.w1_b = values["lokr_w1_b"]
|
||||
else:
|
||||
self.w1_b = None
|
||||
self.w1_a = None
|
||||
|
||||
self.w2 = values.get("lokr_w2", None)
|
||||
if self.w2 is None:
|
||||
self.w2_a = values["lokr_w2_a"]
|
||||
self.w2_b = values["lokr_w2_b"]
|
||||
else:
|
||||
self.w2_a = None
|
||||
self.w2_b = None
|
||||
|
||||
self.t2 = values.get("lokr_t2", None)
|
||||
|
||||
@ -372,7 +378,39 @@ class IA3Layer(LoRALayerBase):
|
||||
self.on_input = self.on_input.to(device=device, dtype=dtype)
|
||||
|
||||
|
||||
AnyLoRALayer = Union[LoRALayer, LoHALayer, LoKRLayer, FullLayer, IA3Layer]
|
||||
class NormLayer(LoRALayerBase):
|
||||
# bias handled in LoRALayerBase(calc_size, to)
|
||||
# weight: torch.Tensor
|
||||
# bias: Optional[torch.Tensor]
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
layer_key: str,
|
||||
values: Dict[str, torch.Tensor],
|
||||
):
|
||||
super().__init__(layer_key, values)
|
||||
|
||||
self.weight = values["w_norm"]
|
||||
self.bias = values.get("b_norm", None)
|
||||
|
||||
self.rank = None # unscaled
|
||||
self.check_keys(values, {"w_norm", "b_norm"})
|
||||
|
||||
def get_weight(self, orig_weight: torch.Tensor) -> torch.Tensor:
|
||||
return self.weight
|
||||
|
||||
def calc_size(self) -> int:
|
||||
model_size = super().calc_size()
|
||||
model_size += self.weight.nelement() * self.weight.element_size()
|
||||
return model_size
|
||||
|
||||
def to(self, device: Optional[torch.device] = None, dtype: Optional[torch.dtype] = None) -> None:
|
||||
super().to(device=device, dtype=dtype)
|
||||
|
||||
self.weight = self.weight.to(device=device, dtype=dtype)
|
||||
|
||||
|
||||
AnyLoRALayer = Union[LoRALayer, LoHALayer, LoKRLayer, FullLayer, IA3Layer, NormLayer]
|
||||
|
||||
|
||||
class LoRAModelRaw(RawModel): # (torch.nn.Module):
|
||||
@ -513,6 +551,10 @@ class LoRAModelRaw(RawModel): # (torch.nn.Module):
|
||||
elif "on_input" in values:
|
||||
layer = IA3Layer(layer_key, values)
|
||||
|
||||
# norms
|
||||
elif "w_norm" in values:
|
||||
layer = NormLayer(layer_key, values)
|
||||
|
||||
else:
|
||||
print(f">> Encountered unknown lora layer module in {model.name}: {layer_key} - {list(values.keys())}")
|
||||
raise Exception("Unknown lora format!")
|
||||
|
@ -11,6 +11,7 @@ from diffusers.pipelines.pipeline_utils import DiffusionPipeline
|
||||
from diffusers.schedulers.scheduling_utils import SchedulerMixin
|
||||
from transformers import CLIPTokenizer
|
||||
|
||||
from invokeai.backend.image_util.depth_anything.depth_anything_pipeline import DepthAnythingPipeline
|
||||
from invokeai.backend.image_util.grounding_dino.grounding_dino_pipeline import GroundingDinoPipeline
|
||||
from invokeai.backend.image_util.segment_anything.segment_anything_pipeline import SegmentAnythingPipeline
|
||||
from invokeai.backend.ip_adapter.ip_adapter import IPAdapter
|
||||
@ -45,6 +46,7 @@ def calc_model_size_by_data(logger: logging.Logger, model: AnyModel) -> int:
|
||||
SpandrelImageToImageModel,
|
||||
GroundingDinoPipeline,
|
||||
SegmentAnythingPipeline,
|
||||
DepthAnythingPipeline,
|
||||
),
|
||||
):
|
||||
return model.calc_size()
|
||||
|
@ -53,61 +53,61 @@
|
||||
},
|
||||
"dependencies": {
|
||||
"@chakra-ui/react-use-size": "^2.1.0",
|
||||
"@dagrejs/dagre": "^1.1.2",
|
||||
"@dagrejs/graphlib": "^2.2.2",
|
||||
"@dagrejs/dagre": "^1.1.3",
|
||||
"@dagrejs/graphlib": "^2.2.3",
|
||||
"@dnd-kit/core": "^6.1.0",
|
||||
"@dnd-kit/sortable": "^8.0.0",
|
||||
"@dnd-kit/utilities": "^3.2.2",
|
||||
"@fontsource-variable/inter": "^5.0.18",
|
||||
"@fontsource-variable/inter": "^5.0.20",
|
||||
"@invoke-ai/ui-library": "^0.0.25",
|
||||
"@nanostores/react": "^0.7.2",
|
||||
"@nanostores/react": "^0.7.3",
|
||||
"@reduxjs/toolkit": "2.2.3",
|
||||
"@roarr/browser-log-writer": "^1.3.0",
|
||||
"chakra-react-select": "^4.7.6",
|
||||
"compare-versions": "^6.1.0",
|
||||
"chakra-react-select": "^4.9.1",
|
||||
"compare-versions": "^6.1.1",
|
||||
"dateformat": "^5.0.3",
|
||||
"fracturedjsonjs": "^4.0.1",
|
||||
"framer-motion": "^11.1.8",
|
||||
"i18next": "^23.11.3",
|
||||
"i18next-http-backend": "^2.5.1",
|
||||
"fracturedjsonjs": "^4.0.2",
|
||||
"framer-motion": "^11.3.24",
|
||||
"i18next": "^23.12.2",
|
||||
"i18next-http-backend": "^2.5.2",
|
||||
"idb-keyval": "^6.2.1",
|
||||
"jsondiffpatch": "^0.6.0",
|
||||
"konva": "^9.3.6",
|
||||
"konva": "^9.3.14",
|
||||
"lodash-es": "^4.17.21",
|
||||
"nanostores": "^0.10.3",
|
||||
"nanostores": "^0.11.2",
|
||||
"new-github-issue-url": "^1.0.0",
|
||||
"overlayscrollbars": "^2.7.3",
|
||||
"overlayscrollbars": "^2.10.0",
|
||||
"overlayscrollbars-react": "^0.5.6",
|
||||
"query-string": "^9.0.0",
|
||||
"query-string": "^9.1.0",
|
||||
"react": "^18.3.1",
|
||||
"react-colorful": "^5.6.1",
|
||||
"react-dom": "^18.3.1",
|
||||
"react-dropzone": "^14.2.3",
|
||||
"react-error-boundary": "^4.0.13",
|
||||
"react-hook-form": "^7.51.4",
|
||||
"react-hook-form": "^7.52.2",
|
||||
"react-hotkeys-hook": "4.5.0",
|
||||
"react-i18next": "^14.1.1",
|
||||
"react-icons": "^5.2.0",
|
||||
"react-i18next": "^14.1.3",
|
||||
"react-icons": "^5.2.1",
|
||||
"react-konva": "^18.2.10",
|
||||
"react-redux": "9.1.2",
|
||||
"react-resizable-panels": "^2.0.19",
|
||||
"react-resizable-panels": "^2.0.23",
|
||||
"react-select": "5.8.0",
|
||||
"react-use": "^17.5.0",
|
||||
"react-virtuoso": "^4.7.10",
|
||||
"reactflow": "^11.11.3",
|
||||
"react-use": "^17.5.1",
|
||||
"react-virtuoso": "^4.9.0",
|
||||
"reactflow": "^11.11.4",
|
||||
"redux-dynamic-middlewares": "^2.2.0",
|
||||
"redux-remember": "^5.1.0",
|
||||
"redux-undo": "^1.1.0",
|
||||
"rfdc": "^1.3.1",
|
||||
"rfdc": "^1.4.1",
|
||||
"roarr": "^7.21.1",
|
||||
"serialize-error": "^11.0.3",
|
||||
"socket.io-client": "^4.7.5",
|
||||
"use-debounce": "^10.0.0",
|
||||
"use-debounce": "^10.0.2",
|
||||
"use-device-pixel-ratio": "^1.1.2",
|
||||
"use-image": "^1.1.1",
|
||||
"uuid": "^9.0.1",
|
||||
"zod": "^3.23.6",
|
||||
"zod-validation-error": "^3.2.0"
|
||||
"uuid": "^10.0.0",
|
||||
"zod": "^3.23.8",
|
||||
"zod-validation-error": "^3.3.1"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"@chakra-ui/react": "^2.8.2",
|
||||
@ -118,38 +118,38 @@
|
||||
"devDependencies": {
|
||||
"@invoke-ai/eslint-config-react": "^0.0.14",
|
||||
"@invoke-ai/prettier-config-react": "^0.0.7",
|
||||
"@storybook/addon-essentials": "^8.0.10",
|
||||
"@storybook/addon-interactions": "^8.0.10",
|
||||
"@storybook/addon-links": "^8.0.10",
|
||||
"@storybook/addon-storysource": "^8.0.10",
|
||||
"@storybook/manager-api": "^8.0.10",
|
||||
"@storybook/react": "^8.0.10",
|
||||
"@storybook/react-vite": "^8.0.10",
|
||||
"@storybook/theming": "^8.0.10",
|
||||
"@storybook/addon-essentials": "^8.2.8",
|
||||
"@storybook/addon-interactions": "^8.2.8",
|
||||
"@storybook/addon-links": "^8.2.8",
|
||||
"@storybook/addon-storysource": "^8.2.8",
|
||||
"@storybook/manager-api": "^8.2.8",
|
||||
"@storybook/react": "^8.2.8",
|
||||
"@storybook/react-vite": "^8.2.8",
|
||||
"@storybook/theming": "^8.2.8",
|
||||
"@types/dateformat": "^5.0.2",
|
||||
"@types/lodash-es": "^4.17.12",
|
||||
"@types/node": "^20.12.10",
|
||||
"@types/react": "^18.3.1",
|
||||
"@types/node": "^20.14.15",
|
||||
"@types/react": "^18.3.3",
|
||||
"@types/react-dom": "^18.3.0",
|
||||
"@types/uuid": "^9.0.8",
|
||||
"@vitejs/plugin-react-swc": "^3.6.0",
|
||||
"@types/uuid": "^10.0.0",
|
||||
"@vitejs/plugin-react-swc": "^3.7.0",
|
||||
"@vitest/coverage-v8": "^1.5.0",
|
||||
"@vitest/ui": "^1.5.0",
|
||||
"concurrently": "^8.2.2",
|
||||
"dpdm": "^3.14.0",
|
||||
"eslint": "^8.57.0",
|
||||
"eslint-plugin-i18next": "^6.0.3",
|
||||
"eslint-plugin-i18next": "^6.0.9",
|
||||
"eslint-plugin-path": "^1.3.0",
|
||||
"knip": "^5.12.3",
|
||||
"knip": "^5.27.2",
|
||||
"openapi-types": "^12.1.3",
|
||||
"openapi-typescript": "^6.7.5",
|
||||
"prettier": "^3.2.5",
|
||||
"openapi-typescript": "^7.3.0",
|
||||
"prettier": "^3.3.3",
|
||||
"rollup-plugin-visualizer": "^5.12.0",
|
||||
"storybook": "^8.0.10",
|
||||
"storybook": "^8.2.8",
|
||||
"ts-toolbelt": "^9.6.0",
|
||||
"tsafe": "^1.6.6",
|
||||
"typescript": "^5.4.5",
|
||||
"vite": "^5.2.11",
|
||||
"tsafe": "^1.7.2",
|
||||
"typescript": "^5.5.4",
|
||||
"vite": "^5.4.0",
|
||||
"vite-plugin-css-injected-by-js": "^3.5.1",
|
||||
"vite-plugin-dts": "^3.9.1",
|
||||
"vite-plugin-eslint": "^1.8.1",
|
||||
|
5151
invokeai/frontend/web/pnpm-lock.yaml
generated
5151
invokeai/frontend/web/pnpm-lock.yaml
generated
File diff suppressed because it is too large
Load Diff
@ -91,7 +91,8 @@
|
||||
"viewingDesc": "Bilder in großer Galerie ansehen",
|
||||
"tab": "Tabulator",
|
||||
"enabled": "Aktiviert",
|
||||
"disabled": "Ausgeschaltet"
|
||||
"disabled": "Ausgeschaltet",
|
||||
"dontShowMeThese": "Zeig mir diese nicht"
|
||||
},
|
||||
"gallery": {
|
||||
"galleryImageSize": "Bildgröße",
|
||||
@ -106,7 +107,6 @@
|
||||
"download": "Runterladen",
|
||||
"setCurrentImage": "Setze aktuelle Bild",
|
||||
"featuresWillReset": "Wenn Sie dieses Bild löschen, werden diese Funktionen sofort zurückgesetzt.",
|
||||
"deleteImageBin": "Gelöschte Bilder werden an den Papierkorb Ihres Betriebssystems gesendet.",
|
||||
"unableToLoad": "Galerie kann nicht geladen werden",
|
||||
"downloadSelection": "Auswahl herunterladen",
|
||||
"currentlyInUse": "Dieses Bild wird derzeit in den folgenden Funktionen verwendet:",
|
||||
@ -628,7 +628,10 @@
|
||||
"private": "Private Ordner",
|
||||
"shared": "Geteilte Ordner",
|
||||
"archiveBoard": "Ordner archivieren",
|
||||
"archived": "Archiviert"
|
||||
"archived": "Archiviert",
|
||||
"noBoards": "Kein {boardType}} Ordner",
|
||||
"hideBoards": "Ordner verstecken",
|
||||
"viewBoards": "Ordner ansehen"
|
||||
},
|
||||
"controlnet": {
|
||||
"showAdvanced": "Zeige Erweitert",
|
||||
@ -943,6 +946,21 @@
|
||||
"paragraphs": [
|
||||
"Reduziert das Ausgangsbild auf die Breite und Höhe des Ausgangsbildes. Empfohlen zu aktivieren."
|
||||
]
|
||||
},
|
||||
"structure": {
|
||||
"paragraphs": [
|
||||
"Die Struktur steuert, wie genau sich das Ausgabebild an das Layout des Originals hält. Eine niedrige Struktur erlaubt größere Änderungen, während eine hohe Struktur die ursprüngliche Komposition und das Layout strikter beibehält."
|
||||
]
|
||||
},
|
||||
"creativity": {
|
||||
"paragraphs": [
|
||||
"Die Kreativität bestimmt den Grad der Freiheit, die dem Modell beim Hinzufügen von Details gewährt wird. Eine niedrige Kreativität hält sich eng an das Originalbild, während eine hohe Kreativität mehr Veränderungen zulässt. Bei der Verwendung eines Prompts erhöht eine hohe Kreativität den Einfluss des Prompts."
|
||||
]
|
||||
},
|
||||
"scale": {
|
||||
"paragraphs": [
|
||||
"Die Skalierung steuert die Größe des Ausgabebildes und basiert auf einem Vielfachen der Auflösung des Originalbildes. So würde z. B. eine 2-fache Hochskalierung eines 1024x1024px Bildes eine 2048x2048px große Ausgabe erzeugen."
|
||||
]
|
||||
}
|
||||
},
|
||||
"invocationCache": {
|
||||
|
@ -200,6 +200,7 @@
|
||||
"delete": "Delete",
|
||||
"depthAnything": "Depth Anything",
|
||||
"depthAnythingDescription": "Depth map generation using the Depth Anything technique",
|
||||
"depthAnythingSmallV2": "Small V2",
|
||||
"depthMidas": "Depth (Midas)",
|
||||
"depthMidasDescription": "Depth map generation using Midas",
|
||||
"depthZoe": "Depth (Zoe)",
|
||||
@ -373,7 +374,6 @@
|
||||
"dropToUpload": "$t(gallery.drop) to Upload",
|
||||
"deleteImage_one": "Delete Image",
|
||||
"deleteImage_other": "Delete {{count}} Images",
|
||||
"deleteImageBin": "Deleted images will be sent to your operating system's Bin.",
|
||||
"deleteImagePermanent": "Deleted images cannot be restored.",
|
||||
"displayBoardSearch": "Display Board Search",
|
||||
"displaySearch": "Display Search",
|
||||
@ -1053,11 +1053,7 @@
|
||||
"remixImage": "Remix Image",
|
||||
"usePrompt": "Use Prompt",
|
||||
"useSeed": "Use Seed",
|
||||
"width": "Width",
|
||||
"isAllowedToUpscale": {
|
||||
"useX2Model": "Image is too large to upscale with x4 model, use x2 model",
|
||||
"tooLarge": "Image is too large to upscale, select smaller image"
|
||||
}
|
||||
"width": "Width"
|
||||
},
|
||||
"dynamicPrompts": {
|
||||
"showDynamicPrompts": "Show Dynamic Prompts",
|
||||
@ -1678,6 +1674,8 @@
|
||||
},
|
||||
"upscaling": {
|
||||
"creativity": "Creativity",
|
||||
"exceedsMaxSize": "Upscale settings exceed max size limit",
|
||||
"exceedsMaxSizeDetails": "Max upscale limit is {{maxUpscaleDimension}}x{{maxUpscaleDimension}} pixels. Please try a smaller image or decrease your scale selection.",
|
||||
"structure": "Structure",
|
||||
"upscaleModel": "Upscale Model",
|
||||
"postProcessingModel": "Post-Processing Model",
|
||||
|
@ -88,7 +88,6 @@
|
||||
"deleteImage_one": "Eliminar Imagen",
|
||||
"deleteImage_many": "",
|
||||
"deleteImage_other": "",
|
||||
"deleteImageBin": "Las imágenes eliminadas se enviarán a la papelera de tu sistema operativo.",
|
||||
"deleteImagePermanent": "Las imágenes eliminadas no se pueden restaurar.",
|
||||
"assets": "Activos",
|
||||
"autoAssignBoardOnClick": "Asignación automática de tableros al hacer clic"
|
||||
|
@ -89,7 +89,8 @@
|
||||
"enabled": "Abilitato",
|
||||
"disabled": "Disabilitato",
|
||||
"comparingDesc": "Confronta due immagini",
|
||||
"comparing": "Confronta"
|
||||
"comparing": "Confronta",
|
||||
"dontShowMeThese": "Non mostrarmi questi"
|
||||
},
|
||||
"gallery": {
|
||||
"galleryImageSize": "Dimensione dell'immagine",
|
||||
@ -101,7 +102,6 @@
|
||||
"deleteImage_many": "Elimina {{count}} immagini",
|
||||
"deleteImage_other": "Elimina {{count}} immagini",
|
||||
"deleteImagePermanent": "Le immagini eliminate non possono essere ripristinate.",
|
||||
"deleteImageBin": "Le immagini eliminate verranno spostate nel cestino del tuo sistema operativo.",
|
||||
"assets": "Risorse",
|
||||
"autoAssignBoardOnClick": "Assegna automaticamente la bacheca al clic",
|
||||
"featuresWillReset": "Se elimini questa immagine, quelle funzionalità verranno immediatamente ripristinate.",
|
||||
@ -154,7 +154,9 @@
|
||||
"selectAllOnPage": "Seleziona tutto nella pagina",
|
||||
"selectAllOnBoard": "Seleziona tutto nella bacheca",
|
||||
"exitBoardSearch": "Esci da Ricerca bacheca",
|
||||
"exitSearch": "Esci dalla ricerca"
|
||||
"exitSearch": "Esci dalla ricerca",
|
||||
"go": "Vai",
|
||||
"jump": "Salta"
|
||||
},
|
||||
"hotkeys": {
|
||||
"keyboardShortcuts": "Tasti di scelta rapida",
|
||||
@ -571,10 +573,6 @@
|
||||
},
|
||||
"useCpuNoise": "Usa la CPU per generare rumore",
|
||||
"iterations": "Iterazioni",
|
||||
"isAllowedToUpscale": {
|
||||
"useX2Model": "L'immagine è troppo grande per l'ampliamento con il modello x4, utilizza il modello x2",
|
||||
"tooLarge": "L'immagine è troppo grande per l'ampliamento, seleziona un'immagine più piccola"
|
||||
},
|
||||
"imageActions": "Azioni Immagine",
|
||||
"cfgRescaleMultiplier": "Moltiplicatore riscala CFG",
|
||||
"useSize": "Usa Dimensioni",
|
||||
@ -630,7 +628,9 @@
|
||||
"enableNSFWChecker": "Abilita controllo NSFW",
|
||||
"enableInvisibleWatermark": "Abilita filigrana invisibile",
|
||||
"enableInformationalPopovers": "Abilita testo informativo a comparsa",
|
||||
"reloadingIn": "Ricaricando in"
|
||||
"reloadingIn": "Ricaricando in",
|
||||
"informationalPopoversDisabled": "Testo informativo a comparsa disabilitato",
|
||||
"informationalPopoversDisabledDesc": "I testi informativi a comparsa sono disabilitati. Attivali nelle impostazioni."
|
||||
},
|
||||
"toast": {
|
||||
"uploadFailed": "Caricamento fallito",
|
||||
@ -951,7 +951,7 @@
|
||||
"deleteBoardOnly": "solo la Bacheca",
|
||||
"deleteBoard": "Elimina Bacheca",
|
||||
"deleteBoardAndImages": "Bacheca e Immagini",
|
||||
"deletedBoardsCannotbeRestored": "Le bacheche eliminate non possono essere ripristinate",
|
||||
"deletedBoardsCannotbeRestored": "Le bacheche eliminate non possono essere ripristinate. Selezionando \"Elimina solo bacheca\" le immagini verranno spostate nella bacheca \"Non categorizzato\".",
|
||||
"movingImagesToBoard_one": "Spostare {{count}} immagine nella bacheca:",
|
||||
"movingImagesToBoard_many": "Spostare {{count}} immagini nella bacheca:",
|
||||
"movingImagesToBoard_other": "Spostare {{count}} immagini nella bacheca:",
|
||||
@ -972,7 +972,8 @@
|
||||
"addPrivateBoard": "Aggiungi una Bacheca Privata",
|
||||
"noBoards": "Nessuna bacheca {{boardType}}",
|
||||
"hideBoards": "Nascondi bacheche",
|
||||
"viewBoards": "Visualizza bacheche"
|
||||
"viewBoards": "Visualizza bacheche",
|
||||
"deletedPrivateBoardsCannotbeRestored": "Le bacheche cancellate non possono essere ripristinate. Selezionando 'Cancella solo bacheca', le immagini verranno spostate nella bacheca \"Non categorizzato\" privata dell'autore dell'immagine."
|
||||
},
|
||||
"controlnet": {
|
||||
"contentShuffleDescription": "Rimescola il contenuto di un'immagine",
|
||||
@ -1516,6 +1517,30 @@
|
||||
"paragraphs": [
|
||||
"Metodo con cui applicare l'adattatore IP corrente."
|
||||
]
|
||||
},
|
||||
"scale": {
|
||||
"heading": "Scala",
|
||||
"paragraphs": [
|
||||
"La scala controlla la dimensione dell'immagine di uscita e si basa su un multiplo della risoluzione dell'immagine di ingresso. Ad esempio, un ampliamento 2x su un'immagine 1024x1024 produrrebbe in uscita a 2048x2048."
|
||||
]
|
||||
},
|
||||
"upscaleModel": {
|
||||
"paragraphs": [
|
||||
"Il modello di ampliamento ridimensiona l'immagine alle dimensioni di uscita prima che vengano aggiunti i dettagli. È possibile utilizzare qualsiasi modello di ampliamento supportato, ma alcuni sono specializzati per diversi tipi di immagini, come foto o disegni al tratto."
|
||||
],
|
||||
"heading": "Modello di ampliamento"
|
||||
},
|
||||
"creativity": {
|
||||
"heading": "Creatività",
|
||||
"paragraphs": [
|
||||
"La creatività controlla quanta libertà è concessa al modello quando si aggiungono dettagli. Una creatività bassa rimane vicina all'immagine originale, mentre una creatività alta consente più cambiamenti. Quando si usa un prompt, una creatività alta aumenta l'influenza del prompt."
|
||||
]
|
||||
},
|
||||
"structure": {
|
||||
"heading": "Struttura",
|
||||
"paragraphs": [
|
||||
"La struttura determina quanto l'immagine finale rispecchierà il layout dell'originale. Una struttura bassa permette cambiamenti significativi, mentre una struttura alta conserva la composizione e il layout originali."
|
||||
]
|
||||
}
|
||||
},
|
||||
"sdxl": {
|
||||
|
@ -109,7 +109,6 @@
|
||||
"drop": "ドロップ",
|
||||
"dropOrUpload": "$t(gallery.drop) またはアップロード",
|
||||
"deleteImage_other": "画像を削除",
|
||||
"deleteImageBin": "削除された画像はOSのゴミ箱に送られます。",
|
||||
"deleteImagePermanent": "削除された画像は復元できません。",
|
||||
"download": "ダウンロード",
|
||||
"unableToLoad": "ギャラリーをロードできません",
|
||||
|
@ -70,7 +70,6 @@
|
||||
"gallerySettings": "갤러리 설정",
|
||||
"deleteSelection": "선택 항목 삭제",
|
||||
"featuresWillReset": "이 이미지를 삭제하면 해당 기능이 즉시 재설정됩니다.",
|
||||
"deleteImageBin": "삭제된 이미지는 운영 체제의 Bin으로 전송됩니다.",
|
||||
"assets": "자산",
|
||||
"problemDeletingImagesDesc": "하나 이상의 이미지를 삭제할 수 없습니다",
|
||||
"noImagesInGallery": "보여줄 이미지가 없음",
|
||||
|
@ -97,7 +97,6 @@
|
||||
"noImagesInGallery": "Geen afbeeldingen om te tonen",
|
||||
"deleteImage_one": "Verwijder afbeelding",
|
||||
"deleteImage_other": "",
|
||||
"deleteImageBin": "Verwijderde afbeeldingen worden naar de prullenbak van je besturingssysteem gestuurd.",
|
||||
"deleteImagePermanent": "Verwijderde afbeeldingen kunnen niet worden hersteld.",
|
||||
"assets": "Eigen onderdelen",
|
||||
"autoAssignBoardOnClick": "Ken automatisch bord toe bij klikken",
|
||||
@ -467,10 +466,6 @@
|
||||
},
|
||||
"imageNotProcessedForControlAdapter": "De afbeelding van controle-adapter #{{number}} is niet verwerkt"
|
||||
},
|
||||
"isAllowedToUpscale": {
|
||||
"useX2Model": "Afbeelding is te groot om te vergroten met het x4-model. Gebruik hiervoor het x2-model",
|
||||
"tooLarge": "Afbeelding is te groot om te vergoten. Kies een kleinere afbeelding"
|
||||
},
|
||||
"patchmatchDownScaleSize": "Verklein",
|
||||
"useCpuNoise": "Gebruik CPU-ruis",
|
||||
"imageActions": "Afbeeldingshandeling",
|
||||
|
@ -100,7 +100,6 @@
|
||||
"loadMore": "Показать больше",
|
||||
"noImagesInGallery": "Изображений нет",
|
||||
"deleteImagePermanent": "Удаленные изображения невозможно восстановить.",
|
||||
"deleteImageBin": "Удаленные изображения будут отправлены в корзину вашей операционной системы.",
|
||||
"deleteImage_one": "Удалить изображение",
|
||||
"deleteImage_few": "Удалить {{count}} изображения",
|
||||
"deleteImage_many": "Удалить {{count}} изображений",
|
||||
@ -567,10 +566,6 @@
|
||||
"ipAdapterNoImageSelected": "изображение IP-адаптера не выбрано"
|
||||
}
|
||||
},
|
||||
"isAllowedToUpscale": {
|
||||
"useX2Model": "Изображение слишком велико для увеличения с помощью модели x4. Используйте модель x2",
|
||||
"tooLarge": "Изображение слишком велико для увеличения. Выберите изображение меньшего размера"
|
||||
},
|
||||
"cfgRescaleMultiplier": "Множитель масштабирования CFG",
|
||||
"patchmatchDownScaleSize": "уменьшить",
|
||||
"useCpuNoise": "Использовать шум CPU",
|
||||
|
@ -278,7 +278,6 @@
|
||||
"enable": "Aç"
|
||||
},
|
||||
"gallery": {
|
||||
"deleteImageBin": "Silinen görseller işletim sisteminin çöp kutusuna gönderilir.",
|
||||
"deleteImagePermanent": "Silinen görseller geri getirilemez.",
|
||||
"assets": "Özkaynaklar",
|
||||
"autoAssignBoardOnClick": "Tıklanan Panoya Otomatik Atama",
|
||||
@ -622,10 +621,6 @@
|
||||
"controlNetControlMode": "Yönetim Kipi",
|
||||
"general": "Genel",
|
||||
"seamlessYAxis": "Dikişsiz Döşeme Y Ekseni",
|
||||
"isAllowedToUpscale": {
|
||||
"tooLarge": "Görsel, büyütme işlemi için çok büyük, daha küçük bir boyut seçin",
|
||||
"useX2Model": "Görsel 4 kat büyütme işlemi için çok geniş, 2 kat büyütmeyi kullanın"
|
||||
},
|
||||
"maskBlur": "Bulandırma",
|
||||
"images": "Görseller",
|
||||
"info": "Bilgi",
|
||||
|
@ -6,7 +6,7 @@
|
||||
"settingsLabel": "设置",
|
||||
"img2img": "图生图",
|
||||
"unifiedCanvas": "统一画布",
|
||||
"nodes": "工作流编辑器",
|
||||
"nodes": "工作流",
|
||||
"upload": "上传",
|
||||
"load": "加载",
|
||||
"statusDisconnected": "未连接",
|
||||
@ -86,7 +86,12 @@
|
||||
"editing": "编辑中",
|
||||
"green": "绿",
|
||||
"blue": "蓝",
|
||||
"editingDesc": "在控制图层画布上编辑"
|
||||
"editingDesc": "在控制图层画布上编辑",
|
||||
"goTo": "前往",
|
||||
"dontShowMeThese": "请勿显示这些内容",
|
||||
"beta": "测试版",
|
||||
"toResolve": "解决",
|
||||
"tab": "标签页"
|
||||
},
|
||||
"gallery": {
|
||||
"galleryImageSize": "预览大小",
|
||||
@ -94,8 +99,7 @@
|
||||
"autoSwitchNewImages": "自动切换到新图像",
|
||||
"loadMore": "加载更多",
|
||||
"noImagesInGallery": "无图像可用于显示",
|
||||
"deleteImage_other": "删除图片",
|
||||
"deleteImageBin": "被删除的图片会发送到你操作系统的回收站。",
|
||||
"deleteImage_other": "删除{{count}}张图片",
|
||||
"deleteImagePermanent": "删除的图片无法被恢复。",
|
||||
"assets": "素材",
|
||||
"autoAssignBoardOnClick": "点击后自动分配面板",
|
||||
@ -133,7 +137,24 @@
|
||||
"hover": "悬停",
|
||||
"selectAllOnPage": "选择本页全部",
|
||||
"swapImages": "交换图像",
|
||||
"compareOptions": "比较选项"
|
||||
"compareOptions": "比较选项",
|
||||
"exitBoardSearch": "退出面板搜索",
|
||||
"exitSearch": "退出搜索",
|
||||
"oldestFirst": "最旧在前",
|
||||
"sortDirection": "排序方向",
|
||||
"showStarredImagesFirst": "优先显示收藏的图片",
|
||||
"compareHelp3": "按 <Kbd>C</Kbd> 键对调正在比较的图片。",
|
||||
"showArchivedBoards": "显示已归档的面板",
|
||||
"newestFirst": "最新在前",
|
||||
"compareHelp4": "按 <Kbd>Z</Kbd>或 <Kbd>Esc</Kbd> 键退出。",
|
||||
"searchImages": "按元数据搜索",
|
||||
"jump": "跳过",
|
||||
"compareHelp2": "按 <Kbd>M</Kbd> 键切换不同的比较模式。",
|
||||
"displayBoardSearch": "显示面板搜索",
|
||||
"displaySearch": "显示搜索",
|
||||
"stretchToFit": "拉伸以适应",
|
||||
"exitCompare": "退出对比",
|
||||
"compareHelp1": "在点击图库中的图片或使用箭头键切换比较图片时,请按住<Kbd>Alt</Kbd> 键。"
|
||||
},
|
||||
"hotkeys": {
|
||||
"keyboardShortcuts": "快捷键",
|
||||
@ -348,7 +369,19 @@
|
||||
"desc": "打开和关闭选项和图库面板",
|
||||
"title": "开关选项和图库"
|
||||
},
|
||||
"clearSearch": "清除检索项"
|
||||
"clearSearch": "清除检索项",
|
||||
"toggleViewer": {
|
||||
"desc": "在当前标签页的图片查看模式和编辑工作区之间切换.",
|
||||
"title": "切换图片查看器"
|
||||
},
|
||||
"postProcess": {
|
||||
"desc": "使用选定的后期处理模型对当前图像进行处理",
|
||||
"title": "处理图像"
|
||||
},
|
||||
"remixImage": {
|
||||
"title": "重新混合图像",
|
||||
"desc": "使用当前图像的所有参数,但不包括随机种子"
|
||||
}
|
||||
},
|
||||
"modelManager": {
|
||||
"modelManager": "模型管理器",
|
||||
@ -396,14 +429,71 @@
|
||||
"modelConversionFailed": "模型转换失败",
|
||||
"baseModel": "基底模型",
|
||||
"convertingModelBegin": "模型转换中. 请稍候.",
|
||||
"predictionType": "预测类型(适用于 Stable Diffusion 2.x 模型和部分 Stable Diffusion 1.x 模型)",
|
||||
"predictionType": "预测类型",
|
||||
"advanced": "高级",
|
||||
"modelType": "模型类别",
|
||||
"variant": "变体",
|
||||
"vae": "VAE",
|
||||
"alpha": "Alpha",
|
||||
"vaePrecision": "VAE 精度",
|
||||
"noModelSelected": "无选中的模型"
|
||||
"noModelSelected": "无选中的模型",
|
||||
"modelImageUpdateFailed": "模型图像更新失败",
|
||||
"scanFolder": "扫描文件夹",
|
||||
"path": "路径",
|
||||
"pathToConfig": "配置路径",
|
||||
"cancel": "取消",
|
||||
"hfTokenUnableToVerify": "无法验证HuggingFace token",
|
||||
"install": "安装",
|
||||
"simpleModelPlaceholder": "本地文件或diffusers文件夹的URL或路径",
|
||||
"hfTokenInvalidErrorMessage": "无效或缺失的HuggingFace token.",
|
||||
"noModelsInstalledDesc1": "安装模型时使用",
|
||||
"inplaceInstallDesc": "安装模型时,不复制文件,直接从原位置加载。如果关闭此选项,模型文件将在安装过程中被复制到Invoke管理的模型文件夹中.",
|
||||
"installAll": "安装全部",
|
||||
"noModelsInstalled": "无已安装的模型",
|
||||
"urlOrLocalPathHelper": "链接应该指向单个文件.本地路径可以指向单个文件,或者对于单个扩散模型(diffusers model),可以指向一个文件夹.",
|
||||
"modelSettings": "模型设置",
|
||||
"useDefaultSettings": "使用默认设置",
|
||||
"scanPlaceholder": "本地文件夹路径",
|
||||
"installRepo": "安装仓库",
|
||||
"modelImageDeleted": "模型图像已删除",
|
||||
"modelImageDeleteFailed": "模型图像删除失败",
|
||||
"scanFolderHelper": "此文件夹将进行递归扫描以寻找模型.对于大型文件夹,这可能需要一些时间.",
|
||||
"scanResults": "扫描结果",
|
||||
"noMatchingModels": "无匹配的模型",
|
||||
"pruneTooltip": "清理队列中已完成的导入任务",
|
||||
"urlOrLocalPath": "链接或本地路径",
|
||||
"localOnly": "仅本地",
|
||||
"hfTokenHelperText": "需要HuggingFace token才能使用Checkpoint模型。点击此处创建或获取您的token.",
|
||||
"huggingFaceHelper": "如果在此代码库中检测到多个模型,系统将提示您选择其中一个进行安装.",
|
||||
"hfTokenUnableToVerifyErrorMessage": "无法验证HuggingFace token.可能是网络问题所致.请稍后再试.",
|
||||
"hfTokenSaved": "HuggingFace token已保存",
|
||||
"imageEncoderModelId": "图像编码器模型ID",
|
||||
"modelImageUpdated": "模型图像已更新",
|
||||
"modelName": "模型名称",
|
||||
"prune": "清理",
|
||||
"repoVariant": "代码库版本",
|
||||
"defaultSettings": "默认设置",
|
||||
"inplaceInstall": "就地安装",
|
||||
"main": "主界面",
|
||||
"starterModels": "初始模型",
|
||||
"installQueue": "安装队列",
|
||||
"hfTokenInvalidErrorMessage2": "更新于其中 ",
|
||||
"hfTokenInvalid": "无效或缺失的HuggingFace token",
|
||||
"mainModelTriggerPhrases": "主模型触发词",
|
||||
"typePhraseHere": "在此输入触发词",
|
||||
"triggerPhrases": "触发词",
|
||||
"metadata": "元数据",
|
||||
"deleteModelImage": "删除模型图片",
|
||||
"edit": "编辑",
|
||||
"source": "来源",
|
||||
"uploadImage": "上传图像",
|
||||
"addModels": "添加模型",
|
||||
"textualInversions": "文本逆向生成",
|
||||
"upcastAttention": "是否为高精度权重",
|
||||
"defaultSettingsSaved": "默认设置已保存",
|
||||
"huggingFacePlaceholder": "所有者或模型名称",
|
||||
"huggingFaceRepoID": "HuggingFace仓库ID",
|
||||
"loraTriggerPhrases": "LoRA 触发词"
|
||||
},
|
||||
"parameters": {
|
||||
"images": "图像",
|
||||
@ -446,7 +536,7 @@
|
||||
"scheduler": "调度器",
|
||||
"general": "通用",
|
||||
"controlNetControlMode": "控制模式",
|
||||
"maskBlur": "模糊",
|
||||
"maskBlur": "遮罩模糊",
|
||||
"invoke": {
|
||||
"noNodesInGraph": "节点图中无节点",
|
||||
"noModelSelected": "无已选中的模型",
|
||||
@ -460,7 +550,21 @@
|
||||
"noPrompts": "没有已生成的提示词",
|
||||
"noControlImageForControlAdapter": "有 #{{number}} 个 Control Adapter 缺失控制图像",
|
||||
"noModelForControlAdapter": "有 #{{number}} 个 Control Adapter 没有选择模型。",
|
||||
"incompatibleBaseModelForControlAdapter": "有 #{{number}} 个 Control Adapter 模型与主模型不兼容。"
|
||||
"incompatibleBaseModelForControlAdapter": "有 #{{number}} 个 Control Adapter 模型与主模型不兼容。",
|
||||
"layer": {
|
||||
"initialImageNoImageSelected": "未选择初始图像",
|
||||
"controlAdapterImageNotProcessed": "Control Adapter图像尚未处理",
|
||||
"ipAdapterNoModelSelected": "未选择IP adapter",
|
||||
"controlAdapterNoModelSelected": "未选择Control Adapter模型",
|
||||
"controlAdapterNoImageSelected": "未选择Control Adapter图像",
|
||||
"rgNoPromptsOrIPAdapters": "无文本提示或IP Adapters",
|
||||
"controlAdapterIncompatibleBaseModel": "Control Adapter的基础模型不兼容",
|
||||
"ipAdapterIncompatibleBaseModel": "IP Adapter的基础模型不兼容",
|
||||
"t2iAdapterIncompatibleDimensions": "T2I Adapter需要图像尺寸为{{multiple}}的倍数",
|
||||
"ipAdapterNoImageSelected": "未选择IP Adapter图像",
|
||||
"rgNoRegion": "未选择区域"
|
||||
},
|
||||
"imageNotProcessedForControlAdapter": "Control Adapter #{{number}} 的图像未处理"
|
||||
},
|
||||
"patchmatchDownScaleSize": "缩小",
|
||||
"clipSkip": "CLIP 跳过层",
|
||||
@ -468,10 +572,6 @@
|
||||
"coherenceMode": "模式",
|
||||
"imageActions": "图像操作",
|
||||
"iterations": "迭代数",
|
||||
"isAllowedToUpscale": {
|
||||
"useX2Model": "图像太大,无法使用 x4 模型,使用 x2 模型作为替代",
|
||||
"tooLarge": "图像太大无法进行放大,请选择更小的图像"
|
||||
},
|
||||
"cfgRescaleMultiplier": "CFG 重缩放倍数",
|
||||
"useSize": "使用尺寸",
|
||||
"setToOptimalSize": "优化模型大小",
|
||||
@ -479,7 +579,21 @@
|
||||
"lockAspectRatio": "锁定纵横比",
|
||||
"swapDimensions": "交换尺寸",
|
||||
"aspect": "纵横",
|
||||
"setToOptimalSizeTooLarge": "$t(parameters.setToOptimalSize) (可能过大)"
|
||||
"setToOptimalSizeTooLarge": "$t(parameters.setToOptimalSize) (可能过大)",
|
||||
"globalNegativePromptPlaceholder": "全局反向提示词",
|
||||
"remixImage": "重新混合图像",
|
||||
"coherenceEdgeSize": "边缘尺寸",
|
||||
"postProcessing": "后处理(Shift + U)",
|
||||
"infillMosaicTileWidth": "瓦片宽度",
|
||||
"sendToUpscale": "发送到放大",
|
||||
"processImage": "处理图像",
|
||||
"globalPositivePromptPlaceholder": "全局正向提示词",
|
||||
"globalSettings": "全局设置",
|
||||
"infillMosaicTileHeight": "瓦片高度",
|
||||
"infillMosaicMinColor": "最小颜色",
|
||||
"infillMosaicMaxColor": "最大颜色",
|
||||
"infillColorValue": "填充颜色",
|
||||
"coherenceMinDenoise": "最小去噪"
|
||||
},
|
||||
"settings": {
|
||||
"models": "模型",
|
||||
@ -509,7 +623,9 @@
|
||||
"enableNSFWChecker": "启用成人内容检测器",
|
||||
"enableInvisibleWatermark": "启用不可见水印",
|
||||
"enableInformationalPopovers": "启用信息弹窗",
|
||||
"reloadingIn": "重新加载中"
|
||||
"reloadingIn": "重新加载中",
|
||||
"informationalPopoversDisabled": "信息提示框已禁用",
|
||||
"informationalPopoversDisabledDesc": "信息提示框已被禁用.请在设置中重新启用."
|
||||
},
|
||||
"toast": {
|
||||
"uploadFailed": "上传失败",
|
||||
@ -518,16 +634,16 @@
|
||||
"canvasMerged": "画布已合并",
|
||||
"sentToImageToImage": "已发送到图生图",
|
||||
"sentToUnifiedCanvas": "已发送到统一画布",
|
||||
"parametersNotSet": "参数未设定",
|
||||
"parametersNotSet": "参数未恢复",
|
||||
"metadataLoadFailed": "加载元数据失败",
|
||||
"uploadFailedInvalidUploadDesc": "必须是单张的 PNG 或 JPEG 图片",
|
||||
"connected": "服务器连接",
|
||||
"parameterSet": "参数已设定",
|
||||
"parameterNotSet": "参数未设定",
|
||||
"parameterSet": "参数已恢复",
|
||||
"parameterNotSet": "参数未恢复",
|
||||
"serverError": "服务器错误",
|
||||
"canceled": "处理取消",
|
||||
"problemCopyingImage": "无法复制图像",
|
||||
"modelAddedSimple": "已添加模型",
|
||||
"modelAddedSimple": "模型已加入队列",
|
||||
"imageSavingFailed": "图像保存失败",
|
||||
"canvasSentControlnetAssets": "画布已发送到 ControlNet & 素材",
|
||||
"problemCopyingCanvasDesc": "无法导出基础层",
|
||||
@ -557,12 +673,28 @@
|
||||
"canvasSavedGallery": "画布已保存到图库",
|
||||
"imageUploadFailed": "图像上传失败",
|
||||
"problemImportingMask": "导入遮罩时出现问题",
|
||||
"baseModelChangedCleared_other": "基础模型已更改, 已清除或禁用 {{count}} 个不兼容的子模型",
|
||||
"baseModelChangedCleared_other": "已清除或禁用{{count}}个不兼容的子模型",
|
||||
"setAsCanvasInitialImage": "设为画布初始图像",
|
||||
"invalidUpload": "无效的上传",
|
||||
"problemDeletingWorkflow": "删除工作流时出现问题",
|
||||
"workflowDeleted": "已删除工作流",
|
||||
"problemRetrievingWorkflow": "检索工作流时发生问题"
|
||||
"problemRetrievingWorkflow": "检索工作流时发生问题",
|
||||
"baseModelChanged": "基础模型已更改",
|
||||
"problemDownloadingImage": "无法下载图像",
|
||||
"outOfMemoryError": "内存不足错误",
|
||||
"parameters": "参数",
|
||||
"resetInitialImage": "重置初始图像",
|
||||
"parameterNotSetDescWithMessage": "无法恢复 {{parameter}}: {{message}}",
|
||||
"parameterSetDesc": "已恢复 {{parameter}}",
|
||||
"parameterNotSetDesc": "无法恢复{{parameter}}",
|
||||
"sessionRef": "会话: {{sessionId}}",
|
||||
"somethingWentWrong": "出现错误",
|
||||
"prunedQueue": "已清理队列",
|
||||
"uploadInitialImage": "上传初始图像",
|
||||
"outOfMemoryErrorDesc": "您当前的生成设置已超出系统处理能力.请调整设置后再次尝试.",
|
||||
"parametersSet": "参数已恢复",
|
||||
"errorCopied": "错误信息已复制",
|
||||
"modelImportCanceled": "模型导入已取消"
|
||||
},
|
||||
"unifiedCanvas": {
|
||||
"layer": "图层",
|
||||
@ -616,7 +748,15 @@
|
||||
"antialiasing": "抗锯齿",
|
||||
"showResultsOn": "显示结果 (开)",
|
||||
"showResultsOff": "显示结果 (关)",
|
||||
"saveMask": "保存 $t(unifiedCanvas.mask)"
|
||||
"saveMask": "保存 $t(unifiedCanvas.mask)",
|
||||
"coherenceModeBoxBlur": "盒子模糊",
|
||||
"showBoundingBox": "显示边界框",
|
||||
"coherenceModeGaussianBlur": "高斯模糊",
|
||||
"coherenceModeStaged": "分阶段",
|
||||
"hideBoundingBox": "隐藏边界框",
|
||||
"initialFitImageSize": "在拖放时调整图像大小以适配",
|
||||
"invertBrushSizeScrollDirection": "反转滚动操作以调整画笔大小",
|
||||
"discardCurrent": "放弃当前设置"
|
||||
},
|
||||
"accessibility": {
|
||||
"invokeProgressBar": "Invoke 进度条",
|
||||
@ -746,11 +886,11 @@
|
||||
"unableToExtractSchemaNameFromRef": "无法从参考中提取架构名",
|
||||
"unknownOutput": "未知输出:{{name}}",
|
||||
"unknownErrorValidatingWorkflow": "验证工作流时出现未知错误",
|
||||
"collectionFieldType": "{{name}} 合集",
|
||||
"collectionFieldType": "{{name}}(合集)",
|
||||
"unknownNodeType": "未知节点类型",
|
||||
"targetNodeDoesNotExist": "无效的边缘:{{node}} 的目标/输入节点不存在",
|
||||
"unknownFieldType": "$t(nodes.unknownField) 类型:{{type}}",
|
||||
"collectionOrScalarFieldType": "{{name}} 合集 | 标量",
|
||||
"collectionOrScalarFieldType": "{{name}} (单一项目或项目集合)",
|
||||
"nodeVersion": "节点版本",
|
||||
"deletedInvalidEdge": "已删除无效的边缘 {{source}} -> {{target}}",
|
||||
"unknownInput": "未知输入:{{name}}",
|
||||
@ -759,7 +899,27 @@
|
||||
"newWorkflow": "新建工作流",
|
||||
"newWorkflowDesc": "是否创建一个新的工作流?",
|
||||
"newWorkflowDesc2": "当前工作流有未保存的更改。",
|
||||
"unsupportedAnyOfLength": "联合(union)数据类型数目过多 ({{count}})"
|
||||
"unsupportedAnyOfLength": "联合(union)数据类型数目过多 ({{count}})",
|
||||
"resetToDefaultValue": "重置为默认值",
|
||||
"clearWorkflowDesc2": "您当前的工作流有未保存的更改.",
|
||||
"missingNode": "缺少调用节点",
|
||||
"missingInvocationTemplate": "缺少调用模版",
|
||||
"noFieldsViewMode": "此工作流程未选择任何要显示的字段.请查看完整工作流程以进行配置.",
|
||||
"reorderLinearView": "调整线性视图顺序",
|
||||
"viewMode": "在线性视图中使用",
|
||||
"showEdgeLabelsHelp": "在边缘上显示标签,指示连接的节点",
|
||||
"cannotMixAndMatchCollectionItemTypes": "集合项目类型不能混用",
|
||||
"missingFieldTemplate": "缺少字段模板",
|
||||
"editMode": "在工作流编辑器中编辑",
|
||||
"showEdgeLabels": "显示边缘标签",
|
||||
"clearWorkflowDesc": "是否清除当前工作流并创建新的?",
|
||||
"graph": "图表",
|
||||
"noGraph": "无图表",
|
||||
"edit": "编辑",
|
||||
"clearWorkflow": "清除工作流",
|
||||
"imageAccessError": "无法找到图像 {{image_name}},正在恢复默认设置",
|
||||
"boardAccessError": "无法找到面板 {{board_id}},正在恢复默认设置",
|
||||
"modelAccessError": "无法找到模型 {{key}},正在恢复默认设置"
|
||||
},
|
||||
"controlnet": {
|
||||
"resize": "直接缩放",
|
||||
@ -799,7 +959,7 @@
|
||||
"mediapipeFaceDescription": "使用 Mediapipe 检测面部",
|
||||
"depthZoeDescription": "使用 Zoe 生成深度图",
|
||||
"hedDescription": "整体嵌套边缘检测",
|
||||
"setControlImageDimensions": "设定控制图像尺寸宽/高为",
|
||||
"setControlImageDimensions": "复制尺寸到宽度/高度(为模型优化)",
|
||||
"amult": "角度倍率 (a_mult)",
|
||||
"bgth": "背景移除阈值 (bg_th)",
|
||||
"lineartAnimeDescription": "动漫风格线稿处理",
|
||||
@ -810,7 +970,7 @@
|
||||
"addControlNet": "添加 $t(common.controlNet)",
|
||||
"addIPAdapter": "添加 $t(common.ipAdapter)",
|
||||
"safe": "保守模式",
|
||||
"scribble": "草绘 (scribble)",
|
||||
"scribble": "草绘",
|
||||
"maxFaces": "最大面部数",
|
||||
"pidi": "PIDI",
|
||||
"normalBae": "Normal BAE",
|
||||
@ -925,7 +1085,8 @@
|
||||
"steps": "步数",
|
||||
"posStylePrompt": "正向样式提示词",
|
||||
"refiner": "Refiner",
|
||||
"freePromptStyle": "手动输入样式提示词"
|
||||
"freePromptStyle": "手动输入样式提示词",
|
||||
"refinerSteps": "精炼步数"
|
||||
},
|
||||
"metadata": {
|
||||
"positivePrompt": "正向提示词",
|
||||
@ -952,7 +1113,12 @@
|
||||
"recallParameters": "召回参数",
|
||||
"noRecallParameters": "未找到要召回的参数",
|
||||
"vae": "VAE",
|
||||
"cfgRescaleMultiplier": "$t(parameters.cfgRescaleMultiplier)"
|
||||
"cfgRescaleMultiplier": "$t(parameters.cfgRescaleMultiplier)",
|
||||
"allPrompts": "所有提示",
|
||||
"parsingFailed": "解析失败",
|
||||
"recallParameter": "调用{{label}}",
|
||||
"imageDimensions": "图像尺寸",
|
||||
"parameterSet": "已设置参数{{parameter}}"
|
||||
},
|
||||
"models": {
|
||||
"noMatchingModels": "无相匹配的模型",
|
||||
@ -965,7 +1131,8 @@
|
||||
"esrganModel": "ESRGAN 模型",
|
||||
"addLora": "添加 LoRA",
|
||||
"lora": "LoRA",
|
||||
"defaultVAE": "默认 VAE"
|
||||
"defaultVAE": "默认 VAE",
|
||||
"concepts": "概念"
|
||||
},
|
||||
"boards": {
|
||||
"autoAddBoard": "自动添加面板",
|
||||
@ -987,8 +1154,23 @@
|
||||
"deleteBoardOnly": "仅删除面板",
|
||||
"deleteBoard": "删除面板",
|
||||
"deleteBoardAndImages": "删除面板和图像",
|
||||
"deletedBoardsCannotbeRestored": "已删除的面板无法被恢复",
|
||||
"movingImagesToBoard_other": "移动 {{count}} 张图像到面板:"
|
||||
"deletedBoardsCannotbeRestored": "删除的面板无法恢复。选择“仅删除面板”选项后,相关图片将会被移至未分类区域。",
|
||||
"movingImagesToBoard_other": "移动 {{count}} 张图像到面板:",
|
||||
"selectedForAutoAdd": "已选中自动添加",
|
||||
"hideBoards": "隐藏面板",
|
||||
"noBoards": "没有{{boardType}}类型的面板",
|
||||
"unarchiveBoard": "恢复面板",
|
||||
"viewBoards": "查看面板",
|
||||
"addPrivateBoard": "创建私密面板",
|
||||
"addSharedBoard": "创建共享面板",
|
||||
"boards": "面板",
|
||||
"imagesWithCount_other": "{{count}}张图片",
|
||||
"deletedPrivateBoardsCannotbeRestored": "删除的面板无法恢复。选择“仅删除面板”后,相关图片将会被移至图片创建者的私密未分类区域。",
|
||||
"private": "私密面板",
|
||||
"shared": "共享面板",
|
||||
"archiveBoard": "归档面板",
|
||||
"archived": "已归档",
|
||||
"assetsWithCount_other": "{{count}}项资源"
|
||||
},
|
||||
"dynamicPrompts": {
|
||||
"seedBehaviour": {
|
||||
@ -1030,32 +1212,33 @@
|
||||
"paramVAEPrecision": {
|
||||
"heading": "VAE 精度",
|
||||
"paragraphs": [
|
||||
"VAE 编解码过程种使用的精度。FP16/半精度以微小的图像变化为代价提高效率。"
|
||||
"在VAE编码和解码过程中使用的精度.",
|
||||
"Fp16/半精度更高效,但可能会造成图像的一些微小差异."
|
||||
]
|
||||
},
|
||||
"compositingCoherenceMode": {
|
||||
"heading": "模式",
|
||||
"paragraphs": [
|
||||
"一致性层模式。"
|
||||
"用于将新生成的遮罩区域与原图像融合的方法."
|
||||
]
|
||||
},
|
||||
"controlNetResizeMode": {
|
||||
"heading": "缩放模式",
|
||||
"paragraphs": [
|
||||
"ControlNet 输入图像适应输出图像大小的方法。"
|
||||
"调整Control Adapter输入图像大小以适应输出图像尺寸的方法."
|
||||
]
|
||||
},
|
||||
"clipSkip": {
|
||||
"paragraphs": [
|
||||
"选择要跳过 CLIP 模型多少层。",
|
||||
"部分模型跳过特定数值的层时效果会更好。"
|
||||
"跳过CLIP模型的层数.",
|
||||
"某些模型更适合结合CLIP Skip功能使用."
|
||||
],
|
||||
"heading": "CLIP 跳过层"
|
||||
},
|
||||
"paramModel": {
|
||||
"heading": "模型",
|
||||
"paragraphs": [
|
||||
"用于去噪过程的模型。"
|
||||
"用于图像生成的模型.不同的模型经过训练,专门用于产生不同的美学效果和内容."
|
||||
]
|
||||
},
|
||||
"paramIterations": {
|
||||
@ -1087,19 +1270,21 @@
|
||||
"paramScheduler": {
|
||||
"heading": "调度器",
|
||||
"paragraphs": [
|
||||
"调度器 (采样器) 定义如何在图像迭代过程中添加噪声,或者定义如何根据一个模型的输出来更新采样。"
|
||||
"生成过程中所使用的调度器.",
|
||||
"每个调度器决定了在生成过程中如何逐步向图像添加噪声,或者如何根据模型的输出更新样本."
|
||||
]
|
||||
},
|
||||
"controlNetWeight": {
|
||||
"heading": "权重",
|
||||
"paragraphs": [
|
||||
"ControlNet 对生成图像的影响强度。"
|
||||
"Control Adapter的权重.权重越高,对最终图像的影响越大."
|
||||
]
|
||||
},
|
||||
"paramCFGScale": {
|
||||
"heading": "CFG 等级",
|
||||
"paragraphs": [
|
||||
"控制提示词对生成过程的影响程度。"
|
||||
"控制提示对生成过程的影响程度.",
|
||||
"较高的CFG比例值可能会导致生成结果过度饱和和扭曲. "
|
||||
]
|
||||
},
|
||||
"paramSteps": {
|
||||
@ -1117,28 +1302,29 @@
|
||||
]
|
||||
},
|
||||
"lora": {
|
||||
"heading": "LoRA 权重",
|
||||
"heading": "LoRA",
|
||||
"paragraphs": [
|
||||
"更高的 LoRA 权重会对最终图像产生更大的影响。"
|
||||
"与基础模型结合使用的轻量级模型."
|
||||
]
|
||||
},
|
||||
"infillMethod": {
|
||||
"heading": "填充方法",
|
||||
"paragraphs": [
|
||||
"填充选定区域的方式。"
|
||||
"在重绘过程中使用的填充方法."
|
||||
]
|
||||
},
|
||||
"controlNetBeginEnd": {
|
||||
"heading": "开始 / 结束步数百分比",
|
||||
"paragraphs": [
|
||||
"去噪过程中在哪部分步数应用 ControlNet。",
|
||||
"在组合处理开始阶段应用 ControlNet,且在引导细节生成的结束阶段应用 ControlNet。"
|
||||
"去噪过程中将应用Control Adapter 的部分.",
|
||||
"通常,在去噪过程初期应用的Control Adapters用于指导整体构图,而在后期应用的Control Adapters则用于调整细节。"
|
||||
]
|
||||
},
|
||||
"scaleBeforeProcessing": {
|
||||
"heading": "处理前缩放",
|
||||
"paragraphs": [
|
||||
"生成图像前将所选区域缩放为最适合模型的大小。"
|
||||
"\"自动\"选项会在图像生成之前将所选区域调整到最适合模型的大小.",
|
||||
"\"手动\"选项允许您在图像生成之前自行选择所选区域的宽度和高度."
|
||||
]
|
||||
},
|
||||
"paramDenoisingStrength": {
|
||||
@ -1152,13 +1338,13 @@
|
||||
"heading": "种子",
|
||||
"paragraphs": [
|
||||
"控制用于生成的起始噪声。",
|
||||
"禁用 “随机种子” 来以相同设置生成相同的结果。"
|
||||
"禁用\"随机\"选项,以使用相同的生成设置产生一致的结果."
|
||||
]
|
||||
},
|
||||
"controlNetControlMode": {
|
||||
"heading": "控制模式",
|
||||
"paragraphs": [
|
||||
"给提示词或 ControlNet 增加更大的权重。"
|
||||
"在提示词和ControlNet之间分配更多的权重."
|
||||
]
|
||||
},
|
||||
"dynamicPrompts": {
|
||||
@ -1199,7 +1385,171 @@
|
||||
"paramCFGRescaleMultiplier": {
|
||||
"heading": "CFG 重缩放倍数",
|
||||
"paragraphs": [
|
||||
"CFG 引导的重缩放倍率,用于通过 zero-terminal SNR (ztsnr) 训练的模型。推荐设为 0.7。"
|
||||
"CFG指导的重缩放乘数,适用于使用零终端信噪比(ztsnr)训练的模型.",
|
||||
"对于这些模型,建议的数值为0.7."
|
||||
]
|
||||
},
|
||||
"imageFit": {
|
||||
"paragraphs": [
|
||||
"将初始图像调整到与输出图像相同的宽度和高度.建议启用此功能."
|
||||
],
|
||||
"heading": "将初始图像适配到输出大小"
|
||||
},
|
||||
"paramAspect": {
|
||||
"paragraphs": [
|
||||
"生成图像的宽高比.调整宽高比会相应地更新图像的宽度和高度.",
|
||||
"选择\"优化\"将把图像的宽度和高度设置为所选模型的最优尺寸."
|
||||
],
|
||||
"heading": "宽高比"
|
||||
},
|
||||
"refinerSteps": {
|
||||
"paragraphs": [
|
||||
"在图像生成过程中的细化阶段将执行的步骤数.",
|
||||
"与生成步骤相似."
|
||||
],
|
||||
"heading": "步数"
|
||||
},
|
||||
"compositingMaskBlur": {
|
||||
"heading": "遮罩模糊",
|
||||
"paragraphs": [
|
||||
"遮罩的模糊范围."
|
||||
]
|
||||
},
|
||||
"compositingCoherenceMinDenoise": {
|
||||
"paragraphs": [
|
||||
"连贯模式下的最小去噪力度",
|
||||
"在图像修复或重绘过程中,连贯区域的最小去噪力度"
|
||||
],
|
||||
"heading": "最小去噪"
|
||||
},
|
||||
"loraWeight": {
|
||||
"paragraphs": [
|
||||
"LoRA的权重,权重越高对最终图像的影响越大."
|
||||
],
|
||||
"heading": "权重"
|
||||
},
|
||||
"paramHrf": {
|
||||
"heading": "启用高分辨率修复",
|
||||
"paragraphs": [
|
||||
"以高于模型最优分辨率的大分辨率生成高质量图像.这通常用于防止生成图像中出现重复内容."
|
||||
]
|
||||
},
|
||||
"compositingCoherenceEdgeSize": {
|
||||
"paragraphs": [
|
||||
"连贯处理的边缘尺寸."
|
||||
],
|
||||
"heading": "边缘尺寸"
|
||||
},
|
||||
"paramWidth": {
|
||||
"paragraphs": [
|
||||
"生成图像的宽度.必须是8的倍数."
|
||||
],
|
||||
"heading": "宽度"
|
||||
},
|
||||
"refinerScheduler": {
|
||||
"paragraphs": [
|
||||
"在图像生成过程中的细化阶段所使用的调度程序.",
|
||||
"与生成调度程序相似."
|
||||
],
|
||||
"heading": "调度器"
|
||||
},
|
||||
"seamlessTilingXAxis": {
|
||||
"paragraphs": [
|
||||
"沿水平轴将图像进行无缝平铺."
|
||||
],
|
||||
"heading": "无缝平铺X轴"
|
||||
},
|
||||
"paramUpscaleMethod": {
|
||||
"heading": "放大方法",
|
||||
"paragraphs": [
|
||||
"用于高分辨率修复的图像放大方法."
|
||||
]
|
||||
},
|
||||
"refinerModel": {
|
||||
"paragraphs": [
|
||||
"在图像生成过程中的细化阶段所使用的模型.",
|
||||
"与生成模型相似."
|
||||
],
|
||||
"heading": "精炼模型"
|
||||
},
|
||||
"paramHeight": {
|
||||
"paragraphs": [
|
||||
"生成图像的高度.必须是8的倍数."
|
||||
],
|
||||
"heading": "高"
|
||||
},
|
||||
"patchmatchDownScaleSize": {
|
||||
"heading": "缩小",
|
||||
"paragraphs": [
|
||||
"在填充之前图像缩小的程度.",
|
||||
"较高的缩小比例会提升处理速度,但可能会降低图像质量."
|
||||
]
|
||||
},
|
||||
"seamlessTilingYAxis": {
|
||||
"heading": "Y轴上的无缝平铺",
|
||||
"paragraphs": [
|
||||
"沿垂直轴将图像进行无缝平铺."
|
||||
]
|
||||
},
|
||||
"ipAdapterMethod": {
|
||||
"paragraphs": [
|
||||
"当前IP Adapter的应用方法."
|
||||
],
|
||||
"heading": "方法"
|
||||
},
|
||||
"controlNetProcessor": {
|
||||
"paragraphs": [
|
||||
"处理输入图像以引导生成过程的方法.不同的处理器会在生成图像中产生不同的效果或风格."
|
||||
],
|
||||
"heading": "处理器"
|
||||
},
|
||||
"refinerPositiveAestheticScore": {
|
||||
"paragraphs": [
|
||||
"根据训练数据,对生成结果进行加权,使其更接近于具有高美学评分的图像."
|
||||
],
|
||||
"heading": "正面美学评分"
|
||||
},
|
||||
"refinerStart": {
|
||||
"paragraphs": [
|
||||
"在图像生成过程中精炼阶段开始被使用的时刻.",
|
||||
"0表示精炼器将全程参与图像生成,0.8表示细化器仅在生成过程的最后20%阶段被使用."
|
||||
],
|
||||
"heading": "精炼开始"
|
||||
},
|
||||
"refinerCfgScale": {
|
||||
"paragraphs": [
|
||||
"控制提示对生成过程的影响程度.",
|
||||
"与生成CFG Scale相似."
|
||||
]
|
||||
},
|
||||
"structure": {
|
||||
"heading": "结构",
|
||||
"paragraphs": [
|
||||
"结构决定了输出图像在多大程度上保持原始图像的布局.较低的结构设置允许进行较大的变化,而较高的结构设置则会严格保持原始图像的构图和布局."
|
||||
]
|
||||
},
|
||||
"creativity": {
|
||||
"paragraphs": [
|
||||
"创造力决定了模型在添加细节时的自由度.较低的创造力会使生成结果更接近原始图像,而较高的创造力则允许更多的变化.在使用提示时,较高的创造力会增加提示对生成结果的影响."
|
||||
],
|
||||
"heading": "创造力"
|
||||
},
|
||||
"refinerNegativeAestheticScore": {
|
||||
"paragraphs": [
|
||||
"根据训练数据,对生成结果进行加权,使其更接近于具有低美学评分的图像."
|
||||
],
|
||||
"heading": "负面美学评分"
|
||||
},
|
||||
"upscaleModel": {
|
||||
"heading": "放大模型",
|
||||
"paragraphs": [
|
||||
"上采样模型在添加细节之前将图像放大到输出尺寸.虽然可以使用任何支持的上采样模型,但有些模型更适合处理特定类型的图像,例如照片或线条画."
|
||||
]
|
||||
},
|
||||
"scale": {
|
||||
"heading": "缩放",
|
||||
"paragraphs": [
|
||||
"比例控制决定了输出图像的大小,它是基于输入图像分辨率的倍数来计算的.例如对一张1024x1024的图像进行2倍上采样,将会得到一张2048x2048的输出图像."
|
||||
]
|
||||
}
|
||||
},
|
||||
@ -1259,7 +1609,16 @@
|
||||
"updated": "已更新",
|
||||
"userWorkflows": "我的工作流",
|
||||
"projectWorkflows": "项目工作流",
|
||||
"opened": "已打开"
|
||||
"opened": "已打开",
|
||||
"noRecentWorkflows": "没有最近的工作流",
|
||||
"workflowCleared": "工作流已清除",
|
||||
"saveWorkflowToProject": "保存工作流到项目",
|
||||
"noWorkflows": "无工作流",
|
||||
"convertGraph": "转换图表",
|
||||
"loadWorkflow": "$t(common.load) 工作流",
|
||||
"noUserWorkflows": "没有用户工作流",
|
||||
"loadFromGraph": "从图表加载工作流",
|
||||
"autoLayout": "自动布局"
|
||||
},
|
||||
"app": {
|
||||
"storeNotInitialized": "商店尚未初始化"
|
||||
@ -1287,5 +1646,68 @@
|
||||
"prompt": {
|
||||
"addPromptTrigger": "添加提示词触发器",
|
||||
"noMatchingTriggers": "没有匹配的触发器"
|
||||
},
|
||||
"controlLayers": {
|
||||
"autoNegative": "自动反向",
|
||||
"opacityFilter": "透明度滤镜",
|
||||
"deleteAll": "删除所有",
|
||||
"moveForward": "向前移动",
|
||||
"layers_other": "层",
|
||||
"globalControlAdapterLayer": "全局 $t(controlnet.controlAdapter_one) $t(unifiedCanvas.layer)",
|
||||
"moveBackward": "向后移动",
|
||||
"regionalGuidance": "区域导向",
|
||||
"controlLayers": "控制层",
|
||||
"moveToBack": "移动到后面",
|
||||
"brushSize": "笔刷尺寸",
|
||||
"moveToFront": "移动到前面",
|
||||
"addLayer": "添加层",
|
||||
"deletePrompt": "删除提示词",
|
||||
"resetRegion": "重置区域",
|
||||
"debugLayers": "调试图层",
|
||||
"maskPreviewColor": "遮罩预览颜色",
|
||||
"addPositivePrompt": "添加 $t(common.positivePrompt)",
|
||||
"addNegativePrompt": "添加 $t(common.negativePrompt)",
|
||||
"addIPAdapter": "添加 $t(common.ipAdapter)",
|
||||
"globalIPAdapterLayer": "全局 $t(common.ipAdapter) $t(unifiedCanvas.layer)",
|
||||
"globalInitialImage": "全局初始图像",
|
||||
"noLayersAdded": "没有层被添加",
|
||||
"globalIPAdapter": "全局 $t(common.ipAdapter)",
|
||||
"resetProcessor": "重置处理器至默认值",
|
||||
"globalMaskOpacity": "全局遮罩透明度",
|
||||
"rectangle": "矩形",
|
||||
"opacity": "透明度",
|
||||
"clearProcessor": "清除处理器",
|
||||
"globalControlAdapter": "全局 $t(controlnet.controlAdapter_one)"
|
||||
},
|
||||
"ui": {
|
||||
"tabs": {
|
||||
"generation": "生成",
|
||||
"queue": "队列",
|
||||
"canvas": "画布",
|
||||
"upscaling": "放大中",
|
||||
"workflows": "工作流",
|
||||
"models": "模型"
|
||||
}
|
||||
},
|
||||
"upscaling": {
|
||||
"structure": "结构",
|
||||
"upscaleModel": "放大模型",
|
||||
"missingUpscaleModel": "缺少放大模型",
|
||||
"missingTileControlNetModel": "没有安装有效的tile ControlNet 模型",
|
||||
"missingUpscaleInitialImage": "缺少用于放大的原始图像",
|
||||
"creativity": "创造力",
|
||||
"postProcessingModel": "后处理模型",
|
||||
"scale": "缩放",
|
||||
"tileControlNetModelDesc": "根据所选的主模型架构,选择相应的Tile ControlNet模型",
|
||||
"upscaleModelDesc": "图像放大(图像到图像转换)模型",
|
||||
"postProcessingMissingModelWarning": "请访问 <LinkComponent>模型管理器</LinkComponent>来安装一个后处理(图像到图像转换)模型.",
|
||||
"missingModelsWarning": "请访问<LinkComponent>模型管理器</LinkComponent> 安装所需的模型:",
|
||||
"mainModelDesc": "主模型(SD1.5或SDXL架构)"
|
||||
},
|
||||
"upsell": {
|
||||
"inviteTeammates": "邀请团队成员",
|
||||
"professional": "专业",
|
||||
"professionalUpsell": "可在 Invoke 的专业版中使用.点击此处或访问 invoke.com/pricing 了解更多详情.",
|
||||
"shareAccess": "共享访问权限"
|
||||
}
|
||||
}
|
||||
|
@ -1,26 +1,40 @@
|
||||
/* eslint-disable no-console */
|
||||
import fs from 'node:fs';
|
||||
|
||||
import openapiTS from 'openapi-typescript';
|
||||
import openapiTS, { astToString } from 'openapi-typescript';
|
||||
import ts from 'typescript';
|
||||
|
||||
const OPENAPI_URL = 'http://127.0.0.1:9090/openapi.json';
|
||||
const OUTPUT_FILE = 'src/services/api/schema.ts';
|
||||
|
||||
async function generateTypes(schema) {
|
||||
process.stdout.write(`Generating types ${OUTPUT_FILE}...`);
|
||||
|
||||
// Use https://ts-ast-viewer.com to figure out how to create these AST nodes - define a type and use the bottom-left pane's output
|
||||
// `Blob` type
|
||||
const BLOB = ts.factory.createTypeReferenceNode(ts.factory.createIdentifier('Blob'));
|
||||
// `null` type
|
||||
const NULL = ts.factory.createLiteralTypeNode(ts.factory.createNull());
|
||||
// `Record<string, unknown>` type
|
||||
const RECORD_STRING_UNKNOWN = ts.factory.createTypeReferenceNode(ts.factory.createIdentifier('Record'), [
|
||||
ts.factory.createKeywordTypeNode(ts.SyntaxKind.StringKeyword),
|
||||
ts.factory.createKeywordTypeNode(ts.SyntaxKind.UnknownKeyword),
|
||||
]);
|
||||
|
||||
const types = await openapiTS(schema, {
|
||||
exportType: true,
|
||||
transform: (schemaObject) => {
|
||||
if ('format' in schemaObject && schemaObject.format === 'binary') {
|
||||
return schemaObject.nullable ? 'Blob | null' : 'Blob';
|
||||
return schemaObject.nullable ? ts.factory.createUnionTypeNode([BLOB, NULL]) : BLOB;
|
||||
}
|
||||
if (schemaObject.title === 'MetadataField') {
|
||||
// This is `Record<string, never>` by default, but it actually accepts any a dict of any valid JSON value.
|
||||
return 'Record<string, unknown>';
|
||||
return RECORD_STRING_UNKNOWN;
|
||||
}
|
||||
},
|
||||
defaultNonNullable: false,
|
||||
});
|
||||
fs.writeFileSync(OUTPUT_FILE, types);
|
||||
fs.writeFileSync(OUTPUT_FILE, astToString(types));
|
||||
process.stdout.write(`\nOK!\r\n`);
|
||||
}
|
||||
|
||||
|
@ -3,7 +3,7 @@ import { deepClone } from 'common/util/deepClone';
|
||||
import { isAnyGraphBuilt } from 'features/nodes/store/actions';
|
||||
import { appInfoApi } from 'services/api/endpoints/appInfo';
|
||||
import type { Graph } from 'services/api/types';
|
||||
import { socketInvocationProgress } from 'services/events/actions';
|
||||
import { socketGeneratorProgress } from 'services/events/actions';
|
||||
|
||||
export const actionSanitizer = <A extends UnknownAction>(action: A): A => {
|
||||
if (isAnyGraphBuilt(action)) {
|
||||
@ -24,10 +24,10 @@ export const actionSanitizer = <A extends UnknownAction>(action: A): A => {
|
||||
};
|
||||
}
|
||||
|
||||
if (socketInvocationProgress.match(action)) {
|
||||
if (socketGeneratorProgress.match(action)) {
|
||||
const sanitized = deepClone(action);
|
||||
if (sanitized.payload.data.image) {
|
||||
sanitized.payload.data.image.dataURL = '<Progress image omitted>';
|
||||
if (sanitized.payload.data.progress_image) {
|
||||
sanitized.payload.data.progress_image.dataURL = '<Progress image omitted>';
|
||||
}
|
||||
return sanitized;
|
||||
}
|
||||
|
@ -39,9 +39,9 @@ import { addDynamicPromptsListener } from 'app/store/middleware/listenerMiddlewa
|
||||
import { addSetDefaultSettingsListener } from 'app/store/middleware/listenerMiddleware/listeners/setDefaultSettings';
|
||||
import { addSocketConnectedEventListener } from 'app/store/middleware/listenerMiddleware/listeners/socketio/socketConnected';
|
||||
import { addSocketDisconnectedEventListener } from 'app/store/middleware/listenerMiddleware/listeners/socketio/socketDisconnected';
|
||||
import { addGeneratorProgressEventListener } from 'app/store/middleware/listenerMiddleware/listeners/socketio/socketGeneratorProgress';
|
||||
import { addInvocationCompleteEventListener } from 'app/store/middleware/listenerMiddleware/listeners/socketio/socketInvocationComplete';
|
||||
import { addInvocationErrorEventListener } from 'app/store/middleware/listenerMiddleware/listeners/socketio/socketInvocationError';
|
||||
import { addInvocationProgressEventListener } from 'app/store/middleware/listenerMiddleware/listeners/socketio/socketInvocationProgress';
|
||||
import { addInvocationStartedEventListener } from 'app/store/middleware/listenerMiddleware/listeners/socketio/socketInvocationStarted';
|
||||
import { addModelInstallEventListener } from 'app/store/middleware/listenerMiddleware/listeners/socketio/socketModelInstall';
|
||||
import { addModelLoadEventListener } from 'app/store/middleware/listenerMiddleware/listeners/socketio/socketModelLoad';
|
||||
@ -102,7 +102,7 @@ addStagingAreaImageSavedListener(startAppListening);
|
||||
addCommitStagingAreaImageListener(startAppListening);
|
||||
|
||||
// Socket.IO
|
||||
addInvocationProgressEventListener(startAppListening);
|
||||
addGeneratorProgressEventListener(startAppListening);
|
||||
addInvocationCompleteEventListener(startAppListening);
|
||||
addInvocationErrorEventListener(startAppListening);
|
||||
addInvocationStartedEventListener(startAppListening);
|
||||
|
@ -4,21 +4,21 @@ import { deepClone } from 'common/util/deepClone';
|
||||
import { parseify } from 'common/util/serialize';
|
||||
import { $nodeExecutionStates, upsertExecutionState } from 'features/nodes/hooks/useExecutionState';
|
||||
import { zNodeStatus } from 'features/nodes/types/invocation';
|
||||
import { socketInvocationProgress } from 'services/events/actions';
|
||||
import { socketGeneratorProgress } from 'services/events/actions';
|
||||
|
||||
const log = logger('socketio');
|
||||
|
||||
export const addInvocationProgressEventListener = (startAppListening: AppStartListening) => {
|
||||
export const addGeneratorProgressEventListener = (startAppListening: AppStartListening) => {
|
||||
startAppListening({
|
||||
actionCreator: socketInvocationProgress,
|
||||
actionCreator: socketGeneratorProgress,
|
||||
effect: (action) => {
|
||||
log.trace(parseify(action.payload), `Generator progress`);
|
||||
const { invocation_source_id, percentage, image } = action.payload.data;
|
||||
const { invocation_source_id, step, total_steps, progress_image } = action.payload.data;
|
||||
const nes = deepClone($nodeExecutionStates.get()[invocation_source_id]);
|
||||
if (nes) {
|
||||
nes.status = zNodeStatus.enum.IN_PROGRESS;
|
||||
nes.progress = percentage;
|
||||
nes.progressImage = image ?? null;
|
||||
nes.progress = (step + 1) / total_steps;
|
||||
nes.progressImage = progress_image ?? null;
|
||||
upsertExecutionState(nes.nodeId, nes);
|
||||
}
|
||||
},
|
@ -65,11 +65,15 @@ export type AppConfig = {
|
||||
*/
|
||||
shouldUpdateImagesOnConnect: boolean;
|
||||
shouldFetchMetadataFromApi: boolean;
|
||||
/**
|
||||
* Sets a size limit for outputs on the upscaling tab. This is a maximum dimension, so the actual max number of pixels
|
||||
* will be the square of this value.
|
||||
*/
|
||||
maxUpscaleDimension?: number;
|
||||
allowPrivateBoards: boolean;
|
||||
disabledTabs: InvokeTabName[];
|
||||
disabledFeatures: AppFeature[];
|
||||
disabledSDFeatures: SDFeature[];
|
||||
canRestoreDeletedImagesFromBin: boolean;
|
||||
nodesAllowlist: string[] | undefined;
|
||||
nodesDenylist: string[] | undefined;
|
||||
metadataFetchDebounce?: number;
|
||||
|
@ -16,6 +16,7 @@ import { selectWorkflowSettingsSlice } from 'features/nodes/store/workflowSettin
|
||||
import { isInvocationNode } from 'features/nodes/types/invocation';
|
||||
import { selectGenerationSlice } from 'features/parameters/store/generationSlice';
|
||||
import { selectUpscalelice } from 'features/parameters/store/upscaleSlice';
|
||||
import { selectConfigSlice } from 'features/system/store/configSlice';
|
||||
import { selectSystemSlice } from 'features/system/store/systemSlice';
|
||||
import { activeTabNameSelector } from 'features/ui/store/uiSelectors';
|
||||
import i18n from 'i18next';
|
||||
@ -42,6 +43,7 @@ const createSelector = (templates: Templates) =>
|
||||
selectControlLayersSlice,
|
||||
activeTabNameSelector,
|
||||
selectUpscalelice,
|
||||
selectConfigSlice,
|
||||
],
|
||||
(
|
||||
controlAdapters,
|
||||
@ -52,7 +54,8 @@ const createSelector = (templates: Templates) =>
|
||||
dynamicPrompts,
|
||||
controlLayers,
|
||||
activeTabName,
|
||||
upscale
|
||||
upscale,
|
||||
config
|
||||
) => {
|
||||
const { model } = generation;
|
||||
const { size } = controlLayers.present;
|
||||
@ -209,6 +212,16 @@ const createSelector = (templates: Templates) =>
|
||||
} else if (activeTabName === 'upscaling') {
|
||||
if (!upscale.upscaleInitialImage) {
|
||||
reasons.push({ content: i18n.t('upscaling.missingUpscaleInitialImage') });
|
||||
} else if (config.maxUpscaleDimension) {
|
||||
const { width, height } = upscale.upscaleInitialImage;
|
||||
const { scale } = upscale;
|
||||
|
||||
const maxPixels = config.maxUpscaleDimension ** 2;
|
||||
const upscaledPixels = width * scale * height * scale;
|
||||
|
||||
if (upscaledPixels > maxPixels) {
|
||||
reasons.push({ content: i18n.t('upscaling.exceedsMaxSize') });
|
||||
}
|
||||
}
|
||||
if (!upscale.upscaleModel) {
|
||||
reasons.push({ content: i18n.t('upscaling.missingUpscaleModel') });
|
||||
|
@ -10,7 +10,8 @@ const progressImageSelector = createMemoizedSelector([selectSystemSlice, selectC
|
||||
const { batchIds } = canvas;
|
||||
|
||||
return {
|
||||
progressImage: denoiseProgress && batchIds.includes(denoiseProgress.batch_id) ? denoiseProgress.image : undefined,
|
||||
progressImage:
|
||||
denoiseProgress && batchIds.includes(denoiseProgress.batch_id) ? denoiseProgress.progress_image : undefined,
|
||||
boundingBox: canvas.layerState.stagingArea.boundingBox,
|
||||
};
|
||||
});
|
||||
|
@ -42,6 +42,7 @@ const DepthAnythingProcessor = (props: Props) => {
|
||||
|
||||
const options: { label: string; value: DepthAnythingModelSize }[] = useMemo(
|
||||
() => [
|
||||
{ label: t('controlnet.depthAnythingSmallV2'), value: 'small_v2' },
|
||||
{ label: t('controlnet.small'), value: 'small' },
|
||||
{ label: t('controlnet.base'), value: 'base' },
|
||||
{ label: t('controlnet.large'), value: 'large' },
|
||||
|
@ -94,7 +94,7 @@ export const CONTROLNET_PROCESSORS: ControlNetProcessorsDict = {
|
||||
buildDefaults: (baseModel?: BaseModelType) => ({
|
||||
id: 'depth_anything_image_processor',
|
||||
type: 'depth_anything_image_processor',
|
||||
model_size: 'small',
|
||||
model_size: 'small_v2',
|
||||
resolution: baseModel === 'sdxl' ? 1024 : 512,
|
||||
}),
|
||||
},
|
||||
|
@ -84,7 +84,7 @@ export type RequiredDepthAnythingImageProcessorInvocation = O.Required<
|
||||
'type' | 'model_size' | 'resolution' | 'offload'
|
||||
>;
|
||||
|
||||
const zDepthAnythingModelSize = z.enum(['large', 'base', 'small']);
|
||||
const zDepthAnythingModelSize = z.enum(['large', 'base', 'small', 'small_v2']);
|
||||
export type DepthAnythingModelSize = z.infer<typeof zDepthAnythingModelSize>;
|
||||
export const isDepthAnythingModelSize = (v: unknown): v is DepthAnythingModelSize =>
|
||||
zDepthAnythingModelSize.safeParse(v).success;
|
||||
|
@ -24,6 +24,7 @@ export const DepthAnythingProcessor = memo(({ onChange, config }: Props) => {
|
||||
|
||||
const options: { label: string; value: DepthAnythingModelSize }[] = useMemo(
|
||||
() => [
|
||||
{ label: t('controlnet.depthAnythingSmallV2'), value: 'small_v2' },
|
||||
{ label: t('controlnet.small'), value: 'small' },
|
||||
{ label: t('controlnet.base'), value: 'base' },
|
||||
{ label: t('controlnet.large'), value: 'large' },
|
||||
|
@ -36,7 +36,7 @@ const zContentShuffleProcessorConfig = z.object({
|
||||
});
|
||||
export type ContentShuffleProcessorConfig = z.infer<typeof zContentShuffleProcessorConfig>;
|
||||
|
||||
const zDepthAnythingModelSize = z.enum(['large', 'base', 'small']);
|
||||
const zDepthAnythingModelSize = z.enum(['large', 'base', 'small', 'small_v2']);
|
||||
export type DepthAnythingModelSize = z.infer<typeof zDepthAnythingModelSize>;
|
||||
export const isDepthAnythingModelSize = (v: unknown): v is DepthAnythingModelSize =>
|
||||
zDepthAnythingModelSize.safeParse(v).success;
|
||||
@ -298,7 +298,7 @@ export const CA_PROCESSOR_DATA: CAProcessorsData = {
|
||||
buildDefaults: () => ({
|
||||
id: 'depth_anything_image_processor',
|
||||
type: 'depth_anything_image_processor',
|
||||
model_size: 'small',
|
||||
model_size: 'small_v2',
|
||||
}),
|
||||
buildNode: (image, config) => ({
|
||||
...config,
|
||||
|
@ -56,7 +56,6 @@ const DeleteImageModal = () => {
|
||||
const dispatch = useAppDispatch();
|
||||
const { t } = useTranslation();
|
||||
const shouldConfirmOnDelete = useAppSelector((s) => s.system.shouldConfirmOnDelete);
|
||||
const canRestoreDeletedImagesFromBin = useAppSelector((s) => s.config.canRestoreDeletedImagesFromBin);
|
||||
const isModalOpen = useAppSelector((s) => s.deleteImageModal.isModalOpen);
|
||||
const { imagesToDelete, imagesUsage, imageUsageSummary } = useAppSelector(selectImageUsages);
|
||||
|
||||
@ -90,7 +89,7 @@ const DeleteImageModal = () => {
|
||||
<Flex direction="column" gap={3}>
|
||||
<ImageUsageMessage imageUsage={imageUsageSummary} />
|
||||
<Divider />
|
||||
<Text>{canRestoreDeletedImagesFromBin ? t('gallery.deleteImageBin') : t('gallery.deleteImagePermanent')}</Text>
|
||||
<Text>{t('gallery.deleteImagePermanent')}</Text>
|
||||
<Text>{t('common.areYouSure')}</Text>
|
||||
<FormControl>
|
||||
<FormLabel>{t('common.dontAskMeAgain')}</FormLabel>
|
||||
|
@ -35,7 +35,6 @@ type Props = {
|
||||
const DeleteBoardModal = (props: Props) => {
|
||||
const { boardToDelete, setBoardToDelete } = props;
|
||||
const { t } = useTranslation();
|
||||
const canRestoreDeletedImagesFromBin = useAppSelector((s) => s.config.canRestoreDeletedImagesFromBin);
|
||||
const { currentData: boardImageNames, isFetching: isFetchingBoardNames } = useListAllImageNamesForBoardQuery(
|
||||
boardToDelete?.board_id ?? skipToken
|
||||
);
|
||||
@ -125,9 +124,7 @@ const DeleteBoardModal = (props: Props) => {
|
||||
? t('boards.deletedPrivateBoardsCannotbeRestored')
|
||||
: t('boards.deletedBoardsCannotbeRestored')}
|
||||
</Text>
|
||||
<Text>
|
||||
{canRestoreDeletedImagesFromBin ? t('gallery.deleteImageBin') : t('gallery.deleteImagePermanent')}
|
||||
</Text>
|
||||
<Text>{t('gallery.deleteImagePermanent')}</Text>
|
||||
</Flex>
|
||||
</AlertDialogBody>
|
||||
<AlertDialogFooter>
|
||||
|
@ -40,7 +40,7 @@ const selectShouldDisableToolbarButtons = createSelector(
|
||||
selectGallerySlice,
|
||||
selectLastSelectedImage,
|
||||
(system, gallery, lastSelectedImage) => {
|
||||
const hasProgressImage = Boolean(system.denoiseProgress?.image);
|
||||
const hasProgressImage = Boolean(system.denoiseProgress?.progress_image);
|
||||
return hasProgressImage || !lastSelectedImage;
|
||||
}
|
||||
);
|
||||
|
@ -4,7 +4,7 @@ import { useAppSelector } from 'app/store/storeHooks';
|
||||
import { memo, useMemo } from 'react';
|
||||
|
||||
const CurrentImagePreview = () => {
|
||||
const image = useAppSelector((s) => s.system.denoiseProgress?.image);
|
||||
const progress_image = useAppSelector((s) => s.system.denoiseProgress?.progress_image);
|
||||
const shouldAntialiasProgressImage = useAppSelector((s) => s.system.shouldAntialiasProgressImage);
|
||||
|
||||
const sx = useMemo<SystemStyleObject>(
|
||||
@ -14,15 +14,15 @@ const CurrentImagePreview = () => {
|
||||
[shouldAntialiasProgressImage]
|
||||
);
|
||||
|
||||
if (!image) {
|
||||
if (!progress_image) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return (
|
||||
<Image
|
||||
src={image.dataURL}
|
||||
width={image.width}
|
||||
height={image.height}
|
||||
src={progress_image.dataURL}
|
||||
width={progress_image.width}
|
||||
height={progress_image.height}
|
||||
draggable={false}
|
||||
data-testid="progress-image"
|
||||
objectFit="contain"
|
||||
|
@ -20,7 +20,7 @@ const selector = createMemoizedSelector(selectSystemSlice, selectGallerySlice, (
|
||||
|
||||
return {
|
||||
imageDTO,
|
||||
progressImage: system.denoiseProgress?.image,
|
||||
progressImage: system.denoiseProgress?.progress_image,
|
||||
};
|
||||
});
|
||||
|
||||
|
@ -0,0 +1,29 @@
|
||||
import { createMemoizedSelector } from 'app/store/createMemoizedSelector';
|
||||
import { useAppSelector } from 'app/store/storeHooks';
|
||||
import { selectUpscalelice } from 'features/parameters/store/upscaleSlice';
|
||||
import { selectConfigSlice } from 'features/system/store/configSlice';
|
||||
import { useMemo } from 'react';
|
||||
import type { ImageDTO } from 'services/api/types';
|
||||
|
||||
const createIsTooLargeToUpscaleSelector = (imageDTO?: ImageDTO) =>
|
||||
createMemoizedSelector(selectUpscalelice, selectConfigSlice, (upscale, config) => {
|
||||
const { upscaleModel, scale } = upscale;
|
||||
const { maxUpscaleDimension } = config;
|
||||
|
||||
if (!maxUpscaleDimension || !upscaleModel || !imageDTO) {
|
||||
// When these are missing, another warning will be shown
|
||||
return false;
|
||||
}
|
||||
|
||||
const { width, height } = imageDTO;
|
||||
|
||||
const maxPixels = maxUpscaleDimension ** 2;
|
||||
const upscaledPixels = width * scale * height * scale;
|
||||
|
||||
return upscaledPixels > maxPixels;
|
||||
});
|
||||
|
||||
export const useIsTooLargeToUpscale = (imageDTO?: ImageDTO) => {
|
||||
const selectIsTooLargeToUpscale = useMemo(() => createIsTooLargeToUpscaleSelector(imageDTO), [imageDTO]);
|
||||
return useAppSelector(selectIsTooLargeToUpscale);
|
||||
};
|
@ -1,4 +1,4 @@
|
||||
import { Flex } from '@invoke-ai/ui-library';
|
||||
import { Flex, Text } from '@invoke-ai/ui-library';
|
||||
import { useAppDispatch, useAppSelector } from 'app/store/storeHooks';
|
||||
import IAIDndImage from 'common/components/IAIDndImage';
|
||||
import IAIDndImageIcon from 'common/components/IAIDndImageIcon';
|
||||
@ -41,13 +41,30 @@ export const UpscaleInitialImage = () => {
|
||||
postUploadAction={postUploadAction}
|
||||
/>
|
||||
{imageDTO && (
|
||||
<Flex position="absolute" flexDir="column" top={1} insetInlineEnd={1} gap={1}>
|
||||
<IAIDndImageIcon
|
||||
onClick={onReset}
|
||||
icon={<PiArrowCounterClockwiseBold size={16} />}
|
||||
tooltip={t('controlnet.resetControlImage')}
|
||||
/>
|
||||
</Flex>
|
||||
<>
|
||||
<Flex position="absolute" flexDir="column" top={1} insetInlineEnd={1} gap={1}>
|
||||
<IAIDndImageIcon
|
||||
onClick={onReset}
|
||||
icon={<PiArrowCounterClockwiseBold size={16} />}
|
||||
tooltip={t('controlnet.resetControlImage')}
|
||||
/>
|
||||
</Flex>
|
||||
<Text
|
||||
position="absolute"
|
||||
background="base.900"
|
||||
color="base.50"
|
||||
fontSize="sm"
|
||||
fontWeight="semibold"
|
||||
bottom={0}
|
||||
left={0}
|
||||
opacity={0.7}
|
||||
px={2}
|
||||
lineHeight={1.25}
|
||||
borderTopEndRadius="base"
|
||||
borderBottomStartRadius="base"
|
||||
pointerEvents="none"
|
||||
>{`${imageDTO.width}x${imageDTO.height}`}</Text>
|
||||
</>
|
||||
)}
|
||||
</Flex>
|
||||
</Flex>
|
||||
|
@ -1,6 +1,7 @@
|
||||
import { Button, Flex, ListItem, Text, UnorderedList } from '@invoke-ai/ui-library';
|
||||
import { useAppDispatch, useAppSelector } from 'app/store/storeHooks';
|
||||
import { $installModelsTab } from 'features/modelManagerV2/subpanels/InstallModels';
|
||||
import { useIsTooLargeToUpscale } from 'features/parameters/hooks/useIsTooLargeToUpscale';
|
||||
import { tileControlnetModelChanged } from 'features/parameters/store/upscaleSlice';
|
||||
import { setActiveTab } from 'features/ui/store/uiSlice';
|
||||
import { useCallback, useEffect, useMemo } from 'react';
|
||||
@ -12,10 +13,13 @@ export const UpscaleWarning = () => {
|
||||
const model = useAppSelector((s) => s.generation.model);
|
||||
const upscaleModel = useAppSelector((s) => s.upscale.upscaleModel);
|
||||
const tileControlnetModel = useAppSelector((s) => s.upscale.tileControlnetModel);
|
||||
const upscaleInitialImage = useAppSelector((s) => s.upscale.upscaleInitialImage);
|
||||
const dispatch = useAppDispatch();
|
||||
const [modelConfigs, { isLoading }] = useControlNetModels();
|
||||
const disabledTabs = useAppSelector((s) => s.config.disabledTabs);
|
||||
const shouldShowButton = useMemo(() => !disabledTabs.includes('models'), [disabledTabs]);
|
||||
const maxUpscaleDimension = useAppSelector((s) => s.config.maxUpscaleDimension);
|
||||
const isTooLargeToUpscale = useIsTooLargeToUpscale(upscaleInitialImage || undefined);
|
||||
|
||||
useEffect(() => {
|
||||
const validModel = modelConfigs.find((cnetModel) => {
|
||||
@ -24,7 +28,7 @@ export const UpscaleWarning = () => {
|
||||
dispatch(tileControlnetModelChanged(validModel || null));
|
||||
}, [model?.base, modelConfigs, dispatch]);
|
||||
|
||||
const warnings = useMemo(() => {
|
||||
const modelWarnings = useMemo(() => {
|
||||
const _warnings: string[] = [];
|
||||
if (!model) {
|
||||
_warnings.push(t('upscaling.mainModelDesc'));
|
||||
@ -35,33 +39,48 @@ export const UpscaleWarning = () => {
|
||||
if (!upscaleModel) {
|
||||
_warnings.push(t('upscaling.upscaleModelDesc'));
|
||||
}
|
||||
|
||||
return _warnings;
|
||||
}, [model, tileControlnetModel, upscaleModel, t]);
|
||||
|
||||
const otherWarnings = useMemo(() => {
|
||||
const _warnings: string[] = [];
|
||||
if (isTooLargeToUpscale && maxUpscaleDimension) {
|
||||
_warnings.push(
|
||||
t('upscaling.exceedsMaxSizeDetails', { maxUpscaleDimension: maxUpscaleDimension.toLocaleString() })
|
||||
);
|
||||
}
|
||||
return _warnings;
|
||||
}, [isTooLargeToUpscale, t, maxUpscaleDimension]);
|
||||
|
||||
const handleGoToModelManager = useCallback(() => {
|
||||
dispatch(setActiveTab('models'));
|
||||
$installModelsTab.set(3);
|
||||
}, [dispatch]);
|
||||
|
||||
if (!warnings.length || isLoading || !shouldShowButton) {
|
||||
if (modelWarnings.length && !shouldShowButton) {
|
||||
return null;
|
||||
}
|
||||
|
||||
if ((!modelWarnings.length && !otherWarnings.length) || isLoading) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return (
|
||||
<Flex bg="error.500" borderRadius="base" padding={4} direction="column" fontSize="sm" gap={2}>
|
||||
<Text>
|
||||
<Trans
|
||||
i18nKey="upscaling.missingModelsWarning"
|
||||
components={{
|
||||
LinkComponent: (
|
||||
<Button size="sm" flexGrow={0} variant="link" color="base.50" onClick={handleGoToModelManager} />
|
||||
),
|
||||
}}
|
||||
/>
|
||||
</Text>
|
||||
{!!modelWarnings.length && (
|
||||
<Text>
|
||||
<Trans
|
||||
i18nKey="upscaling.missingModelsWarning"
|
||||
components={{
|
||||
LinkComponent: (
|
||||
<Button size="sm" flexGrow={0} variant="link" color="base.50" onClick={handleGoToModelManager} />
|
||||
),
|
||||
}}
|
||||
/>
|
||||
</Text>
|
||||
)}
|
||||
<UnorderedList>
|
||||
{warnings.map((warning) => (
|
||||
{[...modelWarnings, ...otherWarnings].map((warning) => (
|
||||
<ListItem key={warning}>{warning}</ListItem>
|
||||
))}
|
||||
</UnorderedList>
|
||||
|
@ -1,4 +1,4 @@
|
||||
import { Progress, Tooltip } from '@invoke-ai/ui-library';
|
||||
import { Progress } from '@invoke-ai/ui-library';
|
||||
import { createSelector } from '@reduxjs/toolkit';
|
||||
import { useAppSelector } from 'app/store/storeHooks';
|
||||
import { selectSystemSlice } from 'features/system/store/systemSlice';
|
||||
@ -15,21 +15,18 @@ const ProgressBar = () => {
|
||||
const { t } = useTranslation();
|
||||
const { data: queueStatus } = useGetQueueStatusQuery();
|
||||
const isConnected = useAppSelector((s) => s.system.isConnected);
|
||||
const message = useAppSelector((s) => s.system.denoiseProgress?.message);
|
||||
const hasSteps = useAppSelector((s) => Boolean(s.system.denoiseProgress?.percentage !== undefined));
|
||||
const hasSteps = useAppSelector((s) => Boolean(s.system.denoiseProgress));
|
||||
const value = useAppSelector(selectProgressValue);
|
||||
|
||||
return (
|
||||
<Tooltip label={message} placement="end">
|
||||
<Progress
|
||||
value={value}
|
||||
aria-label={t('accessibility.invokeProgressBar')}
|
||||
isIndeterminate={isConnected && Boolean(queueStatus?.queue.in_progress) && !hasSteps}
|
||||
h={2}
|
||||
w="full"
|
||||
colorScheme="invokeBlue"
|
||||
/>
|
||||
</Tooltip>
|
||||
<Progress
|
||||
value={value}
|
||||
aria-label={t('accessibility.invokeProgressBar')}
|
||||
isIndeterminate={isConnected && Boolean(queueStatus?.queue.in_progress) && !hasSteps}
|
||||
h={2}
|
||||
w="full"
|
||||
colorScheme="invokeBlue"
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
|
@ -24,7 +24,6 @@ const initialConfigState: AppConfig = {
|
||||
disabledSDFeatures: ['variation', 'symmetry', 'hires', 'perlinNoise', 'noiseThreshold'],
|
||||
nodesAllowlist: undefined,
|
||||
nodesDenylist: undefined,
|
||||
canRestoreDeletedImagesFromBin: true,
|
||||
sd: {
|
||||
disabledControlNetModels: [],
|
||||
disabledControlNetProcessors: [],
|
||||
|
@ -5,8 +5,8 @@ import type { LogLevelName } from 'roarr';
|
||||
import {
|
||||
socketConnected,
|
||||
socketDisconnected,
|
||||
socketGeneratorProgress,
|
||||
socketInvocationComplete,
|
||||
socketInvocationProgress,
|
||||
socketInvocationStarted,
|
||||
socketModelLoadComplete,
|
||||
socketModelLoadStarted,
|
||||
@ -95,8 +95,8 @@ export const systemSlice = createSlice({
|
||||
/**
|
||||
* Generator Progress
|
||||
*/
|
||||
builder.addCase(socketInvocationProgress, (state, action) => {
|
||||
const { image, session_id, batch_id, percentage, message } = action.payload.data;
|
||||
builder.addCase(socketGeneratorProgress, (state, action) => {
|
||||
const { step, total_steps, progress_image, session_id, batch_id, percentage } = action.payload.data;
|
||||
|
||||
if (state.cancellations.includes(session_id)) {
|
||||
// Do not update the progress if this session has been cancelled. This prevents a race condition where we get a
|
||||
@ -105,9 +105,10 @@ export const systemSlice = createSlice({
|
||||
}
|
||||
|
||||
state.denoiseProgress = {
|
||||
message,
|
||||
step,
|
||||
total_steps,
|
||||
percentage,
|
||||
image,
|
||||
progress_image,
|
||||
session_id,
|
||||
batch_id,
|
||||
};
|
||||
|
@ -1,9 +1,18 @@
|
||||
import type { LogLevel } from 'app/logging/logger';
|
||||
import type { InvocationProgressEvent } from 'services/events/types';
|
||||
import type { ProgressImage } from 'services/events/types';
|
||||
import { z } from 'zod';
|
||||
|
||||
type SystemStatus = 'CONNECTED' | 'DISCONNECTED' | 'PROCESSING' | 'ERROR' | 'LOADING_MODEL';
|
||||
|
||||
type DenoiseProgress = {
|
||||
session_id: string;
|
||||
batch_id: string;
|
||||
progress_image: ProgressImage | null | undefined;
|
||||
step: number;
|
||||
total_steps: number;
|
||||
percentage: number;
|
||||
};
|
||||
|
||||
const zLanguage = z.enum([
|
||||
'ar',
|
||||
'az',
|
||||
@ -36,7 +45,7 @@ export interface SystemState {
|
||||
isConnected: boolean;
|
||||
shouldConfirmOnDelete: boolean;
|
||||
enableImageDebugging: boolean;
|
||||
denoiseProgress: Pick<InvocationProgressEvent, 'session_id' | 'batch_id' | 'image' | 'percentage' | 'message'> | null;
|
||||
denoiseProgress: DenoiseProgress | null;
|
||||
consoleLogLevel: LogLevel;
|
||||
shouldLogToConsole: boolean;
|
||||
shouldAntialiasProgressImage: boolean;
|
||||
|
File diff suppressed because one or more lines are too long
@ -9,8 +9,8 @@ import type {
|
||||
DownloadProgressEvent,
|
||||
DownloadStartedEvent,
|
||||
InvocationCompleteEvent,
|
||||
InvocationDenoiseProgressEvent,
|
||||
InvocationErrorEvent,
|
||||
InvocationProgressEvent,
|
||||
InvocationStartedEvent,
|
||||
ModelInstallCancelledEvent,
|
||||
ModelInstallCompleteEvent,
|
||||
@ -32,7 +32,9 @@ export const socketDisconnected = createSocketAction('Disconnected');
|
||||
export const socketInvocationStarted = createSocketAction<InvocationStartedEvent>('InvocationStartedEvent');
|
||||
export const socketInvocationComplete = createSocketAction<InvocationCompleteEvent>('InvocationCompleteEvent');
|
||||
export const socketInvocationError = createSocketAction<InvocationErrorEvent>('InvocationErrorEvent');
|
||||
export const socketInvocationProgress = createSocketAction<InvocationProgressEvent>('InvocationProgressEvent');
|
||||
export const socketGeneratorProgress = createSocketAction<InvocationDenoiseProgressEvent>(
|
||||
'InvocationDenoiseProgressEvent'
|
||||
);
|
||||
export const socketModelLoadStarted = createSocketAction<ModelLoadStartedEvent>('ModelLoadStartedEvent');
|
||||
export const socketModelLoadComplete = createSocketAction<ModelLoadCompleteEvent>('ModelLoadCompleteEvent');
|
||||
export const socketDownloadStarted = createSocketAction<DownloadStartedEvent>('DownloadStartedEvent');
|
||||
|
@ -14,9 +14,9 @@ import {
|
||||
socketDownloadError,
|
||||
socketDownloadProgress,
|
||||
socketDownloadStarted,
|
||||
socketGeneratorProgress,
|
||||
socketInvocationComplete,
|
||||
socketInvocationError,
|
||||
socketInvocationProgress,
|
||||
socketInvocationStarted,
|
||||
socketModelInstallCancelled,
|
||||
socketModelInstallComplete,
|
||||
@ -65,8 +65,8 @@ export const setEventListeners = ({ socket, dispatch }: SetEventListenersArg) =>
|
||||
socket.on('invocation_started', (data) => {
|
||||
dispatch(socketInvocationStarted({ data }));
|
||||
});
|
||||
socket.on('invocation_progress', (data) => {
|
||||
dispatch(socketInvocationProgress({ data }));
|
||||
socket.on('invocation_denoise_progress', (data) => {
|
||||
dispatch(socketGeneratorProgress({ data }));
|
||||
});
|
||||
socket.on('invocation_error', (data) => {
|
||||
dispatch(socketInvocationError({ data }));
|
||||
|
@ -4,9 +4,10 @@ export type ModelLoadStartedEvent = S['ModelLoadStartedEvent'];
|
||||
export type ModelLoadCompleteEvent = S['ModelLoadCompleteEvent'];
|
||||
|
||||
export type InvocationStartedEvent = S['InvocationStartedEvent'];
|
||||
export type InvocationProgressEvent = S['InvocationProgressEvent'];
|
||||
export type InvocationDenoiseProgressEvent = S['InvocationDenoiseProgressEvent'];
|
||||
export type InvocationCompleteEvent = S['InvocationCompleteEvent'];
|
||||
export type InvocationErrorEvent = S['InvocationErrorEvent'];
|
||||
export type ProgressImage = InvocationDenoiseProgressEvent['progress_image'];
|
||||
|
||||
export type ModelInstallDownloadStartedEvent = S['ModelInstallDownloadStartedEvent'];
|
||||
export type ModelInstallDownloadProgressEvent = S['ModelInstallDownloadProgressEvent'];
|
||||
@ -38,7 +39,7 @@ type ClientEmitSubscribeBulkDownload = {
|
||||
type ClientEmitUnsubscribeBulkDownload = ClientEmitSubscribeBulkDownload;
|
||||
|
||||
export type ServerToClientEvents = {
|
||||
invocation_progress: (payload: InvocationProgressEvent) => void;
|
||||
invocation_denoise_progress: (payload: InvocationDenoiseProgressEvent) => void;
|
||||
invocation_complete: (payload: InvocationCompleteEvent) => void;
|
||||
invocation_error: (payload: InvocationErrorEvent) => void;
|
||||
invocation_started: (payload: InvocationStartedEvent) => void;
|
||||
|
@ -66,7 +66,6 @@ from invokeai.app.invocations.scheduler import SchedulerOutput
|
||||
from invokeai.app.services.boards.boards_common import BoardDTO
|
||||
from invokeai.app.services.config.config_default import InvokeAIAppConfig
|
||||
from invokeai.app.services.image_records.image_records_common import ImageCategory
|
||||
from invokeai.app.services.session_processor.session_processor_common import ProgressImage
|
||||
from invokeai.app.services.shared.invocation_context import InvocationContext
|
||||
from invokeai.app.services.workflow_records.workflow_records_common import WorkflowWithoutID
|
||||
from invokeai.app.util.misc import SEED_MAX, get_random_seed
|
||||
@ -177,5 +176,4 @@ __all__ = [
|
||||
# invokeai.app.util.misc
|
||||
"SEED_MAX",
|
||||
"get_random_seed",
|
||||
"ProgressImage",
|
||||
]
|
||||
|
@ -74,7 +74,8 @@ dependencies = [
|
||||
"easing-functions",
|
||||
"einops",
|
||||
"facexlib",
|
||||
"matplotlib", # needed for plotting of Penner easing functions
|
||||
# Exclude 3.9.1 which has a problem on windows, see https://github.com/matplotlib/matplotlib/issues/28551
|
||||
"matplotlib!=3.9.1",
|
||||
"npyscreen",
|
||||
"omegaconf",
|
||||
"picklescan",
|
||||
@ -89,7 +90,6 @@ dependencies = [
|
||||
"rich~=13.3",
|
||||
"scikit-image~=0.21.0",
|
||||
"semver~=3.0.1",
|
||||
"send2trash",
|
||||
"test-tube~=0.7.5",
|
||||
"windows-curses; sys_platform=='win32'",
|
||||
]
|
||||
|
Reference in New Issue
Block a user