Merge branch 'dev' into patch-2

This commit is contained in:
Vladimir Mandic
2025-07-26 08:54:02 -04:00
committed by GitHub
13 changed files with 89 additions and 57 deletions
+8 -6
View File
@@ -1,16 +1,16 @@
# Change Log for SD.Next
## Update for 2025-07-25
## Update for 2025-07-26
### Highlights for 2025-07-25
### Highlights for 2025-07-26
Feature highlights include:
- **ModernUI** layout redesign which should make it more user friendly and easier to navigate and several new UI themes!
- **ModernUI** layout redesign which should make it more user friendly and easier to navigate plus several new UI themes!
- New models [WanAI Wan 2.1](https://wan.video/) for text-to-image workflows, [FreePix F-Lite](https://huggingface.co/Freepik/F-Lite), [Bria 3.2](https://huggingface.co/briaai/BRIA-3.2)
- Redesigned [LTXVideo](https://vladmandic.github.io/sdnext-docs/Video) interface with support for general video models plus optimized [FramePack](https://vladmandic.github.io/sdnext-docs/FramePack) and [LTXVideo](https://vladmandic.github.io/sdnext-docs/LTX) support
- Fully integrated nudity detection and optional censorship with [NudeNet](https://vladmandic.github.io/sdnext-docs/NudeNet)
- New background replacement and relightning methods using **Latent Bridge Matching** and new **PixelArt** processing filter
- New **LLM/VLM** models available for captioning and prompt enhance
- Additional **LLM/VLM** models available for captioning and prompt enhance
- Number of workflow and general quality-of-life improvements, especially around **Styles**, **Detailer**, **Preview**, **Batch**, **Control**
- Compute improvements
- [Wiki](https://github.com/vladmandic/automatic/wiki) & [Docs](https://vladmandic.github.io/sdnext-docs/) updates, especially new end-to-end [Parameters](https://vladmandic.github.io/sdnext-docs/Parameters/) page
@@ -29,7 +29,7 @@ For details, see [ChangeLog](https://github.com/vladmandic/automatic/blob/master
[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-25
### Details for 2025-07-26
- **License**
- SD.Next [license](https://github.com/vladmandic/sdnext/blob/dev/LICENSE.txt) switched from **aGPL-v3.0** to **Apache-v2.0**
@@ -81,7 +81,7 @@ For details, see [ChangeLog](https://github.com/vladmandic/automatic/blob/master
- support for post load quantization
- **UI**
- major update to modernui layout
- add new Windows-like *Blcoks* UI theme
- add new Windows-like *Blocks* UI theme
- redesign of the *Flat* UI theme
- **WIKI**
- new [Parameters](https://vladmandic.github.io/sdnext-docs/Parameters/) page that lists and explains all generation parameters
@@ -144,6 +144,8 @@ For details, see [ChangeLog](https://github.com/vladmandic/automatic/blob/master
- fix modules merge save model
- fix torchvision bicubic upsample with ipex
- fix instantir pipeline
- fix prompt encoding if prompts within batch have different segment counts
- fix detailer min/max size
- cleanup control infotext
- allow upscaling with models that have implicit VAE processing
- framepack improve offloading
Binary file not shown.
+14 -4
View File
@@ -455,7 +455,7 @@ def set_sdpa_params():
from flash_attn import flash_attn_func
sdpa_pre_flash_atten = torch.nn.functional.scaled_dot_product_attention
@wraps(sdpa_pre_flash_atten)
def sdpa_flash_atten(query, key, value, attn_mask=None, dropout_p=0.0, is_causal=False, scale=None):
def sdpa_flash_atten(query, key, value, attn_mask=None, dropout_p=0.0, is_causal=False, scale=None, enable_gqa=False, **kwargs):
if query.shape[-1] <= 128 and attn_mask is None and query.dtype != torch.float32:
is_unsqueezed = False
if query.dim() == 3:
@@ -465,6 +465,9 @@ def set_sdpa_params():
key = key.unsqueeze(0)
if value.dim() == 3:
value = value.unsqueeze(0)
if enable_gqa:
key = key.repeat_interleave(query.size(-3)//key.size(-3), -3)
value = value.repeat_interleave(query.size(-3)//value.size(-3), -3)
query = query.transpose(1, 2)
key = key.transpose(1, 2)
value = value.transpose(1, 2)
@@ -473,7 +476,9 @@ def set_sdpa_params():
attn_output = attn_output.squeeze(0)
return attn_output
else:
return sdpa_pre_flash_atten(query=query, key=key, value=value, attn_mask=attn_mask, dropout_p=dropout_p, is_causal=is_causal, scale=scale)
if enable_gqa:
kwargs["enable_gqa"] = enable_gqa
return sdpa_pre_flash_atten(query=query, key=key, value=value, attn_mask=attn_mask, dropout_p=dropout_p, is_causal=is_causal, scale=scale, **kwargs)
torch.nn.functional.scaled_dot_product_attention = sdpa_flash_atten
log.debug('Torch attention: type="ck flash attention"')
except Exception as err:
@@ -485,11 +490,16 @@ def set_sdpa_params():
from sageattention import sageattn
sdpa_pre_sage_atten = torch.nn.functional.scaled_dot_product_attention
@wraps(sdpa_pre_sage_atten)
def sdpa_sage_atten(query, key, value, attn_mask=None, dropout_p=0.0, is_causal=False, scale=None):
def sdpa_sage_atten(query, key, value, attn_mask=None, dropout_p=0.0, is_causal=False, scale=None, enable_gqa=False, **kwargs):
if (query.shape[-1] in {128, 96, 64}) and (attn_mask is None) and (query.dtype != torch.float32):
if enable_gqa:
key = key.repeat_interleave(query.size(-3)//key.size(-3), -3)
value = value.repeat_interleave(query.size(-3)//value.size(-3), -3)
return sageattn(q=query, k=key, v=value, attn_mask=attn_mask, dropout_p=dropout_p, is_causal=is_causal, scale=scale)
else:
return sdpa_pre_sage_atten(query=query, key=key, value=value, attn_mask=attn_mask, dropout_p=dropout_p, is_causal=is_causal, scale=scale)
if enable_gqa:
kwargs["enable_gqa"] = enable_gqa
return sdpa_pre_sage_atten(query=query, key=key, value=value, attn_mask=attn_mask, dropout_p=dropout_p, is_causal=is_causal, scale=scale, **kwargs)
torch.nn.functional.scaled_dot_product_attention = sdpa_sage_atten
log.debug('Torch attention: type="sage attention"')
except Exception as err:
+4 -4
View File
@@ -242,7 +242,7 @@ def parse_comfy_metadata(data: dict):
version = dct.get('extra', {}).get('frontendVersion', 'unknown')
if version is not None:
res = f" | Version: {version} | Nodes: {nodes}"
except:
except Exception:
pass
return res
@@ -257,7 +257,7 @@ def parse_comfy_metadata(data: dict):
model = inp.get('model', None)
if isinstance(model, str) and len(model) > 0:
res += f" | Model: {model} | Class: {val.get('class_type', '')}"
except:
except Exception:
pass
return res
@@ -280,7 +280,7 @@ def parse_invoke_metadata(data: dict):
version = dct['app_version']
if isinstance(version, str) and len(version) > 0:
res += f" | Version: {version}"
except:
except Exception:
pass
return res
@@ -299,7 +299,7 @@ def parse_novelai_metadata(data: dict):
dct = json.loads(data["Comment"])
sampler = sd_samplers.samplers_map.get(dct["sampler"], "Euler a")
geninfo = f'{data["Description"]} Negative prompt: {dct["uc"]} Steps: {dct["steps"]}, Sampler: {sampler}, CFG scale: {dct["scale"]}, Seed: {dct["seed"]}, Clip skip: 2, ENSD: 31337'
except Exception as e:
except Exception:
pass
return geninfo
+11 -6
View File
@@ -57,9 +57,11 @@ def find_sdpa_slice_sizes(query_shape, key_shape, query_element_size, slice_rate
original_scaled_dot_product_attention = torch.nn.functional.scaled_dot_product_attention
@wraps(torch.nn.functional.scaled_dot_product_attention)
def dynamic_scaled_dot_product_attention(query, key, value, attn_mask=None, dropout_p=0.0, is_causal=False, **kwargs):
def dynamic_scaled_dot_product_attention(query, key, value, attn_mask=None, dropout_p=0.0, is_causal=False, scale=None, enable_gqa=False, **kwargs):
if query.device.type != "xpu":
return original_scaled_dot_product_attention(query, key, value, attn_mask=attn_mask, dropout_p=dropout_p, is_causal=is_causal, **kwargs)
if enable_gqa:
kwargs["enable_gqa"] = enable_gqa
return original_scaled_dot_product_attention(query, key, value, attn_mask=attn_mask, dropout_p=dropout_p, is_causal=is_causal, scale=scale, **kwargs)
is_unsqueezed = False
if query.dim() == 3:
query = query.unsqueeze(0)
@@ -68,6 +70,9 @@ def dynamic_scaled_dot_product_attention(query, key, value, attn_mask=None, drop
key = key.unsqueeze(0)
if value.dim() == 3:
value = value.unsqueeze(0)
if enable_gqa:
key = key.repeat_interleave(query.size(-3)//key.size(-3), -3)
value = value.repeat_interleave(query.size(-3)//value.size(-3), -3)
do_batch_split, do_head_split, do_query_split, split_batch_size, split_head_size, split_query_size = find_sdpa_slice_sizes(query.shape, key.shape, query.element_size(), slice_rate=attention_slice_rate, trigger_rate=sdpa_slice_trigger_rate)
# Slice SDPA
@@ -93,7 +98,7 @@ def dynamic_scaled_dot_product_attention(query, key, value, attn_mask=None, drop
key[start_idx:end_idx, start_idx_h:end_idx_h, :, :],
value[start_idx:end_idx, start_idx_h:end_idx_h, :, :],
attn_mask=attn_mask[start_idx:end_idx, start_idx_h:end_idx_h, start_idx_q:end_idx_q, :] if attn_mask is not None else attn_mask,
dropout_p=dropout_p, is_causal=is_causal, **kwargs
dropout_p=dropout_p, is_causal=is_causal, scale=scale, **kwargs
)
else:
hidden_states[start_idx:end_idx, start_idx_h:end_idx_h, :, :] = original_scaled_dot_product_attention(
@@ -101,7 +106,7 @@ def dynamic_scaled_dot_product_attention(query, key, value, attn_mask=None, drop
key[start_idx:end_idx, start_idx_h:end_idx_h, :, :],
value[start_idx:end_idx, start_idx_h:end_idx_h, :, :],
attn_mask=attn_mask[start_idx:end_idx, start_idx_h:end_idx_h, :, :] if attn_mask is not None else attn_mask,
dropout_p=dropout_p, is_causal=is_causal, **kwargs
dropout_p=dropout_p, is_causal=is_causal, scale=scale, **kwargs
)
else:
hidden_states[start_idx:end_idx, :, :, :] = original_scaled_dot_product_attention(
@@ -109,11 +114,11 @@ def dynamic_scaled_dot_product_attention(query, key, value, attn_mask=None, drop
key[start_idx:end_idx, :, :, :],
value[start_idx:end_idx, :, :, :],
attn_mask=attn_mask[start_idx:end_idx, :, :, :] if attn_mask is not None else attn_mask,
dropout_p=dropout_p, is_causal=is_causal, **kwargs
dropout_p=dropout_p, is_causal=is_causal, scale=scale, **kwargs
)
torch.xpu.synchronize(query.device)
else:
hidden_states = original_scaled_dot_product_attention(query, key, value, attn_mask=attn_mask, dropout_p=dropout_p, is_causal=is_causal, **kwargs)
hidden_states = original_scaled_dot_product_attention(query, key, value, attn_mask=attn_mask, dropout_p=dropout_p, is_causal=is_causal, scale=scale, **kwargs)
if is_unsqueezed:
hidden_states = hidden_states.squeeze(0)
return hidden_states
+2 -2
View File
@@ -144,8 +144,8 @@ class YoloRestorer(Detailer):
mask_image = None
w, h = box[2] - box[0], box[3] - box[1]
x_size, y_size = w/image.width, h/image.height
min_size = shared.opts.detailer_min_size if shared.opts.detailer_min_size > 0 and shared.opts.detailer_min_size < 1 else 0
max_size = shared.opts.detailer_max_size if shared.opts.detailer_max_size > 0 and shared.opts.detailer_max_size < 1 else 1
min_size = shared.opts.detailer_min_size if shared.opts.detailer_min_size >= 0 and shared.opts.detailer_min_size <= 1 else 0
max_size = shared.opts.detailer_max_size if shared.opts.detailer_max_size >= 0 and shared.opts.detailer_max_size <= 1 else 1
if x_size >= min_size and y_size >=min_size and x_size <= max_size and y_size <= max_size:
if mask:
mask_image = image.copy()
+24 -17
View File
@@ -157,7 +157,7 @@ def set_pipeline_args(p, model, prompts:list, negative_prompts:list, prompts_2:t
'StableCascade' in model.__class__.__name__ or
'Flux' in model.__class__.__name__ or
'Chroma' in model.__class__.__name__ or
'HiDreamImagePipeline' in model.__class__.__name__ # hidream-e1 has different embeds
'HiDreamImagePipeline' in model.__class__.__name__
):
try:
prompt_parser_diffusers.embedder = prompt_parser_diffusers.PromptEmbedder(prompts, negative_prompts, steps, clip_skip, p)
@@ -173,23 +173,28 @@ def set_pipeline_args(p, model, prompts:list, negative_prompts:list, prompts_2:t
if 'prompt' in possible:
if 'OmniGen' in model.__class__.__name__:
prompts = [p.replace('|image|', '<img><|image_1|></img>') for p in prompts]
if 'HiDreamImage' in model.__class__.__name__ and prompt_parser_diffusers.embedder is not None:
if ('HiDreamImage' in model.__class__.__name__) and (prompt_parser_diffusers.embedder is not None):
args['pooled_prompt_embeds'] = prompt_parser_diffusers.embedder('positive_pooleds')
prompt_embeds = prompt_parser_diffusers.embedder('prompt_embeds')
args['prompt_embeds_t5'] = prompt_embeds[0]
args['prompt_embeds_llama3'] = prompt_embeds[1]
elif hasattr(model, 'text_encoder') and hasattr(model, 'tokenizer') and 'prompt_embeds' in possible and prompt_parser_diffusers.embedder is not None:
args['prompt_embeds'] = prompt_parser_diffusers.embedder('prompt_embeds')
if 'StableCascade' in model.__class__.__name__:
args['prompt_embeds_pooled'] = prompt_parser_diffusers.embedder('positive_pooleds').unsqueeze(0)
elif 'XL' in model.__class__.__name__:
args['pooled_prompt_embeds'] = prompt_parser_diffusers.embedder('positive_pooleds')
elif 'StableDiffusion3' in model.__class__.__name__:
args['pooled_prompt_embeds'] = prompt_parser_diffusers.embedder('positive_pooleds')
elif 'Flux' in model.__class__.__name__:
args['pooled_prompt_embeds'] = prompt_parser_diffusers.embedder('positive_pooleds')
elif 'Chroma' in model.__class__.__name__:
args['prompt_attention_mask'] = prompt_parser_diffusers.embedder('prompt_attention_masks')
elif hasattr(model, 'text_encoder') and hasattr(model, 'tokenizer') and ('prompt_embeds' in possible) and (prompt_parser_diffusers.embedder is not None):
embeds = prompt_parser_diffusers.embedder('prompt_embeds')
if embeds is None:
shared.log.warning('Prompt parser encode: empty prompt embeds')
args['prompt'] = prompts
else:
args['prompt_embeds'] = embeds
if 'StableCascade' in model.__class__.__name__:
args['prompt_embeds_pooled'] = prompt_parser_diffusers.embedder('positive_pooleds').unsqueeze(0)
elif 'XL' in model.__class__.__name__:
args['pooled_prompt_embeds'] = prompt_parser_diffusers.embedder('positive_pooleds')
elif 'StableDiffusion3' in model.__class__.__name__:
args['pooled_prompt_embeds'] = prompt_parser_diffusers.embedder('positive_pooleds')
elif 'Flux' in model.__class__.__name__:
args['pooled_prompt_embeds'] = prompt_parser_diffusers.embedder('positive_pooleds')
elif 'Chroma' in model.__class__.__name__:
args['prompt_attention_mask'] = prompt_parser_diffusers.embedder('prompt_attention_masks')
else:
args['prompt'] = prompts
if 'negative_prompt' in possible:
@@ -406,11 +411,13 @@ def set_pipeline_args(p, model, prompts:list, negative_prompts:list, prompts_2:t
clean['generator'] = f'{generator[0].device}:{[g.initial_seed() for g in generator]}'
clean['parser'] = parser
for k, v in clean.copy().items():
if isinstance(v, torch.Tensor) or isinstance(v, np.ndarray):
if v is None:
clean[k] = None
elif isinstance(v, torch.Tensor) or isinstance(v, np.ndarray):
clean[k] = v.shape
if isinstance(v, list) and len(v) > 0 and (isinstance(v[0], torch.Tensor) or isinstance(v[0], np.ndarray)):
elif isinstance(v, list) and len(v) > 0 and (isinstance(v[0], torch.Tensor) or isinstance(v[0], np.ndarray)):
clean[k] = [x.shape for x in v]
if not debug_enabled and k.endswith('_embeds'):
elif not debug_enabled and k.endswith('_embeds'):
del clean[k]
clean['prompt'] = 'embeds'
task = str(sd_models.get_diffusers_task(model)).replace('DiffusersTaskType.', '')
+11 -7
View File
@@ -73,17 +73,17 @@ class PromptEmbedder:
earlyout = self.checkcache(p)
if earlyout:
return
pipe = prepare_model(p.sd_model)
if pipe is None:
self.pipe = prepare_model(p.sd_model)
if self.pipe is None:
shared.log.error("Prompt encode: cannot find text encoder in model")
return
# per prompt in batch
for batchidx, (prompt, negative_prompt) in enumerate(zip(self.prompts, self.negative_prompts)):
self.prepare_schedule(prompt, negative_prompt)
if self.scheduled_prompt:
self.scheduled_encode(pipe, batchidx)
self.scheduled_encode(self.pipe, batchidx)
else:
self.encode(pipe, prompt, negative_prompt, batchidx)
self.encode(self.pipe, prompt, negative_prompt, batchidx)
self.checkcache(p)
debug(f"Prompt encode: time={(time.time() - t0):.3f}")
@@ -223,6 +223,8 @@ class PromptEmbedder:
batch = getattr(self, key)
res = []
try:
if len(batch) == 0 or len(batch[0]) == 0:
return None # flux has no negative prompts
if isinstance(batch[0][0], list) and len(batch[0][0]) == 2 and isinstance(batch[0][0][1], torch.Tensor) and batch[0][0][1].shape[0] == 32:
# hidream uses a list of t5 + llama prompt embeds: [t5_embeds, llama_embeds]
# t5_embeds shape: [batch_size, seq_len, dim]
@@ -248,9 +250,11 @@ class PromptEmbedder:
res.append(batch[i][step])
except IndexError:
res.append(batch[i][0]) # if not scheduled, return default
if any(res[0].shape[1] != r.shape[1] for r in res):
res = pad_to_same_length(self.pipe, res)
return torch.cat(res)
except Exception:
pass
except Exception as e:
shared.log.error(f"Prompt encode: {e}")
return None
@@ -472,7 +476,7 @@ def prepare_embedding_providers(pipe, clip_skip) -> list[EmbeddingsProvider]:
def pad_to_same_length(pipe, embeds, empty_embedding_providers=None):
if not hasattr(pipe, 'encode_prompt') and 'StableCascade' not in pipe.__class__.__name__:
if not hasattr(pipe, 'encode_prompt') and ('StableCascade' not in pipe.__class__.__name__):
return embeds
device = devices.device
if shared.opts.diffusers_zeros_prompt_pad or 'StableDiffusion3' in pipe.__class__.__name__:
+8 -5
View File
@@ -54,7 +54,7 @@ def find_sdpa_slice_sizes(query_shape, key_shape, query_element_size, slice_rate
if devices.sdpa_pre_dyanmic_atten is None:
devices.sdpa_pre_dyanmic_atten = torch.nn.functional.scaled_dot_product_attention
@wraps(devices.sdpa_pre_dyanmic_atten)
def dynamic_scaled_dot_product_attention(query, key, value, attn_mask=None, dropout_p=0.0, is_causal=False, **kwargs):
def dynamic_scaled_dot_product_attention(query, key, value, attn_mask=None, dropout_p=0.0, is_causal=False, scale=None, enable_gqa=False, **kwargs):
is_unsqueezed = False
if query.dim() == 3:
query = query.unsqueeze(0)
@@ -63,6 +63,9 @@ def dynamic_scaled_dot_product_attention(query, key, value, attn_mask=None, drop
key = key.unsqueeze(0)
if value.dim() == 3:
value = value.unsqueeze(0)
if enable_gqa:
key = key.repeat_interleave(query.size(-3)//key.size(-3), -3)
value = value.repeat_interleave(query.size(-3)//value.size(-3), -3)
do_batch_split, do_head_split, do_query_split, split_batch_size, split_head_size, split_query_size = find_sdpa_slice_sizes(query.shape, key.shape, query.element_size(), slice_rate=shared.opts.dynamic_attention_slice_rate, trigger_rate=shared.opts.dynamic_attention_trigger_rate)
# Slice SDPA
@@ -88,7 +91,7 @@ def dynamic_scaled_dot_product_attention(query, key, value, attn_mask=None, drop
key[start_idx:end_idx, start_idx_h:end_idx_h, :, :],
value[start_idx:end_idx, start_idx_h:end_idx_h, :, :],
attn_mask=attn_mask[start_idx:end_idx, start_idx_h:end_idx_h, start_idx_q:end_idx_q, :] if attn_mask is not None else attn_mask,
dropout_p=dropout_p, is_causal=is_causal, **kwargs
dropout_p=dropout_p, is_causal=is_causal, scale=scale, **kwargs
)
else:
hidden_states[start_idx:end_idx, start_idx_h:end_idx_h, :, :] = devices.sdpa_pre_dyanmic_atten(
@@ -96,7 +99,7 @@ def dynamic_scaled_dot_product_attention(query, key, value, attn_mask=None, drop
key[start_idx:end_idx, start_idx_h:end_idx_h, :, :],
value[start_idx:end_idx, start_idx_h:end_idx_h, :, :],
attn_mask=attn_mask[start_idx:end_idx, start_idx_h:end_idx_h, :, :] if attn_mask is not None else attn_mask,
dropout_p=dropout_p, is_causal=is_causal, **kwargs
dropout_p=dropout_p, is_causal=is_causal, scale=scale, **kwargs
)
else:
hidden_states[start_idx:end_idx, :, :, :] = devices.sdpa_pre_dyanmic_atten(
@@ -104,12 +107,12 @@ def dynamic_scaled_dot_product_attention(query, key, value, attn_mask=None, drop
key[start_idx:end_idx, :, :, :],
value[start_idx:end_idx, :, :, :],
attn_mask=attn_mask[start_idx:end_idx, :, :, :] if attn_mask is not None else attn_mask,
dropout_p=dropout_p, is_causal=is_causal, **kwargs
dropout_p=dropout_p, is_causal=is_causal, scale=scale, **kwargs
)
if devices.backend != "directml":
getattr(torch, query.device.type).synchronize()
else:
hidden_states = devices.sdpa_pre_dyanmic_atten(query, key, value, attn_mask=attn_mask, dropout_p=dropout_p, is_causal=is_causal, **kwargs)
hidden_states = devices.sdpa_pre_dyanmic_atten(query, key, value, attn_mask=attn_mask, dropout_p=dropout_p, is_causal=is_causal, scale=scale, **kwargs)
if is_unsqueezed:
hidden_states = hidden_states.squeeze(0)
return hidden_states
+2 -2
View File
@@ -618,8 +618,8 @@ options_templates.update(options_section(('postprocessing', "Postprocessing"), {
"detailer_iou": OptionInfo(0.5, "Max overlap", gr.Slider, {"minimum": 0, "maximum": 1.0, "step": 0.05, "visible": False}),
"detailer_sigma_adjust": OptionInfo(1.0, "Detailer sigma adjust", gr.Slider, {"minimum": 0, "maximum": 1.0, "step": 0.05, "visible": False}),
"detailer_sigma_adjust_max": OptionInfo(1.0, "Detailer sigma end", gr.Slider, {"minimum": 0, "maximum": 1.0, "step": 0.05, "visible": False}),
"detailer_min_size": OptionInfo(0.0, "Min object size", gr.Slider, {"minimum": 0.1, "maximum": 1, "step": 0.05, "visible": False}),
"detailer_max_size": OptionInfo(1.0, "Max object size", gr.Slider, {"minimum": 0.1, "maximum": 1, "step": 0.05, "visible": False}),
"detailer_min_size": OptionInfo(0.0, "Min object size", gr.Slider, {"minimum": 0.0, "maximum": 1.0, "step": 0.05, "visible": False}),
"detailer_max_size": OptionInfo(1.0, "Max object size", gr.Slider, {"minimum": 0.0, "maximum": 1.0, "step": 0.05, "visible": False}),
"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}),
+3 -2
View File
@@ -174,10 +174,11 @@ def save_files(js_data, files, html_info, index):
items = infotext.parse(geninfo)
p = PObject(items)
try:
seed = p.all_seeds[i]
prompt = p.all_prompts[i]
seed = p.all_seeds[i] if i < len(p.all_seeds) else p.seed
prompt = p.all_prompts[i] if i < len(p.all_prompts) else p.prompt
fullfn, txt_fullfn, _exif = images.save_image(image, shared.opts.outdir_save, "", seed=seed, prompt=prompt, info=info, extension=shared.opts.samples_format, grid=is_grid, p=p)
except Exception as e:
fullfn, txt_fullfn = None, None
shared.log.error(f'Save: image={image} i={i} seeds={p.all_seeds} prompts={p.all_prompts}')
errors.display(e, 'save')
if fullfn is None:
+1 -1
Submodule wiki updated: 9b30cf154e...8df9dbd32e