diff --git a/docs/features/OTHER.md b/docs/features/OTHER.md index 2401f644ba..00bfd05e14 100644 --- a/docs/features/OTHER.md +++ b/docs/features/OTHER.md @@ -16,21 +16,24 @@ Output Example: --- -## **Seamless Tiling** +## **Invisible Watermark** -The seamless tiling mode causes generated images to seamlessly tile -with itself creating repetitive wallpaper-like patterns. To use it, -activate the Seamless Tiling option in the Web GUI and then select -whether to tile on the X (horizontal) and/or Y (vertical) axes. Tiling -will then be active for the next set of generations. +In keeping with the principles for responsible AI generation, and to +help AI researchers avoid synthetic images contaminating their +training sets, InvokeAI adds an invisible watermark to each of the +final images it generates. The watermark consists of the text +"InvokeAI" and can be viewed using the +[invisible-watermarks](https://github.com/ShieldMnt/invisible-watermark) +tool. -A nice prompt to test seamless tiling with is: +Watermarking is controlled using the `invisible-watermark` setting in +`invokeai.yaml`. To turn it off, add the following line under the `Features` +category. ``` -pond garden with lotus by claude monet" +invisible_watermark: false ``` ---- ## **Weighted Prompts** @@ -39,34 +42,10 @@ priority to them, by adding `:` to the end of the section you wish to u example consider this prompt: ```bash -tabby cat:0.25 white duck:0.75 hybrid +(tabby cat):0.25 (white duck):0.75 hybrid ``` This will tell the sampler to invest 25% of its effort on the tabby cat aspect of the image and 75% on the white duck aspect (surprisingly, this example actually works). The prompt weights can use any combination of integers and floating point numbers, and they do not need to add up to 1. -## **Thresholding and Perlin Noise Initialization Options** - -Under the Noise section of the Web UI, you will find two options named -Perlin Noise and Noise Threshold. [Perlin -noise](https://en.wikipedia.org/wiki/Perlin_noise) is a type of -structured noise used to simulate terrain and other natural -textures. The slider controls the percentage of perlin noise that will -be mixed into the image at the beginning of generation. Adding a little -perlin noise to a generation will alter the image substantially. - -The noise threshold limits the range of the latent values during -sampling and helps combat the oversharpening seem with higher CFG -scale values. - -For better intuition into what these options do in practice: - -![here is a graphic demonstrating them both](../assets/truncation_comparison.jpg) - -In generating this graphic, perlin noise at initialization was -programmatically varied going across on the diagram by values 0.0, -0.1, 0.2, 0.4, 0.5, 0.6, 0.8, 0.9, 1.0; and the threshold was varied -going down from 0, 1, 2, 3, 4, 5, 10, 20, 100. The other options are -fixed using the prompt "a portrait of a beautiful young lady" a CFG of -20, 100 steps, and a seed of 1950357039. diff --git a/invokeai/assets/web/caution.png b/invokeai/app/assets/images/caution.png similarity index 100% rename from invokeai/assets/web/caution.png rename to invokeai/app/assets/images/caution.png diff --git a/invokeai/app/invocations/controlnet_image_processors.py b/invokeai/app/invocations/controlnet_image_processors.py index b04c45ae20..2087594b7b 100644 --- a/invokeai/app/invocations/controlnet_image_processors.py +++ b/invokeai/app/invocations/controlnet_image_processors.py @@ -20,7 +20,7 @@ from ...backend.model_management import BaseModelType, ModelType from ..models.image import ImageCategory, ImageField, ResourceOrigin from .baseinvocation import (BaseInvocation, BaseInvocationOutput, InvocationConfig, InvocationContext) -from .image_defs import ImageOutput, PILInvocationConfig +from ..models.image import ImageOutput, PILInvocationConfig CONTROLNET_DEFAULT_MODELS = [ ########################################### diff --git a/invokeai/app/invocations/image.py b/invokeai/app/invocations/image.py index cf82148483..50f8a4b627 100644 --- a/invokeai/app/invocations/image.py +++ b/invokeai/app/invocations/image.py @@ -4,24 +4,21 @@ from typing import Literal, Optional import numpy from PIL import Image, ImageFilter, ImageOps, ImageChops -from pydantic import BaseModel, Field +from pydantic import Field from pathlib import Path from typing import Union from invokeai.app.invocations.metadata import CoreMetadata from diffusers.pipelines.stable_diffusion.safety_checker import StableDiffusionSafetyChecker from transformers import AutoFeatureExtractor -from ..models.image import ImageCategory, ImageField, ResourceOrigin +from ..models.image import ( + ImageCategory, ImageField, ResourceOrigin, + PILInvocationConfig, ImageOutput, MaskOutput, +) from .baseinvocation import ( BaseInvocation, - BaseInvocationOutput, InvocationContext, InvocationConfig, ) -from .image_defs import ( - PILInvocationConfig, - ImageOutput, - MaskOutput, - ) from ..services.config import InvokeAIAppConfig from invokeai.backend.util.devices import choose_torch_device from invokeai.backend import SilenceWarnings @@ -644,7 +641,7 @@ class ImageNSFWBlurInvocation(BaseInvocation, PILInvocationConfig): device = choose_torch_device() if self.enabled: - logger.info("Running NSFW checker") + logger.debug("Running NSFW checker") safety_checker = StableDiffusionSafetyChecker.from_pretrained(config.models_path / 'core/convert/stable-diffusion-safety-checker') feature_extractor = AutoFeatureExtractor.from_pretrained(config.models_path / 'core/convert/stable-diffusion-safety-checker') @@ -681,8 +678,8 @@ class ImageNSFWBlurInvocation(BaseInvocation, PILInvocationConfig): ) def _get_caution_img(self)->Image: - import invokeai.assets.web as web_assets - caution = Image.open(Path(web_assets.__path__[0]) / 'caution.png') + import invokeai.app.assets.images as image_assets + caution = Image.open(Path(image_assets.__path__[0]) / 'caution.png') return caution.resize((caution.width // 2, caution.height //2)) class ImageWatermarkInvocation(BaseInvocation, PILInvocationConfig): @@ -716,7 +713,7 @@ class ImageWatermarkInvocation(BaseInvocation, PILInvocationConfig): logger = context.services.logger image = context.services.images.get_pil_image(self.image.image_name) if self.enabled: - logger.info("Running invisible watermarker") + logger.debug("Running invisible watermarker") bgr = cv2.cvtColor(numpy.array(image.convert("RGB")), cv2.COLOR_RGB2BGR) wm = self.text encoder = WatermarkEncoder() diff --git a/invokeai/app/invocations/image_defs.py b/invokeai/app/invocations/image_defs.py deleted file mode 100644 index 8bab09debf..0000000000 --- a/invokeai/app/invocations/image_defs.py +++ /dev/null @@ -1,54 +0,0 @@ -# Copyright 2023 Lincoln D. Stein and the InvokeAI Team -""" Common classes used by .image and .controlnet; avoids circular import issues """ - -from pydantic import BaseModel, Field -from typing import Literal -from ..models.image import ImageField -from .baseinvocation import ( - BaseInvocationOutput, - InvocationConfig, -) - -class PILInvocationConfig(BaseModel): - """Helper class to provide all PIL invocations with additional config""" - - class Config(InvocationConfig): - schema_extra = { - "ui": { - "tags": ["PIL", "image"], - }, - } - -class ImageOutput(BaseInvocationOutput): - """Base class for invocations that output an image""" - - # fmt: off - type: Literal["image_output"] = "image_output" - image: ImageField = Field(default=None, description="The output image") - width: int = Field(description="The width of the image in pixels") - height: int = Field(description="The height of the image in pixels") - # fmt: on - - class Config: - schema_extra = {"required": ["type", "image", "width", "height"]} - - -class MaskOutput(BaseInvocationOutput): - """Base class for invocations that output a mask""" - - # fmt: off - type: Literal["mask"] = "mask" - mask: ImageField = Field(default=None, description="The output mask") - width: int = Field(description="The width of the mask in pixels") - height: int = Field(description="The height of the mask in pixels") - # fmt: on - - class Config: - schema_extra = { - "required": [ - "type", - "mask", - ] - } - - diff --git a/invokeai/app/models/image.py b/invokeai/app/models/image.py index 988a3e1447..1183cabc54 100644 --- a/invokeai/app/models/image.py +++ b/invokeai/app/models/image.py @@ -1,9 +1,80 @@ from enum import Enum -from typing import Optional, Tuple +from typing import Optional, Tuple, Literal from pydantic import BaseModel, Field from invokeai.app.util.metaenum import MetaEnum +from ..invocations.baseinvocation import ( + BaseInvocationOutput, + InvocationConfig, +) +class ImageField(BaseModel): + """An image field used for passing image objects between invocations""" + + image_name: Optional[str] = Field(default=None, description="The name of the image") + + class Config: + schema_extra = {"required": ["image_name"]} + + +class ColorField(BaseModel): + r: int = Field(ge=0, le=255, description="The red component") + g: int = Field(ge=0, le=255, description="The green component") + b: int = Field(ge=0, le=255, description="The blue component") + a: int = Field(ge=0, le=255, description="The alpha component") + + def tuple(self) -> Tuple[int, int, int, int]: + return (self.r, self.g, self.b, self.a) + + +class ProgressImage(BaseModel): + """The progress image sent intermittently during processing""" + + 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") + +class PILInvocationConfig(BaseModel): + """Helper class to provide all PIL invocations with additional config""" + + class Config(InvocationConfig): + schema_extra = { + "ui": { + "tags": ["PIL", "image"], + }, + } + +class ImageOutput(BaseInvocationOutput): + """Base class for invocations that output an image""" + + # fmt: off + type: Literal["image_output"] = "image_output" + image: ImageField = Field(default=None, description="The output image") + width: int = Field(description="The width of the image in pixels") + height: int = Field(description="The height of the image in pixels") + # fmt: on + + class Config: + schema_extra = {"required": ["type", "image", "width", "height"]} + + +class MaskOutput(BaseInvocationOutput): + """Base class for invocations that output a mask""" + + # fmt: off + type: Literal["mask"] = "mask" + mask: ImageField = Field(default=None, description="The output mask") + width: int = Field(description="The width of the mask in pixels") + height: int = Field(description="The height of the mask in pixels") + # fmt: on + + class Config: + schema_extra = { + "required": [ + "type", + "mask", + ] + } class ResourceOrigin(str, Enum, metaclass=MetaEnum): """The origin of a resource (eg image). @@ -63,28 +134,3 @@ class InvalidImageCategoryException(ValueError): super().__init__(message) -class ImageField(BaseModel): - """An image field used for passing image objects between invocations""" - - image_name: Optional[str] = Field(default=None, description="The name of the image") - - class Config: - schema_extra = {"required": ["image_name"]} - - -class ColorField(BaseModel): - r: int = Field(ge=0, le=255, description="The red component") - g: int = Field(ge=0, le=255, description="The green component") - b: int = Field(ge=0, le=255, description="The blue component") - a: int = Field(ge=0, le=255, description="The alpha component") - - def tuple(self) -> Tuple[int, int, int, int]: - return (self.r, self.g, self.b, self.a) - - -class ProgressImage(BaseModel): - """The progress image sent intermittently during processing""" - - 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") diff --git a/invokeai/app/services/board_image_record_storage.py b/invokeai/app/services/board_image_record_storage.py index 693b078589..491972bd32 100644 --- a/invokeai/app/services/board_image_record_storage.py +++ b/invokeai/app/services/board_image_record_storage.py @@ -135,7 +135,6 @@ class SqliteBoardImageRecordStorage(BoardImageRecordStorageBase): board_id: str, image_name: str, ) -> None: - print(f'DEBUG: board_id={board_id}, image_name={image_name}') try: self._lock.acquire() self._cursor.execute( @@ -147,7 +146,6 @@ class SqliteBoardImageRecordStorage(BoardImageRecordStorageBase): (board_id, image_name, board_id), ) self._conn.commit() - print('got here') except sqlite3.Error as e: self._conn.rollback() raise e diff --git a/invokeai/frontend/web/src/features/nodes/util/graphBuilders/buildLinearTextToImageGraph.ts b/invokeai/frontend/web/src/features/nodes/util/graphBuilders/buildLinearTextToImageGraph.ts index 754edae873..cfcb1a98c7 100644 --- a/invokeai/frontend/web/src/features/nodes/util/graphBuilders/buildLinearTextToImageGraph.ts +++ b/invokeai/frontend/web/src/features/nodes/util/graphBuilders/buildLinearTextToImageGraph.ts @@ -49,7 +49,7 @@ export const buildLinearTextToImageGraph = ( } /** -v * The easiest way to build linear graphs is to do it in the node editor, then copy and paste the + * The easiest way to build linear graphs is to do it in the node editor, then copy and paste the * full graph here as a template. Then use the parameters from app state and set friendlier node * ids. *