diff --git a/docs/features/CLI.md b/docs/features/CLI.md index 4a1580fc0d..530d659c64 100644 --- a/docs/features/CLI.md +++ b/docs/features/CLI.md @@ -205,6 +205,85 @@ well as the --mask (-M) argument: | --init_mask <path> | -M<path> | None |Path to an image the same size as the initial_image, with areas for inpainting made transparent.| +# Convenience commands + +In addition to the standard image generation arguments, there are a +series of convenience commands that begin with !: + +## !fix + +This command runs a post-processor on a previously-generated image. It +takes a PNG filename or path and applies your choice of the -U, -G, or +--embiggen switches in order to fix faces or upscale. If you provide a +filename, the script will look for it in the current output +directory. Otherwise you can provide a full or partial path to the +desired file. + +Some examples: + +Upscale to 4X its original size and fix faces using codeformer: +~~~ +dream> !fix 0000045.4829112.png -G1 -U4 -ft codeformer +~~~ + +Use the GFPGAN algorithm to fix faces, then upscale to 3X using --embiggen: + +~~~ +dream> !fix 0000045.4829112.png -G0.8 -ft gfpgan +>> fixing outputs/img-samples/0000045.4829112.png +>> retrieved seed 4829112 and prompt "boy enjoying a banana split" +>> GFPGAN - Restoring Faces for image seed:4829112 +Outputs: +[1] outputs/img-samples/000017.4829112.gfpgan-00.png: !fix "outputs/img-samples/0000045.4829112.png" -s 50 -S -W 512 -H 512 -C 7.5 -A k_lms -G 0.8 + +dream> !fix 000017.4829112.gfpgan-00.png --embiggen 3 +...lots of text... +Outputs: +[2] outputs/img-samples/000018.2273800735.embiggen-00.png: !fix "outputs/img-samples/000017.243781548.gfpgan-00.png" -s 50 -S 2273800735 -W 512 -H 512 -C 7.5 -A k_lms --embiggen 3.0 0.75 0.25 +~~~ + +## !fetch + +This command retrieves the generation parameters from a previously +generated image and either loads them into the command line +(Linux|Mac), or prints them out in a comment for copy-and-paste +(Windows). You may provide either the name of a file in the current +output directory, or a full file path. + +~~~ +dream> !fetch 0000015.8929913.png +# the script returns the next line, ready for editing and running: +dream> a fantastic alien landscape -W 576 -H 512 -s 60 -A plms -C 7.5 +~~~ + +Note that this command may behave unexpectedly if given a PNG file that +was not generated by InvokeAI. + +## !history + +The dream script keeps track of all the commands you issue during a +session, allowing you to re-run them. On Mac and Linux systems, it +also writes the command-line history out to disk, giving you access to +the most recent 1000 commands issued. + +The `!history` command will return a numbered list of all the commands +issued during the session (Windows), or the most recent 1000 commands +(Mac|Linux). You can then repeat a command by using the command !NNN, +where "NNN" is the history line number. For example: + +~~~ +dream> !history +... +[14] happy woman sitting under tree wearing broad hat and flowing garment +[15] beautiful woman sitting under tree wearing broad hat and flowing garment +[18] beautiful woman sitting under tree wearing broad hat and flowing garment -v0.2 -n6 +[20] watercolor of beautiful woman sitting under tree wearing broad hat and flowing garment -v0.2 -n6 -S2878767194 +[21] surrealist painting of beautiful woman sitting under tree wearing broad hat and flowing garment -v0.2 -n6 -S2878767194 +... +dream> !20 +dream> watercolor of beautiful woman sitting under tree wearing broad hat and flowing garment -v0.2 -n6 -S2878767194 +~~~ + # Command-line editing and completion If you are on a Macintosh or Linux machine, the command-line offers diff --git a/ldm/dream/args.py b/ldm/dream/args.py index 6c602de0b6..6689fbb322 100644 --- a/ldm/dream/args.py +++ b/ldm/dream/args.py @@ -464,8 +464,36 @@ class Args(object): def _create_dream_cmd_parser(self): parser = argparse.ArgumentParser( description=""" - Generate example: dream> a fantastic alien landscape -W576 -H512 -s60 -n4 - Postprocess example: dream> !pp 0000045.4829112.png -G1 -U4 -ft codeformer + *Image generation:* + To generate images, type a text prompt with optional switches. Example: + a fantastic alien landscape -W576 -H512 -s60 -n4 + + *postprocessing* + To post-process a previously-generated image, use the "!fix" command, and + provide the image filename and postprocessing options. You may provide either the filename, + in which case the script will look in the current output directory, or an arbitrary absolute or + relative path to the desired PNG file. + -G (strength) - apply face-fixing, e.g. -G0.8 + -U (scaleg) - upscale to the desired dimensions with ersgan, e.g. -U2 + --embiggen (scale) - upscale using the embiggen algorithm + -ft (algorithm) - select which face-fixing algorithm to use (gfpgan|codeformer) + + Example: !fix 0000045.4829112.png -G1 -U4 -ft codeformer + + *History manipulation* + Use !fetch to retrieve the image generation parameters used to generate a previously-generated + image. The original command will be inserted onto the command line for editing (Linux, Mac), or + printed as a comment above the dream> prompt (Windows). If a bare filename is provided, the script + will look in the current output directory + + Example: dream> !fetch 0000015.8929913.png + dream> a fantastic alien landscape -W 576 -H 512 -s 60 -A plms -C 7.5 + + Use !history to get a numbered list of the past 1000 commands (Linux, Mac) or the commands issued + during the current session (Windows). + + Use !NN to retrieve the NNth command from the history list and load it into the command line + for editing and re-issuing. """ ) render_group = parser.add_argument_group('General rendering') diff --git a/ldm/dream/log.py b/ldm/dream/log.py index 783a62d4f9..beca10fa3f 100644 --- a/ldm/dream/log.py +++ b/ldm/dream/log.py @@ -23,11 +23,14 @@ def write_log(results, log_path, file_types, output_cntr): def write_log_message(results, output_cntr): """logs to the terminal""" log_lines = [f"{path}: {prompt}\n" for path, prompt in results] - for l in log_lines: - output_cntr += 1 - print(f"[{output_cntr}] {l}", end="") - return output_cntr - + if len(log_lines)>1: + subcntr = 1 + for l in log_lines: + print(f"[{output_cntr}.{subcntr}] {l}", end="") + subcntr += 1 + else: + print(f"[{output_cntr}] {log_lines[0]}", end="") + return output_cntr+1 def write_log_files(results, log_path, file_types): for file_type in file_types: diff --git a/ldm/dream/readline.py b/ldm/dream/readline.py index 6101b1cebd..4bee51233f 100644 --- a/ldm/dream/readline.py +++ b/ldm/dream/readline.py @@ -21,6 +21,10 @@ try: except: readline_available = False +#to simulate what happens on windows systems, uncomment +# this line +#readline_available = False + IMG_EXTENSIONS = ('.png','.jpg','.jpeg') COMMANDS = ( '--steps','-s', @@ -101,12 +105,14 @@ class Completer: response = None return response - def add_to_history(self,line): + def add_history(self,line): ''' - This is a no-op; readline handles this automatically. But we provide it - for DummyReadline compatibility. + Pass thru to readline ''' - pass + readline.add_history(line) + + def remove_history_item(self,pos): + readline.remove_history_item(pos) def add_seed(self, seed): ''' @@ -226,7 +232,7 @@ class DummyCompleter(Completer): super().__init__(options) self.history = list() - def add_to_history(self,line): + def add_history(self,line): self.history.append(line) def get_current_history_length(self): @@ -235,6 +241,9 @@ class DummyCompleter(Completer): def get_history_item(self,index): return self.history[index-1] + def remove_history_item(self,index): + return self.history.pop(index-1) + def set_line(self,line): print(f'# {line}') @@ -244,6 +253,7 @@ if readline_available: readline.set_completer( completer.complete ) + readline.set_auto_history(False) readline.set_pre_input_hook(completer._pre_input_hook) readline.set_completer_delims(' ') readline.parse_and_bind('tab: complete') diff --git a/ldm/generate.py b/ldm/generate.py index 7c0066612e..0acc2f1090 100644 --- a/ldm/generate.py +++ b/ldm/generate.py @@ -490,25 +490,26 @@ class Generate: opt = None, ): # retrieve the seed from the image; - # note that we will try both the new way and the old way, since not all files have the - # metadata (yet) seed = None image_metadata = None prompt = None - try: - args = metadata_from_png(image_path) - seed = args.seed - prompt = args.prompt - print(f'>> retrieved seed {seed} and prompt "{prompt}" from {image_path}') - except: - m = re.search('(\d+)\.png$',image_path) - if m: - seed = m.group(1) + + args = metadata_from_png(image_path) + seed = args.seed + prompt = args.prompt + print(f'>> retrieved seed {seed} and prompt "{prompt}" from {image_path}') if not seed: print('* Could not recover seed for image. Replacing with 42. This will not affect image quality') seed = 42 - + + # try to reuse the same filename prefix as the original file. + # note that this is hacky + prefix = None + m = re.search('(\d+)\.',os.path.basename(image_path)) + if m: + prefix = m.groups()[0] + # face fixers and esrgan take an Image, but embiggen takes a path image = Image.open(image_path) @@ -530,6 +531,7 @@ class Generate: save_original = save_original, upscale = upscale, image_callback = callback, + prefix = prefix, ) elif tool == 'embiggen': @@ -716,7 +718,9 @@ class Generate: strength = 0.0, codeformer_fidelity = 0.75, save_original = False, - image_callback = None): + image_callback = None, + prefix = None, + ): for r in image_list: image, seed = r @@ -750,7 +754,7 @@ class Generate: ) if image_callback is not None: - image_callback(image, seed, upscaled=True) + image_callback(image, seed, upscaled=True, use_prefix=prefix) else: r[0] = image diff --git a/scripts/dream.py b/scripts/dream.py index 5544bd9361..9aa1c7751a 100755 --- a/scripts/dream.py +++ b/scripts/dream.py @@ -15,13 +15,11 @@ from ldm.dream.pngwriter import PngWriter from ldm.dream.image_util import make_grid from ldm.dream.log import write_log from omegaconf import OmegaConf - from backend.invoke_ai_web_server import InvokeAIWebServer -# Placeholder to be replaced with proper class that tracks the -# outputs and associates with the prompt that generated them. -# Just want to get the formatting look right for now. -output_cntr = 0 +# The output counter labels each output and is keyed to the +# command-line history +output_cntr = completer.get_current_history_length()+1 def main(): """Initialize command-line parsers and the diffusion model""" @@ -260,17 +258,21 @@ def main_loop(gen, opt, infile): last_results = [] try: file_writer = PngWriter(current_outdir) - prefix = file_writer.unique_prefix() results = [] # list of filename, prompt pairs grid_images = dict() # seed -> Image, only used if `opt.grid` prior_variations = opt.with_variations or [] - def image_writer(image, seed, upscaled=False, first_seed=None): + def image_writer(image, seed, upscaled=False, first_seed=None, use_prefix=None): # note the seed is the seed of the current image # the first_seed is the original seed that noise is added to # when the -v switch is used to generate variations - path = None nonlocal prior_variations + if use_prefix is not None: + prefix = use_prefix + else: + prefix = file_writer.unique_prefix() + + path = None if opt.grid: grid_images[seed] = image else: @@ -349,7 +351,10 @@ def main_loop(gen, opt, infile): global output_cntr output_cntr = write_log(results, log_path ,('txt', 'md'), output_cntr) print() - completer.add_to_history(command) + if operation == 'postprocess': + completer.add_history(f'!fix {command}') + else: + completer.add_history(command) print('goodbye!') @@ -373,7 +378,7 @@ def do_postprocess (gen, opt, callback): opt.save_original = True # do not overwrite old image! opt.last_operation = f'postprocess:{tool}' gen.apply_postprocessor( - image_path = opt.prompt, + image_path = file_path, tool = tool, gfpgan_strength = opt.gfpgan_strength, codeformer_fidelity = opt.codeformer_fidelity, @@ -424,7 +429,7 @@ def choose_postprocess_name(opt,prefix,seed) -> str: filename = None available = False while not available: - if counter > 0: + if counter == 0: filename = f'{prefix}.{seed}.{modifier}.png' else: filename = f'{prefix}.{seed}.{modifier}-{counter:02d}.png' @@ -500,16 +505,5 @@ def retrieve_dream_command(opt,file_path): cmd = dream_cmd_from_png(path) completer.set_line(cmd) -def write_log_message(results, log_path): - """logs the name of the output image, prompt, and prompt args to the terminal and log file""" - global output_cntr - log_lines = [f'{path}: {prompt}\n' for path, prompt in results] - for l in log_lines: - output_cntr += 1 - print(f'[{output_cntr}] {l}',end='') - - with open(log_path, 'a', encoding='utf-8') as file: - file.writelines(log_lines) - if __name__ == '__main__': main()