From e1c46427e05a70d644fadb7f4a2b072778168243 Mon Sep 17 00:00:00 2001 From: Vladimir Mandic Date: Fri, 20 Oct 2023 19:03:45 -0400 Subject: [PATCH 1/9] minor fixes --- modules/images.py | 35 ++++++++++++++++++++--------------- modules/processing.py | 5 ++++- modules/sd_vae_approx.py | 2 +- modules/ui_extensions.py | 4 +++- modules/ui_tempdir.py | 6 +++--- wiki | 2 +- 6 files changed, 32 insertions(+), 22 deletions(-) diff --git a/modules/images.py b/modules/images.py index 4b0d198a2..23a6c4d13 100644 --- a/modules/images.py +++ b/modules/images.py @@ -298,7 +298,7 @@ class FilenameGenerator: 'model_name': lambda self: shared.sd_model.sd_checkpoint_info.model_name, 'model_hash': lambda self: shared.sd_model.sd_checkpoint_info.shorthash, - 'prompt': lambda self: self.prompt, + 'prompt': lambda self: self.prompt_full(), 'prompt_no_styles': lambda self: self.prompt_no_style(), 'prompt_words': lambda self: self.prompt_words(), 'prompt_hash': lambda self: hashlib.sha256(self.prompt.encode()).hexdigest()[0:8], @@ -350,6 +350,9 @@ class FilenameGenerator: shorthash = hashlib.sha256(img_str).hexdigest()[0:8] return shorthash + def prompt_full(self): + return self.sanitize(self.prompt) + def prompt_words(self): if self.prompt is None: return '' @@ -358,7 +361,7 @@ class FilenameGenerator: no_brackets = re_brackets.sub('', no_network) words = [x for x in re_nonletters.split(no_brackets or "") if len(x) > 0] prompt = " ".join(words[0:shared.opts.directories_max_prompt_words]) - return prompt + return self.sanitize(prompt) def prompt_no_style(self): if self.p is None or self.prompt is None: @@ -369,7 +372,7 @@ class FilenameGenerator: for part in style.split("{prompt}"): prompt_no_style = prompt_no_style.replace(part, "").replace(", ,", ",") prompt_no_style = prompt_no_style.replace(style, "") - return prompt_no_style + return self.sanitize(prompt_no_style) def datetime(self, *args): time_datetime = datetime.datetime.now() @@ -386,19 +389,21 @@ class FilenameGenerator: return formatted_time def sanitize(self, filename): - invalid_chars = '#<>:;"/\\|?*\n\t\r' + invalid_chars = '#<>.:;"/\\|?*\n\t\r' invalid_prefix = '' invalid_suffix = '.' - parts = Path(filename).parts + fn, ext = os.path.splitext(filename) + parts = Path(fn).parts + newparts = [] for part in parts: - part = part.translate({ord(x): '_' for x in invalid_chars}) + part = part.translate({ ord(x): '_' for x in invalid_chars }) part = part.lstrip(invalid_prefix) part = part.rstrip(invalid_suffix) - fn = Path(*parts) - max_length = os.statvfs(__file__).f_namemax if hasattr(os, 'statvfs') else 128 - fn, ext = os.path.splitext(fn) - fn = fn[:max_length-max(4, len(ext))] + ext - # shared.log.debug(f'Filename sanitize: input={filename} parts={parts} output={fn}') + newparts.append(part) + fn = Path(*newparts) + max_length = max(os.statvfs(__file__).f_namemax if hasattr(os, 'statvfs') else 128, 250) + fn = str(fn)[:max_length-max(4, len(ext))] + ext + shared.log.debug(f'Filename sanitize: input="{filename}" parts={parts} output={fn}') return fn def apply(self, x): @@ -563,20 +568,20 @@ def save_image(image, path, basename = '', seed=None, prompt=None, extension=sha if info is not None: pnginfo[pnginfo_section_name] = info params = script_callbacks.ImageSaveParams(image, p, filename, pnginfo) + params.filename = namegen.sanitize(filename) script_callbacks.before_image_saved_callback(params) exifinfo = params.pnginfo.get('UserComment', '') exifinfo = (exifinfo + ', ' if len(exifinfo) > 0 else '') + params.pnginfo.get(pnginfo_section_name, '') - filename = namegen.sanitize(params.filename) - dirname = os.path.dirname(filename) + dirname = os.path.dirname(params.filename) os.makedirs(dirname, exist_ok=True) - filename, extension = os.path.splitext(filename) + filename, extension = os.path.splitext(params.filename) filename_txt = f"{filename}.txt" if shared.opts.save_txt and len(exifinfo) > 0 else None save_queue.put((params.image, filename, extension, params, exifinfo, filename_txt)) # actual save is executed in a thread that polls data from queue save_queue.join() params.image.already_saved_as = params.filename script_callbacks.image_saved_callback(params) - return params.filename, filename_txt + return filename, filename_txt def safe_decode_string(s: bytes): diff --git a/modules/processing.py b/modules/processing.py index fffb93455..db1eeda12 100644 --- a/modules/processing.py +++ b/modules/processing.py @@ -845,6 +845,9 @@ def process_images_inner(p: StableDiffusionProcessing) -> Processed: else: raise ValueError(f"Unknown backend {shared.backend}") + if not shared.opts.keep_incomplete and shared.state.interrupted: + x_samples_ddim = [] + if shared.cmd_opts.lowvram or shared.cmd_opts.medvram and shared.backend == shared.Backend.ORIGINAL: modules.lowvram.send_everything_to_cpu() devices.torch_gc() @@ -945,7 +948,7 @@ def process_images_inner(p: StableDiffusionProcessing) -> Processed: index_of_first_image=index_of_first_image, infotexts=infotexts, ) - if p.scripts is not None and not shared.state.interrupted: + if p.scripts is not None and not (shared.state.interrupted or shared.state.skipped): p.scripts.postprocess(p, res) return res diff --git a/modules/sd_vae_approx.py b/modules/sd_vae_approx.py index 3e4b93ddd..bd09529e4 100644 --- a/modules/sd_vae_approx.py +++ b/modules/sd_vae_approx.py @@ -71,7 +71,7 @@ def cheap_approximation(sample): # Approximate simple ]).reshape(3, 4, 1, 1) simple_bias = None try: - x_sample = nn.functional.conv2d(sample, simple_weights.to(sample.device, sample.dtype), simple_bias.to(sample.device, sample.dtype)) # pylint: disable=not-callable + x_sample = nn.functional.conv2d(sample, simple_weights.to(sample.device, sample.dtype), simple_bias.to(sample.device, sample.dtype) if simple_bias is not None else None) # pylint: disable=not-callable return x_sample except Exception as e: shared.log.error(f'Decode simple: {e}') diff --git a/modules/ui_extensions.py b/modules/ui_extensions.py index a8e93e361..3f5e69b85 100644 --- a/modules/ui_extensions.py +++ b/modules/ui_extensions.py @@ -421,7 +421,6 @@ def create_ui(): update_extension_button = gr.Button(elem_id="update_extension_button", visible=False) with gr.Column(scale=4): search_text = gr.Text(label="Search") - info = gr.HTML('Note: After any operation such as install/uninstall or enable/disable, please restart the server') with gr.Column(scale=1): sort_column = gr.Dropdown(value="default", label="Sort by", choices=list(sort_ordering.keys()), multiselect=False) with gr.Column(scale=1): @@ -429,6 +428,9 @@ def create_ui(): check = gr.Button(value="Update all installed", variant="primary") apply = gr.Button(value="Apply changes", variant="primary") list_extensions() + gr.HTML('

