# 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 ```