diff --git a/CHANGELOG.md b/CHANGELOG.md index 29553c783..6f0d96240 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,11 @@ # Change Log for SD.Next +## Update for 2026-04-29 + +- **Control** + - remove "processed preview" from ui + preprocessor output can still be generated by clicking preview button in in control unit and it will render into normal output area + ## Update for 2026-04-28 ### Highlights for 2026-04-28 diff --git a/modules/control/processors.py b/modules/control/processors.py index bdf5e53c7..1f03f5594 100644 --- a/modules/control/processors.py +++ b/modules/control/processors.py @@ -386,7 +386,9 @@ class Processor: def preview(self): import modules.ui_control_helpers as helpers input_image = helpers.input_source + if input_image is None: + return [] if isinstance(input_image, list): input_image = input_image[0] debug('Control process preview') - return self.__call__(input_image) + return [self.__call__(input_image)] diff --git a/modules/control/test.py b/modules/control/test.py index 29653e41a..6627c7d04 100644 --- a/modules/control/test.py +++ b/modules/control/test.py @@ -39,7 +39,7 @@ def test_processors(image): draw.text((10, 10), processor_id, (0,0,0), font=font) draw.text((8, 8), processor_id, (255,255,255), font=font) res.append(output) - yield output, None, None, res + yield output, None, res rows = round(math.sqrt(len(res))) cols = math.ceil(len(res) / rows) w, h = 256, 256 @@ -52,8 +52,7 @@ def test_processors(image): thumb = image.copy().convert('RGB') thumb.thumbnail((w, h), Image.Resampling.HAMMING) grid.paste(thumb, box=(x, y)) - yield None, grid, None, res - return None, grid, None, res # preview_process, output_image, output_video, output_gallery + yield None, grid, res def test_controlnets(prompt, negative, image): @@ -90,7 +89,7 @@ def test_controlnets(prompt, negative, image): draw.text((10, 10), model_id, (0,0,0), font=font) draw.text((8, 8), model_id, (255,255,255), font=font) res.append(output) - yield output, None, None, res + yield output, None, res rows = round(math.sqrt(len(res))) cols = math.ceil(len(res) / rows) w, h = 256, 256 @@ -103,8 +102,7 @@ def test_controlnets(prompt, negative, image): thumb = image.copy().convert('RGB') thumb.thumbnail((w, h), Image.Resampling.HAMMING) grid.paste(thumb, box=(x, y)) - yield None, grid, None, res - return None, grid, None, res # preview_process, output_image, output_video, output_gallery + yield grid, None, res def test_adapters(prompt, negative, image): @@ -142,7 +140,7 @@ def test_adapters(prompt, negative, image): draw.text((10, 10), model_id, (0,0,0), font=font) draw.text((8, 8), model_id, (255,255,255), font=font) res.append(output) - yield output, None, None, res + yield output, None, res rows = round(math.sqrt(len(res))) cols = math.ceil(len(res) / rows) w, h = 256, 256 @@ -155,8 +153,7 @@ def test_adapters(prompt, negative, image): thumb = image.copy().convert('RGB') thumb.thumbnail((w, h), Image.Resampling.HAMMING) grid.paste(thumb, box=(x, y)) - yield None, grid, None, res - return None, grid, None, res # preview_process, output_image, output_video, output_gallery + yield grid, None, res def test_xs(prompt, negative, image): @@ -193,7 +190,7 @@ def test_xs(prompt, negative, image): draw.text((10, 10), model_id, (0,0,0), font=font) draw.text((8, 8), model_id, (255,255,255), font=font) res.append(output) - yield output, None, None, res + yield output, None, res rows = round(math.sqrt(len(res))) cols = math.ceil(len(res) / rows) w, h = 256, 256 @@ -206,8 +203,7 @@ def test_xs(prompt, negative, image): thumb = image.copy().convert('RGB') thumb.thumbnail((w, h), Image.Resampling.HAMMING) grid.paste(thumb, box=(x, y)) - yield None, grid, None, res - return None, grid, None, res # preview_process, output_image, output_video, output_gallery + yield grid, None, res def test_lite(prompt, negative, image): @@ -245,7 +241,7 @@ def test_lite(prompt, negative, image): draw.text((10, 10), model_id, (0,0,0), font=font) draw.text((8, 8), model_id, (255,255,255), font=font) res.append(output) - yield output, None, None, res + yield output, None, res rows = round(math.sqrt(len(res))) cols = math.ceil(len(res) / rows) w, h = 256, 256 @@ -258,5 +254,5 @@ def test_lite(prompt, negative, image): thumb = image.copy().convert('RGB') thumb.thumbnail((w, h), Image.Resampling.HAMMING) grid.paste(thumb, box=(x, y)) - yield None, grid, None, res - return None, grid, None, res # preview_process, output_image, output_video, output_gallery + yield grid, None, res + return grid, None, res # preview_process, output_image, output_video, output_gallery diff --git a/modules/history.py b/modules/history.py index 62166b895..f98fea1b2 100644 --- a/modules/history.py +++ b/modules/history.py @@ -80,7 +80,6 @@ class History: break current_index -= 1 if item.latent is None: - print('HERE') return None, -1 log.debug(f'History get: index={current_index} time={item.ts} shape={list(item.latent.shape)} dtype={item.latent.dtype} count={self.count}') return item.latent.to(devices.device), current_index diff --git a/modules/masking.py b/modules/masking.py index 9b5b193fa..07dce8a2e 100644 --- a/modules/masking.py +++ b/modules/masking.py @@ -290,9 +290,9 @@ def run_rembg(input_image: Image.Image, input_mask: np.ndarray): 'alpha_matting_foreground_threshold': 240, 'alpha_matting_background_threshold': 10, 'alpha_matting_erode_size': int(opts.mask_erode * 40), - 'session': rembg.new_session(opts.model), + 'session': rembg.new_session(opts.model), # pylint: disable=c-extension-no-member } - mask = rembg.remove(**args) + mask = rembg.remove(**args) # pylint: disable=c-extension-no-member mask = np.array(mask) if input_mask is None: input_mask = np.zeros(mask.shape, dtype='uint8') @@ -574,13 +574,13 @@ def create_segment_ui(): return controls -def bind_controls(image_controls: list[gr.Image], preview_image: gr.Image, output_image: gr.Image): +def bind_controls(image_controls: list[gr.Image], output_image: gr.Image): for image_control in image_controls: - btn_mask.click(run_mask, inputs=[image_control], outputs=[preview_image]) + btn_mask.click(run_mask, inputs=[image_control], outputs=[output_image]) btn_lama.click(run_lama, inputs=[image_control], outputs=[output_image]) - image_control.edit(fn=run_mask_live, inputs=[image_control], outputs=[preview_image]) + image_control.edit(fn=run_mask_live, inputs=[image_control], outputs=[output_image]) for control in controls: - control.change(fn=run_mask_live, inputs=[image_control], outputs=[preview_image]) + control.change(fn=run_mask_live, inputs=[image_control], outputs=[output_image]) def process_kanvas(kanvas_data): diff --git a/modules/ui_control.py b/modules/ui_control.py index da4784403..03e24470a 100644 --- a/modules/ui_control.py +++ b/modules/ui_control.py @@ -56,11 +56,11 @@ def return_controls(res, t: float | None = None): else: perf = return_stats(t) if res is None: # no response - return [None, None, None, None, '', perf] + return [None, None, None, '', perf] elif isinstance(res, str): # error response - return [None, None, None, None, res, perf] + return [None, None, None, res, perf] elif isinstance(res, tuple): # standard response received as tuple via control_run->yield(output_images, process_image, result_txt) - preview_image = res[1] # may be None + _preview_image = res[1] # may be None output_image = res[0][0] if isinstance(res[0], list) else res[0] # may be image or list of images if isinstance(res[0], list): output_gallery = res[0] if res[0][0] is not None else [] @@ -68,9 +68,9 @@ def return_controls(res, t: float | None = None): output_gallery = [res[0]] if res[0] is not None else [] # must return list, but can receive single image result_txt = res[2] if len(res) > 2 else '' # do we have a message output_video = res[3] if len(res) > 3 else None # do we have a video filename - return [preview_image, output_image, output_video, output_gallery, result_txt, perf] + return [output_image, output_video, output_gallery, result_txt, perf] else: # unexpected - return [None, None, None, None, f'Control: Unexpected response: {type(res)}', perf] + return [None, None, None, f'Control: Unexpected response: {type(res)}', perf] def get_units(*values): @@ -168,7 +168,6 @@ def create_ui(_blocks: gr.Blocks=None): with gr.Accordion(open=False, label="Input", elem_id="control_input", elem_classes=["small-accordion"]): with gr.Row(): show_input = gr.Checkbox(label="Show input", value=True, elem_id="control_show_input") - show_preview = gr.Checkbox(label="Show preview", value=False, elem_id="control_show_preview") with gr.Row(): input_type = gr.Radio(label="Control input type", choices=['Control only', 'Init image same as control', 'Separate init image'], value='Control only', type='index', elem_id='control_input_type') with gr.Row(): @@ -259,15 +258,9 @@ def create_ui(_blocks: gr.Blocks=None): output_image = gr.Image(label="Output", show_label=False, type="pil", interactive=False, tool="editor", height=gr_height, elem_id='control_output_image', elem_classes=['control-image']) with gr.Tab('Video', id='out-video'): output_video = gr.Video(label="Output", show_label=False, height=gr_height, elem_id='control_output_video', elem_classes=['control-image']) - with gr.Column(scale=9, elem_id='control-preview-column', visible=False) as column_preview: - gr.HTML('Preview

