RioShiina commited on
Commit
dc0cea5
·
verified ·
1 Parent(s): 32997dd

Upload folder using huggingface_hub

Browse files
Files changed (31) hide show
  1. chain_injectors/pid_injector.py +278 -0
  2. core/pipelines/pipeline_input_processor.py +334 -0
  3. core/pipelines/sd_image_pipeline.py +253 -618
  4. core/pipelines/workflow_executor.py +110 -0
  5. core/pipelines/workflow_recipes/_partials/conditioning/anima.yaml +4 -0
  6. core/pipelines/workflow_recipes/_partials/conditioning/chroma1.yaml +4 -0
  7. core/pipelines/workflow_recipes/_partials/conditioning/ernie-image.yaml +4 -0
  8. core/pipelines/workflow_recipes/_partials/conditioning/flux1.yaml +4 -0
  9. core/pipelines/workflow_recipes/_partials/conditioning/flux2-kv.yaml +4 -0
  10. core/pipelines/workflow_recipes/_partials/conditioning/flux2.yaml +4 -0
  11. core/pipelines/workflow_recipes/_partials/conditioning/hidream-i1.yaml +4 -0
  12. core/pipelines/workflow_recipes/_partials/conditioning/lens.yaml +4 -0
  13. core/pipelines/workflow_recipes/_partials/conditioning/longcat-image.yaml +4 -0
  14. core/pipelines/workflow_recipes/_partials/conditioning/lumina.yaml +4 -0
  15. core/pipelines/workflow_recipes/_partials/conditioning/newbie-image.yaml +4 -0
  16. core/pipelines/workflow_recipes/_partials/conditioning/omnigen2.yaml +4 -0
  17. core/pipelines/workflow_recipes/_partials/conditioning/ovis-image.yaml +4 -0
  18. core/pipelines/workflow_recipes/_partials/conditioning/qwen-image.yaml +4 -0
  19. core/pipelines/workflow_recipes/_partials/conditioning/sd35.yaml +4 -0
  20. core/pipelines/workflow_recipes/_partials/conditioning/sdxl.yaml +4 -0
  21. core/pipelines/workflow_recipes/_partials/conditioning/z-image.yaml +4 -0
  22. requirements.txt +2 -2
  23. ui/events/change_handlers.py +5 -3
  24. ui/events/main.py +8 -3
  25. ui/events/run_handlers.py +4 -1
  26. ui/shared/txt2img_ui.py +3 -1
  27. ui/shared/ui_components.py +19 -2
  28. yaml/file_list.yaml +21 -0
  29. yaml/image_gen_features.yaml +100 -89
  30. yaml/injectors.yaml +4 -1
  31. yaml/pid.yaml +16 -0
