assistanttttttt commited on
Commit
adee902
·
1 Parent(s): 07b236d

Deduplicate SaveImage outputs and hash-filter duplicate files}

Browse files
Files changed (1) hide show
  1. core/pipelines/sd_image_pipeline.py +46 -69
core/pipelines/sd_image_pipeline.py CHANGED
@@ -3,9 +3,9 @@ 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
@@ -26,34 +26,40 @@ class SdImagePipeline(BasePipeline):
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...")
@@ -212,46 +218,17 @@ class SdImagePipeline(BasePipeline):
212
  progress=progress
213
  )
214
 
215
- import json
216
- import glob
217
- from PIL import PngImagePlugin
218
-
219
- prompt_json = json.dumps(workflow)
220
-
221
- out_dir = os.path.abspath(OUTPUT_DIR)
222
- os.makedirs(out_dir, exist_ok=True)
223
-
224
- try:
225
- existing_files = glob.glob(os.path.join(out_dir, "gen_*.png"))
226
- existing_files.sort(key=os.path.getmtime)
227
- while len(existing_files) > 50:
228
- os.remove(existing_files.pop(0))
229
- except Exception as e:
230
- print(f"Warning: Failed to cleanup output dir: {e}")
231
-
232
- final_results = []
233
- for img in results:
234
- if not isinstance(img, Image.Image):
235
- final_results.append(img)
236
- continue
237
-
238
- metadata = PngImagePlugin.PngInfo()
239
- params_string = img.info.get("parameters", "")
240
- if params_string:
241
- metadata.add_text("parameters", params_string)
242
- metadata.add_text("prompt", prompt_json)
243
-
244
- filename = f"gen_{random.randint(1000000, 9999999)}.png"
245
- filepath = os.path.join(out_dir, filename)
246
- img.save(filepath, "PNG", pnginfo=metadata)
247
- final_results.append(filepath)
248
 
249
- results = final_results
250
-
251
- finally:
252
- for temp_file in temp_files_to_clean:
253
- if temp_file and os.path.exists(temp_file):
254
- os.remove(temp_file)
255
- print(f"✅ Cleaned up temp file: {temp_file}")
256
-
257
- return results
 
3
  import shutil
4
  import torch
5
  import gradio as gr
 
6
  from typing import List, Dict, Any
7
 
8
+
9
  from .base_pipeline import BasePipeline
10
  from core.settings import *
11
  from utils.app_utils import sanitize_prompt
 
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
+ """Execute the ComfyUI workflow and return the file paths saved by the SaveImage node.
30
+ The original implementation converted the tensor output to PIL images and then saved
31
+ them again, causing duplicate files. Here we rely on the SaveImage node to write the
32
+ images to the output directory and simply return the path(s) it provides.
33
+ """
34
  progress(0.4, desc="Executing workflow...")
 
35
  initial_objects = {}
36
+ # Execute the workflow; the SaveImage node returns its saved file path(s).
37
+ saved_output = WorkflowExecutor.execute_workflow(workflow, initial_objects=initial_objects)
38
+ # Execute the workflow; the SaveImage node returns the saved file path(s).
39
+ # Ensure we have a list of paths.
40
+ if isinstance(saved_output, (list, tuple)):
41
+ saved_paths = list(saved_output)
42
+ else:
43
+ saved_paths = [saved_output]
44
+ # The workflow may contain more than one SaveImage node (e.g., from base sampler
45
+ # plus conditioning partials), which can produce duplicate images with different
46
+ # filenames. To avoid showing duplicate thumbnails in the Gallery, deduplicate the
47
+ # list by file content hash (SHA‑256). This keeps the first occurrence of each unique
48
+ # image.
49
+ import hashlib
50
+ unique_hashes = set()
51
+ deduped_paths = []
52
+ for p in saved_paths:
53
+ try:
54
+ with open(p, "rb") as f:
55
+ h = hashlib.sha256(f.read()).hexdigest()
56
+ if h not in unique_hashes:
57
+ unique_hashes.add(h)
58
+ deduped_paths.append(p)
59
+ except Exception as e:
60
+ # If reading fails, keep the path (will surface later as a missing file).
61
+ deduped_paths.append(p)
62
+ return deduped_paths
63
 
64
  def run(self, ui_inputs: Dict, progress):
65
  progress(0, desc="Preparing models...")
 
218
  progress=progress
219
  )
220
 
221
+ # The workflow's SaveImage node already writes the generated images to the
222
+ # output directory and returns the file path(s). No additional saving or
223
+ # metadata injection is required.
224
+ # Clean up any temporary files that were created for the workflow inputs.
225
+ # The ``finally`` block below already handles this cleanup.
226
+ # Simply return the list of file paths obtained from the workflow.
227
+ # (If ``results`` is a single string, convert it to a list for Gradio.)
228
+ if isinstance(results, (list, tuple)):
229
+ final_results = list(results)
230
+ else:
231
+ final_results = [results]
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
232
 
233
+ # The surrounding ``try``/``finally`` handles temp file cleanup.
234
+ return final_results