Files
CavinHuang-comfyui-nodes-docs/docs/KSampler Cycle.md
2024-05-26 20:35:16 +08:00

13 KiB

Documentation

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