chain_injectors/pid_injector.py ADDED
@@ -0,0 +1,278 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import os
2
+ import yaml
3
+ import random
4
+
5
+ def load_pid_config():
6
+ project_root = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
7
+ pid_path = os.path.join(project_root, 'yaml', 'pid.yaml')
8
+ with open(pid_path, 'r', encoding='utf-8') as f:
9
+ return yaml.safe_load(f) or {}
10
+
11
+ def load_model_config():
12
+ project_root = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
13
+ model_list_path = os.path.join(project_root, 'yaml', 'model_list.yaml')
14
+ with open(model_list_path, 'r', encoding='utf-8') as f:
15
+ return yaml.safe_load(f) or {}
16
+
17
+ def inject(assembler, chain_definition, chain_items):
18
+ if not chain_items:
19
+ return
20
+
21
+ pid_config = {}
22
+ try:
23
+ pid_config = load_pid_config() or {}
24
+ except Exception as e:
25
+ print(f"Error loading PiD config: {e}")
26
+
27
+ pid_items = pid_config.get("PiD", [])
28
+ architectures_settings = {}
29
+ default_settings = {"unet_name": "pid_flux1_1024_to_4096_4step_mxfp8.safetensors", "latent_format": "flux"}
30
+
31
+ for item in pid_items:
32
+ unet_name = item.get("filepath")
33
+ latent_format = item.get("latent_format")
34
+ archs = item.get("architectures", [])
35
+ for arch in archs:
36
+ architectures_settings[arch] = {
37
+ "unet_name": unet_name,
38
+ "latent_format": latent_format
39
+ }
40
+ if arch == "flux1":
41
+ default_settings = {
42
+ "unet_name": unet_name,
43
+ "latent_format": latent_format
44
+ }
45
+
46
+ ksampler_name = chain_definition.get('ksampler_node', 'ksampler')
47
+ if ksampler_name not in assembler.node_map:
48
+ print(f"Warning: [PiD Injector] KSampler node '{ksampler_name}' not found. Skipping.")
49
+ return
50
+
51
+ original_ksampler_id = assembler.node_map[ksampler_name]
52
+
53
+ original_vae_loader_id = assembler.node_map.get('vae_loader')
54
+ original_vae_decode_id = assembler.node_map.get('vae_decode')
55
+ original_pos_prompt_id = assembler.node_map.get('pos_prompt')
56
+ original_neg_prompt_id = assembler.node_map.get('neg_prompt')
57
+
58
+ if not original_vae_loader_id:
59
+ for node_id, node_data in assembler.workflow.items():
60
+ if node_data.get('class_type') == 'VAELoader':
61
+ original_vae_loader_id = node_id
62
+ break
63
+
64
+ if not original_vae_decode_id:
65
+ for node_id, node_data in assembler.workflow.items():
66
+ if node_data.get('class_type') == 'VAEDecode':
67
+ original_vae_decode_id = node_id
68
+ break
69
+
70
+ if not original_pos_prompt_id or not original_neg_prompt_id:
71
+ for node_id, node_data in assembler.workflow.items():
72
+ if node_data.get('class_type') == 'CLIPTextEncode':
73
+ title = node_data.get('_meta', {}).get('title', '')
74
+ if 'Positive' in title:
75
+ if not original_pos_prompt_id:
76
+ original_pos_prompt_id = node_id
77
+ elif 'Negative' in title:
78
+ if not original_neg_prompt_id:
79
+ original_neg_prompt_id = node_id
80
+
81
+ pos_text = ""
82
+ if original_pos_prompt_id and original_pos_prompt_id in assembler.workflow:
83
+ pos_text = assembler.workflow[original_pos_prompt_id]['inputs'].get('text', '')
84
+
85
+ neg_text = ""
86
+ if original_neg_prompt_id and original_neg_prompt_id in assembler.workflow:
87
+ neg_text = assembler.workflow[original_neg_prompt_id]['inputs'].get('text', '')
88
+
89
+ clip_loader_id = assembler._get_unique_id()
90
+ clip_loader_node = assembler._get_node_template("CLIPLoader")
91
+ clip_loader_node['inputs']['clip_name'] = "gemma_2_2b_it_elm_fp8_scaled.safetensors"
92
+ clip_loader_node['inputs']['type'] = "pixeldit"
93
+ clip_loader_node['inputs']['device'] = "default"
94
+ assembler.workflow[clip_loader_id] = clip_loader_node
95
+
96
+ pos_text_encode_id = assembler._get_unique_id()
97
+ pos_text_encode_node = assembler._get_node_template("CLIPTextEncode")
98
+ pos_text_encode_node['inputs']['text'] = pos_text
99
+ pos_text_encode_node['inputs']['clip'] = [clip_loader_id, 0]
100
+ assembler.workflow[pos_text_encode_id] = pos_text_encode_node
101
+
102
+ neg_text_encode_id = assembler._get_unique_id()
103
+ neg_text_encode_node = assembler._get_node_template("CLIPTextEncode")
104
+ neg_text_encode_node['inputs']['text'] = neg_text
105
+ neg_text_encode_node['inputs']['clip'] = [clip_loader_id, 0]
106
+ assembler.workflow[neg_text_encode_id] = neg_text_encode_node
107
+
108
+ active_model_file = None
109
+ for node_id, node_data in assembler.workflow.items():
110
+ class_type = node_data.get('class_type')
111
+ if class_type == 'UNETLoader':
112
+ active_model_file = node_data.get('inputs', {}).get('unet_name')
113
+ if active_model_file:
114
+ break
115
+ elif class_type == 'CheckpointLoaderSimple':
116
+ active_model_file = node_data.get('inputs', {}).get('ckpt_name')
117
+ if active_model_file:
118
+ break
119
+
120
+ architecture = None
121
+ if active_model_file:
122
+ try:
123
+ model_config = load_model_config()
124
+ checkpoints = model_config.get("Checkpoints", {})
125
+ for arch_name, arch_data in checkpoints.items():
126
+ models_list = arch_data.get("models", [])
127
+ for model_entry in models_list:
128
+ if model_entry.get('path') == active_model_file:
129
+ architecture = arch_name
130
+ break
131
+ components_dict = model_entry.get('components', {})
132
+ if active_model_file in components_dict.values():
133
+ architecture = arch_name
134
+ break
135
+ if architecture:
136
+ break
137
+ except Exception as e:
138
+ print(f"Error looking up model architecture in PiD injector: {e}")
139
+
140
+ if architecture:
141
+ architecture = architecture.lower().replace(" ", "-").replace(".", "")
142
+ else:
143
+ file_lower = active_model_file.lower().replace("-", "").replace("_", "").replace(".", "")
144
+ for arch in sorted(architectures_settings.keys(), key=len, reverse=True):
145
+ candidates = [arch]
146
+ if "-image" in arch:
147
+ candidates.append(arch.replace("-image", ""))
148
+ if "-i1" in arch:
149
+ candidates.append(arch.replace("-i1", ""))
150
+ if "-kv" in arch:
151
+ candidates.append(arch.replace("-kv", ""))
152
+
153
+ matched = False
154
+ for cand in candidates:
155
+ if cand.replace("-", "").replace(".", "") in file_lower:
156
+ architecture = arch
157
+ matched = True
158
+ break
159
+ if matched:
160
+ break
161
+
162
+ unet_name = default_settings.get("unet_name")
163
+ latent_format = default_settings.get("latent_format")
164
+
165
+ if architecture in architectures_settings:
166
+ arch_config = architectures_settings[architecture]
167
+ unet_name = arch_config.get("unet_name", unet_name)
168
+ latent_format = arch_config.get("latent_format", latent_format)
169
+ else:
170
+ print(f"[PiD Injector] Warning: Model architecture '{architecture}' (file: '{active_model_file}') not explicitly mapped. Using default settings.")
171
+
172
+ pid_pos_id = assembler._get_unique_id()
173
+ pid_pos_node = assembler._get_node_template("PiDConditioning")
174
+ pid_pos_node['inputs']['latent_format'] = latent_format
175
+ pid_pos_node['inputs']['degrade_sigma'] = 0
176
+ pid_pos_node['inputs']['positive'] = [pos_text_encode_id, 0]
177
+ pid_pos_node['inputs']['latent'] = [original_ksampler_id, 0]
178
+ assembler.workflow[pid_pos_id] = pid_pos_node
179
+
180
+ pid_neg_id = assembler._get_unique_id()
181
+ pid_neg_node = assembler._get_node_template("PiDConditioning")
182
+ pid_neg_node['inputs']['latent_format'] = latent_format
183
+ pid_neg_node['inputs']['degrade_sigma'] = 0
184
+ pid_neg_node['inputs']['positive'] = [neg_text_encode_id, 0]
185
+ pid_neg_node['inputs']['latent'] = [original_ksampler_id, 0]
186
+ assembler.workflow[pid_neg_id] = pid_neg_node
187
+
188
+ pid_unet_loader_id = assembler._get_unique_id()
189
+ pid_unet_loader_node = assembler._get_node_template("UNETLoader")
190
+ pid_unet_loader_node['inputs']['unet_name'] = unet_name
191
+ pid_unet_loader_node['inputs']['weight_dtype'] = "default"
192
+ assembler.workflow[pid_unet_loader_id] = pid_unet_loader_node
193
+
194
+ orig_width = 1024
195
+ orig_height = 1024
196
+ original_latent_source_id = assembler.node_map.get('latent_source')
197
+ if original_latent_source_id in assembler.workflow:
198
+ node_inputs = assembler.workflow[original_latent_source_id].get('inputs', {})
199
+ if 'width' in node_inputs and 'height' in node_inputs:
200
+ orig_width = node_inputs['width']
201
+ orig_height = node_inputs['height']
202
+ else:
203
+ for node_data in assembler.workflow.values():
204
+ inputs = node_data.get('inputs', {})
205
+ if 'width' in inputs and 'height' in inputs and isinstance(inputs['width'], (int, float)) and isinstance(inputs['height'], (int, float)):
206
+ if 256 <= inputs['width'] <= 4096 and 256 <= inputs['height'] <= 4096:
207
+ orig_width = inputs['width']
208
+ orig_height = inputs['height']
209
+ break
210
+ else:
211
+ for node_data in assembler.workflow.values():
212
+ inputs = node_data.get('inputs', {})
213
+ if 'width' in inputs and 'height' in inputs and isinstance(inputs['width'], (int, float)) and isinstance(inputs['height'], (int, float)):
214
+ if 256 <= inputs['width'] <= 4096 and 256 <= inputs['height'] <= 4096:
215
+ orig_width = inputs['width']
216
+ orig_height = inputs['height']
217
+ break
218
+
219
+ empty_latent_id = assembler._get_unique_id()
220
+ empty_latent_node = assembler._get_node_template("EmptyChromaRadianceLatentImage")
221
+ empty_latent_node['inputs']['width'] = int(orig_width) * 4
222
+ empty_latent_node['inputs']['height'] = int(orig_height) * 4
223
+ empty_latent_node['inputs']['batch_size'] = 1
224
+
225
+ if original_latent_source_id in assembler.workflow:
226
+ orig_batch_size = assembler.workflow[original_latent_source_id]['inputs'].get('batch_size') or assembler.workflow[original_latent_source_id]['inputs'].get('amount')
227
+ if orig_batch_size:
228
+ empty_latent_node['inputs']['batch_size'] = orig_batch_size
229
+
230
+ assembler.workflow[empty_latent_id] = empty_latent_node
231
+
232
+ orig_seed = 0
233
+ if original_ksampler_id in assembler.workflow:
234
+ orig_seed = assembler.workflow[original_ksampler_id]['inputs'].get('seed', 0)
235
+ if orig_seed == -1:
236
+ orig_seed = random.randint(0, 2**32 - 1)
237
+ else:
238
+ orig_seed = (orig_seed + 1) % (2**32)
239
+
240
+ new_ksampler_id = assembler._get_unique_id()
241
+ new_ksampler_node = assembler._get_node_template("KSampler")
242
+ new_ksampler_node['inputs']['seed'] = orig_seed
243
+ new_ksampler_node['inputs']['steps'] = 4
244
+ new_ksampler_node['inputs']['cfg'] = 1
245
+ new_ksampler_node['inputs']['sampler_name'] = "lcm"
246
+ new_ksampler_node['inputs']['scheduler'] = "simple"
247
+ new_ksampler_node['inputs']['denoise'] = 1.0
248
+ new_ksampler_node['inputs']['model'] = [pid_unet_loader_id, 0]
249
+ new_ksampler_node['inputs']['positive'] = [pid_pos_id, 0]
250
+ new_ksampler_node['inputs']['negative'] = [pid_neg_id, 0]
251
+ new_ksampler_node['inputs']['latent_image'] = [empty_latent_id, 0]
252
+ assembler.workflow[new_ksampler_id] = new_ksampler_node
253
+
254
+ pid_vae_loader_id = assembler._get_unique_id()
255
+ pid_vae_loader_node = assembler._get_node_template("VAELoader")
256
+ pid_vae_loader_node['inputs']['vae_name'] = "pixel_space"
257
+ assembler.workflow[pid_vae_loader_id] = pid_vae_loader_node
258
+
259
+ pid_vae_decode_id = assembler._get_unique_id()
260
+ pid_vae_decode_node = assembler._get_node_template("VAEDecode")
261
+ pid_vae_decode_node['inputs']['samples'] = [new_ksampler_id, 0]
262
+ pid_vae_decode_node['inputs']['vae'] = [pid_vae_loader_id, 0]
263
+ assembler.workflow[pid_vae_decode_id] = pid_vae_decode_node
264
+
265
+ if original_vae_decode_id:
266
+ for node_id, node_data in assembler.workflow.items():
267
+ if 'inputs' in node_data:
268
+ for input_name, input_val in list(node_data['inputs'].items()):
269
+ if isinstance(input_val, list) and len(input_val) == 2:
270
+ if input_val[0] == original_vae_decode_id:
271
+ node_data['inputs'][input_name] = [pid_vae_decode_id, 0]
272
+
273
+ if original_vae_loader_id in assembler.workflow:
274
+ del assembler.workflow[original_vae_loader_id]
275
+ if original_vae_decode_id in assembler.workflow:
276
+ del assembler.workflow[original_vae_decode_id]
277
+
278
+ print("[PiD Injector] Successfully injected PiD pipeline and replaced VAE decode/loader.")
core/pipelines/pipeline_input_processor.py ADDED
@@ -0,0 +1,334 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import os
2
+ import random
3
+ import numpy as np
4
+ import gradio as gr
5
+ from PIL import Image, ImageChops
6
+ from typing import Dict, Any, List
7
+
8
+ from core.settings import INPUT_DIR
9
+ from utils.app_utils import (
10
+ sanitize_filename,
11
+ get_lora_path,
12
+ get_embedding_path,
13
+ ensure_controlnet_model_downloaded,
14
+ ensure_ipadapter_models_downloaded,
15
+ _ensure_model_downloaded,
16
+ ensure_sd3_ipadapter_models_downloaded,
17
+ get_vae_path,
18
+ )
19
+
20
+ def process_pipeline_inputs(ui_inputs: Dict[str, Any], progress: gr.Progress, workflow_model_type: str) -> Dict[str, Any]:
21
+ task_type = ui_inputs['task_type']
22
+ temp_files_to_clean = []
23
+
24
+ lora_data = ui_inputs.get('lora_data', [])
25
+ active_loras_for_gpu, active_loras_for_meta = [], []
26
+ if lora_data:
27
+ sources, ids, scales, files = lora_data[0::4], lora_data[1::4], lora_data[2::4], lora_data[3::4]
28
+ for i, (source, lora_id, scale, _) in enumerate(zip(sources, ids, scales, files)):
29
+ if scale > 0 and lora_id and lora_id.strip():
30
+ lora_filename = None
31
+ if source == "File":
32
+ lora_filename = sanitize_filename(lora_id)
33
+ elif source == "Civitai":
34
+ local_path, status = get_lora_path(source, lora_id, os.environ.get("CIVITAI_API_KEY", ""), progress)
35
+ if local_path: lora_filename = os.path.basename(local_path)
36
+ else: raise gr.Error(f"Failed to prepare LoRA {lora_id}: {status}")
37
+
38
+ if lora_filename:
39
+ active_loras_for_gpu.append({"lora_name": lora_filename, "strength_model": scale, "strength_clip": scale})
40
+ active_loras_for_meta.append(f"{source} {lora_id}:{scale}")
41
+
42
+ ui_inputs['denoise'] = 1.0
43
+ if task_type == 'img2img': ui_inputs['denoise'] = ui_inputs.get('img2img_denoise', 0.7)
44
+ elif task_type == 'hires_fix': ui_inputs['denoise'] = ui_inputs.get('hires_denoise', 0.55)
45
+
46
+ if not os.path.exists(INPUT_DIR): os.makedirs(INPUT_DIR)
47
+
48
+ if task_type == 'img2img':
49
+ input_image_pil = ui_inputs.get('img2img_image')
50
+ if not input_image_pil:
51
+ raise gr.Error("Please upload an image for Image-to-Image.")
52
+ temp_file_path = os.path.join(INPUT_DIR, f"temp_input_{random.randint(1000, 9999)}.png")
53
+ input_image_pil.save(temp_file_path, "PNG")
54
+ ui_inputs['input_image'] = os.path.basename(temp_file_path)
55
+ temp_files_to_clean.append(temp_file_path)
56
+ ui_inputs['width'] = input_image_pil.width
57
+ ui_inputs['height'] = input_image_pil.height
58
+
59
+ elif task_type == 'inpaint':
60
+ inpaint_dict = ui_inputs.get('inpaint_image_dict')
61
+ if not inpaint_dict or not inpaint_dict.get('background') or not inpaint_dict.get('layers'):
62
+ raise gr.Error("Inpainting requires an input image and a drawn mask.")
63
+
64
+ background_img = inpaint_dict['background'].convert("RGBA")
65
+ composite_mask_pil = Image.new('L', background_img.size, 0)
66
+ for layer in inpaint_dict['layers']:
67
+ if layer:
68
+ layer_alpha = layer.split()[-1]
69
+ composite_mask_pil = ImageChops.lighter(composite_mask_pil, layer_alpha)
70
+
71
+ inverted_mask_alpha = Image.fromarray(255 - np.array(composite_mask_pil), mode='L')
72
+ r, g, b, _ = background_img.split()
73
+ composite_image_with_mask = Image.merge('RGBA', [r, g, b, inverted_mask_alpha])
74
+
75
+ temp_file_path = os.path.join(INPUT_DIR, f"temp_inpaint_composite_{random.randint(1000, 9999)}.png")
76
+ composite_image_with_mask.save(temp_file_path, "PNG")
77
+
78
+ ui_inputs['input_image'] = os.path.basename(temp_file_path)
79
+ temp_files_to_clean.append(temp_file_path)
80
+ ui_inputs.pop('inpaint_mask', None)
81
+
82
+ elif task_type == 'outpaint':
83
+ input_image_pil = ui_inputs.get('outpaint_image')
84
+ if not input_image_pil:
85
+ raise gr.Error("Please upload an image for Outpainting.")
86
+ temp_file_path = os.path.join(INPUT_DIR, f"temp_input_{random.randint(1000, 9999)}.png")
87
+ input_image_pil.save(temp_file_path, "PNG")
88
+ ui_inputs['input_image'] = os.path.basename(temp_file_path)
89
+ temp_files_to_clean.append(temp_file_path)
90
+
91
+ ui_inputs['megapixels'] = 0.25
92
+ ui_inputs['grow_mask_by'] = ui_inputs.get('feathering', 10)
93
+
94
+ elif task_type == 'hires_fix':
95
+ input_image_pil = ui_inputs.get('hires_image')
96
+ if not input_image_pil:
97
+ raise gr.Error("Please upload an image for Hires Fix.")
98
+ temp_file_path = os.path.join(INPUT_DIR, f"temp_input_{random.randint(1000, 9999)}.png")
99
+ input_image_pil.save(temp_file_path, "PNG")
100
+ ui_inputs['input_image'] = os.path.basename(temp_file_path)
101
+ temp_files_to_clean.append(temp_file_path)
102
+
103
+ embedding_data = ui_inputs.get('embedding_data', [])
104
+ embedding_filenames = []
105
+ if embedding_data:
106
+ emb_sources, emb_ids, emb_files = embedding_data[0::3], embedding_data[1::3], embedding_data[2::3]
107
+ for i, (source, emb_id, _) in enumerate(zip(emb_sources, emb_ids, emb_files)):
108
+ if emb_id and emb_id.strip():
109
+ emb_filename = None
110
+ if source == "File":
111
+ emb_filename = sanitize_filename(emb_id)
112
+ elif source == "Civitai":
113
+ local_path, status = get_embedding_path(source, emb_id, os.environ.get("CIVITAI_API_KEY", ""), progress)
114
+ if local_path: emb_filename = os.path.basename(local_path)
115
+ else: raise gr.Error(f"Failed to prepare Embedding {emb_id}: {status}")
116
+
117
+ if emb_filename:
118
+ embedding_filenames.append(emb_filename)
119
+
120
+ if embedding_filenames:
121
+ embedding_prompt_text = " ".join([f"embedding:{f}" for f in embedding_filenames])
122
+ if ui_inputs['positive_prompt']:
123
+ ui_inputs['positive_prompt'] = f"{ui_inputs['positive_prompt']}, {embedding_prompt_text}"
124
+ else:
125
+ ui_inputs['positive_prompt'] = embedding_prompt_text
126
+
127
+ controlnet_data = ui_inputs.get('controlnet_data', [])
128
+ active_controlnets = []
129
+ if controlnet_data:
130
+ (cn_images, _, _, cn_strengths, cn_filepaths) = [controlnet_data[i::5] for i in range(5)]
131
+ for i in range(len(cn_images)):
132
+ if cn_images[i] and cn_strengths[i] > 0 and cn_filepaths[i] and cn_filepaths[i] != "None":
133
+ ensure_controlnet_model_downloaded(cn_filepaths[i], progress)
134
+ if not os.path.exists(INPUT_DIR): os.makedirs(INPUT_DIR)
135
+ cn_temp_path = os.path.join(INPUT_DIR, f"temp_cn_{i}_{random.randint(1000, 9999)}.png")
136
+ cn_images[i].save(cn_temp_path, "PNG")
137
+ temp_files_to_clean.append(cn_temp_path)
138
+ active_controlnets.append({
139
+ "image": os.path.basename(cn_temp_path), "strength": cn_strengths[i],
140
+ "start_percent": 0.0, "end_percent": 1.0, "control_net_name": cn_filepaths[i]
141
+ })
142
+
143
+ anima_controlnet_lllite_data = ui_inputs.get('anima_controlnet_lllite_data', [])
144
+ active_anima_controlnets = []
145
+ if anima_controlnet_lllite_data:
146
+ (cn_images, _, _, cn_strengths, cn_filepaths, cn_starts, cn_ends) = [anima_controlnet_lllite_data[i::7] for i in range(7)]
147
+ for i in range(len(cn_images)):
148
+ if cn_images[i] and cn_strengths[i] > 0 and cn_filepaths[i] and cn_filepaths[i] != "None":
149
+ _ensure_model_downloaded(cn_filepaths[i], progress)
150
+ if not os.path.exists(INPUT_DIR): os.makedirs(INPUT_DIR)
151
+ cn_temp_path = os.path.join(INPUT_DIR, f"temp_anima_cn_{i}_{random.randint(1000, 9999)}.png")
152
+ cn_images[i].save(cn_temp_path, "PNG")
153
+ temp_files_to_clean.append(cn_temp_path)
154
+ active_anima_controlnets.append({
155
+ "image": os.path.basename(cn_temp_path), "strength": cn_strengths[i],
156
+ "start_percent": cn_starts[i], "end_percent": cn_ends[i], "control_net_name": cn_filepaths[i]
157
+ })
158
+
159
+ diffsynth_controlnet_data = ui_inputs.get('diffsynth_controlnet_data', [])
160
+ active_diffsynth_controlnets = []
161
+ if diffsynth_controlnet_data:
162
+ (cn_images, _, _, cn_strengths, cn_filepaths) = [diffsynth_controlnet_data[i::5] for i in range(5)]
163
+ for i in range(len(cn_images)):
164
+ if cn_images[i] and cn_strengths[i] > 0 and cn_filepaths[i] and cn_filepaths[i] != "None":
165
+ ensure_controlnet_model_downloaded(cn_filepaths[i], progress)
166
+ if not os.path.exists(INPUT_DIR): os.makedirs(INPUT_DIR)
167
+ cn_temp_path = os.path.join(INPUT_DIR, f"temp_diffsynth_cn_{i}_{random.randint(1000, 9999)}.png")
168
+ cn_images[i].save(cn_temp_path, "PNG")
169
+ temp_files_to_clean.append(cn_temp_path)
170
+ active_diffsynth_controlnets.append({
171
+ "image": os.path.basename(cn_temp_path), "strength": cn_strengths[i],
172
+ "control_net_name": cn_filepaths[i]
173
+ })
174
+
175
+ ipadapter_data = ui_inputs.get('ipadapter_data', [])
176
+ active_ipadapters = []
177
+ if ipadapter_data:
178
+ num_ipa_units = (len(ipadapter_data) - 5) // 3
179
+ final_preset, final_weight, final_lora_strength, final_embeds_scaling, final_combine_method = ipadapter_data[-5:]
180
+ ipa_images, ipa_weights, ipa_lora_strengths = [ipadapter_data[i*num_ipa_units:(i+1)*num_ipa_units] for i in range(3)]
181
+ all_presets_to_download = set()
182
+ for i in range(num_ipa_units):
183
+ if ipa_images[i] and ipa_weights[i] > 0 and final_preset:
184
+ all_presets_to_download.add(final_preset)
185
+ if not os.path.exists(INPUT_DIR): os.makedirs(INPUT_DIR)
186
+ ipa_temp_path = os.path.join(INPUT_DIR, f"temp_ipa_{i}_{random.randint(1000, 9999)}.png")
187
+ ipa_images[i].save(ipa_temp_path, "PNG")
188
+ temp_files_to_clean.append(ipa_temp_path)
189
+ active_ipadapters.append({
190
+ "image": os.path.basename(ipa_temp_path), "preset": final_preset,
191
+ "weight": ipa_weights[i], "lora_strength": ipa_lora_strengths[i]
192
+ })
193
+ if active_ipadapters and final_preset:
194
+ all_presets_to_download.add(final_preset)
195
+ for preset in all_presets_to_download:
196
+ ensure_ipadapter_models_downloaded(preset, progress)
197
+
198
+ model_type_key = 'sd15' if workflow_model_type == 'sd15' else 'sdxl'
199
+ if active_ipadapters:
200
+ active_ipadapters.append({
201
+ 'is_final_settings': True, 'model_type': model_type_key, 'final_preset': final_preset,
202
+ 'final_weight': final_weight, 'final_lora_strength': final_lora_strength,
203
+ 'final_embeds_scaling': final_embeds_scaling, 'final_combine_method': final_combine_method
204
+ })
205
+
206
+ flux1_ipadapter_data = ui_inputs.get('flux1_ipadapter_data', [])
207
+ active_flux1_ipadapters = []
208
+ if flux1_ipadapter_data:
209
+ num_units = len(flux1_ipadapter_data) // 4
210
+ f_images = flux1_ipadapter_data[0*num_units : 1*num_units]
211
+ f_weights = flux1_ipadapter_data[1*num_units : 2*num_units]
212
+ f_starts = flux1_ipadapter_data[2*num_units : 3*num_units]
213
+ f_ends = flux1_ipadapter_data[3*num_units : 4*num_units]
214
+ for i in range(len(f_images)):
215
+ if f_images[i] and f_weights[i] > 0:
216
+ for filename in ["ip-adapter.bin"]:
217
+ _ensure_model_downloaded(filename, progress)
218
+
219
+ from huggingface_hub import snapshot_download
220
+ progress(0.5, desc="Caching HF SigLIP model...")
221
+ snapshot_download(
222
+ repo_id="google/siglip-so400m-patch14-384",
223
+ allow_patterns=["*.json", "*.safetensors", "*.txt"],
224
+ ignore_patterns=["*.msgpack", "*.h5", "*.bin"]
225
+ )
226
+
227
+ temp_path = os.path.join(INPUT_DIR, f"temp_fipa_{i}_{random.randint(1000, 9999)}.png")
228
+ f_images[i].save(temp_path, "PNG")
229
+ temp_files_to_clean.append(temp_path)
230
+ active_flux1_ipadapters.append({
231
+ "image": os.path.basename(temp_path),
232
+ "weight": f_weights[i], "start_percent": f_starts[i], "end_percent": f_ends[i]
233
+ })
234
+
235
+ sd3_ipadapter_data = ui_inputs.get('sd3_ipadapter_chain', [])
236
+ active_sd3_ipadapters = []
237
+ if sd3_ipadapter_data:
238
+ num_units = len(sd3_ipadapter_data) // 4
239
+ s_images = sd3_ipadapter_data[0*num_units : 1*num_units]
240
+ s_weights = sd3_ipadapter_data[1*num_units : 2*num_units]
241
+ s_starts = sd3_ipadapter_data[2*num_units : 3*num_units]
242
+ s_ends = sd3_ipadapter_data[3*num_units : 4*num_units]
243
+ sd3_ipa_downloaded = False
244
+ for i in range(len(s_images)):
245
+ if s_images[i] and s_weights[i] > 0:
246
+ if not sd3_ipa_downloaded:
247
+ ensure_sd3_ipadapter_models_downloaded(progress)
248
+ sd3_ipa_downloaded = True
249
+ temp_path = os.path.join(INPUT_DIR, f"temp_s3ipa_{i}_{random.randint(1000, 9999)}.png")
250
+ s_images[i].save(temp_path, "PNG")
251
+ temp_files_to_clean.append(temp_path)
252
+ active_sd3_ipadapters.append({
253
+ "image": os.path.basename(temp_path),
254
+ "weight": s_weights[i], "start_percent": s_starts[i], "end_percent": s_ends[i]
255
+ })
256
+
257
+ style_data = ui_inputs.get('style_data', [])
258
+ active_styles = []
259
+ if style_data:
260
+ num_units = len(style_data) // 2
261
+ st_images = style_data[0*num_units : 1*num_units]
262
+ st_strengths = style_data[1*num_units : 2*num_units]
263
+ for i in range(len(st_images)):
264
+ if st_images[i] and st_strengths[i] > 0:
265
+ _ensure_model_downloaded("sigclip_vision_patch14_384.safetensors", progress)
266
+ temp_path = os.path.join(INPUT_DIR, f"temp_style_{i}_{random.randint(1000, 9999)}.png")
267
+ st_images[i].save(temp_path, "PNG")
268
+ temp_files_to_clean.append(temp_path)
269
+ active_styles.append({
270
+ "image": os.path.basename(temp_path), "strength": st_strengths[i]
271
+ })
272
+
273
+ reference_latent_data = ui_inputs.get('reference_latent_data', [])
274
+ active_reference_latents = []
275
+ if reference_latent_data:
276
+ for img in reference_latent_data:
277
+ if img:
278
+ if not os.path.exists(INPUT_DIR): os.makedirs(INPUT_DIR)
279
+ temp_path = os.path.join(INPUT_DIR, f"temp_ref_{random.randint(1000, 9999)}.png")
280
+ img.save(temp_path, "PNG")
281
+ temp_files_to_clean.append(temp_path)
282
+ active_reference_latents.append(os.path.basename(temp_path))
283
+
284
+ hidream_o1_reference_data = ui_inputs.get('hidream_o1_reference_data', [])
285
+ active_hidream_o1_reference = []
286
+ if hidream_o1_reference_data:
287
+ for img in hidream_o1_reference_data:
288
+ if img:
289
+ if not os.path.exists(INPUT_DIR): os.makedirs(INPUT_DIR)
290
+ temp_path = os.path.join(INPUT_DIR, f"temp_ho1_ref_{random.randint(1000, 9999)}.png")
291
+ img.save(temp_path, "PNG")
292
+ temp_files_to_clean.append(temp_path)
293
+ active_hidream_o1_reference.append(os.path.basename(temp_path))
294
+
295
+ vae_source = ui_inputs.get('vae_source')
296
+ vae_id = ui_inputs.get('vae_id')
297
+ vae_name_override = None
298
+ if vae_source and vae_source != "None":
299
+ if vae_source == "File":
300
+ vae_name_override = sanitize_filename(vae_id)
301
+ elif vae_source == "Civitai" and vae_id and vae_id.strip():
302
+ local_path, status = get_vae_path(vae_source, vae_id, os.environ.get("CIVITAI_API_KEY", ""), progress)
303
+ if local_path: vae_name_override = os.path.basename(local_path)
304
+ else: raise gr.Error(f"Failed to prepare VAE {vae_id}: {status}")
305
+ if vae_name_override:
306
+ ui_inputs['vae_name'] = vae_name_override
307
+
308
+ conditioning_data = ui_inputs.get('conditioning_data', [])
309
+ active_conditioning = []
310
+ if conditioning_data:
311
+ num_units = len(conditioning_data) // 6
312
+ prompts, widths, heights, xs, ys, strengths = [conditioning_data[i*num_units : (i+1)*num_units] for i in range(6)]
313
+ for i in range(num_units):
314
+ if prompts[i] and prompts[i].strip():
315
+ active_conditioning.append({
316
+ "prompt": prompts[i], "width": int(widths[i]), "height": int(heights[i]),
317
+ "x": int(xs[i]), "y": int(ys[i]), "strength": float(strengths[i])
318
+ })
319
+
320
+ return {
321
+ "active_loras_for_gpu": active_loras_for_gpu,
322
+ "active_loras_for_meta": active_loras_for_meta,
323
+ "active_controlnets": active_controlnets,
324
+ "active_anima_controlnets": active_anima_controlnets,
325
+ "active_diffsynth_controlnets": active_diffsynth_controlnets,
326
+ "active_ipadapters": active_ipadapters,
327
+ "active_flux1_ipadapters": active_flux1_ipadapters,
328
+ "active_sd3_ipadapters": active_sd3_ipadapters,
329
+ "active_styles": active_styles,
330
+ "active_reference_latents": active_reference_latents,
331
+ "active_hidream_o1_reference": active_hidream_o1_reference,
332
+ "active_conditioning": active_conditioning,
333
+ "temp_files_to_clean": temp_files_to_clean
334
+ }
core/pipelines/sd_image_pipeline.py CHANGED
@@ -1,619 +1,254 @@
1
- import os
2
- import random
3
- import shutil
4
- import torch
5
- import gradio as gr
6
- from PIL import Image, ImageChops
7
- from typing import List, Dict, Any
8
- from collections import defaultdict, deque
9
- import numpy as np
10
-
11
- from .base_pipeline import BasePipeline
12
- from core.settings import *
13
- from comfy_integration.nodes import *
14
- from utils.app_utils import get_value_at_index, sanitize_prompt, get_lora_path, get_embedding_path, ensure_controlnet_model_downloaded, ensure_ipadapter_models_downloaded, sanitize_filename
15
- from core.workflow_assembler import WorkflowAssembler
16
-
17
- class SdImagePipeline(BasePipeline):
18
- def get_required_models(self, model_display_name: str, **kwargs) -> List[str]:
19
- model_info = ALL_MODEL_MAP.get(model_display_name)
20
- if not model_info:
21
- return [model_display_name]
22
-
23
- path_or_components = model_info[1]
24
- if isinstance(path_or_components, dict):
25
- return [v for v in path_or_components.values() if v and v != "pixel_space"]
26
- else:
27
- return [model_display_name]
28
-
29
- def _topological_sort(self, workflow: Dict[str, Any]) -> List[str]:
30
- graph = defaultdict(list)
31
- in_degree = {node_id: 0 for node_id in workflow}
32
-
33
- for node_id, node_info in workflow.items():
34
- for input_value in node_info.get('inputs', {}).values():
35
- if isinstance(input_value, list) and len(input_value) == 2 and isinstance(input_value[0], str):
36
- source_node_id = input_value[0]
37
- if source_node_id in workflow:
38
- graph[source_node_id].append(node_id)
39
- in_degree[node_id] += 1
40
-
41
- queue = deque([node_id for node_id, degree in in_degree.items() if degree == 0])
42
-
43
- sorted_nodes = []
44
- while queue:
45
- current_node_id = queue.popleft()
46
- sorted_nodes.append(current_node_id)
47
-
48
- for neighbor_node_id in graph[current_node_id]:
49
- in_degree[neighbor_node_id] -= 1
50
- if in_degree[neighbor_node_id] == 0:
51
- queue.append(neighbor_node_id)
52
-
53
- if len(sorted_nodes) != len(workflow):
54
- raise RuntimeError("Workflow contains a cycle and cannot be executed.")
55
-
56
- return sorted_nodes
57
-
58
- def _execute_workflow(self, workflow: Dict[str, Any], initial_objects: Dict[str, Any]):
59
- with torch.no_grad():
60
- computed_outputs = initial_objects
61
-
62
- try:
63
- sorted_node_ids = self._topological_sort(workflow)
64
- print(f"--- [Workflow Executor] Execution order: {sorted_node_ids}")
65
- except RuntimeError as e:
66
- print("--- [Workflow Executor] ERROR: Failed to sort workflow. Dumping graph details. ---")
67
- for node_id, node_info in workflow.items():
68
- print(f" Node {node_id} ({node_info['class_type']}):")
69
- for input_name, input_value in node_info['inputs'].items():
70
- if isinstance(input_value, list) and len(input_value) == 2 and isinstance(input_value[0], str):
71
- print(f" - {input_name} <- [{input_value[0]}, {input_value[1]}]")
72
- raise e
73
-
74
- for node_id in sorted_node_ids:
75
- if node_id in computed_outputs:
76
- continue
77
-
78
- node_info = workflow[node_id]
79
- class_type = node_info['class_type']
80
-
81
- is_loader_with_filename = 'Loader' in class_type and any(key.endswith('_name') for key in node_info['inputs'])
82
- if node_id in initial_objects and is_loader_with_filename:
83
- continue
84
-
85
- node_class = NODE_CLASS_MAPPINGS.get(class_type)
86
- if node_class is None:
87
- raise RuntimeError(f"Could not find node class '{class_type}'. Is it imported in comfy_integration/nodes.py?")
88
-
89
- node_instance = node_class()
90
-
91
- kwargs = {}
92
- for param_name, param_value in node_info['inputs'].items():
93
- if isinstance(param_value, list) and len(param_value) == 2 and isinstance(param_value[0], str):
94
- source_node_id, output_index = param_value
95
- if source_node_id not in computed_outputs:
96
- raise RuntimeError(f"Workflow integrity error: Output of node {source_node_id} needed for {node_id} but not yet computed.")
97
-
98
- source_output_tuple = computed_outputs[source_node_id]
99
- actual_value = get_value_at_index(source_output_tuple, output_index)
100
- else:
101
- actual_value = param_value
102
-
103
- if '.' in param_name:
104
- parent_key, child_key = param_name.split('.', 1)
105
- if parent_key not in kwargs or not isinstance(kwargs[parent_key], dict):
106
- kwargs[parent_key] = {}
107
- kwargs[parent_key][child_key] = actual_value
108
- else:
109
- kwargs[param_name] = actual_value
110
-
111
- function_name = getattr(node_class, 'FUNCTION')
112
- execution_method = getattr(node_instance, function_name)
113
-
114
- result = execution_method(**kwargs)
115
- computed_outputs[node_id] = result
116
-
117
- final_node_id = None
118
- for node_id in reversed(sorted_node_ids):
119
- if workflow[node_id]['class_type'] == 'SaveImage':
120
- final_node_id = node_id
121
- break
122
-
123
- if not final_node_id:
124
- raise RuntimeError("Workflow does not contain a 'SaveImage' node as the output.")
125
-
126
- save_image_inputs = workflow[final_node_id]['inputs']
127
- image_source_node_id, image_source_index = save_image_inputs['images']
128
-
129
- return get_value_at_index(computed_outputs[image_source_node_id], image_source_index)
130
-
131
- def _gpu_logic(self, ui_inputs: Dict, loras_string: str, workflow: Dict[str, Any], assembler: WorkflowAssembler, progress=gr.Progress(track_tqdm=True)):
132
- model_display_name = ui_inputs['model_display_name']
133
-
134
- progress(0.4, desc="Executing workflow...")
135
-
136
- initial_objects = {}
137
-
138
- decoded_images_tensor = self._execute_workflow(workflow, initial_objects=initial_objects)
139
-
140
- output_images = []
141
- start_seed = ui_inputs['seed'] if ui_inputs['seed'] != -1 else random.randint(0, 2**64 - 1)
142
- for i in range(decoded_images_tensor.shape[0]):
143
- img_tensor = decoded_images_tensor[i]
144
- pil_image = Image.fromarray((img_tensor.cpu().numpy() * 255.0).astype("uint8"))
145
- current_seed = start_seed + i
146
-
147
- width_for_meta = ui_inputs.get('width', 'N/A')
148
- height_for_meta = ui_inputs.get('height', 'N/A')
149
-
150
- params_string = f"{ui_inputs['positive_prompt']}\nNegative prompt: {ui_inputs['negative_prompt']}\n"
151
- params_string += f"Steps: {ui_inputs['num_inference_steps']}, Sampler: {ui_inputs['sampler']}, Scheduler: {ui_inputs['scheduler']}, CFG scale: {ui_inputs['guidance_scale']}, Seed: {current_seed}, Size: {width_for_meta}x{height_for_meta}, Base Model: {model_display_name}"
152
- if ui_inputs['task_type'] != 'txt2img': params_string += f", Denoise: {ui_inputs['denoise']}"
153
- if ui_inputs.get('clip_skip') and ui_inputs['clip_skip'] != 1: params_string += f", Clip skip: {abs(ui_inputs['clip_skip'])}"
154
- if loras_string: params_string += f", {loras_string}"
155
-
156
- pil_image.info = {'parameters': params_string.strip()}
157
- output_images.append(pil_image)
158
-
159
- return output_images
160
-
161
- def run(self, ui_inputs: Dict, progress):
162
- progress(0, desc="Preparing models...")
163
-
164
- task_type = ui_inputs['task_type']
165
- model_display_name = ui_inputs['model_display_name']
166
- model_type = MODEL_TYPE_MAP.get(model_display_name, 'sdxl')
167
-
168
- architectures_dict = ARCHITECTURES_CONFIG.get('architectures', {})
169
- workflow_model_type = architectures_dict.get(model_type, {}).get("model_type", "sdxl")
170
-
171
- ui_inputs['positive_prompt'] = sanitize_prompt(ui_inputs.get('positive_prompt', ''))
172
- ui_inputs['negative_prompt'] = sanitize_prompt(ui_inputs.get('negative_prompt', ''))
173
-
174
- if 'clip_skip' in ui_inputs and ui_inputs['clip_skip'] is not None:
175
- ui_inputs['clip_skip'] = -int(ui_inputs['clip_skip'])
176
- else:
177
- ui_inputs['clip_skip'] = -1
178
-
179
- required_models = self.get_required_models(model_display_name=model_display_name)
180
- self.model_manager.ensure_models_downloaded(required_models, progress=progress)
181
-
182
- lora_data = ui_inputs.get('lora_data', [])
183
- active_loras_for_gpu, active_loras_for_meta = [], []
184
- if lora_data:
185
- sources, ids, scales, files = lora_data[0::4], lora_data[1::4], lora_data[2::4], lora_data[3::4]
186
- for i, (source, lora_id, scale, _) in enumerate(zip(sources, ids, scales, files)):
187
- if scale > 0 and lora_id and lora_id.strip():
188
- lora_filename = None
189
- if source == "File":
190
- lora_filename = sanitize_filename(lora_id)
191
- elif source == "Civitai":
192
- local_path, status = get_lora_path(source, lora_id, os.environ.get("CIVITAI_API_KEY", ""), progress)
193
- if local_path: lora_filename = os.path.basename(local_path)
194
- else: raise gr.Error(f"Failed to prepare LoRA {lora_id}: {status}")
195
-
196
- if lora_filename:
197
- active_loras_for_gpu.append({"lora_name": lora_filename, "strength_model": scale, "strength_clip": scale})
198
- active_loras_for_meta.append(f"{source} {lora_id}:{scale}")
199
-
200
- ui_inputs['denoise'] = 1.0
201
- if task_type == 'img2img': ui_inputs['denoise'] = ui_inputs.get('img2img_denoise', 0.7)
202
- elif task_type == 'hires_fix': ui_inputs['denoise'] = ui_inputs.get('hires_denoise', 0.55)
203
-
204
- temp_files_to_clean = []
205
- if not os.path.exists(INPUT_DIR): os.makedirs(INPUT_DIR)
206
-
207
- if task_type == 'img2img':
208
- input_image_pil = ui_inputs.get('img2img_image')
209
- if not input_image_pil:
210
- raise gr.Error("Please upload an image for Image-to-Image.")
211
- temp_file_path = os.path.join(INPUT_DIR, f"temp_input_{random.randint(1000, 9999)}.png")
212
- input_image_pil.save(temp_file_path, "PNG")
213
- ui_inputs['input_image'] = os.path.basename(temp_file_path)
214
- temp_files_to_clean.append(temp_file_path)
215
- ui_inputs['width'] = input_image_pil.width
216
- ui_inputs['height'] = input_image_pil.height
217
-
218
- elif task_type == 'inpaint':
219
- inpaint_dict = ui_inputs.get('inpaint_image_dict')
220
- if not inpaint_dict or not inpaint_dict.get('background') or not inpaint_dict.get('layers'):
221
- raise gr.Error("Inpainting requires an input image and a drawn mask.")
222
-
223
- background_img = inpaint_dict['background'].convert("RGBA")
224
- composite_mask_pil = Image.new('L', background_img.size, 0)
225
- for layer in inpaint_dict['layers']:
226
- if layer:
227
- layer_alpha = layer.split()[-1]
228
- composite_mask_pil = ImageChops.lighter(composite_mask_pil, layer_alpha)
229
-
230
- inverted_mask_alpha = Image.fromarray(255 - np.array(composite_mask_pil), mode='L')
231
- r, g, b, _ = background_img.split()
232
- composite_image_with_mask = Image.merge('RGBA', [r, g, b, inverted_mask_alpha])
233
-
234
- temp_file_path = os.path.join(INPUT_DIR, f"temp_inpaint_composite_{random.randint(1000, 9999)}.png")
235
- composite_image_with_mask.save(temp_file_path, "PNG")
236
-
237
- ui_inputs['input_image'] = os.path.basename(temp_file_path)
238
- temp_files_to_clean.append(temp_file_path)
239
- ui_inputs.pop('inpaint_mask', None)
240
-
241
- elif task_type == 'outpaint':
242
- input_image_pil = ui_inputs.get('outpaint_image')
243
- if not input_image_pil:
244
- raise gr.Error("Please upload an image for Outpainting.")
245
- temp_file_path = os.path.join(INPUT_DIR, f"temp_input_{random.randint(1000, 9999)}.png")
246
- input_image_pil.save(temp_file_path, "PNG")
247
- ui_inputs['input_image'] = os.path.basename(temp_file_path)
248
- temp_files_to_clean.append(temp_file_path)
249
-
250
- ui_inputs['megapixels'] = 0.25
251
- ui_inputs['grow_mask_by'] = ui_inputs.get('feathering', 10)
252
-
253
- elif task_type == 'hires_fix':
254
- input_image_pil = ui_inputs.get('hires_image')
255
- if not input_image_pil:
256
- raise gr.Error("Please upload an image for Hires Fix.")
257
- temp_file_path = os.path.join(INPUT_DIR, f"temp_input_{random.randint(1000, 9999)}.png")
258
- input_image_pil.save(temp_file_path, "PNG")
259
- ui_inputs['input_image'] = os.path.basename(temp_file_path)
260
- temp_files_to_clean.append(temp_file_path)
261
-
262
- embedding_data = ui_inputs.get('embedding_data', [])
263
- embedding_filenames = []
264
- if embedding_data:
265
- emb_sources, emb_ids, emb_files = embedding_data[0::3], embedding_data[1::3], embedding_data[2::3]
266
- for i, (source, emb_id, _) in enumerate(zip(emb_sources, emb_ids, emb_files)):
267
- if emb_id and emb_id.strip():
268
- emb_filename = None
269
- if source == "File":
270
- emb_filename = sanitize_filename(emb_id)
271
- elif source == "Civitai":
272
- local_path, status = get_embedding_path(source, emb_id, os.environ.get("CIVITAI_API_KEY", ""), progress)
273
- if local_path: emb_filename = os.path.basename(local_path)
274
- else: raise gr.Error(f"Failed to prepare Embedding {emb_id}: {status}")
275
-
276
- if emb_filename:
277
- embedding_filenames.append(emb_filename)
278
-
279
- if embedding_filenames:
280
- embedding_prompt_text = " ".join([f"embedding:{f}" for f in embedding_filenames])
281
- if ui_inputs['positive_prompt']:
282
- ui_inputs['positive_prompt'] = f"{ui_inputs['positive_prompt']}, {embedding_prompt_text}"
283
- else:
284
- ui_inputs['positive_prompt'] = embedding_prompt_text
285
-
286
- controlnet_data = ui_inputs.get('controlnet_data', [])
287
- active_controlnets = []
288
- if controlnet_data:
289
- (cn_images, _, _, cn_strengths, cn_filepaths) = [controlnet_data[i::5] for i in range(5)]
290
- for i in range(len(cn_images)):
291
- if cn_images[i] and cn_strengths[i] > 0 and cn_filepaths[i] and cn_filepaths[i] != "None":
292
- ensure_controlnet_model_downloaded(cn_filepaths[i], progress)
293
- if not os.path.exists(INPUT_DIR): os.makedirs(INPUT_DIR)
294
- cn_temp_path = os.path.join(INPUT_DIR, f"temp_cn_{i}_{random.randint(1000, 9999)}.png")
295
- cn_images[i].save(cn_temp_path, "PNG")
296
- temp_files_to_clean.append(cn_temp_path)
297
- active_controlnets.append({
298
- "image": os.path.basename(cn_temp_path), "strength": cn_strengths[i],
299
- "start_percent": 0.0, "end_percent": 1.0, "control_net_name": cn_filepaths[i]
300
- })
301
-
302
- anima_controlnet_lllite_data = ui_inputs.get('anima_controlnet_lllite_data', [])
303
- active_anima_controlnets = []
304
- if anima_controlnet_lllite_data:
305
- (cn_images, _, _, cn_strengths, cn_filepaths, cn_starts, cn_ends) = [anima_controlnet_lllite_data[i::7] for i in range(7)]
306
- for i in range(len(cn_images)):
307
- if cn_images[i] and cn_strengths[i] > 0 and cn_filepaths[i] and cn_filepaths[i] != "None":
308
- from utils.app_utils import _ensure_model_downloaded
309
- _ensure_model_downloaded(cn_filepaths[i], progress)
310
- if not os.path.exists(INPUT_DIR): os.makedirs(INPUT_DIR)
311
- cn_temp_path = os.path.join(INPUT_DIR, f"temp_anima_cn_{i}_{random.randint(1000, 9999)}.png")
312
- cn_images[i].save(cn_temp_path, "PNG")
313
- temp_files_to_clean.append(cn_temp_path)
314
- active_anima_controlnets.append({
315
- "image": os.path.basename(cn_temp_path), "strength": cn_strengths[i],
316
- "start_percent": cn_starts[i], "end_percent": cn_ends[i], "control_net_name": cn_filepaths[i]
317
- })
318
-
319
- diffsynth_controlnet_data = ui_inputs.get('diffsynth_controlnet_data', [])
320
- active_diffsynth_controlnets = []
321
- if diffsynth_controlnet_data:
322
- (cn_images, _, _, cn_strengths, cn_filepaths) = [diffsynth_controlnet_data[i::5] for i in range(5)]
323
- for i in range(len(cn_images)):
324
- if cn_images[i] and cn_strengths[i] > 0 and cn_filepaths[i] and cn_filepaths[i] != "None":
325
- ensure_controlnet_model_downloaded(cn_filepaths[i], progress)
326
-
327
- if not os.path.exists(INPUT_DIR): os.makedirs(INPUT_DIR)
328
- cn_temp_path = os.path.join(INPUT_DIR, f"temp_diffsynth_cn_{i}_{random.randint(1000, 9999)}.png")
329
- cn_images[i].save(cn_temp_path, "PNG")
330
- temp_files_to_clean.append(cn_temp_path)
331
- active_diffsynth_controlnets.append({
332
- "image": os.path.basename(cn_temp_path), "strength": cn_strengths[i],
333
- "control_net_name": cn_filepaths[i]
334
- })
335
-
336
- ipadapter_data = ui_inputs.get('ipadapter_data', [])
337
- active_ipadapters = []
338
- if ipadapter_data:
339
- num_ipa_units = (len(ipadapter_data) - 5) // 3
340
- final_preset, final_weight, final_lora_strength, final_embeds_scaling, final_combine_method = ipadapter_data[-5:]
341
- ipa_images, ipa_weights, ipa_lora_strengths = [ipadapter_data[i*num_ipa_units:(i+1)*num_ipa_units] for i in range(3)]
342
- all_presets_to_download = set()
343
- for i in range(num_ipa_units):
344
- if ipa_images[i] and ipa_weights[i] > 0 and final_preset:
345
- all_presets_to_download.add(final_preset)
346
- if not os.path.exists(INPUT_DIR): os.makedirs(INPUT_DIR)
347
- ipa_temp_path = os.path.join(INPUT_DIR, f"temp_ipa_{i}_{random.randint(1000, 9999)}.png")
348
- ipa_images[i].save(ipa_temp_path, "PNG")
349
- temp_files_to_clean.append(ipa_temp_path)
350
- active_ipadapters.append({
351
- "image": os.path.basename(ipa_temp_path), "preset": final_preset,
352
- "weight": ipa_weights[i], "lora_strength": ipa_lora_strengths[i]
353
- })
354
- if active_ipadapters and final_preset:
355
- all_presets_to_download.add(final_preset)
356
- for preset in all_presets_to_download:
357
- ensure_ipadapter_models_downloaded(preset, progress)
358
-
359
- model_type_key = 'sd15' if workflow_model_type == 'sd15' else 'sdxl'
360
- if active_ipadapters:
361
- active_ipadapters.append({
362
- 'is_final_settings': True, 'model_type': model_type_key, 'final_preset': final_preset,
363
- 'final_weight': final_weight, 'final_lora_strength': final_lora_strength,
364
- 'final_embeds_scaling': final_embeds_scaling, 'final_combine_method': final_combine_method
365
- })
366
-
367
- flux1_ipadapter_data = ui_inputs.get('flux1_ipadapter_data', [])
368
- active_flux1_ipadapters = []
369
- if flux1_ipadapter_data:
370
- num_units = len(flux1_ipadapter_data) // 4
371
- f_images = flux1_ipadapter_data[0*num_units : 1*num_units]
372
- f_weights = flux1_ipadapter_data[1*num_units : 2*num_units]
373
- f_starts = flux1_ipadapter_data[2*num_units : 3*num_units]
374
- f_ends = flux1_ipadapter_data[3*num_units : 4*num_units]
375
- for i in range(len(f_images)):
376
- if f_images[i] and f_weights[i] > 0:
377
- from utils.app_utils import _ensure_model_downloaded
378
- for filename in ["ip-adapter.bin"]:
379
- _ensure_model_downloaded(filename, progress)
380
-
381
- from huggingface_hub import snapshot_download
382
- progress(0.5, desc="Caching HF SigLIP model...")
383
- snapshot_download(
384
- repo_id="google/siglip-so400m-patch14-384",
385
- allow_patterns=["*.json", "*.safetensors", "*.txt"],
386
- ignore_patterns=["*.msgpack", "*.h5", "*.bin"]
387
- )
388
-
389
- temp_path = os.path.join(INPUT_DIR, f"temp_fipa_{i}_{random.randint(1000, 9999)}.png")
390
- f_images[i].save(temp_path, "PNG")
391
- temp_files_to_clean.append(temp_path)
392
- active_flux1_ipadapters.append({
393
- "image": os.path.basename(temp_path),
394
- "weight": f_weights[i], "start_percent": f_starts[i], "end_percent": f_ends[i]
395
- })
396
-
397
- sd3_ipadapter_data = ui_inputs.get('sd3_ipadapter_chain', [])
398
- active_sd3_ipadapters = []
399
- if sd3_ipadapter_data:
400
- num_units = len(sd3_ipadapter_data) // 4
401
- s_images = sd3_ipadapter_data[0*num_units : 1*num_units]
402
- s_weights = sd3_ipadapter_data[1*num_units : 2*num_units]
403
- s_starts = sd3_ipadapter_data[2*num_units : 3*num_units]
404
- s_ends = sd3_ipadapter_data[3*num_units : 4*num_units]
405
- sd3_ipa_downloaded = False
406
- for i in range(len(s_images)):
407
- if s_images[i] and s_weights[i] > 0:
408
- if not sd3_ipa_downloaded:
409
- from utils.app_utils import ensure_sd3_ipadapter_models_downloaded
410
- ensure_sd3_ipadapter_models_downloaded(progress)
411
- sd3_ipa_downloaded = True
412
- temp_path = os.path.join(INPUT_DIR, f"temp_s3ipa_{i}_{random.randint(1000, 9999)}.png")
413
- s_images[i].save(temp_path, "PNG")
414
- temp_files_to_clean.append(temp_path)
415
- active_sd3_ipadapters.append({
416
- "image": os.path.basename(temp_path),
417
- "weight": s_weights[i], "start_percent": s_starts[i], "end_percent": s_ends[i]
418
- })
419
-
420
- style_data = ui_inputs.get('style_data', [])
421
- active_styles = []
422
- if style_data:
423
- num_units = len(style_data) // 2
424
- st_images = style_data[0*num_units : 1*num_units]
425
- st_strengths = style_data[1*num_units : 2*num_units]
426
- for i in range(len(st_images)):
427
- if st_images[i] and st_strengths[i] > 0:
428
- from utils.app_utils import _ensure_model_downloaded
429
- _ensure_model_downloaded("sigclip_vision_patch14_384.safetensors", progress)
430
- temp_path = os.path.join(INPUT_DIR, f"temp_style_{i}_{random.randint(1000, 9999)}.png")
431
- st_images[i].save(temp_path, "PNG")
432
- temp_files_to_clean.append(temp_path)
433
- active_styles.append({
434
- "image": os.path.basename(temp_path), "strength": st_strengths[i]
435
- })
436
-
437
- reference_latent_data = ui_inputs.get('reference_latent_data', [])
438
- active_reference_latents = []
439
- if reference_latent_data:
440
- for img in reference_latent_data:
441
- if img:
442
- if not os.path.exists(INPUT_DIR): os.makedirs(INPUT_DIR)
443
- temp_path = os.path.join(INPUT_DIR, f"temp_ref_{random.randint(1000, 9999)}.png")
444
- img.save(temp_path, "PNG")
445
- temp_files_to_clean.append(temp_path)
446
- active_reference_latents.append(os.path.basename(temp_path))
447
-
448
- hidream_o1_reference_data = ui_inputs.get('hidream_o1_reference_data', [])
449
- active_hidream_o1_reference = []
450
- if hidream_o1_reference_data:
451
- for img in hidream_o1_reference_data:
452
- if img:
453
- if not os.path.exists(INPUT_DIR): os.makedirs(INPUT_DIR)
454
- temp_path = os.path.join(INPUT_DIR, f"temp_ho1_ref_{random.randint(1000, 9999)}.png")
455
- img.save(temp_path, "PNG")
456
- temp_files_to_clean.append(temp_path)
457
- active_hidream_o1_reference.append(os.path.basename(temp_path))
458
-
459
- from utils.app_utils import get_vae_path
460
- vae_source = ui_inputs.get('vae_source')
461
- vae_id = ui_inputs.get('vae_id')
462
- vae_name_override = None
463
- if vae_source and vae_source != "None":
464
- if vae_source == "File":
465
- vae_name_override = sanitize_filename(vae_id)
466
- elif vae_source == "Civitai" and vae_id and vae_id.strip():
467
- local_path, status = get_vae_path(vae_source, vae_id, os.environ.get("CIVITAI_API_KEY", ""), progress)
468
- if local_path: vae_name_override = os.path.basename(local_path)
469
- else: raise gr.Error(f"Failed to prepare VAE {vae_id}: {status}")
470
- if vae_name_override:
471
- ui_inputs['vae_name'] = vae_name_override
472
-
473
- conditioning_data = ui_inputs.get('conditioning_data', [])
474
- active_conditioning = []
475
- if conditioning_data:
476
- num_units = len(conditioning_data) // 6
477
- prompts, widths, heights, xs, ys, strengths = [conditioning_data[i*num_units : (i+1)*num_units] for i in range(6)]
478
- for i in range(num_units):
479
- if prompts[i] and prompts[i].strip():
480
- active_conditioning.append({
481
- "prompt": prompts[i], "width": int(widths[i]), "height": int(heights[i]),
482
- "x": int(xs[i]), "y": int(ys[i]), "strength": float(strengths[i])
483
- })
484
-
485
- loras_string = f"LoRAs: [{', '.join(active_loras_for_meta)}]" if active_loras_for_meta else ""
486
-
487
- progress(0.8, desc="Assembling workflow...")
488
-
489
- if ui_inputs.get('seed') == -1:
490
- ui_inputs['seed'] = random.randint(0, 2**32 - 1)
491
-
492
- model_info = ALL_MODEL_MAP[model_display_name]
493
- path_or_components = model_info[1]
494
- latent_type = model_info[3] if len(model_info) > 3 and model_info[3] else 'latent'
495
- latent_generator_template = "EmptyLatentImage"
496
- if latent_type == 'sd3_latent':
497
- latent_generator_template = "EmptySD3LatentImage"
498
- elif latent_type == 'chroma_radiance_latent':
499
- latent_generator_template = "EmptyChromaRadianceLatentImage"
500
- elif latent_type == 'hunyuan_latent':
501
- latent_generator_template = "EmptyHunyuanImageLatent"
502
-
503
- dynamic_values = {
504
- 'task_type': ui_inputs['task_type'],
505
- 'model_type': workflow_model_type,
506
- 'latent_type': latent_type,
507
- 'latent_generator_template': latent_generator_template
508
- }
509
-
510
- recipe_path = os.path.join(os.path.dirname(__file__), "workflow_recipes", "sd_unified_recipe.yaml")
511
- assembler = WorkflowAssembler(recipe_path, dynamic_values=dynamic_values)
512
-
513
- hidream_o1_smoothing_data = []
514
- if workflow_model_type == 'hidream-o1' and model_display_name == "HiDream-O1-Image":
515
- hidream_o1_smoothing_data.append({})
516
-
517
- workflow_inputs = {
518
- **ui_inputs,
519
- "positive_prompt": ui_inputs['positive_prompt'], "negative_prompt": ui_inputs['negative_prompt'],
520
- "seed": ui_inputs['seed'], "steps": ui_inputs['num_inference_steps'], "cfg": ui_inputs['guidance_scale'],
521
- "sampler_name": ui_inputs['sampler'], "scheduler": ui_inputs['scheduler'],
522
- "batch_size": ui_inputs['batch_size'],
523
- "clip_skip": ui_inputs['clip_skip'],
524
- "denoise": ui_inputs['denoise'],
525
- "vae_name": ui_inputs.get('vae_name'),
526
- "guidance": ui_inputs.get('guidance', 3.5),
527
- "lora_chain": active_loras_for_gpu,
528
- "controlnet_chain": active_controlnets if not active_anima_controlnets else [],
529
- "anima_controlnet_lllite_chain": active_anima_controlnets,
530
- "diffsynth_controlnet_chain": active_diffsynth_controlnets,
531
- "ipadapter_chain": active_ipadapters,
532
- "flux1_ipadapter_chain": active_flux1_ipadapters,
533
- "sd3_ipadapter_chain": active_sd3_ipadapters,
534
- "style_chain": active_styles,
535
- "conditioning_chain": active_conditioning,
536
- "reference_latent_chain": active_reference_latents,
537
- "hidream_o1_reference_chain": active_hidream_o1_reference,
538
- "vae_chain": [ui_inputs.get('vae_name')] if ui_inputs.get('vae_name') else [],
539
- "hidream_o1_smoothing_chain": hidream_o1_smoothing_data,
540
- }
541
-
542
- if isinstance(path_or_components, dict):
543
- workflow_inputs.update({
544
- 'unet_name': path_or_components.get('unet'),
545
- 'vae_name': ui_inputs.get('vae_name') or path_or_components.get('vae'),
546
- 'clip_name': path_or_components.get('clip'),
547
- 'clip1_name': path_or_components.get('clip1'),
548
- 'clip2_name': path_or_components.get('clip2'),
549
- 'clip3_name': path_or_components.get('clip3'),
550
- 'clip4_name': path_or_components.get('clip4'),
551
- 'lora_name': path_or_components.get('lora'),
552
- })
553
- else:
554
- workflow_inputs['model_name'] = path_or_components
555
-
556
- if task_type == 'txt2img':
557
- workflow_inputs['width'] = ui_inputs['width']
558
- workflow_inputs['height'] = ui_inputs['height']
559
-
560
- workflow = assembler.assemble(workflow_inputs)
561
-
562
- progress(1.0, desc="All models ready. Requesting GPU for generation...")
563
-
564
- try:
565
- results = self._execute_gpu_logic(
566
- self._gpu_logic,
567
- duration=ui_inputs['zero_gpu_duration'],
568
- default_duration=60,
569
- task_name=f"ImageGen ({task_type})",
570
- ui_inputs=ui_inputs,
571
- loras_string=loras_string,
572
- workflow=workflow,
573
- assembler=assembler,
574
- progress=progress
575
- )
576
-
577
- import json
578
- import glob
579
- from PIL import PngImagePlugin
580
-
581
- prompt_json = json.dumps(workflow)
582
-
583
- out_dir = os.path.abspath(OUTPUT_DIR)
584
- os.makedirs(out_dir, exist_ok=True)
585
-
586
- try:
587
- existing_files = glob.glob(os.path.join(out_dir, "gen_*.png"))
588
- existing_files.sort(key=os.path.getmtime)
589
- while len(existing_files) > 50:
590
- os.remove(existing_files.pop(0))
591
- except Exception as e:
592
- print(f"Warning: Failed to cleanup output dir: {e}")
593
-
594
- final_results = []
595
- for img in results:
596
- if not isinstance(img, Image.Image):
597
- final_results.append(img)
598
- continue
599
-
600
- metadata = PngImagePlugin.PngInfo()
601
- params_string = img.info.get("parameters", "")
602
- if params_string:
603
- metadata.add_text("parameters", params_string)
604
- metadata.add_text("prompt", prompt_json)
605
-
606
- filename = f"gen_{random.randint(1000000, 9999999)}.png"
607
- filepath = os.path.join(out_dir, filename)
608
- img.save(filepath, "PNG", pnginfo=metadata)
609
- final_results.append(filepath)
610
-
611
- results = final_results
612
-
613
- finally:
614
- for temp_file in temp_files_to_clean:
615
- if temp_file and os.path.exists(temp_file):
616
- os.remove(temp_file)
617
- print(f"✅ Cleaned up temp file: {temp_file}")
618
-
619
  return results
 
