File size: 5,118 Bytes
bcc2f7b |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 |
"""
Image processing utilities for the FastAPI application.
"""
import base64
import io
from typing import Tuple
import numpy as np
from PIL import Image
import cv2
from app.core.config import settings
from app.core.logging import get_logger
logger = get_logger(__name__)
def decode_base64_image(image_data: str) -> Tuple[np.ndarray, Tuple[int, int]]:
"""
Decode base64 image data to numpy array.
Args:
image_data: Base64 encoded image string
Returns:
Tuple of (image_array, (width, height))
"""
try:
# Remove data URL prefix if present
if image_data.startswith('data:image'):
image_data = image_data.split(',')[1]
# Decode base64
image_bytes = base64.b64decode(image_data)
# Open with PIL
pil_image = Image.open(io.BytesIO(image_bytes))
# Convert to RGB if necessary
if pil_image.mode != 'RGB':
pil_image = pil_image.convert('RGB')
# Get original dimensions
original_dims = pil_image.size # (width, height)
# Convert to numpy array
image_array = np.array(pil_image)
logger.debug(f"Decoded image with shape: {image_array.shape}")
return image_array, original_dims
except Exception as e:
logger.error(f"Failed to decode base64 image: {str(e)}")
raise ValueError(f"Invalid image data: {str(e)}")
def encode_image_to_base64(image: np.ndarray, format: str = "JPEG", quality: int = 95) -> str:
"""
Encode numpy array image to base64 string.
Args:
image: Image as numpy array
format: Image format (JPEG, PNG, etc.)
quality: JPEG quality (1-100)
Returns:
Base64 encoded image string
"""
try:
# Convert numpy array to PIL Image
if image.dtype != np.uint8:
image = (image * 255).astype(np.uint8)
pil_image = Image.fromarray(image)
# Save to bytes buffer
buffer = io.BytesIO()
save_kwargs = {"format": format}
if format.upper() == "JPEG":
save_kwargs["quality"] = quality
save_kwargs["optimize"] = True
pil_image.save(buffer, **save_kwargs)
# Encode to base64
image_bytes = buffer.getvalue()
base64_string = base64.b64encode(image_bytes).decode('utf-8')
return base64_string
except Exception as e:
logger.error(f"Failed to encode image to base64: {str(e)}")
raise ValueError(f"Image encoding failed: {str(e)}")
def validate_image_size(image: np.ndarray) -> bool:
"""
Validate image dimensions.
Args:
image: Image as numpy array
Returns:
True if image size is valid
"""
height, width = image.shape[:2]
# Check minimum and maximum dimensions
min_dim = min(width, height)
max_dim = max(width, height)
if min_dim < 32: # Too small
return False
if max_dim > 4096: # Too large
return False
return True
def resize_image_if_needed(image: np.ndarray, max_size: int = 1280) -> np.ndarray:
"""
Resize image if it's too large while maintaining aspect ratio.
Args:
image: Image as numpy array
max_size: Maximum dimension size
Returns:
Resized image
"""
height, width = image.shape[:2]
if max(height, width) <= max_size:
return image
# Calculate new dimensions
if width > height:
new_width = max_size
new_height = int(height * (max_size / width))
else:
new_height = max_size
new_width = int(width * (max_size / height))
# Resize using PIL for better quality
pil_image = Image.fromarray(image)
resized_pil = pil_image.resize((new_width, new_height), Image.Resampling.LANCZOS)
return np.array(resized_pil)
def validate_image_format(image_bytes: bytes) -> bool:
"""
Validate if the image format is supported.
Args:
image_bytes: Raw image bytes
Returns:
True if format is supported
"""
try:
with Image.open(io.BytesIO(image_bytes)) as img:
# Check if format is in allowed extensions
format_lower = img.format.lower() if img.format else ""
allowed_formats = {"jpeg", "jpg", "png", "bmp", "tiff", "webp"}
return format_lower in allowed_formats
except Exception:
return False
def get_image_info(image: np.ndarray) -> dict:
"""
Get information about an image.
Args:
image: Image as numpy array
Returns:
Dictionary with image information
"""
height, width = image.shape[:2]
channels = image.shape[2] if len(image.shape) > 2 else 1
return {
"width": width,
"height": height,
"channels": channels,
"dtype": str(image.dtype),
"size_mb": image.nbytes / (1024 * 1024)
}
|