| import io
|
| import os
|
| import base64
|
| import random
|
| import torch
|
| from PIL import Image
|
| from diffusers import AutoencoderKL
|
| from transformers import CLIPTokenizer, CLIPTextModel
|
| from flask import Flask, request, jsonify, render_template
|
| from utils import get_prompts, get_vocab, load_esrgan, load_diffusion_model, save_images, create_denoising_video
|
|
|
|
|
|
|
| app = Flask(__name__)
|
|
|
|
|
| @app.route('/')
|
| def index():
|
| return render_template('index.html',
|
| vocab=vocab,
|
| diffusion_model_list=diffusion_model_list,
|
| diffusion_model_path=diffusion_model_path)
|
|
|
|
|
| @app.route('/help')
|
| def help_page():
|
| return render_template('help.html')
|
|
|
|
|
| @app.route('/editor')
|
| def editor():
|
| return render_template('editor.html')
|
|
|
|
|
| @app.route('/random_prompt', methods=['GET'])
|
| def random_prompt():
|
| categories = request.args.getlist('categories')
|
| use_training_prompts = request.args.get('use_training_prompts', 'false').lower() == 'true'
|
| prompt = get_prompts(categories, use_training_prompts)
|
| return jsonify({"prompt": prompt})
|
|
|
|
|
| @app.route('/get_vocab', methods=['GET'])
|
| def get_vocab_route():
|
|
|
| return jsonify(vocab)
|
|
|
|
|
| @app.route('/progress', methods=['GET'])
|
| def get_progress():
|
| return jsonify({"progress": current_progress})
|
|
|
|
|
|
|
| @app.route('/generate', methods=['POST'])
|
| def generate():
|
| global current_consecutive_words, vocab, esrgan_type, upsampler, diffusion_model, diffusion_model_path, current_progress
|
|
|
|
|
| current_progress = 0
|
|
|
|
|
| prompt = str(request.form.get('prompt'))
|
|
|
| batch_size = int(request.form.get('batch_size'))
|
| denoising_steps = int(request.form.get('denoising_steps'))
|
| cfg_scale = float(request.form.get('cfg_scale', 4.5))
|
| img2img = False
|
| img_strength = float(request.form.get('strength', 0.5))
|
| noise_img2img = request.form.get('noise_image') is not None
|
| inpaint = False
|
| mask = None
|
|
|
| seed_str = request.form.get('seed', '').strip()
|
| chosen_model = request.form.get('diffusion_model_type')
|
| sampling_method = request.form.get('sampling_method')
|
| real_esrgan = request.form.get('esrgan')
|
| consecutive_words = int(request.form.get('consecutiveWords'))
|
| realtime_denoise = request.form.get('realtime_denoise') is not None
|
| video_fps = int(request.form.get('video_fps', 60))
|
|
|
|
|
| if consecutive_words != current_consecutive_words:
|
| current_consecutive_words = consecutive_words
|
| vocab = get_vocab(current_consecutive_words)
|
|
|
| if real_esrgan != esrgan_type and real_esrgan != "None":
|
| print(f"Now using upsampler type as : {real_esrgan}")
|
| esrgan_type = real_esrgan
|
| upsampler = load_esrgan(esrgan_type, device)
|
|
|
| if real_esrgan == "None":
|
| print(f"Now using Upsampler=None")
|
| upsampler = None
|
|
|
| if chosen_model != diffusion_model_path:
|
| diffusion_model_path = chosen_model
|
| diffusion_model, _ = load_diffusion_model(initial_load=False, diffusion_model_path=diffusion_model_path, device=device)
|
| print(f"Now using {diffusion_model_path=}")
|
|
|
| if seed_str.isnumeric():
|
| seed = int(seed_str)
|
| else:
|
| seed = random.randint(-10_000, 10_000)
|
|
|
|
|
| if 'image' in request.files and request.files['image'].filename != '':
|
| image_file = request.files['image']
|
| image = Image.open(image_file).convert("RGB")
|
| img2img = True
|
|
|
| if 'mask' in request.files and request.files['mask'].filename != '':
|
| mask_file = request.files['mask']
|
| mask = Image.open(mask_file).convert("L")
|
| inpaint = True
|
| noise_img2img = True
|
|
|
|
|
| print(f"Prompt: {prompt}")
|
| print(f"BS: {batch_size}")
|
| print(f"{real_esrgan=}")
|
| print(f"{esrgan_type=}")
|
| print(f"Seed: {seed}")
|
| print(f"Realtime denoise: {realtime_denoise}")
|
|
|
|
|
| gen.manual_seed(seed)
|
| use_ddim = sampling_method == "ddim"
|
|
|
|
|
| if realtime_denoise:
|
| for files in os.listdir("./flask_outputs/denoising_temp"):
|
| os.remove(os.path.join("./flask_outputs/denoising_temp", files))
|
|
|
|
|
|
|
| starting_step = 999
|
|
|
|
|
| img_warning = None
|
| msk_warning = None
|
| clean_latents = None
|
|
|
|
|
|
|
| if img2img:
|
| try:
|
|
|
|
|
|
|
| image = torch.tensor(image.getdata(), dtype=torch.float32).reshape(1, image_size, image_size, 3).permute(0, 3, 2, 1)
|
| image = ((image / 255) - 0.5) * 2
|
| clean_latents = vae.encode(image).latent_dist.sample() * 0.18215
|
| clean_latents = clean_latents.repeat(batch_size, 1, 1, 1)
|
|
|
|
|
|
|
|
|
|
|
|
|
| starting_step = 1000 - (1000 * img_strength)
|
| latents = clean_latents.clone().to(device)
|
|
|
| if noise_img2img:
|
| timesteps = torch.tensor([starting_step] * batch_size, device=device).long()
|
|
|
| latents, _ = diffusion_model._forward_process(latents, timesteps)
|
|
|
| except Exception as e:
|
| latents = torch.randn((batch_size, 4, image_size // 8, image_size // 8), generator=gen,
|
| device=device) * 0.18215
|
| img_warning = f"Warning: The provided image failed to be processed (likely due to size mismatch). Generating image from noise instead.\nWarning: {e=}"
|
| else:
|
| latents = torch.randn((batch_size, 4, image_size // 8, image_size // 8), generator=gen, device=device) * 0.18215
|
|
|
|
|
| if inpaint:
|
| try:
|
|
|
| assert img2img, "Need to submit an image for img2img to inpaint"
|
|
|
|
|
| assert img_warning is None, "Invalid image was given, no inpainting will be done."
|
|
|
|
|
| assert mask.size == (image_size, image_size), f"Image should be of shape ({image_size}, {image_size}), instead got {mask.size=}"
|
|
|
|
|
| mask = mask.resize((image_size//8, image_size//8), Image.Resampling.NEAREST)
|
|
|
|
|
| mask = torch.tensor(mask.getdata(), dtype=torch.int32)
|
|
|
|
|
| unique_vals = set(mask.flatten().tolist())
|
| assert len(unique_vals) == 2 and (sorted(unique_vals)[0] == 0 and sorted(unique_vals)[1] == 255), \
|
| f"This should only be a binary mask of 0s and 255s! Got {unique_vals=}"
|
|
|
|
|
|
|
| mask = mask.reshape(1, image_size//8, image_size//8, 1).permute(0, 3, 2, 1)
|
|
|
|
|
| mask = mask.masked_fill(mask == 255, 1)
|
|
|
| mask = mask.repeat(batch_size, 1, 1, 1)
|
|
|
| except Exception as e:
|
| msk_warning = f"Warning: The provided mask failed to be processed (likely due to size mismatch or invalid values). Mask will not be used.\nWarning: {e=}"
|
|
|
| if msk_warning is not None:
|
| mask = None
|
|
|
|
|
| generated_image, remaining_img = gen_image(latents, clean_latents, mask, prompt, starting_step, denoising_steps, cfg_scale, use_ddim, realtime_denoise)
|
|
|
|
|
| save_images(remaining_img)
|
|
|
| generated_image = Image.fromarray(generated_image.to(torch.uint8).numpy())
|
|
|
|
|
| buffered = io.BytesIO()
|
| generated_image.save(buffered, format="PNG")
|
| img_str = base64.b64encode(buffered.getvalue()).decode("utf-8")
|
|
|
| if realtime_denoise:
|
| create_denoising_video(video_fps)
|
|
|
|
|
| response_data = {"image": img_str}
|
|
|
| if img_warning is not None:
|
| response_data["img_warning"] = img_warning
|
|
|
| if msk_warning is not None:
|
| response_data["msk_warning"] = msk_warning
|
|
|
| return jsonify(response_data)
|
|
|
|
|
| def gen_image(latents, clean_latents, mask, prompt, starting_step, denoising_steps, cfg_scale, use_ddim, realtime_denoise):
|
| prompt_tensor = get_prompt_tensor(prompts=[prompt], batch_size=latents.shape[0])
|
| img_tensor = diffusion_model.generate(vae=vae,
|
| latents=latents,
|
| clean_latents=clean_latents,
|
| inpainting_mask=mask,
|
| prompt_tensor=prompt_tensor,
|
| t=starting_step,
|
| num_steps=denoising_steps,
|
| cfg_scale=cfg_scale,
|
| use_ddim=use_ddim,
|
| device=device,
|
| realtime_denoise=realtime_denoise,
|
| progress_callback=update_progress,
|
| )
|
|
|
|
|
| if upsampler is not None:
|
| img_tensor = upscale_tensor(img_tensor)
|
| else:
|
|
|
|
|
| img_tensor = torch.clip(img_tensor.permute(0, 3, 2, 1) * 255, min=0, max=255)
|
|
|
| display_img = img_tensor[0]
|
| remaining_img = img_tensor[1:]
|
|
|
|
|
| return display_img, remaining_img
|
|
|
|
|
| def upscale_tensor(input_tensor: torch.Tensor):
|
| assert len(input_tensor.shape) == 4, f"Expected input_tensor to be of shape (b, c, h, w) instead got {input_tensor.shape=}"
|
|
|
|
|
|
|
|
|
| input_img = input_tensor.permute(0, 2, 3, 1).cpu().numpy()
|
| input_img = (input_img * 255.0).clip(0, 255).astype('uint8')
|
|
|
|
|
| temp_holder = []
|
| for tensor in input_img:
|
| output_img, _ = upsampler.enhance(tensor)
|
| temp_holder.append(torch.tensor(output_img))
|
|
|
|
|
| output_img = torch.stack(temp_holder, dim=0)
|
|
|
|
|
|
|
| output_tensor = torch.tensor(output_img).permute(0, 2, 1, 3).float()
|
|
|
| return output_tensor
|
|
|
|
|
| def update_progress(value):
|
| global current_progress
|
| current_progress = 1000 - value
|
| print(current_progress)
|
|
|
|
|
| @torch.no_grad()
|
| def get_prompt_tensor(prompts, batch_size):
|
| """
|
| :param prompts: A list of strings
|
| :return: Torch tensor of shape (b, 77, 512)
|
| """
|
|
|
| tokens = clip_tokenizer(prompts, return_tensors="pt", padding="max_length", truncation=True).to(device)
|
| with torch.no_grad():
|
| outputs = clip_text_model(**tokens)
|
|
|
| return outputs.last_hidden_state.repeat(batch_size, 1, 1)
|
|
|
|
|
| @app.route('/autocomplete', methods=['GET'])
|
| def autocomplete():
|
| """Returns up to 5 matching words from the vocab_list based on the query."""
|
| query = request.args.get('q', '').strip().lower()
|
| if not query:
|
| return jsonify([])
|
|
|
|
|
| matches = [word for word in vocab if word.lower().startswith(query)]
|
|
|
| matches = matches[:5]
|
|
|
| return jsonify(matches)
|
|
|
|
|
|
|
| if __name__ == '__main__':
|
| device = "cuda" if torch.cuda.is_available() else "cpu"
|
|
|
| vae_path = "stabilityai/sd-vae-ft-ema"
|
| clip_path = "openai/clip-vit-base-patch32"
|
|
|
| image_size = 128
|
| current_progress = 0
|
|
|
| assert os.path.isdir("diffusion_model_weights")
|
|
|
| diffusion_model_list = [p for p in os.listdir("diffusion_model_weights") if p.endswith(".pth")]
|
| assert len(diffusion_model_list), ".pth file containing model weights should exist in the folder!"
|
|
|
|
|
|
|
| try:
|
| diffusion_model_list = sorted(diffusion_model_list, key=lambda x: int(x.split("_")[1]))
|
| except Exception as e:
|
| diffusion_model_list = sorted(diffusion_model_list)
|
|
|
|
|
|
|
| os.makedirs("flask_outputs", exist_ok=True)
|
| os.makedirs("./flask_outputs/batch_gen", exist_ok=True)
|
| os.makedirs("./flask_outputs/denoising_outputs", exist_ok=True)
|
| os.makedirs("./flask_outputs/denoising_temp", exist_ok=True)
|
|
|
|
|
|
|
| gen = torch.Generator()
|
|
|
|
|
| diffusion_model, diffusion_model_path = load_diffusion_model(initial_load=True, diffusion_model_path=None, device=device)
|
| print(f"Initial Diffusion Model Path: {diffusion_model_path}")
|
|
|
| n_params = sum([p.numel() for p in diffusion_model.parameters()])
|
| print(f"There are {n_params / 1e6:.1f}M parameters in the Diffusion Model")
|
|
|
|
|
| vae: AutoencoderKL = AutoencoderKL.from_pretrained(vae_path).to(device)
|
| vae.requires_grad_(False)
|
| vae.eval()
|
|
|
|
|
| clip_tokenizer = CLIPTokenizer.from_pretrained(clip_path, clean_up_tokenization_spaces=False)
|
| clip_text_model = CLIPTextModel.from_pretrained(clip_path).to(device)
|
| clip_text_model.requires_grad_(False)
|
| clip_text_model.eval()
|
|
|
|
|
| esrgan_type = "2x"
|
| upsampler = load_esrgan(esrgan_type, device)
|
|
|
| current_consecutive_words = 1
|
| vocab = get_vocab(current_consecutive_words)
|
|
|
|
|
| app.run(debug=True)
|
|
|