1
+ import os
2
+ import random
3
+ import shutil
4
+ import torch
5
+ import gradio as gr
6
+ from PIL import Image
7
+ from typing import List, Dict, Any
8
+
9
+ from .base_pipeline import BasePipeline
10
+ from core.settings import *
11
+ from utils.app_utils import sanitize_prompt
12
+ from core.workflow_assembler import WorkflowAssembler
13
+ from .workflow_executor import WorkflowExecutor
14
+ from .pipeline_input_processor import process_pipeline_inputs
15
+
16
+ class SdImagePipeline(BasePipeline):
17
+ def get_required_models(self, model_display_name: str, **kwargs) -> List[str]:
18
+ model_info = ALL_MODEL_MAP.get(model_display_name)
19
+ if not model_info:
20
+ return [model_display_name]
21
+
22
+ path_or_components = model_info[1]
23
+ if isinstance(path_or_components, dict):
24
+ return [v for v in path_or_components.values() if v and v != "pixel_space"]
25
+ else:
26
+ return [model_display_name]
27
+
28
+ def _gpu_logic(self, ui_inputs: Dict, loras_string: str, workflow: Dict[str, Any], assembler: WorkflowAssembler, progress=gr.Progress(track_tqdm=True)):
29
+ model_display_name = ui_inputs['model_display_name']
30
+
31
+ progress(0.4, desc="Executing workflow...")
32
+
33
+ initial_objects = {}
34
+
35
+ decoded_images_tensor = WorkflowExecutor.execute_workflow(workflow, initial_objects=initial_objects)
36
+
37
+ output_images = []
38
+ start_seed = ui_inputs['seed'] if ui_inputs['seed'] != -1 else random.randint(0, 2**64 - 1)
39
+ for i in range(decoded_images_tensor.shape[0]):
40
+ img_tensor = decoded_images_tensor[i]
41
+ pil_image = Image.fromarray((img_tensor.cpu().numpy() * 255.0).astype("uint8"))
42
+ current_seed = start_seed + i
43
+
44
+ width_for_meta = ui_inputs.get('width', 'N/A')
45
+ height_for_meta = ui_inputs.get('height', 'N/A')
46
+
47
+ params_string = f"{ui_inputs['positive_prompt']}\nNegative prompt: {ui_inputs['negative_prompt']}\n"
48
+ params_string += f"Steps: {ui_inputs['num_inference_steps']}, Sampler: {ui_inputs['sampler']}, Scheduler: {ui_inputs['scheduler']}, CFG scale: {ui_inputs['guidance_scale']}, Seed: {current_seed}, Size: {width_for_meta}x{height_for_meta}, Base Model: {model_display_name}"
49
+ if ui_inputs['task_type'] != 'txt2img': params_string += f", Denoise: {ui_inputs['denoise']}"
50
+ if ui_inputs.get('clip_skip') and ui_inputs['clip_skip'] != 1: params_string += f", Clip skip: {abs(ui_inputs['clip_skip'])}"
51
+ if loras_string: params_string += f", {loras_string}"
52
+
53
+ pil_image.info = {'parameters': params_string.strip()}
54
+ output_images.append(pil_image)
55
+
56
+ return output_images
57
+
58
+ def run(self, ui_inputs: Dict, progress):
59
+ progress(0, desc="Preparing models...")
60
+
61
+ task_type = ui_inputs['task_type']
62
+ model_display_name = ui_inputs['model_display_name']
63
+ model_type = MODEL_TYPE_MAP.get(model_display_name, 'sdxl')
64
+
65
+ architectures_dict = ARCHITECTURES_CONFIG.get('architectures', {})
66
+ workflow_model_type = architectures_dict.get(model_type, {}).get("model_type", model_type.lower().replace(" ", "").replace(".", ""))
67
+
68
+ ui_inputs['positive_prompt'] = sanitize_prompt(ui_inputs.get('positive_prompt', ''))
69
+ ui_inputs['negative_prompt'] = sanitize_prompt(ui_inputs.get('negative_prompt', ''))
70
+
71
+ if 'clip_skip' in ui_inputs and ui_inputs['clip_skip'] is not None:
72
+ ui_inputs['clip_skip'] = -int(ui_inputs['clip_skip'])
73
+ else:
74
+ ui_inputs['clip_skip'] = -1
75
+
76
+ required_models = self.get_required_models(model_display_name=model_display_name)
77
+
78
+ is_pid_enabled = (ui_inputs.get('pid_settings', 'OFF') == 'ON' and task_type == 'txt2img')
79
+ if is_pid_enabled:
80
+ import yaml
81
+ pid_config_path = os.path.join(os.path.dirname(os.path.dirname(os.path.dirname(os.path.abspath(__file__)))), 'yaml', 'pid.yaml')
82
+ pid_unet_name = "pid_flux1_1024_to_4096_4step_mxfp8.safetensors"
83
+ try:
84
+ with open(pid_config_path, 'r', encoding='utf-8') as f:
85
+ pid_config = yaml.safe_load(f) or {}
86
+ pid_items = pid_config.get("PiD", [])
87
+ for item in pid_items:
88
+ archs = item.get("architectures", [])
89
+ if workflow_model_type in archs:
90
+ pid_unet_name = item.get("filepath")
91
+ break
92
+ except Exception as e:
93
+ print(f"Error loading PiD config for download: {e}")
94
+
95
+ if pid_unet_name not in required_models:
96
+ required_models.append(pid_unet_name)
97
+ if "gemma_2_2b_it_elm_fp8_scaled.safetensors" not in required_models:
98
+ required_models.append("gemma_2_2b_it_elm_fp8_scaled.safetensors")
99
+
100
+ self.model_manager.ensure_models_downloaded(required_models, progress=progress)
101
+
102
+ temp_files_to_clean = []
103
+ try:
104
+ processed = process_pipeline_inputs(ui_inputs, progress, workflow_model_type)
105
+ temp_files_to_clean.extend(processed["temp_files_to_clean"])
106
+
107
+ active_loras_for_gpu = processed["active_loras_for_gpu"]
108
+ active_loras_for_meta = processed["active_loras_for_meta"]
109
+ active_controlnets = processed["active_controlnets"]
110
+ active_anima_controlnets = processed["active_anima_controlnets"]
111
+ active_diffsynth_controlnets = processed["active_diffsynth_controlnets"]
112
+ active_ipadapters = processed["active_ipadapters"]
113
+ active_flux1_ipadapters = processed["active_flux1_ipadapters"]
114
+ active_sd3_ipadapters = processed["active_sd3_ipadapters"]
115
+ active_styles = processed["active_styles"]
116
+ active_reference_latents = processed["active_reference_latents"]
117
+ active_hidream_o1_reference = processed["active_hidream_o1_reference"]
118
+ active_conditioning = processed["active_conditioning"]
119
+
120
+ loras_string = f"LoRAs: [{', '.join(active_loras_for_meta)}]" if active_loras_for_meta else ""
121
+
122
+ progress(0.8, desc="Assembling workflow...")
123
+
124
+ if ui_inputs.get('seed') == -1:
125
+ ui_inputs['seed'] = random.randint(0, 2**32 - 1)
126
+
127
+ model_info = ALL_MODEL_MAP[model_display_name]
128
+ path_or_components = model_info[1]
129
+ latent_type = model_info[3] if len(model_info) > 3 and model_info[3] else 'latent'
130
+ latent_generator_template = "EmptyLatentImage"
131
+ if latent_type == 'sd3_latent':
132
+ latent_generator_template = "EmptySD3LatentImage"
133
+ elif latent_type == 'chroma_radiance_latent':
134
+ latent_generator_template = "EmptyChromaRadianceLatentImage"
135
+ elif latent_type == 'hunyuan_latent':
136
+ latent_generator_template = "EmptyHunyuanImageLatent"
137
+
138
+ dynamic_values = {
139
+ 'task_type': ui_inputs['task_type'],
140
+ 'model_type': workflow_model_type,
141
+ 'latent_type': latent_type,
142
+ 'latent_generator_template': latent_generator_template
143
+ }
144
+
145
+ recipe_path = os.path.join(os.path.dirname(__file__), "workflow_recipes", "sd_unified_recipe.yaml")
146
+ assembler = WorkflowAssembler(recipe_path, dynamic_values=dynamic_values)
147
+
148
+ hidream_o1_smoothing_data = []
149
+ if workflow_model_type == 'hidream-o1' and model_display_name == "HiDream-O1-Image":
150
+ hidream_o1_smoothing_data.append({})
151
+
152
+ workflow_inputs = {
153
+ **ui_inputs,
154
+ "positive_prompt": ui_inputs['positive_prompt'], "negative_prompt": ui_inputs['negative_prompt'],
155
+ "seed": ui_inputs['seed'], "steps": ui_inputs['num_inference_steps'], "cfg": ui_inputs['guidance_scale'],
156
+ "sampler_name": ui_inputs['sampler'], "scheduler": ui_inputs['scheduler'],
157
+ "batch_size": ui_inputs['batch_size'],
158
+ "clip_skip": ui_inputs['clip_skip'],
159
+ "denoise": ui_inputs['denoise'],
160
+ "vae_name": ui_inputs.get('vae_name'),
161
+ "guidance": ui_inputs.get('guidance', 3.5),
162
+ "lora_chain": active_loras_for_gpu,
163
+ "controlnet_chain": active_controlnets if not active_anima_controlnets else [],
164
+ "anima_controlnet_lllite_chain": active_anima_controlnets,
165
+ "diffsynth_controlnet_chain": active_diffsynth_controlnets,
166
+ "ipadapter_chain": active_ipadapters,
167
+ "flux1_ipadapter_chain": active_flux1_ipadapters,
168
+ "sd3_ipadapter_chain": active_sd3_ipadapters,
169
+ "style_chain": active_styles,
170
+ "conditioning_chain": active_conditioning,
171
+ "reference_latent_chain": active_reference_latents,
172
+ "hidream_o1_reference_chain": active_hidream_o1_reference,
173
+ "vae_chain": [ui_inputs.get('vae_name')] if ui_inputs.get('vae_name') else [],
174
+ "hidream_o1_smoothing_chain": hidream_o1_smoothing_data,
175
+ "pid_chain": [ui_inputs.get('pid_settings', 'OFF')] if is_pid_enabled else [],
176
+ }
177
+
178
+ if isinstance(path_or_components, dict):
179
+ workflow_inputs.update({
180
+ 'unet_name': path_or_components.get('unet'),
181
+ 'vae_name': ui_inputs.get('vae_name') or path_or_components.get('vae'),
182
+ 'clip_name': path_or_components.get('clip'),
183
+ 'clip1_name': path_or_components.get('clip1'),
184
+ 'clip2_name': path_or_components.get('clip2'),
185
+ 'clip3_name': path_or_components.get('clip3'),
186
+ 'clip4_name': path_or_components.get('clip4'),
187
+ 'lora_name': path_or_components.get('lora'),
188
+ })
189
+ else:
190
+ workflow_inputs['model_name'] = path_or_components
191
+
192
+ if task_type == 'txt2img':
193
+ workflow_inputs['width'] = ui_inputs['width']
194
+ workflow_inputs['height'] = ui_inputs['height']
195
+
196
+ workflow = assembler.assemble(workflow_inputs)
197
+
198
+ progress(1.0, desc="All models ready. Requesting GPU for generation...")
199
+
200
+ results = self._execute_gpu_logic(
201
+ self._gpu_logic,
202
+ duration=ui_inputs['zero_gpu_duration'],
203
+ default_duration=60,
204
+ task_name=f"ImageGen ({task_type})",
205
+ ui_inputs=ui_inputs,
206
+ loras_string=loras_string,
207
+ workflow=workflow,
208
+ assembler=assembler,
209
+ progress=progress
210
+ )
211
+
212
+ import json
213
+ import glob
214
+ from PIL import PngImagePlugin
215
+
216
+ prompt_json = json.dumps(workflow)
217
+
218
+ out_dir = os.path.abspath(OUTPUT_DIR)
219
+ os.makedirs(out_dir, exist_ok=True)
220
+
221
+ try:
222
+ existing_files = glob.glob(os.path.join(out_dir, "gen_*.png"))
223
+ existing_files.sort(key=os.path.getmtime)
224
+ while len(existing_files) > 50:
225
+ os.remove(existing_files.pop(0))
226
+ except Exception as e:
227
+ print(f"Warning: Failed to cleanup output dir: {e}")
228
+
229
+ final_results = []
230
+ for img in results:
231
+ if not isinstance(img, Image.Image):
232
+ final_results.append(img)
233
+ continue
234
+
235
+ metadata = PngImagePlugin.PngInfo()
236
+ params_string = img.info.get("parameters", "")
237
+ if params_string:
238
+ metadata.add_text("parameters", params_string)
239
+ metadata.add_text("prompt", prompt_json)
240
+
241
+ filename = f"gen_{random.randint(1000000, 9999999)}.png"
242
+ filepath = os.path.join(out_dir, filename)
243
+ img.save(filepath, "PNG", pnginfo=metadata)
244
+ final_results.append(filepath)
245
+
246
+ results = final_results
247
+
248
+ finally:
249
+ for temp_file in temp_files_to_clean:
250
+ if temp_file and os.path.exists(temp_file):
251
+ os.remove(temp_file)
252
+ print(f"✅ Cleaned up temp file: {temp_file}")
253
+
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
254
  return results
