Cluster/core/nodes/preprocess_node.py
2025-07-17 17:04:56 +08:00

240 lines
8.8 KiB
Python

"""
Preprocessing node implementation for data transformation operations.
This module provides the PreprocessNode class which handles data preprocessing
operations in the pipeline, including image resizing, normalization, cropping,
and other transformation operations.
Main Components:
- PreprocessNode: Core preprocessing node implementation
- Image and data transformation operations
- Preprocessing configuration and validation
Usage:
from cluster4npu_ui.core.nodes.preprocess_node import PreprocessNode
node = PreprocessNode()
node.set_property('resize_width', 640)
node.set_property('resize_height', 480)
"""
from .base_node import BaseNodeWithProperties
class PreprocessNode(BaseNodeWithProperties):
"""
Preprocessing node for data transformation operations.
This node handles various preprocessing operations including image resizing,
normalization, cropping, and other transformations required before model inference.
"""
__identifier__ = 'com.cluster.preprocess_node'
NODE_NAME = 'Preprocess Node'
def __init__(self):
super().__init__()
# Setup node connections
self.add_input('input', multi_input=False, color=(255, 140, 0))
self.add_output('output', color=(0, 255, 0))
self.set_color(45, 126, 72)
# Initialize properties
self.setup_properties()
def setup_properties(self):
"""Initialize preprocessing-specific properties."""
# Image resizing
self.create_business_property('resize_width', 640, {
'min': 64,
'max': 4096,
'description': 'Target width for image resizing'
})
self.create_business_property('resize_height', 480, {
'min': 64,
'max': 4096,
'description': 'Target height for image resizing'
})
self.create_business_property('maintain_aspect_ratio', True, {
'description': 'Maintain aspect ratio during resizing'
})
# Normalization
self.create_business_property('normalize', True, {
'description': 'Apply normalization to input data'
})
self.create_business_property('normalization_type', 'zero_one', [
'zero_one', # [0, 1]
'neg_one_one', # [-1, 1]
'imagenet', # ImageNet mean/std
'custom' # Custom mean/std
])
self.create_business_property('custom_mean', '0.485,0.456,0.406', {
'placeholder': 'comma-separated values for RGB channels',
'description': 'Custom normalization mean values'
})
self.create_business_property('custom_std', '0.229,0.224,0.225', {
'placeholder': 'comma-separated values for RGB channels',
'description': 'Custom normalization std values'
})
# Cropping
self.create_business_property('crop_enabled', False, {
'description': 'Enable image cropping'
})
self.create_business_property('crop_type', 'center', [
'center', # Center crop
'random', # Random crop
'custom' # Custom coordinates
])
self.create_business_property('crop_width', 224, {
'min': 32,
'max': 2048,
'description': 'Crop width in pixels'
})
self.create_business_property('crop_height', 224, {
'min': 32,
'max': 2048,
'description': 'Crop height in pixels'
})
# Color space conversion
self.create_business_property('color_space', 'RGB', [
'RGB', 'BGR', 'HSV', 'LAB', 'YUV', 'GRAY'
])
# Operations chain
self.create_business_property('operations', 'resize,normalize', {
'placeholder': 'comma-separated: resize,normalize,crop,flip,rotate',
'description': 'Ordered list of preprocessing operations'
})
# Advanced options
self.create_business_property('enable_augmentation', False, {
'description': 'Enable data augmentation during preprocessing'
})
self.create_business_property('interpolation_method', 'bilinear', [
'nearest', 'bilinear', 'bicubic', 'lanczos'
])
def validate_configuration(self) -> tuple[bool, str]:
"""
Validate the current node configuration.
Returns:
Tuple of (is_valid, error_message)
"""
# Check resize dimensions
resize_width = self.get_property('resize_width')
resize_height = self.get_property('resize_height')
if not isinstance(resize_width, int) or resize_width < 64:
return False, "Resize width must be at least 64 pixels"
if not isinstance(resize_height, int) or resize_height < 64:
return False, "Resize height must be at least 64 pixels"
# Check crop dimensions if cropping is enabled
if self.get_property('crop_enabled'):
crop_width = self.get_property('crop_width')
crop_height = self.get_property('crop_height')
if crop_width > resize_width or crop_height > resize_height:
return False, "Crop dimensions cannot exceed resize dimensions"
# Validate operations string
operations = self.get_property('operations')
valid_operations = ['resize', 'normalize', 'crop', 'flip', 'rotate', 'blur', 'sharpen']
if operations:
ops_list = [op.strip() for op in operations.split(',')]
invalid_ops = [op for op in ops_list if op not in valid_operations]
if invalid_ops:
return False, f"Invalid operations: {', '.join(invalid_ops)}"
return True, ""
def get_preprocessing_config(self) -> dict:
"""
Get preprocessing configuration for pipeline execution.
Returns:
Dictionary containing preprocessing configuration
"""
return {
'node_id': self.id,
'node_name': self.name(),
'resize_width': self.get_property('resize_width'),
'resize_height': self.get_property('resize_height'),
'maintain_aspect_ratio': self.get_property('maintain_aspect_ratio'),
'normalize': self.get_property('normalize'),
'normalization_type': self.get_property('normalization_type'),
'custom_mean': self._parse_float_list(self.get_property('custom_mean')),
'custom_std': self._parse_float_list(self.get_property('custom_std')),
'crop_enabled': self.get_property('crop_enabled'),
'crop_type': self.get_property('crop_type'),
'crop_width': self.get_property('crop_width'),
'crop_height': self.get_property('crop_height'),
'color_space': self.get_property('color_space'),
'operations': self._parse_operations_list(self.get_property('operations')),
'enable_augmentation': self.get_property('enable_augmentation'),
'interpolation_method': self.get_property('interpolation_method')
}
def _parse_float_list(self, value_str: str) -> list[float]:
"""Parse comma-separated float values."""
try:
return [float(x.strip()) for x in value_str.split(',') if x.strip()]
except (ValueError, AttributeError):
return []
def _parse_operations_list(self, operations_str: str) -> list[str]:
"""Parse comma-separated operations list."""
if not operations_str:
return []
return [op.strip() for op in operations_str.split(',') if op.strip()]
def get_estimated_processing_time(self, input_size: tuple = None) -> float:
"""
Estimate processing time for given input size.
Args:
input_size: Tuple of (width, height) for input image
Returns:
Estimated processing time in milliseconds
"""
if input_size is None:
input_size = (1920, 1080) # Default HD resolution
width, height = input_size
pixel_count = width * height
# Base processing time (ms per megapixel)
base_time = 5.0
# Operation-specific time factors
operations = self._parse_operations_list(self.get_property('operations'))
operation_factors = {
'resize': 1.0,
'normalize': 0.5,
'crop': 0.2,
'flip': 0.1,
'rotate': 1.5,
'blur': 2.0,
'sharpen': 2.0
}
total_factor = sum(operation_factors.get(op, 1.0) for op in operations)
return (pixel_count / 1000000) * base_time * total_factor