diff --git a/node_wrappers/mediapipe_vision/common/base_visualization_nodes.py b/node_wrappers/mediapipe_vision/common/base_visualization_nodes.py index 594fa0f..f258cd0 100644 --- a/node_wrappers/mediapipe_vision/common/base_visualization_nodes.py +++ b/node_wrappers/mediapipe_vision/common/base_visualization_nodes.py @@ -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__) diff --git a/node_wrappers/mediapipe_vision/face/landmark/blendshape.py b/node_wrappers/mediapipe_vision/face/landmark/blendshape.py index 61a79b1..135ac77 100644 --- a/node_wrappers/mediapipe_vision/face/landmark/blendshape.py +++ b/node_wrappers/mediapipe_vision/face/landmark/blendshape.py @@ -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__) diff --git a/node_wrappers/mediapipe_vision/face/landmark/face_masking.py b/node_wrappers/mediapipe_vision/face/landmark/face_masking.py index 0832272..54dd39f 100644 --- a/node_wrappers/mediapipe_vision/face/landmark/face_masking.py +++ b/node_wrappers/mediapipe_vision/face/landmark/face_masking.py @@ -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" diff --git a/node_wrappers/mediapipe_vision/face/landmark/head_pose.py b/node_wrappers/mediapipe_vision/face/landmark/head_pose.py index 4a0b891..6130c2a 100644 --- a/node_wrappers/mediapipe_vision/face/landmark/head_pose.py +++ b/node_wrappers/mediapipe_vision/face/landmark/head_pose.py @@ -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__) diff --git a/node_wrappers/mediapipe_vision/hand/landmark/hand_landmark_masking.py b/node_wrappers/mediapipe_vision/hand/landmark/hand_landmark_masking.py index 041b04e..b5279e5 100644 --- a/node_wrappers/mediapipe_vision/hand/landmark/hand_landmark_masking.py +++ b/node_wrappers/mediapipe_vision/hand/landmark/hand_landmark_masking.py @@ -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" diff --git a/node_wrappers/mediapipe_vision/location_landmark/universal_landmark_nodes.py b/node_wrappers/mediapipe_vision/location_landmark/universal_landmark_nodes.py index b284201..b5b1e50 100644 --- a/node_wrappers/mediapipe_vision/location_landmark/universal_landmark_nodes.py +++ b/node_wrappers/mediapipe_vision/location_landmark/universal_landmark_nodes.py @@ -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__) diff --git a/node_wrappers/mediapipe_vision/object_detection/object_detection_visualization.py b/node_wrappers/mediapipe_vision/object_detection/object_detection_visualization.py index ab5df83..ba065cb 100644 --- a/node_wrappers/mediapipe_vision/object_detection/object_detection_visualization.py +++ b/node_wrappers/mediapipe_vision/object_detection/object_detection_visualization.py @@ -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 diff --git a/node_wrappers/mediapipe_vision/pose/pose_landmark_masking.py b/node_wrappers/mediapipe_vision/pose/pose_landmark_masking.py index e282345..6af580c 100644 --- a/node_wrappers/mediapipe_vision/pose/pose_landmark_masking.py +++ b/node_wrappers/mediapipe_vision/pose/pose_landmark_masking.py @@ -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__) diff --git a/node_wrappers/realtimenodes/controls/state_management_controls.py b/node_wrappers/realtimenodes/controls/state_management_controls.py index 85ba291..09c0d82 100644 --- a/node_wrappers/realtimenodes/controls/state_management_controls.py +++ b/node_wrappers/realtimenodes/controls/state_management_controls.py @@ -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""" diff --git a/node_wrappers/realtimenodes/logic/logic.py b/node_wrappers/realtimenodes/logic/logic.py index 7bcdc91..7fcb23e 100644 --- a/node_wrappers/realtimenodes/logic/logic.py +++ b/node_wrappers/realtimenodes/logic/logic.py @@ -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." diff --git a/node_wrappers/realtimenodes/utils/utility_controls.py b/node_wrappers/realtimenodes/utils/utility_controls.py index e6a298e..92d08e5 100644 --- a/node_wrappers/realtimenodes/utils/utility_controls.py +++ b/node_wrappers/realtimenodes/utils/utility_controls.py @@ -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 diff --git a/node_wrappers/utils/misc_nodes.py b/node_wrappers/utils/misc_nodes.py index 7ce8959..d7ffcde 100644 --- a/node_wrappers/utils/misc_nodes.py +++ b/node_wrappers/utils/misc_nodes.py @@ -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: diff --git a/src/mediapipe_vision/face_detection/detector.py b/src/mediapipe_vision/face_detection/detector.py index 4a8b6e2..0e40e6e 100644 --- a/src/mediapipe_vision/face_detection/detector.py +++ b/src/mediapipe_vision/face_detection/detector.py @@ -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.""" diff --git a/src/mediapipe_vision/face_landmark/detector.py b/src/mediapipe_vision/face_landmark/detector.py index 90ce5fa..73b448d 100644 --- a/src/mediapipe_vision/face_landmark/detector.py +++ b/src/mediapipe_vision/face_landmark/detector.py @@ -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.""" diff --git a/src/mediapipe_vision/face_stylization/detector.py b/src/mediapipe_vision/face_stylization/detector.py index 9926d0f..706a0de 100644 --- a/src/mediapipe_vision/face_stylization/detector.py +++ b/src/mediapipe_vision/face_stylization/detector.py @@ -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.""" diff --git a/src/mediapipe_vision/gesture_recognition/detector.py b/src/mediapipe_vision/gesture_recognition/detector.py index 200692a..565274a 100644 --- a/src/mediapipe_vision/gesture_recognition/detector.py +++ b/src/mediapipe_vision/gesture_recognition/detector.py @@ -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.""" diff --git a/src/mediapipe_vision/hand_landmark/detector.py b/src/mediapipe_vision/hand_landmark/detector.py index 2b3eeb0..693764d 100644 --- a/src/mediapipe_vision/hand_landmark/detector.py +++ b/src/mediapipe_vision/hand_landmark/detector.py @@ -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.""" diff --git a/src/mediapipe_vision/holistic_landmark/detector.py b/src/mediapipe_vision/holistic_landmark/detector.py index 5acb471..33b157c 100644 --- a/src/mediapipe_vision/holistic_landmark/detector.py +++ b/src/mediapipe_vision/holistic_landmark/detector.py @@ -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. diff --git a/src/mediapipe_vision/image_embedding/detector.py b/src/mediapipe_vision/image_embedding/detector.py index 2a1c5f1..41aad69 100644 --- a/src/mediapipe_vision/image_embedding/detector.py +++ b/src/mediapipe_vision/image_embedding/detector.py @@ -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.""" diff --git a/src/mediapipe_vision/image_segmentation/segmenter.py b/src/mediapipe_vision/image_segmentation/segmenter.py index 9d4cc8c..f78b91c 100644 --- a/src/mediapipe_vision/image_segmentation/segmenter.py +++ b/src/mediapipe_vision/image_segmentation/segmenter.py @@ -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__) diff --git a/src/mediapipe_vision/interactive_segmentation/detector.py b/src/mediapipe_vision/interactive_segmentation/detector.py index c9c1774..c9aafd4 100644 --- a/src/mediapipe_vision/interactive_segmentation/detector.py +++ b/src/mediapipe_vision/interactive_segmentation/detector.py @@ -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__) diff --git a/src/mediapipe_vision/object_detection/detector.py b/src/mediapipe_vision/object_detection/detector.py index d5514e7..a4ab82b 100644 --- a/src/mediapipe_vision/object_detection/detector.py +++ b/src/mediapipe_vision/object_detection/detector.py @@ -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.""" diff --git a/src/mediapipe_vision/pose_landmark/detector.py b/src/mediapipe_vision/pose_landmark/detector.py index ae49bc6..b4e3c5e 100644 --- a/src/mediapipe_vision/pose_landmark/detector.py +++ b/src/mediapipe_vision/pose_landmark/detector.py @@ -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.""" diff --git a/src/mediapipe_vision/utils/__init__.py b/src/mediapipe_vision/utils/__init__.py index 5c7b5fd..5505374 100644 --- a/src/mediapipe_vision/utils/__init__.py +++ b/src/mediapipe_vision/utils/__init__.py @@ -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" ] \ No newline at end of file diff --git a/src/mediapipe_vision/utils/misc_utils.py b/src/mediapipe_vision/utils/blendshape_utils.py similarity index 100% rename from src/mediapipe_vision/utils/misc_utils.py rename to src/mediapipe_vision/utils/blendshape_utils.py diff --git a/src/mediapipe_vision/utils/delta_utils.py b/src/mediapipe_vision/utils/delta_utils.py index efbbf47..6642527 100644 --- a/src/mediapipe_vision/utils/delta_utils.py +++ b/src/mediapipe_vision/utils/delta_utils.py @@ -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 \ No newline at end of file +# scale_value is now imported from utils.math \ No newline at end of file diff --git a/src/mediapipe_vision/utils/mask_utils.py b/src/mediapipe_vision/utils/mask_utils.py deleted file mode 100644 index 3a80c74..0000000 --- a/src/mediapipe_vision/utils/mask_utils.py +++ /dev/null @@ -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 \ No newline at end of file diff --git a/src/utils/__init__.py b/src/utils/__init__.py index da22290..ed501ed 100644 --- a/src/utils/__init__.py +++ b/src/utils/__init__.py @@ -1 +1,20 @@ -# This file intentionally left blank \ No newline at end of file +""" +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 \ No newline at end of file diff --git a/src/mediapipe_vision/utils/comfy_utils.py b/src/utils/general.py similarity index 63% rename from src/mediapipe_vision/utils/comfy_utils.py rename to src/utils/general.py index b1c1f39..e911761 100644 --- a/src/mediapipe_vision/utils/comfy_utils.py +++ b/src/utils/general.py @@ -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 diff --git a/src/utils/general_utils.py b/src/utils/general_utils.py deleted file mode 100644 index 44b9be7..0000000 --- a/src/utils/general_utils.py +++ /dev/null @@ -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 \ No newline at end of file diff --git a/src/mediapipe_vision/utils/image_utils.py b/src/utils/image.py similarity index 61% rename from src/mediapipe_vision/utils/image_utils.py rename to src/utils/image.py index 9def0b3..5caa2fb 100644 --- a/src/mediapipe_vision/utils/image_utils.py +++ b/src/utils/image.py @@ -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 \ No newline at end of file + 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 \ No newline at end of file diff --git a/src/utils/math.py b/src/utils/math.py new file mode 100644 index 0000000..4f985d9 --- /dev/null +++ b/src/utils/math.py @@ -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 \ No newline at end of file diff --git a/src/mediapipe_vision/utils/timestamp_provider.py b/src/utils/timing.py similarity index 56% rename from src/mediapipe_vision/utils/timestamp_provider.py rename to src/utils/timing.py index 871c1bb..3c6bcf9 100644 --- a/src/mediapipe_vision/utils/timestamp_provider.py +++ b/src/utils/timing.py @@ -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