Spaces:
Sleeping
Sleeping
File size: 12,879 Bytes
5b6e956 |
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 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 376 377 378 379 380 381 382 383 384 385 386 387 388 389 390 391 392 393 394 395 396 397 398 399 400 401 402 403 404 405 406 407 408 409 410 411 412 413 414 415 416 417 418 419 420 421 422 423 424 425 426 427 428 429 430 431 432 433 434 435 436 |
"""
Input Validation Utilities
===========================
Validation functions for user inputs in Nano Banana Streamlit.
Ensures data integrity and provides clear error messages.
"""
from typing import Optional, List, Tuple
from pathlib import Path
from PIL import Image
from config.settings import Settings
from utils.logging_utils import get_logger
logger = get_logger(__name__)
# =============================================================================
# PARAMETER VALIDATION
# =============================================================================
def validate_temperature(temperature: float) -> Tuple[bool, Optional[str]]:
"""
Validate temperature parameter.
Args:
temperature: Temperature value to validate
Returns:
Tuple of (is_valid, error_message)
error_message is None if valid
"""
if not isinstance(temperature, (int, float)):
return False, "Temperature must be a number"
if temperature < Settings.MIN_TEMPERATURE or temperature > Settings.MAX_TEMPERATURE:
return False, f"Temperature must be between {Settings.MIN_TEMPERATURE} and {Settings.MAX_TEMPERATURE}"
return True, None
def validate_aspect_ratio(aspect_ratio: str) -> Tuple[bool, Optional[str]]:
"""
Validate aspect ratio parameter.
Args:
aspect_ratio: Aspect ratio string (display name or value)
Returns:
Tuple of (is_valid, error_message)
"""
if not isinstance(aspect_ratio, str):
return False, "Aspect ratio must be a string"
# Check if it's a display name
if aspect_ratio in Settings.ASPECT_RATIOS:
return True, None
# Check if it's a ratio value
if aspect_ratio in Settings.ASPECT_RATIOS.values():
return True, None
return False, f"Invalid aspect ratio: {aspect_ratio}"
def validate_backend(backend: str) -> Tuple[bool, Optional[str]]:
"""
Validate backend parameter.
Args:
backend: Backend name
Returns:
Tuple of (is_valid, error_message)
"""
if not isinstance(backend, str):
return False, "Backend must be a string"
if backend not in Settings.AVAILABLE_BACKENDS:
return False, f"Invalid backend: {backend}. Must be one of {Settings.AVAILABLE_BACKENDS}"
return True, None
def validate_prompt(prompt: str, min_length: int = 1, max_length: int = 5000) -> Tuple[bool, Optional[str]]:
"""
Validate text prompt.
Args:
prompt: Text prompt
min_length: Minimum required length (default: 1)
max_length: Maximum allowed length (default: 5000)
Returns:
Tuple of (is_valid, error_message)
"""
if not isinstance(prompt, str):
return False, "Prompt must be a string"
prompt = prompt.strip()
if len(prompt) < min_length:
return False, f"Prompt must be at least {min_length} character(s)"
if len(prompt) > max_length:
return False, f"Prompt must be at most {max_length} characters"
return True, None
def validate_character_name(name: str) -> Tuple[bool, Optional[str]]:
"""
Validate character name.
Args:
name: Character name
Returns:
Tuple of (is_valid, error_message)
"""
if not isinstance(name, str):
return False, "Character name must be a string"
name = name.strip()
if len(name) < 1:
return False, "Character name cannot be empty"
if len(name) > 100:
return False, "Character name must be at most 100 characters"
return True, None
# =============================================================================
# IMAGE VALIDATION
# =============================================================================
def validate_image(image: Image.Image) -> Tuple[bool, Optional[str]]:
"""
Validate PIL Image object.
Checks:
- Is valid Image instance
- Has reasonable dimensions
- Is in supported format
Args:
image: PIL Image to validate
Returns:
Tuple of (is_valid, error_message)
"""
if not isinstance(image, Image.Image):
return False, "Invalid image object"
# Check dimensions
width, height = image.size
if width < 1 or height < 1:
return False, "Image has invalid dimensions"
if width > 8192 or height > 8192:
return False, "Image is too large (max 8192x8192 pixels)"
# Check mode (format)
if image.mode not in ['RGB', 'RGBA', 'L', 'P']:
return False, f"Unsupported image mode: {image.mode}"
return True, None
def validate_image_file(file_path: Path) -> Tuple[bool, Optional[str]]:
"""
Validate image file path and format.
Args:
file_path: Path to image file
Returns:
Tuple of (is_valid, error_message)
"""
if not isinstance(file_path, Path):
try:
file_path = Path(file_path)
except Exception:
return False, "Invalid file path"
# Check exists
if not file_path.exists():
return False, f"File not found: {file_path}"
# Check is file (not directory)
if not file_path.is_file():
return False, f"Not a file: {file_path}"
# Check extension
valid_extensions = {'.png', '.jpg', '.jpeg', '.webp', '.bmp'}
if file_path.suffix.lower() not in valid_extensions:
return False, f"Unsupported file format: {file_path.suffix}"
# Try to open as image
try:
with Image.open(file_path) as img:
return validate_image(img)
except Exception as e:
return False, f"Cannot open as image: {e}"
def validate_image_upload_size(file_size_bytes: int) -> Tuple[bool, Optional[str]]:
"""
Validate uploaded file size.
Args:
file_size_bytes: File size in bytes
Returns:
Tuple of (is_valid, error_message)
"""
max_bytes = Settings.MAX_IMAGE_UPLOAD_SIZE * 1024 * 1024 # Convert MB to bytes
if file_size_bytes > max_bytes:
max_mb = Settings.MAX_IMAGE_UPLOAD_SIZE
actual_mb = file_size_bytes / (1024 * 1024)
return False, f"File too large: {actual_mb:.1f}MB (max: {max_mb}MB)"
return True, None
# =============================================================================
# GENERATION REQUEST VALIDATION
# =============================================================================
def validate_generation_request(
prompt: str,
backend: str,
aspect_ratio: str,
temperature: float,
input_images: Optional[List[Image.Image]] = None
) -> Tuple[bool, Optional[str]]:
"""
Validate a complete generation request.
Validates all parameters required for image generation.
Args:
prompt: Text prompt
backend: Backend name
aspect_ratio: Aspect ratio
temperature: Temperature value
input_images: Optional list of input images
Returns:
Tuple of (is_valid, error_message)
error_message is None if valid
"""
# Validate prompt
valid, error = validate_prompt(prompt)
if not valid:
return False, f"Invalid prompt: {error}"
# Validate backend
valid, error = validate_backend(backend)
if not valid:
return False, f"Invalid backend: {error}"
# Validate aspect ratio
valid, error = validate_aspect_ratio(aspect_ratio)
if not valid:
return False, f"Invalid aspect ratio: {error}"
# Validate temperature
valid, error = validate_temperature(temperature)
if not valid:
return False, f"Invalid temperature: {error}"
# Validate input images if provided
if input_images:
if not isinstance(input_images, list):
return False, "Input images must be a list"
if len(input_images) > 3:
return False, "Maximum 3 input images allowed"
for idx, img in enumerate(input_images, 1):
valid, error = validate_image(img)
if not valid:
return False, f"Invalid input image {idx}: {error}"
return True, None
def validate_character_forge_request(
character_name: str,
initial_image: Optional[Image.Image],
face_image: Optional[Image.Image],
body_image: Optional[Image.Image],
image_type: str,
backend: str
) -> Tuple[bool, Optional[str]]:
"""
Validate a Character Forge generation request.
Args:
character_name: Name for character
initial_image: Initial image (for Face Only / Full Body modes)
face_image: Face image (for Face+Body Separate mode)
body_image: Body image (for Face+Body Separate mode)
image_type: Type of input ("Face Only", "Full Body", "Face + Body (Separate)")
backend: Backend to use
Returns:
Tuple of (is_valid, error_message)
"""
# Validate character name
valid, error = validate_character_name(character_name)
if not valid:
return False, error
# Validate backend
valid, error = validate_backend(backend)
if not valid:
return False, error
# Validate images based on mode
if image_type == "Face + Body (Separate)":
if face_image is None:
return False, "Face image is required for Face+Body Separate mode"
if body_image is None:
return False, "Body image is required for Face+Body Separate mode"
valid, error = validate_image(face_image)
if not valid:
return False, f"Invalid face image: {error}"
valid, error = validate_image(body_image)
if not valid:
return False, f"Invalid body image: {error}"
else: # Face Only or Full Body
if initial_image is None:
return False, f"Initial image is required for {image_type} mode"
valid, error = validate_image(initial_image)
if not valid:
return False, f"Invalid initial image: {error}"
return True, None
# =============================================================================
# BACKEND AVAILABILITY VALIDATION
# =============================================================================
def validate_backend_available(backend: str, api_key: Optional[str] = None) -> Tuple[bool, Optional[str]]:
"""
Check if a backend is available and properly configured.
Args:
backend: Backend name
api_key: API key (for Gemini backend)
Returns:
Tuple of (is_available, error_message)
"""
# Validate backend name first
valid, error = validate_backend(backend)
if not valid:
return False, error
# Check Gemini API
if backend == Settings.BACKEND_GEMINI:
if not api_key:
return False, "Gemini API key not configured. Please set GEMINI_API_KEY or enter it in settings."
return True, None
# Check OmniGen2
if backend == Settings.BACKEND_OMNIGEN2:
# Try to check if server is running
try:
import requests
response = requests.get(f"{Settings.OMNIGEN2_BASE_URL}/health", timeout=2)
if response.ok:
data = response.json()
if data.get('status') == 'healthy':
return True, None
else:
return False, "OmniGen2 server is not healthy. Check server.log for details."
else:
return False, f"OmniGen2 server returned error: {response.status_code}"
except Exception as e:
return False, f"OmniGen2 server not responding. Start it with: omnigen2_plugin/server.bat start"
return False, f"Unknown backend: {backend}"
# =============================================================================
# HELPER FUNCTIONS
# =============================================================================
def raise_if_invalid(is_valid: bool, error_message: Optional[str], exception_type=ValueError):
"""
Raise an exception if validation failed.
Helper function for turning validation results into exceptions.
Args:
is_valid: Validation result
error_message: Error message (if invalid)
exception_type: Exception class to raise (default: ValueError)
Raises:
exception_type: If is_valid is False
"""
if not is_valid:
logger.error(f"Validation failed: {error_message}")
raise exception_type(error_message)
def log_validation_error(validation_result: Tuple[bool, Optional[str]], context: str = ""):
"""
Log a validation error if validation failed.
Args:
validation_result: Result tuple from validation function
context: Optional context string for the log message
"""
is_valid, error_message = validation_result
if not is_valid:
if context:
logger.warning(f"Validation failed [{context}]: {error_message}")
else:
logger.warning(f"Validation failed: {error_message}")
|