2022-08-21 23:57:48 +00:00
#!/usr/bin/env python3
2022-08-24 13:22:04 +00:00
# Copyright (c) 2022 Lincoln D. Stein (https://github.com/lstein)
2022-08-17 01:34:37 +00:00
import argparse
import shlex
2022-08-17 02:49:47 +00:00
import os
2022-08-21 15:03:22 +00:00
import sys
2022-08-24 16:02:36 +00:00
import copy
2022-08-26 02:57:30 +00:00
2022-08-24 23:47:59 +00:00
from ldm . dream_util import Completer , PngWriter , PromptFormatter
2022-08-18 20:00:44 +00:00
2022-08-21 23:57:48 +00:00
debugging = False
2022-08-18 16:43:59 +00:00
2022-08-17 01:34:37 +00:00
def main ( ) :
2022-08-17 16:00:00 +00:00
''' Initialize command-line parsers and the diffusion model '''
2022-08-17 01:34:37 +00:00
arg_parser = create_argv_parser ( )
opt = arg_parser . parse_args ( )
if opt . laion400m :
# defaults suitable to the older latent diffusion weights
width = 256
height = 256
config = " configs/latent-diffusion/txt2img-1p4B-eval.yaml "
weights = " models/ldm/text2img-large/model.ckpt "
else :
# some defaults suitable for stable diffusion weights
width = 512
height = 512
config = " configs/stable-diffusion/v1-inference.yaml "
weights = " models/ldm/stable-diffusion-v1/model.ckpt "
print ( " * Initializing, be patient... \n " )
2022-08-21 15:03:22 +00:00
sys . path . append ( ' . ' )
2022-08-17 01:34:37 +00:00
from pytorch_lightning import logging
from ldm . simplet2i import T2I
2022-08-22 19:33:27 +00:00
# these two lines prevent a horrible warning message from appearing
# when the frozen CLIP tokenizer is imported
import transformers
transformers . logging . set_verbosity_error ( )
2022-08-17 01:34:37 +00:00
# creating a simple text2image object with a handful of
# defaults passed on the command line.
# additional parameters will be added (or overriden) during
# the user input loop
t2i = T2I ( width = width ,
height = height ,
2022-08-22 04:12:16 +00:00
sampler_name = opt . sampler_name ,
2022-08-17 01:34:37 +00:00
weights = weights ,
2022-08-21 23:57:48 +00:00
full_precision = opt . full_precision ,
2022-08-22 02:48:40 +00:00
config = config ,
2022-08-23 22:26:28 +00:00
latent_diffusion_weights = opt . laion400m , # this is solely for recreating the prompt
2022-08-24 15:50:48 +00:00
embedding_path = opt . embedding_path ,
2022-08-23 17:49:17 +00:00
device = opt . device
2022-08-22 02:48:40 +00:00
)
2022-08-17 01:34:37 +00:00
2022-08-17 02:49:47 +00:00
# make sure the output directory exists
if not os . path . exists ( opt . outdir ) :
os . makedirs ( opt . outdir )
2022-08-17 01:34:37 +00:00
# gets rid of annoying messages about random seed
logging . getLogger ( " pytorch_lightning " ) . setLevel ( logging . ERROR )
2022-08-23 04:30:06 +00:00
infile = None
try :
if opt . infile is not None :
infile = open ( opt . infile , ' r ' )
except FileNotFoundError as e :
print ( e )
exit ( - 1 )
2022-08-17 01:34:37 +00:00
# preload the model
2022-08-24 16:57:04 +00:00
t2i . load_model ( )
2022-08-26 02:57:30 +00:00
# load GFPGAN if requested
if opt . use_gfpgan :
print ( " \n * --gfpgan was specified, loading gfpgan... " )
try :
model_path = os . path . join ( opt . gfpgan_dir , opt . gfpgan_model_path )
if not os . path . isfile ( model_path ) :
raise Exception ( " GFPGAN model not found at path " + model_path )
sys . path . append ( os . path . abspath ( opt . gfpgan_dir ) )
from gfpgan import GFPGANer
bg_upsampler = None
if opt . gfpgan_bg_upsampler is not None :
bg_upsampler = load_gfpgan_bg_upsampler ( opt . gfpgan_bg_upsampler , opt . gfpgan_bg_tile )
t2i . gfpgan = GFPGANer ( model_path = model_path , upscale = opt . gfpgan_upscale , arch = ' clean ' , channel_multiplier = 2 , bg_upsampler = bg_upsampler )
except Exception :
import traceback
print ( " Error loading GFPGAN: " , file = sys . stderr )
print ( traceback . format_exc ( ) , file = sys . stderr )
2022-08-23 01:14:31 +00:00
print ( " \n * Initialization done! Awaiting your command (-h for help, ' q ' to quit, ' cd ' to change output dir, ' pwd ' to print output dir)... " )
2022-08-17 01:34:37 +00:00
2022-08-23 04:30:06 +00:00
log_path = os . path . join ( opt . outdir , ' dream_log.txt ' )
2022-08-17 01:34:37 +00:00
with open ( log_path , ' a ' ) as log :
cmd_parser = create_cmd_parser ( )
2022-08-24 23:47:59 +00:00
main_loop ( t2i , opt . outdir , cmd_parser , log , infile )
2022-08-17 01:34:37 +00:00
log . close ( )
2022-08-23 04:51:38 +00:00
if infile :
infile . close ( )
2022-08-17 01:34:37 +00:00
2022-08-19 03:23:44 +00:00
2022-08-24 23:47:59 +00:00
def main_loop ( t2i , outdir , parser , log , infile ) :
2022-08-17 16:00:00 +00:00
''' prompt/read/execute loop '''
2022-08-19 03:03:22 +00:00
done = False
while not done :
2022-08-17 01:34:37 +00:00
try :
2022-08-23 04:30:06 +00:00
command = infile . readline ( ) if infile else input ( " dream> " )
2022-08-17 01:34:37 +00:00
except EOFError :
2022-08-19 03:03:22 +00:00
done = True
2022-08-17 01:34:37 +00:00
break
2022-08-23 04:30:06 +00:00
if infile and len ( command ) == 0 :
done = True
break
if command . startswith ( ( ' # ' , ' // ' ) ) :
continue
2022-08-23 17:46:50 +00:00
# before splitting, escape single quotes so as not to mess
# up the parser
command = command . replace ( " ' " , " \\ ' " )
2022-08-22 20:55:06 +00:00
try :
elements = shlex . split ( command )
except ValueError as e :
print ( str ( e ) )
continue
2022-08-22 16:40:54 +00:00
if len ( elements ) == 0 :
continue
2022-08-23 04:30:06 +00:00
2022-08-23 01:14:31 +00:00
if elements [ 0 ] == ' q ' :
2022-08-19 03:03:22 +00:00
done = True
break
2022-08-23 01:01:06 +00:00
2022-08-23 03:56:36 +00:00
if elements [ 0 ] == ' cd ' and len ( elements ) > 1 :
if os . path . exists ( elements [ 1 ] ) :
print ( f " setting image output directory to { elements [ 1 ] } " )
2022-08-24 23:47:59 +00:00
outdir = elements [ 1 ]
2022-08-23 03:56:36 +00:00
else :
print ( f " directory { elements [ 1 ] } does not exist " )
2022-08-23 01:14:31 +00:00
continue
if elements [ 0 ] == ' pwd ' :
2022-08-24 23:47:59 +00:00
print ( f " current output directory is { outdir } " )
2022-08-23 01:14:31 +00:00
continue
2022-08-19 03:03:22 +00:00
if elements [ 0 ] . startswith ( ' !dream ' ) : # in case a stored prompt still contains the !dream command
elements . pop ( 0 )
# rearrange the arguments to mimic how it works in the Dream bot.
2022-08-17 01:34:37 +00:00
switches = [ ' ' ]
switches_started = False
for el in elements :
if el [ 0 ] == ' - ' and not switches_started :
switches_started = True
if switches_started :
switches . append ( el )
else :
switches [ 0 ] + = el
switches [ 0 ] + = ' '
switches [ 0 ] = switches [ 0 ] [ : len ( switches [ 0 ] ) - 1 ]
2022-08-17 16:35:49 +00:00
2022-08-17 01:34:37 +00:00
try :
opt = parser . parse_args ( switches )
except SystemExit :
parser . print_help ( )
2022-08-17 16:35:49 +00:00
continue
if len ( opt . prompt ) == 0 :
print ( " Try again with a prompt! " )
continue
2022-08-24 23:47:59 +00:00
normalized_prompt = PromptFormatter ( t2i , opt ) . normalize_prompt ( )
2022-08-25 21:26:48 +00:00
individual_images = not opt . grid
2022-08-25 04:42:37 +00:00
2022-08-24 13:22:04 +00:00
try :
2022-08-25 04:42:37 +00:00
file_writer = PngWriter ( outdir , normalized_prompt , opt . batch_size )
2022-08-25 21:26:48 +00:00
callback = file_writer . write_image if individual_images else None
2022-08-24 23:47:59 +00:00
2022-08-25 21:26:48 +00:00
image_list = t2i . prompt2image ( image_callback = callback , * * vars ( opt ) )
results = file_writer . files_written if individual_images else image_list
2022-08-24 23:47:59 +00:00
2022-08-25 21:26:48 +00:00
if opt . grid and len ( results ) > 0 :
grid_img = file_writer . make_grid ( [ r [ 0 ] for r in results ] )
filename = file_writer . unique_filename ( results [ 0 ] [ 1 ] )
seeds = [ a [ 1 ] for a in results ]
results = [ [ filename , seeds ] ]
metadata_prompt = f ' { normalized_prompt } -S { results [ 0 ] [ 1 ] } '
file_writer . save_image_and_prompt_to_png ( grid_img , metadata_prompt , filename )
2022-08-25 04:42:37 +00:00
2022-08-24 13:22:04 +00:00
except AssertionError as e :
print ( e )
continue
2022-08-24 15:42:44 +00:00
2022-08-25 21:26:48 +00:00
except OSError as e :
print ( e )
continue
2022-08-23 04:51:38 +00:00
print ( " Outputs: " )
2022-08-24 23:47:59 +00:00
write_log_message ( t2i , normalized_prompt , results , log )
2022-08-19 03:23:44 +00:00
2022-08-19 03:03:22 +00:00
print ( " goodbye! " )
2022-08-26 02:57:30 +00:00
def load_gfpgan_bg_upsampler ( bg_upsampler , bg_tile = 400 ) :
import torch
if bg_upsampler == ' realesrgan ' :
if not torch . cuda . is_available ( ) : # CPU
import warnings
warnings . warn ( ' The unoptimized RealESRGAN is slow on CPU. We do not use it. '
' If you really want to use it, please modify the corresponding codes. ' )
bg_upsampler = None
else :
from basicsr . archs . rrdbnet_arch import RRDBNet
from realesrgan import RealESRGANer
model = RRDBNet ( num_in_ch = 3 , num_out_ch = 3 , num_feat = 64 , num_block = 23 , num_grow_ch = 32 , scale = 2 )
bg_upsampler = RealESRGANer (
scale = 2 ,
model_path = ' https://github.com/xinntao/Real-ESRGAN/releases/download/v0.2.1/RealESRGAN_x2plus.pth ' ,
model = model ,
tile = bg_tile ,
tile_pad = 10 ,
pre_pad = 0 ,
half = True ) # need to set False in CPU mode
else :
bg_upsampler = None
return bg_upsampler
2022-08-25 21:26:48 +00:00
# variant generation is going to be superseded by a generalized
# "prompt-morph" functionality
# def generate_variants(t2i,outdir,opt,previous_gens):
# variants = []
# print(f"Generating {opt.variants} variant(s)...")
# newopt = copy.deepcopy(opt)
# newopt.iterations = 1
# newopt.variants = None
# for r in previous_gens:
# newopt.init_img = r[0]
# prompt = PromptFormatter(t2i,newopt).normalize_prompt()
# print(f"] generating variant for {newopt.init_img}")
# for j in range(0,opt.variants):
# try:
# file_writer = PngWriter(outdir,prompt,newopt.batch_size)
# callback = file_writer.write_image
# t2i.prompt2image(image_callback=callback,**vars(newopt))
# results = file_writer.files_written
# variants.append([prompt,results])
# except AssertionError as e:
# print(e)
# continue
# print(f'{opt.variants} variants generated')
# return variants
2022-08-25 04:42:37 +00:00
2022-08-24 23:47:59 +00:00
def write_log_message ( t2i , prompt , results , logfile ) :
2022-08-25 04:42:37 +00:00
''' logs the name of the output image, its prompt and seed to the terminal, log file, and a Dream text chunk in the PNG metadata '''
2022-08-22 02:48:40 +00:00
last_seed = None
img_num = 1
2022-08-22 04:12:16 +00:00
seenit = { }
2022-08-23 03:56:36 +00:00
2022-08-17 16:00:00 +00:00
for r in results :
2022-08-22 02:48:40 +00:00
seed = r [ 1 ]
2022-08-24 23:47:59 +00:00
log_message = ( f ' { r [ 0 ] } : { prompt } -S { seed } ' )
2022-08-22 02:48:40 +00:00
2022-08-17 16:00:00 +00:00
print ( log_message )
logfile . write ( log_message + " \n " )
logfile . flush ( )
2022-08-22 02:48:40 +00:00
2022-08-17 01:34:37 +00:00
def create_argv_parser ( ) :
parser = argparse . ArgumentParser ( description = " Parse script ' s command line args " )
parser . add_argument ( " --laion400m " ,
" --latent_diffusion " ,
" -l " ,
dest = ' laion400m ' ,
action = ' store_true ' ,
2022-08-22 02:48:40 +00:00
help = " fallback to the latent diffusion (laion400m) weights and config " )
2022-08-23 04:30:06 +00:00
parser . add_argument ( " --from_file " ,
dest = ' infile ' ,
type = str ,
help = " if specified, load prompts from this file " )
2022-08-17 01:34:37 +00:00
parser . add_argument ( ' -n ' , ' --iterations ' ,
type = int ,
default = 1 ,
2022-08-17 02:23:24 +00:00
help = " number of images to generate " )
2022-08-21 23:57:48 +00:00
parser . add_argument ( ' -F ' , ' --full_precision ' ,
dest = ' full_precision ' ,
action = ' store_true ' ,
help = " use slower full precision math for calculations " )
2022-08-22 02:48:40 +00:00
parser . add_argument ( ' --sampler ' , ' -m ' ,
2022-08-22 04:12:16 +00:00
dest = " sampler_name " ,
2022-08-23 21:25:39 +00:00
choices = [ ' ddim ' , ' k_dpm_2_a ' , ' k_dpm_2 ' , ' k_euler_a ' , ' k_euler ' , ' k_heun ' , ' k_lms ' , ' plms ' ] ,
default = ' k_lms ' ,
help = " which sampler to use (k_lms) - can only be set on command line " )
2022-08-23 14:39:18 +00:00
parser . add_argument ( ' --outdir ' ,
' -o ' ,
2022-08-17 01:34:37 +00:00
type = str ,
2022-08-19 03:23:44 +00:00
default = " outputs/img-samples " ,
2022-08-26 02:19:12 +00:00
help = " directory in which to place generated images and a log of prompts and seeds (outputs/img-samples " )
2022-08-23 22:26:28 +00:00
parser . add_argument ( ' --embedding_path ' ,
type = str ,
help = " Path to a pre-trained embedding manager checkpoint - can only be set on command line " )
2022-08-23 17:49:17 +00:00
parser . add_argument ( ' --device ' ,
' -d ' ,
type = str ,
default = " cuda " ,
help = " device to run stable diffusion on. defaults to cuda `torch.cuda.current_device()` if avalible " )
2022-08-26 02:57:30 +00:00
# GFPGAN related args
parser . add_argument ( ' --gfpgan ' ,
dest = ' use_gfpgan ' ,
action = ' store_true ' ,
help = " load gfpgan for use in the dreambot. Note: Enabling GFPGAN will require more GPU memory " )
parser . add_argument ( " --gfpgan_upscale " ,
type = int ,
default = 2 ,
help = " The final upsampling scale of the image. Default: 2. Only used if --gfpgan is specified " )
parser . add_argument ( " --gfpgan_bg_upsampler " ,
type = str ,
default = ' realesrgan ' ,
help = " Background upsampler. Default: None. Options: realesrgan, none. Only used if --gfpgan is specified " )
parser . add_argument ( " --gfpgan_bg_tile " ,
type = int ,
default = 400 ,
help = " Tile size for background sampler, 0 for no tile during testing. Default: 400. Only used if --gfpgan is specified " )
parser . add_argument ( " --gfpgan_model_path " ,
type = str ,
default = ' experiments/pretrained_models/GFPGANv1.3.pth ' ,
help = " indicates the path to the GFPGAN model, relative to --gfpgan_dir. Only used if --gfpgan is specified " )
parser . add_argument ( " --gfpgan_dir " ,
type = str ,
2022-08-26 03:31:08 +00:00
default = ' ../GFPGAN ' ,
2022-08-26 02:57:30 +00:00
help = " indicates the directory containing the GFPGAN code. Only used if --gfpgan is specified " )
2022-08-17 01:34:37 +00:00
return parser
def create_cmd_parser ( ) :
2022-08-17 16:35:49 +00:00
parser = argparse . ArgumentParser ( description = ' Example: dream> a fantastic alien landscape -W1024 -H960 -s100 -n12 ' )
2022-08-17 01:34:37 +00:00
parser . add_argument ( ' prompt ' )
parser . add_argument ( ' -s ' , ' --steps ' , type = int , help = " number of steps " )
parser . add_argument ( ' -S ' , ' --seed ' , type = int , help = " image seed " )
2022-08-23 14:39:18 +00:00
parser . add_argument ( ' -n ' , ' --iterations ' , type = int , default = 1 , help = " number of samplings to perform (slower, but will provide seeds for individual images) " )
parser . add_argument ( ' -b ' , ' --batch_size ' , type = int , default = 1 , help = " number of images to produce per sampling (will not provide seeds for individual images!) " )
2022-08-17 01:34:37 +00:00
parser . add_argument ( ' -W ' , ' --width ' , type = int , help = " image width, multiple of 64 " )
parser . add_argument ( ' -H ' , ' --height ' , type = int , help = " image height, multiple of 64 " )
2022-08-18 14:47:53 +00:00
parser . add_argument ( ' -C ' , ' --cfg_scale ' , default = 7.5 , type = float , help = " prompt configuration scale " )
2022-08-17 01:34:37 +00:00
parser . add_argument ( ' -g ' , ' --grid ' , action = ' store_true ' , help = " generate a grid " )
2022-08-17 16:00:00 +00:00
parser . add_argument ( ' -i ' , ' --individual ' , action = ' store_true ' , help = " generate individual files (default) " )
2022-08-24 19:25:52 +00:00
parser . add_argument ( ' -I ' , ' --init_img ' , type = str , help = " path to input image for img2img mode (supersedes width and height) " )
2022-08-18 14:47:53 +00:00
parser . add_argument ( ' -f ' , ' --strength ' , default = 0.75 , type = float , help = " strength for noising/unnoising. 0.0 preserves image exactly, 1.0 replaces it completely " )
2022-08-26 02:57:30 +00:00
parser . add_argument ( ' -G ' , ' --gfpgan_strength ' , default = 0.5 , type = float , help = " The strength at which to apply the GFPGAN model to the result, in order to improve faces. " )
2022-08-25 21:26:48 +00:00
# variants is going to be superseded by a generalized "prompt-morph" function
# parser.add_argument('-v','--variants',type=int,help="in img2img mode, the first generated image will get passed back to img2img to generate the requested number of variants")
2022-08-22 15:03:32 +00:00
parser . add_argument ( ' -x ' , ' --skip_normalize ' , action = ' store_true ' , help = " skip subprompt weight normalization " )
2022-08-17 01:34:37 +00:00
return parser
2022-08-18 20:00:44 +00:00
2022-08-18 16:43:59 +00:00
2022-08-17 01:34:37 +00:00
if __name__ == " __main__ " :
main ( )
2022-08-24 13:22:04 +00:00