diff --git a/.github/ISSUE_TEMPLATE/bug_report.yml b/.github/ISSUE_TEMPLATE/bug_report.yml index 03b3f3ad4..146272b27 100644 --- a/.github/ISSUE_TEMPLATE/bug_report.yml +++ b/.github/ISSUE_TEMPLATE/bug_report.yml @@ -19,18 +19,23 @@ body: - type: markdown attributes: value: | - Any issues without version information will be closed - Look at console log and copy the version string from there - For example: `Version: f256fb8b Fri Apr 14 17:41:30 2023 -0400` + Any issues without version information will be closed + Look at console log and copy the version string from there + For example: `Version: f256fb8b Fri Apr 14 17:41:30 2023 -0400` - Additionally provide any relevant platorm information (OS, browser, versions) + Additionally provide any relevant platorm information (OS, browser, versions) - type: markdown attributes: value: | If issue is setup, installation or startup related, please check `setup.log` before reporting - And when posting console logs, please use code blocks (\`\`\`) to format them insead of uploading screenshots + And when posting console logs, please use code blocks ( \`\`\` ) to format them insead of uploading screenshots - type: markdown attributes: value: | - If possible update to latest version before reporting the issue as older versions cannot be properly supported - And search existing **issues** and **discussions** before creating a new one + If you have additional extensions installed, try to reproduce the issue with user extensions disabled + And if the issue is with compatibility with specific extension, mark it as such when creating the issue + - type: markdown + attributes: + value: | + If possible update to latest version before reporting the issue as older versions cannot be properly supported + And search existing **issues** and **discussions** before creating a new one diff --git a/TODO.md b/TODO.md index 55fe98b2c..d1181ab65 100644 --- a/TODO.md +++ b/TODO.md @@ -1,24 +1,29 @@ # TODO +## Always-on + +- Pick & merge PRs from main repo + ## Fixes Stuff to be fixed... - Reconnect UI to ops in progress on browser restart -- Redo Extensions tab: see -- Cleanup & integrate CSS into single file -- Replace PngInfo/EXIF metadata handler -- Pick & merge PRs from main repo - Create new GitHub hooks/actions for CI/CD -- Investigate integration with `Torch-DirectML` -- Set defaults for Apple M1 -- Revisit `torch.compile` -- Ask to download default model -- Stream-load as option -- Dont spawn extensions installer - Remove `models` from git repo -- Merge `Image Info` into `Process` -- Refresh theme list + +## Features + +- Redo Extensions tab: see +- Stream-load models as option for slow storage +- Replace **PngInfo** / **EXIF** metadata handler +- Investigate integration with `Torch-DirectML` +- Investigate best practices for **Apple M1** +- Investigate best practices for **AMD GPUs** + +## Investigate + +- Revisit `torch.compile` ## Integration diff --git a/extensions-builtin/sd-webui-controlnet b/extensions-builtin/sd-webui-controlnet index 1dc09b161..80c7a6cc9 160000 --- a/extensions-builtin/sd-webui-controlnet +++ b/extensions-builtin/sd-webui-controlnet @@ -1 +1 @@ -Subproject commit 1dc09b161abc15aeacc7b7cd0933d073aa7476b5 +Subproject commit 80c7a6cc9547bce3a0b5833c367b3b23a422abf3 diff --git a/modules/modelloader.py b/modules/modelloader.py index 597afbdf1..f1c79ff4d 100644 --- a/modules/modelloader.py +++ b/modules/modelloader.py @@ -50,7 +50,7 @@ def load_models(model_path: str, model_url: str = None, command_path: str = None if ext_blacklist is not None and any([full_path.endswith(x) for x in ext_blacklist]): continue if len(ext_filter) != 0: - model_name, extension = os.path.splitext(file) + _model_name, extension = os.path.splitext(file) if extension not in ext_filter: continue if file not in output: @@ -75,7 +75,7 @@ def friendly_name(file: str): file = urlparse(file).path file = os.path.basename(file) - model_name, extension = os.path.splitext(file) + model_name, _extension = os.path.splitext(file) return model_name diff --git a/modules/sd_models.py b/modules/sd_models.py index 249157e57..7b4189eba 100644 --- a/modules/sd_models.py +++ b/modules/sd_models.py @@ -22,7 +22,7 @@ model_dir = "Stable-diffusion" model_path = os.path.abspath(os.path.join(paths.models_path, model_dir)) checkpoints_list = {} -checkpoint_alisases = {} +checkpoint_aliases = {} checkpoints_loaded = collections.OrderedDict() @@ -56,7 +56,7 @@ class CheckpointInfo: def register(self): checkpoints_list[self.title] = self for i in self.ids: - checkpoint_alisases[i] = self + checkpoint_aliases[i] = self def calculate_shorthash(self): self.sha256 = hashes.sha256(self.filename, "checkpoint/" + self.name) @@ -103,32 +103,31 @@ def checkpoint_tiles(): def list_models(): checkpoints_list.clear() - checkpoint_alisases.clear() - - cmd_ckpt = shared.cmd_opts.ckpt - if shared.cmd_opts.no_download_sd_model or cmd_ckpt != shared.sd_model_file or os.path.exists(cmd_ckpt): - model_url = None - else: - model_url = "https://huggingface.co/runwayml/stable-diffusion-v1-5/resolve/main/v1-5-pruned-emaonly.safetensors" - - model_list = modelloader.load_models(model_path=model_path, model_url=model_url, command_path=shared.opts.ckpt_dir, ext_filter=[".ckpt", ".safetensors"], download_name="v1-5-pruned-emaonly.safetensors", ext_blacklist=[".vae.ckpt", ".vae.safetensors"]) - - if os.path.exists(cmd_ckpt): - checkpoint_info = CheckpointInfo(cmd_ckpt) + checkpoint_aliases.clear() + model_list = modelloader.load_models(model_path=model_path, model_url=None, command_path=shared.opts.ckpt_dir, ext_filter=[".ckpt", ".safetensors"], download_name=None, ext_blacklist=[".vae.ckpt", ".vae.safetensors"]) + if shared.cmd_opts.ckpt is not None and os.path.exists(shared.cmd_opts.ckpt): + checkpoint_info = CheckpointInfo(shared.cmd_opts.ckpt) checkpoint_info.register() shared.opts.data['sd_model_checkpoint'] = checkpoint_info.title - elif cmd_ckpt is not None and cmd_ckpt != shared.default_sd_model_file: - print("Checkpoint in --ckpt argument not found", file=sys.stderr) - + elif shared.cmd_opts.ckpt != shared.default_sd_model_file: + print(f"Checkpoint not found: {shared.cmd_opts.ckpt}", file=sys.stderr) for filename in sorted(model_list, key=str.lower): checkpoint_info = CheckpointInfo(filename) checkpoint_info.register() - - shared.log.info(f'Available models: {shared.opts.ckpt_dir} {len(checkpoints_list)}') + print(f'Available models: {shared.opts.ckpt_dir} {len(checkpoints_list)}') + if len(checkpoints_list) == 0: + if not shared.cmd_opts.no_download_sd_model: + key = input('Download the default model? (y/N) ') + if key.lower().startswith == 'y': + model_url = "https://huggingface.co/runwayml/stable-diffusion-v1-5/resolve/main/v1-5-pruned-emaonly.safetensors" + model_list = modelloader.load_models(model_path=model_path, model_url=model_url, command_path=shared.opts.ckpt_dir, ext_filter=[".ckpt", ".safetensors"], download_name="v1-5-pruned-emaonly.safetensors", ext_blacklist=[".vae.ckpt", ".vae.safetensors"]) + for filename in sorted(model_list, key=str.lower): + checkpoint_info = CheckpointInfo(filename) + checkpoint_info.register() def get_closet_checkpoint_match(search_string): - checkpoint_info = checkpoint_alisases.get(search_string, None) + checkpoint_info = checkpoint_aliases.get(search_string, None) if checkpoint_info is not None: return checkpoint_info @@ -157,18 +156,12 @@ def model_hash(filename): def select_checkpoint(): model_checkpoint = shared.opts.sd_model_checkpoint - checkpoint_info = checkpoint_alisases.get(model_checkpoint, None) + checkpoint_info = checkpoint_aliases.get(model_checkpoint, None) if checkpoint_info is not None: return checkpoint_info if len(checkpoints_list) == 0: - print("No checkpoints found. When searching for checkpoints, looked at:", file=sys.stderr) - if shared.cmd_opts.ckpt is not None: - print(f" - file {os.path.abspath(shared.cmd_opts.ckpt)}", file=sys.stderr) - print(f" - directory {model_path}", file=sys.stderr) - if shared.opts.ckpt_dir is not None: - print(f" - directory {os.path.abspath(shared.opts.ckpt_dir)}", file=sys.stderr) - print("Can't run without a checkpoint. Find and place a .ckpt or .safetensors file into any of those locations. The program will exit.", file=sys.stderr) + print("Cannot run without a checkpoint", file=sys.stderr) exit(1) checkpoint_info = next(iter(checkpoints_list.values())) diff --git a/modules/ui.py b/modules/ui.py index f01627806..6d5b6a5f3 100644 --- a/modules/ui.py +++ b/modules/ui.py @@ -925,6 +925,7 @@ def create_ui(): with gr.Blocks(analytics_enabled=False) as extras_interface: ui_postprocessing.create_ui() + """ with gr.Blocks(analytics_enabled=False) as pnginfo_interface: with gr.Row().style(equal_height=False): with gr.Column(variant='panel'): @@ -947,6 +948,7 @@ def create_ui(): inputs=[image], outputs=[html, generation_info, html2], ) + """ def update_interp_description(value): interp_description_css = "

{}

" @@ -1480,8 +1482,8 @@ def create_ui(): interfaces = [ (txt2img_interface, "From Text", "txt2img"), (img2img_interface, "From Image", "img2img"), - (extras_interface, "Process", "extras"), - (pnginfo_interface, "Image Info", "pnginfo"), + (extras_interface, "Process Image", "extras"), + # (pnginfo_interface, "Image Info", "pnginfo"), # (modelmerger_interface, "Checkpoint Merger", "modelmerger"), (train_interface, "Train", "ti"), ] diff --git a/modules/ui_common.py b/modules/ui_common.py index 312b07094..12908005e 100644 --- a/modules/ui_common.py +++ b/modules/ui_common.py @@ -119,7 +119,7 @@ Requested path was: {f} if not shared.cmd_opts.hide_ui_dir_config: path = os.path.normpath(f) if platform.system() == "Windows": - os.startfile(path) + os.startfile(path) # pylint: disable=no-member elif platform.system() == "Darwin": sp.Popen(["open", path]) elif "microsoft-standard-WSL2" in platform.uname().release: @@ -134,7 +134,7 @@ Requested path was: {f} generation_info = None with gr.Column(): with gr.Row(elem_id=f"image_buttons_{tabname}", elem_classes="image-buttons"): - open_folder_button = gr.Button('Load', visible=not shared.cmd_opts.hide_ui_dir_config) + open_folder_button = gr.Button('show', visible=not shared.cmd_opts.hide_ui_dir_config) if tabname != "extras": save = gr.Button('Save', elem_id=f'save_{tabname}') diff --git a/modules/ui_postprocessing.py b/modules/ui_postprocessing.py index b418d9553..ccd419d28 100644 --- a/modules/ui_postprocessing.py +++ b/modules/ui_postprocessing.py @@ -1,7 +1,8 @@ import gradio as gr from modules import scripts_postprocessing, scripts, shared, gfpgan_model, codeformer_model, ui_common, postprocessing, call_queue import modules.generation_parameters_copypaste as parameters_copypaste - +from modules.call_queue import wrap_gradio_gpu_call, wrap_queued_call, wrap_gradio_call +from modules.extras import run_pnginfo def create_ui(): tab_index = gr.State(value=0) @@ -9,28 +10,44 @@ def create_ui(): with gr.Row().style(equal_height=False, variant='compact'): with gr.Column(variant='compact'): with gr.Tabs(elem_id="mode_extras"): - with gr.TabItem('Single Image', elem_id="extras_single_tab") as tab_single: + + with gr.TabItem('Process Image', elem_id="extras_single_tab") as tab_single: extras_image = gr.Image(label="Source", source="upload", interactive=True, type="pil", elem_id="extras_image") - with gr.TabItem('Batch Process', elem_id="extras_batch_process_tab") as tab_batch: + with gr.TabItem('Process Batch', elem_id="extras_batch_process_tab") as tab_batch: image_batch = gr.File(label="Batch Process", file_count="multiple", interactive=True, type="file", elem_id="extras_image_batch") - with gr.TabItem('Batch from Directory', elem_id="extras_batch_directory_tab") as tab_batch_dir: + with gr.TabItem('Process Folder', elem_id="extras_batch_directory_tab") as tab_batch_dir: extras_batch_input_dir = gr.Textbox(label="Input directory", **shared.hide_dirs, placeholder="A directory on the same machine where the server is running.", elem_id="extras_batch_input_dir") extras_batch_output_dir = gr.Textbox(label="Output directory", **shared.hide_dirs, placeholder="Leave blank to save images to the default path.", elem_id="extras_batch_output_dir") show_extras_results = gr.Checkbox(label='Show result images', value=True, elem_id="extras_show_extras_results") + with gr.Row(): + buttons = parameters_copypaste.create_buttons(["txt2img", "img2img", "inpaint"]) + submit = gr.Button('Generate', elem_id="extras_generate", variant='primary') script_inputs = scripts.scripts_postproc.setup_ui() with gr.Column(): result_images, html_info_x, html_info, html_log = ui_common.create_output_panel("extras", shared.opts.outdir_extras_samples) + html_info = gr.HTML() + generation_info = gr.Textbox(visible=False, elem_id="pnginfo_generation_info") + html2_info = gr.HTML() + + for tabname, button in buttons.items(): + parameters_copypaste.register_paste_params_button(parameters_copypaste.ParamBinding(paste_button=button, tabname=tabname, source_text_component=generation_info, source_image_component=extras_image)) tab_single.select(fn=lambda: 0, inputs=[], outputs=[tab_index]) tab_batch.select(fn=lambda: 1, inputs=[], outputs=[tab_index]) tab_batch_dir.select(fn=lambda: 2, inputs=[], outputs=[tab_index]) + extras_image.change( + fn=wrap_gradio_call(run_pnginfo), + inputs=[extras_image], + outputs=[html_info, generation_info, html2_info], + ) + submit.click( fn=call_queue.wrap_gradio_gpu_call(postprocessing.run_postprocessing, extra_outputs=[None, '']), inputs=[ diff --git a/setup.py b/setup.py index d039ce28e..591912113 100644 --- a/setup.py +++ b/setup.py @@ -349,7 +349,7 @@ def check_extensions(): newest = max(newest, ts) newest_all = max(newest_all, newest) log.debug(f'Extension version: {time.ctime(newest)} {folder}{os.pathsep}{ext}') - return newest_all + return round(newest_all) # check version of the main repo and optionally upgrade it @@ -393,10 +393,9 @@ def check_version(): # check if we can run setup in quick mode def check_timestamp(): - if not quick_allowed: - return False - if not os.path.isfile('setup.log'): + if not quick_allowed or not os.path.isfile('setup.log'): return False + ok = True setup_time = -1 with open('setup.log', 'r', encoding='utf8') as f: lines = f.readlines() @@ -410,15 +409,16 @@ def check_timestamp(): exit(1) log.debug(f'Repository update time: {time.ctime(int(version_time))}') if setup_time == -1: - return False + ok = False log.debug(f'Previous setup time: {time.ctime(setup_time)}') if setup_time < version_time: - return False + ok = False extension_time = check_extensions() log.debug(f'Latest extensions time: {time.ctime(extension_time)}') if setup_time < extension_time: - return False - return True + ok = False + log.debug(f'Timestamps: version:{version_time} setup:{setup_time} extension:{extension_time}') + return ok def parse_args():