# Documentation - Class name: BatchCreativeInterpolationNode - Category: Steerable-Motion - Output node: False - Repo Ref: https://github.com/banodoco/steerable-motion BatchCreativeInterpolationNode旨在对一批图像执行创意插值。它利用各种参数来控制插值过程中帧的分布、关键帧影响和强度值。该节点特别适用于在不同图像状态之间生成平滑过渡,并且它提供了对最终输出的高控制度,使其适用于广泛的创意应用。 # Input types ## Required - positive - 正面条件图像是一个关键输入,它影响插值的方向和风格。 - Comfy dtype: IMAGE - Python dtype: torch.Tensor - negative - 负面条件图像用于限制或引导插值,使其与正面图像的方向相反。 - Comfy dtype: IMAGE - Python dtype: torch.Tensor - images - 将被插值的输入图像。此参数至关重要,因为它定义了将要被转换的内容。 - Comfy dtype: IMAGE - Python dtype: torch.Tensor - model - 用于插值过程的模型。对于生成连贯的转换至关重要。 - Comfy dtype: MODEL - Python dtype: torch.nn.Module - ipadapter - IPA适配器用于在插值期间对图像应用特定的转换或风格。 - Comfy dtype: IPADAPTER - Python dtype: dict - clip_vision - CLIP Vision模型被用来以支持插值的方式分析和处理图像。 - Comfy dtype: CLIP_VISION - Python dtype: torch.nn.Module - type_of_frame_distribution - 确定插值的帧分布方法,可以是线性的或使用动态值。 - Comfy dtype: COMBO[linear, dynamic] - Python dtype: str - type_of_key_frame_influence - 指定关键帧的影响是线性的还是遵循动态模式。 - Comfy dtype: COMBO[linear, dynamic] - Python dtype: str - type_of_strength_distribution - 指示插值的强度分布类型,可以是线性或动态。 - Comfy dtype: COMBO[linear, dynamic] - Python dtype: str ## Optional - linear_frame_distribution_value - 用于线性帧分布的值。仅当帧分布类型设置为'linear'时才相关。 - Comfy dtype: INT - Python dtype: int - dynamic_frame_distribution_values - 定义动态帧分布的逗号分隔值字符串。如果帧分布类型为'dynamic',则需要此参数。 - Comfy dtype: STRING - Python dtype: str - linear_key_frame_influence_value - 确定关键帧线性影响的值。如果关键帧影响类型为'linear',则适用。 - Comfy dtype: STRING - Python dtype: str - dynamic_key_frame_influence_values - 表示关键帧的动态影响值的字符串,当关键帧影响类型为'dynamic'时使用。 - Comfy dtype: STRING - Python dtype: str - linear_strength_value - 如果选择了线性强度分布,用于插值的线性强度值。 - Comfy dtype: STRING - Python dtype: str - dynamic_strength_values - 定义帧的动态强度分布的逗号分隔值字符串。 - Comfy dtype: STRING - Python dtype: str - buffer - 缓冲值向关键帧位置添加填充,影响插值的范围。 - Comfy dtype: INT - Python dtype: int - high_detail_mode - 启用时,高细节模式会调整IPA设置,以获得更详细和精细的插值。 - Comfy dtype: BOOLEAN - Python dtype: bool - base_ipa_advanced_settings - 基本IPA应用的高级设置,允许微调插值过程。 - Comfy dtype: ADVANCED_IPA_SETTINGS - Python dtype: dict - detail_ipa_advanced_settings - 详细IPA应用的高级设置,在使用高细节模式时使用。 - Comfy dtype: ADVANCED_IPA_SETTINGS - Python dtype: dict # Output types - GRAPH - GRAPH输出可视化插值过程中应用的权重比较,提供对不同帧如何受到影响的洞察。 - Comfy dtype: IMAGE - Python dtype: torch.Tensor - POSITIVE - POSITIVE输出反映了插值中使用的正面条件,捕捉了过程的指导方向。 - Comfy dtype: CONDITIONING - Python dtype: torch.Tensor - NEGATIVE - NEGATIVE输出代表负面条件,它以期望的方式限制插值。 - Comfy dtype: CONDITIONING - Python dtype: torch.Tensor - MODEL - MODEL输出是插值后修改的模型,包含了应用于输入图像的创意转换。 - Comfy dtype: MODEL - Python dtype: torch.nn.Module - KEYFRAME_POSITIONS - KEYFRAME_POSITIONS输出提供了用于插值的稀疏索引方法,详细说明了关键帧的位置。 - Comfy dtype: SPARSE_METHOD - Python dtype: SparseIndexMethodImport - BATCH_SIZE - BATCH_SIZE输出指示了插值期间处理的批次大小,反映了转换的图像数量。 - Comfy dtype: INT - Python dtype: int # Usage tips - Infra type: GPU # Source code ``` class BatchCreativeInterpolationNode: @classmethod def IS_CHANGED(cls, **kwargs): return float('NaN') @classmethod def INPUT_TYPES(s): return {'required': {'positive': ('CONDITIONING',), 'negative': ('CONDITIONING',), 'images': ('IMAGE',), 'model': ('MODEL',), 'ipadapter': ('IPADAPTER',), 'clip_vision': ('CLIP_VISION',), 'type_of_frame_distribution': (['linear', 'dynamic'],), 'linear_frame_distribution_value': ('INT', {'default': 16, 'min': 4, 'max': 64, 'step': 1}), 'dynamic_frame_distribution_values': ('STRING', {'multiline': True, 'default': '0,10,26,40'}), 'type_of_key_frame_influence': (['linear', 'dynamic'],), 'linear_key_frame_influence_value': ('STRING', {'multiline': False, 'default': '(1.0,1.0)'}), 'dynamic_key_frame_influence_values': ('STRING', {'multiline': True, 'default': '(1.0,1.0),(1.0,1.5)(1.0,0.5)'}), 'type_of_strength_distribution': (['linear', 'dynamic'],), 'linear_strength_value': ('STRING', {'multiline': False, 'default': '(0.3,0.4)'}), 'dynamic_strength_values': ('STRING', {'multiline': True, 'default': '(0.0,1.0),(0.0,1.0),(0.0,1.0),(0.0,1.0)'}), 'buffer': ('INT', {'default': 4, 'min': 1, 'max': 16, 'step': 1}), 'high_detail_mode': ('BOOLEAN', {'default': True})}, 'optional': {'base_ipa_advanced_settings': ('ADVANCED_IPA_SETTINGS',), 'detail_ipa_advanced_settings': ('ADVANCED_IPA_SETTINGS',)}} RETURN_TYPES = ('IMAGE', 'CONDITIONING', 'CONDITIONING', 'MODEL', 'SPARSE_METHOD', 'INT') RETURN_NAMES = ('GRAPH', 'POSITIVE', 'NEGATIVE', 'MODEL', 'KEYFRAME_POSITIONS', 'BATCH_SIZE') FUNCTION = 'combined_function' CATEGORY = 'Steerable-Motion' def combined_function(self, positive, negative, images, model, ipadapter, clip_vision, type_of_frame_distribution, linear_frame_distribution_value, dynamic_frame_distribution_values, type_of_key_frame_influence, linear_key_frame_influence_value, dynamic_key_frame_influence_values, type_of_strength_distribution, linear_strength_value, dynamic_strength_values, buffer, high_detail_mode, base_ipa_advanced_settings=None, detail_ipa_advanced_settings=None): def get_keyframe_positions(type_of_frame_distribution, dynamic_frame_distribution_values, images, linear_frame_distribution_value): if type_of_frame_distribution == 'dynamic': if isinstance(dynamic_frame_distribution_values, str): return sorted([int(kf.strip()) for kf in dynamic_frame_distribution_values.split(',')]) elif isinstance(dynamic_frame_distribution_values, list): return sorted(dynamic_frame_distribution_values) else: return [i * linear_frame_distribution_value for i in range(len(images))] def create_mask_batch(last_key_frame_position, weights, frames): (width, height) = (512, 512) frame_to_weight = {frame: weights[i] for (i, frame) in enumerate(frames)} masks = [] for frame_number in range(last_key_frame_position): strength = frame_to_weight.get(frame_number, 0.0) mask = torch.full((height, width), strength) masks.append(mask) masks_tensor = torch.stack(masks, dim=0) return masks_tensor def plot_weight_comparison(cn_frame_numbers, cn_weights, ipadapter_frame_numbers, ipadapter_weights, buffer): plt.figure(figsize=(12, 8)) colors = ['b', 'g', 'r', 'c', 'm', 'y', 'k'] cn_frame_numbers = cn_frame_numbers if cn_frame_numbers is not None else [] cn_weights = cn_weights if cn_weights is not None else [] ipadapter_frame_numbers = ipadapter_frame_numbers if ipadapter_frame_numbers is not None else [] ipadapter_weights = ipadapter_weights if ipadapter_weights is not None else [] max_length = max(len(cn_frame_numbers), len(ipadapter_frame_numbers)) label_counter = 1 if buffer < 0 else 0 for i in range(max_length): if i < len(cn_frame_numbers): label = 'cn_strength_buffer' if i == 0 and buffer > 0 else f'cn_strength_{label_counter}' plt.plot(cn_frame_numbers[i], cn_weights[i], marker='o', color=colors[i % len(colors)], label=label) if i < len(ipadapter_frame_numbers): label = 'ipa_strength_buffer' if i == 0 and buffer > 0 else f'ipa_strength_{label_counter}' plt.plot(ipadapter_frame_numbers[i], ipadapter_weights[i], marker='x', linestyle='--', color=colors[i % len(colors)], label=label) if label_counter == 0 or buffer < 0 or i > 0: label_counter += 1 plt.legend() all_weights = cn_weights + ipadapter_weights max_weight = max((max(sublist) for sublist in all_weights if sublist)) * 1.5 plt.ylim(0, max_weight) buffer_io = BytesIO() plt.savefig(buffer_io, format='png', bbox_inches='tight') plt.close() buffer_io.seek(0) img = Image.open(buffer_io) img_tensor = transforms.ToTensor()(img) img_tensor = img_tensor.unsqueeze(0) img_tensor = img_tensor.permute([0, 2, 3, 1]) return (img_tensor,) def extract_strength_values(type_of_key_frame_influence, dynamic_key_frame_influence_values, keyframe_positions, linear_key_frame_influence_value): if type_of_key_frame_influence == 'dynamic': if isinstance(dynamic_key_frame_influence_values, str): dynamic_values = eval(dynamic_key_frame_influence_values) else: dynamic_values = dynamic_key_frame_influence_values dynamic_values_corrected = [] for value in dynamic_values: if len(value) == 2: value = (value[0], value[1], value[0]) dynamic_values_corrected.append(value) return dynamic_values_corrected else: if len(linear_key_frame_influence_value) == 2: linear_key_frame_influence_value = (linear_key_frame_influence_value[0], linear_key_frame_influence_value[1], linear_key_frame_influence_value[0]) return [linear_key_frame_influence_value for _ in range(len(keyframe_positions) - 1)] def extract_influence_values(type_of_key_frame_influence, dynamic_key_frame_influence_values, keyframe_positions, linear_key_frame_influence_value): if isinstance(linear_key_frame_influence_value, str) and linear_key_frame_influence_value[0] == '(': linear_key_frame_influence_value = eval(linear_key_frame_influence_value) if not isinstance(linear_key_frame_influence_value, tuple): if isinstance(linear_key_frame_influence_value, (float, str)): try: value = float(linear_key_frame_influence_value) linear_key_frame_influence_value = (value, value) except ValueError: raise ValueError('linear_key_frame_influence_value must be a float or a string representing a float') number_of_outputs = len(keyframe_positions) - 1 if type_of_key_frame_influence == 'dynamic': if all((isinstance(x, float) for x in dynamic_key_frame_influence_values)): dynamic_values = [(value, value) for value in dynamic_key_frame_influence_values] elif isinstance(dynamic_key_frame_influence_values[0], str) and dynamic_key_frame_influence_values[0] == '(': string_representation = ''.join(dynamic_key_frame_influence_values) dynamic_values = eval(f'[{string_representation}]') else: dynamic_values = dynamic_key_frame_influence_values if isinstance(dynamic_key_frame_influence_values, list) else [dynamic_key_frame_influence_values] return dynamic_values[:number_of_outputs] else: return [linear_key_frame_influence_value for _ in range(number_of_outputs)] def calculate_weights(batch_index_from, batch_index_to, strength_from, strength_to, interpolation, revert_direction_at_midpoint, last_key_frame_position, i, number_of_items, buffer): range_start = batch_index_from range_end = batch_index_to if i == number_of_items - 1: range_end = last_key_frame_position steps = range_end - range_start diff = strength_to - strength_from index = np.linspace(0, 1, steps // 2 + 1) if revert_direction_at_midpoint else np.linspace(0, 1, steps) if interpolation == 'linear': weights = np.linspace(strength_from, strength_to, len(index)) elif interpolation == 'ease-in': weights = diff * np.power(index, 2) + strength_from elif interpolation == 'ease-out': weights = diff * (1 - np.power(1 - index, 2)) + strength_from elif interpolation == 'ease-in-out': weights = diff * ((1 - np.cos(index * np.pi)) / 2) + strength_from if revert_direction_at_midpoint: weights = np.concatenate([weights, weights[::-1]]) frame_numbers = np.arange(range_start, range_start + len(weights)) if range_start < 0 and i > 0: drop_count = abs(range_start) weights = weights[drop_count:] frame_numbers = frame_numbers[drop_count:] if range_end > last_key_frame_position and i < number_of_items - 1: drop_count = range_end - last_key_frame_position weights = weights[:-drop_count] frame_numbers = frame_numbers[:-drop_count] return (weights, frame_numbers) def process_weights(frame_numbers, weights, multiplier): adjusted_weights = [min(max(weight * multiplier, 0.0), 1.0) for weight in weights] filtered_frames_and_weights = [(frame, weight) for (frame, weight) in zip(frame_numbers, adjusted_weights) if weight > 0.0] (filtered_frame_numbers, filtered_weights) = zip(*filtered_frames_and_weights) if filtered_frames_and_weights else ([], []) return (list(filtered_frame_numbers), list(filtered_weights)) def calculate_influence_frame_number(key_frame_position, next_key_frame_position, distance): key_frame_distance = abs(next_key_frame_position - key_frame_position) extended_distance = key_frame_distance * distance if key_frame_position < next_key_frame_position: influence_frame_number = key_frame_position + extended_distance else: influence_frame_number = key_frame_position - extended_distance return round(influence_frame_number) keyframe_positions = get_keyframe_positions(type_of_frame_distribution, dynamic_frame_distribution_values, images, linear_frame_distribution_value) shifted_keyframes_position = [position + buffer - 2 for position in keyframe_positions] shifted_keyframe_positions_string = ','.join((str(pos) for pos in shifted_keyframes_position)) sparseindexmethod = SparseIndexMethodNodeImport() (sparse_indexes,) = sparseindexmethod.get_method(shifted_keyframe_positions_string) if buffer > 0: keyframe_positions = [position + buffer - 1 for position in keyframe_positions] keyframe_positions.insert(0, 0) last_position_with_buffer = keyframe_positions[-1] + buffer - 1 keyframe_positions.append(last_position_with_buffer) if base_ipa_advanced_settings is None: if high_detail_mode: base_ipa_advanced_settings = {'ipa_starts_at': 0.0, 'ipa_ends_at': 0.3, 'ipa_weight_type': 'ease in-out', 'ipa_weight': 1.0, 'ipa_embeds_scaling': 'V only', 'ipa_noise_strength': 0.0, 'use_image_for_noise': False, 'type_of_noise': 'fade', 'noise_blur': 0} else: base_ipa_advanced_settings = {'ipa_starts_at': 0.0, 'ipa_ends_at': 0.75, 'ipa_weight_type': 'ease in-out', 'ipa_weight': 1.0, 'ipa_embeds_scaling': 'V only', 'ipa_noise_strength': 0.0, 'use_image_for_noise': False, 'type_of_noise': 'fade', 'noise_blur': 0} if detail_ipa_advanced_settings is None: if high_detail_mode: detail_ipa_advanced_settings = {'ipa_starts_at': 0.25, 'ipa_ends_at': 0.75, 'ipa_weight_type': 'ease in-out', 'ipa_weight': 1.0, 'ipa_embeds_scaling': 'V only', 'ipa_noise_strength': 0.0, 'use_image_for_noise': False, 'type_of_noise': 'fade', 'noise_blur': 0} strength_values = extract_strength_values(type_of_strength_distribution, dynamic_strength_values, keyframe_positions, linear_strength_value) strength_values = [literal_eval(val) if isinstance(val, str) else val for val in strength_values] corrected_strength_values = [] for val in strength_values: if len(val) == 2: val = (val[0], val[1], val[0]) corrected_strength_values.append(val) strength_values = corrected_strength_values key_frame_influence_values = extract_influence_values(type_of_key_frame_influence, dynamic_key_frame_influence_values, keyframe_positions, linear_key_frame_influence_value) key_frame_influence_values = [literal_eval(val) if isinstance(val, str) else val for val in key_frame_influence_values] last_key_frame_position = keyframe_positions[-1] + 1 all_cn_frame_numbers = [] all_cn_weights = [] all_ipa_weights = [] all_ipa_frame_numbers = [] for i in range(len(keyframe_positions)): keyframe_position = keyframe_positions[i] interpolation = 'ease-in-out' if i == 0: image = images[0] strength_from = strength_to = strength_values[0][1] batch_index_from = 0 batch_index_to_excl = buffer (weights, frame_numbers) = calculate_weights(batch_index_from, batch_index_to_excl, strength_from, strength_to, interpolation, False, last_key_frame_position, i, len(keyframe_positions), buffer) elif i == 1: image = images[i - 1] (key_frame_influence_from, key_frame_influence_to) = key_frame_influence_values[i - 1] (start_strength, mid_strength, end_strength) = strength_values[i - 1] keyframe_position = keyframe_positions[i] next_key_frame_position = keyframe_positions[i + 1] batch_index_from = keyframe_position batch_index_to_excl = calculate_influence_frame_number(keyframe_position, next_key_frame_position, key_frame_influence_to) (weights, frame_numbers) = calculate_weights(batch_index_from, batch_index_to_excl, mid_strength, end_strength, interpolation, False, last_key_frame_position, i, len(keyframe_positions), buffer) elif i == len(keyframe_positions) - 2: image = images[i - 1] (key_frame_influence_from, key_frame_influence_to) = key_frame_influence_values[i - 1] (start_strength, mid_strength, end_strength) = strength_values[i - 1] keyframe_position = keyframe_positions[i] previous_key_frame_position = keyframe_positions[i - 1] batch_index_from = calculate_influence_frame_number(keyframe_position, previous_key_frame_position, key_frame_influence_from) batch_index_to_excl = keyframe_position (weights, frame_numbers) = calculate_weights(batch_index_from, batch_index_to_excl, start_strength, mid_strength, interpolation, False, last_key_frame_position, i, len(keyframe_positions), buffer) elif i == len(keyframe_positions) - 1: image = images[i - 2] strength_from = strength_to = strength_values[i - 2][1] batch_index_from = keyframe_positions[i - 1] batch_index_to_excl = last_key_frame_position (weights, frame_numbers) = calculate_weights(batch_index_from, batch_index_to_excl, strength_from, strength_to, interpolation, False, last_key_frame_position, i, len(keyframe_positions), buffer) else: image = images[i - 1] (key_frame_influence_from, key_frame_influence_to) = key_frame_influence_values[i - 1] (start_strength, mid_strength, end_strength) = strength_values[i - 1] keyframe_position = keyframe_positions[i] previous_key_frame_position = keyframe_positions[i - 1] batch_index_from = calculate_influence_frame_number(keyframe_position, previous_key_frame_position, key_frame_influence_from) batch_index_to_excl = keyframe_position (first_half_weights, first_half_frame_numbers) = calculate_weights(batch_index_from, batch_index_to_excl, start_strength, mid_strength, interpolation, False, last_key_frame_position, i, len(keyframe_positions), buffer) next_key_frame_position = keyframe_positions[i + 1] batch_index_from = keyframe_position batch_index_to_excl = calculate_influence_frame_number(keyframe_position, next_key_frame_position, key_frame_influence_to) (second_half_weights, second_half_frame_numbers) = calculate_weights(batch_index_from, batch_index_to_excl, mid_strength, end_strength, interpolation, False, last_key_frame_position, i, len(keyframe_positions), buffer) weights = np.concatenate([first_half_weights, second_half_weights]) frame_numbers = np.concatenate([first_half_frame_numbers, second_half_frame_numbers]) (ipa_frame_numbers, ipa_weights) = process_weights(frame_numbers, weights, 1.0) prepare_for_clip_vision = PrepImageForClipVisionImport() (prepped_image,) = prepare_for_clip_vision.prep_image(image=image.unsqueeze(0), interpolation='LANCZOS', crop_position='pad', sharpening=0.1) mask = create_mask_batch(last_key_frame_position, ipa_weights, ipa_frame_numbers) if base_ipa_advanced_settings['ipa_noise_strength'] > 0: if base_ipa_advanced_settings['use_image_for_noise']: noise_image = prepped_image else: noise_image = None ipa_noise = IPAdapterNoiseImport() (negative_noise,) = ipa_noise.make_noise(type=base_ipa_advanced_settings['type_of_noise'], strength=base_ipa_advanced_settings['ipa_noise_strength'], blur=base_ipa_advanced_settings['noise_blur'], image_optional=noise_image) else: negative_noise = None ipadapter_application = IPAdapterAdvancedImport() (model,) = ipadapter_application.apply_ipadapter(model=model, ipadapter=ipadapter, image=prepped_image, weight=base_ipa_advanced_settings['ipa_weight'], weight_type=base_ipa_advanced_settings['ipa_weight_type'], start_at=base_ipa_advanced_settings['ipa_starts_at'], end_at=base_ipa_advanced_settings['ipa_ends_at'], clip_vision=clip_vision, attn_mask=mask, image_negative=negative_noise, embeds_scaling=base_ipa_advanced_settings['ipa_embeds_scaling']) if high_detail_mode: if detail_ipa_advanced_settings['ipa_noise_strength'] > 0: if detail_ipa_advanced_settings['use_image_for_noise']: noise_image = image.unsqueeze(0) else: noise_image = None ipa_noise = IPAdapterNoiseImport() (negative_noise,) = ipa_noise.make_noise(type=detail_ipa_advanced_settings['type_of_noise'], strength=detail_ipa_advanced_settings['ipa_noise_strength'], blur=detail_ipa_advanced_settings['noise_blur'], image_optional=noise_image) else: negative_noise = None tiled_ipa_application = IPAdapterTiledImport() (model, *_) = tiled_ipa_application.apply_tiled(model=model, ipadapter=ipadapter, image=image.unsqueeze(0), weight=detail_ipa_advanced_settings['ipa_weight'], weight_type=detail_ipa_advanced_settings['ipa_weight_type'], start_at=detail_ipa_advanced_settings['ipa_starts_at'], end_at=detail_ipa_advanced_settings['ipa_ends_at'], clip_vision=clip_vision, attn_mask=mask, sharpening=0.1, image_negative=negative_noise, embeds_scaling=detail_ipa_advanced_settings['ipa_embeds_scaling']) all_ipa_frame_numbers.append(ipa_frame_numbers) all_ipa_weights.append(ipa_weights) (comparison_diagram,) = plot_weight_comparison(all_cn_frame_numbers, all_cn_weights, all_ipa_frame_numbers, all_ipa_weights, buffer) return (comparison_diagram, positive, negative, model, sparse_indexes, last_key_frame_position) ```