Merge pull request #14 from ryanontheinside/refactor/phase-1
Refactor/phase 1
This commit is contained in:
@@ -3,7 +3,7 @@ from typing import List, Dict, Tuple, Any, Optional, Type
|
||||
import cv2
|
||||
import numpy as np
|
||||
|
||||
from ....src.mediapipe_vision.utils.image_utils import convert_to_cv2, convert_to_tensor
|
||||
from ....src.utils.image import convert_to_cv2, convert_to_tensor
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
@@ -3,11 +3,11 @@ import logging
|
||||
from typing import Optional, List, Any, Dict, Tuple
|
||||
|
||||
from .....src.mediapipe_vision.types import BLENDSHAPES_LIST # Updated type alias
|
||||
from .....src.mediapipe_vision.utils.misc_utils import get_blendshape_categories # Utility to get all category names
|
||||
from .....src.mediapipe_vision.utils.blendshape_utils import get_blendshape_categories # Utility to get all category names
|
||||
|
||||
# Import base trigger/control logic if we adapt base classes later, or implement directly for now
|
||||
from ...location_landmark.universal_landmark_nodes import FLOAT_EQUALITY_TOLERANCE # For trigger node comparisons
|
||||
from .....src.mediapipe_vision.utils.delta_utils import scale_value
|
||||
from .....src.utils.math import scale_value
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
@@ -6,7 +6,7 @@ import logging
|
||||
from typing import List, Dict, Tuple
|
||||
|
||||
from .....src.mediapipe_vision.types import LandmarkPoint
|
||||
from .....src.mediapipe_vision.utils.mask_utils import create_mask_from_points
|
||||
from .....src.utils.image import create_mask_from_points
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
_category = "Realtime Nodes/MediaPipe Vision/Face/FaceLandmark/Masking"
|
||||
|
||||
@@ -5,7 +5,7 @@ from scipy.spatial.transform import Rotation as R
|
||||
from typing import Optional, List, Any, Tuple, Dict
|
||||
|
||||
from .....src.mediapipe_vision.types import TRANSFORM_MATRIX_LIST
|
||||
from .....src.mediapipe_vision.utils.delta_utils import scale_value
|
||||
from .....src.utils.math import scale_value
|
||||
from ...location_landmark.universal_landmark_nodes import FLOAT_EQUALITY_TOLERANCE # For trigger node comparisons
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
@@ -5,8 +5,8 @@ import logging
|
||||
from typing import List
|
||||
|
||||
from .....src.mediapipe_vision.types import HandLandmarksResult
|
||||
# Reusing the helper function from face masking
|
||||
from .....src.mediapipe_vision.utils.mask_utils import create_mask_from_points
|
||||
# Updated import path
|
||||
from .....src.utils.image import create_mask_from_points
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
_category = "Realtime Nodes/MediaPipe Vision/Hand/HandLandmark/Masking"
|
||||
|
||||
@@ -15,9 +15,9 @@ from ....src.mediapipe_vision.types import (
|
||||
LandmarkPoint, FaceLandmarksResult, HandLandmarksResult, PoseLandmarksResult
|
||||
)
|
||||
|
||||
# Re-import needed utils if not available globally
|
||||
from ....src.mediapipe_vision.utils.delta_utils import scale_value
|
||||
from ....src.mediapipe_vision.utils.comfy_utils import AlwaysEqualProxy
|
||||
# Import from the new consolidated utilities
|
||||
from ....src.utils.math import scale_value
|
||||
from ....src.utils.general import AlwaysEqualProxy
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
@@ -3,7 +3,7 @@ import torch
|
||||
import numpy as np
|
||||
import cv2
|
||||
|
||||
from ....src.mediapipe_vision.utils.image_utils import convert_to_cv2, convert_to_tensor
|
||||
from ....src.utils.image import convert_to_cv2, convert_to_tensor
|
||||
from ..common.base_visualization_nodes import BaseDetectionVisualizationNode
|
||||
|
||||
import logging
|
||||
|
||||
@@ -6,7 +6,7 @@ import logging
|
||||
from typing import List, Dict, Tuple
|
||||
|
||||
from ....src.mediapipe_vision.types import PoseLandmarksResult
|
||||
from ....src.mediapipe_vision.utils.mask_utils import create_mask_from_points
|
||||
from ....src.utils.image import create_mask_from_points
|
||||
# Removed GetImageDimensionsNode import
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
from ....src.realtimenodes.control_base import ControlNodeBase
|
||||
import copy
|
||||
from ....src.utils.general_utils import AlwaysEqualProxy
|
||||
from ....src.utils.general import AlwaysEqualProxy
|
||||
|
||||
class StateResetNode(ControlNodeBase):
|
||||
"""Node that resets all control node states when triggered"""
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
from ....src.utils.general_utils import AlwaysEqualProxy
|
||||
from ....src.realtimenodes.control_base import ControlNodeBase
|
||||
from ....src.utils.general import AlwaysEqualProxy
|
||||
|
||||
class LazyCondition(ControlNodeBase):
|
||||
DESCRIPTION = "Uses lazy evaluation to truly skip execution of unused paths. Maintains state of the last value to circumvent feedback loops."
|
||||
|
||||
@@ -5,7 +5,7 @@ import numpy as np
|
||||
import cv2
|
||||
import torch
|
||||
import random
|
||||
from ....src.utils.general_utils import AlwaysEqualProxy
|
||||
from ....src.utils.general import AlwaysEqualProxy
|
||||
# # Add package root to Python path # <-- REMOVED
|
||||
# sys.path.append(os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
|
||||
from ....src.realtimenodes.control_base import ControlNodeBase
|
||||
|
||||
@@ -9,7 +9,7 @@ from PIL import Image, ImageFont, ImageDraw, ImageOps
|
||||
import nodes
|
||||
import random
|
||||
from torchvision import transforms
|
||||
from ...src.utils.general_utils import AlwaysEqualProxy
|
||||
from ...src.utils.general import AlwaysEqualProxy
|
||||
MAX_RESOLUTION = nodes.MAX_RESOLUTION # Get the same max resolution as core nodes
|
||||
|
||||
class DTypeConverter:
|
||||
|
||||
@@ -12,7 +12,7 @@ from mediapipe.tasks.python.core.base_options import BaseOptions
|
||||
from typing import List, Tuple
|
||||
# Import new types
|
||||
from ..types import FaceDetectionResult, BoundingBox, FaceKeypoint
|
||||
from ..utils.timestamp_provider import TimestampProvider # Import from utils
|
||||
from ...utils.timing import TimestampProvider # Import from utils
|
||||
|
||||
class FaceDetector:
|
||||
"""Detects faces in an image using MediaPipe FaceDetector."""
|
||||
|
||||
@@ -7,7 +7,7 @@ from mediapipe.tasks.python.core.base_options import BaseOptions
|
||||
from typing import List, Tuple, Optional, Dict
|
||||
# Import new types
|
||||
from ..types import LandmarkPoint, FaceLandmarksResult
|
||||
from ..utils.timestamp_provider import TimestampProvider # Import TimestampProvider
|
||||
from ...utils.timing import TimestampProvider # Import TimestampProvider
|
||||
|
||||
class FaceLandmarkDetector:
|
||||
"""Detects face landmarks and blendshapes using MediaPipe FaceLandmarker."""
|
||||
|
||||
@@ -4,7 +4,7 @@ import torch
|
||||
from mediapipe.tasks import python
|
||||
from mediapipe.tasks.python import vision
|
||||
from typing import List, Optional
|
||||
from ..utils.timestamp_provider import TimestampProvider
|
||||
from ...utils.timing import TimestampProvider
|
||||
|
||||
class FaceStylizer:
|
||||
"""Applies stylization to faces using MediaPipe FaceStylizer."""
|
||||
|
||||
@@ -7,7 +7,7 @@ from mediapipe.tasks.python.core.base_options import BaseOptions
|
||||
from typing import List
|
||||
# Import new types
|
||||
from ..types import GestureRecognitionResult, GestureCategory
|
||||
from ..utils.timestamp_provider import TimestampProvider # Import TimestampProvider
|
||||
from ...utils.timing import TimestampProvider # Import TimestampProvider
|
||||
|
||||
class GestureRecognizer:
|
||||
"""Recognizes hand gestures using MediaPipe GestureRecognizer."""
|
||||
|
||||
@@ -7,7 +7,7 @@ from mediapipe.tasks.python.core.base_options import BaseOptions
|
||||
from typing import List, Tuple, Optional
|
||||
# Import new types
|
||||
from ..types import LandmarkPoint, HandLandmarksResult
|
||||
from ..utils.timestamp_provider import TimestampProvider # Import TimestampProvider
|
||||
from ...utils.timing import TimestampProvider # Import TimestampProvider
|
||||
|
||||
class HandLandmarkDetector:
|
||||
"""Detects hand landmarks using MediaPipe HandLandmarker."""
|
||||
|
||||
@@ -12,7 +12,7 @@ from typing import List, Tuple, Optional, Dict, Any
|
||||
|
||||
# Import needed types
|
||||
from ..types import LandmarkPoint, HolisticLandmarksResult
|
||||
from ..utils.timestamp_provider import TimestampProvider
|
||||
from ...utils.timing import TimestampProvider
|
||||
|
||||
class HolisticLandmarkDetector:
|
||||
"""Detects holistic landmarks combining face, pose and hand tracking.
|
||||
|
||||
@@ -7,7 +7,8 @@ from mediapipe.tasks.python.core.base_options import BaseOptions
|
||||
from typing import List, Optional
|
||||
# Import new types
|
||||
from ..types import ImageEmbedderResult
|
||||
from ..utils.timestamp_provider import TimestampProvider # Import TimestampProvider
|
||||
# Import TimestampProvider from the new location
|
||||
from ...utils.timing import TimestampProvider
|
||||
|
||||
class ImageEmbedder:
|
||||
"""Generates image embeddings using MediaPipe ImageEmbedder."""
|
||||
|
||||
@@ -11,7 +11,7 @@ from mediapipe.tasks.python import vision
|
||||
from mediapipe.tasks.python.vision import ImageSegmenter
|
||||
import platform # Needed for OS check
|
||||
import logging
|
||||
from ..utils.timestamp_provider import TimestampProvider # Import TimestampProvider
|
||||
from ...utils.timing import TimestampProvider
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import logging
|
||||
import mediapipe as mp
|
||||
import numpy as np
|
||||
import torch
|
||||
@@ -11,7 +12,7 @@ from mediapipe.tasks.python.components.containers import keypoint
|
||||
|
||||
from typing import List, Optional
|
||||
from ..types import PointOfInterest
|
||||
from ..utils.timestamp_provider import TimestampProvider
|
||||
from ...utils.timing import TimestampProvider
|
||||
|
||||
import logging
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
@@ -7,7 +7,7 @@ from mediapipe.tasks.python.core.base_options import BaseOptions
|
||||
from typing import List
|
||||
# Import new types
|
||||
from ..types import ObjectDetectionResult, ObjectDetectionCategory, BoundingBox
|
||||
from ..utils.timestamp_provider import TimestampProvider # Import TimestampProvider
|
||||
from ...utils.timing import TimestampProvider
|
||||
|
||||
class ObjectDetector:
|
||||
"""Detects objects using MediaPipe ObjectDetector."""
|
||||
|
||||
@@ -7,7 +7,7 @@ from mediapipe.tasks.python.core.base_options import BaseOptions
|
||||
from typing import List, Tuple, Optional
|
||||
# Import new types
|
||||
from ..types import LandmarkPoint, PoseLandmarksResult
|
||||
from ..utils.timestamp_provider import TimestampProvider # Import TimestampProvider
|
||||
from ...utils.timing import TimestampProvider
|
||||
|
||||
class PoseLandmarkDetector:
|
||||
"""Detects pose landmarks in an image using MediaPipe PoseLandmarker."""
|
||||
|
||||
@@ -1,12 +1,19 @@
|
||||
"""Utility functions for MediaPipe Vision."""
|
||||
|
||||
# DEPRECATED: These imports are maintained only for backward compatibility.
|
||||
# For new code, please import directly from src.utils
|
||||
|
||||
from ...utils.math import scale_value
|
||||
from ...utils.image import convert_to_cv2, convert_to_tensor
|
||||
|
||||
# Keep only the functions specific to mediapipe_vision
|
||||
from .download_utils import download_model
|
||||
from .image_utils import convert_to_cv2, convert_to_tensor
|
||||
from .delta_utils import calculate_euclidean_delta, scale_value
|
||||
from .delta_utils import calculate_euclidean_delta
|
||||
|
||||
__all__ = [
|
||||
"convert_to_cv2",
|
||||
"convert_to_tensor",
|
||||
"calculate_euclidean_delta",
|
||||
"scale_value"
|
||||
"scale_value",
|
||||
"download_model"
|
||||
]
|
||||
@@ -1,8 +1,16 @@
|
||||
# DEPRECATED: This file is maintained only for backward compatibility.
|
||||
# For new code, please import directly from:
|
||||
# src.utils.math
|
||||
|
||||
# scale_value has been moved to src/utils/math.py
|
||||
# Please update your imports.
|
||||
|
||||
import math
|
||||
from typing import Optional
|
||||
|
||||
from ...utils.math import scale_value
|
||||
from ..types import LandmarkPoint # Assuming LandmarkPoint is in src/types.py
|
||||
|
||||
#TODO: refactor to common after unified coordinate system
|
||||
def calculate_euclidean_delta(p1: Optional[LandmarkPoint], p2: Optional[LandmarkPoint]) -> Optional[float]:
|
||||
"""Calculates the Euclidean distance between two LandmarkPoint objects."""
|
||||
if p1 is None or p2 is None:
|
||||
@@ -15,21 +23,4 @@ def calculate_euclidean_delta(p1: Optional[LandmarkPoint], p2: Optional[Landmark
|
||||
distance = math.sqrt(delta_x**2 + delta_y**2 + delta_z**2)
|
||||
return distance
|
||||
|
||||
def scale_value(value: Optional[float],
|
||||
delta_min: float, delta_max: float,
|
||||
output_min: float, output_max: float,
|
||||
clamp: bool = True) -> Optional[float]:
|
||||
"""Linearly scales a value from one range to another."""
|
||||
if value is None:
|
||||
return None
|
||||
if delta_max == delta_min: # Avoid division by zero
|
||||
return output_min if value <= delta_min else output_max
|
||||
|
||||
# Scale
|
||||
scaled = output_min + (value - delta_min) * (output_max - output_min) / (delta_max - delta_min)
|
||||
|
||||
# Clamp
|
||||
if clamp:
|
||||
scaled = max(output_min, min(output_max, scaled))
|
||||
|
||||
return scaled
|
||||
# scale_value is now imported from utils.math
|
||||
@@ -1,26 +0,0 @@
|
||||
# --- Helper Function ---
|
||||
import torch
|
||||
import numpy as np
|
||||
import cv2
|
||||
from typing import List, Tuple
|
||||
import logging
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
def create_mask_from_points(height: int, width: int, points: List[Tuple[int, int]], device='cpu') -> torch.Tensor:
|
||||
"""Creates a binary mask tensor from a list of points using convex hull."""
|
||||
mask = np.zeros((height, width), dtype=np.uint8)
|
||||
if len(points) < 3:
|
||||
logger.warning(f"Cannot create mask from {len(points)} points (need >= 3 for convex hull). Returning empty mask.")
|
||||
return torch.from_numpy(mask).float().unsqueeze(0).to(device)
|
||||
|
||||
try:
|
||||
np_points = np.array(points, dtype=np.int32)
|
||||
hull = cv2.convexHull(np_points)
|
||||
cv2.fillPoly(mask, [hull], 1)
|
||||
except Exception as e:
|
||||
logger.error(f"Error creating mask polygon: {e}")
|
||||
return torch.from_numpy(mask).float().unsqueeze(0).to(device)
|
||||
|
||||
mask_tensor = torch.from_numpy(mask).float().unsqueeze(0).to(device)
|
||||
return mask_tensor
|
||||
+20
-1
@@ -1 +1,20 @@
|
||||
# This file intentionally left blank
|
||||
"""
|
||||
Unified utilities package for ComfyUI RealTimeNodes.
|
||||
"""
|
||||
|
||||
# General utilities
|
||||
from .general import AlwaysEqualProxy
|
||||
|
||||
# Image utilities
|
||||
from .image import convert_to_cv2, convert_to_tensor, create_mask_from_points
|
||||
|
||||
# Math utilities
|
||||
from .math import scale_value, calculate_euclidean_distance, calculate_euclidean_distance_2d
|
||||
|
||||
# Timing utilities
|
||||
from .timing import TimestampProvider
|
||||
|
||||
# For backward compatibility, expose the original names
|
||||
# This allows existing code to continue working while we transition
|
||||
image_to_cv2 = convert_to_cv2
|
||||
cv2_to_image = convert_to_tensor
|
||||
@@ -1,8 +1,10 @@
|
||||
"""Utilities specific to ComfyUI integration."""
|
||||
"""
|
||||
General utilities for ComfyUI RealTimeNodes.
|
||||
"""
|
||||
|
||||
class AlwaysEqualProxy(str):
|
||||
"""Helper class to allow connecting multiple specific input types to a single input node.
|
||||
Borrowed from https://github.com/theUpsider/ComfyUI-Logic
|
||||
Originally borrowed from https://github.com/theUpsider/ComfyUI-Logic
|
||||
"""
|
||||
def __eq__(self, _):
|
||||
return True
|
||||
@@ -1,7 +0,0 @@
|
||||
class AlwaysEqualProxy(str):
|
||||
#borrowed from https://github.com/theUpsider/ComfyUI-Logic
|
||||
def __eq__(self, _):
|
||||
return True
|
||||
|
||||
def __ne__(self, _):
|
||||
return False
|
||||
@@ -1,6 +1,14 @@
|
||||
"""
|
||||
Image processing utilities for ComfyUI RealTimeNodes.
|
||||
|
||||
Contains functions for converting between different image formats,
|
||||
particularly between ComfyUI tensor format (BHWC) and OpenCV format.
|
||||
"""
|
||||
|
||||
import torch
|
||||
import numpy as np
|
||||
import cv2
|
||||
from typing import List, Union, Tuple
|
||||
|
||||
def convert_to_cv2(tensor: torch.Tensor) -> list:
|
||||
"""Converts a ComfyUI IMAGE tensor (BHWC, float32, 0-1) to a list of cv2 images (BGR, uint8)."""
|
||||
@@ -41,4 +49,20 @@ def convert_to_tensor(cv2_images) -> torch.Tensor:
|
||||
# Convert to tensor (already has batch dimension)
|
||||
tensor = torch.from_numpy(batch_np)
|
||||
|
||||
return tensor
|
||||
return tensor
|
||||
|
||||
def create_mask_from_points(height: int, width: int, points: List[Tuple[int, int]], device='cpu') -> torch.Tensor:
|
||||
"""Creates a binary mask tensor from a list of points using convex hull."""
|
||||
mask = np.zeros((height, width), dtype=np.uint8)
|
||||
if len(points) < 3:
|
||||
return torch.from_numpy(mask).float().unsqueeze(0).to(device)
|
||||
|
||||
try:
|
||||
np_points = np.array(points, dtype=np.int32)
|
||||
hull = cv2.convexHull(np_points)
|
||||
cv2.fillPoly(mask, [hull], 1)
|
||||
except Exception as e:
|
||||
return torch.from_numpy(mask).float().unsqueeze(0).to(device)
|
||||
|
||||
mask_tensor = torch.from_numpy(mask).float().unsqueeze(0).to(device)
|
||||
return mask_tensor
|
||||
@@ -0,0 +1,80 @@
|
||||
"""
|
||||
Math utility functions for RealTimeNodes.
|
||||
|
||||
Contains functions for mathematical operations including:
|
||||
- Value scaling and normalization
|
||||
- Distance calculations
|
||||
- Other general math operations
|
||||
"""
|
||||
|
||||
import math
|
||||
from typing import Optional, Tuple, Any
|
||||
|
||||
def scale_value(value: Optional[float],
|
||||
input_min: float, input_max: float,
|
||||
output_min: float, output_max: float,
|
||||
clamp: bool = True) -> Optional[float]:
|
||||
"""
|
||||
Linearly scales a value from one range to another.
|
||||
|
||||
Args:
|
||||
value: The value to scale
|
||||
input_min: Minimum value of input range
|
||||
input_max: Maximum value of input range
|
||||
output_min: Minimum value of output range
|
||||
output_max: Maximum value of output range
|
||||
clamp: Whether to clamp the output to the output range
|
||||
|
||||
Returns:
|
||||
Scaled value, or None if input was None
|
||||
"""
|
||||
if value is None:
|
||||
return None
|
||||
if input_max == input_min: # Avoid division by zero
|
||||
return output_min if value <= input_min else output_max
|
||||
|
||||
# Scale
|
||||
scaled = output_min + (value - input_min) * (output_max - output_min) / (input_max - input_min)
|
||||
|
||||
# Clamp
|
||||
if clamp:
|
||||
scaled = max(output_min, min(output_max, scaled))
|
||||
|
||||
return scaled
|
||||
|
||||
def calculate_euclidean_distance(point1: Tuple[float, float, float],
|
||||
point2: Tuple[float, float, float]) -> float:
|
||||
"""
|
||||
Calculates the Euclidean distance between two 3D points.
|
||||
|
||||
Args:
|
||||
point1: First point as (x, y, z)
|
||||
point2: Second point as (x, y, z)
|
||||
|
||||
Returns:
|
||||
Euclidean distance between the points
|
||||
"""
|
||||
delta_x = point1[0] - point2[0]
|
||||
delta_y = point1[1] - point2[1]
|
||||
delta_z = point1[2] - point2[2]
|
||||
|
||||
distance = math.sqrt(delta_x**2 + delta_y**2 + delta_z**2)
|
||||
return distance
|
||||
|
||||
def calculate_euclidean_distance_2d(point1: Tuple[float, float],
|
||||
point2: Tuple[float, float]) -> float:
|
||||
"""
|
||||
Calculates the Euclidean distance between two 2D points.
|
||||
|
||||
Args:
|
||||
point1: First point as (x, y)
|
||||
point2: Second point as (x, y)
|
||||
|
||||
Returns:
|
||||
Euclidean distance between the points
|
||||
"""
|
||||
delta_x = point1[0] - point2[0]
|
||||
delta_y = point1[1] - point2[1]
|
||||
|
||||
distance = math.sqrt(delta_x**2 + delta_y**2)
|
||||
return distance
|
||||
@@ -1,19 +1,22 @@
|
||||
"""
|
||||
Timing and timestamp utilities for RealTimeNodes.
|
||||
"""
|
||||
|
||||
import time
|
||||
|
||||
class TimestampProvider:
|
||||
"""
|
||||
Provides real-time, monotonic timestamps in milliseconds.
|
||||
|
||||
MediaPipe's RunningMode.VIDEO requires strictly increasing timestamp_ms values.
|
||||
Using time.time() alone can result in duplicates during tight loops, which causes
|
||||
MediaPipe to skip or freeze frames. This class ensures timestamps are always
|
||||
increasing while staying aligned with real elapsed time.
|
||||
This ensures timestamps are always increasing while staying aligned with real elapsed time.
|
||||
Useful for any process that requires monotonically increasing time values.
|
||||
"""
|
||||
def __init__(self):
|
||||
self._start_time = time.time()
|
||||
self._last_timestamp = 0
|
||||
|
||||
def next(self) -> int:
|
||||
"""Get the next timestamp in milliseconds, guaranteed to be greater than the previous one."""
|
||||
now = int((time.time() - self._start_time) * 1000)
|
||||
if now <= self._last_timestamp:
|
||||
now = self._last_timestamp + 1
|
||||
Reference in New Issue
Block a user