Files

574 lines
34 KiB
Python

import re
import time
from copy import copy
import numpy as np
import gradio as gr
from PIL import Image, ImageDraw
from modules.logger import log
from modules import shared, processing, devices, processing_class, ui_common, ui_components, ui_symbols, images, extra_networks, sd_models
from modules.detailer import DetailerResult, detailer_opt, assign_prompts, parse_prompt_lines
class Detailer():
def __init__(self):
super().__init__()
self.model_name = None
self.models = {} # cache loaded models
self.list = {}
self.ui_mode = True
self.cmd_dir = shared.opts.yolo_dir
self.enumerate()
def name(self):
return "Detailer"
def predict(
self,
name: str,
model,
image: Image.Image,
imgsz: int = 640,
half: bool = True,
device = devices.device,
agnostic: bool = False,
retina: bool = False,
mask: bool = True,
augment: bool | None = None,
offload: bool | None = None,
p = None,
) -> list[DetailerResult]:
jobid = shared.state.begin('Detect')
if 'LocateAnything' in name:
from modules.detailer import locateanything
results = locateanything.predict(self, name, image, device=device, mask=mask, offload=offload, p=p)
elif 'Qwen3-VL' in name:
from modules.detailer import qwen
results = qwen.predict(self, name, image, device=device, mask=mask, offload=offload, p=p)
elif 'Florence-2' in name:
from modules.detailer import florence
results = florence.predict(self, name, image, device=device, mask=mask, offload=offload, p=p)
elif 'Grounding-DINO' in name:
from modules.detailer import dino
results = dino.predict(self, name, image, device=device, mask=mask, offload=offload, p=p)
elif 'Rex-Omni' in name:
from modules.detailer import rexomni
results = rexomni.predict(self, name, image, device=device, mask=mask, offload=offload, p=p)
elif 'Facebook-SAM3' in name:
from modules.detailer import sam
results = sam.predict(self, name, image, device=device, mask=mask, offload=offload, p=p)
else:
from modules.detailer import yolo
results = yolo.predict(self, model, image, imgsz=imgsz, half=half, device=device, agnostic=agnostic, retina=retina, mask=mask, augment=augment, offload=offload, p=p)
shared.state.end(jobid)
return results
def enumerate(self):
from modules.detailer import list_models
return list_models(self)
def load(self, model_name: str | None = None):
jobid = shared.state.begin('Load detailer')
if 'LocateAnything' in model_name:
from modules.detailer import locateanything
model_name, model = locateanything.load(self, model_name=model_name)
elif 'Qwen3-VL' in model_name:
from modules.detailer import qwen
model_name, model = qwen.load(self, model_name=model_name)
elif 'Florence-2' in model_name:
from modules.detailer import florence
model_name, model = florence.load(self, model_name=model_name)
elif 'Grounding-DINO' in model_name:
from modules.detailer import dino
model_name, model = dino.load(self, model_name=model_name)
elif 'Rex-Omni' in model_name:
from modules.detailer import rexomni
model_name, model = rexomni.load(self, model_name=model_name)
elif 'Facebook-SAM3' in model_name:
from modules.detailer import sam
model_name, model = sam.load(self, model_name=model_name)
else:
from modules.detailer import yolo
model_name, model = yolo.load(self, model_name=model_name)
shared.state.end(jobid)
return model_name, model
def merge(self, items: list[DetailerResult]) -> list[DetailerResult]:
if items is None or len(items) == 0:
return []
box=[min(item.box[0] for item in items), min(item.box[1] for item in items), max(item.box[2] for item in items), max(item.box[3] for item in items)]
mask = Image.new('L', items[0].mask.size, 0)
for item in items:
mask = Image.fromarray(np.maximum(np.array(mask), np.array(item.mask)))
merged = DetailerResult(
cls=items[0].cls,
label=items[0].label,
score=sum(item.score for item in items) / len(items),
box=box,
mask=mask,
item=None,
width=box[2] - box[0],
height=box[3] - box[1],
)
return [merged]
def filter(self, items: list[DetailerResult], image: Image.Image, p: processing.StableDiffusionProcessing = None) -> list[DetailerResult]:
if items is None or len(items) == 0:
return []
if p is not None:
min_conf = detailer_opt(p, 'detailer_conf')
max_detected = detailer_opt(p, 'detailer_max')
filtered = [item for item in items if item.score >= min_conf]
opt_min = detailer_opt(p, 'detailer_min_size') or 0
opt_max = detailer_opt(p, 'detailer_max_size') or 1
for item in filtered.copy():
w, h = item.box[2] - item.box[0], item.box[3] - item.box[1]
x_size, y_size = w/image.width, h/image.height
min_size = opt_min if 0 <= opt_min <= 1 else 0
max_size = opt_max if 0 < opt_max <= 1 else 1
if not ((x_size >= min_size) and (y_size >= min_size) and (x_size <= max_size) and (y_size <= max_size)):
filtered.remove(item)
filtered = sorted(filtered, key=lambda x: x.score, reverse=True)
filtered = filtered[:max_detected]
else:
filtered = items
if len(filtered) != len(items):
log.debug(f'Detailer: items={len(items)} filtered={len(filtered)}')
return filtered
def draw_masks(self, image: Image.Image, items: list[DetailerResult], p=None) -> Image.Image | np.ndarray:
if not isinstance(image, Image.Image):
image = Image.fromarray(image)
image = image.convert('RGBA')
size = min(image.width, image.height) // 32
font = images.get_font(size)
color = (0, 190, 190)
# log.debug(f'Detailer: draw={items}')
for i, item in enumerate(items):
if detailer_opt(p, 'detailer_segmentation') and item.mask is not None:
mask = item.mask.convert('L')
else:
mask = Image.new('L', image.size, 0)
draw_mask = ImageDraw.Draw(mask)
draw_mask.rectangle(item.box, fill="white", outline=None, width=0)
alpha = mask.point(lambda p: int(p * 0.5))
overlay = Image.new("RGBA", image.size, color + (0,))
overlay.putalpha(alpha)
image = Image.alpha_composite(image, overlay)
draw_text = ImageDraw.Draw(image)
draw_text.text((item.box[0] + 2, item.box[1] - size - 2), f'{i+1} {item.label} {item.score:.2f}', fill="black", font=font)
draw_text.text((item.box[0] + 0, item.box[1] - size - 4), f'{i+1} {item.label} {item.score:.2f}', fill="white", font=font)
image = image.convert("RGB")
return np.array(image)
def restore(self, np_image, p: processing.StableDiffusionProcessing = None):
if shared.state.interrupted or shared.state.skipped:
return np_image
if hasattr(p, 'recursion'):
return np_image
if not hasattr(p, 'detailer_active'):
p.detailer_active = 0
if np_image is None or p.detailer_active >= p.batch_size * p.n_iter:
return np_image
shared.sd_model = sd_models.set_diffuser_pipe(shared.sd_model, sd_models.DiffusersTaskType.INPAINTING)
if (sd_models.get_diffusers_task(shared.sd_model) != sd_models.DiffusersTaskType.INPAINTING) and (shared.sd_model.__class__.__name__ not in sd_models.pipe_switch_task_exclude):
log.error(f'Detailer: model="{shared.sd_model.__class__.__name__}" not compatible')
return np_image
models = []
if len(shared.opts.detailer_args) > 0:
models = [m.strip() for m in re.split(r'[\n,;]+', shared.opts.detailer_args)]
models = [m for m in models if len(m) > 0]
if len(models) == 0:
models = detailer_opt(p, 'detailer_models') or []
if len(models) == 0:
log.warning('Detailer: model=None')
return np_image
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 = []
np_images = []
annotated = Image.fromarray(np_image)
image = None
# detailer_prompt/negative are the same for every model in the chain, so resolve them once
orig_prompt: str = orig_p.get('all_prompts', [''])[0]
orig_negative: str = orig_p.get('all_negative_prompts', [''])[0]
prompt: str = orig_p.get('detailer_prompt', '')
negative: str = orig_p.get('detailer_negative', '')
if prompt is None or len(prompt) == 0:
prompt = orig_prompt
else:
prompt = prompt.replace('[PROMPT]', orig_prompt)
prompt = prompt.replace('[prompt]', orig_prompt)
if len(negative) == 0:
negative = orig_negative
else:
negative = negative.replace('[PROMPT]', orig_negative)
negative = negative.replace('[prompt]', orig_negative)
# track which '[CLASS=name]' tags get matched by any model in the chain, to warn on genuine typos only
prompt_classes, _ = parse_prompt_lines(prompt)
negative_classes, _ = parse_prompt_lines(negative)
prompt_classes = set(prompt_classes.keys())
negative_classes = set(negative_classes.keys())
matched_prompt_classes = set()
matched_negative_classes = set()
for i, model_val in enumerate(models):
if shared.state.skipped:
shared.state.skipped = False
continue
if shared.state.interrupted:
break
while shared.state.paused:
log.debug('Detail paused')
if shared.state.interrupted:
break
if shared.state.skipped:
continue
time.sleep(0.1)
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)}
name, model = self.load(model_name)
if model is None:
log.warning(f'Detailer: model="{name}" not loaded')
continue
if image is None:
image = Image.fromarray(np_image)
items = self.predict(name, model, image, p=p)
items = self.filter(items, image, p=p)
if len(items) == 0:
log.info(f'Detailer: model="{name}" no items detected')
continue
if detailer_opt(p, 'detailer_merge') and len(items) > 1:
log.debug(f'Detailer: model="{name}" items={len(items)} merge')
items = self.merge(items)
shared.opts.data['mask_apply_overlay'] = True
args = {
'detailer': True,
'batch_size': 1,
'n_iter': 1,
'prompt': prompt,
'negative_prompt': negative,
'denoising_strength': p.detailer_strength,
'sampler_name': orig_p.get('hr_sampler_name', 'default'),
'steps': p.detailer_steps,
'styles': [],
'inpaint_full_res': True,
'inpainting_mask_invert': 0,
'mask_blur': detailer_opt(p, 'detailer_blur'),
'inpaint_full_res_padding': detailer_opt(p, 'detailer_padding'),
'width': p.detailer_resolution,
'height': p.detailer_resolution,
'vae_type': orig_p.get('vae_type', 'Full'),
}
args.update(model_args)
if args['denoising_strength'] == 0:
log.debug(f'Detailer: model="{name}" strength=0 skip')
return np_image
control_pipeline = None
orig_class = shared.sd_model.__class__
if getattr(p, 'is_control', False):
from modules.control import run
control_pipeline = shared.sd_model
run.restore_pipeline()
p = processing_class.switch_class(p, processing.StableDiffusionProcessingImg2Img, args)
if hasattr(shared.sd_model, 'restore_pipeline'):
shared.sd_model.restore_pipeline()
p.detailer_active += 1 # set flag to avoid recursion
if p.steps < 1:
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]
# log.info(f'Detailer: model="{name}" items={report} args={args}')
models_used.append(name)
mask_all = []
p.state = ''
pc = copy(p)
pc.ops.append('detailer')
orig_sigma_adjust: float = shared.opts.schedulers_sigma_adjust
orig_sigma_end: float = shared.opts.schedulers_sigma_adjust_max
shared.opts.schedulers_sigma_adjust = detailer_opt(p, 'detailer_sigma_adjust')
shared.opts.schedulers_sigma_adjust_max = detailer_opt(p, 'detailer_sigma_adjust_max')
if detailer_opt(p, 'detailer_sort'):
items = sorted(items, key=lambda x: x.box[0]) # sort items left-to-right to improve consistency
if detailer_opt(p, 'detailer_include_detections', 'detailer_save'):
annotated = self.draw_masks(annotated, items, p=p)
labels_this_pass = {(item.label or '').strip().lower() for item in items}
matched_prompt_classes |= (prompt_classes & labels_this_pass)
matched_negative_classes |= (negative_classes & labels_this_pass)
resolved_prompts = assign_prompts(prompt, items)
resolved_negatives = assign_prompts(negative, items)
for j, item in enumerate(items):
if shared.state.skipped:
shared.state.skipped = False
continue
if shared.state.interrupted:
break
while shared.state.paused:
log.debug('Detail paused')
if shared.state.interrupted:
break
if shared.state.skipped:
continue
time.sleep(0.1)
if item.mask is None:
continue
shared.sd_model.fail_on_switch_error = True
pc.keep_prompts = True
pc.prompt = resolved_prompts[j]
pc.negative_prompt = resolved_negatives[j]
pc.prompts = [pc.prompt]
pc.negative_prompts = [pc.negative_prompt]
pc.prompts, pc.network_data = extra_networks.parse_prompts(pc.prompts, pc.network_data)
pc.disable_extra_networks = True # disable processing_diffusers from handling network activation since its handled here
network_same = len(p.network_data.values()) == len(pc.network_data.values()) and all(x == y for x, y in zip(p.network_data.values(), pc.network_data.values()))
if not network_same:
extra_networks.activate(pc, pc.network_data)
log.debug(f'Detail: model="{i+1}:{name}" item={j+1}/{len(items)} box={item.box} label="{item.label}" score={item.score:.2f} seg={detailer_opt(p, "detailer_segmentation")} network={network_same} prompt="{pc.prompt}"')
pc.init_images = [image]
pc.image_mask = [item.mask]
pc.overlay_images = []
# explicitly disable for detailer pass
pc.enable_hr = False
pc.do_not_save_samples = True
pc.do_not_save_grid = True
# set recursion flag to avoid nested detailer calls
pc.recursion = True
# process
jobid = shared.state.begin('Detailer')
pp = processing.process_images_inner(pc)
if not network_same:
extra_networks.deactivate(pc, force=True)
shared.sd_model.fail_on_switch_error = False
shared.state.end(jobid)
del pc.recursion
if (pp is not None) and (pp.images is not None) and (len(pp.images) > 0):
image = pp.images[0] # update image to be reused for next item
if len(pp.images) > 1:
mask_all.append(pp.images[1])
shared.opts.schedulers_sigma_adjust = orig_sigma_adjust
shared.opts.schedulers_sigma_adjust_max = orig_sigma_end
# restore pipeline
if control_pipeline is not None:
shared.sd_model = control_pipeline
else:
shared.sd_model.__class__ = orig_class
p = processing_class.switch_class(p, orig_cls, orig_p)
p.init_images = orig_p.get('init_images', None)
p.image_mask = orig_p.get('image_mask', None)
p.state = orig_p.get('state', None)
p.ops = orig_p.get('ops', [])
shared.opts.data['mask_apply_overlay'] = orig_apply_overlay
if len(mask_all) > 0 and shared.opts.include_mask:
from modules.control.util import blend
p.image_mask = blend([np.array(m) for m in mask_all])
p.image_mask = Image.fromarray(p.image_mask)
unmatched_prompt = prompt_classes - matched_prompt_classes
if len(unmatched_prompt) > 0:
log.warning(f'Detailer prompt: class tags did not match any detection across models={models}: unmatched={sorted(unmatched_prompt)}')
unmatched_negative = negative_classes - matched_negative_classes
if len(unmatched_negative) > 0:
log.warning(f'Detailer negative: class tags did not match any detection across models={models}: unmatched={sorted(unmatched_negative)}')
if image is not None:
np_images.append(np.array(image))
if detailer_opt(p, 'detailer_include_detections', 'detailer_save') and annotated is not None:
np_images.append(annotated) # save debug image with boxes
return np_images
def make_processing(self, image, prompt='', negative='', steps=10, strength=0.3, resolution=1024, seed=-1, overrides=None, classes=''):
"""Build a synthetic Img2Img processing object to run restore() standalone, with no base generation pass.
The primary params map to the detailer_* fields restore() reads directly. overrides is an optional
dict of the remaining detailer_* settings; None values are skipped and fall through to shared.opts
via detailer_opt(). The seed is resolved here so restore()'s inpaint passes are reproducible and the
effective value can be reported back.
"""
if image is None:
return None
from modules.processing_helpers import get_fixed_seed
from modules.paths import resolve_output_path
seed = int(get_fixed_seed(seed))
outpath = resolve_output_path(shared.opts.outdir_samples, shared.opts.outdir_extras_samples)
p = processing.StableDiffusionProcessingImg2Img(
sd_model=shared.sd_model,
prompt=prompt or '',
negative_prompt=negative or '',
init_images=[image],
outpath_samples=outpath,
outpath_grids=outpath,
batch_size=1,
n_iter=1,
seed=seed,
width=image.width,
height=image.height,
detailer_enabled=True,
detailer_prompt=prompt or '',
detailer_negative=negative or '',
detailer_steps=steps,
detailer_strength=strength,
detailer_resolution=resolution,
detailer_classes=classes,
)
for attr, val in (overrides or {}).items():
if val is not None:
setattr(p, attr, val)
# restore() at yolo.py reads all_prompts[0]/all_negative_prompts[0]; the rest avoid AttributeError downstream
p.all_prompts = [p.detailer_prompt or '']
p.all_negative_prompts = [p.detailer_negative or '']
p.all_seeds = [seed]
p.all_subseeds = [-1]
p.scripts = None
p.is_control = False
p.do_not_save_samples = True
p.do_not_save_grid = True
return p
def change_mode(self, dropdown, text):
self.ui_mode = not self.ui_mode
if self.ui_mode:
value = [val.split(':', 1)[0].strip() for val in text.split(',') if val.strip()]
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(merge, detailers, text, classes, strength, padding, blur, min_confidence, max_detected, min_size, max_size, iou, steps, renoise_value, renoise_end, resolution, save, sort, seg): # pylint: disable=unused-argument
shared.opts.detailer_merge = merge
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
shared.opts.detailer_conf = min_confidence
shared.opts.detailer_max = max_detected
shared.opts.detailer_min_size = min_size
shared.opts.detailer_max_size = max_size
shared.opts.detailer_iou = iou
shared.opts.detailer_sigma_adjust = renoise_value
shared.opts.detailer_sigma_adjust_max = renoise_end
shared.opts.detailer_save = save
shared.opts.detailer_sort = sort
shared.opts.detailer_segmentation = seg
# shared.opts.detailer_resolution = resolution
shared.opts.save(silent=True)
# log.debug(f'Detailer settings: models={detailers} classes={classes} strength={strength} conf={min_confidence} max={max_detected} iou={iou} size={min_size}-{max_size} padding={padding} steps={steps} resolution={resolution} save={save} sort={sort} seg={seg}')
if not self.ui_mode:
log.debug(f'Detailer expert: {text}')
with gr.Accordion(open=False, label="Detailer", elem_id=f"{tab}_detailer_accordion", elem_classes=["small-accordion"]):
with gr.Row():
enabled = gr.Checkbox(label="Enable detailer pass", elem_id=f"{tab}_detailer_enabled", value=False)
with gr.Row():
seg = gr.Checkbox(label="Use segmentation", elem_id=f"{tab}_detailer_seg", value=shared.opts.detailer_segmentation, visible=True)
save = gr.Checkbox(label="Include detections", elem_id=f"{tab}_detailer_save", value=shared.opts.detailer_save, visible=True)
with gr.Row():
merge = gr.Checkbox(label="Merge detailers", elem_id=f"{tab}_detailer_merge", value=shared.opts.detailer_merge, visible=True)
sort = gr.Checkbox(label="Sort detections", elem_id=f"{tab}_detailer_sort", value=shared.opts.detailer_sort, visible=True)
with gr.Row():
detailers = gr.Dropdown(label="Detailer models", elem_id=f"{tab}_detailers", choices=list(self.list), value=shared.opts.detailer_models, multiselect=True, visible=True)
detailers_text = gr.Textbox(label="Detailer list", 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, lambda: {"choices": self.enumerate()}, 'yolo_models_refresh')
ui_mode = ui_components.ToolButton(value=ui_symbols.view, elem_id=f'{tab}_yolo_models_list')
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 or instructions", placeholder="List of classes or human instructions", elem_id=f"{tab}_detailer_classes")
if tab == 'extras': # Process tab is standalone, there is no base prompt to fall back to
prompt_placeholder = 'detailer prompt, leave empty for none'
negative_placeholder = 'detailer negative prompt, leave empty for none'
else:
prompt_placeholder = 'detailer prompt or leave empty to use main prompt'
negative_placeholder = 'detailer prompt or leave empty to use main prompt'
with gr.Row():
prompt = gr.Textbox(label="Detailer prompt", value='', placeholder=prompt_placeholder, lines=2, elem_id=f"{tab}_detailer_prompt", elem_classes=["prompt"])
with gr.Row():
negative = gr.Textbox(label="Detailer negative prompt", value='', placeholder=negative_placeholder, lines=2, elem_id=f"{tab}_detailer_negative", elem_classes=["prompt"])
with gr.Row():
steps = gr.Slider(label="Detailer steps", elem_id=f"{tab}_detailer_steps", value=10, minimum=0, maximum=99, step=1)
strength = gr.Slider(label="Detailer strength", elem_id=f"{tab}_detailer_strength", value=0.3, minimum=0, maximum=1, step=0.01)
with gr.Row():
resolution = gr.Slider(label="Detailer resolution", elem_id=f"{tab}_detailer_resolution", value=1024, minimum=256, maximum=4096, step=8)
max_detected = gr.Slider(label="Max detected", elem_id=f"{tab}_detailer_max", value=shared.opts.detailer_max, minimum=1, maximum=10, step=1)
with gr.Row():
padding = gr.Slider(label="Edge padding", elem_id=f"{tab}_detailer_padding", value=shared.opts.detailer_padding, minimum=0, maximum=100, step=1)
blur = gr.Slider(label="Edge blur", elem_id=f"{tab}_detailer_blur", value=shared.opts.detailer_blur, minimum=0, maximum=100, step=1)
with gr.Row():
min_confidence = gr.Slider(label="Min confidence", elem_id=f"{tab}_detailer_conf", value=shared.opts.detailer_conf, minimum=0.0, maximum=1.0, step=0.05)
iou = gr.Slider(label="Max overlap", elem_id=f"{tab}_detailer_iou", value=shared.opts.detailer_iou, minimum=0, maximum=1.0, step=0.05)
with gr.Row():
min_size = shared.opts.detailer_min_size if shared.opts.detailer_min_size < 1 else 0.0
min_size = gr.Slider(label="Min size", elem_id=f"{tab}_detailer_min_size", value=min_size, minimum=0.0, maximum=1.0, step=0.05)
max_size = shared.opts.detailer_max_size if shared.opts.detailer_max_size < 1 and shared.opts.detailer_max_size > 0 else 1.0
max_size = gr.Slider(label="Max size", elem_id=f"{tab}_detailer_max_size", value=max_size, minimum=0.0, maximum=1.0, step=0.05)
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")
sampler_block = None
if tab == 'extras': # fold the standalone sampler settings into the detailer accordion; values applied per-job in make_processing, never global opts
from modules import ui_sections
sampler_choices, default_value, filtered = ui_sections.sampler_choices()
with gr.Accordion('Sampler', open=False, elem_id=f"{tab}_detailer_sampler_accordion", elem_classes=["small-accordion"]):
with gr.Row():
ui_sections.create_filter_indicator(tab, 'Sampler', filtered)
d_sampler = gr.Dropdown(label='Sampling method', choices=sampler_choices, value=default_value, type='value', elem_id=f"{tab}_detailer_sampler")
d_prediction = gr.Dropdown(label='Prediction method', choices=['default', 'epsilon', 'sample', 'v_prediction', 'flow_prediction'], value='default', elem_id=f"{tab}_detailer_prediction")
with gr.Row():
d_shift = gr.Slider(label='Flow shift', minimum=0, maximum=10, step=0.1, value=shared.opts.schedulers_shift, elem_id=f"{tab}_detailer_shift")
d_cfg = gr.Slider(label='Guidance scale', minimum=0, maximum=30, step=0.1, value=6.0, elem_id=f"{tab}_detailer_cfg")
with gr.Row():
d_options = gr.CheckboxGroup(label='Options', choices=['low order', 'thresholding', 'dynamic', 'rescale'], value=['low order'], elem_id=f"{tab}_detailer_options")
with gr.Row():
d_seed = gr.Number(label='Seed', value=-1, precision=0, elem_id=f"{tab}_detailer_seed")
sampler_block = {'sampler': d_sampler, 'prediction': d_prediction, 'shift': d_shift, 'cfg_scale': d_cfg, 'options': d_options, 'seed': d_seed}
merge.change(fn=ui_settings_change, inputs=[merge, detailers, detailers_text, classes, strength, padding, blur, min_confidence, max_detected, min_size, max_size, iou, steps, renoise_value, renoise_end, resolution, save, sort, seg], outputs=[])
detailers.change(fn=ui_settings_change, inputs=[merge, detailers, detailers_text, classes, strength, padding, blur, min_confidence, max_detected, min_size, max_size, iou, steps, renoise_value, renoise_end, resolution, save, sort, seg], outputs=[])
detailers_text.change(fn=ui_settings_change, inputs=[merge, detailers, detailers_text, classes, strength, padding, blur, min_confidence, max_detected, min_size, max_size, iou, steps, renoise_value, renoise_end, resolution, save, sort, seg], outputs=[])
classes.change(fn=ui_settings_change, inputs=[merge, detailers, detailers_text, classes, strength, padding, blur, min_confidence, max_detected, min_size, max_size, iou, steps, renoise_value, renoise_end, resolution, save, sort, seg], outputs=[])
padding.change(fn=ui_settings_change, inputs=[merge, detailers, detailers_text, classes, strength, padding, blur, min_confidence, max_detected, min_size, max_size, iou, steps, renoise_value, renoise_end, resolution, save, sort, seg], outputs=[])
blur.change(fn=ui_settings_change, inputs=[merge, detailers, detailers_text, classes, strength, padding, blur, min_confidence, max_detected, min_size, max_size, iou, steps, renoise_value, renoise_end, resolution, save, sort, seg], outputs=[])
min_confidence.change(fn=ui_settings_change, inputs=[merge, detailers, detailers_text, classes, strength, padding, blur, min_confidence, max_detected, min_size, max_size, iou, steps, renoise_value, renoise_end, resolution, save, sort, seg], outputs=[])
max_detected.change(fn=ui_settings_change, inputs=[merge, detailers, detailers_text, classes, strength, padding, blur, min_confidence, max_detected, min_size, max_size, iou, steps, renoise_value, renoise_end, resolution, save, sort, seg], outputs=[])
min_size.change(fn=ui_settings_change, inputs=[merge, detailers, detailers_text, classes, strength, padding, blur, min_confidence, max_detected, min_size, max_size, iou, steps, renoise_value, renoise_end, resolution, save, sort, seg], outputs=[])
max_size.change(fn=ui_settings_change, inputs=[merge, detailers, detailers_text, classes, strength, padding, blur, min_confidence, max_detected, min_size, max_size, iou, steps, renoise_value, renoise_end, resolution, save, sort, seg], outputs=[])
iou.change(fn=ui_settings_change, inputs=[merge, detailers, detailers_text, classes, strength, padding, blur, min_confidence, max_detected, min_size, max_size, iou, steps, renoise_value, renoise_end, resolution, save, sort, seg], outputs=[])
resolution.change(fn=ui_settings_change, inputs=[merge, detailers, detailers_text, classes, strength, padding, blur, min_confidence, max_detected, min_size, max_size, iou, steps, renoise_value, renoise_end, resolution, save, sort, seg], outputs=[])
save.change(fn=ui_settings_change, inputs=[merge, detailers, detailers_text, classes, strength, padding, blur, min_confidence, max_detected, min_size, max_size, iou, steps, renoise_value, renoise_end, resolution, save, sort, seg], outputs=[])
sort.change(fn=ui_settings_change, inputs=[merge, detailers, detailers_text, classes, strength, padding, blur, min_confidence, max_detected, min_size, max_size, iou, steps, renoise_value, renoise_end, resolution, save, sort, seg], outputs=[])
seg.change(fn=ui_settings_change, inputs=[merge, detailers, detailers_text, classes, strength, padding, blur, min_confidence, max_detected, min_size, max_size, iou, steps, renoise_value, renoise_end, resolution, save, sort, seg], outputs=[])
if tab == 'extras':
return enabled, prompt, negative, steps, strength, resolution, classes, sampler_block
return enabled, prompt, negative, steps, strength, resolution, classes