core/pipelines/workflow_executor.py ADDED
@@ -0,0 +1,110 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import torch
2
+ from collections import defaultdict, deque
3
+ from typing import Dict, Any, List
4
+ from comfy_integration.nodes import NODE_CLASS_MAPPINGS
5
+ from utils.app_utils import get_value_at_index
6
+
7
+ class WorkflowExecutor:
8
+ @staticmethod
9
+ def topological_sort(workflow: Dict[str, Any]) -> List[str]:
10
+ graph = defaultdict(list)
11
+ in_degree = {node_id: 0 for node_id in workflow}
12
+
13
+ for node_id, node_info in workflow.items():
14
+ for input_value in node_info.get('inputs', {}).values():
15
+ if isinstance(input_value, list) and len(input_value) == 2 and isinstance(input_value[0], str):
16
+ source_node_id = input_value[0]
17
+ if source_node_id in workflow:
18
+ graph[source_node_id].append(node_id)
19
+ in_degree[node_id] += 1
20
+
21
+ queue = deque([node_id for node_id, degree in in_degree.items() if degree == 0])
22
+
23
+ sorted_nodes = []
24
+ while queue:
25
+ current_node_id = queue.popleft()
26
+ sorted_nodes.append(current_node_id)
27
+
28
+ for neighbor_node_id in graph[current_node_id]:
29
+ in_degree[neighbor_node_id] -= 1
30
+ if in_degree[neighbor_node_id] == 0:
31
+ queue.append(neighbor_node_id)
32
+
33
+ if len(sorted_nodes) != len(workflow):
34
+ raise RuntimeError("Workflow contains a cycle and cannot be executed.")
35
+
36
+ return sorted_nodes
37
+
38
+ @staticmethod
39
+ def execute_workflow(workflow: Dict[str, Any], initial_objects: Dict[str, Any]):
40
+ with torch.no_grad():
41
+ computed_outputs = initial_objects
42
+
43
+ try:
44
+ sorted_node_ids = WorkflowExecutor.topological_sort(workflow)
45
+ print(f"--- [Workflow Executor] Execution order: {sorted_node_ids}")
46
+ except RuntimeError as e:
47
+ print("--- [Workflow Executor] ERROR: Failed to sort workflow. Dumping graph details. ---")
48
+ for node_id, node_info in workflow.items():
49
+ print(f" Node {node_id} ({node_info['class_type']}):")
50
+ for input_name, input_value in node_info['inputs'].items():
51
+ if isinstance(input_value, list) and len(input_value) == 2 and isinstance(input_value[0], str):
52
+ print(f" - {input_name} <- [{input_value[0]}, {input_value[1]}]")
53
+ raise e
54
+
55
+ for node_id in sorted_node_ids:
56
+ if node_id in computed_outputs:
57
+ continue
58
+
59
+ node_info = workflow[node_id]
60
+ class_type = node_info['class_type']
61
+
62
+ is_loader_with_filename = 'Loader' in class_type and any(key.endswith('_name') for key in node_info['inputs'])
63
+ if node_id in initial_objects and is_loader_with_filename:
64
+ continue
65
+
66
+ node_class = NODE_CLASS_MAPPINGS.get(class_type)
67
+ if node_class is None:
68
+ raise RuntimeError(f"Could not find node class '{class_type}'. Is it imported in comfy_integration/nodes.py?")
69
+
70
+ node_instance = node_class()
71
+
72
+ kwargs = {}
73
+ for param_name, param_value in node_info['inputs'].items():
74
+ if isinstance(param_value, list) and len(param_value) == 2 and isinstance(param_value[0], str):
75
+ source_node_id, output_index = param_value
76
+ if source_node_id not in computed_outputs:
77
+ raise RuntimeError(f"Workflow integrity error: Output of node {source_node_id} needed for {node_id} but not yet computed.")
78
+
79
+ source_output_tuple = computed_outputs[source_node_id]
80
+ actual_value = get_value_at_index(source_output_tuple, output_index)
81
+ else:
82
+ actual_value = param_value
83
+
84
+ if '.' in param_name:
85
+ parent_key, child_key = param_name.split('.', 1)
86
+ if parent_key not in kwargs or not isinstance(kwargs[parent_key], dict):
87
+ kwargs[parent_key] = {}
88
+ kwargs[parent_key][child_key] = actual_value
89
+ else:
90
+ kwargs[param_name] = actual_value
91
+
92
+ function_name = getattr(node_class, 'FUNCTION')
93
+ execution_method = getattr(node_instance, function_name)
94
+
95
+ result = execution_method(**kwargs)
96
+ computed_outputs[node_id] = result
97
+
98
+ final_node_id = None
99
+ for node_id in reversed(sorted_node_ids):
100
+ if workflow[node_id]['class_type'] == 'SaveImage':
101
+ final_node_id = node_id
102
+ break
103
+
104
+ if not final_node_id:
105
+ raise RuntimeError("Workflow does not contain a 'SaveImage' node as the output.")
106
+
107
+ save_image_inputs = workflow[final_node_id]['inputs']
108
+ image_source_node_id, image_source_index = save_image_inputs['images']
109
+
110
+ return get_value_at_index(computed_outputs[image_source_node_id], image_source_index)
core/pipelines/workflow_recipes/_partials/conditioning/anima.yaml CHANGED
@@ -52,6 +52,10 @@ dynamic_conditioning_chains:
52
  ksampler_node: "ksampler"