') - with gr.Tabs(elem_classes=['control-tabs'], elem_id='control-tab-preview'): - with gr.Tab('Preview', id='preview-image') as _tab_preview: - preview_process = gr.Image(label="Preview", show_label=False, type="pil", interactive=False, height=gr_height, visible=True, elem_id='control_preview', elem_classes=['control-image']) - from modules.ui_control_elements import create_ui_elements - create_ui_elements(units, result_txt, preview_process) + create_ui_elements(units, result_txt, output_gallery) with gr.Row(elem_id="control_script_container"): input_script_args = scripts_manager.scripts_current.setup_ui(parent='control', accordion=True) @@ -284,7 +277,6 @@ def create_ui(_blocks: gr.Blocks=None): btn_update.click(fn=get_units, inputs=controls, outputs=[], show_progress='hidden', queue=False) show_input.change(fn=lambda x: gr.update(visible=x), inputs=[show_input], outputs=[column_input]) - show_preview.change(fn=lambda x: gr.update(visible=x), inputs=[show_preview], outputs=[column_preview]) input_type.change(fn=lambda x: gr.update(visible=x == 2), inputs=[input_type], outputs=[column_init]) btn_prompt_counter.click( fn=call_queue.wrap_queued_call(ui_common.update_token_counter), @@ -303,7 +295,7 @@ def create_ui(_blocks: gr.Blocks=None): fn=helpers.select_input, _js="controlInputMode", inputs=[input_mode, input_image, init_image, input_type, input_video, input_batch, input_folder], - outputs=[output_tabs, preview_process, result_txt, width_before, height_before], + outputs=[output_tabs, result_txt, width_before, height_before], show_progress='hidden', queue=False, ) @@ -345,7 +337,6 @@ def create_ui(_blocks: gr.Blocks=None): override_script_name, override_script_args, override_settings, ] output_fields = [ - preview_process, output_image, output_video, output_gallery, @@ -470,9 +461,9 @@ def create_ui(_blocks: gr.Blocks=None): generation_parameters_copypaste.register_paste_params_button(bindings) if (installer.version['kanvas'] == 'disabled') or (installer.version['kanvas'] == 'unavailable'): - masking.bind_controls([input_image], preview_process, output_image) + masking.bind_controls([input_image], output_image) else: - masking.bind_kanvas(input_image, preview_process) + masking.bind_kanvas(input_image, output_image) if os.environ.get('SD_CONTROL_DEBUG', None) is not None: # debug only from modules.control.test import test_processors, test_controlnets, test_adapters, test_xs, test_lite @@ -484,11 +475,11 @@ def create_ui(_blocks: gr.Blocks=None): run_test_adapters_btn = gr.Button(value="Test:Adapters", variant='primary', elem_classes=['control-button']) run_test_lite_btn = gr.Button(value="Test:Control-LLLite", variant='primary', elem_classes=['control-button']) - run_test_processors_btn.click(fn=test_processors, inputs=[input_image], outputs=[preview_process, output_image, output_video, output_gallery]) - run_test_controlnets_btn.click(fn=test_controlnets, inputs=[prompt, negative, input_image], outputs=[preview_process, output_image, output_video, output_gallery]) - run_test_xs_btn.click(fn=test_xs, inputs=[prompt, negative, input_image], outputs=[preview_process, output_image, output_video, output_gallery]) - run_test_adapters_btn.click(fn=test_adapters, inputs=[prompt, negative, input_image], outputs=[preview_process, output_image, output_video, output_gallery]) - run_test_lite_btn.click(fn=test_lite, inputs=[prompt, negative, input_image], outputs=[preview_process, output_image, output_video, output_gallery]) + run_test_processors_btn.click(fn=test_processors, inputs=[input_image], outputs=[output_image, output_video, output_gallery]) + run_test_controlnets_btn.click(fn=test_controlnets, inputs=[prompt, negative, input_image], outputs=[output_image, output_video, output_gallery]) + run_test_xs_btn.click(fn=test_xs, inputs=[prompt, negative, input_image], outputs=[output_image, output_video, output_gallery]) + run_test_adapters_btn.click(fn=test_adapters, inputs=[prompt, negative, input_image], outputs=[output_image, output_video, output_gallery]) + run_test_lite_btn.click(fn=test_lite, inputs=[prompt, negative, input_image], outputs=[output_image, output_video, output_gallery]) ui_extra_networks.setup_ui(extra_networks_ui, output_gallery) return [(control_ui, 'Control', 'control')] diff --git a/modules/ui_control_elements.py b/modules/ui_control_elements.py index 6c207087a..cc4579940 100644 --- a/modules/ui_control_elements.py +++ b/modules/ui_control_elements.py @@ -11,7 +11,7 @@ from modules import shared, ui_components, ui_symbols, ui_common, masking # pyli from modules import ui_control_helpers as helpers -def create_ui_elements(units, result_txt, preview_process): +def create_ui_elements(units, result_txt, output_gallery): max_units = shared.opts.control_max_units with gr.Accordion('Control elements', open=False, elem_id="control_elements"): with gr.Tabs(elem_id='control-tabs') as _tabs_control_type: @@ -54,7 +54,7 @@ def create_ui_elements(units, result_txt, preview_process): process_id = process_id, model_id = model_id, model_strength = model_strength, - preview_process = preview_process, + preview_process = output_gallery, preview_btn = preview_btn, image_upload = image_upload, image_reuse = image_reuse, @@ -103,7 +103,7 @@ def create_ui_elements(units, result_txt, preview_process): process_id = process_id, model_id = model_id, model_strength = model_strength, - preview_process = preview_process, + preview_process = output_gallery, preview_btn = btn_preview, image_upload = image_upload, image_reuse = image_reuse, @@ -150,7 +150,7 @@ def create_ui_elements(units, result_txt, preview_process): process_id = process_id, model_id = model_id, model_strength = model_strength, - preview_process = preview_process, + preview_process = output_gallery, preview_btn = btn_preview, image_upload = image_upload, image_reuse = image_reuse, @@ -196,7 +196,7 @@ def create_ui_elements(units, result_txt, preview_process): process_id = process_id, model_id = model_id, model_strength = model_strength, - preview_process = preview_process, + preview_process = output_gallery, preview_btn = btn_preview, image_upload = image_upload, image_reuse = image_reuse, @@ -239,7 +239,7 @@ def create_ui_elements(units, result_txt, preview_process): process_id = process_id, model_id = model_id, model_strength = model_strength, - preview_process = preview_process, + preview_process = output_gallery, preview_btn = btn_preview, image_upload = image_upload, image_reuse = image_reuse, diff --git a/modules/ui_control_helpers.py b/modules/ui_control_helpers.py index 36f63d337..a789ecad1 100644 --- a/modules/ui_control_helpers.py +++ b/modules/ui_control_helpers.py @@ -146,17 +146,17 @@ def select_input(input_mode, input_image, init_image, init_type, input_video, in if selected_input is None: # log.debug(f'Select input: image={selected_input}') input_source = None - return [gr.Tabs.update(), None, ''] + size + return [gr.Tabs.update(), ''] + size elif selected_input == input_prev: # log.debug(f'Select input: image={selected_input} no change') - return [gr.Tabs.update(), None, ''] + size + return [gr.Tabs.update(), ''] + size input_prev = selected_input busy = True input_type = type(selected_input) input_mask = None status = 'Control input | Unknown' - res = [gr.Tabs.update(selected='out-gallery'), input_mask, status] + res = [gr.Tabs.update(selected='out-gallery'), status] # control inputs if isinstance(selected_input, Image.Image): # image via upload -> image if input_mode == 'Outpaint': @@ -166,7 +166,7 @@ def select_input(input_mode, input_image, init_image, init_type, input_video, in input_type = 'PIL.Image' status = f'Control input | Image | Size {selected_input.width if selected_input else 0}x{selected_input.height if selected_input else 0} | Mode {selected_input.mode if selected_input else "Unknown"}' size = [gr.update(value=selected_input.width), gr.update(value=selected_input.height)] - res = [gr.Tabs.update(selected='out-gallery'), input_mask, status] + res = [gr.Tabs.update(selected='out-gallery'), status] elif isinstance(selected_input, dict) and 'kanvas' in selected_input: # kanvas via js -> kanvas dict selected_input, input_mask = process_kanvas(selected_input) input_source = [selected_input] @@ -174,23 +174,23 @@ def select_input(input_mode, input_image, init_image, init_type, input_video, in status = f'Control input | Kanvas | Size {selected_input.width if selected_input else 0}x{selected_input.height if selected_input else 0} | Mode {selected_input.mode if selected_input else "Unknown"}' if selected_input: size = [gr.update(value=selected_input.width), gr.update(value=selected_input.height)] - res = [gr.Tabs.update(selected='out-gallery'), input_mask, status] + res = [gr.Tabs.update(selected='out-gallery'), status] elif isinstance(selected_input, dict) and 'mask' in selected_input: # inpaint -> dict image+mask input_mask = selected_input['mask'] selected_input = selected_input['image'] input_source = [selected_input] input_type = 'PIL.Image' status = f'Control input | Image | Size {selected_input.width if selected_input else 0}x{selected_input.height if selected_input else 0} | Mode {selected_input.mode if selected_input else "Unknown"}' - res = [gr.Tabs.update(selected='out-gallery'), input_mask, status] + res = [gr.Tabs.update(selected='out-gallery'), status] elif isinstance(selected_input, gr.components.image.Image): # not likely input_source = [selected_input.value] input_type = 'gr.Image' - res = [gr.Tabs.update(selected='out-gallery'), input_mask, status] + res = [gr.Tabs.update(selected='out-gallery'), status] elif isinstance(selected_input, str) and os.path.exists(selected_input): # video via upload > tmp filepath to video input_source = selected_input input_type = 'gr.Video' status = get_video(input_source) - res = [gr.Tabs.update(selected='out-video'), input_mask, status] + res = [gr.Tabs.update(selected='out-video'), status] elif isinstance(selected_input, list): # batch or folder via upload -> list of tmp filepaths if hasattr(selected_input[0], 'name'): input_type = 'tempfiles' @@ -199,7 +199,7 @@ def select_input(input_mode, input_image, init_image, init_type, input_video, in input_type = 'files' input_source = selected_input status = f'Control input | Images | Files {len(input_source)}' - res = [gr.Tabs.update(selected='out-gallery'), input_mask, status] + res = [gr.Tabs.update(selected='out-gallery'), status] else: # unknown input_source = None if init_type == 0: # Control only diff --git a/wiki b/wiki index 070218735..890415e99 160000 --- a/wiki +++ b/wiki @@ -1 +1 @@ -Subproject commit 07021873564383be3456b673ad27693db31a8d9a +Subproject commit 890415e996c14738eab02524f11a675f9b5a56b4