From a852d1cea9fe6860e6573a022ec80a9a62b996e1 Mon Sep 17 00:00:00 2001 From: Vladimir Mandic Date: Mon, 21 Jul 2025 10:39:19 -0400 Subject: [PATCH] add detailer expert mode Signed-off-by: Vladimir Mandic --- CHANGELOG.md | 10 ++-- extensions-builtin/sd-extension-chainner | 2 +- modules/postprocess/yolo.py | 62 +++++++++++++++++------- modules/processing_class.py | 6 ++- modules/processing_info.py | 2 +- modules/shared.py | 1 + package.json | 1 + scripts/xyz/xyz_grid_classes.py | 1 - 8 files changed, 60 insertions(+), 25 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index bd26c93d7..6346ee851 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,8 +1,8 @@ # Change Log for SD.Next -## Update for 2025-07-20 +## Update for 2025-07-21 -### Highlights for 2025-07-20 +### Highlights for 2025-07-21 Feature highlights include: - **ModernUI** layout redesign which should make it more user friendly and easier to navigate @@ -26,7 +26,7 @@ Although upgrades and existing installations are tested and should work fine! [ReadMe](https://github.com/vladmandic/automatic/blob/master/README.md) | [ChangeLog](https://github.com/vladmandic/automatic/blob/master/CHANGELOG.md) | [Docs](https://vladmandic.github.io/sdnext-docs/) | [WiKi](https://github.com/vladmandic/automatic/wiki) | [Discord](https://discord.com/invite/sd-next-federal-batch-inspectors-1101998836328697867) -### Details for 2025-07-20 +### Details for 2025-07-21 - **License** - SD.Next [license](https://github.com/vladmandic/sdnext/blob/dev/LICENSE.txt) switched from **aGPL-v3.0** to **Apache-v2.0** @@ -79,7 +79,7 @@ Although upgrades and existing installations are tested and should work fine! - new [Parameters](https://vladmandic.github.io/sdnext-docs/Parameters/) page that lists and explains all generation parameters massive thanks to @CalamitousFelicitousness for bringing this to life! - updated *Models, Video, LTX, FramePack, Styles*, etc. -- **Compute** +- **Compute** - support for [SageAttention2++](https://github.com/thu-ml/SageAttention) provides 10-15% performance improvement over default SDPA for transformer-based models! enable in *settings -> compute settings -> sdp options* @@ -107,6 +107,8 @@ Although upgrades and existing installations are tested and should work fine! - **Batch** warn on unprocessable images and skip operations on errors so that other images can still be processed - **Metadata** improved parsing and detect foreign metadata detect ComfyUI images + - **Detailer** add `expert` mode where list of detailer models can be converted to textbox for manual editing + see [docs](https://vladmandic.github.io/sdnext-docs/Detailer/) for more information - **SDNQ** - use inference context during quantization - use static compile diff --git a/extensions-builtin/sd-extension-chainner b/extensions-builtin/sd-extension-chainner index c12e8cda4..716b1ee7d 160000 --- a/extensions-builtin/sd-extension-chainner +++ b/extensions-builtin/sd-extension-chainner @@ -1 +1 @@ -Subproject commit c12e8cda4e45a1d5f20659a30e329c7cc7e69339 +Subproject commit 716b1ee7dc8042ba2a62460425930cf3ab472919 diff --git a/modules/postprocess/yolo.py b/modules/postprocess/yolo.py index 67c1243f9..5ea566b7a 100644 --- a/modules/postprocess/yolo.py +++ b/modules/postprocess/yolo.py @@ -5,7 +5,7 @@ from copy import copy import numpy as np import gradio as gr from PIL import Image, ImageDraw -from modules import shared, processing, devices, processing_class, ui_common +from modules import shared, processing, devices, processing_class, ui_common, ui_components, ui_symbols from modules.detailer import Detailer @@ -46,6 +46,7 @@ class YoloRestorer(Detailer): super().__init__() self.models = {} # cache loaded models self.list = {} + self.ui_mode = True self.enumerate() def name(self): @@ -202,16 +203,28 @@ class YoloRestorer(Detailer): p.detailer_active = 0 if np_image is None or p.detailer_active >= p.batch_size * p.n_iter: return np_image - if len(shared.opts.detailer_models) == 0: + models = [m.strip() for m in shared.opts.detailer_args.split(',')] + if len(models) == 0: + models = shared.opts.detailer_models + if len(models) == 0: shared.log.warning('Detailer: model=None') return np_image - models_used = [] + shared.log.debug(f'Detailer: models={models}') + # create backups orig_apply_overlay = shared.opts.mask_apply_overlay orig_p = p.__dict__.copy() orig_cls = p.__class__ + models_used = [] + + for i, model_val in enumerate(models): + if ':' in model_val: + model_name, model_args = model_val.split(':', 1) + else: + model_name, model_args = model_val, '' + model_args = [m.strip() for m in model_args.split(':')] + model_args = {k.strip(): v.strip() for k, v in (arg.split('=') for arg in model_args if '=' in arg)} - for i, model_name in enumerate(shared.opts.detailer_models): name, model = self.load(model_name) if model is None: shared.log.warning(f'Detailer: model="{name}" not loaded') @@ -267,6 +280,7 @@ class YoloRestorer(Detailer): 'height': resolution, 'vae_type': orig_p.get('vae_type', 'Full'), } + args.update(model_args) if args['denoising_strength'] == 0: shared.log.debug(f'Detailer: model="{name}" strength=0 skip') return np_image @@ -286,7 +300,7 @@ class YoloRestorer(Detailer): p.steps = orig_p.get('steps', 0) report = [{'label': i.label, 'score': i.score, 'size': f'{i.width}x{i.height}' } for i in items] - shared.log.info(f'Detailer: model="{name}" items={report} args={items[0].args} strength={p.detailer_strength} blur={p.mask_blur} width={p.width} height={p.height} padding={p.inpaint_full_res_padding}') + shared.log.info(f'Detailer: model="{name}" items={report} args={args}') models_used.append(name) mask_all = [] @@ -338,9 +352,19 @@ class YoloRestorer(Detailer): return np_image + def change_mode(self, dropdown, text): + self.ui_mode = not self.ui_mode + if self.ui_mode: + value = [val.split(':')[0].strip() for val in text.split(',')] + return gr.update(visible=True, value=value), gr.update(visible=False), gr.update(visible=True) + else: + value = ', '.join(dropdown) + return gr.update(visible=False), gr.update(visible=True, value=value), gr.update(visible=False) + def ui(self, tab: str): - def ui_settings_change(detailers, classes, strength, padding, blur, min_confidence, max_detected, min_size, max_size, iou, steps, renoise_value, renoise_end): + def ui_settings_change(detailers, text, classes, strength, padding, blur, min_confidence, max_detected, min_size, max_size, iou, steps, renoise_value, renoise_end): shared.opts.detailer_models = detailers + shared.opts.detailer_args = text if not self.ui_mode else '' shared.opts.detailer_classes = classes shared.opts.detailer_padding = padding shared.opts.detailer_blur = blur @@ -358,8 +382,11 @@ class YoloRestorer(Detailer): with gr.Row(): enabled = gr.Checkbox(label="Enable detailer pass", elem_id=f"{tab}_detailer_enabled", value=False) with gr.Row(): - detailers = gr.Dropdown(label="Detailer models", elem_id=f"{tab}_detailers", choices=self.list, value=shared.opts.detailer_models, multiselect=True) - ui_common.create_refresh_button(detailers, self.enumerate, {}, elem_id=f"{tab}_detailers_refresh") + detailers = gr.Dropdown(label="Detailer models", elem_id=f"{tab}_detailers", choices=self.list, value=shared.opts.detailer_models, multiselect=True, visible=True) + detailers_text = gr.Textbox(label="Detailer models", elem_id=f"{tab}_detailers_text", placeholder="Comma separated list of detailer models", lines=2, visible=False, interactive=True) + refresh_btn = ui_common.create_refresh_button(detailers, self.enumerate, {}, elem_id=f"{tab}_detailers_refresh") + ui_mode = ui_components.ToolButton(value=ui_symbols.view) + ui_mode.click(fn=self.change_mode, inputs=[detailers, detailers_text], outputs=[detailers, detailers_text, refresh_btn]) with gr.Row(): classes = gr.Textbox(label="Detailer classes", placeholder="Classes", elem_id=f"{tab}_detailer_classes") with gr.Row(): @@ -385,15 +412,16 @@ class YoloRestorer(Detailer): with gr.Row(elem_classes=['flex-break']): renoise_value = gr.Slider(minimum=0.5, maximum=1.5, step=0.01, label='Renoise', value=shared.opts.detailer_sigma_adjust, elem_id=f"{tab}_detailer_renoise") renoise_end = gr.Slider(minimum=0.0, maximum=1.0, step=0.01, label='Renoise end', value=shared.opts.detailer_sigma_adjust_max, elem_id=f"{tab}_detailer_renoise_end") - detailers.change(fn=ui_settings_change, inputs=[detailers, classes, strength, padding, blur, min_confidence, max_detected, min_size, max_size, iou, steps, renoise_value, renoise_end], outputs=[]) - classes.change(fn=ui_settings_change, inputs=[detailers, classes, strength, padding, blur, min_confidence, max_detected, min_size, max_size, iou, steps, renoise_value, renoise_end], outputs=[]) - padding.change(fn=ui_settings_change, inputs=[detailers, classes, strength, padding, blur, min_confidence, max_detected, min_size, max_size, iou, steps, renoise_value, renoise_end], outputs=[]) - blur.change(fn=ui_settings_change, inputs=[detailers, classes, strength, padding, blur, min_confidence, max_detected, min_size, max_size, iou, steps, renoise_value, renoise_end], outputs=[]) - min_confidence.change(fn=ui_settings_change, inputs=[detailers, classes, strength, padding, blur, min_confidence, max_detected, min_size, max_size, iou, steps, renoise_value, renoise_end], outputs=[]) - max_detected.change(fn=ui_settings_change, inputs=[detailers, classes, strength, padding, blur, min_confidence, max_detected, min_size, max_size, iou, steps, renoise_value, renoise_end], outputs=[]) - min_size.change(fn=ui_settings_change, inputs=[detailers, classes, strength, padding, blur, min_confidence, max_detected, min_size, max_size, iou, steps, renoise_value, renoise_end], outputs=[]) - max_size.change(fn=ui_settings_change, inputs=[detailers, classes, strength, padding, blur, min_confidence, max_detected, min_size, max_size, iou, steps, renoise_value, renoise_end], outputs=[]) - iou.change(fn=ui_settings_change, inputs=[detailers, classes, strength, padding, blur, min_confidence, max_detected, min_size, max_size, iou, steps, renoise_value, renoise_end], outputs=[]) + detailers.change(fn=ui_settings_change, inputs=[detailers, detailers_text, classes, strength, padding, blur, min_confidence, max_detected, min_size, max_size, iou, steps, renoise_value, renoise_end], outputs=[]) + detailers_text.change(fn=ui_settings_change, inputs=[detailers, detailers_text, classes, strength, padding, blur, min_confidence, max_detected, min_size, max_size, iou, steps, renoise_value, renoise_end], outputs=[]) + classes.change(fn=ui_settings_change, inputs=[detailers, detailers_text, classes, strength, padding, blur, min_confidence, max_detected, min_size, max_size, iou, steps, renoise_value, renoise_end], outputs=[]) + padding.change(fn=ui_settings_change, inputs=[detailers, detailers_text, classes, strength, padding, blur, min_confidence, max_detected, min_size, max_size, iou, steps, renoise_value, renoise_end], outputs=[]) + blur.change(fn=ui_settings_change, inputs=[detailers, detailers_text, classes, strength, padding, blur, min_confidence, max_detected, min_size, max_size, iou, steps, renoise_value, renoise_end], outputs=[]) + min_confidence.change(fn=ui_settings_change, inputs=[detailers, detailers_text, classes, strength, padding, blur, min_confidence, max_detected, min_size, max_size, iou, steps, renoise_value, renoise_end], outputs=[]) + max_detected.change(fn=ui_settings_change, inputs=[detailers, detailers_text, classes, strength, padding, blur, min_confidence, max_detected, min_size, max_size, iou, steps, renoise_value, renoise_end], outputs=[]) + min_size.change(fn=ui_settings_change, inputs=[detailers, detailers_text, classes, strength, padding, blur, min_confidence, max_detected, min_size, max_size, iou, steps, renoise_value, renoise_end], outputs=[]) + max_size.change(fn=ui_settings_change, inputs=[detailers, detailers_text, classes, strength, padding, blur, min_confidence, max_detected, min_size, max_size, iou, steps, renoise_value, renoise_end], outputs=[]) + iou.change(fn=ui_settings_change, inputs=[detailers, detailers_text, classes, strength, padding, blur, min_confidence, max_detected, min_size, max_size, iou, steps, renoise_value, renoise_end], outputs=[]) return enabled, prompt, negative, steps, strength diff --git a/modules/processing_class.py b/modules/processing_class.py index 50b6f2473..4a9d93b90 100644 --- a/modules/processing_class.py +++ b/modules/processing_class.py @@ -582,5 +582,9 @@ def switch_class(p: StableDiffusionProcessing, new_class: type, dct: dict = None if dct is not None: # post init set additional values for k, v in dct.items(): if hasattr(p, k): - setattr(p, k, v) + valtype = type(getattr(p, k, None)) + if valtype in [int, float, str]: + setattr(p, k, valtype(v)) + else: + setattr(p, k, v) return p diff --git a/modules/processing_info.py b/modules/processing_info.py index b04233679..529af75a9 100644 --- a/modules/processing_info.py +++ b/modules/processing_info.py @@ -141,7 +141,7 @@ def create_infotext(p: StableDiffusionProcessing, all_prompts=None, all_seeds=No args['Resize name mask'] = p.resize_name_mask args['Resize scale mask'] = p.scale_by_mask if 'detailer' in p.ops: - args["Detailer"] = ', '.join(shared.opts.detailer_models) + args["Detailer"] = ', '.join(shared.opts.detailer_models) if len(shared.opts.detailer_args) == 0 else shared.opts.detailer_args args["Detailer steps"] = p.detailer_steps args["Detailer strength"] = p.detailer_strength args["Detailer prompt"] = p.detailer_prompt if len(p.detailer_prompt) > 0 else None diff --git a/modules/shared.py b/modules/shared.py index 32b8b180a..849802232 100644 --- a/modules/shared.py +++ b/modules/shared.py @@ -623,6 +623,7 @@ options_templates.update(options_section(('postprocessing', "Postprocessing"), { "detailer_padding": OptionInfo(20, "Item padding", gr.Slider, {"minimum": 0, "maximum": 100, "step": 1, "visible": False}), "detailer_blur": OptionInfo(10, "Item edge blur", gr.Slider, {"minimum": 0, "maximum": 100, "step": 1, "visible": False}), "detailer_models": OptionInfo(['face-yolo8n'], "Detailer models", gr.Dropdown, lambda: {"multiselect":True, "choices": list(yolo.list), "visible": False}), + "detailer_args": OptionInfo("", "Detailer args", gr.Textbox, { "visible": False}), "detailer_unload": OptionInfo(False, "Move detailer model to CPU when complete"), "detailer_augment": OptionInfo(True, "Detailer use model augment"), diff --git a/package.json b/package.json index 549ba03a6..619c30576 100644 --- a/package.json +++ b/package.json @@ -19,6 +19,7 @@ "venv": ". venv/bin/activate", "start": ". venv/bin/activate; python launch.py --debug", "localize": "node cli/localize.js", + "update": ". venv/bin/activate && pip install --upgrade transformers accelerate huggingface_hub safetensors tokenizers peft compel pytorch_lightning", "eslint": "eslint . javascript/ extensions-builtin/sdnext-modernui/javascript/", "ruff": ". venv/bin/activate && ruff check", "pylint": ". venv/bin/activate && pylint *.py modules/ pipelines/ scripts/ extensions-builtin/ | grep -v '^*'", diff --git a/scripts/xyz/xyz_grid_classes.py b/scripts/xyz/xyz_grid_classes.py index 5429cf116..306b591f3 100644 --- a/scripts/xyz/xyz_grid_classes.py +++ b/scripts/xyz/xyz_grid_classes.py @@ -263,7 +263,6 @@ axis_options = [ AxisOption("[Control] Strength", float, apply_control('control_strength')), AxisOption("[Control] Start", float, apply_control('control_start')), AxisOption("[Control] End", float, apply_control('control_end')), - AxisOption("[HiDiffusion] T1", float, apply_override('hidiffusion_t1')), AxisOption("[HiDiffusion] T2", float, apply_override('hidiffusion_t2')), AxisOption("[HiDiffusion] Agression step", float, apply_field('hidiffusion_steps')),