53
  clip_source: "clip_loader:0"
54
 
 
 
 
 
55
  ui_map:
56
  unet_name: "unet_loader:unet_name"
57
  vae_name: "vae_loader:vae_name"
 
52
  ksampler_node: "ksampler"
53
  clip_source: "clip_loader:0"
54
 
55
+ dynamic_pid_chains:
56
+ pid_chain:
57
+ ksampler_node: "ksampler"
58
+
59
  ui_map:
60
  unet_name: "unet_loader:unet_name"
61
  vae_name: "vae_loader:vae_name"
core/pipelines/workflow_recipes/_partials/conditioning/chroma1.yaml CHANGED
@@ -55,6 +55,10 @@ dynamic_conditioning_chains:
55
  ksampler_node: "ksampler"
56
  clip_source: "t5_tokenizer:0"
57
 
 
 
 
 
58
  ui_map:
59
  unet_name: "unet_loader:unet_name"
60
  vae_name: "vae_loader:vae_name"
 
55
  ksampler_node: "ksampler"
56
  clip_source: "t5_tokenizer:0"
57
 
58
+ dynamic_pid_chains:
59
+ pid_chain:
60
+ ksampler_node: "ksampler"
61
+
62
  ui_map:
63
  unet_name: "unet_loader:unet_name"
