| | """
|
| | Input Module - Image Upload and Validation
|
| | =========================================
|
| |
|
| | Handles image file upload, format validation, and preprocessing
|
| | for the image deblurring system.
|
| | """
|
| |
|
| | import cv2
|
| | import numpy as np
|
| | from PIL import Image
|
| | import io
|
| | import streamlit as st
|
| | from typing import Optional, Tuple, Union
|
| | import logging
|
| |
|
| |
|
| | logging.basicConfig(level=logging.INFO)
|
| | logger = logging.getLogger(__name__)
|
| |
|
| | class ImageValidator:
|
| | """Validates and processes uploaded images"""
|
| |
|
| | SUPPORTED_FORMATS = {'.jpg', '.jpeg', '.png', '.bmp', '.tiff', '.tif'}
|
| | MAX_SIZE_MB = 50
|
| | MIN_RESOLUTION = (100, 100)
|
| | MAX_RESOLUTION = (8192, 8192)
|
| |
|
| | @classmethod
|
| | def validate_format(cls, file) -> bool:
|
| | """Validate if file format is supported"""
|
| | try:
|
| | if hasattr(file, 'name'):
|
| | filename = file.name.lower()
|
| | return any(filename.endswith(fmt) for fmt in cls.SUPPORTED_FORMATS)
|
| | return False
|
| | except Exception as e:
|
| | logger.error(f"Format validation error: {e}")
|
| | return False
|
| |
|
| | @classmethod
|
| | def validate_size(cls, file) -> bool:
|
| | """Validate file size"""
|
| | try:
|
| | if hasattr(file, 'size'):
|
| | size_mb = file.size / (1024 * 1024)
|
| | return size_mb <= cls.MAX_SIZE_MB
|
| | return True
|
| | except Exception as e:
|
| | logger.error(f"Size validation error: {e}")
|
| | return False
|
| |
|
| | @classmethod
|
| | def validate_resolution(cls, image: np.ndarray) -> bool:
|
| | """Validate image resolution"""
|
| | try:
|
| | height, width = image.shape[:2]
|
| |
|
| |
|
| | if width < cls.MIN_RESOLUTION[0] or height < cls.MIN_RESOLUTION[1]:
|
| | return False
|
| |
|
| |
|
| | if width > cls.MAX_RESOLUTION[0] or height > cls.MAX_RESOLUTION[1]:
|
| | return False
|
| |
|
| | return True
|
| | except Exception as e:
|
| | logger.error(f"Resolution validation error: {e}")
|
| | return False
|
| |
|
| | def load_image_from_upload(uploaded_file) -> Optional[np.ndarray]:
|
| | """
|
| | Load and validate image from Streamlit file upload
|
| |
|
| | Args:
|
| | uploaded_file: Streamlit UploadedFile object
|
| |
|
| | Returns:
|
| | np.ndarray: Image as OpenCV format (BGR) or None if invalid
|
| | """
|
| | try:
|
| |
|
| | if not ImageValidator.validate_format(uploaded_file):
|
| | st.error("❌ Unsupported file format. Please use: JPG, PNG, BMP, or TIFF")
|
| | return None
|
| |
|
| |
|
| | if not ImageValidator.validate_size(uploaded_file):
|
| | st.error(f"❌ File too large. Maximum size: {ImageValidator.MAX_SIZE_MB}MB")
|
| | return None
|
| |
|
| |
|
| | file_bytes = uploaded_file.getvalue()
|
| | image = Image.open(io.BytesIO(file_bytes))
|
| |
|
| |
|
| | img_array = np.array(image)
|
| |
|
| |
|
| | if len(img_array.shape) == 3:
|
| | if img_array.shape[2] == 4:
|
| | img_array = cv2.cvtColor(img_array, cv2.COLOR_RGBA2BGR)
|
| | elif img_array.shape[2] == 3:
|
| | img_array = cv2.cvtColor(img_array, cv2.COLOR_RGB2BGR)
|
| | elif len(img_array.shape) == 2:
|
| | img_array = cv2.cvtColor(img_array, cv2.COLOR_GRAY2BGR)
|
| |
|
| |
|
| | if not ImageValidator.validate_resolution(img_array):
|
| | min_res = ImageValidator.MIN_RESOLUTION
|
| | max_res = ImageValidator.MAX_RESOLUTION
|
| | st.error(f"❌ Invalid resolution. Must be {min_res[0]}x{min_res[1]} to {max_res[0]}x{max_res[1]}")
|
| | return None
|
| |
|
| | logger.info(f"Successfully loaded image: {img_array.shape}")
|
| | return img_array
|
| |
|
| | except Exception as e:
|
| | logger.error(f"Error loading image: {e}")
|
| | st.error(f"❌ Error loading image: {str(e)}")
|
| | return None
|
| |
|
| | def load_image_from_path(image_path: str) -> Optional[np.ndarray]:
|
| | """
|
| | Load image from file path
|
| |
|
| | Args:
|
| | image_path: Path to image file
|
| |
|
| | Returns:
|
| | np.ndarray: Image as OpenCV format (BGR) or None if error
|
| | """
|
| | try:
|
| | image = cv2.imread(image_path)
|
| | if image is None:
|
| | logger.error(f"Could not load image from {image_path}")
|
| | return None
|
| |
|
| |
|
| | if not ImageValidator.validate_resolution(image):
|
| | logger.error(f"Invalid resolution for image: {image.shape}")
|
| | return None
|
| |
|
| | logger.info(f"Loaded image from path: {image.shape}")
|
| | return image
|
| |
|
| | except Exception as e:
|
| | logger.error(f"Error loading image from path: {e}")
|
| | return None
|
| |
|
| | def preprocess_image(image: np.ndarray, max_size: Tuple[int, int] = (1024, 1024)) -> np.ndarray:
|
| | """
|
| | Preprocess image for processing (resize if needed, normalize)
|
| |
|
| | Args:
|
| | image: Input image
|
| | max_size: Maximum dimensions for processing
|
| |
|
| | Returns:
|
| | np.ndarray: Preprocessed image
|
| | """
|
| | try:
|
| | height, width = image.shape[:2]
|
| |
|
| |
|
| | if width > max_size[0] or height > max_size[1]:
|
| |
|
| | scale = min(max_size[0] / width, max_size[1] / height)
|
| | new_width = int(width * scale)
|
| | new_height = int(height * scale)
|
| |
|
| |
|
| | image = cv2.resize(image, (new_width, new_height), interpolation=cv2.INTER_LANCZOS4)
|
| | logger.info(f"Resized image to: {image.shape}")
|
| |
|
| |
|
| | image = image.astype(np.uint8)
|
| |
|
| | return image
|
| |
|
| | except Exception as e:
|
| | logger.error(f"Error preprocessing image: {e}")
|
| | return image
|
| |
|
| | def validate_and_load_image(uploaded_file, preprocess: bool = True) -> Optional[np.ndarray]:
|
| | """
|
| | Complete image validation and loading pipeline
|
| |
|
| | Args:
|
| | uploaded_file: Streamlit UploadedFile object
|
| | preprocess: Whether to preprocess the image
|
| |
|
| | Returns:
|
| | np.ndarray: Validated and preprocessed image or None
|
| | """
|
| |
|
| | image = load_image_from_upload(uploaded_file)
|
| | if image is None:
|
| | return None
|
| |
|
| |
|
| | if preprocess:
|
| | image = preprocess_image(image)
|
| |
|
| | return image
|
| |
|
| | def get_image_info(image: np.ndarray) -> dict:
|
| | """
|
| | Get comprehensive image information
|
| |
|
| | Args:
|
| | image: Input image
|
| |
|
| | Returns:
|
| | dict: Image information
|
| | """
|
| | try:
|
| | height, width = image.shape[:2]
|
| | channels = image.shape[2] if len(image.shape) == 3 else 1
|
| |
|
| | return {
|
| | 'width': width,
|
| | 'height': height,
|
| | 'channels': channels,
|
| | 'total_pixels': width * height,
|
| | 'data_type': str(image.dtype),
|
| | 'memory_size_mb': image.nbytes / (1024 * 1024),
|
| | 'aspect_ratio': width / height
|
| | }
|
| | except Exception as e:
|
| | logger.error(f"Error getting image info: {e}")
|
| | return {}
|
| |
|
| |
|
| | if __name__ == "__main__":
|
| | print("Input Module - Image Upload and Validation")
|
| | print("==========================================")
|
| |
|
| |
|
| | test_image = np.random.randint(0, 255, (480, 640, 3), dtype=np.uint8)
|
| |
|
| |
|
| | validator = ImageValidator()
|
| | print(f"Resolution validation: {validator.validate_resolution(test_image)}")
|
| |
|
| |
|
| | processed = preprocess_image(test_image)
|
| | print(f"Original shape: {test_image.shape}")
|
| | print(f"Processed shape: {processed.shape}")
|
| |
|
| |
|
| | info = get_image_info(test_image)
|
| | print(f"Image info: {info}") |