diff --git a/README.md b/README.md index bac646700b..8aa98d5b8b 100644 --- a/README.md +++ b/README.md @@ -1,7 +1,7 @@

Stable Diffusion Dream Script

- +

@@ -22,7 +22,7 @@ text-to-image generator. This fork supports: generating images in your browser. 3. Support for img2img in which you provide a seed image to guide the - image creation + image creation. (inpainting & masking coming soon) 4. Preliminary inpainting support. @@ -33,9 +33,9 @@ text-to-image generator. This fork supports: 7. Weighted subprompts for prompt tuning. -8. [Image variations](VARIATIONS.md) which allow you to systematically -generate variations of an image you like and combine two or more -images together to combine the best features of both. +7. [Image variations](VARIATIONS.md) which allow you to systematically + generate variations of an image you like and combine two or more + images together to combine the best features of both. 9. Textual inversion for customization of the prompt language and images. @@ -387,9 +387,8 @@ and introducing a new vocabulary to the fixed model. To train, prepare a folder that contains images sized at 512x512 and execute the following: - WINDOWS: As the default backend is not available on Windows, if you're using that platform, set the environment variable `PL_TORCH_DISTRIBUTED_BACKEND=gloo` - + ``` (ldm) ~/stable-diffusion$ python3 ./main.py --base ./configs/stable-diffusion/v1-finetune.yaml \ -t \ @@ -460,7 +459,7 @@ repository and associated paper for details and limitations. - WebUI supports GFPGAN/ESRGAN facial reconstruction and upscaling [Kevin Gibbons](https://github.com/bakkot) - WebUI supports incremental display of in-progress images during generation [Kevin Gibbons](https://github.com/bakkot) - A new configuration file scheme that allows new models (including upcoming stable-diffusion-v1.5) - to be added without altering the code. ([David Wager](https://github.com/maddavid12)) + to be added without altering the code. ([David Wager](https://github.com/maddavid12)) - Can specify --grid on dream.py command line as the default. - Miscellaneous internal bug and stability fixes. - Works on M1 Apple hardware. @@ -484,8 +483,8 @@ There are separate installation walkthroughs for [Linux](#linux), [Windows](#win ``` ~$ wget https://repo.anaconda.com/archive/Anaconda3-2022.05-Linux-x86_64.sh -~$ chmod +x Anaconda3-2022.05-Linux-x86_64.sh -~$ ./Anaconda3-2022.05-Linux-x86_64.sh +~$ chmod +x Anaconda3-2022.05-Linux-x86_64.sh +~$ ./Anaconda3-2022.05-Linux-x86_64.sh ``` After installing anaconda, you should log out of your system and log back in. If the installation @@ -674,9 +673,9 @@ python scripts\dream.py ``` 10. Subsequently, to relaunch the script, first activate the Anaconda -command window (step 3), enter the stable-diffusion directory (step 5, -"cd \path\to\stable-diffusion"), run "conda activate ldm" (step 6b), -and then launch the dream script (step 9). + command window (step 3), enter the stable-diffusion directory (step 5, + "cd \path\to\stable-diffusion"), run "conda activate ldm" (step 6b), + and then launch the dream script (step 9). **Note:** Tildebyte has written an alternative ["Easy peasy Windows install"](https://github.com/lstein/stable-diffusion/wiki/Easy-peasy-Windows-install) diff --git a/ldm/dream/server.py b/ldm/dream/server.py index 74064c4586..45847d00b7 100644 --- a/ldm/dream/server.py +++ b/ldm/dream/server.py @@ -1,11 +1,61 @@ +import argparse import json import base64 import mimetypes import os from http.server import BaseHTTPRequestHandler, ThreadingHTTPServer -from ldm.dream.pngwriter import PngWriter +from ldm.dream.pngwriter import PngWriter, PromptFormatter from threading import Event +def build_opt(post_data, seed, gfpgan_model_exists): + opt = argparse.Namespace() + setattr(opt, 'prompt', post_data['prompt']) + setattr(opt, 'init_img', post_data['initimg']) + setattr(opt, 'strength', float(post_data['strength'])) + setattr(opt, 'iterations', int(post_data['iterations'])) + setattr(opt, 'steps', int(post_data['steps'])) + setattr(opt, 'width', int(post_data['width'])) + setattr(opt, 'height', int(post_data['height'])) + setattr(opt, 'seamless', 'seamless' in post_data) + setattr(opt, 'fit', 'fit' in post_data) + setattr(opt, 'mask', 'mask' in post_data) + setattr(opt, 'invert_mask', 'invert_mask' in post_data) + setattr(opt, 'cfg_scale', float(post_data['cfg_scale'])) + setattr(opt, 'sampler_name', post_data['sampler_name']) + setattr(opt, 'gfpgan_strength', float(post_data['gfpgan_strength']) if gfpgan_model_exists else 0) + setattr(opt, 'upscale', [int(post_data['upscale_level']), float(post_data['upscale_strength'])] if post_data['upscale_level'] != '' else None) + setattr(opt, 'progress_images', 'progress_images' in post_data) + setattr(opt, 'seed', seed if int(post_data['seed']) == -1 else int(post_data['seed'])) + setattr(opt, 'threshold', float(post_data['threshold'])) + setattr(opt, 'perlin', float(post_data['perlin'])) + setattr(opt, 'variation_amount', float(post_data['variation_amount']) if int(post_data['seed']) != -1 else 0) + setattr(opt, 'with_variations', []) + + broken = False + if int(post_data['seed']) != -1 and post_data['with_variations'] != '': + for part in post_data['with_variations'].split(','): + seed_and_weight = part.split(':') + if len(seed_and_weight) != 2: + print(f'could not parse with_variation part "{part}"') + broken = True + break + try: + seed = int(seed_and_weight[0]) + weight = float(seed_and_weight[1]) + except ValueError: + print(f'could not parse with_variation part "{part}"') + broken = True + break + opt.with_variations.append([seed, weight]) + + if broken: + raise CanceledException + + if len(opt.with_variations) == 0: + opt.with_variations = None + + return opt + class CanceledException(Exception): pass @@ -81,59 +131,15 @@ class DreamServer(BaseHTTPRequestHandler): content_length = int(self.headers['Content-Length']) post_data = json.loads(self.rfile.read(content_length)) - prompt = post_data['prompt'] - initimg = post_data['initimg'] - strength = float(post_data['strength']) - iterations = int(post_data['iterations']) - steps = int(post_data['steps']) - width = int(post_data['width']) - height = int(post_data['height']) - fit = 'fit' in post_data - seamless = 'seamless' in post_data - cfgscale = float(post_data['cfgscale']) - sampler_name = post_data['sampler'] - variation_amount = float(post_data['variation_amount']) - with_variations = post_data['with_variations'] - gfpgan_strength = float(post_data['gfpgan_strength']) if gfpgan_model_exists else 0 - upscale_level = post_data['upscale_level'] - upscale_strength = post_data['upscale_strength'] - upscale = [int(upscale_level),float(upscale_strength)] if upscale_level != '' else None - progress_images = 'progress_images' in post_data - threshold = float(post_data['threshold']) - perlin = float(post_data['perlin']) - seed = None if int(post_data['seed']) == -1 else int(post_data['seed']) - - if with_variations != '': - parts = [] - broken = False - for part in with_variations.split(','): - seed_and_weight = part.split(':') - if len(seed_and_weight) != 2: - print(f'could not parse with_variation part "{part}"') - broken = True - break - try: - vseed = int(seed_and_weight[0]) - vweight = float(seed_and_weight[1]) - except ValueError: - print(f'could not parse with_variation part "{part}"') - broken = True - break - parts.append([vseed, vweight]) - if broken: - raise CanceledException - if len(parts) > 0: - with_variations = parts - else: - with_variations = None + opt = build_opt(post_data, self.model.seed, gfpgan_model_exists) self.canceled.clear() - print(f">> Request to generate with prompt: {prompt}") + print(f">> Request to generate with prompt: {opt.prompt}") # In order to handle upscaled images, the PngWriter needs to maintain state # across images generated by each call to prompt2img(), so we define it in # the outer scope of image_done() config = post_data.copy() # Shallow copy - config['initimg'] = config.pop('initimg_name','') + config['initimg'] = config.pop('initimg_name', '') images_generated = 0 # helps keep track of when upscaling is started images_upscaled = 0 # helps keep track of when upscaling is completed @@ -146,9 +152,21 @@ class DreamServer(BaseHTTPRequestHandler): # entry should not be inserted into the image list. def image_done(image, seed, upscaled=False): name = f'{prefix}.{seed}.png' - path = pngwriter.save_image_and_prompt_to_png(image, f'{prompt} -S{seed}', name) + iter_opt = argparse.Namespace(**vars(opt)) # copy + if opt.variation_amount > 0: + this_variation = [[seed, opt.variation_amount]] + if opt.with_variations is None: + iter_opt.with_variations = this_variation + else: + iter_opt.with_variations = opt.with_variations + this_variation + iter_opt.variation_amount = 0 + elif opt.with_variations is None: + iter_opt.seed = seed + normalized_prompt = PromptFormatter(self.model, iter_opt).normalize_prompt() + path = pngwriter.save_image_and_prompt_to_png(image, f'{normalized_prompt} -S{iter_opt.seed}', name) - config['seed'] = seed + if int(config['seed']) == -1: + config['seed'] = seed # Append post_data to log, but only once! if not upscaled: with open(os.path.join(self.outdir, "dream_web_log.txt"), "a") as log: @@ -159,24 +177,24 @@ class DreamServer(BaseHTTPRequestHandler): ) + '\n',"utf-8")) # control state of the "postprocessing..." message - upscaling_requested = upscale or gfpgan_strength>0 + upscaling_requested = opt.upscale or opt.gfpgan_strength > 0 nonlocal images_generated # NB: Is this bad python style? It is typical usage in a perl closure. nonlocal images_upscaled # NB: Is this bad python style? It is typical usage in a perl closure. if upscaled: images_upscaled += 1 else: - images_generated +=1 + images_generated += 1 if upscaling_requested: action = None - if images_generated >= iterations: - if images_upscaled < iterations: + if images_generated >= opt.iterations: + if images_upscaled < opt.iterations: action = 'upscaling-started' else: action = 'upscaling-done' if action: - x = images_upscaled+1 + x = images_upscaled + 1 self.wfile.write(bytes(json.dumps( - {'event':action,'processed_file_cnt':f'{x}/{iterations}'} + {'event': action, 'processed_file_cnt': f'{x}/{opt.iterations}'} ) + '\n',"utf-8")) step_writer = PngWriter(os.path.join(self.outdir, "intermediates")) @@ -189,10 +207,10 @@ class DreamServer(BaseHTTPRequestHandler): # since rendering images is moderately expensive, only render every 5th image # and don't bother with the last one, since it'll render anyway nonlocal step_index - if progress_images and step % 5 == 0 and step < steps - 1: + if opt.progress_images and step % 5 == 0 and step < opt.steps - 1: image = self.model.sample_to_image(sample) - name = f'{prefix}.{seed}.{step_index}.png' - metadata = f'{prompt} -S{seed} [intermediate]' + name = f'{prefix}.{opt.seed}.{step_index}.png' + metadata = f'{opt.prompt} -S{opt.seed} [intermediate]' path = step_writer.save_image_and_prompt_to_png(image, metadata, name) step_index += 1 self.wfile.write(bytes(json.dumps( @@ -200,53 +218,20 @@ class DreamServer(BaseHTTPRequestHandler): ) + '\n',"utf-8")) try: - if initimg is None: + if opt.init_img is None: # Run txt2img - self.model.prompt2image(prompt, - iterations=iterations, - cfg_scale = cfgscale, - width = width, - height = height, - seed = seed, - steps = steps, - variation_amount = variation_amount, - with_variations = with_variations, - gfpgan_strength = gfpgan_strength, - upscale = upscale, - sampler_name = sampler_name, - seamless = seamless, - step_callback=image_progress, - image_callback=image_done, - threshold=threshold, - perlin=perlin) + self.model.prompt2image(**vars(opt), step_callback=image_progress, image_callback=image_done) else: # Decode initimg as base64 to temp file with open("./img2img-tmp.png", "wb") as f: - initimg = initimg.split(",")[1] # Ignore mime type + initimg = opt.init_img.split(",")[1] # Ignore mime type f.write(base64.b64decode(initimg)) + opt1 = argparse.Namespace(**vars(opt)) + opt1.init_img = "./img2img-tmp.png" try: # Run img2img - self.model.prompt2image(prompt, - init_img = "./img2img-tmp.png", - strength = strength, - iterations = iterations, - cfg_scale = cfgscale, - seed = seed, - steps = steps, - variation_amount = variation_amount, - with_variations = with_variations, - sampler_name = sampler_name, - width = width, - height = height, - fit = fit, - seamless = seamless, - gfpgan_strength=gfpgan_strength, - upscale = upscale, - step_callback=image_progress, - image_callback=image_done, - threshold=threshold, - perlin=perlin) + self.model.prompt2image(**vars(opt1), step_callback=image_progress, image_callback=image_done) finally: # Remove the temp file os.remove("./img2img-tmp.png") diff --git a/ldm/modules/diffusionmodules/util.py b/ldm/modules/diffusionmodules/util.py index 197b42b2bc..2cb56a14a0 100644 --- a/ldm/modules/diffusionmodules/util.py +++ b/ldm/modules/diffusionmodules/util.py @@ -81,7 +81,9 @@ def make_ddim_timesteps( # assert ddim_timesteps.shape[0] == num_ddim_timesteps # add one to get the final alpha values right (the ones from first scale to data during sampling) - steps_out = ddim_timesteps + 1 +# steps_out = ddim_timesteps + 1 + steps_out = ddim_timesteps + if verbose: print(f'Selected timesteps for ddim sampler: {steps_out}') return steps_out diff --git a/ldm/simplet2i.py b/ldm/simplet2i.py index 548c44fa49..b4f6b3ef42 100644 --- a/ldm/simplet2i.py +++ b/ldm/simplet2i.py @@ -10,4 +10,4 @@ from ldm.generate import Generate class T2I(Generate): def __init__(self,**kwargs): print(f'>> The ldm.simplet2i module is deprecated. Use ldm.generate instead. It is a drop-in replacement.') - super().__init__(kwargs) + super().__init__(kwargs) \ No newline at end of file diff --git a/static/dream_web/favicon.ico b/static/dream_web/favicon.ico new file mode 100644 index 0000000000..51eb844a6a Binary files /dev/null and b/static/dream_web/favicon.ico differ diff --git a/static/dream_web/index.html b/static/dream_web/index.html index 2f02ed2094..da628c8e0e 100644 --- a/static/dream_web/index.html +++ b/static/dream_web/index.html @@ -2,9 +2,8 @@ Stable Diffusion Dream Server - + - @@ -30,21 +29,21 @@ - - - - + + - +