Extension list

⯀ Refesh extension list to download latest list with status
⯀ Check status of an extension by looking at status icon before installing it
⯀ After any operation such as install/uninstall or enable/disable, please restart the server
') + gr.HTML('') + info = gr.HTML('') extensions_table = gr.HTML(create_html(search_text.value, sort_column.value)) check.click( fn=wrap_gradio_gpu_call(check_updates, extra_outputs=[gr.update()]), diff --git a/modules/ui_tempdir.py b/modules/ui_tempdir.py index 5b027fa98..1293ad6b6 100644 --- a/modules/ui_tempdir.py +++ b/modules/ui_tempdir.py @@ -58,9 +58,9 @@ def pil_to_temp_file(self, img, dir: str, format="png") -> str: # pylint: disabl if isinstance(key, str) and isinstance(value, str): metadata.add_text(key, value) use_metadata = True - with tempfile.NamedTemporaryFile(delete=False, suffix=".png", dir=dir) as file_obj: - img.save(file_obj, pnginfo=(metadata if use_metadata else None)) - name = file_obj.name + with tempfile.NamedTemporaryFile(delete=False, suffix=".png", dir=dir) as tmp: + img.save(tmp, pnginfo=(metadata if use_metadata else None)) + name = tmp.name shared.log.debug(f'Saving temp: image="{name}"') return name diff --git a/wiki b/wiki index 58e8d11f9..b9712d07c 160000 --- a/wiki +++ b/wiki @@ -1 +1 @@ -Subproject commit 58e8d11f94503d86f0b9b3f73046c5996a2fc000 +Subproject commit b9712d07cf931b8bca512ae4f083b88877062c77 From c22dd2586834dd2432f38936bea016612cf76e0f Mon Sep 17 00:00:00 2001 From: Vladimir Mandic Date: Fri, 20 Oct 2023 20:11:07 -0400 Subject: [PATCH 2/9] quick fix --- modules/images.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/modules/images.py b/modules/images.py index 23a6c4d13..effac2ac9 100644 --- a/modules/images.py +++ b/modules/images.py @@ -389,7 +389,7 @@ class FilenameGenerator: return formatted_time def sanitize(self, filename): - invalid_chars = '#<>.:;"/\\|?*\n\t\r' + invalid_chars = '#<>.;"/\\|?*\n\t\r' invalid_prefix = '' invalid_suffix = '.' fn, ext = os.path.splitext(filename) From b00abc4c1d16e275422555bf1545dba5d1b2277c Mon Sep 17 00:00:00 2001 From: vladmandic Date: Sat, 21 Oct 2023 03:13:26 +0000 Subject: [PATCH 3/9] =?UTF-8?q?Deploying=20to=20master=20from=20@=20vladma?= =?UTF-8?q?ndic/automatic@c22dd2586834dd2432f38936bea016612cf76e0f=20?= =?UTF-8?q?=F0=9F=9A=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 13e53421a..98f7cc56c 100644 --- a/README.md +++ b/README.md @@ -175,7 +175,7 @@ General goals: ### **Sponsors**
-Allan GrantMichael HarrisBrent OzarToniXMatthew RunoHELLO WORLD SASSalad TechnologiesGym Dreams • GymDreams8 +Allan GrantMichael HarrisBrent OzarToniXMatthew RunoHELLO WORLD SASSalad TechnologiesGym Dreams • GymDreams8a.v.mantzaris

