13 KiB
13 KiB
Documentation
- Class name: WAS_KSampler_Cycle
- Category: WAS Suite/Sampling
- Output node: False
- Repo Ref: https://github.com/WASasquatch/was-node-suite-comfyui
WAS_KSampler_Cycle 节点旨在 WAS 套件中执行高级采样操作。它利用各种参数来控制采样过程,包括模型选择、种子初始化、步数计数和条件输入。该节点能够处理复杂的采样任务,如迭代去噪和潜在图像操作,以实现高质量的结果。它还支持可选功能,如次要模型切换和后处理增强,使其成为复杂采样需求的多功能工具。
Input types
Required
- model
- 模型参数对于采样过程至关重要,因为它定义了用于生成输出的底层神经网络模型。模型的选择可以显著影响生成样本的质量和特性。
- Comfy dtype: MODEL
- Python dtype: torch.nn.Module
- seed
- 种子参数对于初始化随机数生成器至关重要,确保了采样过程的可重复性。它允许在不同运行中使用相同的种子值生成一致的结果。
- Comfy dtype: INT
- Python dtype: int
- steps
- 步数参数决定了采样过程中的迭代次数。步数越多,可以导致更精细的样本,但也可能增加计算时间。
- Comfy dtype: INT
- Python dtype: int
Output types
- latent(s)
- 输出的潜在表示生成样本的编码形式。这些潜在表示可以进一步处理或用于各种下游任务,如图像重建或特征提取。
- Comfy dtype: LATENT
- Python dtype: torch.Tensor
Usage tips
- Infra type: GPU
Source code
class WAS_KSampler_Cycle:
@classmethod
def INPUT_TYPES(s):
return {'required': {'model': ('MODEL',), 'seed': ('INT', {'default': 0, 'min': 0, 'max': 18446744073709551615}), 'steps': ('INT', {'default': 20, 'min': 1, 'max': 10000}), 'cfg': ('FLOAT', {'default': 8.0, 'min': 0.0, 'max': 100.0}), 'sampler_name': (comfy.samplers.KSampler.SAMPLERS,), 'scheduler': (comfy.samplers.KSampler.SCHEDULERS,), 'positive': ('CONDITIONING',), 'negative': ('CONDITIONING',), 'latent_image': ('LATENT',), 'tiled_vae': (['disable', 'enable'],), 'latent_upscale': (['disable', 'nearest-exact', 'bilinear', 'area', 'bicubic', 'bislerp'],), 'upscale_factor': ('FLOAT', {'default': 2.0, 'min': 0.1, 'max': 8.0, 'step': 0.1}), 'upscale_cycles': ('INT', {'default': 2, 'min': 2, 'max': 12, 'step': 1}), 'starting_denoise': ('FLOAT', {'default': 1.0, 'min': 0.0, 'max': 1.0, 'step': 0.01}), 'cycle_denoise': ('FLOAT', {'default': 0.5, 'min': 0.0, 'max': 1.0, 'step': 0.01}), 'scale_denoise': (['enable', 'disable'],), 'scale_sampling': (['bilinear', 'bicubic', 'nearest', 'lanczos'],), 'vae': ('VAE',)}, 'optional': {'secondary_model': ('MODEL',), 'secondary_start_cycle': ('INT', {'default': 2, 'min': 2, 'max': 16, 'step': 1}), 'upscale_model': ('UPSCALE_MODEL',), 'processor_model': ('UPSCALE_MODEL',), 'pos_additive': ('CONDITIONING',), 'neg_additive': ('CONDITIONING',), 'pos_add_mode': (['increment', 'decrement'],), 'pos_add_strength': ('FLOAT', {'default': 0.25, 'min': 0.01, 'max': 1.0, 'step': 0.01}), 'pos_add_strength_scaling': (['enable', 'disable'],), 'pos_add_strength_cutoff': ('FLOAT', {'default': 2.0, 'min': 0.01, 'max': 10.0, 'step': 0.01}), 'neg_add_mode': (['increment', 'decrement'],), 'neg_add_strength': ('FLOAT', {'default': 0.25, 'min': 0.01, 'max': 1.0, 'step': 0.01}), 'neg_add_strength_scaling': (['enable', 'disable'],), 'neg_add_strength_cutoff': ('FLOAT', {'default': 2.0, 'min': 0.01, 'max': 10.0, 'step': 0.01}), 'sharpen_strength': ('FLOAT', {'default': 0.0, 'min': 0.0, 'max': 10.0, 'step': 0.01}), 'sharpen_radius': ('INT', {'default': 2, 'min': 1, 'max': 12, 'step': 1}), 'steps_scaling': (['enable', 'disable'],), 'steps_control': (['decrement', 'increment'],), 'steps_scaling_value': ('INT', {'default': 10, 'min': 1, 'max': 20, 'step': 1}), 'steps_cutoff': ('INT', {'default': 20, 'min': 4, 'max': 1000, 'step': 1}), 'denoise_cutoff': ('FLOAT', {'default': 0.25, 'min': 0.01, 'max': 1.0, 'step': 0.01})}}
RETURN_TYPES = ('LATENT',)
RETURN_NAMES = ('latent(s)',)
FUNCTION = 'sample'
CATEGORY = 'WAS Suite/Sampling'
def sample(self, model, seed, steps, cfg, sampler_name, scheduler, positive, negative, latent_image, tiled_vae, latent_upscale, upscale_factor, upscale_cycles, starting_denoise, cycle_denoise, scale_denoise, scale_sampling, vae, secondary_model=None, secondary_start_cycle=None, pos_additive=None, pos_add_mode=None, pos_add_strength=None, pos_add_strength_scaling=None, pos_add_strength_cutoff=None, neg_additive=None, neg_add_mode=None, neg_add_strength=None, neg_add_strength_scaling=None, neg_add_strength_cutoff=None, upscale_model=None, processor_model=None, sharpen_strength=0, sharpen_radius=2, steps_scaling=None, steps_control=None, steps_scaling_value=None, steps_cutoff=None, denoise_cutoff=0.25):
upscale_steps = upscale_cycles
division_factor = upscale_steps if steps >= upscale_steps else steps
current_upscale_factor = upscale_factor ** (1 / (division_factor - 1))
tiled_vae = tiled_vae == 'enable'
scale_denoise = scale_denoise == 'enable'
pos_add_strength_scaling = pos_add_strength_scaling == 'enable'
neg_add_strength_scaling = neg_add_strength_scaling == 'enable'
steps_scaling = steps_scaling == 'enable'
run_model = model
secondary_switched = False
for i in range(division_factor):
cstr(f'Cycle Pass {i + 1}/{division_factor}').msg.print()
if scale_denoise:
denoise = (round(cycle_denoise * 2 ** (-(i - 1)), 2) if i > 0 else cycle_denoise) if i > 0 else round(starting_denoise, 2)
else:
denoise = round(cycle_denoise if i > 0 else starting_denoise, 2)
if denoise < denoise_cutoff and scale_denoise:
denoise = denoise_cutoff
if i >= secondary_start_cycle - 1 and secondary_model and (not secondary_switched):
run_model = secondary_model
denoise = cycle_denoise
model = None
secondary_switched = True
if steps_scaling and i > 0:
steps = steps + steps_scaling_value if steps_control == 'increment' else steps - steps_scaling_value
steps = (steps if steps <= steps_cutoff else steps_cutoff) if steps_control == 'increment' else steps if steps >= steps_cutoff else steps_cutoff
print('Steps:', steps)
print('Denoise:', denoise)
if pos_additive:
pos_strength = 0.0 if i <= 0 else pos_add_strength
if pos_add_mode == 'increment':
pos_strength = (round(pos_add_strength * 2 ** (i - 1), 2) if i > 0 else pos_add_strength) if pos_add_strength_scaling else pos_add_strength
pos_strength = pos_add_strength_cutoff if pos_strength > pos_add_strength_cutoff else pos_strength
else:
pos_strength = (round(pos_add_strength / 2 ** (i - 1), 2) if i > 0 else pos_add_strength) if pos_add_strength_scaling else pos_add_strength
pos_strength = pos_add_strength_cutoff if pos_strength < pos_add_strength_cutoff else pos_strength
comb = nodes.ConditioningAverage()
positive = comb.addWeighted(pos_additive, positive, pos_strength)[0]
print('Positive Additive Strength:', pos_strength)
if neg_additive:
neg_strength = 0.0 if i <= 0 else pos_add_strength
if neg_add_mode == 'increment':
neg_strength = (round(neg_add_strength * 2 ** (i - 1), 2) if i > 0 else neg_add_strength) if neg_add_strength_scaling else neg_add_strength
neg_strength = neg_add_strength_cutoff if neg_strength > neg_add_strength_cutoff else neg_strength
else:
neg_strength = (round(neg_add_strength / 2 ** (i - 1), 2) if i > 0 else neg_add_strength) if neg_add_strength_scaling else neg_add_strength
neg_strength = neg_add_strength_cutoff if neg_strength < neg_add_strength_cutoff else neg_strength
comb = nodes.ConditioningAverage()
negative = comb.addWeighted(neg_additive, negative, neg_strength)[0]
print('Negative Additive Strength:', neg_strength)
if i != 0:
latent_image = latent_image_result
samples = nodes.common_ksampler(run_model, seed, steps, cfg, sampler_name, scheduler, positive, negative, latent_image, denoise=denoise)
if i < division_factor - 1:
tensors = None
upscaler = None
resample_filters = {'nearest': 0, 'bilinear': 2, 'bicubic': 3, 'lanczos': 1}
if latent_upscale == 'disable':
if tiled_vae:
tensors = vae.decode_tiled(samples[0]['samples'])
else:
tensors = vae.decode(samples[0]['samples'])
if processor_model or upscale_model:
from comfy_extras import nodes_upscale_model
upscaler = nodes_upscale_model.ImageUpscaleWithModel()
if processor_model:
original_size = tensor2pil(tensors[0]).size
upscaled_tensors = upscaler.upscale(processor_model, tensors)
tensor_images = []
for tensor in upscaled_tensors[0]:
pil = tensor2pil(tensor)
if pil.size[0] != original_size[0] or pil.size[1] != original_size[1]:
pil = pil.resize((original_size[0], original_size[1]), Image.Resampling(resample_filters[scale_sampling]))
if sharpen_strength != 0.0:
pil = self.unsharp_filter(pil, sharpen_radius, sharpen_strength)
tensor_images.append(pil2tensor(pil))
tensor_images = torch.cat(tensor_images, dim=0)
if upscale_model:
if processor_model:
tensors = tensor_images
del tensor_images
original_size = tensor2pil(tensors[0]).size
new_width = round(original_size[0] * current_upscale_factor)
new_height = round(original_size[1] * current_upscale_factor)
new_width = int(round(new_width / 32) * 32)
new_height = int(round(new_height / 32) * 32)
upscaled_tensors = upscaler.upscale(upscale_model, tensors)
tensor_images = []
for tensor in upscaled_tensors[0]:
tensor = pil2tensor(tensor2pil(tensor).resize((new_width, new_height), Image.Resampling(resample_filters[scale_sampling])))
size = max(tensor2pil(tensor).size)
if sharpen_strength != 0.0:
tensor = pil2tensor(self.unsharp_filter(tensor2pil(tensor), sharpen_radius, sharpen_strength))
tensor_images.append(tensor)
tensor_images = torch.cat(tensor_images, dim=0)
else:
tensor_images = []
scale = WAS_Image_Rescale()
for tensor in tensors:
tensor = scale.image_rescale(tensor.unsqueeze(0), 'rescale', 'true', scale_sampling, current_upscale_factor, 0, 0)[0]
size = max(tensor2pil(tensor).size)
if sharpen_strength > 0.0:
tensor = pil2tensor(self.unsharp_filter(tensor2pil(tensor), sharpen_radius, sharpen_strength))
tensor_images.append(tensor)
tensor_images = torch.cat(tensor_images, dim=0)
if tiled_vae:
latent_image_result = {'samples': vae.encode_tiled(self.vae_encode_crop_pixels(tensor_images)[:, :, :, :3])}
else:
latent_image_result = {'samples': vae.encode(self.vae_encode_crop_pixels(tensor_images)[:, :, :, :3])}
else:
upscaler = nodes.LatentUpscaleBy()
latent_image_result = upscaler.upscale(samples[0], latent_upscale, current_upscale_factor)[0]
else:
latent_image_result = samples[0]
return (latent_image_result,)
@staticmethod
def vae_encode_crop_pixels(pixels):
x = pixels.shape[1] // 8 * 8
y = pixels.shape[2] // 8 * 8
if pixels.shape[1] != x or pixels.shape[2] != y:
x_offset = pixels.shape[1] % 8 // 2
y_offset = pixels.shape[2] % 8 // 2
pixels = pixels[:, x_offset:x + x_offset, y_offset:y + y_offset, :]
return pixels
@staticmethod
def unsharp_filter(image, radius=2, amount=1.0):
from skimage.filters import unsharp_mask
img_array = np.array(image)
img_array = img_array / 255.0
sharpened = unsharp_mask(img_array, radius=radius, amount=amount, channel_axis=2)
sharpened = (sharpened * 255.0).astype(np.uint8)
sharpened_pil = Image.fromarray(sharpened)
return sharpened_pil