qwen multi-image edits

Signed-off-by: Vladimir Mandic <mandic00@live.com>
This commit is contained in:
Vladimir Mandic
2025-10-04 18:06:22 -04:00
parent 4257ed7c40
commit c530167cbe
7 changed files with 37 additions and 29 deletions
+8 -1
View File
@@ -1,6 +1,6 @@
# Change Log for SD.Next
## Update for 2025-10-03
## Update for 2025-10-04
- **Models**
- [WAN 2.2 14B VACE](https://huggingface.co/alibaba-pai/Wan2.2-VACE-Fun-A14B)
@@ -18,6 +18,11 @@
note that nunchaku optimized and prequantized unet is replacement for base unet, so its only applicable to base models, not any of finetunes
*how to use*: enable nunchaku in settings -> quantization and then load either sdxl-base or sdxl-base-turbo reference models
- **Features**
- [Qwen Image-Edit] multi-image editing
requires qwen-image-edit-2509 or its variant as multi-image edits are not available in original qwen-image
in ui control tab: inputs -> separate init image
add image for *input media* and *control media*
can be
- [Cache-DiT](https://github.com/vipshop/cache-dit)
cache-dit is a unified, flexible and training-free cache acceleration framework
compatible with many dit-based models such as FLUX.1, Qwen, HunyuanImage, Wan2.2, Chroma, etc.
@@ -87,6 +92,8 @@
- **modular guiders**: automatically used for compatible pipelines when *modular pipelines* is enabled
allows for using many different guidance methods:
*CFG, CFGZero, PAG, APG, SLG, SEG, TCFG, FDG*
- **Wiki**
- updates to *AMD-ROCm, ZLUDA, LoRA, DirectML, SDNQ* pages
- **Fixes**
- **Microsoft Florence 2** both base and large variants
*note* this will trigger download of the new variant of the model, feel free to delete older variant in `huggingface` folder
+2 -6
View File
@@ -2,13 +2,9 @@ function controlInputMode(inputMode, ...args) {
const updateEl = gradioApp().getElementById('control_update');
if (updateEl) updateEl.click();
const tab = gradioApp().querySelector('#control-tab-input button.selected');
if (!tab) return ['Select', ...args];
if (!tab) return ['Image', ...args];
inputMode = tab.innerText;
if (inputMode === 'Image') {
if (!gradioApp().getElementById('control_input_select').classList.contains('hidden')) inputMode = 'Select';
else if (!gradioApp().getElementById('control_input_resize').classList.contains('hidden')) inputMode = 'Outpaint';
else if (!gradioApp().getElementById('control_input_inpaint').classList.contains('hidden')) inputMode = 'Inpaint';
}
console.log('HERE0', tab, inputMode);
return [inputMode, ...args];
}
+1
View File
@@ -388,6 +388,7 @@ def control_run(state: str = '', # pylint: disable=keyword-arg-before-vararg
)
p.state = state
p.is_tile = False
p.init_control = inits or []
p.orig_init_images = inputs
# TODO modernui: monkey-patch for missing tabs.select event
+9 -7
View File
@@ -123,9 +123,11 @@ def task_specific_kwargs(p, model):
}
# model specific args
if 'QwenImageEdit' in model_cls and len(getattr(p, 'init_images', [])) == 0:
if 'QwenImageEdit' in model_cls and (p.init_images is None or len(p.init_images) == 0):
task_args['image'] = [Image.new('RGB', (p.width, p.height), (0, 0, 0))] # monkey-patch so qwen-image-edit pipeline does not error-out on t2i
if 'LatentConsistencyModelPipeline' in model_cls and hasattr(p, 'init_images') and len(p.init_images) > 0:
if 'QwenImageEditPlusPipeline' in model_cls and p.init_control is not None and len(p.init_control) > 0:
task_args['image'] += p.init_control
if 'LatentConsistencyModelPipeline' in model_cls and len(p.init_images) > 0:
p.ops.append('lcm')
init_latents = [processing_vae.vae_encode(image, model=shared.sd_model, vae_type=p.vae_type).squeeze(dim=0) for image in p.init_images]
init_latent = torch.stack(init_latents, dim=0).to(shared.device)
@@ -133,11 +135,11 @@ def task_specific_kwargs(p, model):
init_latent = (1 - p.denoising_strength) * init_latent + init_noise
task_args = {
'latents': init_latent.to(model.dtype),
'width': p.width if hasattr(p, 'width') else None,
'height': p.height if hasattr(p, 'height') else None,
'width': p.width,
'height': p.height,
}
if 'BlipDiffusionPipeline' in model_cls:
if len(getattr(p, 'init_images', [])) == 0:
if len(p.init_images) == 0:
shared.log.error('BLiP diffusion requires init image')
return task_args
task_args = {
@@ -146,9 +148,9 @@ def task_specific_kwargs(p, model):
'target_subject_category': getattr(p, 'prompt', '').split()[-1],
'output_type': 'pil',
}
if ('WanImageToVideoPipeline' in model_cls) and (getattr(p, 'init_images', None) is not None) and (len(p.init_images) > 0):
if ('WanImageToVideoPipeline' in model_cls) and (p.init_images is not None) and (len(p.init_images) > 0):
task_args['image'] = p.init_images[0]
if ('WanVACEPipeline' in model_cls) and (getattr(p, 'init_images', None) is not None) and (len(p.init_images) > 0):
if ('WanVACEPipeline' in model_cls) and (p.init_images is not None) and (len(p.init_images) > 0):
task_args['reference_images'] = p.init_images
if debug_enabled:
+3 -1
View File
@@ -78,7 +78,8 @@ class StableDiffusionProcessing:
hdr_color_picker: str = None,
hdr_tint_ratio: float = 0,
# img2img
init_images: list = None,
init_images: list = [],
init_control: list = [],
denoising_strength: float = 0.3,
image_cfg_scale: float = None,
initial_noise_multiplier: float = None, # pylint: disable=unused-argument # a1111 compatibility
@@ -214,6 +215,7 @@ class StableDiffusionProcessing:
self.detailer_resolution = detailer_resolution
self.restore_faces = restore_faces
self.init_images = init_images
self.init_control = init_control
self.resize_mode = resize_mode
self.resize_name = resize_name
self.resize_context = resize_context
+9 -9
View File
@@ -183,14 +183,14 @@ def create_ui(_blocks: gr.Blocks=None):
with gr.Column(scale=9, elem_id='control-input-column', visible=True) as column_input:
gr.HTML('<span id="control-input-button">Input</p>')
with gr.Tabs(elem_classes=['control-tabs'], elem_id='control-tab-input'):
input_mode = gr.Label(value='select', visible=False)
with gr.Tab('Image', id='in-image') as tab_image:
input_mode = gr.Label(value='select', visible=False)
input_image = gr.Image(label="Input", show_label=False, type="pil", interactive=True, tool="editor", height=gr_height, visible=True, image_mode='RGB', elem_id='control_input_select', elem_classes=['control-image'])
input_resize = gr.Image(label="Input", show_label=False, type="pil", interactive=True, tool="select", height=gr_height, visible=False, image_mode='RGB', elem_id='control_input_resize', elem_classes=['control-image'])
input_inpaint = gr.Image(label="Input", show_label=False, type="pil", interactive=True, tool="sketch", height=gr_height, visible=False, image_mode='RGB', elem_id='control_input_inpaint', brush_radius=32, mask_opacity=0.6, elem_classes=['control-image'])
input_image = gr.Image(label="Input", show_label=False, type="pil", interactive=True, tool="editor", height=gr_height, image_mode='RGB', elem_id='control_input_select', elem_classes=['control-image'])
btn_interrogate = ui_sections.create_interrogate_button('control', what='input')
with gr.Row():
input_buttons = [gr.Button('Select', visible=True, interactive=False), gr.Button('Inpaint', visible=True, interactive=True), gr.Button('Outpaint', visible=True, interactive=True)]
with gr.Tab('Inpaint', id='in-inpaint') as _tab_inpaint:
input_inpaint = gr.Image(label="Input", show_label=False, type="pil", interactive=True, tool="sketch", height=gr_height, image_mode='RGB', elem_id='control_input_inpaint', brush_radius=32, mask_opacity=0.6, elem_classes=['control-image'])
with gr.Tab('Outpaint', id='in-outpaint') as _tab_outpaint:
input_resize = gr.Image(label="Input", show_label=False, type="pil", interactive=True, tool="select", height=gr_height, image_mode='RGB', elem_id='control_input_resize', elem_classes=['control-image'])
with gr.Tab('Video', id='in-video') as tab_video:
input_video = gr.Video(label="Input", show_label=False, interactive=True, height=gr_height, elem_classes=['control-image'])
with gr.Tab('Batch', id='in-batch') as tab_batch:
@@ -231,9 +231,9 @@ def create_ui(_blocks: gr.Blocks=None):
input_script_args = scripts_manager.scripts_current.setup_ui(parent='control', accordion=True)
# handlers
for btn in input_buttons:
btn.click(fn=helpers.copy_input, inputs=[input_mode, btn, input_image, input_resize, input_inpaint], outputs=[input_image, input_resize, input_inpaint], _js='controlInputMode')
btn.click(fn=helpers.transfer_input, inputs=[btn], outputs=[input_image, input_resize, input_inpaint] + input_buttons)
# for btn in input_buttons:
# btn.click(fn=helpers.copy_input, inputs=[input_mode, btn, input_image, input_resize, input_inpaint], outputs=[input_image, input_resize, input_inpaint], _js='controlInputMode')
# btn.click(fn=helpers.transfer_input, inputs=[btn], outputs=[input_image, input_resize, input_inpaint] + input_buttons)
# hidden button to update gradio control values
for u in units:
+5 -5
View File
@@ -77,7 +77,7 @@ def get_video(filepath: str):
def select_input(input_mode, input_image, init_image, init_type, input_resize, input_inpaint, input_video, input_batch, input_folder):
global busy, input_source, input_init, input_mask # pylint: disable=global-statement
busy = True
if input_mode == 'Select':
if input_mode == 'Image':
selected_input = input_image
elif input_mode == 'Outpaint':
selected_input = input_resize
@@ -159,16 +159,16 @@ def copy_input(mode_from, mode_to, input_image, input_resize, input_inpaint):
if mode_from == mode_to:
return [gr.update(), gr.update(), gr.update()]
elif mode_to == 'Select':
elif mode_to == 'Image':
return [getimg(input_resize) if mode_from == 'Outpaint' else getimg(input_inpaint), None, None]
elif mode_to == 'Inpaint':
return [None, None, getimg(input_image) if mode_from == 'Select' else getimg(input_resize)]
return [None, None, getimg(input_image) if mode_from == 'Image' else getimg(input_resize)]
elif mode_to == 'Outpaint':
return [None, getimg(input_image) if mode_from == 'Select' else getimg(input_inpaint), None]
return [None, getimg(input_image) if mode_from == 'Image' else getimg(input_inpaint), None]
else:
shared.log.error(f'Control transfer unknown input: from={mode_from} to={mode_to}')
return [gr.update(), gr.update(), gr.update()]
def transfer_input(dst):
return [gr.update(visible=dst=='Select'), gr.update(visible=dst=='Outpaint'), gr.update(visible=dst=='Inpaint'), gr.update(interactive=dst!='Select'), gr.update(interactive=dst!='Inpaint'), gr.update(interactive=dst!='Outpaint')]
return [gr.update(visible=dst=='Image'), gr.update(visible=dst=='Outpaint'), gr.update(visible=dst=='Inpaint'), gr.update(interactive=dst!='Image'), gr.update(interactive=dst!='Inpaint'), gr.update(interactive=dst!='Outpaint')]