From 06be80f4b335528e21e7e125f8c3ff4bae315140 Mon Sep 17 00:00:00 2001 From: RyanOnTheInside <7623207+ryanontheinside@users.noreply.github.com> Date: Wed, 4 Jun 2025 16:41:24 -0400 Subject: [PATCH 1/5] appendage editor --- __init__.py | 5 + appendage_editor_nodes.py | 505 ++++++++++++++++++++++++++++++++++++++ 2 files changed, 510 insertions(+) create mode 100644 appendage_editor_nodes.py diff --git a/__init__.py b/__init__.py index bda963a..2bd1776 100644 --- a/__init__.py +++ b/__init__.py @@ -1,14 +1,19 @@ from .openpose_editor_nodes import OpenposeEditorNode +from .appendage_editor_nodes import AppendageEditorNode, PoseRendererNode WEB_DIRECTORY = "js" NODE_CLASS_MAPPINGS = { "OpenposeEditorNode": OpenposeEditorNode, + "AppendageEditorNode": AppendageEditorNode, + "PoseRendererNode": PoseRendererNode, } NODE_DISPLAY_NAME_MAPPINGS = { "OpenposeEditorNode": "Openpose Editor Node", + "AppendageEditorNode": "Appendage Editor", + "PoseRendererNode": "Pose Renderer", } __all__ = ["NODE_CLASS_MAPPINGS", "NODE_DISPLAY_NAME_MAPPINGS", "WEB_DIRECTORY"] diff --git a/appendage_editor_nodes.py b/appendage_editor_nodes.py new file mode 100644 index 0000000..df5c23f --- /dev/null +++ b/appendage_editor_nodes.py @@ -0,0 +1,505 @@ +import json +import copy +import math +import torch +import numpy as np +from .util import scale, draw_pose_json +from .openpose_editor_nodes import OpenposeEditorNode + +class AppendageEditorNode: + @classmethod + def INPUT_TYPES(s): + return { + "required": { + "POSE_KEYPOINT": ("POSE_KEYPOINT",), + "appendage_type": ([ + "left_upper_arm", "left_forearm", "left_full_arm", + "right_upper_arm", "right_forearm", "right_full_arm", + "left_upper_leg", "left_lower_leg", "left_full_leg", + "right_upper_leg", "right_lower_leg", "right_full_leg", + "left_hand", "right_hand", "left_foot", "right_foot", + "torso", "shoulders" + ], { + "default": "left_upper_arm" + }), + }, + "optional": { + "scale": ("FLOAT", { + "default": 1.0, + "min": 0.0, + "max": 10.0, + "step": 0.05 + }), + "x_offset": ("FLOAT", { + "default": 0.0, + "min": -2.0, + "max": 2.0, + "step": 0.01 + }), + "y_offset": ("FLOAT", { + "default": 0.0, + "min": -2.0, + "max": 2.0, + "step": 0.01 + }), + "rotation": ("FLOAT", { + "default": 0.0, + "min": -180.0, + "max": 180.0, + "step": 1.0 + }), + "bidirectional_scale": ("BOOLEAN", { + "default": False, + "tooltip": "If true, scales in both directions from pivot. If false, only scales away from body to prevent cannibalizing adjacent parts." + }), + "length_scale": ("BOOLEAN", { + "default": False, + "tooltip": "If true, scales the total length of the appendage while keeping the attachment point fixed. Overrides bidirectional_scale." + }), + "person_index": ("INT", { + "default": -1, + "min": -1, + "max": 100, + "tooltip": "Person to edit (-1 for all people)" + }), + "list_mismatch_behavior": (["truncate", "loop", "repeat"], {"default": "loop", "tooltip": "Truncate: Truncate the list to the shortest length. Loop: Loop the list to the longest length. Repeat: Repeat the list to the longest length."}), + }, + } + + RETURN_NAMES = ("POSE_KEYPOINT",) + RETURN_TYPES = ("POSE_KEYPOINT",) + FUNCTION = "edit_appendage" + CATEGORY = "ultimate-openpose" + + def edit_appendage(self, POSE_KEYPOINT, appendage_type, scale=1.0, x_offset=0.0, y_offset=0.0, rotation=0.0, bidirectional_scale=False, length_scale=False, person_index=-1, list_mismatch_behavior="loop"): + if POSE_KEYPOINT is None: + return (None,) + + # Deep copy to avoid modifying the original + pose_data = copy.deepcopy(POSE_KEYPOINT) + if not isinstance(pose_data, list): + pose_data = [pose_data] + + pose_count = len(pose_data) + + # Normalize scale parameters to handle lists vs single floats using the original node's methods + scale_params = [scale, x_offset, y_offset, rotation] + output_length = OpenposeEditorNode.determine_output_length(scale_params, pose_count, list_mismatch_behavior) + + scale_list = OpenposeEditorNode.normalize_scale_parameter(scale, output_length, list_mismatch_behavior) + x_offset_list = OpenposeEditorNode.normalize_scale_parameter(x_offset, output_length, list_mismatch_behavior) + y_offset_list = OpenposeEditorNode.normalize_scale_parameter(y_offset, output_length, list_mismatch_behavior) + rotation_list = OpenposeEditorNode.normalize_scale_parameter(rotation, output_length, list_mismatch_behavior) + + # Process each frame with its corresponding parameter values + output_pose_data = [] + + for i in range(output_length): + # Get the pose data for this index + pose_idx = i if i < pose_count else pose_count - 1 + if list_mismatch_behavior == "loop" and pose_count > 0: + pose_idx = i % pose_count + + # Get current frame and parameter values + current_frame = copy.deepcopy(pose_data[pose_idx]) + current_scale = scale_list[i] + current_x_offset = x_offset_list[i] + current_y_offset = y_offset_list[i] + current_rotation = rotation_list[i] + + # Apply transformations to this frame + if 'people' in current_frame: + people_to_edit = range(len(current_frame['people'])) if person_index == -1 else [person_index] + + for person_idx in people_to_edit: + if person_idx >= len(current_frame['people']): + continue + + person = current_frame['people'][person_idx] + + if appendage_type in ["left_hand", "right_hand"]: + self._edit_hand_appendage(person, appendage_type, current_scale, current_x_offset, current_y_offset, current_rotation, bidirectional_scale, length_scale) + else: + self._edit_body_appendage(person, appendage_type, current_scale, current_x_offset, current_y_offset, current_rotation, bidirectional_scale, length_scale) + + output_pose_data.append(current_frame) + + return (output_pose_data if isinstance(POSE_KEYPOINT, list) else output_pose_data[0],) + + def _edit_hand_appendage(self, person, appendage_type, scale_factor, x_offset, y_offset, rotation, bidirectional_scale, length_scale): + """Edit hand appendages using hand keypoints.""" + keypoint_field = "hand_left_keypoints_2d" if appendage_type == "left_hand" else "hand_right_keypoints_2d" + + if keypoint_field not in person or not person[keypoint_field]: + return + + keypoints = person[keypoint_field] + + # Use wrist (first point) as pivot for hands + if len(keypoints) >= 3 and keypoints[2] > 0: + pivot = [keypoints[0], keypoints[1]] + else: + # Calculate center of mass if wrist not available + pivot = self._calculate_center_of_mass(keypoints) + if pivot is None: + return + + # Apply transformations + new_keypoints = self._apply_transformations(keypoints, scale_factor, x_offset, y_offset, rotation, pivot, bidirectional_scale, length_scale) + person[keypoint_field] = new_keypoints + + def _edit_body_appendage(self, person, appendage_type, scale_factor, x_offset, y_offset, rotation, bidirectional_scale, length_scale): + """Edit body appendages (arms, legs, feet) using body pose keypoints.""" + if 'pose_keypoints_2d' not in person or not person['pose_keypoints_2d']: + return + + keypoints = person['pose_keypoints_2d'] + + # Get keypoint indices for the specific appendage + appendage_indices, pivot_index = self._get_appendage_indices(appendage_type) + if not appendage_indices: + return + + # Calculate pivot point for the appendage + pivot = self._calculate_appendage_pivot(keypoints, appendage_indices, pivot_index) + if pivot is None: + return + + # Apply transformations only to the appendage keypoints + new_keypoints = keypoints[:] + + if length_scale and scale_factor != 1.0: + # For length scaling, we need to handle multi-segment appendages specially + new_keypoints = self._apply_length_scale(keypoints, appendage_indices, pivot_index, scale_factor) + + for i in range(0, len(keypoints), 3): + keypoint_idx = i // 3 + if keypoint_idx in appendage_indices and len(keypoints) > i+2: + x, y, conf = keypoints[i], keypoints[i+1], keypoints[i+2] + + if conf > 0: + # If length scaling was applied, use the new keypoints + if length_scale and scale_factor != 1.0: + x, y = new_keypoints[i], new_keypoints[i+1] + else: + # Apply rotation + if rotation != 0.0: + rad = math.radians(rotation) + cos_r, sin_r = math.cos(rad), math.sin(rad) + rel_x, rel_y = x - pivot[0], y - pivot[1] + x = rel_x * cos_r - rel_y * sin_r + pivot[0] + y = rel_x * sin_r + rel_y * cos_r + pivot[1] + + # Apply scaling with directional control + if scale_factor != 1.0: + if bidirectional_scale: + scaled_point = scale([x, y], scale_factor, pivot) + x, y = scaled_point[0], scaled_point[1] + else: + # Unidirectional scaling - only scale away from body + x, y = self._apply_unidirectional_scale([x, y], scale_factor, pivot, keypoint_idx, pivot_index) + + # Always apply rotation and offset even with length scaling + if length_scale and rotation != 0.0: + rad = math.radians(rotation) + cos_r, sin_r = math.cos(rad), math.sin(rad) + rel_x, rel_y = x - pivot[0], y - pivot[1] + x = rel_x * cos_r - rel_y * sin_r + pivot[0] + y = rel_x * sin_r + rel_y * cos_r + pivot[1] + + # Apply offset + x += x_offset + y += y_offset + + new_keypoints[i] = x + new_keypoints[i+1] = y + + person['pose_keypoints_2d'] = new_keypoints + + def _get_appendage_indices(self, appendage_type): + """Get OpenPose keypoint indices for specific appendages and their pivot points.""" + # COCO 18-keypoint format (0-based) used by ComfyUI ControlNet Aux OpenPose Pose node: + # 0: Nose, 1: Neck, 2: RShoulder, 3: RElbow, 4: RWrist, 5: LShoulder, 6: LElbow, 7: LWrist, + # 8: RHip, 9: RKnee, 10: RAnkle, 11: LHip, 12: LKnee, 13: LAnkle, 14: REye, 15: LEye, 16: REar, 17: LEar + + appendage_map = { + # Arms - COCO format + "left_upper_arm": ([5, 6], 5), # LShoulder, LElbow (pivot: shoulder) + "left_forearm": ([6, 7], 6), # LElbow, LWrist (pivot: elbow) + "left_full_arm": ([5, 6, 7], 5), # LShoulder, LElbow, LWrist (pivot: shoulder) + "right_upper_arm": ([2, 3], 2), # RShoulder, RElbow (pivot: shoulder) + "right_forearm": ([3, 4], 3), # RElbow, RWrist (pivot: elbow) + "right_full_arm": ([2, 3, 4], 2), # RShoulder, RElbow, RWrist (pivot: shoulder) + + # Legs - COCO format (FIXED!) + "left_upper_leg": ([11, 12], 11), # LHip, LKnee (pivot: hip) + "left_lower_leg": ([12, 13], 12), # LKnee, LAnkle (pivot: knee) - FIXED: was [13,14] which was LAnkle,REye! + "left_full_leg": ([11, 12, 13], 11), # LHip, LKnee, LAnkle (pivot: hip) + "right_upper_leg": ([8, 9], 8), # RHip, RKnee (pivot: hip) + "right_lower_leg": ([9, 10], 9), # RKnee, RAnkle (pivot: knee) + "right_full_leg": ([8, 9, 10], 8), # RHip, RKnee, RAnkle (pivot: hip) + + # Feet - COCO format (no foot keypoints in COCO, use ankle only) + "left_foot": ([13], 13), # LAnkle only (pivot: ankle) + "right_foot": ([10], 10), # RAnkle only (pivot: ankle) + + # Torso and Shoulders - COCO format + "torso": ([1, 2, 5, 8, 11], 1), # Neck, RShoulder, LShoulder, RHip, LHip (pivot: neck) + "shoulders": ([2, 5], 1), # RShoulder, LShoulder (pivot: neck) + } + + result = appendage_map.get(appendage_type, ([], None)) + return result[0], result[1] + + def _calculate_appendage_pivot(self, keypoints, appendage_indices, pivot_index): + """Calculate pivot point for body appendage using specified pivot index.""" + if pivot_index is not None: + # Use specific pivot point (e.g., shoulder for upper arm, elbow for forearm) + i = pivot_index * 3 + if len(keypoints) > i+2 and keypoints[i+2] > 0: + return [keypoints[i], keypoints[i+1]] + + # Fallback to center of mass if pivot point not available + valid_points = [] + for idx in appendage_indices: + i = idx * 3 + if len(keypoints) > i+2 and keypoints[i+2] > 0: + valid_points.append([keypoints[i], keypoints[i+1]]) + + if not valid_points: + return None + + pivot_x = sum(p[0] for p in valid_points) / len(valid_points) + pivot_y = sum(p[1] for p in valid_points) / len(valid_points) + return [pivot_x, pivot_y] + + def _apply_unidirectional_scale(self, point, scale_factor, pivot, keypoint_idx, pivot_index): + """Apply scaling only in the direction away from the body/pivot.""" + x, y = point + + if keypoint_idx == pivot_index: + # Don't scale the pivot point itself + return x, y + + # Calculate direction vector from pivot to point + dx = x - pivot[0] + dy = y - pivot[1] + + # Scale only the distance, keeping direction + distance = math.sqrt(dx*dx + dy*dy) + if distance > 0: + new_distance = distance * scale_factor + scale_ratio = new_distance / distance + + new_x = pivot[0] + dx * scale_ratio + new_y = pivot[1] + dy * scale_ratio + return new_x, new_y + + return x, y + + def _calculate_center_of_mass(self, keypoints): + """Calculate center of mass from valid keypoints.""" + valid_points = [] + for i in range(0, len(keypoints), 3): + if len(keypoints) > i+2 and keypoints[i+2] > 0: + valid_points.append([keypoints[i], keypoints[i+1]]) + + if not valid_points: + return None + + pivot_x = sum(p[0] for p in valid_points) / len(valid_points) + pivot_y = sum(p[1] for p in valid_points) / len(valid_points) + return [pivot_x, pivot_y] + + def _apply_transformations(self, keypoints, scale_factor, x_offset, y_offset, rotation, pivot, bidirectional_scale, length_scale): + """Apply transformations to all keypoints.""" + new_keypoints = [] + for i in range(0, len(keypoints), 3): + if len(keypoints) > i+2: + x, y, conf = keypoints[i], keypoints[i+1], keypoints[i+2] + + if conf > 0: + # Apply rotation + if rotation != 0.0: + rad = math.radians(rotation) + cos_r, sin_r = math.cos(rad), math.sin(rad) + rel_x, rel_y = x - pivot[0], y - pivot[1] + x = rel_x * cos_r - rel_y * sin_r + pivot[0] + y = rel_x * sin_r + rel_y * cos_r + pivot[1] + + # Apply scaling + if scale_factor != 1.0: + if length_scale: + # Length scale - only scale the distance, keeping direction + distance = math.sqrt((x - pivot[0])**2 + (y - pivot[1])**2) + if distance > 0: + new_distance = distance * scale_factor + scale_ratio = new_distance / distance + x, y = pivot[0] + (x - pivot[0]) * scale_ratio, pivot[1] + (y - pivot[1]) * scale_ratio + else: + if bidirectional_scale: + scaled_point = scale([x, y], scale_factor, pivot) + x, y = scaled_point[0], scaled_point[1] + else: + # For hands, use unidirectional scaling from wrist + x, y = self._apply_unidirectional_scale([x, y], scale_factor, pivot, i//3, 0) + + # Apply offset + x += x_offset + y += y_offset + + new_keypoints.extend([x, y, conf]) + + return new_keypoints + + def _apply_length_scale(self, keypoints, appendage_indices, pivot_index, scale_factor): + """Apply length scaling to multi-segment appendages.""" + new_keypoints = keypoints[:] + + if scale_factor == 1.0: + return new_keypoints + + # Get valid appendage points in order + appendage_points = [] + for idx in sorted(appendage_indices): + i = idx * 3 + if len(keypoints) > i+2 and keypoints[i+2] > 0: + appendage_points.append([keypoints[i], keypoints[i+1], idx]) + + if len(appendage_points) < 2: + return new_keypoints + + # Calculate current total length + total_length = 0.0 + for i in range(1, len(appendage_points)): + dx = appendage_points[i][0] - appendage_points[i-1][0] + dy = appendage_points[i][1] - appendage_points[i-1][1] + total_length += math.sqrt(dx*dx + dy*dy) + + if total_length == 0: + return new_keypoints + + # Calculate new total length + new_total_length = total_length * scale_factor + + # Keep pivot point fixed and redistribute others + pivot_point = None + for point in appendage_points: + if point[2] == pivot_index: + pivot_point = point + break + + if pivot_point is None: + pivot_point = appendage_points[0] # Use first point as pivot + + # Redistribute points along the appendage direction + for i, point in enumerate(appendage_points): + if point[2] == pivot_point[2]: + continue # Keep pivot fixed + + # Calculate original distance from pivot + dx = point[0] - pivot_point[0] + dy = point[1] - pivot_point[1] + original_distance = math.sqrt(dx*dx + dy*dy) + + if original_distance > 0: + # Calculate new distance (scaled) + new_distance = original_distance * scale_factor + + # Calculate new position + direction_x = dx / original_distance + direction_y = dy / original_distance + + new_x = pivot_point[0] + direction_x * new_distance + new_y = pivot_point[1] + direction_y * new_distance + + # Update keypoints + idx = point[2] * 3 + new_keypoints[idx] = new_x + new_keypoints[idx+1] = new_y + + return new_keypoints + + +class PoseRendererNode: + @classmethod + def INPUT_TYPES(s): + return { + "required": { + "POSE_KEYPOINT": ("POSE_KEYPOINT",), + }, + "optional": { + "show_body": ("BOOLEAN", {"default": True}), + "show_face": ("BOOLEAN", {"default": True}), + "show_hands": ("BOOLEAN", {"default": True}), + "resolution_x": ("INT", { + "default": -1, + "min": -1, + "max": 12800 + }), + "pose_marker_size": ("INT", { + "default": 4, + "min": 0, + "max": 100 + }), + "face_marker_size": ("INT", { + "default": 3, + "min": 0, + "max": 100 + }), + "hand_marker_size": ("INT", { + "default": 2, + "min": 0, + "max": 100 + }), + }, + } + + RETURN_NAMES = ("IMAGE",) + RETURN_TYPES = ("IMAGE",) + FUNCTION = "render_pose" + CATEGORY = "ultimate-openpose" + + def render_pose(self, POSE_KEYPOINT, show_body=True, show_face=True, show_hands=True, resolution_x=-1, pose_marker_size=4, face_marker_size=3, hand_marker_size=2): + if POSE_KEYPOINT is None: + # Create default blank image + W, H = 512, 768 + W_scaled = resolution_x if resolution_x >= 64 else W + H_scaled = int(H * (W_scaled * 1.0 / W)) + blank_img = np.zeros((H_scaled, W_scaled, 3), dtype=np.uint8) + pose_img_np = np.array([blank_img]).astype(np.float32) / 255 + return (torch.from_numpy(pose_img_np),) + + # Convert pose keypoints to JSON string for rendering + if isinstance(POSE_KEYPOINT, list): + pose_json = json.dumps(POSE_KEYPOINT) + else: + pose_json = json.dumps([POSE_KEYPOINT]) + + # Use existing draw_pose_json function to render + pose_imgs = draw_pose_json( + pose_json, + resolution_x, + show_body, + show_face, + show_hands, + pose_marker_size, + face_marker_size, + hand_marker_size, + 1.0, # hands_scale + 1.0, # body_scale + 1.0, # head_scale + 1.0 # overall_scale + ) + + if pose_imgs: + pose_imgs_np = np.array(pose_imgs).astype(np.float32) / 255 + return (torch.from_numpy(pose_imgs_np),) + + # Create default blank image if rendering fails + W, H = 512, 768 + W_scaled = resolution_x if resolution_x >= 64 else W + H_scaled = int(H * (W_scaled * 1.0 / W)) + blank_img = np.zeros((H_scaled, W_scaled, 3), dtype=np.uint8) + pose_img_np = np.array([blank_img]).astype(np.float32) / 255 + return (torch.from_numpy(pose_img_np),) \ No newline at end of file From ea50244cd95102872209f60087ed49c80e750ff8 Mon Sep 17 00:00:00 2001 From: RyanOnTheInside <7623207+ryanontheinside@users.noreply.github.com> Date: Wed, 4 Jun 2025 16:41:37 -0400 Subject: [PATCH 2/5] remove scale, simplify --- appendage_editor_nodes.py | 142 ++++++-------------------------------- 1 file changed, 23 insertions(+), 119 deletions(-) diff --git a/appendage_editor_nodes.py b/appendage_editor_nodes.py index df5c23f..918eb67 100644 --- a/appendage_editor_nodes.py +++ b/appendage_editor_nodes.py @@ -52,10 +52,6 @@ class AppendageEditorNode: "default": False, "tooltip": "If true, scales in both directions from pivot. If false, only scales away from body to prevent cannibalizing adjacent parts." }), - "length_scale": ("BOOLEAN", { - "default": False, - "tooltip": "If true, scales the total length of the appendage while keeping the attachment point fixed. Overrides bidirectional_scale." - }), "person_index": ("INT", { "default": -1, "min": -1, @@ -71,7 +67,7 @@ class AppendageEditorNode: FUNCTION = "edit_appendage" CATEGORY = "ultimate-openpose" - def edit_appendage(self, POSE_KEYPOINT, appendage_type, scale=1.0, x_offset=0.0, y_offset=0.0, rotation=0.0, bidirectional_scale=False, length_scale=False, person_index=-1, list_mismatch_behavior="loop"): + def edit_appendage(self, POSE_KEYPOINT, appendage_type, scale=1.0, x_offset=0.0, y_offset=0.0, rotation=0.0, bidirectional_scale=False, person_index=-1, list_mismatch_behavior="loop"): if POSE_KEYPOINT is None: return (None,) @@ -118,15 +114,15 @@ class AppendageEditorNode: person = current_frame['people'][person_idx] if appendage_type in ["left_hand", "right_hand"]: - self._edit_hand_appendage(person, appendage_type, current_scale, current_x_offset, current_y_offset, current_rotation, bidirectional_scale, length_scale) + self._edit_hand_appendage(person, appendage_type, current_scale, current_x_offset, current_y_offset, current_rotation, bidirectional_scale) else: - self._edit_body_appendage(person, appendage_type, current_scale, current_x_offset, current_y_offset, current_rotation, bidirectional_scale, length_scale) + self._edit_body_appendage(person, appendage_type, current_scale, current_x_offset, current_y_offset, current_rotation, bidirectional_scale) output_pose_data.append(current_frame) return (output_pose_data if isinstance(POSE_KEYPOINT, list) else output_pose_data[0],) - def _edit_hand_appendage(self, person, appendage_type, scale_factor, x_offset, y_offset, rotation, bidirectional_scale, length_scale): + def _edit_hand_appendage(self, person, appendage_type, scale_factor, x_offset, y_offset, rotation, bidirectional_scale): """Edit hand appendages using hand keypoints.""" keypoint_field = "hand_left_keypoints_2d" if appendage_type == "left_hand" else "hand_right_keypoints_2d" @@ -145,10 +141,10 @@ class AppendageEditorNode: return # Apply transformations - new_keypoints = self._apply_transformations(keypoints, scale_factor, x_offset, y_offset, rotation, pivot, bidirectional_scale, length_scale) + new_keypoints = self._apply_transformations(keypoints, scale_factor, x_offset, y_offset, rotation, pivot, bidirectional_scale) person[keypoint_field] = new_keypoints - def _edit_body_appendage(self, person, appendage_type, scale_factor, x_offset, y_offset, rotation, bidirectional_scale, length_scale): + def _edit_body_appendage(self, person, appendage_type, scale_factor, x_offset, y_offset, rotation, bidirectional_scale): """Edit body appendages (arms, legs, feet) using body pose keypoints.""" if 'pose_keypoints_2d' not in person or not person['pose_keypoints_2d']: return @@ -168,45 +164,29 @@ class AppendageEditorNode: # Apply transformations only to the appendage keypoints new_keypoints = keypoints[:] - if length_scale and scale_factor != 1.0: - # For length scaling, we need to handle multi-segment appendages specially - new_keypoints = self._apply_length_scale(keypoints, appendage_indices, pivot_index, scale_factor) - for i in range(0, len(keypoints), 3): keypoint_idx = i // 3 if keypoint_idx in appendage_indices and len(keypoints) > i+2: x, y, conf = keypoints[i], keypoints[i+1], keypoints[i+2] if conf > 0: - # If length scaling was applied, use the new keypoints - if length_scale and scale_factor != 1.0: - x, y = new_keypoints[i], new_keypoints[i+1] - else: - # Apply rotation - if rotation != 0.0: - rad = math.radians(rotation) - cos_r, sin_r = math.cos(rad), math.sin(rad) - rel_x, rel_y = x - pivot[0], y - pivot[1] - x = rel_x * cos_r - rel_y * sin_r + pivot[0] - y = rel_x * sin_r + rel_y * cos_r + pivot[1] - - # Apply scaling with directional control - if scale_factor != 1.0: - if bidirectional_scale: - scaled_point = scale([x, y], scale_factor, pivot) - x, y = scaled_point[0], scaled_point[1] - else: - # Unidirectional scaling - only scale away from body - x, y = self._apply_unidirectional_scale([x, y], scale_factor, pivot, keypoint_idx, pivot_index) - - # Always apply rotation and offset even with length scaling - if length_scale and rotation != 0.0: + # Apply rotation + if rotation != 0.0: rad = math.radians(rotation) cos_r, sin_r = math.cos(rad), math.sin(rad) rel_x, rel_y = x - pivot[0], y - pivot[1] x = rel_x * cos_r - rel_y * sin_r + pivot[0] y = rel_x * sin_r + rel_y * cos_r + pivot[1] + # Apply scaling with directional control + if scale_factor != 1.0: + if bidirectional_scale: + scaled_point = scale([x, y], scale_factor, pivot) + x, y = scaled_point[0], scaled_point[1] + else: + # Unidirectional scaling - only scale away from body + x, y = self._apply_unidirectional_scale([x, y], scale_factor, pivot, keypoint_idx, pivot_index) + # Apply offset x += x_offset y += y_offset @@ -311,7 +291,7 @@ class AppendageEditorNode: pivot_y = sum(p[1] for p in valid_points) / len(valid_points) return [pivot_x, pivot_y] - def _apply_transformations(self, keypoints, scale_factor, x_offset, y_offset, rotation, pivot, bidirectional_scale, length_scale): + def _apply_transformations(self, keypoints, scale_factor, x_offset, y_offset, rotation, pivot, bidirectional_scale): """Apply transformations to all keypoints.""" new_keypoints = [] for i in range(0, len(keypoints), 3): @@ -329,20 +309,12 @@ class AppendageEditorNode: # Apply scaling if scale_factor != 1.0: - if length_scale: - # Length scale - only scale the distance, keeping direction - distance = math.sqrt((x - pivot[0])**2 + (y - pivot[1])**2) - if distance > 0: - new_distance = distance * scale_factor - scale_ratio = new_distance / distance - x, y = pivot[0] + (x - pivot[0]) * scale_ratio, pivot[1] + (y - pivot[1]) * scale_ratio + if bidirectional_scale: + scaled_point = scale([x, y], scale_factor, pivot) + x, y = scaled_point[0], scaled_point[1] else: - if bidirectional_scale: - scaled_point = scale([x, y], scale_factor, pivot) - x, y = scaled_point[0], scaled_point[1] - else: - # For hands, use unidirectional scaling from wrist - x, y = self._apply_unidirectional_scale([x, y], scale_factor, pivot, i//3, 0) + # For hands, use unidirectional scaling from wrist + x, y = self._apply_unidirectional_scale([x, y], scale_factor, pivot, i//3, 0) # Apply offset x += x_offset @@ -352,74 +324,6 @@ class AppendageEditorNode: return new_keypoints - def _apply_length_scale(self, keypoints, appendage_indices, pivot_index, scale_factor): - """Apply length scaling to multi-segment appendages.""" - new_keypoints = keypoints[:] - - if scale_factor == 1.0: - return new_keypoints - - # Get valid appendage points in order - appendage_points = [] - for idx in sorted(appendage_indices): - i = idx * 3 - if len(keypoints) > i+2 and keypoints[i+2] > 0: - appendage_points.append([keypoints[i], keypoints[i+1], idx]) - - if len(appendage_points) < 2: - return new_keypoints - - # Calculate current total length - total_length = 0.0 - for i in range(1, len(appendage_points)): - dx = appendage_points[i][0] - appendage_points[i-1][0] - dy = appendage_points[i][1] - appendage_points[i-1][1] - total_length += math.sqrt(dx*dx + dy*dy) - - if total_length == 0: - return new_keypoints - - # Calculate new total length - new_total_length = total_length * scale_factor - - # Keep pivot point fixed and redistribute others - pivot_point = None - for point in appendage_points: - if point[2] == pivot_index: - pivot_point = point - break - - if pivot_point is None: - pivot_point = appendage_points[0] # Use first point as pivot - - # Redistribute points along the appendage direction - for i, point in enumerate(appendage_points): - if point[2] == pivot_point[2]: - continue # Keep pivot fixed - - # Calculate original distance from pivot - dx = point[0] - pivot_point[0] - dy = point[1] - pivot_point[1] - original_distance = math.sqrt(dx*dx + dy*dy) - - if original_distance > 0: - # Calculate new distance (scaled) - new_distance = original_distance * scale_factor - - # Calculate new position - direction_x = dx / original_distance - direction_y = dy / original_distance - - new_x = pivot_point[0] + direction_x * new_distance - new_y = pivot_point[1] + direction_y * new_distance - - # Update keypoints - idx = point[2] * 3 - new_keypoints[idx] = new_x - new_keypoints[idx+1] = new_y - - return new_keypoints - class PoseRendererNode: @classmethod From ee26efc69a20f56eb7adab25d88737dfbeaa3558 Mon Sep 17 00:00:00 2001 From: RyanOnTheInside <7623207+ryanontheinside@users.noreply.github.com> Date: Wed, 4 Jun 2025 16:52:40 -0400 Subject: [PATCH 3/5] example --- examples/appendage_editor_example.json | 893 +++++++++++++++++++++++++ 1 file changed, 893 insertions(+) create mode 100644 examples/appendage_editor_example.json diff --git a/examples/appendage_editor_example.json b/examples/appendage_editor_example.json new file mode 100644 index 0000000..f4cd811 --- /dev/null +++ b/examples/appendage_editor_example.json @@ -0,0 +1,893 @@ +{ + "id": "b7f4ff12-a2ac-431b-8ea5-f6f2e7e1b982", + "revision": 0, + "last_node_id": 567, + "last_link_id": 1093, + "nodes": [ + { + "id": 556, + "type": "AppendageEditorNode", + "pos": [ + 53.78276062011719, + 1469.3089599609375 + ], + "size": [ + 456, + 226 + ], + "flags": {}, + "order": 7, + "mode": 0, + "inputs": [ + { + "name": "POSE_KEYPOINT", + "type": "POSE_KEYPOINT", + "link": 1078 + } + ], + "outputs": [ + { + "name": "POSE_KEYPOINT", + "type": "POSE_KEYPOINT", + "links": [ + 1080 + ] + } + ], + "properties": { + "cnr_id": "westNeighbor_ComfyUI-ultimate-openpose-editor", + "ver": "ea50244cd95102872209f60087ed49c80e750ff8", + "widget_ue_connectable": {}, + "Node name for S&R": "AppendageEditorNode" + }, + "widgets_values": [ + "shoulders", + 0.6300000000000001, + 0, + 0, + 0, + false, + -1, + "loop" + ] + }, + { + "id": 557, + "type": "AppendageEditorNode", + "pos": [ + 636.4037475585938, + 1472.977783203125 + ], + "size": [ + 456, + 226 + ], + "flags": {}, + "order": 9, + "mode": 0, + "inputs": [ + { + "name": "POSE_KEYPOINT", + "type": "POSE_KEYPOINT", + "link": 1080 + } + ], + "outputs": [ + { + "name": "POSE_KEYPOINT", + "type": "POSE_KEYPOINT", + "links": [ + 1082 + ] + } + ], + "properties": { + "cnr_id": "westNeighbor_ComfyUI-ultimate-openpose-editor", + "ver": "ea50244cd95102872209f60087ed49c80e750ff8", + "widget_ue_connectable": {}, + "Node name for S&R": "AppendageEditorNode" + }, + "widgets_values": [ + "torso", + 0.6600000000000001, + 0, + 0, + 0, + false, + -1, + "loop" + ] + }, + { + "id": 558, + "type": "AppendageEditorNode", + "pos": [ + 1213.678466796875, + 1469.1339111328125 + ], + "size": [ + 456, + 226 + ], + "flags": {}, + "order": 11, + "mode": 0, + "inputs": [ + { + "name": "POSE_KEYPOINT", + "type": "POSE_KEYPOINT", + "link": 1082 + } + ], + "outputs": [ + { + "name": "POSE_KEYPOINT", + "type": "POSE_KEYPOINT", + "links": [ + 1084 + ] + } + ], + "properties": { + "cnr_id": "westNeighbor_ComfyUI-ultimate-openpose-editor", + "ver": "ea50244cd95102872209f60087ed49c80e750ff8", + "widget_ue_connectable": {}, + "Node name for S&R": "AppendageEditorNode" + }, + "widgets_values": [ + "left_lower_leg", + 1, + 0.10000000000000002, + 0, + 11.818115234375, + false, + -1, + "loop" + ] + }, + { + "id": 521, + "type": "PoseRendererNode", + "pos": [ + 1762.187744140625, + 1466.8565673828125 + ], + "size": [ + 315, + 202 + ], + "flags": {}, + "order": 13, + "mode": 0, + "inputs": [ + { + "name": "POSE_KEYPOINT", + "type": "POSE_KEYPOINT", + "link": 1084 + } + ], + "outputs": [ + { + "name": "IMAGE", + "type": "IMAGE", + "links": [ + 1019 + ] + } + ], + "properties": { + "cnr_id": "westNeighbor_ComfyUI-ultimate-openpose-editor", + "ver": "4cfb549344eb2c6f893b364da88d690c5414d3a2", + "Node name for S&R": "PoseRendererNode", + "widget_ue_connectable": {} + }, + "widgets_values": [ + true, + true, + true, + -1, + 4, + 3, + 2 + ] + }, + { + "id": 524, + "type": "PreviewImage", + "pos": [ + 2159.125, + 1455.14111328125 + ], + "size": [ + 210, + 246 + ], + "flags": {}, + "order": 15, + "mode": 0, + "inputs": [ + { + "name": "images", + "type": "IMAGE", + "link": 1019 + } + ], + "outputs": [], + "properties": { + "cnr_id": "comfy-core", + "ver": "0.3.30", + "Node name for S&R": "PreviewImage", + "widget_ue_connectable": {} + }, + "widgets_values": [] + }, + { + "id": 518, + "type": "LoadImage", + "pos": [ + -1169.2633056640625, + 1442.111328125 + ], + "size": [ + 342.5999755859375, + 314 + ], + "flags": {}, + "order": 0, + "mode": 0, + "inputs": [], + "outputs": [ + { + "name": "IMAGE", + "type": "IMAGE", + "links": [ + 1014 + ] + }, + { + "name": "MASK", + "type": "MASK", + "links": null + } + ], + "properties": { + "cnr_id": "comfy-core", + "ver": "0.3.30", + "Node name for S&R": "LoadImage", + "widget_ue_connectable": {} + }, + "widgets_values": [ + "tpose.png", + "image" + ] + }, + { + "id": 516, + "type": "OpenposePreprocessor", + "pos": [ + -686.077392578125, + 1477.248779296875 + ], + "size": [ + 481.1999816894531, + 174 + ], + "flags": {}, + "order": 4, + "mode": 0, + "inputs": [ + { + "name": "image", + "type": "IMAGE", + "link": 1014 + } + ], + "outputs": [ + { + "name": "IMAGE", + "type": "IMAGE", + "links": null + }, + { + "name": "POSE_KEYPOINT", + "type": "POSE_KEYPOINT", + "links": [ + 1078, + 1086 + ] + } + ], + "properties": { + "cnr_id": "comfyui_controlnet_aux", + "ver": "1.0.7", + "Node name for S&R": "OpenposePreprocessor", + "widget_ue_connectable": {} + }, + "widgets_values": [ + "enable", + "enable", + "enable", + 512, + "disable" + ] + }, + { + "id": 564, + "type": "FeatureToFlexFloatParam", + "pos": [ + 20, + 2690 + ], + "size": [ + 343.35400390625, + 106 + ], + "flags": {}, + "order": 6, + "mode": 0, + "inputs": [ + { + "name": "feature", + "type": "FEATURE", + "link": 1088 + } + ], + "outputs": [ + { + "name": "PARAMETER", + "type": "FLOAT", + "links": [ + 1090 + ] + } + ], + "properties": { + "cnr_id": "comfyui_ryanontheinside", + "ver": "5907a3591a2feb5531d533194b6c26ead84ce835", + "widget_ue_connectable": {}, + "Node name for S&R": "FeatureToFlexFloatParam" + }, + "widgets_values": [ + true, + 0.5000000000000001, + 1 + ] + }, + { + "id": 565, + "type": "PoseRendererNode", + "pos": [ + 1610, + 2450 + ], + "size": [ + 315, + 202 + ], + "flags": {}, + "order": 12, + "mode": 0, + "inputs": [ + { + "name": "POSE_KEYPOINT", + "type": "POSE_KEYPOINT", + "link": 1093 + } + ], + "outputs": [ + { + "name": "IMAGE", + "type": "IMAGE", + "links": [ + 1092 + ] + } + ], + "properties": { + "cnr_id": "westNeighbor_ComfyUI-ultimate-openpose-editor", + "ver": "4cfb549344eb2c6f893b364da88d690c5414d3a2", + "widget_ue_connectable": {}, + "Node name for S&R": "PoseRendererNode" + }, + "widgets_values": [ + true, + true, + true, + -1, + 4, + 3, + 2 + ] + }, + { + "id": 561, + "type": "AppendageEditorNode", + "pos": [ + 1090, + 2430 + ], + "size": [ + 456, + 226 + ], + "flags": {}, + "order": 10, + "mode": 0, + "inputs": [ + { + "name": "POSE_KEYPOINT", + "type": "POSE_KEYPOINT", + "link": 1085 + }, + { + "name": "scale", + "shape": 7, + "type": "FLOAT", + "widget": { + "name": "scale" + }, + "link": 1090 + } + ], + "outputs": [ + { + "name": "POSE_KEYPOINT", + "type": "POSE_KEYPOINT", + "links": [ + 1093 + ] + } + ], + "properties": { + "cnr_id": "westNeighbor_ComfyUI-ultimate-openpose-editor", + "ver": "ea50244cd95102872209f60087ed49c80e750ff8", + "widget_ue_connectable": {}, + "Node name for S&R": "AppendageEditorNode" + }, + "widgets_values": [ + "torso", + 0.6600000000000001, + 0, + 0, + 0, + false, + -1, + "loop" + ] + }, + { + "id": 559, + "type": "Note", + "pos": [ + -1086.4820556640625, + 1180.427734375 + ], + "size": [ + 362.96533203125, + 155.5653076171875 + ], + "flags": {}, + "order": 1, + "mode": 0, + "inputs": [], + "outputs": [], + "properties": {}, + "widgets_values": [ + "Chain any number of the appendage editors together, then render with the pose renderer node." + ], + "color": "#432", + "bgcolor": "#653" + }, + { + "id": 560, + "type": "AppendageEditorNode", + "pos": [ + 570, + 2430 + ], + "size": [ + 456, + 226 + ], + "flags": {}, + "order": 8, + "mode": 0, + "inputs": [ + { + "name": "POSE_KEYPOINT", + "type": "POSE_KEYPOINT", + "link": 1086 + }, + { + "name": "scale", + "shape": 7, + "type": "FLOAT", + "widget": { + "name": "scale" + }, + "link": 1089 + } + ], + "outputs": [ + { + "name": "POSE_KEYPOINT", + "type": "POSE_KEYPOINT", + "links": [ + 1085 + ] + } + ], + "properties": { + "cnr_id": "westNeighbor_ComfyUI-ultimate-openpose-editor", + "ver": "ea50244cd95102872209f60087ed49c80e750ff8", + "widget_ue_connectable": {}, + "Node name for S&R": "AppendageEditorNode" + }, + "widgets_values": [ + "shoulders", + 0.6300000000000001, + 0, + 0, + 0, + false, + -1, + "loop" + ] + }, + { + "id": 562, + "type": "DrawableFeatureNode", + "pos": [ + -1188.8538818359375, + 2666.18310546875 + ], + "size": [ + 1050, + 800 + ], + "flags": {}, + "order": 2, + "mode": 0, + "inputs": [], + "outputs": [ + { + "name": "FEATURE", + "type": "FEATURE", + "links": [ + 1087, + 1088 + ] + } + ], + "properties": { + "cnr_id": "comfyui_ryanontheinside", + "ver": "5907a3591a2feb5531d533194b6c26ead84ce835", + "widget_ue_connectable": {}, + "Node name for S&R": "DrawableFeatureNode" + }, + "widgets_values": [ + "drawn", + 30, + 30, + 512, + 512, + 0, + 1, + "linear", + 0, + "[[0,0.00197697778680872],[12,0.6067085132724491],[17,0.549718180796861],[21,0.8061749426494923],[26,0.796676465332732],[29,0.9993090183870311]]", + "selected_template", + 1, + "load", + "clear" + ], + "points": [ + [ + 0, + 0.00197697778680872 + ], + [ + 12, + 0.6067085132724491 + ], + [ + 17, + 0.549718180796861 + ], + [ + 21, + 0.8061749426494923 + ], + [ + 26, + 0.796676465332732 + ], + [ + 29, + 0.9993090183870311 + ] + ] + }, + { + "id": 567, + "type": "Note", + "pos": [ + -1087.937255859375, + 2311.41943359375 + ], + "size": [ + 406.4625549316406, + 253.2763671875 + ], + "flags": {}, + "order": 3, + "mode": 0, + "inputs": [], + "outputs": [], + "properties": {}, + "widgets_values": [ + "You can also schedule the parameters using features. We still have a single image as input, but 30 frames in the feature, so the number of images is increased (according to list_mismatch_behavior). Instead of a single image, a video of poses can be used if so desired. \n\nThis is a drawable feature below, but there are many feature sources including but not limited to \n\nAudio\nMIDI\nDepth\nColor\nProximity\nTime\nText \nArea\nand so on" + ], + "color": "#432", + "bgcolor": "#653" + }, + { + "id": 563, + "type": "FeatureToFlexFloatParam", + "pos": [ + 20, + 2490 + ], + "size": [ + 330.15570068359375, + 109.04576110839844 + ], + "flags": {}, + "order": 5, + "mode": 0, + "inputs": [ + { + "name": "feature", + "type": "FEATURE", + "link": 1087 + } + ], + "outputs": [ + { + "name": "PARAMETER", + "type": "FLOAT", + "links": [ + 1089 + ] + } + ], + "properties": { + "cnr_id": "comfyui_ryanontheinside", + "ver": "5907a3591a2feb5531d533194b6c26ead84ce835", + "widget_ue_connectable": {}, + "Node name for S&R": "FeatureToFlexFloatParam" + }, + "widgets_values": [ + false, + 0.6000000000000001, + 3.3600000000000008 + ] + }, + { + "id": 566, + "type": "PreviewImage", + "pos": [ + 1983.6669921875, + 2444.78759765625 + ], + "size": [ + 210, + 246 + ], + "flags": {}, + "order": 14, + "mode": 0, + "inputs": [ + { + "name": "images", + "type": "IMAGE", + "link": 1092 + } + ], + "outputs": [], + "properties": { + "cnr_id": "comfy-core", + "ver": "0.3.30", + "widget_ue_connectable": {}, + "Node name for S&R": "PreviewImage" + }, + "widgets_values": [] + } + ], + "links": [ + [ + 1014, + 518, + 0, + 516, + 0, + "IMAGE" + ], + [ + 1019, + 521, + 0, + 524, + 0, + "IMAGE" + ], + [ + 1078, + 516, + 1, + 556, + 0, + "POSE_KEYPOINT" + ], + [ + 1080, + 556, + 0, + 557, + 0, + "POSE_KEYPOINT" + ], + [ + 1082, + 557, + 0, + 558, + 0, + "POSE_KEYPOINT" + ], + [ + 1084, + 558, + 0, + 521, + 0, + "POSE_KEYPOINT" + ], + [ + 1085, + 560, + 0, + 561, + 0, + "POSE_KEYPOINT" + ], + [ + 1086, + 516, + 1, + 560, + 0, + "POSE_KEYPOINT" + ], + [ + 1087, + 562, + 0, + 563, + 0, + "FEATURE" + ], + [ + 1088, + 562, + 0, + 564, + 0, + "FEATURE" + ], + [ + 1089, + 563, + 0, + 560, + 1, + "FLOAT" + ], + [ + 1090, + 564, + 0, + 561, + 1, + "FLOAT" + ], + [ + 1092, + 565, + 0, + 566, + 0, + "IMAGE" + ], + [ + 1093, + 561, + 0, + 565, + 0, + "POSE_KEYPOINT" + ] + ], + "groups": [ + { + "id": 1, + "title": "Single image", + "bounding": [ + -1244.7318115234375, + 998.99267578125, + 3810.3720703125, + 943.616943359375 + ], + "color": "#3f789e", + "font_size": 24, + "flags": {} + }, + { + "id": 2, + "title": "Multi image", + "bounding": [ + -1244.4027099609375, + 2165.394775390625, + 3705.696533203125, + 1379.8316650390625 + ], + "color": "#3f789e", + "font_size": 24, + "flags": {} + } + ], + "config": {}, + "extra": { + "ds": { + "scale": 0.25937424601001946, + "offset": [ + 2334.3305516827477, + -871.2916448549256 + ] + }, + "node_versions": { + "comfy-core": "0.3.15", + "ComfyUI-VideoHelperSuite": "8629188458dc6cb832f871ece3bd273507e8a766", + "ComfyUI-Custom-Scripts": "a53ef9b617ed1331640d7a2cd97644995908dc00", + "ComfyUI_Comfyroll_CustomNodes": "d78b780ae43fcf8c6b7c6505e6ffb4584281ceca", + "ComfyUI-KJNodes": "86b5453a5ca9ecb883eedc9d0a96bf942b9ca73e", + "comfyui_ttp_toolset": "6dd3f3566ce0925b71e9cdb54243119685ccbc10", + "was-node-suite-comfyui": "3ed45af34a14551dc28cb3127235cc7197d4633f", + "ComfyUI-Frame-Interpolation": "c336f7184cb1ac1243381e725fea1ad2c0a10c09", + "comfyui-easy-use": "123917da9adec0d2b0b5f817deefb9ac3ed464f1", + "ComfyLiterals": "bdddb08ca82d90d75d97b1d437a652e0284a32ac", + "ComfyUI-Image-Filters": "0ff33fe29f7be072ad5d2cd89efa18fed82957fe", + "ComfyUI_essentials": "33ff89fd354d8ec3ab6affb605a79a931b445d99", + "ComfyUI-Impact-Pack": "1ae7cae2df8cca06027edfa3a24512671239d6c4", + "comfyui-detail-daemon": "90e703d3d3f979438471c646a5d030840a2caac3", + "comfyui-reactor-node": "a43075813dbd17de9c51c9fc7ea768eaf1681d89", + "ComfyUI-HunyuanVideoMultiLora": "7e3e3444d4e34557a24b3e0c502c94fe556237e4", + "ComfyUI-AutomaticCFG": "2e395317b65c05a97a0ef566c4a8c7969305dafa", + "ComfyUI_UltimateSDUpscale": "ff3fdfeee03de46d4462211cffd165d27155e858", + "ComfyUI-DepthAnythingV2": "003d7b44bafd3a8a4c3693a9ca3ddcd72f4883ab", + "comfyui-various": "36454f91606bbff4fc36d90234981ca4a47e2695", + "ComfyUI_SLK_joy_caption_two": "667751cab945bd8e9fb0be4d557d47e36821350a", + "comfyui-ollama": "cfa314d36efbb2344ba22a76256200016ad9570a", + "ComfyUI-Florence2": "90b012e922f8bb0482bcd2ae24cdc191ec12a11f", + "save-image-extended-comfyui": "5f104fddfc8281d9fd85d03e63c5f70454db6701" + }, + "VHS_latentpreview": false, + "VHS_latentpreviewrate": 0, + "VHS_MetadataImage": true, + "VHS_KeepIntermediate": true, + "ue_links": [], + "links_added_by_ue": [], + "frontendVersion": "1.17.11" + }, + "version": 0.4 +} \ No newline at end of file From a83728d68d641f68d85a19701e7fc6864057ae53 Mon Sep 17 00:00:00 2001 From: mhstar <143965219+westNeighbor@users.noreply.github.com> Date: Wed, 4 Jun 2025 17:07:44 -0500 Subject: [PATCH 4/5] Update __init__.py remove pose render node cause already having a separate one --- __init__.py | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/__init__.py b/__init__.py index 2bd1776..f5690f5 100644 --- a/__init__.py +++ b/__init__.py @@ -1,5 +1,5 @@ from .openpose_editor_nodes import OpenposeEditorNode -from .appendage_editor_nodes import AppendageEditorNode, PoseRendererNode +from .appendage_editor_nodes import AppendageEditorNode WEB_DIRECTORY = "js" @@ -7,13 +7,11 @@ WEB_DIRECTORY = "js" NODE_CLASS_MAPPINGS = { "OpenposeEditorNode": OpenposeEditorNode, "AppendageEditorNode": AppendageEditorNode, - "PoseRendererNode": PoseRendererNode, } NODE_DISPLAY_NAME_MAPPINGS = { "OpenposeEditorNode": "Openpose Editor Node", "AppendageEditorNode": "Appendage Editor", - "PoseRendererNode": "Pose Renderer", } __all__ = ["NODE_CLASS_MAPPINGS", "NODE_DISPLAY_NAME_MAPPINGS", "WEB_DIRECTORY"] From ebc42d49092a3eeca1aa74a3610dc5ef1b75be8e Mon Sep 17 00:00:00 2001 From: mhstar <143965219+westNeighbor@users.noreply.github.com> Date: Wed, 4 Jun 2025 17:26:04 -0500 Subject: [PATCH 5/5] Update appendage_editor_nodes.py remove pose render node cause there is already having a separate one --- appendage_editor_nodes.py | 84 --------------------------------------- 1 file changed, 84 deletions(-) diff --git a/appendage_editor_nodes.py b/appendage_editor_nodes.py index 918eb67..14d6df2 100644 --- a/appendage_editor_nodes.py +++ b/appendage_editor_nodes.py @@ -323,87 +323,3 @@ class AppendageEditorNode: new_keypoints.extend([x, y, conf]) return new_keypoints - - -class PoseRendererNode: - @classmethod - def INPUT_TYPES(s): - return { - "required": { - "POSE_KEYPOINT": ("POSE_KEYPOINT",), - }, - "optional": { - "show_body": ("BOOLEAN", {"default": True}), - "show_face": ("BOOLEAN", {"default": True}), - "show_hands": ("BOOLEAN", {"default": True}), - "resolution_x": ("INT", { - "default": -1, - "min": -1, - "max": 12800 - }), - "pose_marker_size": ("INT", { - "default": 4, - "min": 0, - "max": 100 - }), - "face_marker_size": ("INT", { - "default": 3, - "min": 0, - "max": 100 - }), - "hand_marker_size": ("INT", { - "default": 2, - "min": 0, - "max": 100 - }), - }, - } - - RETURN_NAMES = ("IMAGE",) - RETURN_TYPES = ("IMAGE",) - FUNCTION = "render_pose" - CATEGORY = "ultimate-openpose" - - def render_pose(self, POSE_KEYPOINT, show_body=True, show_face=True, show_hands=True, resolution_x=-1, pose_marker_size=4, face_marker_size=3, hand_marker_size=2): - if POSE_KEYPOINT is None: - # Create default blank image - W, H = 512, 768 - W_scaled = resolution_x if resolution_x >= 64 else W - H_scaled = int(H * (W_scaled * 1.0 / W)) - blank_img = np.zeros((H_scaled, W_scaled, 3), dtype=np.uint8) - pose_img_np = np.array([blank_img]).astype(np.float32) / 255 - return (torch.from_numpy(pose_img_np),) - - # Convert pose keypoints to JSON string for rendering - if isinstance(POSE_KEYPOINT, list): - pose_json = json.dumps(POSE_KEYPOINT) - else: - pose_json = json.dumps([POSE_KEYPOINT]) - - # Use existing draw_pose_json function to render - pose_imgs = draw_pose_json( - pose_json, - resolution_x, - show_body, - show_face, - show_hands, - pose_marker_size, - face_marker_size, - hand_marker_size, - 1.0, # hands_scale - 1.0, # body_scale - 1.0, # head_scale - 1.0 # overall_scale - ) - - if pose_imgs: - pose_imgs_np = np.array(pose_imgs).astype(np.float32) / 255 - return (torch.from_numpy(pose_imgs_np),) - - # Create default blank image if rendering fails - W, H = 512, 768 - W_scaled = resolution_x if resolution_x >= 64 else W - H_scaled = int(H * (W_scaled * 1.0 / W)) - blank_img = np.zeros((H_scaled, W_scaled, 3), dtype=np.uint8) - pose_img_np = np.array([blank_img]).astype(np.float32) / 255 - return (torch.from_numpy(pose_img_np),) \ No newline at end of file