64
  vae_name: "vae_loader:vae_name"
core/pipelines/workflow_recipes/_partials/conditioning/ernie-image.yaml CHANGED
@@ -48,6 +48,10 @@ dynamic_conditioning_chains:
48
  ksampler_node: "ksampler"
49
  clip_source: "clip_loader:0"
50
 
 
 
 
 
51
  ui_map:
52
  unet_name: "unet_loader:unet_name"
53
  clip_name: "clip_loader:clip_name"
 
48
  ksampler_node: "ksampler"
49
  clip_source: "clip_loader:0"
50
 
51
+ dynamic_pid_chains:
52
+ pid_chain:
53
+ ksampler_node: "ksampler"
54
+
55
  ui_map:
56
  unet_name: "unet_loader:unet_name"
57
  clip_name: "clip_loader:clip_name"
core/pipelines/workflow_recipes/_partials/conditioning/flux1.yaml CHANGED
@@ -56,6 +56,10 @@ dynamic_conditioning_chains:
56
  ksampler_node: "ksampler"
57
  clip_source: "clip_loader:0"
58
 
 
 
 
 
59
  ui_map:
60
  unet_name: "unet_loader:unet_name"
61
  vae_name: "vae_loader:vae_name"
 
56
  ksampler_node: "ksampler"
57
  clip_source: "clip_loader:0"
58
 
59
+ dynamic_pid_chains:
60
+ pid_chain:
61
+ ksampler_node: "ksampler"
62
+
63
  ui_map:
64
  unet_name: "unet_loader:unet_name"
65
  vae_name: "vae_loader:vae_name"
core/pipelines/workflow_recipes/_partials/conditioning/flux2-kv.yaml CHANGED
@@ -86,6 +86,10 @@ dynamic_reference_latent_chains:
86
  ksampler_node: "ksampler"
87
  vae_node: "vae_loader"
88
 
 
 
 
 
89
  ui_map:
90
  unet_name: "unet_loader:unet_name"
91
  clip_name: "clip_loader:clip_name"
 
86
  ksampler_node: "ksampler"
87
  vae_node: "vae_loader"
88
 
89
+ dynamic_pid_chains:
90
+ pid_chain:
91
+ ksampler_node: "ksampler"
92
+
93
  ui_map:
94
  unet_name: "unet_loader:unet_name"
95
  clip_name: "clip_loader:clip_name"
core/pipelines/workflow_recipes/_partials/conditioning/flux2.yaml CHANGED
@@ -78,6 +78,10 @@ dynamic_reference_latent_chains:
78
  ksampler_node: "ksampler"
79
  vae_node: "vae_loader"
80
 
 
 
 
 
81
  ui_map:
82
  unet_name: "unet_loader:unet_name"
83
  clip_name: "clip_loader:clip_name"
 
78
  ksampler_node: "ksampler"
79
  vae_node: "vae_loader"
80
 
81
+ dynamic_pid_chains:
82
+ pid_chain:
83
+ ksampler_node: "ksampler"
84
+
85
  ui_map:
86
  unet_name: "unet_loader:unet_name"
87
  clip_name: "clip_loader:clip_name"
core/pipelines/workflow_recipes/_partials/conditioning/hidream-i1.yaml CHANGED
@@ -44,6 +44,10 @@ dynamic_conditioning_chains:
44
  ksampler_node: "ksampler"
45
  clip_source: "clip_loader:0"
46
 
 
 
 
 
47
  ui_map:
48
  unet_name: "unet_loader:unet_name"
49
  vae_name: "vae_loader:vae_name"
 
44
  ksampler_node: "ksampler"
45
  clip_source: "clip_loader:0"
46
 
47
+ dynamic_pid_chains:
48
+ pid_chain:
49
+ ksampler_node: "ksampler"
50
+
51
  ui_map:
52
  unet_name: "unet_loader:unet_name"
53
  vae_name: "vae_loader:vae_name"
core/pipelines/workflow_recipes/_partials/conditioning/lens.yaml CHANGED
@@ -48,6 +48,10 @@ dynamic_conditioning_chains:
48
  ksampler_node: "ksampler"
49
  clip_source: "clip_loader:0"
50
 
 
 
 
 
51
  ui_map:
52
  unet_name: "unet_loader:unet_name"
53
  clip_name: "clip_loader:clip_name"
 
48
  ksampler_node: "ksampler"
49
  clip_source: "clip_loader:0"
50
 
51
+ dynamic_pid_chains:
52
+ pid_chain:
53
+ ksampler_node: "ksampler"
54
+
55
  ui_map:
56
  unet_name: "unet_loader:unet_name"
57
  clip_name: "clip_loader:clip_name"
core/pipelines/workflow_recipes/_partials/conditioning/longcat-image.yaml CHANGED
@@ -77,6 +77,10 @@ dynamic_conditioning_chains:
77
  ksampler_node: "ksampler"
78
  clip_source: "clip_loader:0"
79
 
 
 
 
 
80
  ui_map:
81
  unet_name: "unet_loader:unet_name"
82
  vae_name: "vae_loader:vae_name"
 
77
  ksampler_node: "ksampler"
78
  clip_source: "clip_loader:0"
79
 
80
+ dynamic_pid_chains:
81
+ pid_chain:
82
+ ksampler_node: "ksampler"
83
+
84
  ui_map:
85
  unet_name: "unet_loader:unet_name"
86
  vae_name: "vae_loader:vae_name"
core/pipelines/workflow_recipes/_partials/conditioning/lumina.yaml CHANGED
@@ -53,5 +53,9 @@ dynamic_conditioning_chains:
53
  ksampler_node: "ksampler"
54
  clip_source: "ckpt_loader:1"
55
 
 
 
 
 
56
  ui_map:
57
  model_name: "ckpt_loader:ckpt_name"
 
53
  ksampler_node: "ksampler"
54
  clip_source: "ckpt_loader:1"
55
 
56
+ dynamic_pid_chains:
57
+ pid_chain:
58
+ ksampler_node: "ksampler"
59
+
60
  ui_map:
61
  model_name: "ckpt_loader:ckpt_name"
core/pipelines/workflow_recipes/_partials/conditioning/newbie-image.yaml CHANGED
@@ -58,6 +58,10 @@ dynamic_conditioning_chains:
58
  ksampler_node: "ksampler"
59
  clip_source: "clip_loader:0"
60
 
 
 
 
 
61
  ui_map:
62
  unet_name: "unet_loader:unet_name"
63
  vae_name: "vae_loader:vae_name"
 
58
  ksampler_node: "ksampler"
59
  clip_source: "clip_loader:0"
60
 
61
+ dynamic_pid_chains:
62
+ pid_chain:
63
+ ksampler_node: "ksampler"
64
+
65
  ui_map:
66
  unet_name: "unet_loader:unet_name"
67
  vae_name: "vae_loader:vae_name"
core/pipelines/workflow_recipes/_partials/conditioning/omnigen2.yaml CHANGED
@@ -53,6 +53,10 @@ dynamic_reference_latent_chains:
53
  ksampler_node: "ksampler"
54
  vae_node: "vae_loader"
55
 
 
 
 
 
56
  ui_map:
57
  unet_name: "unet_loader:unet_name"
58
  vae_name: "vae_loader:vae_name"
 
53
  ksampler_node: "ksampler"
54
  vae_node: "vae_loader"
55
 
56
+ dynamic_pid_chains:
57
+ pid_chain:
58
+ ksampler_node: "ksampler"
59
+
60
  ui_map:
61
  unet_name: "unet_loader:unet_name"
62
  vae_name: "vae_loader:vae_name"
core/pipelines/workflow_recipes/_partials/conditioning/ovis-image.yaml CHANGED
@@ -44,6 +44,10 @@ dynamic_conditioning_chains:
44
  ksampler_node: "ksampler"
45
  clip_source: "clip_loader:0"
46
 
 
 
 
 
47
  ui_map:
48
  unet_name: "unet_loader:unet_name"
49
  vae_name: "vae_loader:vae_name"
 
44
  ksampler_node: "ksampler"
45
  clip_source: "clip_loader:0"
46
 
47
+ dynamic_pid_chains:
48
+ pid_chain:
49
+ ksampler_node: "ksampler"
50
+
51
  ui_map:
52
  unet_name: "unet_loader:unet_name"
53
  vae_name: "vae_loader:vae_name"
core/pipelines/workflow_recipes/_partials/conditioning/qwen-image.yaml CHANGED
@@ -73,6 +73,10 @@ dynamic_conditioning_chains:
73
  ksampler_node: "ksampler"
74
  clip_source: "clip_loader:0"
75
 
 
 
 
 
76
  ui_map:
77
  unet_name: "unet_loader:unet_name"
78
  vae_name: "vae_loader:vae_name"
 
73
  ksampler_node: "ksampler"
