1010import numpy as np
1111import logging
1212
13+
1314class PhotoDoodleSamplerAdvanced (SamplerCustomAdvanced ):
1415 """
1516 扩展的自定义采样器,支持额外的条件图像和位置编码选项
1617 自动处理掩码和引导强度
1718 """
18-
19+
1920 @classmethod
2021 def INPUT_TYPES (s ):
2122 # 继承原始输入类型并添加新的参数
2223 original_inputs = SamplerCustomAdvanced .INPUT_TYPES ()
23-
24+
2425 # 添加新的必需参数,但移除不需要的参数
25- original_inputs ["required" ].update ({
26- "condition_image" : ("LATENT" , {"default" : None }),
27- "use_clone_pe" : ("BOOLEAN" , {"default" : False }),
28- })
29-
26+ original_inputs ["required" ].update (
27+ {
28+ "condition_image" : ("LATENT" , {"default" : None }),
29+ "use_clone_pe" : ("BOOLEAN" , {"default" : False }),
30+ }
31+ )
32+
3033 # 不需要可选参数,使用内部默认值
3134 # original_inputs["optional"] = {}
32-
35+
3336 return original_inputs
3437
3538 RETURN_TYPES = SamplerCustomAdvanced .RETURN_TYPES
3639 RETURN_NAMES = SamplerCustomAdvanced .RETURN_NAMES
3740 FUNCTION = "sample_with_condition"
3841 CATEGORY = "sampling/custom_sampling"
3942
40- def sample_with_condition (self , noise , guider , sampler , sigmas , latent_image ,
41- condition_image = None , use_clone_pe = False ):
43+ def sample_with_condition (
44+ self ,
45+ noise ,
46+ guider ,
47+ sampler ,
48+ sigmas ,
49+ latent_image ,
50+ condition_image = None ,
51+ use_clone_pe = False ,
52+ ):
4253 """
4354 扩展的采样函数,支持条件图像和自定义位置编码
4455 自动处理掩码和引导强度
4556 """
4657 latent = latent_image
4758 latent_image = latent ["samples" ]
4859 latent = latent .copy ()
49- latent_image = comfy .sample .fix_empty_latent_channels (guider .model_patcher , latent_image )
60+ latent_image = comfy .sample .fix_empty_latent_channels (
61+ guider .model_patcher , latent_image
62+ )
5063 latent ["samples" ] = latent_image
5164
5265 # 处理条件图像
5366 cond_latent = None
5467 if condition_image is not None :
5568 cond_latent = condition_image ["samples" ]
56-
69+
5770 # 确保条件图像在与潜在图像相同的设备上
5871 # 一次性移动到 GPU,避免每次推理都移动
5972 target_device = comfy .model_management .get_torch_device ()
6073 if cond_latent .device != target_device :
61- logging .info (f"将条件图像从 { cond_latent .device } 移动到 { target_device } " )
74+ logging .info (
75+ f"将条件图像从 { cond_latent .device } 移动到 { target_device } "
76+ )
6277 cond_latent = cond_latent .to (target_device )
63-
78+
79+ # 记录条件图像的形状和设备
80+ logging .debug (
81+ f"条件图像: shape={ cond_latent .shape } , device={ cond_latent .device } "
82+ )
83+
6484 # 创建去噪掩码 - 只对非条件部分应用去噪
6585 noise_mask = None
6686 if "noise_mask" in latent :
6787 noise_mask = latent ["noise_mask" ]
68-
88+ logging .debug (f"使用噪声掩码: shape={ noise_mask .shape } " )
89+
6990 # 准备回调
7091 x0_output = {}
71- callback = latent_preview .prepare_callback (guider .model_patcher , sigmas .shape [- 1 ] - 1 , x0_output )
92+ callback = latent_preview .prepare_callback (
93+ guider .model_patcher , sigmas .shape [- 1 ] - 1 , x0_output
94+ )
7295
73- # 准备额外的关键字参数
96+ # 确保 guider 有 model_options 属性
97+ if not hasattr (guider , "model_options" ):
98+ guider .model_options = {}
99+ logging .info ("为 guider 创建 model_options 属性" )
100+
101+ # 直接设置 model_options
102+ guider .model_options ["condition_image" ] = cond_latent
103+ guider .model_options ["use_clone_pe" ] = use_clone_pe
104+ guider .model_options ["guider_id" ] = id (guider )
105+
106+ # 记录设置的选项
107+ logging .debug (
108+ f"设置 guider.model_options: condition_image={ cond_latent is not None } , use_clone_pe={ use_clone_pe } "
109+ )
110+
111+ # 准备额外的关键字参数 - 同时通过 kwargs 和 model_options 传递
74112 extra_kwargs = {
75113 "condition_image" : cond_latent ,
76114 "use_clone_pe" : use_clone_pe ,
77115 }
78-
116+
79117 # 生成噪声张量
80118 noise_tensor = noise .generate_noise (latent )
81-
119+
82120 # 调用采样器
83121 samples = guider .sample (
84122 noise_tensor ,
85- latent_image ,
86- sampler ,
87- sigmas ,
88- denoise_mask = noise_mask ,
89- callback = callback ,
90- disable_pbar = not comfy .utils .PROGRESS_BAR_ENABLED ,
123+ latent_image ,
124+ sampler ,
125+ sigmas ,
126+ denoise_mask = noise_mask ,
127+ callback = callback ,
128+ disable_pbar = not comfy .utils .PROGRESS_BAR_ENABLED ,
91129 seed = noise .seed ,
92- ** extra_kwargs
130+ ** extra_kwargs ,
93131 )
94-
132+
95133 latent ["samples" ] = samples
96134 return (latent , x0_output .get ("x0" , None ))
97135
136+
98137class PhotoDoodleCrop :
99138 """
100139 图片裁切节点:在保持原图比例的情况下,尽可能最大化地裁切出目标宽高的区域
101140 如果原图尺寸不足,则放大至目标尺寸,保持比例且不留白
102141 """
103-
142+
104143 @classmethod
105144 def INPUT_TYPES (s ):
106145 return {
@@ -118,45 +157,45 @@ def INPUT_TYPES(s):
118157 def crop_image (self , image , width , height ):
119158 """
120159 裁切图片,保持比例,最大化填充目标区域,无留白
121-
160+
122161 参数:
123162 image: 输入图片张量 [B, H, W, C]
124163 width: 目标宽度
125164 height: 目标高度
126-
165+
127166 返回:
128167 裁切后的图片张量 [B, height, width, C]
129168 """
130169 # 转换为numpy处理
131170 result = []
132171 for img in image :
133172 img_np = img .cpu ().numpy ()
134-
173+
135174 # 获取原始尺寸
136175 orig_h , orig_w = img_np .shape [0 ], img_np .shape [1 ]
137-
176+
138177 # 计算目标比例和原始比例
139178 target_ratio = width / height
140179 orig_ratio = orig_w / orig_h
141-
180+
142181 # 策略:先调整比例(缩小或裁切),再缩放到目标尺寸
143182 if orig_ratio > target_ratio :
144183 # 原图更宽,需要调整宽度
145184 new_w = int (orig_h * target_ratio )
146185 offset_w = (orig_w - new_w ) // 2
147- adjusted = img_np [:, offset_w : offset_w + new_w , :]
186+ adjusted = img_np [:, offset_w : offset_w + new_w , :]
148187 else :
149188 # 原图更高,需要调整高度
150189 new_h = int (orig_w / target_ratio )
151190 offset_h = (orig_h - new_h ) // 2
152- adjusted = img_np [offset_h : offset_h + new_h , :]
153-
191+ adjusted = img_np [offset_h : offset_h + new_h , :]
192+
154193 # 调整后的图像缩放到目标尺寸
155194 pil_img = Image .fromarray ((adjusted * 255 ).astype (np .uint8 ))
156195 resized_pil = pil_img .resize ((width , height ), Image .LANCZOS )
157196 resized_np = np .array (resized_pil ).astype (np .float32 ) / 255.0
158-
197+
159198 result .append (torch .from_numpy (resized_np ))
160-
199+
161200 # 堆叠所有处理后的图片
162- return (torch .stack (result ),)
201+ return (torch .stack (result ),)
0 commit comments