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
ldm/invoke/server.py
ADDED
|
@@ -0,0 +1,282 @@
|
|
|
1
|
+
import argparse
|
|
2
|
+
import json
|
|
3
|
+
import copy
|
|
4
|
+
import base64
|
|
5
|
+
import mimetypes
|
|
6
|
+
import os
|
|
7
|
+
from ldm.invoke.args import Args, metadata_dumps
|
|
8
|
+
from http.server import BaseHTTPRequestHandler, ThreadingHTTPServer
|
|
9
|
+
from ldm.invoke.pngwriter import PngWriter
|
|
10
|
+
from threading import Event
|
|
11
|
+
|
|
12
|
+
def build_opt(post_data, seed, gfpgan_model_exists):
|
|
13
|
+
opt = Args()
|
|
14
|
+
opt.parse_args() # initialize defaults
|
|
15
|
+
setattr(opt, 'prompt', post_data['prompt'])
|
|
16
|
+
setattr(opt, 'init_img', post_data['initimg'])
|
|
17
|
+
setattr(opt, 'strength', float(post_data['strength']))
|
|
18
|
+
setattr(opt, 'iterations', int(post_data['iterations']))
|
|
19
|
+
setattr(opt, 'steps', int(post_data['steps']))
|
|
20
|
+
setattr(opt, 'width', int(post_data['width']))
|
|
21
|
+
setattr(opt, 'height', int(post_data['height']))
|
|
22
|
+
setattr(opt, 'seamless', 'seamless' in post_data)
|
|
23
|
+
setattr(opt, 'fit', 'fit' in post_data)
|
|
24
|
+
setattr(opt, 'mask', 'mask' in post_data)
|
|
25
|
+
setattr(opt, 'invert_mask', 'invert_mask' in post_data)
|
|
26
|
+
setattr(opt, 'cfg_scale', float(post_data['cfg_scale']))
|
|
27
|
+
setattr(opt, 'sampler_name', post_data['sampler_name'])
|
|
28
|
+
|
|
29
|
+
# embiggen not practical at this point because we have no way of feeding images back into img2img
|
|
30
|
+
# however, this code is here against that eventuality
|
|
31
|
+
setattr(opt, 'embiggen', None)
|
|
32
|
+
setattr(opt, 'embiggen_tiles', None)
|
|
33
|
+
setattr(opt, 'embiggen_strength', None)
|
|
34
|
+
|
|
35
|
+
setattr(opt, 'facetool_strength', float(post_data['facetool_strength']) if gfpgan_model_exists else 0)
|
|
36
|
+
setattr(opt, 'upscale', [int(post_data['upscale_level']), float(post_data['upscale_strength'])] if post_data['upscale_level'] != '' else None)
|
|
37
|
+
setattr(opt, 'progress_images', 'progress_images' in post_data)
|
|
38
|
+
setattr(opt, 'progress_latents', 'progress_latents' in post_data)
|
|
39
|
+
setattr(opt, 'seed', None if int(post_data['seed']) == -1 else int(post_data['seed']))
|
|
40
|
+
setattr(opt, 'threshold', float(post_data['threshold']))
|
|
41
|
+
setattr(opt, 'perlin', float(post_data['perlin']))
|
|
42
|
+
setattr(opt, 'hires_fix', 'hires_fix' in post_data)
|
|
43
|
+
setattr(opt, 'variation_amount', float(post_data['variation_amount']) if int(post_data['seed']) != -1 else 0)
|
|
44
|
+
setattr(opt, 'with_variations', [])
|
|
45
|
+
setattr(opt, 'embiggen', None)
|
|
46
|
+
setattr(opt, 'embiggen_tiles', None)
|
|
47
|
+
|
|
48
|
+
broken = False
|
|
49
|
+
if int(post_data['seed']) != -1 and post_data['with_variations'] != '':
|
|
50
|
+
for part in post_data['with_variations'].split(','):
|
|
51
|
+
seed_and_weight = part.split(':')
|
|
52
|
+
if len(seed_and_weight) != 2:
|
|
53
|
+
print(f'could not parse WITH_variation part "{part}"')
|
|
54
|
+
broken = True
|
|
55
|
+
break
|
|
56
|
+
try:
|
|
57
|
+
seed = int(seed_and_weight[0])
|
|
58
|
+
weight = float(seed_and_weight[1])
|
|
59
|
+
except ValueError:
|
|
60
|
+
print(f'could not parse with_variation part "{part}"')
|
|
61
|
+
broken = True
|
|
62
|
+
break
|
|
63
|
+
opt.with_variations.append([seed, weight])
|
|
64
|
+
|
|
65
|
+
if broken:
|
|
66
|
+
raise CanceledException
|
|
67
|
+
|
|
68
|
+
if len(opt.with_variations) == 0:
|
|
69
|
+
opt.with_variations = None
|
|
70
|
+
|
|
71
|
+
return opt
|
|
72
|
+
|
|
73
|
+
class CanceledException(Exception):
|
|
74
|
+
pass
|
|
75
|
+
|
|
76
|
+
class DreamServer(BaseHTTPRequestHandler):
|
|
77
|
+
model = None
|
|
78
|
+
outdir = None
|
|
79
|
+
canceled = Event()
|
|
80
|
+
|
|
81
|
+
def do_GET(self):
|
|
82
|
+
if self.path == "/":
|
|
83
|
+
self.send_response(200)
|
|
84
|
+
self.send_header("Content-type", "text/html")
|
|
85
|
+
self.end_headers()
|
|
86
|
+
with open("./static/legacy_web/index.html", "rb") as content:
|
|
87
|
+
self.wfile.write(content.read())
|
|
88
|
+
elif self.path == "/config.js":
|
|
89
|
+
# unfortunately this import can't be at the top level, since that would cause a circular import
|
|
90
|
+
self.send_response(200)
|
|
91
|
+
self.send_header("Content-type", "application/javascript")
|
|
92
|
+
self.end_headers()
|
|
93
|
+
config = {
|
|
94
|
+
'gfpgan_model_exists': self.gfpgan_model_exists
|
|
95
|
+
}
|
|
96
|
+
self.wfile.write(bytes("let config = " + json.dumps(config) + ";\n", "utf-8"))
|
|
97
|
+
elif self.path == "/run_log.json":
|
|
98
|
+
self.send_response(200)
|
|
99
|
+
self.send_header("Content-type", "application/json")
|
|
100
|
+
self.end_headers()
|
|
101
|
+
output = []
|
|
102
|
+
|
|
103
|
+
log_file = os.path.join(self.outdir, "legacy_web_log.txt")
|
|
104
|
+
if os.path.exists(log_file):
|
|
105
|
+
with open(log_file, "r") as log:
|
|
106
|
+
for line in log:
|
|
107
|
+
url, config = line.split(": {", maxsplit=1)
|
|
108
|
+
config = json.loads("{" + config)
|
|
109
|
+
config["url"] = url.lstrip(".")
|
|
110
|
+
if os.path.exists(url):
|
|
111
|
+
output.append(config)
|
|
112
|
+
|
|
113
|
+
self.wfile.write(bytes(json.dumps({"run_log": output}), "utf-8"))
|
|
114
|
+
elif self.path == "/cancel":
|
|
115
|
+
self.canceled.set()
|
|
116
|
+
self.send_response(200)
|
|
117
|
+
self.send_header("Content-type", "application/json")
|
|
118
|
+
self.end_headers()
|
|
119
|
+
self.wfile.write(bytes('{}', 'utf8'))
|
|
120
|
+
else:
|
|
121
|
+
path_dir = os.path.dirname(self.path)
|
|
122
|
+
out_dir = os.path.realpath(self.outdir.rstrip('/'))
|
|
123
|
+
if self.path.startswith('/static/legacy_web/'):
|
|
124
|
+
path = '.' + self.path
|
|
125
|
+
elif out_dir.replace('\\', '/').endswith(path_dir):
|
|
126
|
+
file = os.path.basename(self.path)
|
|
127
|
+
path = os.path.join(self.outdir,file)
|
|
128
|
+
else:
|
|
129
|
+
self.send_response(404)
|
|
130
|
+
return
|
|
131
|
+
mime_type = mimetypes.guess_type(path)[0]
|
|
132
|
+
if mime_type is not None:
|
|
133
|
+
self.send_response(200)
|
|
134
|
+
self.send_header("Content-type", mime_type)
|
|
135
|
+
self.end_headers()
|
|
136
|
+
with open(path, "rb") as content:
|
|
137
|
+
self.wfile.write(content.read())
|
|
138
|
+
else:
|
|
139
|
+
self.send_response(404)
|
|
140
|
+
|
|
141
|
+
def do_POST(self):
|
|
142
|
+
self.send_response(200)
|
|
143
|
+
self.send_header("Content-type", "application/json")
|
|
144
|
+
self.end_headers()
|
|
145
|
+
|
|
146
|
+
# unfortunately this import can't be at the top level, since that would cause a circular import
|
|
147
|
+
|
|
148
|
+
content_length = int(self.headers['Content-Length'])
|
|
149
|
+
post_data = json.loads(self.rfile.read(content_length))
|
|
150
|
+
opt = build_opt(post_data, self.model.seed, self.gfpgan_model_exists)
|
|
151
|
+
|
|
152
|
+
self.canceled.clear()
|
|
153
|
+
# In order to handle upscaled images, the PngWriter needs to maintain state
|
|
154
|
+
# across images generated by each call to prompt2img(), so we define it in
|
|
155
|
+
# the outer scope of image_done()
|
|
156
|
+
config = post_data.copy() # Shallow copy
|
|
157
|
+
config['initimg'] = config.pop('initimg_name', '')
|
|
158
|
+
|
|
159
|
+
images_generated = 0 # helps keep track of when upscaling is started
|
|
160
|
+
images_upscaled = 0 # helps keep track of when upscaling is completed
|
|
161
|
+
pngwriter = PngWriter(self.outdir)
|
|
162
|
+
|
|
163
|
+
prefix = pngwriter.unique_prefix()
|
|
164
|
+
# if upscaling is requested, then this will be called twice, once when
|
|
165
|
+
# the images are first generated, and then again when after upscaling
|
|
166
|
+
# is complete. The upscaling replaces the original file, so the second
|
|
167
|
+
# entry should not be inserted into the image list.
|
|
168
|
+
# LS: This repeats code in dream.py
|
|
169
|
+
def image_done(image, seed, upscaled=False, first_seed=None):
|
|
170
|
+
name = f'{prefix}.{seed}.png'
|
|
171
|
+
iter_opt = copy.copy(opt)
|
|
172
|
+
if opt.variation_amount > 0:
|
|
173
|
+
this_variation = [[seed, opt.variation_amount]]
|
|
174
|
+
if opt.with_variations is None:
|
|
175
|
+
iter_opt.with_variations = this_variation
|
|
176
|
+
else:
|
|
177
|
+
iter_opt.with_variations = opt.with_variations + this_variation
|
|
178
|
+
iter_opt.variation_amount = 0
|
|
179
|
+
formatted_prompt = opt.dream_prompt_str(seed=seed)
|
|
180
|
+
path = pngwriter.save_image_and_prompt_to_png(
|
|
181
|
+
image,
|
|
182
|
+
dream_prompt = formatted_prompt,
|
|
183
|
+
metadata = metadata_dumps(iter_opt,
|
|
184
|
+
seeds = [seed],
|
|
185
|
+
model_hash = self.model.model_hash
|
|
186
|
+
),
|
|
187
|
+
name = name,
|
|
188
|
+
)
|
|
189
|
+
|
|
190
|
+
if int(config['seed']) == -1:
|
|
191
|
+
config['seed'] = seed
|
|
192
|
+
# Append post_data to log, but only once!
|
|
193
|
+
if not upscaled:
|
|
194
|
+
with open(os.path.join(self.outdir, "legacy_web_log.txt"), "a") as log:
|
|
195
|
+
log.write(f"{path}: {json.dumps(config)}\n")
|
|
196
|
+
|
|
197
|
+
self.wfile.write(bytes(json.dumps(
|
|
198
|
+
{'event': 'result', 'url': path, 'seed': seed, 'config': config}
|
|
199
|
+
) + '\n',"utf-8"))
|
|
200
|
+
|
|
201
|
+
# control state of the "postprocessing..." message
|
|
202
|
+
upscaling_requested = opt.upscale or opt.facetool_strength > 0
|
|
203
|
+
nonlocal images_generated # NB: Is this bad python style? It is typical usage in a perl closure.
|
|
204
|
+
nonlocal images_upscaled # NB: Is this bad python style? It is typical usage in a perl closure.
|
|
205
|
+
if upscaled:
|
|
206
|
+
images_upscaled += 1
|
|
207
|
+
else:
|
|
208
|
+
images_generated += 1
|
|
209
|
+
if upscaling_requested:
|
|
210
|
+
action = None
|
|
211
|
+
if images_generated >= opt.iterations:
|
|
212
|
+
if images_upscaled < opt.iterations:
|
|
213
|
+
action = 'upscaling-started'
|
|
214
|
+
else:
|
|
215
|
+
action = 'upscaling-done'
|
|
216
|
+
if action:
|
|
217
|
+
x = images_upscaled + 1
|
|
218
|
+
self.wfile.write(bytes(json.dumps(
|
|
219
|
+
{'event': action, 'processed_file_cnt': f'{x}/{opt.iterations}'}
|
|
220
|
+
) + '\n',"utf-8"))
|
|
221
|
+
|
|
222
|
+
step_writer = PngWriter(os.path.join(self.outdir, "intermediates"))
|
|
223
|
+
step_index = 1
|
|
224
|
+
def image_progress(sample, step):
|
|
225
|
+
if self.canceled.is_set():
|
|
226
|
+
self.wfile.write(bytes(json.dumps({'event':'canceled'}) + '\n', 'utf-8'))
|
|
227
|
+
raise CanceledException
|
|
228
|
+
path = None
|
|
229
|
+
# since rendering images is moderately expensive, only render every 5th image
|
|
230
|
+
# and don't bother with the last one, since it'll render anyway
|
|
231
|
+
nonlocal step_index
|
|
232
|
+
|
|
233
|
+
wants_progress_latents = opt.progress_latents
|
|
234
|
+
wants_progress_image = opt.progress_image and step % 5 == 0
|
|
235
|
+
|
|
236
|
+
if (wants_progress_image | wants_progress_latents) and step < opt.steps - 1:
|
|
237
|
+
image = self.model.sample_to_image(sample) if wants_progress_image \
|
|
238
|
+
else self.model.sample_to_lowres_estimated_image(sample)
|
|
239
|
+
step_index_padded = str(step_index).rjust(len(str(opt.steps)), '0')
|
|
240
|
+
name = f'{prefix}.{opt.seed}.{step_index_padded}.png'
|
|
241
|
+
metadata = f'{opt.prompt} -S{opt.seed} [intermediate]'
|
|
242
|
+
path = step_writer.save_image_and_prompt_to_png(image, dream_prompt=metadata, name=name)
|
|
243
|
+
step_index += 1
|
|
244
|
+
self.wfile.write(bytes(json.dumps(
|
|
245
|
+
{'event': 'step', 'step': step + 1, 'url': path}
|
|
246
|
+
) + '\n',"utf-8"))
|
|
247
|
+
|
|
248
|
+
try:
|
|
249
|
+
if opt.init_img is None:
|
|
250
|
+
# Run txt2img
|
|
251
|
+
self.model.prompt2image(**vars(opt), step_callback=image_progress, image_callback=image_done)
|
|
252
|
+
else:
|
|
253
|
+
# Decode initimg as base64 to temp file
|
|
254
|
+
with open("./img2img-tmp.png", "wb") as f:
|
|
255
|
+
initimg = opt.init_img.split(",")[1] # Ignore mime type
|
|
256
|
+
f.write(base64.b64decode(initimg))
|
|
257
|
+
opt1 = argparse.Namespace(**vars(opt))
|
|
258
|
+
opt1.init_img = "./img2img-tmp.png"
|
|
259
|
+
|
|
260
|
+
try:
|
|
261
|
+
# Run img2img
|
|
262
|
+
self.model.prompt2image(**vars(opt1), step_callback=image_progress, image_callback=image_done)
|
|
263
|
+
finally:
|
|
264
|
+
# Remove the temp file
|
|
265
|
+
os.remove("./img2img-tmp.png")
|
|
266
|
+
except CanceledException:
|
|
267
|
+
print(f"Canceled.")
|
|
268
|
+
return
|
|
269
|
+
except Exception as e:
|
|
270
|
+
print("Error happened")
|
|
271
|
+
print(e)
|
|
272
|
+
self.wfile.write(bytes(json.dumps(
|
|
273
|
+
{'event': 'error',
|
|
274
|
+
'message': str(e),
|
|
275
|
+
'type': e.__class__.__name__}
|
|
276
|
+
) + '\n',"utf-8"))
|
|
277
|
+
raise e
|
|
278
|
+
|
|
279
|
+
|
|
280
|
+
class ThreadingDreamServer(ThreadingHTTPServer):
|
|
281
|
+
def __init__(self, server_address):
|
|
282
|
+
super(ThreadingDreamServer, self).__init__(server_address, DreamServer)
|
|
@@ -0,0 +1,246 @@
|
|
|
1
|
+
import argparse
|
|
2
|
+
import json
|
|
3
|
+
import base64
|
|
4
|
+
import mimetypes
|
|
5
|
+
import os
|
|
6
|
+
from http.server import BaseHTTPRequestHandler, ThreadingHTTPServer
|
|
7
|
+
from ldm.invoke.pngwriter import PngWriter, PromptFormatter
|
|
8
|
+
from threading import Event
|
|
9
|
+
|
|
10
|
+
def build_opt(post_data, seed, gfpgan_model_exists):
|
|
11
|
+
opt = argparse.Namespace()
|
|
12
|
+
setattr(opt, 'prompt', post_data['prompt'])
|
|
13
|
+
setattr(opt, 'init_img', post_data['initimg'])
|
|
14
|
+
setattr(opt, 'strength', float(post_data['strength']))
|
|
15
|
+
setattr(opt, 'iterations', int(post_data['iterations']))
|
|
16
|
+
setattr(opt, 'steps', int(post_data['steps']))
|
|
17
|
+
setattr(opt, 'width', int(post_data['width']))
|
|
18
|
+
setattr(opt, 'height', int(post_data['height']))
|
|
19
|
+
setattr(opt, 'seamless', 'seamless' in post_data)
|
|
20
|
+
setattr(opt, 'fit', 'fit' in post_data)
|
|
21
|
+
setattr(opt, 'mask', 'mask' in post_data)
|
|
22
|
+
setattr(opt, 'invert_mask', 'invert_mask' in post_data)
|
|
23
|
+
setattr(opt, 'cfg_scale', float(post_data['cfg_scale']))
|
|
24
|
+
setattr(opt, 'sampler_name', post_data['sampler_name'])
|
|
25
|
+
setattr(opt, 'gfpgan_strength', float(post_data['gfpgan_strength']) if gfpgan_model_exists else 0)
|
|
26
|
+
setattr(opt, 'upscale', [int(post_data['upscale_level']), float(post_data['upscale_strength'])] if post_data['upscale_level'] != '' else None)
|
|
27
|
+
setattr(opt, 'progress_images', 'progress_images' in post_data)
|
|
28
|
+
setattr(opt, 'seed', None if int(post_data['seed']) == -1 else int(post_data['seed']))
|
|
29
|
+
setattr(opt, 'variation_amount', float(post_data['variation_amount']) if int(post_data['seed']) != -1 else 0)
|
|
30
|
+
setattr(opt, 'with_variations', [])
|
|
31
|
+
|
|
32
|
+
broken = False
|
|
33
|
+
if int(post_data['seed']) != -1 and post_data['with_variations'] != '':
|
|
34
|
+
for part in post_data['with_variations'].split(','):
|
|
35
|
+
seed_and_weight = part.split(':')
|
|
36
|
+
if len(seed_and_weight) != 2:
|
|
37
|
+
print(f'could not parse with_variation part "{part}"')
|
|
38
|
+
broken = True
|
|
39
|
+
break
|
|
40
|
+
try:
|
|
41
|
+
seed = int(seed_and_weight[0])
|
|
42
|
+
weight = float(seed_and_weight[1])
|
|
43
|
+
except ValueError:
|
|
44
|
+
print(f'could not parse with_variation part "{part}"')
|
|
45
|
+
broken = True
|
|
46
|
+
break
|
|
47
|
+
opt.with_variations.append([seed, weight])
|
|
48
|
+
|
|
49
|
+
if broken:
|
|
50
|
+
raise CanceledException
|
|
51
|
+
|
|
52
|
+
if len(opt.with_variations) == 0:
|
|
53
|
+
opt.with_variations = None
|
|
54
|
+
|
|
55
|
+
return opt
|
|
56
|
+
|
|
57
|
+
class CanceledException(Exception):
|
|
58
|
+
pass
|
|
59
|
+
|
|
60
|
+
class DreamServer(BaseHTTPRequestHandler):
|
|
61
|
+
model = None
|
|
62
|
+
outdir = None
|
|
63
|
+
canceled = Event()
|
|
64
|
+
|
|
65
|
+
def do_GET(self):
|
|
66
|
+
if self.path == "/":
|
|
67
|
+
self.send_response(200)
|
|
68
|
+
self.send_header("Content-type", "text/html")
|
|
69
|
+
self.end_headers()
|
|
70
|
+
with open("./static/dream_web/index.html", "rb") as content:
|
|
71
|
+
self.wfile.write(content.read())
|
|
72
|
+
elif self.path == "/config.js":
|
|
73
|
+
# unfortunately this import can't be at the top level, since that would cause a circular import
|
|
74
|
+
from ldm.gfpgan.gfpgan_tools import gfpgan_model_exists
|
|
75
|
+
self.send_response(200)
|
|
76
|
+
self.send_header("Content-type", "application/javascript")
|
|
77
|
+
self.end_headers()
|
|
78
|
+
config = {
|
|
79
|
+
'gfpgan_model_exists': gfpgan_model_exists
|
|
80
|
+
}
|
|
81
|
+
self.wfile.write(bytes("let config = " + json.dumps(config) + ";\n", "utf-8"))
|
|
82
|
+
elif self.path == "/run_log.json":
|
|
83
|
+
self.send_response(200)
|
|
84
|
+
self.send_header("Content-type", "application/json")
|
|
85
|
+
self.end_headers()
|
|
86
|
+
output = []
|
|
87
|
+
|
|
88
|
+
log_file = os.path.join(self.outdir, "dream_web_log.txt")
|
|
89
|
+
if os.path.exists(log_file):
|
|
90
|
+
with open(log_file, "r") as log:
|
|
91
|
+
for line in log:
|
|
92
|
+
url, config = line.split(": {", maxsplit=1)
|
|
93
|
+
config = json.loads("{" + config)
|
|
94
|
+
config["url"] = url.lstrip(".")
|
|
95
|
+
if os.path.exists(url):
|
|
96
|
+
output.append(config)
|
|
97
|
+
|
|
98
|
+
self.wfile.write(bytes(json.dumps({"run_log": output}), "utf-8"))
|
|
99
|
+
elif self.path == "/cancel":
|
|
100
|
+
self.canceled.set()
|
|
101
|
+
self.send_response(200)
|
|
102
|
+
self.send_header("Content-type", "application/json")
|
|
103
|
+
self.end_headers()
|
|
104
|
+
self.wfile.write(bytes('{}', 'utf8'))
|
|
105
|
+
else:
|
|
106
|
+
path = "." + self.path
|
|
107
|
+
cwd = os.path.realpath(os.getcwd())
|
|
108
|
+
is_in_cwd = os.path.commonprefix((os.path.realpath(path), cwd)) == cwd
|
|
109
|
+
if not (is_in_cwd and os.path.exists(path)):
|
|
110
|
+
self.send_response(404)
|
|
111
|
+
return
|
|
112
|
+
mime_type = mimetypes.guess_type(path)[0]
|
|
113
|
+
if mime_type is not None:
|
|
114
|
+
self.send_response(200)
|
|
115
|
+
self.send_header("Content-type", mime_type)
|
|
116
|
+
self.end_headers()
|
|
117
|
+
with open("." + self.path, "rb") as content:
|
|
118
|
+
self.wfile.write(content.read())
|
|
119
|
+
else:
|
|
120
|
+
self.send_response(404)
|
|
121
|
+
|
|
122
|
+
def do_POST(self):
|
|
123
|
+
self.send_response(200)
|
|
124
|
+
self.send_header("Content-type", "application/json")
|
|
125
|
+
self.end_headers()
|
|
126
|
+
|
|
127
|
+
# unfortunately this import can't be at the top level, since that would cause a circular import
|
|
128
|
+
# TODO temporarily commented out, import fails for some reason
|
|
129
|
+
# from ldm.gfpgan.gfpgan_tools import gfpgan_model_exists
|
|
130
|
+
gfpgan_model_exists = False
|
|
131
|
+
|
|
132
|
+
content_length = int(self.headers['Content-Length'])
|
|
133
|
+
post_data = json.loads(self.rfile.read(content_length))
|
|
134
|
+
opt = build_opt(post_data, self.model.seed, gfpgan_model_exists)
|
|
135
|
+
|
|
136
|
+
self.canceled.clear()
|
|
137
|
+
print(f">> Request to generate with prompt: {opt.prompt}")
|
|
138
|
+
# In order to handle upscaled images, the PngWriter needs to maintain state
|
|
139
|
+
# across images generated by each call to prompt2img(), so we define it in
|
|
140
|
+
# the outer scope of image_done()
|
|
141
|
+
config = post_data.copy() # Shallow copy
|
|
142
|
+
config['initimg'] = config.pop('initimg_name', '')
|
|
143
|
+
|
|
144
|
+
images_generated = 0 # helps keep track of when upscaling is started
|
|
145
|
+
images_upscaled = 0 # helps keep track of when upscaling is completed
|
|
146
|
+
pngwriter = PngWriter(self.outdir)
|
|
147
|
+
|
|
148
|
+
prefix = pngwriter.unique_prefix()
|
|
149
|
+
# if upscaling is requested, then this will be called twice, once when
|
|
150
|
+
# the images are first generated, and then again when after upscaling
|
|
151
|
+
# is complete. The upscaling replaces the original file, so the second
|
|
152
|
+
# entry should not be inserted into the image list.
|
|
153
|
+
def image_done(image, seed, upscaled=False, first_seed=-1, use_prefix=None):
|
|
154
|
+
print(f'First seed: {first_seed}')
|
|
155
|
+
name = f'{prefix}.{seed}.png'
|
|
156
|
+
iter_opt = argparse.Namespace(**vars(opt)) # copy
|
|
157
|
+
if opt.variation_amount > 0:
|
|
158
|
+
this_variation = [[seed, opt.variation_amount]]
|
|
159
|
+
if opt.with_variations is None:
|
|
160
|
+
iter_opt.with_variations = this_variation
|
|
161
|
+
else:
|
|
162
|
+
iter_opt.with_variations = opt.with_variations + this_variation
|
|
163
|
+
iter_opt.variation_amount = 0
|
|
164
|
+
elif opt.with_variations is None:
|
|
165
|
+
iter_opt.seed = seed
|
|
166
|
+
normalized_prompt = PromptFormatter(self.model, iter_opt).normalize_prompt()
|
|
167
|
+
path = pngwriter.save_image_and_prompt_to_png(image, f'{normalized_prompt} -S{iter_opt.seed}', name)
|
|
168
|
+
|
|
169
|
+
if int(config['seed']) == -1:
|
|
170
|
+
config['seed'] = seed
|
|
171
|
+
# Append post_data to log, but only once!
|
|
172
|
+
if not upscaled:
|
|
173
|
+
with open(os.path.join(self.outdir, "dream_web_log.txt"), "a") as log:
|
|
174
|
+
log.write(f"{path}: {json.dumps(config)}\n")
|
|
175
|
+
|
|
176
|
+
self.wfile.write(bytes(json.dumps(
|
|
177
|
+
{'event': 'result', 'url': path, 'seed': seed, 'config': config}
|
|
178
|
+
) + '\n',"utf-8"))
|
|
179
|
+
|
|
180
|
+
# control state of the "postprocessing..." message
|
|
181
|
+
upscaling_requested = opt.upscale or opt.gfpgan_strength > 0
|
|
182
|
+
nonlocal images_generated # NB: Is this bad python style? It is typical usage in a perl closure.
|
|
183
|
+
nonlocal images_upscaled # NB: Is this bad python style? It is typical usage in a perl closure.
|
|
184
|
+
if upscaled:
|
|
185
|
+
images_upscaled += 1
|
|
186
|
+
else:
|
|
187
|
+
images_generated += 1
|
|
188
|
+
if upscaling_requested:
|
|
189
|
+
action = None
|
|
190
|
+
if images_generated >= opt.iterations:
|
|
191
|
+
if images_upscaled < opt.iterations:
|
|
192
|
+
action = 'upscaling-started'
|
|
193
|
+
else:
|
|
194
|
+
action = 'upscaling-done'
|
|
195
|
+
if action:
|
|
196
|
+
x = images_upscaled + 1
|
|
197
|
+
self.wfile.write(bytes(json.dumps(
|
|
198
|
+
{'event': action, 'processed_file_cnt': f'{x}/{opt.iterations}'}
|
|
199
|
+
) + '\n',"utf-8"))
|
|
200
|
+
|
|
201
|
+
step_writer = PngWriter(os.path.join(self.outdir, "intermediates"))
|
|
202
|
+
step_index = 1
|
|
203
|
+
def image_progress(sample, step):
|
|
204
|
+
if self.canceled.is_set():
|
|
205
|
+
self.wfile.write(bytes(json.dumps({'event':'canceled'}) + '\n', 'utf-8'))
|
|
206
|
+
raise CanceledException
|
|
207
|
+
path = None
|
|
208
|
+
# since rendering images is moderately expensive, only render every 5th image
|
|
209
|
+
# and don't bother with the last one, since it'll render anyway
|
|
210
|
+
nonlocal step_index
|
|
211
|
+
if opt.progress_images and step % 5 == 0 and step < opt.steps - 1:
|
|
212
|
+
image = self.model.sample_to_image(sample)
|
|
213
|
+
name = f'{prefix}.{opt.seed}.{step_index}.png'
|
|
214
|
+
metadata = f'{opt.prompt} -S{opt.seed} [intermediate]'
|
|
215
|
+
path = step_writer.save_image_and_prompt_to_png(image, metadata, name)
|
|
216
|
+
step_index += 1
|
|
217
|
+
self.wfile.write(bytes(json.dumps(
|
|
218
|
+
{'event': 'step', 'step': step + 1, 'url': path}
|
|
219
|
+
) + '\n',"utf-8"))
|
|
220
|
+
|
|
221
|
+
try:
|
|
222
|
+
if opt.init_img is None:
|
|
223
|
+
# Run txt2img
|
|
224
|
+
self.model.prompt2image(**vars(opt), step_callback=image_progress, image_callback=image_done)
|
|
225
|
+
else:
|
|
226
|
+
# Decode initimg as base64 to temp file
|
|
227
|
+
with open("./img2img-tmp.png", "wb") as f:
|
|
228
|
+
initimg = opt.init_img.split(",")[1] # Ignore mime type
|
|
229
|
+
f.write(base64.b64decode(initimg))
|
|
230
|
+
opt1 = argparse.Namespace(**vars(opt))
|
|
231
|
+
opt1.init_img = "./img2img-tmp.png"
|
|
232
|
+
|
|
233
|
+
try:
|
|
234
|
+
# Run img2img
|
|
235
|
+
self.model.prompt2image(**vars(opt1), step_callback=image_progress, image_callback=image_done)
|
|
236
|
+
finally:
|
|
237
|
+
# Remove the temp file
|
|
238
|
+
os.remove("./img2img-tmp.png")
|
|
239
|
+
except CanceledException:
|
|
240
|
+
print(f"Canceled.")
|
|
241
|
+
return
|
|
242
|
+
|
|
243
|
+
|
|
244
|
+
class ThreadingDreamServer(ThreadingHTTPServer):
|
|
245
|
+
def __init__(self, server_address):
|
|
246
|
+
super(ThreadingDreamServer, self).__init__(server_address, DreamServer)
|
ldm/invoke/txt2mask.py
ADDED
|
@@ -0,0 +1,136 @@
|
|
|
1
|
+
'''Makes available the Txt2Mask class, which assists in the automatic
|
|
2
|
+
assignment of masks via text prompt using clipseg.
|
|
3
|
+
|
|
4
|
+
Here is typical usage:
|
|
5
|
+
|
|
6
|
+
from ldm.invoke.txt2mask import Txt2Mask, SegmentedGrayscale
|
|
7
|
+
from PIL import Image
|
|
8
|
+
|
|
9
|
+
txt2mask = Txt2Mask(self.device)
|
|
10
|
+
segmented = txt2mask.segment(Image.open('/path/to/img.png'),'a bagel')
|
|
11
|
+
|
|
12
|
+
# this will return a grayscale Image of the segmented data
|
|
13
|
+
grayscale = segmented.to_grayscale()
|
|
14
|
+
|
|
15
|
+
# this will return a semi-transparent image in which the
|
|
16
|
+
# selected object(s) are opaque and the rest is at various
|
|
17
|
+
# levels of transparency
|
|
18
|
+
transparent = segmented.to_transparent()
|
|
19
|
+
|
|
20
|
+
# this will return a masked image suitable for use in inpainting:
|
|
21
|
+
mask = segmented.to_mask(threshold=0.5)
|
|
22
|
+
|
|
23
|
+
The threshold used in the call to to_mask() selects pixels for use in
|
|
24
|
+
the mask that exceed the indicated confidence threshold. Values range
|
|
25
|
+
from 0.0 to 1.0. The higher the threshold, the more confident the
|
|
26
|
+
algorithm is. In limited testing, I have found that values around 0.5
|
|
27
|
+
work fine.
|
|
28
|
+
'''
|
|
29
|
+
|
|
30
|
+
import torch
|
|
31
|
+
import numpy as np
|
|
32
|
+
import os
|
|
33
|
+
from vendored.clipseg.models.clipseg import CLIPDensePredT
|
|
34
|
+
from einops import rearrange, repeat
|
|
35
|
+
from PIL import Image, ImageOps
|
|
36
|
+
from torchvision import transforms
|
|
37
|
+
from ldm.invoke.globals import Globals
|
|
38
|
+
|
|
39
|
+
CLIP_VERSION = 'ViT-B/16'
|
|
40
|
+
CLIPSEG_WEIGHTS = 'models/clipseg/clipseg_weights/rd64-uni.pth'
|
|
41
|
+
CLIPSEG_WEIGHTS_REFINED = 'models/clipseg/clipseg_weights/rd64-uni-refined.pth'
|
|
42
|
+
CLIPSEG_SIZE = 352
|
|
43
|
+
|
|
44
|
+
class SegmentedGrayscale(object):
|
|
45
|
+
def __init__(self, image:Image, heatmap:torch.Tensor):
|
|
46
|
+
self.heatmap = heatmap
|
|
47
|
+
self.image = image
|
|
48
|
+
|
|
49
|
+
def to_grayscale(self,invert:bool=False)->Image:
|
|
50
|
+
return self._rescale(Image.fromarray(np.uint8(255 - self.heatmap * 255 if invert else self.heatmap * 255)))
|
|
51
|
+
|
|
52
|
+
def to_mask(self,threshold:float=0.5)->Image:
|
|
53
|
+
discrete_heatmap = self.heatmap.lt(threshold).int()
|
|
54
|
+
return self._rescale(Image.fromarray(np.uint8(discrete_heatmap*255),mode='L'))
|
|
55
|
+
|
|
56
|
+
def to_transparent(self,invert:bool=False)->Image:
|
|
57
|
+
transparent_image = self.image.copy()
|
|
58
|
+
# For img2img, we want the selected regions to be transparent,
|
|
59
|
+
# but to_grayscale() returns the opposite. Thus invert.
|
|
60
|
+
gs = self.to_grayscale(not invert)
|
|
61
|
+
transparent_image.putalpha(gs)
|
|
62
|
+
return transparent_image
|
|
63
|
+
|
|
64
|
+
# unscales and uncrops the 352x352 heatmap so that it matches the image again
|
|
65
|
+
def _rescale(self, heatmap:Image)->Image:
|
|
66
|
+
size = self.image.width if (self.image.width > self.image.height) else self.image.height
|
|
67
|
+
resized_image = heatmap.resize(
|
|
68
|
+
(size,size),
|
|
69
|
+
resample=Image.Resampling.LANCZOS
|
|
70
|
+
)
|
|
71
|
+
return resized_image.crop((0,0,self.image.width,self.image.height))
|
|
72
|
+
|
|
73
|
+
class Txt2Mask(object):
|
|
74
|
+
'''
|
|
75
|
+
Create new Txt2Mask object. The optional device argument can be one of
|
|
76
|
+
'cuda', 'mps' or 'cpu'.
|
|
77
|
+
'''
|
|
78
|
+
def __init__(self,device='cpu',refined=False):
|
|
79
|
+
print('>> Initializing clipseg model for text to mask inference')
|
|
80
|
+
self.device = device
|
|
81
|
+
self.model = CLIPDensePredT(version=CLIP_VERSION, reduce_dim=64, complex_trans_conv=refined)
|
|
82
|
+
self.model.eval()
|
|
83
|
+
# initially we keep everything in cpu to conserve space
|
|
84
|
+
self.model.to('cpu')
|
|
85
|
+
self.model.load_state_dict(torch.load(os.path.join(Globals.root,CLIPSEG_WEIGHTS_REFINED)
|
|
86
|
+
if refined
|
|
87
|
+
else os.path.join(Globals.root,CLIPSEG_WEIGHTS),
|
|
88
|
+
map_location=torch.device('cpu')), strict=False
|
|
89
|
+
)
|
|
90
|
+
|
|
91
|
+
@torch.no_grad()
|
|
92
|
+
def segment(self, image, prompt:str) -> SegmentedGrayscale:
|
|
93
|
+
'''
|
|
94
|
+
Given a prompt string such as "a bagel", tries to identify the object in the
|
|
95
|
+
provided image and returns a SegmentedGrayscale object in which the brighter
|
|
96
|
+
pixels indicate where the object is inferred to be.
|
|
97
|
+
'''
|
|
98
|
+
self._to_device(self.device)
|
|
99
|
+
prompts = [prompt] # right now we operate on just a single prompt at a time
|
|
100
|
+
|
|
101
|
+
transform = transforms.Compose([
|
|
102
|
+
transforms.ToTensor(),
|
|
103
|
+
transforms.Normalize(mean=[0.485, 0.456, 0.406], std=[0.229, 0.224, 0.225]),
|
|
104
|
+
transforms.Resize((CLIPSEG_SIZE, CLIPSEG_SIZE)), # must be multiple of 64...
|
|
105
|
+
])
|
|
106
|
+
|
|
107
|
+
if type(image) is str:
|
|
108
|
+
image = Image.open(image).convert('RGB')
|
|
109
|
+
|
|
110
|
+
image = ImageOps.exif_transpose(image)
|
|
111
|
+
img = self._scale_and_crop(image)
|
|
112
|
+
img = transform(img).unsqueeze(0)
|
|
113
|
+
|
|
114
|
+
preds = self.model(img.repeat(len(prompts),1,1,1), prompts)[0]
|
|
115
|
+
heatmap = torch.sigmoid(preds[0][0]).cpu()
|
|
116
|
+
self._to_device('cpu')
|
|
117
|
+
return SegmentedGrayscale(image, heatmap)
|
|
118
|
+
|
|
119
|
+
def _to_device(self, device):
|
|
120
|
+
self.model.to(device)
|
|
121
|
+
|
|
122
|
+
def _scale_and_crop(self, image:Image)->Image:
|
|
123
|
+
scaled_image = Image.new('RGB',(CLIPSEG_SIZE,CLIPSEG_SIZE))
|
|
124
|
+
if image.width > image.height: # width is constraint
|
|
125
|
+
scale = CLIPSEG_SIZE / image.width
|
|
126
|
+
else:
|
|
127
|
+
scale = CLIPSEG_SIZE / image.height
|
|
128
|
+
scaled_image.paste(
|
|
129
|
+
image.resize(
|
|
130
|
+
(int(scale * image.width),
|
|
131
|
+
int(scale * image.height)
|
|
132
|
+
),
|
|
133
|
+
resample=Image.Resampling.LANCZOS
|
|
134
|
+
),box=(0,0)
|
|
135
|
+
)
|
|
136
|
+
return scaled_image
|