74
  clip_source: "clip_loader:0"
75
 
76
+ dynamic_pid_chains:
77
+ pid_chain:
78
+ ksampler_node: "ksampler"
79
+
80
  ui_map:
81
  unet_name: "unet_loader:unet_name"
82
  vae_name: "vae_loader:vae_name"
core/pipelines/workflow_recipes/_partials/conditioning/sd35.yaml CHANGED
@@ -54,5 +54,9 @@ dynamic_conditioning_chains:
54
  ksampler_node: "ksampler"
55
  clip_source: "ckpt_loader:1"
56
 
 
 
 
 
57
  ui_map:
58
  model_name: "ckpt_loader:ckpt_name"
 
54
  ksampler_node: "ksampler"
55
  clip_source: "ckpt_loader:1"
56
 
57
+ dynamic_pid_chains:
58
+ pid_chain:
59
+ ksampler_node: "ksampler"
60
+
61
  ui_map:
62
  model_name: "ckpt_loader:ckpt_name"
core/pipelines/workflow_recipes/_partials/conditioning/sdxl.yaml CHANGED
@@ -59,5 +59,9 @@ dynamic_conditioning_chains:
59
  ksampler_node: "ksampler"
60
  clip_source: "ckpt_loader:1"
61
 
 
 
 
 
62
  ui_map:
63
  model_name: "ckpt_loader:ckpt_name"
 
59
  ksampler_node: "ksampler"
60
  clip_source: "ckpt_loader:1"
61
 
62
+ dynamic_pid_chains:
63
+ pid_chain:
64
+ ksampler_node: "ksampler"
65
+
66
  ui_map:
67
  model_name: "ckpt_loader:ckpt_name"
core/pipelines/workflow_recipes/_partials/conditioning/z-image.yaml CHANGED
@@ -59,6 +59,10 @@ dynamic_diffsynth_controlnet_chains:
59
  ksampler_node: "ksampler"
60
  vae_source: "vae_loader:0"
61
 
 
 
 
 
62
  ui_map:
63
  unet_name: "unet_loader:unet_name"
64
  vae_name: "vae_loader:vae_name"
 
59
  ksampler_node: "ksampler"
60
  vae_source: "vae_loader:0"
61
 
62
+ dynamic_pid_chains:
63
+ pid_chain:
64
+ ksampler_node: "ksampler"
65
+
66
  ui_map:
67
  unet_name: "unet_loader:unet_name"
68
  vae_name: "vae_loader:vae_name"
requirements.txt CHANGED
@@ -1,5 +1,5 @@
1
- comfyui-frontend-package==1.44.19
2
- comfyui-workflow-templates==0.9.92
3
  comfyui-embedded-docs==0.5.2
4
  torch
5
  torchsde
 
1
+ comfyui-frontend-package==1.45.15
2
+ comfyui-workflow-templates==0.9.98
3
  comfyui-embedded-docs==0.5.2
4
  torch
5
  torchsde
ui/events/change_handlers.py CHANGED
@@ -16,7 +16,7 @@ from .config_loaders import (
16
  load_ipadapter_config
17
  )
18
 
19
- def make_update_fn(m_comp, cat_comp, cs_comp, ar_comp, width_comp, height_comp, cn_types, cn_series, cn_filepaths, anima_cn_types, anima_cn_series, anima_cn_filepaths, diffsynth_cn_types, diffsynth_cn_series, diffsynth_cn_filepaths, ipa_preset, lora_acc, cn_acc, anima_cn_acc, diffsynth_cn_acc, ipa_acc, sd3_ipa_acc, flux1_ipa_acc, style_acc, embed_acc, cond_acc, ref_latent_acc, hidream_o1_ref_acc, guidance_comp, prompt_comp, neg_prompt_comp, steps_comp, cfg_comp, sampler_comp, scheduler_comp):
20
  def update_fn(*args):
21
  arch = args[0]
22
  category = args[1]
@@ -66,6 +66,7 @@ def make_update_fn(m_comp, cat_comp, cs_comp, ar_comp, width_comp, height_comp,
66
  if cond_acc: updates[cond_acc] = gr.update(visible=('conditioning' in enabled_chains))
67
  if ref_latent_acc: updates[ref_latent_acc] = gr.update(visible=('reference_latent' in enabled_chains))
68
  if hidream_o1_ref_acc: updates[hidream_o1_ref_acc] = gr.update(visible=('hidream_o1_reference' in enabled_chains))
 
69
 
70
  if cs_comp:
71
  updates[cs_comp] = gr.update(visible=(arch_model_type == "sd15"))
@@ -130,7 +131,7 @@ def make_update_fn(m_comp, cat_comp, cs_comp, ar_comp, width_comp, height_comp,
130
  return update_fn
131
 
132
 
133
- def make_model_change_fn(cat_comp_ref, cs_comp, ar_comp, width_comp, height_comp, cn_types, cn_series, cn_filepaths, anima_cn_types, anima_cn_series, anima_cn_filepaths, diffsynth_cn_types, diffsynth_cn_series, diffsynth_cn_filepaths, arch_comp_ref, ipa_preset, lora_acc, cn_acc, anima_cn_acc, diffsynth_cn_acc, ipa_acc, sd3_ipa_acc, flux1_ipa_acc, style_acc, embed_acc, cond_acc, ref_latent_acc, hidream_o1_ref_acc, guidance_comp, prompt_comp, neg_prompt_comp, steps_comp, cfg_comp, sampler_comp, scheduler_comp):
134
  def change_fn(*args):
135
  model_name = args[0]
136
  idx = 1
@@ -185,6 +186,7 @@ def make_model_change_fn(cat_comp_ref, cs_comp, ar_comp, width_comp, height_comp
185
  if cond_acc: updates[cond_acc] = gr.update(visible=('conditioning' in enabled_chains))
186
  if ref_latent_acc: updates[ref_latent_acc] = gr.update(visible=('reference_latent' in enabled_chains))
187
  if hidream_o1_ref_acc: updates[hidream_o1_ref_acc] = gr.update(visible=('hidream_o1_reference' in enabled_chains))
 
188
 
189
  if cs_comp:
190
  updates[cs_comp] = gr.update(visible=(arch_model_type == "sd15"))
@@ -330,4 +332,4 @@ def on_aspect_ratio_change(ratio_key, model_display_name):
330
 
331
  res_map = RESOLUTION_MAP.get(arch_model_type, RESOLUTION_MAP.get("sdxl", {}))
332
  w, h = res_map.get(ratio_key, (1024, 1024))
333
- return w, h
 
16
  load_ipadapter_config
17
  )
18
 
19
+ def make_update_fn(m_comp, cat_comp, cs_comp, ar_comp, width_comp, height_comp, cn_types, cn_series, cn_filepaths, anima_cn_types, anima_cn_series, anima_cn_filepaths, diffsynth_cn_types, diffsynth_cn_series, diffsynth_cn_filepaths, ipa_preset, lora_acc, cn_acc, anima_cn_acc, diffsynth_cn_acc, ipa_acc, sd3_ipa_acc, flux1_ipa_acc, style_acc, embed_acc, cond_acc, ref_latent_acc, hidream_o1_ref_acc, guidance_comp, prompt_comp, neg_prompt_comp, steps_comp, cfg_comp, sampler_comp, scheduler_comp, pid_acc=None):
20
  def update_fn(*args):
21
  arch = args[0]
22
  category = args[1]
 
66
  if cond_acc: updates[cond_acc] = gr.update(visible=('conditioning' in enabled_chains))
67
  if ref_latent_acc: updates[ref_latent_acc] = gr.update(visible=('reference_latent' in enabled_chains))
68
  if hidream_o1_ref_acc: updates[hidream_o1_ref_acc] = gr.update(visible=('hidream_o1_reference' in enabled_chains))
69
+ if pid_acc: updates[pid_acc] = gr.update(visible=('pid' in enabled_chains))
70
 
71
  if cs_comp:
72
  updates[cs_comp] = gr.update(visible=(arch_model_type == "sd15"))
 
131
  return update_fn
132
 
133
 
134
+ def make_model_change_fn(cat_comp_ref, cs_comp, ar_comp, width_comp, height_comp, cn_types, cn_series, cn_filepaths, anima_cn_types, anima_cn_series, anima_cn_filepaths, diffsynth_cn_types, diffsynth_cn_series, diffsynth_cn_filepaths, arch_comp_ref, ipa_preset, lora_acc, cn_acc, anima_cn_acc, diffsynth_cn_acc, ipa_acc, sd3_ipa_acc, flux1_ipa_acc, style_acc, embed_acc, cond_acc, ref_latent_acc, hidream_o1_ref_acc, guidance_comp, prompt_comp, neg_prompt_comp, steps_comp, cfg_comp, sampler_comp, scheduler_comp, pid_acc=None):
135
  def change_fn(*args):
136
  model_name = args[0]
137
  idx = 1
 
186
  if cond_acc: updates[cond_acc] = gr.update(visible=('conditioning' in enabled_chains))
187
  if ref_latent_acc: updates[ref_latent_acc] = gr.update(visible=('reference_latent' in enabled_chains))
188
  if hidream_o1_ref_acc: updates[hidream_o1_ref_acc] = gr.update(visible=('hidream_o1_reference' in enabled_chains))
189
+ if pid_acc: updates[pid_acc] = gr.update(visible=('pid' in enabled_chains))
190
 
191
  if cs_comp:
192
  updates[cs_comp] = gr.update(visible=(arch_model_type == "sd15"))
 
332
 
333
  res_map = RESOLUTION_MAP.get(arch_model_type, RESOLUTION_MAP.get("sdxl", {}))
334
  w, h = res_map.get(ratio_key, (1024, 1024))
335
+ return w, h
ui/events/main.py CHANGED
@@ -60,6 +60,7 @@ def attach_event_handlers(ui_components, demo):
60
  conditioning_accordion = ui_components.get(f'conditioning_accordion_{prefix}')
61
  ref_latent_accordion = ui_components.get(f'reference_latent_accordion_{prefix}')
62
  hidream_o1_ref_accordion = ui_components.get(f'hidream_o1_reference_accordion_{prefix}')
 
63
 
64
  ipa_preset_list = ui_components.get(f'ipadapter_final_preset_{prefix}')
65
 
@@ -93,6 +94,7 @@ def attach_event_handlers(ui_components, demo):
93
  if conditioning_accordion: outputs.append(conditioning_accordion)
94
  if ref_latent_accordion: outputs.append(ref_latent_accordion)
95
  if hidream_o1_ref_accordion: outputs.append(hidream_o1_ref_accordion)
 
96
  if ipa_preset_list: outputs.append(ipa_preset_list)
97
 
98
  outputs.extend(valid_extra_comps)
@@ -103,7 +105,8 @@ def attach_event_handlers(ui_components, demo):
103
  anima_cn_types_list, anima_cn_series_list, anima_cn_filepaths_list,
104
  diffsynth_cn_types_list, diffsynth_cn_series_list, diffsynth_cn_filepaths_list,
105
  ipa_preset_list, lora_accordion, cn_accordion, anima_cn_accordion, diffsynth_cn_accordion, ipa_accordion, sd3_ipa_accordion, flux1_ipa_accordion, style_accordion, embedding_accordion, conditioning_accordion,
106
- ref_latent_accordion, hidream_o1_ref_accordion, guidance_comp, prompt_comp, neg_prompt_comp, steps_comp, cfg_comp, sampler_comp, scheduler_comp
 
107
  )
108
  inputs = [arch_comp, cat_comp]
109
  if aspect_ratio_comp:
@@ -133,6 +136,7 @@ def attach_event_handlers(ui_components, demo):
133
  if conditioning_accordion: outputs2.append(conditioning_accordion)
134
  if ref_latent_accordion: outputs2.append(ref_latent_accordion)
135
  if hidream_o1_ref_accordion: outputs2.append(hidream_o1_ref_accordion)
 
136
  if ipa_preset_list: outputs2.append(ipa_preset_list)
137
 
138
  outputs2.extend(valid_extra_comps)
@@ -148,7 +152,8 @@ def attach_event_handlers(ui_components, demo):
148
  anima_cn_types_list, anima_cn_series_list, anima_cn_filepaths_list,
149
  diffsynth_cn_types_list, diffsynth_cn_series_list, diffsynth_cn_filepaths_list,
150
  arch_comp, ipa_preset_list, lora_accordion, cn_accordion, anima_cn_accordion, diffsynth_cn_accordion, ipa_accordion, sd3_ipa_accordion, flux1_ipa_accordion, style_accordion, embedding_accordion, conditioning_accordion,
151
- ref_latent_accordion, hidream_o1_ref_accordion, guidance_comp, prompt_comp, neg_prompt_comp, steps_comp, cfg_comp, sampler_comp, scheduler_comp
 
152
  )
153
  model_comp.change(fn=change_fn, inputs=inputs2, outputs=outputs2)
154
 
@@ -237,4 +242,4 @@ def attach_event_handlers(ui_components, demo):
237
  height_component = ui_components.get(f'height_{prefix}') or ui_components.get(f'{prefix}_height')
238
  model_dropdown = ui_components.get(f'base_model_{prefix}')
239
  if aspect_ratio_dropdown and width_component and height_component and model_dropdown:
240
- aspect_ratio_dropdown.change(fn=on_aspect_ratio_change, inputs=[aspect_ratio_dropdown, model_dropdown], outputs=[width_component, height_component], show_progress=False)
 
60
  conditioning_accordion = ui_components.get(f'conditioning_accordion_{prefix}')
61
  ref_latent_accordion = ui_components.get(f'reference_latent_accordion_{prefix}')
62
  hidream_o1_ref_accordion = ui_components.get(f'hidream_o1_reference_accordion_{prefix}')
63
+ pid_accordion = ui_components.get(f'pid_accordion_{prefix}')
64
 
65
  ipa_preset_list = ui_components.get(f'ipadapter_final_preset_{prefix}')
66
 
 
94
  if conditioning_accordion: outputs.append(conditioning_accordion)
95
  if ref_latent_accordion: outputs.append(ref_latent_accordion)
96
  if hidream_o1_ref_accordion: outputs.append(hidream_o1_ref_accordion)
97
+ if pid_accordion: outputs.append(pid_accordion)
98
  if ipa_preset_list: outputs.append(ipa_preset_list)
99
 
100
  outputs.extend(valid_extra_comps)
 
105
  anima_cn_types_list, anima_cn_series_list, anima_cn_filepaths_list,
106
  diffsynth_cn_types_list, diffsynth_cn_series_list, diffsynth_cn_filepaths_list,
107
  ipa_preset_list, lora_accordion, cn_accordion, anima_cn_accordion, diffsynth_cn_accordion, ipa_accordion, sd3_ipa_accordion, flux1_ipa_accordion, style_accordion, embedding_accordion, conditioning_accordion,
108
+ ref_latent_accordion, hidream_o1_ref_accordion, guidance_comp, prompt_comp, neg_prompt_comp, steps_comp, cfg_comp, sampler_comp, scheduler_comp,
109
+ pid_acc=pid_accordion
110
  )
111
  inputs = [arch_comp, cat_comp]
112
  if aspect_ratio_comp:
 
136
  if conditioning_accordion: outputs2.append(conditioning_accordion)
137
  if ref_latent_accordion: outputs2.append(ref_latent_accordion)
138
  if hidream_o1_ref_accordion: outputs2.append(hidream_o1_ref_accordion)
139
+ if pid_accordion: outputs2.append(pid_accordion)
140
  if ipa_preset_list: outputs2.append(ipa_preset_list)
141
 
142
  outputs2.extend(valid_extra_comps)
 
152
  anima_cn_types_list, anima_cn_series_list, anima_cn_filepaths_list,
153
  diffsynth_cn_types_list, diffsynth_cn_series_list, diffsynth_cn_filepaths_list,
154
  arch_comp, ipa_preset_list, lora_accordion, cn_accordion, anima_cn_accordion, diffsynth_cn_accordion, ipa_accordion, sd3_ipa_accordion, flux1_ipa_accordion, style_accordion, embedding_accordion, conditioning_accordion,
155
+ ref_latent_accordion, hidream_o1_ref_accordion, guidance_comp, prompt_comp, neg_prompt_comp, steps_comp, cfg_comp, sampler_comp, scheduler_comp,
156
+ pid_acc=pid_accordion
157
  )
158
  model_comp.change(fn=change_fn, inputs=inputs2, outputs=outputs2)
159
 
 
242
  height_component = ui_components.get(f'height_{prefix}') or ui_components.get(f'{prefix}_height')
