Spaces:
Sleeping
Sleeping
| """ | |
| Data augmentation utilities for the Emotion Recognition System. | |
| """ | |
| import numpy as np | |
| from typing import Tuple, Optional | |
| from tensorflow.keras.preprocessing.image import ImageDataGenerator | |
| import sys | |
| from pathlib import Path | |
| sys.path.append(str(Path(__file__).parent.parent.parent)) | |
| from src.config import AUGMENTATION_CONFIG | |
| def get_augmentation_generator( | |
| rotation_range: int = AUGMENTATION_CONFIG["rotation_range"], | |
| width_shift_range: float = AUGMENTATION_CONFIG["width_shift_range"], | |
| height_shift_range: float = AUGMENTATION_CONFIG["height_shift_range"], | |
| horizontal_flip: bool = AUGMENTATION_CONFIG["horizontal_flip"], | |
| zoom_range: float = AUGMENTATION_CONFIG["zoom_range"], | |
| brightness_range: Tuple[float, float] = AUGMENTATION_CONFIG["brightness_range"], | |
| fill_mode: str = AUGMENTATION_CONFIG["fill_mode"], | |
| rescale: float = 1./255 | |
| ) -> ImageDataGenerator: | |
| """ | |
| Create an ImageDataGenerator with augmentation settings. | |
| Args: | |
| rotation_range: Degree range for random rotations | |
| width_shift_range: Fraction for horizontal shifts | |
| height_shift_range: Fraction for vertical shifts | |
| horizontal_flip: Whether to randomly flip horizontally | |
| zoom_range: Range for random zoom | |
| brightness_range: Range for brightness adjustment | |
| fill_mode: Points outside boundaries fill method | |
| rescale: Rescaling factor | |
| Returns: | |
| Configured ImageDataGenerator | |
| """ | |
| return ImageDataGenerator( | |
| rescale=rescale, | |
| rotation_range=rotation_range, | |
| width_shift_range=width_shift_range, | |
| height_shift_range=height_shift_range, | |
| horizontal_flip=horizontal_flip, | |
| zoom_range=zoom_range, | |
| brightness_range=brightness_range, | |
| fill_mode=fill_mode | |
| ) | |
| def augment_image( | |
| image: np.ndarray, | |
| num_augmentations: int = 5, | |
| generator: Optional[ImageDataGenerator] = None | |
| ) -> np.ndarray: | |
| """ | |
| Generate augmented versions of a single image. | |
| Args: | |
| image: Input image array of shape (height, width, channels) | |
| num_augmentations: Number of augmented images to generate | |
| generator: Optional ImageDataGenerator, creates default if None | |
| Returns: | |
| Array of augmented images of shape (num_augmentations, height, width, channels) | |
| """ | |
| if generator is None: | |
| generator = get_augmentation_generator(rescale=1.0) # No rescale for single images | |
| # Reshape for generator (needs batch dimension) | |
| image_batch = np.expand_dims(image, axis=0) | |
| # Generate augmented images | |
| augmented_images = [] | |
| aug_iter = generator.flow(image_batch, batch_size=1) | |
| for _ in range(num_augmentations): | |
| augmented = next(aug_iter)[0] | |
| augmented_images.append(augmented) | |
| return np.array(augmented_images) | |
| def create_balanced_augmentation( | |
| images: np.ndarray, | |
| labels: np.ndarray, | |
| target_samples_per_class: int | |
| ) -> Tuple[np.ndarray, np.ndarray]: | |
| """ | |
| Create a balanced dataset through augmentation of minority classes. | |
| Args: | |
| images: Array of images | |
| labels: Array of one-hot encoded labels | |
| target_samples_per_class: Target number of samples per class | |
| Returns: | |
| Tuple of (augmented_images, augmented_labels) | |
| """ | |
| generator = get_augmentation_generator(rescale=1.0) | |
| # Convert one-hot to class indices | |
| class_indices = np.argmax(labels, axis=1) | |
| unique_classes = np.unique(class_indices) | |
| augmented_images = [] | |
| augmented_labels = [] | |
| for class_idx in unique_classes: | |
| # Get images of this class | |
| class_mask = class_indices == class_idx | |
| class_images = images[class_mask] | |
| class_labels = labels[class_mask] | |
| current_count = len(class_images) | |
| # Add original images | |
| augmented_images.extend(class_images) | |
| augmented_labels.extend(class_labels) | |
| # Generate more if needed | |
| if current_count < target_samples_per_class: | |
| needed = target_samples_per_class - current_count | |
| for i in range(needed): | |
| # Select random image from class | |
| idx = np.random.randint(0, current_count) | |
| original = class_images[idx] | |
| # Generate one augmented version | |
| aug = augment_image(original, num_augmentations=1, generator=generator)[0] | |
| augmented_images.append(aug) | |
| augmented_labels.append(class_labels[idx]) | |
| return np.array(augmented_images), np.array(augmented_labels) | |
| def get_augmentation_preview( | |
| image: np.ndarray, | |
| num_samples: int = 9 | |
| ) -> np.ndarray: | |
| """ | |
| Generate a preview of augmentations for visualization. | |
| Args: | |
| image: Original image | |
| num_samples: Number of augmented samples to generate | |
| Returns: | |
| Array including original + augmented images | |
| """ | |
| augmented = augment_image(image, num_augmentations=num_samples - 1) | |
| # Add original as first image | |
| original = np.expand_dims(image, axis=0) | |
| return np.concatenate([original, augmented], axis=0) | |