From 02fe33cb16bbb9f54b4301f3e3c385561ba4c0f1 Mon Sep 17 00:00:00 2001 From: Vladimir Mandic Date: Sat, 21 Oct 2023 11:13:47 -0400 Subject: [PATCH 4/9] fix image filename handling --- html/locale_en.json | 2 +- javascript/amethyst-nightfall.css | 1 - javascript/black-orange.css | 1 - javascript/black-teal.css | 1 - javascript/invoked.css | 1 - javascript/light-teal.css | 1 - javascript/midnight-barbie.css | 1 - javascript/sdnext.css | 6 ++-- modules/images.py | 53 +++++++++++++++++++++---------- modules/ui.py | 2 +- modules/ui_tempdir.py | 9 ++++-- wiki | 2 +- 12 files changed, 48 insertions(+), 32 deletions(-) diff --git a/html/locale_en.json b/html/locale_en.json index de35d33ec..5a046245f 100644 --- a/html/locale_en.json +++ b/html/locale_en.json @@ -237,7 +237,7 @@ {"id":"","label":"latent nothing","localized":"","hint":"fill it with latent space zeroes"}, {"id":"","label":"Whole picture","localized":"","hint":""}, {"id":"","label":"Only masked","localized":"","hint":""}, - {"id":"","label":"Only masked padding, pixels","localized":"","hint":""}, + {"id":"","label":"Masked padding","localized":"","hint":""}, {"id":"","label":"Scale","localized":"","hint":""}, {"id":"","label":"Unused","localized":"","hint":""}, {"id":"","label":"Image CFG scale","localized":"","hint":""} diff --git a/javascript/amethyst-nightfall.css b/javascript/amethyst-nightfall.css index a8119ed02..6cf91ab63 100644 --- a/javascript/amethyst-nightfall.css +++ b/javascript/amethyst-nightfall.css @@ -73,7 +73,6 @@ svg.feather.feather-image, .feather .feather-image { display: none } .block.token-counter span { background-color: #222 !important; box-shadow: 2px 2px 2px #111; border: none !important; font-size: 0.8rem; } .tab-nav { zoom: 120%; margin-bottom: 10px; border-bottom: 2px solid var(--highlight-color) !important; padding-bottom: 2px; } .label-wrap { margin: 16px 0px 8px 0px; } -.gradio-slider input[type="number"] { width: 4.5em; font-size: 0.8rem; height: 20px; } .gradio-button.tool { border: none; background: none; box-shadow: none; } #tab_extensions table td, #tab_extensions table th { border: none; padding: 0.5em; } #tab_extensions table { width: 96vw } diff --git a/javascript/black-orange.css b/javascript/black-orange.css index 91eda0540..30e069777 100644 --- a/javascript/black-orange.css +++ b/javascript/black-orange.css @@ -78,7 +78,6 @@ svg.feather.feather-image, .feather .feather-image { display: none } .block.token-counter span { background-color: #222 !important; box-shadow: 2px 2px 2px #111; border: none !important; font-size: 0.8rem; } .tab-nav { zoom: 120%; margin-bottom: 10px; border-bottom: 2px solid var(--highlight-color) !important; padding-bottom: 2px; } .label-wrap { margin: 16px 0px 8px 0px; } -.gradio-slider input[type="number"] { width: 4.5em; font-size: 0.8rem; height: 20px; } .gradio-button.tool { border: none; background: none; box-shadow: none; } #tab_extensions table td, #tab_extensions table th { border: none; padding: 0.5em; } #tab_extensions table { width: 96vw } diff --git a/javascript/black-teal.css b/javascript/black-teal.css index 87b9d6e4e..bef9280ac 100644 --- a/javascript/black-teal.css +++ b/javascript/black-teal.css @@ -83,7 +83,6 @@ svg.feather.feather-image, .feather .feather-image { display: none } .block.token-counter span { background-color: var(--input-background-fill) !important; box-shadow: 2px 2px 2px #111; border: none !important; font-size: 0.8rem; } .tab-nav { zoom: 120%; margin-top: 10px; margin-bottom: 10px; border-bottom: 2px solid var(--highlight-color) !important; padding-bottom: 2px; } .label-wrap { margin: 8px 0px 4px 0px; } -.gradio-slider input[type="number"] { width: 4.5em; font-size: 0.8rem; height: 20px; } .gradio-button.tool { border: none; background: none; box-shadow: none; filter: hue-rotate(340deg) saturate(0.5); } #tab_extensions table td, #tab_extensions table th, #tab_config table td, #tab_config table th { border: none; padding: 0.5em; } #tab_extensions table, #tab_config table { width: 96vw } diff --git a/javascript/invoked.css b/javascript/invoked.css index eb8960264..6d4150d02 100644 --- a/javascript/invoked.css +++ b/javascript/invoked.css @@ -77,7 +77,6 @@ div.tab-nav button.selected {background-color: var(--button-primary-background-f .label-wrap { background-color: #363c4a; padding: 16px 8px 8px 8px; border-radius: var(--radius-lg); padding-left: 8px !important; } .small-accordion .label-wrap { padding: 8px 0px 8px 0px; } .small-accordion .label-wrap .icon { margin-right: 1em; } -.gradio-slider input[type="number"] { width: 4.5em; font-size: 0.8rem; height: 20px; } .gradio-button.tool { border: none; box-shadow: none; border-radius: var(--radius-lg);} button.selected {background: var(--button-primary-background-fill);} .center.boundedheight.flex {background-color: var(--input-background-fill);} diff --git a/javascript/light-teal.css b/javascript/light-teal.css index 0b908c059..7778caded 100644 --- a/javascript/light-teal.css +++ b/javascript/light-teal.css @@ -84,7 +84,6 @@ svg.feather.feather-image, .feather .feather-image { display: none } .block.token-counter span { background-color: var(--input-background-fill) !important; box-shadow: 2px 2px 2px #111; border: none !important; font-size: 0.8rem; } .tab-nav { zoom: 120%; margin-top: 10px; margin-bottom: 10px; border-bottom: 2px solid var(--highlight-color) !important; padding-bottom: 2px; } .label-wrap { margin: 16px 0px 8px 0px; } -.gradio-slider input[type="number"] { width: 4.5em; font-size: 0.8rem; height: 20px; } .gradio-button.tool { border: none; background: none; box-shadow: none; filter: hue-rotate(340deg) saturate(0.5); } #tab_extensions table td, #tab_extensions table th { border: none; padding: 0.5em; } #tab_extensions table { width: 96vw } diff --git a/javascript/midnight-barbie.css b/javascript/midnight-barbie.css index 67f616f1e..238dac9ff 100644 --- a/javascript/midnight-barbie.css +++ b/javascript/midnight-barbie.css @@ -78,7 +78,6 @@ svg.feather.feather-image, .feather .feather-image { display: none } .block.token-counter span { background-color: #222 !important; box-shadow: 2px 2px 2px #111; border: none !important; font-size: 0.8rem; } .tab-nav { zoom: 120%; margin-bottom: 10px; border-bottom: 2px solid var(--highlight-color) !important; padding-bottom: 2px; } .label-wrap { margin: 16px 0px 8px 0px; } -.gradio-slider input[type="number"] { width: 4.5em; font-size: 0.8rem; height: 20px; } .gradio-button.tool { border: none; background: none; box-shadow: none; } #tab_extensions table td, #tab_extensions table th { border: none; padding: 0.5em; } #tab_extensions table { width: 96vw } diff --git a/javascript/sdnext.css b/javascript/sdnext.css index fb6a198f3..4b0bdbeb1 100644 --- a/javascript/sdnext.css +++ b/javascript/sdnext.css @@ -38,10 +38,10 @@ tr { border-bottom: none !important; padding: 0.1em 0.5em !important; } .gradio-html .min { min-height: 0; } .gradio-html div.wrap { height: 100%; } .gradio-number { min-width: unset !important; max-width: 5em !important; } -.gradio-slider { max-width: 50%; margin-right: var(--spacing-sm) !important; } -.gradio-slider input[type="number"] { width: 6em; margin-left: 0.5em; } .gradio-textbox { overflow: visible !important; } -.gradio-radio { padding: 0 !important; } +.gradio-radio { padding: 0 !important; width: max-content !important; } +.gradio-slider { margin-right: var(--spacing-sm) !important; width: max-content !important } +.gradio-slider input[type="number"] { width: 6em; font-size: 0.8rem; height: 20px; margin-left: 0.5em; } /* custom gradio elements */ .accordion-compact { padding: 8px 0px 4px 0px !important; } diff --git a/modules/images.py b/modules/images.py index effac2ac9..ba653f36f 100644 --- a/modules/images.py +++ b/modules/images.py @@ -19,6 +19,7 @@ from PIL import Image, ImageFont, ImageDraw, PngImagePlugin, ExifTags from modules import sd_samplers, shared, script_callbacks, errors, paths LANCZOS = (Image.Resampling.LANCZOS if hasattr(Image, 'Resampling') else Image.LANCZOS) +debug = errors.log.info if os.environ.get('SD_PATH_DEBUG', None) is not None else lambda *args, **kwargs: None try: @@ -351,7 +352,7 @@ class FilenameGenerator: return shorthash def prompt_full(self): - return self.sanitize(self.prompt) + return self.prompt_sanitize(self.prompt) def prompt_words(self): if self.prompt is None: @@ -361,7 +362,7 @@ class FilenameGenerator: no_brackets = re_brackets.sub('', no_network) words = [x for x in re_nonletters.split(no_brackets or "") if len(x) > 0] prompt = " ".join(words[0:shared.opts.directories_max_prompt_words]) - return self.sanitize(prompt) + return self.prompt_sanitize(prompt) def prompt_no_style(self): if self.p is None or self.prompt is None: @@ -372,7 +373,7 @@ class FilenameGenerator: for part in style.split("{prompt}"): prompt_no_style = prompt_no_style.replace(part, "").replace(", ,", ",") prompt_no_style = prompt_no_style.replace(style, "") - return self.sanitize(prompt_no_style) + return self.prompt_sanitize(prompt_no_style) def datetime(self, *args): time_datetime = datetime.datetime.now() @@ -388,22 +389,33 @@ class FilenameGenerator: formatted_time = time_zone_time.strftime(self.default_time_format) return formatted_time + def prompt_sanitize(self, prompt): + invalid_chars = '#<>:\'"\\|?*\n\t\r' + sanitized = prompt.translate({ ord(x): '_' for x in invalid_chars }).strip() + debug(f'Prompt sanitize: input="{prompt}" output={sanitized}') + return sanitized + def sanitize(self, filename): - invalid_chars = '#<>.;"/\\|?*\n\t\r' - invalid_prefix = '' - invalid_suffix = '.' + invalid_chars = '\'"\\|?*\n\t\r' # + invalid_folder = ':' + invalid_files = ['CON', 'PRN', 'AUX', 'NUL', 'NULL', 'COM0', 'COM1', 'LPT0', 'LPT1'] + invalid_prefix = ', ' + invalid_suffix = '.,_ ' fn, ext = os.path.splitext(filename) parts = Path(fn).parts newparts = [] - for part in parts: + for i, part in enumerate(parts): part = part.translate({ ord(x): '_' for x in invalid_chars }) - part = part.lstrip(invalid_prefix) - part = part.rstrip(invalid_suffix) + if i > 0 or (len(part) >= 2 and part[1] != invalid_folder): # skip drive, otherwise remove + part = part.translate({ ord(x): '_' for x in invalid_folder }) + part = part.lstrip(invalid_prefix).rstrip(invalid_suffix) + if part in invalid_files: # reserved names + [part := part.replace(word, '_') for word in invalid_files] # pylint: disable=expression-not-assigned newparts.append(part) fn = Path(*newparts) - max_length = max(os.statvfs(__file__).f_namemax if hasattr(os, 'statvfs') else 128, 250) - fn = str(fn)[:max_length-max(4, len(ext))] + ext - shared.log.debug(f'Filename sanitize: input="{filename}" parts={parts} output={fn}') + max_length = os.statvfs(__file__).f_namemax - 32 if hasattr(os, 'statvfs') else 230 + fn = str(fn)[:max_length-max(4, len(ext))].rstrip(invalid_suffix) + ext + debug(f'Filename sanitize: input="{filename}" parts={parts} output="{fn}" ext={ext} max={max_length} len={len(fn)}') return fn def apply(self, x): @@ -479,7 +491,10 @@ def atomically_save_image(): pnginfo_data = PngImagePlugin.PngInfo() for k, v in params.pnginfo.items(): pnginfo_data.add_text(k, str(v)) - image.save(fn, format=image_format, compress_level=6, pnginfo=pnginfo_data if shared.opts.image_metadata else None) + try: + image.save(fn, format=image_format, compress_level=6, pnginfo=pnginfo_data if shared.opts.image_metadata else None) + except Exception as e: + shared.log.warning(f'Image save failed: {fn} {e}') elif image_format == 'JPEG': if image.mode == 'RGBA': shared.log.warning('Saving RGBA image as JPEG: Alpha channel will be lost') @@ -487,7 +502,10 @@ def atomically_save_image(): elif image.mode == 'I;16': image = image.point(lambda p: p * 0.0038910505836576).convert("L") exif_bytes = piexif.dump({ "Exif": { piexif.ExifIFD.UserComment: piexif.helper.UserComment.dump(exifinfo, encoding="unicode") } }) - image.save(fn, format=image_format, optimize=True, quality=shared.opts.jpeg_quality, exif=exif_bytes) + try: + image.save(fn, format=image_format, optimize=True, quality=shared.opts.jpeg_quality, exif=exif_bytes) + except Exception as e: + shared.log.warning(f'Image save failed: {fn} {e}') elif image_format == 'WEBP': if image.mode == 'I;16': image = image.point(lambda p: p * 0.0038910505836576).convert("RGB") @@ -578,10 +596,11 @@ def save_image(image, path, basename = '', seed=None, prompt=None, extension=sha filename_txt = f"{filename}.txt" if shared.opts.save_txt and len(exifinfo) > 0 else None save_queue.put((params.image, filename, extension, params, exifinfo, filename_txt)) # actual save is executed in a thread that polls data from queue save_queue.join() - - params.image.already_saved_as = params.filename + if not hasattr(params.image, 'already_saved_as'): + debug(f'Image marked: "{params.filename}"') + params.image.already_saved_as = params.filename script_callbacks.image_saved_callback(params) - return filename, filename_txt + return params.filename, filename_txt def safe_decode_string(s: bytes): diff --git a/modules/ui.py b/modules/ui.py index 3477a2802..21f816c58 100644 --- a/modules/ui.py +++ b/modules/ui.py @@ -725,7 +725,7 @@ def create_ui(startup_timer = None): with gr.Column(): inpaint_full_res = gr.Radio(label="Inpaint area", choices=["Whole picture", "Only masked"], type="index", value="Whole picture", elem_id="img2img_inpaint_full_res") with gr.Column(): - inpaint_full_res_padding = gr.Slider(label='Only masked padding, pixels', minimum=0, maximum=256, step=4, value=32, elem_id="img2img_inpaint_full_res_padding") + inpaint_full_res_padding = gr.Slider(label='Masked padding', minimum=0, maximum=256, step=4, value=32, elem_id="img2img_inpaint_full_res_padding") def select_img2img_tab(tab): return gr.update(visible=tab in [2, 3, 4]), gr.update(visible=tab == 3) diff --git a/modules/ui_tempdir.py b/modules/ui_tempdir.py index 1293ad6b6..f245edb4e 100644 --- a/modules/ui_tempdir.py +++ b/modules/ui_tempdir.py @@ -4,10 +4,11 @@ from collections import namedtuple from pathlib import Path import gradio as gr from PIL import PngImagePlugin -from modules import shared +from modules import shared, errors Savedfile = namedtuple("Savedfile", ["name"]) +debug = errors.log.info if os.environ.get('SD_PATH_DEBUG', None) is not None else lambda *args, **kwargs: None def register_tmp_file(gradio, filename): @@ -45,10 +46,13 @@ def pil_to_temp_file(self, img, dir: str, format="png") -> str: # pylint: disabl img.save(filename, pnginfo=gr.processing_utils.get_pil_metadata(img)) """ already_saved_as = getattr(img, 'already_saved_as', None) - if already_saved_as and os.path.isfile(already_saved_as): + exists = os.path.isfile(already_saved_as) + debug(f'Image lookup: {already_saved_as} exists={exists}') + if already_saved_as and exists: register_tmp_file(shared.demo, already_saved_as) file_obj = Savedfile(already_saved_as) name = file_obj.name + debug(f'Image registered: {name}') return name if shared.opts.temp_dir != "": dir = shared.opts.temp_dir @@ -66,7 +70,6 @@ def pil_to_temp_file(self, img, dir: str, format="png") -> str: # pylint: disabl # override save to file function so that it also writes PNG info -# gr.processing_utils.save_pil_to_file = save_pil_to_file # gradio <=3.31.0 gr.components.IOComponent.pil_to_temp_file = pil_to_temp_file # gradio >=3.32.0 def on_tmpdir_changed(): diff --git a/wiki b/wiki index b9712d07c..21fecf2ec 160000 --- a/wiki +++ b/wiki @@ -1 +1 @@ -Subproject commit b9712d07cf931b8bca512ae4f083b88877062c77 +Subproject commit 21fecf2ec5a2efef8bf7b5a2ca7c415fe3a41042 From b648acf9b4b63a92809abea1139ce5393fa64ef1 Mon Sep 17 00:00:00 2001 From: Vladimir Mandic Date: Sat, 21 Oct 2023 11:22:47 -0400 Subject: [PATCH 5/9] fix windows path --- modules/images.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/modules/images.py b/modules/images.py index ba653f36f..937a7c6a7 100644 --- a/modules/images.py +++ b/modules/images.py @@ -396,7 +396,7 @@ class FilenameGenerator: return sanitized def sanitize(self, filename): - invalid_chars = '\'"\\|?*\n\t\r' # + invalid_chars = '\'"|?*\n\t\r' # invalid_folder = ':' invalid_files = ['CON', 'PRN', 'AUX', 'NUL', 'NULL', 'COM0', 'COM1', 'LPT0', 'LPT1'] invalid_prefix = ', ' From 9a1c52f4db531aef89b6c39a0556008c490b8494 Mon Sep 17 00:00:00 2001 From: Vladimir Mandic Date: Sat, 21 Oct 2023 12:20:10 -0400 Subject: [PATCH 6/9] fix --- modules/ui_tempdir.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/modules/ui_tempdir.py b/modules/ui_tempdir.py index f245edb4e..72fe53b7d 100644 --- a/modules/ui_tempdir.py +++ b/modules/ui_tempdir.py @@ -46,7 +46,7 @@ def pil_to_temp_file(self, img, dir: str, format="png") -> str: # pylint: disabl img.save(filename, pnginfo=gr.processing_utils.get_pil_metadata(img)) """ already_saved_as = getattr(img, 'already_saved_as', None) - exists = os.path.isfile(already_saved_as) + exists = os.path.isfile(already_saved_as) if already_saved_as is not None else False debug(f'Image lookup: {already_saved_as} exists={exists}') if already_saved_as and exists: register_tmp_file(shared.demo, already_saved_as) From f15308c47313f31ba7fcff9f1c9d239b7455f5ab Mon Sep 17 00:00:00 2001 From: Disty0 Date: Sat, 21 Oct 2023 19:30:33 +0300 Subject: [PATCH 7/9] Update Diffusers secondary sampler --- modules/processing_diffusers.py | 34 ++++++++++++++------------------- wiki | 2 +- 2 files changed, 15 insertions(+), 21 deletions(-) diff --git a/modules/processing_diffusers.py b/modules/processing_diffusers.py index 1bcb959e9..84f425f5a 100644 --- a/modules/processing_diffusers.py +++ b/modules/processing_diffusers.py @@ -316,17 +316,19 @@ def process_diffusers(p: StableDiffusionProcessing, seeds, prompts, negative_pro else: pass #Do nothing if compile is disabled + def update_sampler(sd_model, second_pass=False): + sampler_selection = p.latent_sampler if second_pass else p.sampler_name + is_karras_compatible = sd_model.__class__.__init__.__annotations__.get("scheduler", None) == diffusers.schedulers.scheduling_utils.KarrasDiffusionSchedulers + if hasattr(sd_model, 'scheduler') and sampler_selection != 'Default' and is_karras_compatible: + sampler = sd_samplers.all_samplers_map.get(sampler_selection, None) + if sampler is None: + sampler = sd_samplers.all_samplers_map.get("UniPC") + sd_samplers.create_sampler(sampler.name, sd_model) + # TODO extra_generation_params add sampler options + # p.extra_generation_params['Sampler options'] = '' + recompile_model() - - is_karras_compatible = shared.sd_model.__class__.__init__.__annotations__.get("scheduler", None) == diffusers.schedulers.scheduling_utils.KarrasDiffusionSchedulers - if hasattr(shared.sd_model, 'scheduler') and p.sampler_name != 'Default' and is_karras_compatible: - sampler = sd_samplers.all_samplers_map.get(p.sampler_name, None) - if sampler is None: - sampler = sd_samplers.all_samplers_map.get("UniPC") - sd_samplers.create_sampler(sampler.name, shared.sd_model) - # TODO extra_generation_params add sampler options - # p.extra_generation_params['Sampler options'] = '' - + update_sampler(shared.sd_model) p.extra_generation_params['Pipeline'] = shared.sd_model.__class__.__name__ if len(getattr(p, 'init_images', [])) > 0: @@ -403,11 +405,7 @@ def process_diffusers(p: StableDiffusionProcessing, seeds, prompts, negative_pro p.ops.append('hires') shared.sd_model = sd_models.set_diffuser_pipe(shared.sd_model, sd_models.DiffusersTaskType.IMAGE_2_IMAGE) recompile_model(hires=True) - if ((not hasattr(shared.sd_model.scheduler, 'name')) or (p.latent_sampler == 'DPM SDE') or (shared.sd_model.scheduler.name != p.latent_sampler)) and (p.latent_sampler != 'Default') and is_karras_compatible: - sampler = sd_samplers.all_samplers_map.get(p.latent_sampler, None) - if sampler is None: - sampler = sd_samplers.all_samplers_map.get("UniPC") - sd_samplers.create_sampler(sampler.name, shared.sd_model) + update_sampler(shared.sd_model, second_pass=True) hires_args = set_pipeline_args( model=shared.sd_model, prompts=[p.refiner_prompt] if len(p.refiner_prompt) > 0 else prompts, @@ -439,11 +437,7 @@ def process_diffusers(p: StableDiffusionProcessing, seeds, prompts, negative_pro shared.sd_model.to(devices.cpu) devices.torch_gc() - if ((not hasattr(shared.sd_refiner.scheduler, 'name')) or (p.latent_sampler == 'DPM SDE') or (shared.sd_refiner.scheduler.name != p.latent_sampler)) and (p.latent_sampler != 'Default'): - sampler = sd_samplers.all_samplers_map.get(p.latent_sampler, None) - if sampler is None: - sampler = sd_samplers.all_samplers_map.get("UniPC") - sd_samplers.create_sampler(sampler.name, shared.sd_refiner) + update_sampler(shared.sd_refiner, second_pass=True) if shared.state.interrupted or shared.state.skipped: return results diff --git a/wiki b/wiki index 21fecf2ec..3723be600 160000 --- a/wiki +++ b/wiki @@ -1 +1 @@ -Subproject commit 21fecf2ec5a2efef8bf7b5a2ca7c415fe3a41042 +Subproject commit 3723be6002a01d94934267413bc8af1168fa9865 From 2b59b10b8aab7b0cda29da20fef97e3b164de717 Mon Sep 17 00:00:00 2001 From: Vladimir Mandic Date: Sat, 21 Oct 2023 13:53:13 -0400 Subject: [PATCH 8/9] update todo/changelog --- CHANGELOG.md | 3 +- TODO.md | 78 +------------------------------------ installer.py | 2 +- javascript/base.css | 2 +- javascript/sdnext.css | 2 +- modules/ui_prompt_styles.py | 6 +-- 6 files changed, 9 insertions(+), 84 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 7110eb3f2..1d6bea72b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -16,12 +16,13 @@ Service release addressing all zero-day issues reported so far... - fix new style filename template - fix image name template using model name - fix model path using relative path -- fix torch-rocm version detection (thanks @xangelix) +- fix `torch-rocm` and `tensorflow-rocm` version detection (thanks @xangelix) - fix chainner upscalers color clipping - force second requirements check on startup - remove lyco, multiple_tqdm - enhance extension compatibility for exensions directly importing codeformers - enhance extension compatibility for exensions directly accessing processing params +- css fixes - clearly mark external themes in ui - new option: *settings -> images -> keep incomplete* can be used to skip vae decode on aborted/skipped/interrupted image generations diff --git a/TODO.md b/TODO.md index 8f83079bb..0c895d17b 100644 --- a/TODO.md +++ b/TODO.md @@ -1,79 +1,3 @@ # TODO -## Issues - -Stuff to be fixed, in no particular order... - -N/A - -## Features - -Stuff to be added, in no particular order... - -- Diffusers: - - Add ControlNet - - Add unCLIP model - - Add Training support -- Technical debt: - - Port **A1111** stuff - - Port `p.all_hr_prompts` - - Import core repos to reduce dependencies -- Non-technical: - - Update Wiki - - Rename repo: **automatic** -> **sdnext** -- Backends: - - PyTorch / XLA - - Diffusers / ONNX - - ScaleCrafter -- New Minor - - Prompt padding for positive/negative -- New Major - - Profile manager (for `config.json` and `ui-config.json`) - - Multi-user support - - Image phash and hdash using `imagehash` - - Model merge using `git-rebasin` - - Enable refiner-style workflow for `ldm` backend - - Add `sgm` backend - - Cache models in VRAM - - Train: - - Use `interrogator` - - Use `rembg` - - Templates for SD-XL training - - Lora train UI -- Redesign - - New UI - - New inpainting canvas controls (move from backend to purely frontend) - - New image browser (move from backend to purely frontend) - - Change workflows from static/legacy to steps-based -- Video processing - - -## Investigate - -Stuff to be investigated... - -## Merge PRs - -Pick & merge PRs from main repo... - -- up-to-date with: df004be -- current todo list: - -## Integration - -Tech that can be integrated as part of the core workflow... - -- [Git-ReBasin]([https://github.com/ogkalu2/Merge-Stable-Diffusion-models-without-distortion](https://github.com/vladmandic/automatic/issues/1176)) -- [Null-text inversion](https://github.com/ouhenio/null-text-inversion-colab) -- [Custom diffusion](https://github.com/guaneec/custom-diffusion-webui), [Custom diffusion](https://www.cs.cmu.edu/~custom-diffusion/) -- [Dream artist](https://github.com/7eu7d7/DreamArtist-sd-webui-extension) -- [QuickEmbedding](https://github.com/ethansmith2000/QuickEmbedding) -- [DragGAN](https://github.com/XingangPan/DragGAN) -- [LamaCleaner](https://github.com/Sanster/lama-cleaner) -- [SAG](https://huggingface.co/docs/diffusers/v0.19.3/en/api/pipelines/self_attention_guidance), [SAG](https://github.com/ashen-sensored/sd_webui_SAG) -- [Localization](https://app.transifex.com/signup/open-source/) -- `TensorRT` - -## Random - -- Bunch of stuff: +ToDo list has moved to [GitHub projects](https://github.com/users/vladmandic/projects) diff --git a/installer.py b/installer.py index e93ae9b01..205f16cee 100644 --- a/installer.py +++ b/installer.py @@ -575,7 +575,7 @@ def install_packages(): install('onnxruntime==1.15.1', 'onnxruntime', ignore=True) install('pi-heif', 'pi_heif', ignore=True) tensorflow_package = os.environ.get('TENSORFLOW_PACKAGE', 'tensorflow==2.13.0') - install(tensorflow_package, 'tensorflow', ignore=True) + install(tensorflow_package, 'tensorflow-rocm' if 'rocm' in tensorflow_package else 'tensorflow', ignore=True) # install('nvidia-ml-py', 'pynvml', ignore=True) bitsandbytes_package = os.environ.get('BITSANDBYTES_PACKAGE', None) if bitsandbytes_package is not None: diff --git a/javascript/base.css b/javascript/base.css index 86ef0f2a7..d6b2c126e 100644 --- a/javascript/base.css +++ b/javascript/base.css @@ -71,7 +71,7 @@ table.settings-value-table td { padding: 0.4em; border: 1px solid #ccc; max-widt #extensions .date{ opacity: 0.85; font-size: 90%; } /* extra networks */ -.extra-networks > div { margin: 0; gap: 0.2em; border-bottom: none !important; } +.extra-networks > div { margin: 0; border-bottom: none !important; } .extra-networks .second-line { display: flex; width: -moz-available; width: -webkit-fill-available; gap: 0.3em; box-shadow: var(--input-shadow); } .extra-networks .search { flex: 1; } .extra-networks .description { flex: 3; } diff --git a/javascript/sdnext.css b/javascript/sdnext.css index 4b0bdbeb1..63859f303 100644 --- a/javascript/sdnext.css +++ b/javascript/sdnext.css @@ -175,7 +175,7 @@ table.settings-value-table td { padding: 0.4em; border: 1px solid #ccc; max-widt #extensions .date{ opacity: 0.85; font-size: 90%; } /* extra networks */ -.extra-networks > div { margin: 0; gap: 0.2em; border-bottom: none !important; } +.extra-networks > div { margin: 0; border-bottom: none !important; gap: 0.3em 0; } .extra-networks .second-line { display: flex; width: -moz-available; width: -webkit-fill-available; gap: 0.3em; box-shadow: var(--input-shadow); } .extra-networks .search { flex: 1; } .extra-networks .description { flex: 3; } diff --git a/modules/ui_prompt_styles.py b/modules/ui_prompt_styles.py index 9f8a43c7c..c394cd852 100644 --- a/modules/ui_prompt_styles.py +++ b/modules/ui_prompt_styles.py @@ -20,9 +20,9 @@ def select_style(name): def save_style(name, prompt, negative_prompt): if not name: return gr.update(visible=False) - style = styles.PromptStyle(name, prompt, negative_prompt) + style = styles.Style(name, prompt, negative_prompt) shared.prompt_styles.styles[style.name] = style - shared.prompt_styles.save_styles(shared.styles_filename) + shared.prompt_styles.save_styles('') return gr.update(visible=True) @@ -30,7 +30,7 @@ def delete_style(name): if name == "": return shared.prompt_styles.styles.pop(name, None) - shared.prompt_styles.save_styles(shared.styles_filename) + shared.prompt_styles.save_styles('') return '', '', '' From 2f1f9974210b569fed1f904a9cd12f0041904aa5 Mon Sep 17 00:00:00 2001 From: Vladimir Mandic Date: Sat, 21 Oct 2023 14:16:16 -0400 Subject: [PATCH 9/9] fix xyz grid fill --- modules/sd_samplers.py | 4 ++-- scripts/xyz_grid.py | 8 +++++--- wiki | 2 +- 3 files changed, 8 insertions(+), 6 deletions(-) diff --git a/modules/sd_samplers.py b/modules/sd_samplers.py index d538a0da9..db9ad937f 100644 --- a/modules/sd_samplers.py +++ b/modules/sd_samplers.py @@ -35,8 +35,8 @@ def find_sampler_config(name): def visible_sampler_names(): - samplers = [x for x in all_samplers if x.name in shared.opts.show_samplers] if len(shared.opts.show_samplers) > 0 else all_samplers - return samplers + visible_samplers = [x for x in all_samplers if x.name in shared.opts.show_samplers] if len(shared.opts.show_samplers) > 0 else all_samplers + return visible_samplers def create_sampler(name, model): diff --git a/scripts/xyz_grid.py b/scripts/xyz_grid.py index 81ef716d1..22eb5699c 100644 --- a/scripts/xyz_grid.py +++ b/scripts/xyz_grid.py @@ -444,9 +444,9 @@ class Script(scripts.Script): else: return gr.update(), gr.update() - fill_x_button.click(fn=fill, inputs=[x_type, csv_mode], outputs=[x_values_dropdown]) - fill_y_button.click(fn=fill, inputs=[y_type, csv_mode], outputs=[y_values_dropdown]) - fill_z_button.click(fn=fill, inputs=[z_type, csv_mode], outputs=[z_values_dropdown]) + fill_x_button.click(fn=fill, inputs=[x_type, csv_mode], outputs=[x_values, x_values_dropdown]) + fill_y_button.click(fn=fill, inputs=[y_type, csv_mode], outputs=[y_values, y_values_dropdown]) + fill_z_button.click(fn=fill, inputs=[z_type, csv_mode], outputs=[z_values, z_values_dropdown]) def select_axis(axis_type, axis_values, axis_values_dropdown, csv_mode): choices = self.current_axis_options[axis_type].choices @@ -700,3 +700,5 @@ class Script(scripts.Script): del processed.all_seeds[1] del processed.infotexts[1] return processed + +print('HERE', [x.name for x in sd_samplers.samplers]) diff --git a/wiki b/wiki index 3723be600..5e13a66ab 160000 --- a/wiki +++ b/wiki @@ -1 +1 @@ -Subproject commit 3723be6002a01d94934267413bc8af1168fa9865 +Subproject commit 5e13a66ab9c494f8ac4893d522bfec5c30605cd8