243
  model_dropdown = ui_components.get(f'base_model_{prefix}')
244
  if aspect_ratio_dropdown and width_component and height_component and model_dropdown:
245
+ aspect_ratio_dropdown.change(fn=on_aspect_ratio_change, inputs=[aspect_ratio_dropdown, model_dropdown], outputs=[width_component, height_component], show_progress=False)
ui/events/run_handlers.py CHANGED
@@ -19,6 +19,9 @@ def create_run_event(prefix: str, task_type: str, ui_components: dict):
19
  'task_type': gr.State(task_type)
20
  }
21
 
 
 
 
22
  if task_type not in ['img2img', 'inpaint']:
23
  run_inputs_map.update({
24
  'width': ui_components.get(f'width_{prefix}') or ui_components.get(f'{prefix}_width'),
@@ -97,4 +100,4 @@ def create_run_event(prefix: str, task_type: str, ui_components: dict):
97
  fn=lambda *args, progress=gr.Progress(track_tqdm=True): generate_image_wrapper(create_ui_inputs_dict(*args), progress),
98
  inputs=input_list_flat,
99
  outputs=[res_gal]
100
- )
 
19
  'task_type': gr.State(task_type)
20
  }
21
 
22
+ if ui_components.get(f'pid_settings_{prefix}'):
23
+ run_inputs_map['pid_settings'] = ui_components[f'pid_settings_{prefix}']
24
+
25
  if task_type not in ['img2img', 'inpaint']:
26
  run_inputs_map.update({
27
  'width': ui_components.get(f'width_{prefix}') or ui_components.get(f'{prefix}_width'),
 
100
  fn=lambda *args, progress=gr.Progress(track_tqdm=True): generate_image_wrapper(create_ui_inputs_dict(*args), progress),
101
  inputs=input_list_flat,
102
  outputs=[res_gal]
103
+ )
ui/shared/txt2img_ui.py CHANGED
@@ -6,7 +6,8 @@ from .ui_components import (
6
  create_conditioning_ui, create_vae_override_ui,
7
  create_model_architecture_filter_ui, create_category_filter_ui,
8
  create_sd3_ipadapter_ui, create_flux1_ipadapter_ui, create_style_ui,
9
- create_reference_latent_ui, create_hidream_o1_reference_ui
 
10
  )
11
 
12
  def create_ui():
@@ -53,5 +54,6 @@ def create_ui():
53
  components.update(create_reference_latent_ui(prefix))
54
  components.update(create_hidream_o1_reference_ui(prefix))
55
  components.update(create_vae_override_ui(prefix))
 
56
 
57
  return components
 
6
  create_conditioning_ui, create_vae_override_ui,
7
  create_model_architecture_filter_ui, create_category_filter_ui,
8
  create_sd3_ipadapter_ui, create_flux1_ipadapter_ui, create_style_ui,
9
+ create_reference_latent_ui, create_hidream_o1_reference_ui,
10
+ create_pid_ui
11
  )
12
 
13
  def create_ui():
 
54
  components.update(create_reference_latent_ui(prefix))
55
  components.update(create_hidream_o1_reference_ui(prefix))
56
  components.update(create_vae_override_ui(prefix))
57
+ components.update(create_pid_ui(prefix))
58
 
59
  return components
ui/shared/ui_components.py CHANGED
@@ -548,8 +548,8 @@ def create_vae_override_ui(prefix: str):
548
  key = lambda name: f"{name}_{prefix}"
549
  source_choices = ["None"] + LORA_SOURCE_CHOICES
550
 
551
- with gr.Accordion("VAE Settings (Override)", open=False) as accordion:
552
- components[key('vae_accordion')] = accordion
553
  gr.Markdown("💡 **Tip:** When downloading from Civitai, please use the **Version ID**, not the Model ID. You can find the Version ID in the URL (e.g., `civitai.com/models/123?modelVersionId=456`) or under the model's download button.")
554
  with gr.Row():
555
  components[key('vae_source')] = gr.Dropdown(
@@ -638,4 +638,21 @@ def create_hidream_o1_reference_ui(prefix: str, max_units=10):
638
 
639
  components[key('all_hidream_o1_reference_components_flat')] = ref_image_inputs
640
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
641
  return components
 
548
  key = lambda name: f"{name}_{prefix}"
549
  source_choices = ["None"] + LORA_SOURCE_CHOICES
550
 
551
+ with gr.Accordion("VAE Settings (Override)", open=False) as vae_accordion:
552
+ components[key('vae_accordion')] = vae_accordion
553
  gr.Markdown("💡 **Tip:** When downloading from Civitai, please use the **Version ID**, not the Model ID. You can find the Version ID in the URL (e.g., `civitai.com/models/123?modelVersionId=456`) or under the model's download button.")
554
  with gr.Row():
555
  components[key('vae_source')] = gr.Dropdown(
 
638
 
639
  components[key('all_hidream_o1_reference_components_flat')] = ref_image_inputs
640
 
641
+ return components
642
+
643
+ def create_pid_ui(prefix: str):
644
+ components = {}
645
+ key = lambda name: f"{name}_{prefix}"
646
+
647
+ with gr.Accordion("PiD Settings", open=False, visible=('pid' in default_enabled_chains)) as pid_accordion:
648
+ components[key('pid_accordion')] = pid_accordion
649
+ gr.Markdown("💡 **Tip:** Use PiD (Pixel Diffusion Decoder) instead of the VAE Decoder for 4x decoding.")
650
+ with gr.Row():
651
+ components[key('pid_settings')] = gr.Dropdown(
652
+ label="PiD Mode",
653
+ choices=["OFF", "ON"],
654
+ value="OFF",
655
+ interactive=True
656
+ )
657
+
658
  return components
yaml/file_list.yaml CHANGED
@@ -408,6 +408,27 @@ file:
408
  source: "hf"
409
  repo_id: "Comfy-Org/PixelDiT"
410
  repository_file_path: "diffusion_models/pixeldit_1300m_1024px_mxfp8.safetensors"
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
411
  # Lens
412
  - filename: "lens_mxfp8.safetensors"
413
  source: "hf"
 
408
  source: "hf"
409
  repo_id: "Comfy-Org/PixelDiT"
410
  repository_file_path: "diffusion_models/pixeldit_1300m_1024px_mxfp8.safetensors"
411
+ # PiD
412
+ - filename: "pid_sdxl_1024_to_4096_4step_bf16.safetensors"
413
+ source: "hf"
414
+ repo_id: "Comfy-Org/PixelDiT"
415
+ repository_file_path: "diffusion_models/pid_sdxl_1024_to_4096_4step_bf16.safetensors"
416
+ - filename: "pid_sd3_1024_to_4096_4step_bf16.safetensors"
417
+ source: "hf"
418
+ repo_id: "Comfy-Org/PixelDiT"
419
+ repository_file_path: "diffusion_models/pid_sd3_1024_to_4096_4step_bf16.safetensors"
420
+ - filename: "pid_flux1_1024_to_4096_4step_mxfp8.safetensors"
421
+ source: "hf"
422
+ repo_id: "Comfy-Org/PixelDiT"
423
+ repository_file_path: "diffusion_models/pid_flux1_1024_to_4096_4step_mxfp8.safetensors"
424
+ - filename: "pid_qwenimage_1024_to_4096_4step_bf16.safetensors"
425
+ source: "hf"
426
+ repo_id: "Comfy-Org/PixelDiT"
427
+ repository_file_path: "diffusion_models/pid_qwenimage_1024_to_4096_4step_bf16.safetensors"
428
+ - filename: "pid_flux2_1024_to_4096_4step_mxfp8.safetensors"
429
+ source: "hf"
430
+ repo_id: "Comfy-Org/PixelDiT"
431
+ repository_file_path: "diffusion_models/pid_flux2_1024_to_4096_4step_mxfp8.safetensors"
432
  # Lens
433
  - filename: "lens_mxfp8.safetensors"
434
  source: "hf"
yaml/image_gen_features.yaml CHANGED
@@ -1,11 +1,12 @@
1
  default:
2
  enabled_chains:
3
- - lora
4
- - controlnet
5
- - ipadapter
6
- - embedding
7
- - style
8
- - conditioning
 
9
 
10
  pixeldit:
11
  enabled_chains:
@@ -14,119 +15,129 @@ pixeldit:
14
  lens:
15
  enabled_chains:
16
  - conditioning
 
17
 
18
  ernie-image:
19
  enabled_chains:
20
- - lora
21
- - conditioning
22
-
23
- flux2:
24
- enabled_chains:
25
- - lora
26
- - conditioning
27
- - reference_latent
28
-
29
- flux2-kv:
30
- enabled_chains:
31
- - lora
32
- - conditioning
33
- - reference_latent
34
-
35
- z-image:
36
- enabled_chains:
37
- - lora
38
- - conditioning
39
- - controlnet_model_patch
40
-
41
- qwen-image:
42
  enabled_chains:
43
- - lora
44
- - controlnet
45
- - conditioning
46
-
 
47
  longcat-image:
48
  enabled_chains:
49
- - lora
50
- - conditioning
51
-
52
- anima:
53
- enabled_chains:
54
- - lora
55
- - anima_controlnet_lllite
56
- - conditioning
57
-
58
  newbie-image:
59
  enabled_chains:
60
- - lora
61
- - embedding
62
- - conditioning
63
-
64
- omnigen2:
 
65
  enabled_chains:
66
- - conditioning
67
- - reference_latent
68
-
 
 
69
  lumina:
70
  enabled_chains:
71
- - lora
72
- - embedding
73
- - conditioning
74
-
75
- ovis-image:
76
- enabled_chains:
77
- - conditioning
78
-
79
  sd35:
80
  enabled_chains:
81
- - lora
82
- - controlnet
83
- - embedding
84
- - conditioning
85
- - sd3_ipadapter
86
-
 
87
  sdxl:
88
  enabled_chains:
89
- - lora
90
- - controlnet
91
- - ipadapter
92
- - embedding
93
- - conditioning
94
-
 
95
  sd15:
96
  enabled_chains:
97
- - lora
98
- - controlnet
99
- - ipadapter
100
- - embedding
101
- - conditioning
102
-
 
 
 
 
 
 
 
 
 
 
 
 
 
 
103
  flux1:
104
  enabled_chains:
105
- - lora
106
- - controlnet
107
- - style
108
- - conditioning
109
- - flux1_ipadapter
110
-
 
 
 
 
 
 
 
 
 
 
 
 
 
111
  hidream-o1:
112
  enabled_chains:
113
  - lora
114
  - conditioning
115
  - hidream_o1_reference
116
-
117
  hidream-i1:
118
  enabled_chains:
119
  - lora
120
  - conditioning
121
-
122
- chroma1:
123
  enabled_chains:
124
- - conditioning
 
125
 
126
- chroma1-radiance:
127
  enabled_chains:
128
- - conditioning
 
 
129
 
130
- hunyuanimage:
131
  enabled_chains:
132
- - conditioning
 
 
 
1
  default:
2
  enabled_chains:
3
+ - lora
4
+ - controlnet
5
+ - ipadapter
6
+ - embedding
7
+ - style
8
+ - conditioning
9
+ - vae
10
 
11
  pixeldit:
12
  enabled_chains:
 
15
  lens:
16
  enabled_chains:
17
  - conditioning
18
+ - pid
19
 
20
  ernie-image:
21
  enabled_chains:
22
+ - conditioning
23
+ - pid
24
+ anima:
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
25
  enabled_chains:
26
+ - lora
27
+ - anima_controlnet_lllite
28
+ - conditioning
29
+ - vae
30
+ - pid
31
  longcat-image:
32
  enabled_chains:
33
+ - lora
34
+ - conditioning
35
+ - pid
 
 
 
 
 
 
36
  newbie-image:
37
  enabled_chains:
38
+ - lora
39
+ - embedding
40
+ - conditioning
41
+ - vae
42
+ - pid
43
+ z-image:
44
  enabled_chains:
45
+ - lora
46
+ - conditioning
47
+ - controlnet_model_patch
48
+ - vae
49
+ - pid
50
  lumina:
51
  enabled_chains:
52
+ - lora
53
+ - embedding
54
+ - conditioning
55
+ - vae
56
+ - pid
 
 
 
57
  sd35:
58
  enabled_chains:
59
+ - lora
60
+ - controlnet
61
+ - embedding
62
+ - conditioning
63
+ - sd3_ipadapter
64
+ - vae
65
+ - pid
66
  sdxl:
67
  enabled_chains:
68
+ - lora
69
+ - controlnet
70
+ - ipadapter
71
+ - embedding
72
+ - conditioning
73
+ - vae
74
+ - pid
75
  sd15:
76
  enabled_chains:
77
+ - lora
78
+ - controlnet
79
+ - ipadapter
80
+ - embedding
81
+ - conditioning
82
+ - vae
83
+ flux2:
84
+ enabled_chains:
85
+ - lora
86
+ - conditioning
87
+ - reference_latent
88
+ - vae
89
+ - pid
90
+ flux2-kv:
91
+ enabled_chains:
92
+ - lora
93
+ - conditioning
94
+ - reference_latent
95
+ - vae
96
+ - pid
97
  flux1:
98
  enabled_chains:
99
+ - lora
100
+ - controlnet
101
+ - style
102
+ - conditioning
103
+ - flux1_ipadapter
104
+ - vae
105
+ - pid
106
+ omnigen2:
107
+ enabled_chains:
108
+ - conditioning
109
+ - reference_latent
110
+ - pid
111
+ qwen-image:
112
+ enabled_chains:
113
+ - lora
114
+ - controlnet
115
+ - conditioning
116
+ - vae
117
+ - pid
118
  hidream-o1:
119
  enabled_chains:
120
  - lora
121
  - conditioning
122
  - hidream_o1_reference
 
123
  hidream-i1:
124
  enabled_chains:
125
  - lora
126
  - conditioning
127
+ - pid
128
+ hunyuanimage:
129
  enabled_chains:
130
+ - conditioning
131
+ - vae
132
 
133
+ ovis-image:
134
  enabled_chains:
135
+ - conditioning
136
+ - vae
137
+ - pid
138
 
139
+ chroma1:
140
  enabled_chains:
141
+ - conditioning
142
+ - vae
143
+ - pid
yaml/injectors.yaml CHANGED
@@ -27,6 +27,8 @@ injector_definitions:
27
  module: "chain_injectors.hidream_o1_smoothing_injector"
28
  dynamic_hidream_o1_reference_chains:
29
  module: "chain_injectors.hidream_o1_reference_injector"
 
 
30
 
31
  injector_order:
32
  - dynamic_vae_chains
@@ -42,4 +44,5 @@ injector_order:
42
  - dynamic_controlnet_chains
43
  - dynamic_anima_controlnet_lllite_chains
44
  - dynamic_hidream_o1_smoothing_chains
45
- - dynamic_hidream_o1_reference_chains
 
 
27
  module: "chain_injectors.hidream_o1_smoothing_injector"
28
  dynamic_hidream_o1_reference_chains:
29
  module: "chain_injectors.hidream_o1_reference_injector"
30
+ dynamic_pid_chains:
31
+ module: "chain_injectors.pid_injector"
32
 
33
  injector_order:
34
  - dynamic_vae_chains
 
44
  - dynamic_controlnet_chains
45
  - dynamic_anima_controlnet_lllite_chains
46
  - dynamic_hidream_o1_smoothing_chains
47
+ - dynamic_hidream_o1_reference_chains
48
+ - dynamic_pid_chains
yaml/pid.yaml ADDED
@@ -0,0 +1,16 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ PiD:
2
+ - filepath: "pid_flux2_1024_to_4096_4step_mxfp8.safetensors"
3
+ latent_format: "flux"
4
+ architectures: ["flux2", "flux2-kv", "lens", "ernie-image"]
5
+ - filepath: "pid_qwenimage_1024_to_4096_4step_bf16.safetensors"
6
+ latent_format: "qwenimage"
7
+ architectures: ["anima", "qwen-image"]
8
+ - filepath: "pid_flux1_1024_to_4096_4step_mxfp8.safetensors"
9
+ latent_format: "flux"
10
+ architectures: ["longcat-image", "newbie-image", "z-image", "ovis-image", "omnigen2", "chroma1", "hidream-i1", "flux1"]
11
+ - filepath: "pid_sd3_1024_to_4096_4step_bf16.safetensors"
12
+ latent_format: "sd3"
13
+ architectures: ["sd35"]
14
+ - filepath: "pid_sdxl_1024_to_4096_4step_bf16.safetensors"
15
+ latent_format: "sdxl"
16
+ architectures: ["sdxl"]