Commit ·
45e7b61
1
Parent(s): fc39079
Remove SaveImage node, adjust workflow executor, restore tensor-to-image saving and deduplication
Browse files
core/pipelines/sd_image_pipeline.py
CHANGED
|
@@ -34,10 +34,10 @@ class SdImagePipeline(BasePipeline):
|
|
| 34 |
"""
|
| 35 |
progress(0.4, desc="Executing workflow...")
|
| 36 |
initial_objects = {}
|
| 37 |
-
# Execute the workflow;
|
| 38 |
decoded_images_tensor = WorkflowExecutor.execute_workflow(workflow, initial_objects=initial_objects)
|
| 39 |
|
| 40 |
-
# Convert tensors to PIL images,
|
| 41 |
out_dir = os.path.abspath(OUTPUT_DIR)
|
| 42 |
os.makedirs(out_dir, exist_ok=True)
|
| 43 |
saved_file_paths = []
|
|
@@ -65,22 +65,19 @@ class SdImagePipeline(BasePipeline):
|
|
| 65 |
pil_image.save(filepath, "PNG")
|
| 66 |
saved_file_paths.append(filepath)
|
| 67 |
|
| 68 |
-
#
|
| 69 |
-
# manual save, the output directory may contain duplicate images. Deduplicate by
|
| 70 |
-
# content hash (SHA‑256) and return a list of unique file paths.
|
| 71 |
import hashlib
|
| 72 |
unique_hashes = set()
|
| 73 |
deduped_paths = []
|
| 74 |
-
for p in
|
| 75 |
-
full_path = os.path.join(out_dir, p)
|
| 76 |
try:
|
| 77 |
-
with open(
|
| 78 |
h = hashlib.sha256(f.read()).hexdigest()
|
| 79 |
if h not in unique_hashes:
|
| 80 |
unique_hashes.add(h)
|
| 81 |
-
deduped_paths.append(
|
| 82 |
except Exception:
|
| 83 |
-
|
| 84 |
return deduped_paths
|
| 85 |
|
| 86 |
def run(self, ui_inputs: Dict, progress):
|
|
|
|
| 34 |
"""
|
| 35 |
progress(0.4, desc="Executing workflow...")
|
| 36 |
initial_objects = {}
|
| 37 |
+
# Execute the workflow; it returns image tensor(s) from the VAE Decode node.
|
| 38 |
decoded_images_tensor = WorkflowExecutor.execute_workflow(workflow, initial_objects=initial_objects)
|
| 39 |
|
| 40 |
+
# Convert tensors to PIL images, embed metadata and save them to the output directory.
|
| 41 |
out_dir = os.path.abspath(OUTPUT_DIR)
|
| 42 |
os.makedirs(out_dir, exist_ok=True)
|
| 43 |
saved_file_paths = []
|
|
|
|
| 65 |
pil_image.save(filepath, "PNG")
|
| 66 |
saved_file_paths.append(filepath)
|
| 67 |
|
| 68 |
+
# Deduplicate by file content hash (SHA‑256) to avoid identical images.
|
|
|
|
|
|
|
| 69 |
import hashlib
|
| 70 |
unique_hashes = set()
|
| 71 |
deduped_paths = []
|
| 72 |
+
for p in saved_file_paths:
|
|
|
|
| 73 |
try:
|
| 74 |
+
with open(p, "rb") as f:
|
| 75 |
h = hashlib.sha256(f.read()).hexdigest()
|
| 76 |
if h not in unique_hashes:
|
| 77 |
unique_hashes.add(h)
|
| 78 |
+
deduped_paths.append(p)
|
| 79 |
except Exception:
|
| 80 |
+
deduped_paths.append(p)
|
| 81 |
return deduped_paths
|
| 82 |
|
| 83 |
def run(self, ui_inputs: Dict, progress):
|
core/pipelines/workflow_executor.py
CHANGED
|
@@ -95,16 +95,17 @@ class WorkflowExecutor:
|
|
| 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 |
-
|
| 101 |
-
|
| 102 |
-
|
| 103 |
-
|
| 104 |
-
|
| 105 |
-
|
| 106 |
-
|
| 107 |
-
|
| 108 |
-
|
| 109 |
-
|
| 110 |
-
|
|
|
|
| 95 |
result = execution_method(**kwargs)
|
| 96 |
computed_outputs[node_id] = result
|
| 97 |
|
| 98 |
+
# Determine the final output. If a SaveImage node exists, use its input image.
|
| 99 |
final_node_id = None
|
| 100 |
for node_id in reversed(sorted_node_ids):
|
| 101 |
+
if workflow[node_id]['class_type'] == 'SaveImage':
|
| 102 |
+
final_node_id = node_id
|
| 103 |
+
break
|
| 104 |
+
if final_node_id:
|
| 105 |
+
save_image_inputs = workflow[final_node_id]['inputs']
|
| 106 |
+
image_source_node_id, image_source_index = save_image_inputs['images']
|
| 107 |
+
return get_value_at_index(computed_outputs[image_source_node_id], image_source_index)
|
| 108 |
+
else:
|
| 109 |
+
# No SaveImage node – return the output of the last node in execution order.
|
| 110 |
+
last_node_id = sorted_node_ids[-1]
|
| 111 |
+
return computed_outputs[last_node_id]
|
core/pipelines/workflow_recipes/_partials/_base_sampler_sd.yaml
CHANGED
|
@@ -1,36 +1,29 @@
|
|
| 1 |
-
nodes:
|
| 2 |
-
pos_prompt:
|
| 3 |
-
class_type: CLIPTextEncode
|
| 4 |
-
title: "CLIP Text Encode (Positive)"
|
| 5 |
-
neg_prompt:
|
| 6 |
-
class_type: CLIPTextEncode
|
| 7 |
-
title: "CLIP Text Encode (Negative)"
|
| 8 |
-
ksampler:
|
| 9 |
-
class_type: KSampler
|
| 10 |
-
title: "KSampler"
|
| 11 |
-
params:
|
| 12 |
-
denoise: 1.0
|
| 13 |
-
vae_decode:
|
| 14 |
-
class_type: VAEDecode
|
| 15 |
-
title: "VAE Decode"
|
| 16 |
-
|
| 17 |
-
|
| 18 |
-
|
| 19 |
-
|
| 20 |
-
|
| 21 |
-
|
| 22 |
-
|
| 23 |
-
|
| 24 |
-
|
| 25 |
-
|
| 26 |
-
|
| 27 |
-
|
| 28 |
-
|
| 29 |
-
|
| 30 |
-
seed: "ksampler:seed"
|
| 31 |
-
steps: "ksampler:steps"
|
| 32 |
-
cfg: "ksampler:cfg"
|
| 33 |
-
sampler_name: "ksampler:sampler_name"
|
| 34 |
-
scheduler: "ksampler:scheduler"
|
| 35 |
-
denoise: "ksampler:denoise"
|
| 36 |
-
filename_prefix: "save_image:filename_prefix"
|
|
|
|
| 1 |
+
nodes:
|
| 2 |
+
pos_prompt:
|
| 3 |
+
class_type: CLIPTextEncode
|
| 4 |
+
title: "CLIP Text Encode (Positive)"
|
| 5 |
+
neg_prompt:
|
| 6 |
+
class_type: CLIPTextEncode
|
| 7 |
+
title: "CLIP Text Encode (Negative)"
|
| 8 |
+
ksampler:
|
| 9 |
+
class_type: KSampler
|
| 10 |
+
title: "KSampler"
|
| 11 |
+
params:
|
| 12 |
+
denoise: 1.0
|
| 13 |
+
vae_decode:
|
| 14 |
+
class_type: VAEDecode
|
| 15 |
+
title: "VAE Decode"
|
| 16 |
+
|
| 17 |
+
connections:
|
| 18 |
+
- from: "ksampler:0"
|
| 19 |
+
to: "vae_decode:samples"
|
| 20 |
+
|
| 21 |
+
ui_map:
|
| 22 |
+
positive_prompt: "pos_prompt:text"
|
| 23 |
+
negative_prompt: "neg_prompt:text"
|
| 24 |
+
seed: "ksampler:seed"
|
| 25 |
+
steps: "ksampler:steps"
|
| 26 |
+
cfg: "ksampler:cfg"
|
| 27 |
+
sampler_name: "ksampler:sampler_name"
|
| 28 |
+
scheduler: "ksampler:scheduler"
|
| 29 |
+
denoise: "ksampler:denoise"
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|