InvokeAI 2.2.4.5__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- backend/__init__.py +0 -0
- backend/invoke_ai_web_server.py +1724 -0
- backend/modules/__init__.py +0 -0
- backend/modules/create_cmd_parser.py +55 -0
- backend/modules/get_canvas_generation_mode.py +117 -0
- backend/modules/parameters.py +71 -0
- backend/modules/parse_seed_weights.py +47 -0
- configs/INITIAL_MODELS.yaml +80 -0
- configs/models.yaml.example +29 -0
- configs/sd-concepts.txt +803 -0
- configs/stable-diffusion/v1-finetune.yaml +110 -0
- configs/stable-diffusion/v1-finetune_style.yaml +103 -0
- configs/stable-diffusion/v1-inference.yaml +79 -0
- configs/stable-diffusion/v1-inpainting-inference.yaml +79 -0
- configs/stable-diffusion/v1-m1-finetune.yaml +110 -0
- docs/assets/invoke-web-server-1.png +0 -0
- docs/assets/invoke_ai_banner.png +0 -0
- invokeai-2.2.4.5.data/data/assets/caution.png +0 -0
- invokeai-2.2.4.5.data/data/configs/INITIAL_MODELS.yaml +80 -0
- invokeai-2.2.4.5.data/data/configs/models.yaml.example +29 -0
- invokeai-2.2.4.5.data/data/configs/sd-concepts.txt +803 -0
- invokeai-2.2.4.5.data/data/configs/stable-diffusion/v1-finetune.yaml +110 -0
- invokeai-2.2.4.5.data/data/configs/stable-diffusion/v1-finetune_style.yaml +103 -0
- invokeai-2.2.4.5.data/data/configs/stable-diffusion/v1-inference.yaml +79 -0
- invokeai-2.2.4.5.data/data/configs/stable-diffusion/v1-inpainting-inference.yaml +79 -0
- invokeai-2.2.4.5.data/data/configs/stable-diffusion/v1-m1-finetune.yaml +110 -0
- invokeai-2.2.4.5.data/data/frontend/dist/assets/Inter-Bold.790c108b.ttf +0 -0
- invokeai-2.2.4.5.data/data/frontend/dist/assets/Inter.b9a8e5e2.ttf +0 -0
- invokeai-2.2.4.5.data/data/frontend/dist/assets/favicon.0d253ced.ico +0 -0
- invokeai-2.2.4.5.data/data/frontend/dist/assets/index-legacy-b741077f.js +48 -0
- invokeai-2.2.4.5.data/data/frontend/dist/assets/index.0dadf5d0.css +1 -0
- invokeai-2.2.4.5.data/data/frontend/dist/assets/index.21ff81eb.js +625 -0
- invokeai-2.2.4.5.data/data/frontend/dist/assets/logo.13003d72.png +0 -0
- invokeai-2.2.4.5.data/data/frontend/dist/assets/polyfills-legacy-dde3a68a.js +4 -0
- invokeai-2.2.4.5.data/data/frontend/dist/assets/polyfills.1ff60148.js +1 -0
- invokeai-2.2.4.5.data/data/frontend/dist/index.html +23 -0
- invokeai-2.2.4.5.data/data/scripts/configure_invokeai.py +797 -0
- invokeai-2.2.4.5.data/data/scripts/create_root.py +43 -0
- invokeai-2.2.4.5.data/data/scripts/dream.py +10 -0
- invokeai-2.2.4.5.data/data/scripts/images2prompt.py +30 -0
- invokeai-2.2.4.5.data/data/scripts/invoke.py +5 -0
- invokeai-2.2.4.5.data/data/scripts/legacy_api.py +681 -0
- invokeai-2.2.4.5.data/data/scripts/load_models.py +12 -0
- invokeai-2.2.4.5.data/data/scripts/merge_embeddings.py +124 -0
- invokeai-2.2.4.5.data/data/scripts/preload_models.py +12 -0
- invokeai-2.2.4.5.data/data/scripts/sd-metadata.py +22 -0
- invokeai-2.2.4.5.dist-info/LICENSE +21 -0
- invokeai-2.2.4.5.dist-info/LICENSE-ModelWeights.txt +294 -0
- invokeai-2.2.4.5.dist-info/METADATA +288 -0
- invokeai-2.2.4.5.dist-info/RECORD +169 -0
- invokeai-2.2.4.5.dist-info/WHEEL +5 -0
- invokeai-2.2.4.5.dist-info/entry_points.txt +4 -0
- invokeai-2.2.4.5.dist-info/top_level.txt +6 -0
- invokeai-2.2.4.5.dist-info/zip-safe +1 -0
- ldm/__init__.py +0 -0
- ldm/data/__init__.py +0 -0
- ldm/data/base.py +31 -0
- ldm/data/imagenet.py +466 -0
- ldm/data/lsun.py +126 -0
- ldm/data/personalized.py +202 -0
- ldm/data/personalized_style.py +169 -0
- ldm/generate.py +1159 -0
- ldm/invoke/CLI.py +960 -0
- ldm/invoke/__init__.py +3 -0
- ldm/invoke/args.py +1257 -0
- ldm/invoke/concepts_lib.py +169 -0
- ldm/invoke/conditioning.py +289 -0
- ldm/invoke/devices.py +27 -0
- ldm/invoke/generator/__init__.py +4 -0
- ldm/invoke/generator/base.py +335 -0
- ldm/invoke/generator/embiggen.py +501 -0
- ldm/invoke/generator/img2img.py +97 -0
- ldm/invoke/generator/inpaint.py +358 -0
- ldm/invoke/generator/omnibus.py +175 -0
- ldm/invoke/generator/txt2img.py +88 -0
- ldm/invoke/generator/txt2img2img.py +182 -0
- ldm/invoke/globals.py +31 -0
- ldm/invoke/image_util.py +70 -0
- ldm/invoke/log.py +66 -0
- ldm/invoke/model_cache.py +449 -0
- ldm/invoke/patchmatch.py +44 -0
- ldm/invoke/pngwriter.py +113 -0
- ldm/invoke/prompt_parser.py +655 -0
- ldm/invoke/readline.py +442 -0
- ldm/invoke/restoration/__init__.py +4 -0
- ldm/invoke/restoration/base.py +38 -0
- ldm/invoke/restoration/codeformer.py +103 -0
- ldm/invoke/restoration/codeformer_arch.py +276 -0
- ldm/invoke/restoration/gfpgan.py +84 -0
- ldm/invoke/restoration/outcrop.py +108 -0
- ldm/invoke/restoration/outpaint.py +92 -0
- ldm/invoke/restoration/realesrgan.py +90 -0
- ldm/invoke/restoration/vqgan_arch.py +435 -0
- ldm/invoke/seamless.py +30 -0
- ldm/invoke/server.py +282 -0
- ldm/invoke/server_legacy.py +246 -0
- ldm/invoke/txt2mask.py +136 -0
- ldm/lr_scheduler.py +143 -0
- ldm/models/__init__.py +0 -0
- ldm/models/autoencoder.py +596 -0
- ldm/models/diffusion/__init__.py +0 -0
- ldm/models/diffusion/classifier.py +355 -0
- ldm/models/diffusion/cross_attention_control.py +443 -0
- ldm/models/diffusion/cross_attention_map_saving.py +95 -0
- ldm/models/diffusion/ddim.py +111 -0
- ldm/models/diffusion/ddpm.py +2263 -0
- ldm/models/diffusion/ksampler.py +312 -0
- ldm/models/diffusion/plms.py +146 -0
- ldm/models/diffusion/sampler.py +450 -0
- ldm/models/diffusion/shared_invokeai_diffusion.py +244 -0
- ldm/modules/__init__.py +0 -0
- ldm/modules/attention.py +274 -0
- ldm/modules/diffusionmodules/__init__.py +0 -0
- ldm/modules/diffusionmodules/model.py +894 -0
- ldm/modules/diffusionmodules/openaimodel.py +1036 -0
- ldm/modules/diffusionmodules/util.py +314 -0
- ldm/modules/distributions/__init__.py +0 -0
- ldm/modules/distributions/distributions.py +104 -0
- ldm/modules/ema.py +88 -0
- ldm/modules/embedding_manager.py +413 -0
- ldm/modules/encoders/__init__.py +0 -0
- ldm/modules/encoders/modules.py +772 -0
- ldm/modules/image_degradation/__init__.py +6 -0
- ldm/modules/image_degradation/bsrgan.py +846 -0
- ldm/modules/image_degradation/bsrgan_light.py +751 -0
- ldm/modules/image_degradation/utils_image.py +1024 -0
- ldm/modules/losses/__init__.py +1 -0
- ldm/modules/losses/contperceptual.py +175 -0
- ldm/modules/losses/vqperceptual.py +240 -0
- ldm/modules/x_transformer.py +752 -0
- ldm/simplet2i.py +13 -0
- ldm/util.py +264 -0
- scripts/configure_invokeai.py +797 -0
- scripts/create_root.py +43 -0
- scripts/dream.py +10 -0
- scripts/images2prompt.py +30 -0
- scripts/invoke.py +5 -0
- scripts/legacy_api.py +681 -0
- scripts/load_models.py +12 -0
- scripts/merge_embeddings.py +124 -0
- scripts/orig_scripts/img2img.py +296 -0
- scripts/orig_scripts/inpaint.py +98 -0
- scripts/orig_scripts/knn2img.py +397 -0
- scripts/orig_scripts/sample_diffusion.py +313 -0
- scripts/orig_scripts/train_searcher.py +147 -0
- scripts/orig_scripts/txt2img.py +324 -0
- scripts/preload_models.py +12 -0
- scripts/sd-metadata.py +22 -0
- vendored/clip/clip/__init__.py +1 -0
- vendored/clip/clip/bpe_simple_vocab_16e6.txt.gz +0 -0
- vendored/clip/clip/clip.py +237 -0
- vendored/clip/clip/model.py +436 -0
- vendored/clip/clip/simple_tokenizer.py +132 -0
- vendored/clip/hubconf.py +42 -0
- vendored/clip/setup.py +21 -0
- vendored/clip/tests/test_consistency.py +25 -0
- vendored/clipseg/datasets/coco_wrapper.py +99 -0
- vendored/clipseg/datasets/pascal_zeroshot.py +60 -0
- vendored/clipseg/datasets/pfe_dataset.py +129 -0
- vendored/clipseg/datasets/phrasecut.py +380 -0
- vendored/clipseg/datasets/utils.py +68 -0
- vendored/clipseg/evaluation_utils.py +292 -0
- vendored/clipseg/general_utils.py +272 -0
- vendored/clipseg/metrics.py +271 -0
- vendored/clipseg/models/clipseg.py +538 -0
- vendored/clipseg/models/vitseg.py +286 -0
- vendored/clipseg/score.py +453 -0
- vendored/clipseg/setup.py +30 -0
- vendored/clipseg/training.py +266 -0
|
@@ -0,0 +1,358 @@
|
|
|
1
|
+
'''
|
|
2
|
+
ldm.invoke.generator.inpaint descends from ldm.invoke.generator
|
|
3
|
+
'''
|
|
4
|
+
|
|
5
|
+
import math
|
|
6
|
+
import torch
|
|
7
|
+
import torchvision.transforms as T
|
|
8
|
+
import numpy as np
|
|
9
|
+
import cv2 as cv
|
|
10
|
+
import PIL
|
|
11
|
+
from PIL import Image, ImageFilter, ImageOps, ImageChops
|
|
12
|
+
from skimage.exposure.histogram_matching import match_histograms
|
|
13
|
+
from einops import rearrange, repeat
|
|
14
|
+
from ldm.invoke.devices import choose_autocast
|
|
15
|
+
from ldm.invoke.generator.img2img import Img2Img
|
|
16
|
+
from ldm.models.diffusion.ddim import DDIMSampler
|
|
17
|
+
from ldm.models.diffusion.ksampler import KSampler
|
|
18
|
+
from ldm.invoke.generator.base import downsampling
|
|
19
|
+
from ldm.util import debug_image
|
|
20
|
+
from ldm.invoke.patchmatch import PatchMatch
|
|
21
|
+
from ldm.invoke.globals import Globals
|
|
22
|
+
|
|
23
|
+
def infill_methods()->list[str]:
|
|
24
|
+
methods = list()
|
|
25
|
+
if PatchMatch.patchmatch_available():
|
|
26
|
+
methods.append('patchmatch')
|
|
27
|
+
methods.append('tile')
|
|
28
|
+
return methods
|
|
29
|
+
|
|
30
|
+
class Inpaint(Img2Img):
|
|
31
|
+
def __init__(self, model, precision):
|
|
32
|
+
self.init_latent = None
|
|
33
|
+
self.pil_image = None
|
|
34
|
+
self.pil_mask = None
|
|
35
|
+
self.mask_blur_radius = 0
|
|
36
|
+
self.infill_method = None
|
|
37
|
+
super().__init__(model, precision)
|
|
38
|
+
|
|
39
|
+
# Outpaint support code
|
|
40
|
+
def get_tile_images(self, image: np.ndarray, width=8, height=8):
|
|
41
|
+
_nrows, _ncols, depth = image.shape
|
|
42
|
+
_strides = image.strides
|
|
43
|
+
|
|
44
|
+
nrows, _m = divmod(_nrows, height)
|
|
45
|
+
ncols, _n = divmod(_ncols, width)
|
|
46
|
+
if _m != 0 or _n != 0:
|
|
47
|
+
return None
|
|
48
|
+
|
|
49
|
+
return np.lib.stride_tricks.as_strided(
|
|
50
|
+
np.ravel(image),
|
|
51
|
+
shape=(nrows, ncols, height, width, depth),
|
|
52
|
+
strides=(height * _strides[0], width * _strides[1], *_strides),
|
|
53
|
+
writeable=False
|
|
54
|
+
)
|
|
55
|
+
|
|
56
|
+
def infill_patchmatch(self, im: Image.Image) -> Image:
|
|
57
|
+
if im.mode != 'RGBA':
|
|
58
|
+
return im
|
|
59
|
+
|
|
60
|
+
# Skip patchmatch if patchmatch isn't available
|
|
61
|
+
if not PatchMatch.patchmatch_available():
|
|
62
|
+
return im
|
|
63
|
+
|
|
64
|
+
# Patchmatch (note, we may want to expose patch_size? Increasing it significantly impacts performance though)
|
|
65
|
+
im_patched_np = PatchMatch.inpaint(im.convert('RGB'), ImageOps.invert(im.split()[-1]), patch_size = 3)
|
|
66
|
+
im_patched = Image.fromarray(im_patched_np, mode = 'RGB')
|
|
67
|
+
return im_patched
|
|
68
|
+
|
|
69
|
+
def tile_fill_missing(self, im: Image.Image, tile_size: int = 16, seed: int = None) -> Image:
|
|
70
|
+
# Only fill if there's an alpha layer
|
|
71
|
+
if im.mode != 'RGBA':
|
|
72
|
+
return im
|
|
73
|
+
|
|
74
|
+
a = np.asarray(im, dtype=np.uint8)
|
|
75
|
+
|
|
76
|
+
tile_size = (tile_size, tile_size)
|
|
77
|
+
|
|
78
|
+
# Get the image as tiles of a specified size
|
|
79
|
+
tiles = self.get_tile_images(a,*tile_size).copy()
|
|
80
|
+
|
|
81
|
+
# Get the mask as tiles
|
|
82
|
+
tiles_mask = tiles[:,:,:,:,3]
|
|
83
|
+
|
|
84
|
+
# Find any mask tiles with any fully transparent pixels (we will be replacing these later)
|
|
85
|
+
tmask_shape = tiles_mask.shape
|
|
86
|
+
tiles_mask = tiles_mask.reshape(math.prod(tiles_mask.shape))
|
|
87
|
+
n,ny = (math.prod(tmask_shape[0:2])), math.prod(tmask_shape[2:])
|
|
88
|
+
tiles_mask = (tiles_mask > 0)
|
|
89
|
+
tiles_mask = tiles_mask.reshape((n,ny)).all(axis = 1)
|
|
90
|
+
|
|
91
|
+
# Get RGB tiles in single array and filter by the mask
|
|
92
|
+
tshape = tiles.shape
|
|
93
|
+
tiles_all = tiles.reshape((math.prod(tiles.shape[0:2]), * tiles.shape[2:]))
|
|
94
|
+
filtered_tiles = tiles_all[tiles_mask]
|
|
95
|
+
|
|
96
|
+
if len(filtered_tiles) == 0:
|
|
97
|
+
return im
|
|
98
|
+
|
|
99
|
+
# Find all invalid tiles and replace with a random valid tile
|
|
100
|
+
replace_count = (tiles_mask == False).sum()
|
|
101
|
+
rng = np.random.default_rng(seed = seed)
|
|
102
|
+
tiles_all[np.logical_not(tiles_mask)] = filtered_tiles[rng.choice(filtered_tiles.shape[0], replace_count),:,:,:]
|
|
103
|
+
|
|
104
|
+
# Convert back to an image
|
|
105
|
+
tiles_all = tiles_all.reshape(tshape)
|
|
106
|
+
tiles_all = tiles_all.swapaxes(1,2)
|
|
107
|
+
st = tiles_all.reshape((math.prod(tiles_all.shape[0:2]), math.prod(tiles_all.shape[2:4]), tiles_all.shape[4]))
|
|
108
|
+
si = Image.fromarray(st, mode='RGBA')
|
|
109
|
+
|
|
110
|
+
return si
|
|
111
|
+
|
|
112
|
+
|
|
113
|
+
def mask_edge(self, mask: Image, edge_size: int, edge_blur: int) -> Image:
|
|
114
|
+
npimg = np.asarray(mask, dtype=np.uint8)
|
|
115
|
+
|
|
116
|
+
# Detect any partially transparent regions
|
|
117
|
+
npgradient = np.uint8(255 * (1.0 - np.floor(np.abs(0.5 - np.float32(npimg) / 255.0) * 2.0)))
|
|
118
|
+
|
|
119
|
+
# Detect hard edges
|
|
120
|
+
npedge = cv.Canny(npimg, threshold1=100, threshold2=200)
|
|
121
|
+
|
|
122
|
+
# Combine
|
|
123
|
+
npmask = npgradient + npedge
|
|
124
|
+
|
|
125
|
+
# Expand
|
|
126
|
+
npmask = cv.dilate(npmask, np.ones((3,3), np.uint8), iterations = int(edge_size / 2))
|
|
127
|
+
|
|
128
|
+
new_mask = Image.fromarray(npmask)
|
|
129
|
+
|
|
130
|
+
if edge_blur > 0:
|
|
131
|
+
new_mask = new_mask.filter(ImageFilter.BoxBlur(edge_blur))
|
|
132
|
+
|
|
133
|
+
return ImageOps.invert(new_mask)
|
|
134
|
+
|
|
135
|
+
|
|
136
|
+
def seam_paint(self,
|
|
137
|
+
im: Image.Image,
|
|
138
|
+
seam_size: int,
|
|
139
|
+
seam_blur: int,
|
|
140
|
+
prompt,sampler,steps,cfg_scale,ddim_eta,
|
|
141
|
+
conditioning,strength,
|
|
142
|
+
noise,
|
|
143
|
+
step_callback
|
|
144
|
+
) -> Image.Image:
|
|
145
|
+
hard_mask = self.pil_image.split()[-1].copy()
|
|
146
|
+
mask = self.mask_edge(hard_mask, seam_size, seam_blur)
|
|
147
|
+
|
|
148
|
+
make_image = self.get_make_image(
|
|
149
|
+
prompt,
|
|
150
|
+
sampler,
|
|
151
|
+
steps,
|
|
152
|
+
cfg_scale,
|
|
153
|
+
ddim_eta,
|
|
154
|
+
conditioning,
|
|
155
|
+
init_image = im.copy().convert('RGBA'),
|
|
156
|
+
mask_image = mask.convert('RGB'), # Code currently requires an RGB mask
|
|
157
|
+
strength = strength,
|
|
158
|
+
mask_blur_radius = 0,
|
|
159
|
+
seam_size = 0,
|
|
160
|
+
step_callback = step_callback,
|
|
161
|
+
inpaint_width = im.width,
|
|
162
|
+
inpaint_height = im.height
|
|
163
|
+
)
|
|
164
|
+
|
|
165
|
+
seam_noise = self.get_noise(im.width, im.height)
|
|
166
|
+
|
|
167
|
+
result = make_image(seam_noise)
|
|
168
|
+
|
|
169
|
+
return result
|
|
170
|
+
|
|
171
|
+
|
|
172
|
+
@torch.no_grad()
|
|
173
|
+
def get_make_image(self,prompt,sampler,steps,cfg_scale,ddim_eta,
|
|
174
|
+
conditioning,init_image,mask_image,strength,
|
|
175
|
+
mask_blur_radius: int = 8,
|
|
176
|
+
# Seam settings - when 0, doesn't fill seam
|
|
177
|
+
seam_size: int = 0,
|
|
178
|
+
seam_blur: int = 0,
|
|
179
|
+
seam_strength: float = 0.7,
|
|
180
|
+
seam_steps: int = 10,
|
|
181
|
+
tile_size: int = 32,
|
|
182
|
+
step_callback=None,
|
|
183
|
+
inpaint_replace=False, enable_image_debugging=False,
|
|
184
|
+
infill_method = None,
|
|
185
|
+
inpaint_width=None,
|
|
186
|
+
inpaint_height=None,
|
|
187
|
+
**kwargs):
|
|
188
|
+
"""
|
|
189
|
+
Returns a function returning an image derived from the prompt and
|
|
190
|
+
the initial image + mask. Return value depends on the seed at
|
|
191
|
+
the time you call it. kwargs are 'init_latent' and 'strength'
|
|
192
|
+
"""
|
|
193
|
+
|
|
194
|
+
self.enable_image_debugging = enable_image_debugging
|
|
195
|
+
self.infill_method = infill_method or infill_methods()[0], # The infill method to use
|
|
196
|
+
|
|
197
|
+
self.inpaint_width = inpaint_width
|
|
198
|
+
self.inpaint_height = inpaint_height
|
|
199
|
+
|
|
200
|
+
if isinstance(init_image, PIL.Image.Image):
|
|
201
|
+
self.pil_image = init_image.copy()
|
|
202
|
+
|
|
203
|
+
# Do infill
|
|
204
|
+
if infill_method == 'patchmatch' and PatchMatch.patchmatch_available():
|
|
205
|
+
init_filled = self.infill_patchmatch(self.pil_image.copy())
|
|
206
|
+
else: # if infill_method == 'tile': # Only two methods right now, so always use 'tile' if not patchmatch
|
|
207
|
+
init_filled = self.tile_fill_missing(
|
|
208
|
+
self.pil_image.copy(),
|
|
209
|
+
seed = self.seed,
|
|
210
|
+
tile_size = tile_size
|
|
211
|
+
)
|
|
212
|
+
init_filled.paste(init_image, (0,0), init_image.split()[-1])
|
|
213
|
+
|
|
214
|
+
# Resize if requested for inpainting
|
|
215
|
+
if inpaint_width and inpaint_height:
|
|
216
|
+
init_filled = init_filled.resize((inpaint_width, inpaint_height))
|
|
217
|
+
|
|
218
|
+
debug_image(init_filled, "init_filled", debug_status=self.enable_image_debugging)
|
|
219
|
+
|
|
220
|
+
# Create init tensor
|
|
221
|
+
init_image = self._image_to_tensor(init_filled.convert('RGB'))
|
|
222
|
+
|
|
223
|
+
if isinstance(mask_image, PIL.Image.Image):
|
|
224
|
+
self.pil_mask = mask_image.copy()
|
|
225
|
+
debug_image(mask_image, "mask_image BEFORE multiply with pil_image", debug_status=self.enable_image_debugging)
|
|
226
|
+
|
|
227
|
+
mask_image = ImageChops.multiply(mask_image, self.pil_image.split()[-1].convert('RGB'))
|
|
228
|
+
self.pil_mask = mask_image
|
|
229
|
+
|
|
230
|
+
# Resize if requested for inpainting
|
|
231
|
+
if inpaint_width and inpaint_height:
|
|
232
|
+
mask_image = mask_image.resize((inpaint_width, inpaint_height))
|
|
233
|
+
|
|
234
|
+
debug_image(mask_image, "mask_image AFTER multiply with pil_image", debug_status=self.enable_image_debugging)
|
|
235
|
+
mask_image = mask_image.resize(
|
|
236
|
+
(
|
|
237
|
+
mask_image.width // downsampling,
|
|
238
|
+
mask_image.height // downsampling
|
|
239
|
+
),
|
|
240
|
+
resample=Image.Resampling.NEAREST
|
|
241
|
+
)
|
|
242
|
+
mask_image = self._image_to_tensor(mask_image,normalize=False)
|
|
243
|
+
|
|
244
|
+
self.mask_blur_radius = mask_blur_radius
|
|
245
|
+
|
|
246
|
+
# klms samplers not supported yet, so ignore previous sampler
|
|
247
|
+
if isinstance(sampler,KSampler):
|
|
248
|
+
print(
|
|
249
|
+
f">> Using recommended DDIM sampler for inpainting."
|
|
250
|
+
)
|
|
251
|
+
sampler = DDIMSampler(self.model, device=self.model.device)
|
|
252
|
+
|
|
253
|
+
sampler.make_schedule(
|
|
254
|
+
ddim_num_steps=steps, ddim_eta=ddim_eta, verbose=False
|
|
255
|
+
)
|
|
256
|
+
|
|
257
|
+
mask_image = mask_image[0][0].unsqueeze(0).repeat(4,1,1).unsqueeze(0)
|
|
258
|
+
mask_image = repeat(mask_image, '1 ... -> b ...', b=1)
|
|
259
|
+
|
|
260
|
+
scope = choose_autocast(self.precision)
|
|
261
|
+
with scope(self.model.device.type):
|
|
262
|
+
self.init_latent = self.model.get_first_stage_encoding(
|
|
263
|
+
self.model.encode_first_stage(init_image)
|
|
264
|
+
) # move to latent space
|
|
265
|
+
|
|
266
|
+
t_enc = int(strength * steps)
|
|
267
|
+
# todo: support cross-attention control
|
|
268
|
+
uc, c, _ = conditioning
|
|
269
|
+
|
|
270
|
+
print(f">> target t_enc is {t_enc} steps")
|
|
271
|
+
|
|
272
|
+
@torch.no_grad()
|
|
273
|
+
def make_image(x_T):
|
|
274
|
+
# encode (scaled latent)
|
|
275
|
+
z_enc = sampler.stochastic_encode(
|
|
276
|
+
self.init_latent,
|
|
277
|
+
torch.tensor([t_enc - 1]).to(self.model.device),
|
|
278
|
+
noise=x_T
|
|
279
|
+
)
|
|
280
|
+
|
|
281
|
+
# to replace masked area with latent noise, weighted by inpaint_replace strength
|
|
282
|
+
if inpaint_replace > 0.0:
|
|
283
|
+
print(f'>> inpaint will replace what was under the mask with a strength of {inpaint_replace}')
|
|
284
|
+
l_noise = self.get_noise(kwargs['width'],kwargs['height'])
|
|
285
|
+
inverted_mask = 1.0-mask_image # there will be 1s where the mask is
|
|
286
|
+
masked_region = (1.0-inpaint_replace) * inverted_mask * z_enc + inpaint_replace * inverted_mask * l_noise
|
|
287
|
+
z_enc = z_enc * mask_image + masked_region
|
|
288
|
+
|
|
289
|
+
if self.free_gpu_mem and self.model.model.device != self.model.device:
|
|
290
|
+
self.model.model.to(self.model.device)
|
|
291
|
+
|
|
292
|
+
# decode it
|
|
293
|
+
samples = sampler.decode(
|
|
294
|
+
z_enc,
|
|
295
|
+
c,
|
|
296
|
+
t_enc,
|
|
297
|
+
img_callback = step_callback,
|
|
298
|
+
unconditional_guidance_scale = cfg_scale,
|
|
299
|
+
unconditional_conditioning = uc,
|
|
300
|
+
mask = mask_image,
|
|
301
|
+
init_latent = self.init_latent
|
|
302
|
+
)
|
|
303
|
+
|
|
304
|
+
result = self.sample_to_image(samples)
|
|
305
|
+
|
|
306
|
+
# Seam paint if this is our first pass (seam_size set to 0 during seam painting)
|
|
307
|
+
if seam_size > 0:
|
|
308
|
+
old_image = self.pil_image or init_image
|
|
309
|
+
old_mask = self.pil_mask or mask_image
|
|
310
|
+
|
|
311
|
+
result = self.seam_paint(
|
|
312
|
+
result,
|
|
313
|
+
seam_size,
|
|
314
|
+
seam_blur,
|
|
315
|
+
prompt,
|
|
316
|
+
sampler,
|
|
317
|
+
seam_steps,
|
|
318
|
+
cfg_scale,
|
|
319
|
+
ddim_eta,
|
|
320
|
+
conditioning,
|
|
321
|
+
seam_strength,
|
|
322
|
+
x_T,
|
|
323
|
+
step_callback)
|
|
324
|
+
|
|
325
|
+
# Restore original settings
|
|
326
|
+
self.get_make_image(prompt,sampler,steps,cfg_scale,ddim_eta,
|
|
327
|
+
conditioning,
|
|
328
|
+
old_image,
|
|
329
|
+
old_mask,
|
|
330
|
+
strength,
|
|
331
|
+
mask_blur_radius, seam_size, seam_blur, seam_strength,
|
|
332
|
+
seam_steps, tile_size, step_callback,
|
|
333
|
+
inpaint_replace, enable_image_debugging,
|
|
334
|
+
inpaint_width = inpaint_width,
|
|
335
|
+
inpaint_height = inpaint_height,
|
|
336
|
+
infill_method = infill_method,
|
|
337
|
+
**kwargs)
|
|
338
|
+
|
|
339
|
+
return result
|
|
340
|
+
|
|
341
|
+
return make_image
|
|
342
|
+
|
|
343
|
+
|
|
344
|
+
def sample_to_image(self, samples)->Image.Image:
|
|
345
|
+
gen_result = super().sample_to_image(samples).convert('RGB')
|
|
346
|
+
debug_image(gen_result, "gen_result", debug_status=self.enable_image_debugging)
|
|
347
|
+
|
|
348
|
+
# Resize if necessary
|
|
349
|
+
if self.inpaint_width and self.inpaint_height:
|
|
350
|
+
gen_result = gen_result.resize(self.pil_image.size)
|
|
351
|
+
|
|
352
|
+
if self.pil_image is None or self.pil_mask is None:
|
|
353
|
+
return gen_result
|
|
354
|
+
|
|
355
|
+
corrected_result = super().repaste_and_color_correct(gen_result, self.pil_image, self.pil_mask, self.mask_blur_radius)
|
|
356
|
+
debug_image(corrected_result, "corrected_result", debug_status=self.enable_image_debugging)
|
|
357
|
+
|
|
358
|
+
return corrected_result
|
|
@@ -0,0 +1,175 @@
|
|
|
1
|
+
"""omnibus module to be used with the runwayml 9-channel custom inpainting model"""
|
|
2
|
+
|
|
3
|
+
import torch
|
|
4
|
+
import numpy as np
|
|
5
|
+
from einops import repeat
|
|
6
|
+
from PIL import Image, ImageOps, ImageChops
|
|
7
|
+
from ldm.invoke.devices import choose_autocast
|
|
8
|
+
from ldm.invoke.generator.base import downsampling
|
|
9
|
+
from ldm.invoke.generator.img2img import Img2Img
|
|
10
|
+
from ldm.invoke.generator.txt2img import Txt2Img
|
|
11
|
+
|
|
12
|
+
class Omnibus(Img2Img,Txt2Img):
|
|
13
|
+
def __init__(self, model, precision):
|
|
14
|
+
super().__init__(model, precision)
|
|
15
|
+
self.pil_mask = None
|
|
16
|
+
self.pil_image = None
|
|
17
|
+
|
|
18
|
+
def get_make_image(
|
|
19
|
+
self,
|
|
20
|
+
prompt,
|
|
21
|
+
sampler,
|
|
22
|
+
steps,
|
|
23
|
+
cfg_scale,
|
|
24
|
+
ddim_eta,
|
|
25
|
+
conditioning,
|
|
26
|
+
width,
|
|
27
|
+
height,
|
|
28
|
+
init_image = None,
|
|
29
|
+
mask_image = None,
|
|
30
|
+
strength = None,
|
|
31
|
+
step_callback=None,
|
|
32
|
+
threshold=0.0,
|
|
33
|
+
perlin=0.0,
|
|
34
|
+
mask_blur_radius: int = 8,
|
|
35
|
+
**kwargs):
|
|
36
|
+
"""
|
|
37
|
+
Returns a function returning an image derived from the prompt and the initial image
|
|
38
|
+
Return value depends on the seed at the time you call it.
|
|
39
|
+
"""
|
|
40
|
+
self.perlin = perlin
|
|
41
|
+
num_samples = 1
|
|
42
|
+
|
|
43
|
+
sampler.make_schedule(
|
|
44
|
+
ddim_num_steps=steps, ddim_eta=ddim_eta, verbose=False
|
|
45
|
+
)
|
|
46
|
+
|
|
47
|
+
if isinstance(init_image, Image.Image):
|
|
48
|
+
self.pil_image = init_image
|
|
49
|
+
if init_image.mode != 'RGB':
|
|
50
|
+
init_image = init_image.convert('RGB')
|
|
51
|
+
init_image = self._image_to_tensor(init_image)
|
|
52
|
+
|
|
53
|
+
if isinstance(mask_image, Image.Image):
|
|
54
|
+
self.pil_mask = mask_image
|
|
55
|
+
|
|
56
|
+
mask_image = ImageChops.multiply(mask_image.convert('L'), self.pil_image.split()[-1])
|
|
57
|
+
mask_image = self._image_to_tensor(ImageOps.invert(mask_image), normalize=False)
|
|
58
|
+
|
|
59
|
+
self.mask_blur_radius = mask_blur_radius
|
|
60
|
+
|
|
61
|
+
t_enc = steps
|
|
62
|
+
|
|
63
|
+
if init_image is not None and mask_image is not None: # inpainting
|
|
64
|
+
masked_image = init_image * (1 - mask_image) # masked image is the image masked by mask - masked regions zero
|
|
65
|
+
|
|
66
|
+
elif init_image is not None: # img2img
|
|
67
|
+
scope = choose_autocast(self.precision)
|
|
68
|
+
|
|
69
|
+
with scope(self.model.device.type):
|
|
70
|
+
self.init_latent = self.model.get_first_stage_encoding(
|
|
71
|
+
self.model.encode_first_stage(init_image)
|
|
72
|
+
) # move to latent space
|
|
73
|
+
|
|
74
|
+
# create a completely black mask (1s)
|
|
75
|
+
mask_image = torch.ones(1, 1, init_image.shape[2], init_image.shape[3], device=self.model.device)
|
|
76
|
+
# and the masked image is just a copy of the original
|
|
77
|
+
masked_image = init_image
|
|
78
|
+
|
|
79
|
+
else: # txt2img
|
|
80
|
+
init_image = torch.zeros(1, 3, height, width, device=self.model.device)
|
|
81
|
+
mask_image = torch.ones(1, 1, height, width, device=self.model.device)
|
|
82
|
+
masked_image = init_image
|
|
83
|
+
|
|
84
|
+
self.init_latent = init_image
|
|
85
|
+
height = init_image.shape[2]
|
|
86
|
+
width = init_image.shape[3]
|
|
87
|
+
model = self.model
|
|
88
|
+
|
|
89
|
+
def make_image(x_T):
|
|
90
|
+
with torch.no_grad():
|
|
91
|
+
scope = choose_autocast(self.precision)
|
|
92
|
+
with scope(self.model.device.type):
|
|
93
|
+
|
|
94
|
+
batch = self.make_batch_sd(
|
|
95
|
+
init_image,
|
|
96
|
+
mask_image,
|
|
97
|
+
masked_image,
|
|
98
|
+
prompt=prompt,
|
|
99
|
+
device=model.device,
|
|
100
|
+
num_samples=num_samples,
|
|
101
|
+
)
|
|
102
|
+
|
|
103
|
+
c = model.cond_stage_model.encode(batch["txt"])
|
|
104
|
+
c_cat = list()
|
|
105
|
+
for ck in model.concat_keys:
|
|
106
|
+
cc = batch[ck].float()
|
|
107
|
+
if ck != model.masked_image_key:
|
|
108
|
+
bchw = [num_samples, 4, height//8, width//8]
|
|
109
|
+
cc = torch.nn.functional.interpolate(cc, size=bchw[-2:])
|
|
110
|
+
else:
|
|
111
|
+
cc = model.get_first_stage_encoding(model.encode_first_stage(cc))
|
|
112
|
+
c_cat.append(cc)
|
|
113
|
+
c_cat = torch.cat(c_cat, dim=1)
|
|
114
|
+
|
|
115
|
+
# cond
|
|
116
|
+
cond={"c_concat": [c_cat], "c_crossattn": [c]}
|
|
117
|
+
|
|
118
|
+
# uncond cond
|
|
119
|
+
uc_cross = model.get_unconditional_conditioning(num_samples, "")
|
|
120
|
+
uc_full = {"c_concat": [c_cat], "c_crossattn": [uc_cross]}
|
|
121
|
+
shape = [model.channels, height//8, width//8]
|
|
122
|
+
|
|
123
|
+
samples, _ = sampler.sample(
|
|
124
|
+
batch_size = 1,
|
|
125
|
+
S = steps,
|
|
126
|
+
x_T = x_T,
|
|
127
|
+
conditioning = cond,
|
|
128
|
+
shape = shape,
|
|
129
|
+
verbose = False,
|
|
130
|
+
unconditional_guidance_scale = cfg_scale,
|
|
131
|
+
unconditional_conditioning = uc_full,
|
|
132
|
+
eta = 1.0,
|
|
133
|
+
img_callback = step_callback,
|
|
134
|
+
threshold = threshold,
|
|
135
|
+
)
|
|
136
|
+
if self.free_gpu_mem:
|
|
137
|
+
self.model.model.to("cpu")
|
|
138
|
+
return self.sample_to_image(samples)
|
|
139
|
+
|
|
140
|
+
return make_image
|
|
141
|
+
|
|
142
|
+
def make_batch_sd(
|
|
143
|
+
self,
|
|
144
|
+
image,
|
|
145
|
+
mask,
|
|
146
|
+
masked_image,
|
|
147
|
+
prompt,
|
|
148
|
+
device,
|
|
149
|
+
num_samples=1):
|
|
150
|
+
batch = {
|
|
151
|
+
"image": repeat(image.to(device=device), "1 ... -> n ...", n=num_samples),
|
|
152
|
+
"txt": num_samples * [prompt],
|
|
153
|
+
"mask": repeat(mask.to(device=device), "1 ... -> n ...", n=num_samples),
|
|
154
|
+
"masked_image": repeat(masked_image.to(device=device), "1 ... -> n ...", n=num_samples),
|
|
155
|
+
}
|
|
156
|
+
return batch
|
|
157
|
+
|
|
158
|
+
def get_noise(self, width:int, height:int):
|
|
159
|
+
if self.init_latent is not None:
|
|
160
|
+
height = self.init_latent.shape[2]
|
|
161
|
+
width = self.init_latent.shape[3]
|
|
162
|
+
return Txt2Img.get_noise(self,width,height)
|
|
163
|
+
|
|
164
|
+
|
|
165
|
+
def sample_to_image(self, samples)->Image.Image:
|
|
166
|
+
gen_result = super().sample_to_image(samples).convert('RGB')
|
|
167
|
+
|
|
168
|
+
if self.pil_image is None or self.pil_mask is None:
|
|
169
|
+
return gen_result
|
|
170
|
+
if self.pil_image.size != self.pil_mask.size:
|
|
171
|
+
return gen_result
|
|
172
|
+
|
|
173
|
+
corrected_result = super(Img2Img, self).repaste_and_color_correct(gen_result, self.pil_image, self.pil_mask, self.mask_blur_radius)
|
|
174
|
+
|
|
175
|
+
return corrected_result
|
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
'''
|
|
2
|
+
ldm.invoke.generator.txt2img inherits from ldm.invoke.generator
|
|
3
|
+
'''
|
|
4
|
+
|
|
5
|
+
import torch
|
|
6
|
+
import numpy as np
|
|
7
|
+
from ldm.invoke.generator.base import Generator
|
|
8
|
+
from ldm.models.diffusion.shared_invokeai_diffusion import InvokeAIDiffuserComponent
|
|
9
|
+
import gc
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
class Txt2Img(Generator):
|
|
13
|
+
def __init__(self, model, precision):
|
|
14
|
+
super().__init__(model, precision)
|
|
15
|
+
|
|
16
|
+
@torch.no_grad()
|
|
17
|
+
def get_make_image(self,prompt,sampler,steps,cfg_scale,ddim_eta,
|
|
18
|
+
conditioning,width,height,step_callback=None,threshold=0.0,perlin=0.0,
|
|
19
|
+
attention_maps_callback=None,
|
|
20
|
+
**kwargs):
|
|
21
|
+
"""
|
|
22
|
+
Returns a function returning an image derived from the prompt and the initial image
|
|
23
|
+
Return value depends on the seed at the time you call it
|
|
24
|
+
kwargs are 'width' and 'height'
|
|
25
|
+
"""
|
|
26
|
+
self.perlin = perlin
|
|
27
|
+
uc, c, extra_conditioning_info = conditioning
|
|
28
|
+
|
|
29
|
+
@torch.no_grad()
|
|
30
|
+
def make_image(x_T):
|
|
31
|
+
shape = [
|
|
32
|
+
self.latent_channels,
|
|
33
|
+
height // self.downsampling_factor,
|
|
34
|
+
width // self.downsampling_factor,
|
|
35
|
+
]
|
|
36
|
+
|
|
37
|
+
if self.free_gpu_mem and self.model.model.device != self.model.device:
|
|
38
|
+
self.model.model.to(self.model.device)
|
|
39
|
+
|
|
40
|
+
sampler.make_schedule(ddim_num_steps=steps, ddim_eta=ddim_eta, verbose=False)
|
|
41
|
+
|
|
42
|
+
samples, _ = sampler.sample(
|
|
43
|
+
batch_size = 1,
|
|
44
|
+
S = steps,
|
|
45
|
+
x_T = x_T,
|
|
46
|
+
conditioning = c,
|
|
47
|
+
shape = shape,
|
|
48
|
+
verbose = False,
|
|
49
|
+
unconditional_guidance_scale = cfg_scale,
|
|
50
|
+
unconditional_conditioning = uc,
|
|
51
|
+
extra_conditioning_info = extra_conditioning_info,
|
|
52
|
+
eta = ddim_eta,
|
|
53
|
+
img_callback = step_callback,
|
|
54
|
+
threshold = threshold,
|
|
55
|
+
attention_maps_callback = attention_maps_callback,
|
|
56
|
+
)
|
|
57
|
+
|
|
58
|
+
if self.free_gpu_mem:
|
|
59
|
+
self.model.model.to('cpu')
|
|
60
|
+
self.model.cond_stage_model.device = 'cpu'
|
|
61
|
+
self.model.cond_stage_model.to('cpu')
|
|
62
|
+
gc.collect()
|
|
63
|
+
torch.cuda.empty_cache()
|
|
64
|
+
|
|
65
|
+
return self.sample_to_image(samples)
|
|
66
|
+
|
|
67
|
+
return make_image
|
|
68
|
+
|
|
69
|
+
|
|
70
|
+
# returns a tensor filled with random numbers from a normal distribution
|
|
71
|
+
def get_noise(self,width,height):
|
|
72
|
+
device = self.model.device
|
|
73
|
+
if self.use_mps_noise or device.type == 'mps':
|
|
74
|
+
x = torch.randn([1,
|
|
75
|
+
self.latent_channels,
|
|
76
|
+
height // self.downsampling_factor,
|
|
77
|
+
width // self.downsampling_factor],
|
|
78
|
+
device='cpu').to(device)
|
|
79
|
+
else:
|
|
80
|
+
x = torch.randn([1,
|
|
81
|
+
self.latent_channels,
|
|
82
|
+
height // self.downsampling_factor,
|
|
83
|
+
width // self.downsampling_factor],
|
|
84
|
+
device=device)
|
|
85
|
+
if self.perlin > 0.0:
|
|
86
|
+
x = (1-self.perlin)*x + self.perlin*self.get_perlin_noise(width // self.downsampling_factor, height // self.downsampling_factor)
|
|
87
|
+
return x
|
|
88
|
+
|