From 43d06770639f801d0199d67dcdbedd69e6774eaf Mon Sep 17 00:00:00 2001 From: Sinan Date: Sat, 24 May 2025 22:53:32 +0200 Subject: [PATCH 01/32] started repolishing namings and structure of data pipeline for final structure --- recaptcha_classifier/data/__init__.py | 15 ++++----- recaptcha_classifier/data/augment.py | 14 ++++----- recaptcha_classifier/data/collate_batch.py | 7 +---- recaptcha_classifier/data/dataset.py | 4 +-- recaptcha_classifier/data/downloader.py | 19 +++++++----- recaptcha_classifier/data/loader_factory.py | 6 ++-- recaptcha_classifier/data/pair_loader.py | 6 ++-- recaptcha_classifier/data/splitter.py | 14 ++++----- recaptcha_classifier/data/types.py | 34 +++++++-------------- recaptcha_classifier/data/visualizer.py | 10 +++--- recaptcha_classifier/detection_labels.py | 7 ----- 11 files changed, 56 insertions(+), 80 deletions(-) diff --git a/recaptcha_classifier/data/__init__.py b/recaptcha_classifier/data/__init__.py index c341ff9f94..c7d918d6f9 100644 --- a/recaptcha_classifier/data/__init__.py +++ b/recaptcha_classifier/data/__init__.py @@ -16,11 +16,9 @@ from .collate_batch import collate_batch from .types import ( - FilePair, - FilePairList, - DatasetSplitDict, - BBoxList, - DataPair, + ImagePathList, + DatasetSplitMap, + LoadedImg, DataItem, DataBatch ) @@ -43,10 +41,9 @@ "collate_batch", # Types "FilePair", - "FilePairList", - "DatasetSplitDict", - "BBoxList", - "DataPair", + "ImagePathList", + "DatasetSplitMap", + "LoadedImg", "DataItem", "DataBatch", ] diff --git a/recaptcha_classifier/data/augment.py b/recaptcha_classifier/data/augment.py index ffbf127646..8b622db68f 100644 --- a/recaptcha_classifier/data/augment.py +++ b/recaptcha_classifier/data/augment.py @@ -3,7 +3,7 @@ from PIL import Image from typing import List from .scaler import YOLOScaler -from .types import DataPair, BBoxList +from .types import LoadedImg, BBoxList class Augmentation(ABC): @@ -11,7 +11,7 @@ class Augmentation(ABC): @abstractmethod def augment(self, image: Image.Image, - annotations: List) -> DataPair: + annotations: List) -> LoadedImg: """ Apply the transformation of the image and updates the bounding boxes if necessary. @@ -21,7 +21,7 @@ def augment(self, annotations (List): List of annotations associated with the image. Returns: - DataPair: The augmented image and the updated + LoadedImg: The augmented image and the updated annotations. """ pass @@ -34,7 +34,7 @@ def __init__(self, transforms=[]) -> None: def apply_transforms(self, image: Image.Image, - annotations: BBoxList) -> DataPair: + annotations: BBoxList) -> LoadedImg: """ Apply all transformations in the pipeline to the image and annotations. @@ -45,7 +45,7 @@ def apply_transforms(self, associated with the image. Returns: - DataPair: The augmented image and the updated + LoadedImg: The augmented image and the updated annotations. """ for transform in self._transforms: @@ -62,7 +62,7 @@ def __init__(self, p: float = 0.5) -> None: def augment(self, image: Image.Image, - annotations: BBoxList) -> DataPair: + annotations: BBoxList) -> LoadedImg: flipped = image.transpose(Image.FLIP_LEFT_RIGHT) new_annotations = YOLOScaler.scale_for_flip(annotations) @@ -80,7 +80,7 @@ def __init__(self, degrees: float = 30.0, p: float = 0.5) -> None: def augment(self, image: Image.Image, - annotations: BBoxList) -> DataPair: + annotations: BBoxList) -> LoadedImg: angle = random.uniform(-self._degrees, self._degrees) rotated = image.rotate(angle) diff --git a/recaptcha_classifier/data/collate_batch.py b/recaptcha_classifier/data/collate_batch.py index 55cc67c688..a1f12afab6 100644 --- a/recaptcha_classifier/data/collate_batch.py +++ b/recaptcha_classifier/data/collate_batch.py @@ -10,25 +10,20 @@ def collate_batch(batch: List[DataItem]) -> DataBatch: Args: batch (List[DataItem]): A batch of training items; each item is - a tuple of format (image tensor, bounding boxes, class index). + a tuple of format (image tensor, class index). Returns: Batch: A single tuple containing: - images_tensor (Tensor): Batched images of shape (batch_size, 3, H, W) - - bboxes (List[BBoxList]): A list of - bounding boxes for each image - labels_tensor (Tensor): A tensor of shape containing the class indices. """ images = [item[0] for item in batch] - # bboxes = [item[1] for item in batch] - # labels = [item[2] for item in batch] labels = [item[1] for item in batch] # Stack them as (3, H, W) tensors images_tensor = torch.stack(images) labels_tensor = torch.stack(labels) - # return images_tensor, bboxes, labels_tensor return images_tensor, labels_tensor diff --git a/recaptcha_classifier/data/dataset.py b/recaptcha_classifier/data/dataset.py index 803b4b6027..f14ad87f0b 100644 --- a/recaptcha_classifier/data/dataset.py +++ b/recaptcha_classifier/data/dataset.py @@ -2,7 +2,7 @@ from torch.utils.data import Dataset from .preprocessor import ImagePrep from .augment import AugmentationPipeline -from .types import FilePairList, DataItem +from .types import ImagePathList, DataItem class ImageDataset(Dataset): @@ -17,7 +17,7 @@ class ImageDataset(Dataset): https://docs.pytorch.org/tutorials/beginner/basics/data_tutorial.html """ def __init__(self, - pairs: FilePairList, + pairs: ImagePathList, preprocessor: ImagePrep, augmentator: Optional[AugmentationPipeline] = None, class_map: dict = {} diff --git a/recaptcha_classifier/data/downloader.py b/recaptcha_classifier/data/downloader.py index 0ba4c6a336..e03c87561c 100644 --- a/recaptcha_classifier/data/downloader.py +++ b/recaptcha_classifier/data/downloader.py @@ -1,11 +1,9 @@ import requests import zipfile -import logging +import shutil from pathlib import Path from alive_progress import alive_bar -logger = logging.getLogger(__name__) - class DatasetDownloader: """ @@ -37,11 +35,11 @@ def download(self) -> None: If not, downloads and then unzips it. """ if self._is_downloaded(): - logger.info("Dataset already exists, skipping download.") + print("Dataset already exists, skipping download.") return self._prepare_dest() - logger.info(f"Downloading {self._url} to {self._dest}...") + print(f"Downloading {self._url} to {self._dest}...") self._download_zip() self._unzip_and_cleanup() @@ -82,7 +80,7 @@ def _download_zip(self) -> None: for chunk in resp.iter_content(chunk_size=8192): f.write(chunk) bar(len(chunk)) - logger.info("Download completed successfully.") + print("Download completed successfully.") def _unzip_and_cleanup(self) -> None: """ @@ -92,7 +90,7 @@ def _unzip_and_cleanup(self) -> None: Note: this assumes that the downloaded dataset has exactly the structure of our selected Kaggle dataset for simplicity. """ - logger.info("Extracting...") + print("Extracting...") with zipfile.ZipFile(self._zip_path) as z: z.extractall(self._dest) @@ -101,5 +99,10 @@ def _unzip_and_cleanup(self) -> None: (root / sub).rename(self._dest / sub) root.rmdir() + + label_dir = self._dest / "labels" + if label_dir.exists() and label_dir.is_dir(): + shutil.rmtree(label_dir) + self._zip_path.unlink() - print("Extraction and cleanup completed successfully.") + print("Extraction and cleanup completed successfully.") \ No newline at end of file diff --git a/recaptcha_classifier/data/loader_factory.py b/recaptcha_classifier/data/loader_factory.py index 0b804644bb..caa452fbee 100644 --- a/recaptcha_classifier/data/loader_factory.py +++ b/recaptcha_classifier/data/loader_factory.py @@ -4,7 +4,7 @@ from .dataset import ImageDataset from .preprocessor import ImagePrep from .augment import AugmentationPipeline -from .types import DatasetSplitDict, FilePairList +from .types import DatasetSplitMap, ImagePathList from .collate_batch import collate_batch @@ -40,12 +40,12 @@ def __init__(self, # OPTIONAL: self._loaders to cache response def create_loaders(self, - splits: DatasetSplitDict) -> Dict[str, DataLoader]: + splits: DatasetSplitMap) -> Dict[str, DataLoader]: loaders: Dict[str, DataLoader] = {} for split_name, cls_dict in splits.items(): # flatten nested dict of pairs - flat_pairs: FilePairList = [pair + flat_pairs: ImagePathList = [pair # traversing over classes for pairs in cls_dict.values() # traversing over pairs diff --git a/recaptcha_classifier/data/pair_loader.py b/recaptcha_classifier/data/pair_loader.py index c8885cbf2f..828727c5d0 100644 --- a/recaptcha_classifier/data/pair_loader.py +++ b/recaptcha_classifier/data/pair_loader.py @@ -1,7 +1,7 @@ import logging from pathlib import Path from typing import List, Dict -from .types import ClassFileDict +from .types import ClassToImgPaths logger = logging.getLogger(__name__) @@ -33,9 +33,9 @@ def __init__(self, self._classes = classes self._images_dir = Path(images_dir) # self._labels_dir = Path(labels_dir) - self._pairs: ClassFileDict = dict() + self._pairs: ClassToImgPaths = dict() - def find_pairs(self) -> ClassFileDict: + def find_pairs(self) -> ClassToImgPaths: """ Returns all pairs loaded for the given classes. It caches the response after first run. diff --git a/recaptcha_classifier/data/splitter.py b/recaptcha_classifier/data/splitter.py index 4e36bbdb76..31416a9274 100644 --- a/recaptcha_classifier/data/splitter.py +++ b/recaptcha_classifier/data/splitter.py @@ -1,6 +1,6 @@ import random from typing import Tuple -from .types import ClassFileDict, DatasetSplitDict, FilePairList +from .types import ClassToImgPaths, DatasetSplitMap, ImagePathList class DataSplitter: @@ -29,8 +29,8 @@ def __init__(self, self._validate_ratios() def split(self, - pairs_by_class: ClassFileDict - ) -> DatasetSplitDict: + pairs_by_class: ClassToImgPaths + ) -> DatasetSplitMap: """ Splits each class into train, validation, and test sets. It shuffles the data if specified and returns a dictionary @@ -40,7 +40,7 @@ def split(self, items (List): List of items to be split. Returns: - DatasetSplitDict: Nested dictionary containing + DatasetSplitMap: Nested dictionary containing splits for each class. """ splits = {'train': {}, 'val': {}, 'test': {}} @@ -69,16 +69,16 @@ def _validate_ratios(self) -> None: if any(ratio < 0 for ratio in self._ratios): raise ValueError("Ratios must be positive.") - def _shuffle_items(self, items: FilePairList) -> FilePairList: + def _shuffle_items(self, items: ImagePathList) -> ImagePathList: """ Returns a shuffled copied version of the items list, using seed if provided. Args: - items (FilePairList): List of items to be shuffled. + items (ImagePathList): List of items to be shuffled. Returns: - FilePairList: Shuffled list of items. + ImagePathList: Shuffled list of items. """ new_items = items.copy() rand = random.Random(self._seed) diff --git a/recaptcha_classifier/data/types.py b/recaptcha_classifier/data/types.py index f5ad6b42d7..f5537406a1 100644 --- a/recaptcha_classifier/data/types.py +++ b/recaptcha_classifier/data/types.py @@ -3,34 +3,22 @@ from pathlib import Path from torch import Tensor -# OBJECT DETECTION TASK CLASSES -# A (image, label) pair containing their system paths/ locations -FilePair = Path # Tuple[Path, Path] - -# A list of (image, label) pairs, for more items in the dataset -FilePairList = List[FilePair] +# A list of image paths, for more items in the dataset +ImagePathList = List[Path] # A dictionary where the keys are class names and -# the values are lists of (image, label) pairs, elements of that class -ClassFileDict = Dict[str, FilePairList] +# the values are lists of all image paths for that class in the dataset +ClassToImgPaths = Dict[str, ImagePathList] # A nested dictionary where main keys are train/val/test -# and the subkeys are class names -DatasetSplitDict = Dict[str, ClassFileDict] - -# YOLO bounding box format (x_center, y_center, width, height) -BBox = Tuple[float, float, float, float] - -# List of bounding boxes for one image from the dataset -BBoxList = List[BBox] +# and the subkeys are class names, containing all image paths of that class +DatasetSplitMap = Dict[str, ClassToImgPaths] -# A dataset item, of (image, annotations), where both features -# now loaded in memory, instead of Paths like in FilePair -DataPair = Tuple[Image.Image, BBoxList] +# A dataset item, where the image is now loaded from disk +LoadedImg = Image.Image -# A final dataset item, that now contains the image tensor, -# the bounding boxes and the class id; ready for model training -DataItem = Tuple[Tensor, BBoxList, int] +# A final dataset item, that now contains the image tensor, and the class id; ready for model training +DataItem = Tuple[Tensor, int] # Output of the dataloader, a batch of data items -DataBatch = Tuple[Tensor, List[BBoxList], Tensor] +DataBatch = Tuple[Tensor, Tensor] diff --git a/recaptcha_classifier/data/visualizer.py b/recaptcha_classifier/data/visualizer.py index cca973d9ef..b0315c9dea 100644 --- a/recaptcha_classifier/data/visualizer.py +++ b/recaptcha_classifier/data/visualizer.py @@ -1,5 +1,5 @@ import matplotlib.pyplot as plt -from .types import DatasetSplitDict +from .types import DatasetSplitMap import numpy as np @@ -10,12 +10,12 @@ class Visualizer: visualization of classes across the splits. """ @classmethod - def print_counts(cls, splits: DatasetSplitDict) -> None: + def print_counts(cls, splits: DatasetSplitMap) -> None: """ Prints the counts of samples in each class, for each split. Args: - splits (DatasetSplitDict): the dataset splits + splits (DatasetSplitMap): the dataset splits containing the pairs for each class. """ for split, cls_dict in splits.items(): @@ -26,14 +26,14 @@ def print_counts(cls, splits: DatasetSplitDict) -> None: @classmethod def plot_splits(cls, - splits: DatasetSplitDict, + splits: DatasetSplitMap, title: str = "Class Distribution in Splits") -> None: """ Plots a bar chart showing the amount and percentage of samples present in each class, for each of the splits. Args: - splits (DatasetSplitDict): the dataset splits + splits (DatasetSplitMap): the dataset splits containing the pairs for each class. title (str): the title of the plot. """ diff --git a/recaptcha_classifier/detection_labels.py b/recaptcha_classifier/detection_labels.py index 5c9fe8872c..2ca79204d7 100644 --- a/recaptcha_classifier/detection_labels.py +++ b/recaptcha_classifier/detection_labels.py @@ -5,13 +5,6 @@ class DetectionLabels(Enum): """ Enum for improving readability of the object classes. """ - - """ - OBJECT DETECTION TASK CLASSES - CROSSWALK = 0 - CHIMNEY = 1 - STAIR = 2 - """ BICYCLE = 0 BRIDGE = 1 BUS = 2 From e0710793c2824d59678bb9a46307dd5f8bf4de41 Mon Sep 17 00:00:00 2001 From: Sinan Date: Sat, 24 May 2025 23:35:18 +0200 Subject: [PATCH 02/32] revamped download process to cleanup structure of dataset --- main.py | 6 +- recaptcha_classifier/data/__init__.py | 2 - recaptcha_classifier/data/augment.py | 31 +++----- recaptcha_classifier/data/dataset.py | 6 -- recaptcha_classifier/data/downloader.py | 70 +++++++++++++----- recaptcha_classifier/data/pipeline.py | 2 +- recaptcha_classifier/data/preprocessor.py | 23 ------ recaptcha_classifier/data/scaler.py | 90 ----------------------- 8 files changed, 65 insertions(+), 165 deletions(-) delete mode 100644 recaptcha_classifier/data/scaler.py diff --git a/main.py b/main.py index 48501a3fe8..40a717dcdf 100644 --- a/main.py +++ b/main.py @@ -10,8 +10,8 @@ def main(): balance=True ) - loaders = pipeline.run() - + loaders = pipeline._downloader.download() + """ print("Data loaders built successfully.") for split, loader in loaders.items(): @@ -21,7 +21,7 @@ def main(): print(f" - images.shape: {images.shape}") print(f" - labels.shape: {labels.shape}") print(f" - class IDs: {labels.tolist()}") - + """ if __name__ == '__main__': main() diff --git a/recaptcha_classifier/data/__init__.py b/recaptcha_classifier/data/__init__.py index c7d918d6f9..7fdb9a5551 100644 --- a/recaptcha_classifier/data/__init__.py +++ b/recaptcha_classifier/data/__init__.py @@ -11,7 +11,6 @@ HorizontalFlip, RandomRotation ) -from .scaler import YOLOScaler from .collate_batch import collate_batch @@ -36,7 +35,6 @@ "AugmentationPipeline", "HorizontalFlip", "RandomRotation", - "YOLOScaler", # Methods "collate_batch", # Types diff --git a/recaptcha_classifier/data/augment.py b/recaptcha_classifier/data/augment.py index 8b622db68f..ca51e07e3a 100644 --- a/recaptcha_classifier/data/augment.py +++ b/recaptcha_classifier/data/augment.py @@ -2,8 +2,7 @@ from abc import ABC, abstractmethod from PIL import Image from typing import List -from .scaler import YOLOScaler -from .types import LoadedImg, BBoxList +from .types import LoadedImg class Augmentation(ABC): @@ -33,15 +32,13 @@ def __init__(self, transforms=[]) -> None: self._transforms: List[Augmentation] = transforms def apply_transforms(self, - image: Image.Image, - annotations: BBoxList) -> LoadedImg: + image: Image.Image) -> LoadedImg: """ Apply all transformations in the pipeline to the image and annotations. Args: image (Image.Image): The image to be augmented. - annotations (BBoxList): List of annotations associated with the image. Returns: @@ -51,43 +48,33 @@ def apply_transforms(self, for transform in self._transforms: if hasattr(transform, 'prob') and random.random() > transform.prob: continue - image, annotations = transform.augment(image, annotations) + image, annotations = transform.augment(image) return image, annotations class HorizontalFlip(Augmentation): - """Flips the image horizontally, with probability p and updates bboxes.""" + """Flips the image horizontally, with probability p.""" def __init__(self, p: float = 0.5) -> None: self.prob = p def augment(self, - image: Image.Image, - annotations: BBoxList) -> LoadedImg: + image: Image.Image) -> LoadedImg: flipped = image.transpose(Image.FLIP_LEFT_RIGHT) - new_annotations = YOLOScaler.scale_for_flip(annotations) - return flipped, new_annotations + return flipped class RandomRotation(Augmentation): """ - Rotates the image by a random angle, - also updates bboxes to reflect the rotation. + Rotates the image by a random angle. """ def __init__(self, degrees: float = 30.0, p: float = 0.5) -> None: self._degrees = degrees self.prob = p def augment(self, - image: Image.Image, - annotations: BBoxList) -> LoadedImg: + image: Image.Image) -> LoadedImg: angle = random.uniform(-self._degrees, self._degrees) rotated = image.rotate(angle) - new_annotations = (YOLOScaler - .scale_for_rotation(annotations, - angle, - image.size) - ) - - return rotated, new_annotations + return rotated diff --git a/recaptcha_classifier/data/dataset.py b/recaptcha_classifier/data/dataset.py index f14ad87f0b..8adda5a9a8 100644 --- a/recaptcha_classifier/data/dataset.py +++ b/recaptcha_classifier/data/dataset.py @@ -52,10 +52,6 @@ def __getitem__(self, # Load image and label img = self._prep.load_image(img_path) - # bb = self._prep.load_labels(lbl_path) - - # if not bb: - # raise ValueError(f"Bounding box list is empty for {lbl_path}") # Apply augmentation if passed if self._aug: @@ -71,6 +67,4 @@ def __getitem__(self, raise KeyError(f"Class name '{c_name}' not found in classes.") c_id = self._class_map[c_name] - # Return image tensor, bounding box and class index return tensor, self._prep.class_id_to_tensor(c_id) - # return tensor, bb, c_id diff --git a/recaptcha_classifier/data/downloader.py b/recaptcha_classifier/data/downloader.py index e03c87561c..1e597b6b7e 100644 --- a/recaptcha_classifier/data/downloader.py +++ b/recaptcha_classifier/data/downloader.py @@ -3,6 +3,7 @@ import shutil from pathlib import Path from alive_progress import alive_bar +from recaptcha_classifier.detection_labels import DetectionLabels class DatasetDownloader: @@ -28,6 +29,7 @@ def __init__(self, self._dest: Path = Path(dest) self._zip_path: Path = self._dest / "dataset.zip" self._progress = alive_bar + self._expected_folder_names = DetectionLabels.dataset_classnames() def download(self) -> None: """ @@ -41,16 +43,25 @@ def download(self) -> None: self._prepare_dest() print(f"Downloading {self._url} to {self._dest}...") self._download_zip() - self._unzip_and_cleanup() + self._extract_zip() + self._move_subfolders() + self._delete_labels() + self._flatten_images_folder() + self._zip_path.unlink() + print("Download and extraction completed successfully.") def _is_downloaded(self) -> bool: """ - Checks if the dataset is already downloaded. + Checks if the dataset is already downloaded and in the expected format. Returns: bool: True if the dataset is already downloaded, False otherwise. """ - return self._dest.exists() and any(self._dest.iterdir()) + if not self._dest.exists(): + return False + folders = {p.name for p in self._dest.iterdir() if p.is_dir()} + expected = set(self._expected_folder_names) + return expected.issubset(folders) def _prepare_dest(self) -> None: """ @@ -80,29 +91,52 @@ def _download_zip(self) -> None: for chunk in resp.iter_content(chunk_size=8192): f.write(chunk) bar(len(chunk)) - print("Download completed successfully.") - - def _unzip_and_cleanup(self) -> None: + + def _extract_zip(self) -> None: """ - Unzips the downloaded dataset. - Then it cleans up the zip file and its main extracted directory. - - Note: this assumes that the downloaded dataset has exactly - the structure of our selected Kaggle dataset for simplicity. + Extracts the downloaded zip file to the destination directory. """ - print("Extracting...") with zipfile.ZipFile(self._zip_path) as z: z.extractall(self._dest) - root = next(p for p in self._dest.iterdir() if p.is_dir()) + def _move_subfolders(self) -> None: + """ + Moves the main images and labels subfolders + from the extracted directory to the destination directory. + Finally, it removes the main extracted directory that is now empty. + """ + root = next(p for p in self._dest.iterdir() if p.is_dir() and p.name not in self._expected_folder_names) + for sub in ("images", "labels"): - (root / sub).rename(self._dest / sub) - - root.rmdir() + source = root / sub + if source.exists(): + target = self._dest / sub + if target.exists(): + shutil.rmtree(target) + source.rename(target) + if root.exists() and root.is_dir(): + root.rmdir() + + def _delete_labels(self) -> None: + """ + Deletes the labels directory if it exists. + """ label_dir = self._dest / "labels" if label_dir.exists() and label_dir.is_dir(): shutil.rmtree(label_dir) + + def _flatten_images_folder(self) -> None: + images_dir = self._dest / "images" + if not images_dir.exists(): + return + + for subfolder in images_dir.iterdir(): + if subfolder.is_dir(): + target_path = self._dest / subfolder.name + if target_path.exists(): + shutil.rmtree(target_path) + subfolder.rename(target_path) - self._zip_path.unlink() - print("Extraction and cleanup completed successfully.") \ No newline at end of file + if not any(images_dir.iterdir()): + images_dir.rmdir() diff --git a/recaptcha_classifier/data/pipeline.py b/recaptcha_classifier/data/pipeline.py index a2949c67c6..961e44a513 100644 --- a/recaptcha_classifier/data/pipeline.py +++ b/recaptcha_classifier/data/pipeline.py @@ -114,4 +114,4 @@ def run(self) -> Dict[str, DataLoader]: # 4. Create DataLoaders for each split print("e. Creating DataLoaders for each split...") loaders = self._creator.create_loaders(splits) - return loaders + return loaders \ No newline at end of file diff --git a/recaptcha_classifier/data/preprocessor.py b/recaptcha_classifier/data/preprocessor.py index c171503a2e..51cd4dd0ea 100644 --- a/recaptcha_classifier/data/preprocessor.py +++ b/recaptcha_classifier/data/preprocessor.py @@ -3,7 +3,6 @@ from PIL import Image import numpy as np import torch -from .types import BBoxList class ImagePrep: @@ -39,28 +38,6 @@ def load_image(self, img_path: Path) -> Image.Image: loaded = Image.open(img_path).convert("RGB") return self._resize(loaded) - def load_labels(self, lbl_path: Path) -> BBoxList: - """ - Parses the list of YOLO format labels from the file at the given path. - - Args: - lbl_path (Path): Path to the label file. - - Returns: - BBoxList: List of bounding boxes in YOLO format - (x_center, y_center, width, height). - """ - bounding_boxes = [] - with open(lbl_path, "r") as f: - for line in f: - parts = line.strip().split() - if len(parts) < 5: - continue # invalid line skipped - _, x_center, y_center, width, height = map(float, parts) - bounding_boxes.append((x_center, y_center, width, height)) - - return bounding_boxes - def _resize(self, img: Image.Image) -> Image.Image: """ Resizes the image to the target size. diff --git a/recaptcha_classifier/data/scaler.py b/recaptcha_classifier/data/scaler.py deleted file mode 100644 index f35a303125..0000000000 --- a/recaptcha_classifier/data/scaler.py +++ /dev/null @@ -1,90 +0,0 @@ -from typing import Tuple -from .types import BBoxList - - -class YOLOScaler: - """ - This class is responsible for scaling the YOLO format bounding boxes - based on the transform applied to the image. It is only - used inside the AugmentationPipeline class, to adjust - the coordinates of the bounding boxes after applying - transformations to the image. - """ - @staticmethod - def scale_for_flip(bboxes: BBoxList) -> BBoxList: - """ - Adjusts the bounding boxes for horizontal flip. - - Args: - bboxes (BBoxList): List of bounding boxes in YOLO format - (x_center, y_center, width, height). - - Returns: - BBoxList: List of scaled bounding boxes. - """ - return [(1 - x, y, w, h) for (x, y, w, h) in bboxes] - - @staticmethod - def scale_for_rotation(bboxes: BBoxList, - angle: float, - size: Tuple[int, int]) -> BBoxList: - """ - Adjusts the bounding boxes for rotation. - - Args: - bboxes (BBoxList): List of bounding boxes in YOLO format - (x_center, y_center, width, height). - angle (float): Angle of rotation. - size (Tuple[int, int]): Size of the image. (width, height) - - Returns: - BBoxList: List of scaled bounding boxes. - """ - import math - width, height = size - angle_rad = math.radians(angle) - c_x, c_y = width / 2, height / 2 # center coordinates of the image - - n_ann = [] # the new bounding boxes, that we will return - - for x, y, w, h in bboxes: - # calculate pixel coordinates from bb - x0, y0 = x * width, y * height - bw, bh = w * width, h * height - - # calculate corner coordinates - corners = [ - (x0 - bw / 2, y0 - bh / 2), - (x0 + bw / 2, y0 - bh / 2), - (x0 + bw / 2, y0 + bh / 2), - (x0 - bw / 2, y0 + bh / 2) - ] - - # rotate the corners - new_corners = [] - for cx, cy in corners: - # rotate the corners around the center of the image - # formula at https://en.wikipedia.org/wiki/Rotation_matrix - x_rot = (math.cos(angle_rad) * (cx - c_x) - - math.sin(angle_rad) * (cy - c_y) + - c_x) - y_rot = (math.sin(angle_rad) * (cx - c_x) + - math.cos(angle_rad) * (cy - c_y) + - c_y) - - new_corners.append((x_rot, y_rot)) - - # calculate new bounding box - x_min = min(c[0] for c in new_corners) - x_max = max(c[0] for c in new_corners) - y_min = min(c[1] for c in new_corners) - y_max = max(c[1] for c in new_corners) - - new_x = max(0, min(1, (x_min + x_max) / (2 * width))) - new_y = max(0, min(1, (y_min + y_max) / (2 * height))) - new_w = max(0, min(1, (x_max - x_min) / width)) - new_h = max(0, min(1, (y_max - y_min) / height)) - - n_ann.append((new_x, new_y, new_w, new_h)) - - return n_ann From 6f136c6e2ba6c41e07656d4e649ebaa1f3ccb901 Mon Sep 17 00:00:00 2001 From: Sinan Date: Sun, 25 May 2025 00:01:21 +0200 Subject: [PATCH 03/32] polished some more types and docs, given the uodates --- main.py | 5 +- recaptcha_classifier/data/__init__.py | 4 +- recaptcha_classifier/data/downloader.py | 5 +- recaptcha_classifier/data/pair_loader.py | 121 ---------------------- recaptcha_classifier/data/paths_loader.py | 93 +++++++++++++++++ recaptcha_classifier/data/pipeline.py | 23 ++-- recaptcha_classifier/data/splitter.py | 3 +- recaptcha_classifier/data/visualizer.py | 44 ++------ recaptcha_classifier/detection_labels.py | 2 +- tests/data/notyet-test_pair_loader.py | 16 +-- 10 files changed, 130 insertions(+), 186 deletions(-) delete mode 100644 recaptcha_classifier/data/pair_loader.py create mode 100644 recaptcha_classifier/data/paths_loader.py diff --git a/main.py b/main.py index 40a717dcdf..e00783860d 100644 --- a/main.py +++ b/main.py @@ -6,11 +6,10 @@ def main(): pipeline = DataPreprocessingPipeline( - DetectionLabels.to_class_map(), + DetectionLabels, balance=True ) - - loaders = pipeline._downloader.download() + loaders = pipeline.run() """ print("Data loaders built successfully.") diff --git a/recaptcha_classifier/data/__init__.py b/recaptcha_classifier/data/__init__.py index 7fdb9a5551..b4dde44d2b 100644 --- a/recaptcha_classifier/data/__init__.py +++ b/recaptcha_classifier/data/__init__.py @@ -1,6 +1,6 @@ from .pipeline import DataPreprocessingPipeline from .downloader import DatasetDownloader -from .pair_loader import ImageLabelLoader +from .paths_loader import ImagePathsLoader from .splitter import DataSplitter from .visualizer import Visualizer from .loader_factory import LoaderFactory @@ -26,7 +26,7 @@ # Classes "DataPreprocessingPipeline", "DatasetDownloader", - "ImageLabelLoader", + "ImagePathsLoader", "DataSplitter", "Visualizer", "LoaderFactory", diff --git a/recaptcha_classifier/data/downloader.py b/recaptcha_classifier/data/downloader.py index 1e597b6b7e..219c7f25f0 100644 --- a/recaptcha_classifier/data/downloader.py +++ b/recaptcha_classifier/data/downloader.py @@ -2,8 +2,8 @@ import zipfile import shutil from pathlib import Path +from typing import List from alive_progress import alive_bar -from recaptcha_classifier.detection_labels import DetectionLabels class DatasetDownloader: @@ -15,6 +15,7 @@ class DatasetDownloader: dataset downloading operation, with no other responsibilities. """ def __init__(self, + class_names: List[str], url: str = ("https://www.kaggle.com/api/v1/datasets/" "download/mikhailma/test-dataset"), dest: str = "data") -> None: @@ -29,7 +30,7 @@ def __init__(self, self._dest: Path = Path(dest) self._zip_path: Path = self._dest / "dataset.zip" self._progress = alive_bar - self._expected_folder_names = DetectionLabels.dataset_classnames() + self._expected_folder_names = class_names def download(self) -> None: """ diff --git a/recaptcha_classifier/data/pair_loader.py b/recaptcha_classifier/data/pair_loader.py deleted file mode 100644 index 828727c5d0..0000000000 --- a/recaptcha_classifier/data/pair_loader.py +++ /dev/null @@ -1,121 +0,0 @@ -import logging -from pathlib import Path -from typing import List, Dict -from .types import ClassToImgPaths - -logger = logging.getLogger(__name__) - - -class ImageLabelLoader: - """ - This class loads all image-label pairs for given classes. - - It scans for all matching images and labels and caches the result. - - It follows Single Responsibility Principle (SRP) as it only handles - the loading of the pairs. Also, it uses the Iterator pattern, as - it can be looped over to get the list pairs as tuples by class, - in format (class, [(img_path, lbl_path), ...].) - """ - def __init__(self, - classes: List[str], - images_dir: str = "data/images", - # labels_dir: str = "data/labels" - ) -> None: - """ - Initializes the PairsLoader instance. - - Args: - classes (List[str]): List of class names to load. - images_dir (str): Path to the directory containing images. - labels_dir (str): Path to the directory containing labels. - """ - self._classes = classes - self._images_dir = Path(images_dir) - # self._labels_dir = Path(labels_dir) - self._pairs: ClassToImgPaths = dict() - - def find_pairs(self) -> ClassToImgPaths: - """ - Returns all pairs loaded for the given classes. - It caches the response after first run. - - Returns: - List[Tuple[Path, Path]]: List of tuples - containing image and label paths. (img_path, lbl_path) - """ - if not self._pairs: - self._load_pairs() - return self._pairs - - def __iter__(self): - """ - Iterates over classes and their respective list of pairs. - - Yields: - Tuple[str, List[Tuple[Path, Path]]]: Class name and list of - tuples containing image and label paths. - """ - for cls, pairs in self.find_pairs().items(): - yield cls, pairs - - def __len__(self) -> int: - """ - Returns total number of matched pairs. - - Returns: - int: Number of matched pairs. - """ - return sum(len(pairs) for pairs in self.find_pairs().values()) - - def class_count(self) -> Dict[str, int]: - """ - Returns a dictionary with the count of pairs for each class. - The keys are class names and the values are the counts. - - Returns: - Dict[str, int]: Dictionary with class names as keys and - counts as values. - """ - return {cls: len(pairs) for cls, pairs in self.find_pairs().items()} - - def _load_pairs(self) -> None: - """ - Private method to scan directories of given classes and match all - available image-label pairs. - It ignores images with missing labels. - """ - total_count = 0 - for cls in self._classes: - img_dir = self._images_dir / cls - # lbl_dir = self._labels_dir / cls - # skipped, cls_count = 0, 0 - - if not Path.is_dir(img_dir): # or not Path.exists(lbl_dir) - logger.info(f"Warning: Missing folder for {cls}. Skipping.") - continue - - self._pairs[cls] = list(img_dir.glob("*.png")) # [] - N = len(self._pairs[cls]) - logger.info(f"Found {N} images in {cls}.") - total_count += N - """ - for img_path in img_dir.glob("*.png"): - lbl_path = lbl_dir / img_path.name.replace(".png", ".txt") - cls_count += 1 - - if not Path.exists(lbl_path): - skipped += 1 - continue - - self._pairs[cls].append((img_path, lbl_path)) - - - print(f"Loaded {cls_count - skipped} image-label pairs for {cls}.") - if skipped > 0: - print(f"Warning: {skipped} missing labels in {cls}. Skipped.") - - total_count += cls_count - skipped - """ - - print(f"Total pairs loaded: {total_count}") diff --git a/recaptcha_classifier/data/paths_loader.py b/recaptcha_classifier/data/paths_loader.py new file mode 100644 index 0000000000..712e09ad90 --- /dev/null +++ b/recaptcha_classifier/data/paths_loader.py @@ -0,0 +1,93 @@ +from pathlib import Path +from typing import List, Dict +from .types import ClassToImgPaths + + +class ImagePathsLoader: + """ + This class loads all image paths for given classes. + + It scans for all matching images and caches the result. + + It follows Single Responsibility Principle (SRP) as it only handles + the loading of the image paths. Also, it uses the Iterator pattern, as + it can be looped over to get the list paths as tuples by class, + in format (class, [img_path1, ...]). + """ + def __init__(self, + classes: List[str], + images_dir: str = "data") -> None: + """ + Initializes the ImagePathsLoader instance. + + Args: + classes (List[str]): List of class names to load. + images_dir (str): Path to the directory containing images. + """ + self._classes = classes + self._images_dir = Path(images_dir) + self._paths: ClassToImgPaths = dict() + + def find_image_paths(self) -> ClassToImgPaths: + """ + Returns all image paths loaded for the given classes. + It caches the response after first run. + + Returns: + ClassToImgPaths: Dictionary mapping class names to lists + of image paths. + """ + if not self._paths: + self._load_pairs() + return self._paths + + def __iter__(self): + """ + Iterates over classes and their respective list of pairs. + + Yields: + Tuple[str, List[Path]]: Class name and list of + image paths. + """ + for cls, pairs in self.find_image_paths().items(): + yield cls, pairs + + def __len__(self) -> int: + """ + Returns total number of matched pairs. + + Returns: + int: Number of matched pairs. + """ + return sum(len(pairs) for pairs in self.find_image_paths().values()) + + def class_count(self) -> Dict[str, int]: + """ + Returns a dictionary with the count of pairs for each class. + The keys are class names and the values are the counts. + + Returns: + Dict[str, int]: Dictionary with class names as keys and + counts as values. + """ + return {cls: len(pairs) for cls, pairs in self.find_image_paths().items()} + + def _load_pairs(self) -> None: + """ + Private method to scan directories of given classes and match all + available image paths. + """ + total_count = 0 + for cls in self._classes: + img_dir = self._images_dir / cls + + if not Path.is_dir(img_dir): + print(f"Warning: Missing folder for {cls}. Skipping.") + continue + + self._paths[cls] = sorted(img_dir.glob("*.png")) + N = len(self._paths[cls]) + print(f"Found {N} images in {cls}.") + total_count += N + + print(f"Total image paths found: {total_count}") diff --git a/recaptcha_classifier/data/pipeline.py b/recaptcha_classifier/data/pipeline.py index 961e44a513..b9082fec72 100644 --- a/recaptcha_classifier/data/pipeline.py +++ b/recaptcha_classifier/data/pipeline.py @@ -1,8 +1,8 @@ from typing import Dict, Tuple from torch.utils.data import DataLoader - +from enum import EnumMeta from .downloader import DatasetDownloader -from .pair_loader import ImageLabelLoader +from .paths_loader import ImagePathsLoader from .splitter import DataSplitter from .visualizer import Visualizer from .preprocessor import ImagePrep @@ -33,7 +33,7 @@ class DataPreprocessingPipeline: pipeline and it includes all its components. """ def __init__(self, - class_map: Dict[str, int], + class_enum: EnumMeta, ratios: Tuple[float, float, float] = (0.7, 0.2, 0.1), seed: int = 23, # our group number batch_size: int = 32, @@ -44,7 +44,7 @@ def __init__(self, Initializes the DataPreprocessingPipeline with the given parameters. Args: - class_map (Dict[str, int]): Mappng of class names to indices. + class_enum (EnumMeta): Enum class containing dataset classes. ratios (Tuple[float, float, float]): Ratios for train, val, and test splits. seed (int): Random seed for reproducibility. @@ -53,14 +53,16 @@ def __init__(self, balance (bool): Whether to balance the dataset. show_plots (bool): Whether to show plots. """ - self._downloader = DatasetDownloader() - self._loader = ImageLabelLoader(list(class_map.keys())) + self._class_enum = class_enum + self._downloader = DatasetDownloader(self._class_enum + .dataset_classnames()) + self._loader = ImagePathsLoader(self._class_enum.dataset_classnames()) self._splitter = DataSplitter(ratios, seed=seed) self._show_plots = show_plots self._preproc = ImagePrep() self._augment = self._build_augmentator() self._creator = LoaderFactory( - class_map=class_map, + class_map=self._class_enum.to_class_map(), preprocessor=self._preproc, augmentator=self._augment, batch_size=batch_size, @@ -95,7 +97,7 @@ def run(self) -> Dict[str, DataLoader]: # 2. Finds all pairs of images and YOLO annotations from the dataset print("b. Searching for all the data...") - pairs_by_class = self._loader.find_pairs() + pairs_by_class = self._loader.find_image_paths() # 3a. Splits the data into train, val, and test sets print("c. Splitting the data...") @@ -113,5 +115,6 @@ def run(self) -> Dict[str, DataLoader]: # 4. Create DataLoaders for each split print("e. Creating DataLoaders for each split...") - loaders = self._creator.create_loaders(splits) - return loaders \ No newline at end of file + #loaders = self._creator.create_loaders(splits) + #return loaders + return [] \ No newline at end of file diff --git a/recaptcha_classifier/data/splitter.py b/recaptcha_classifier/data/splitter.py index 31416a9274..dcba9fd3b3 100644 --- a/recaptcha_classifier/data/splitter.py +++ b/recaptcha_classifier/data/splitter.py @@ -15,7 +15,8 @@ class DataSplitter: def __init__(self, ratios: Tuple[float, float, float] = (0.7, 0.2, 0.1), shuffle: bool = True, - seed: int = None) -> None: + seed: int = 23 # our group number + ) -> None: """ Args: ratios (Tuple[float, float, float]): Ratios for diff --git a/recaptcha_classifier/data/visualizer.py b/recaptcha_classifier/data/visualizer.py index b0315c9dea..6a95de7c33 100644 --- a/recaptcha_classifier/data/visualizer.py +++ b/recaptcha_classifier/data/visualizer.py @@ -16,12 +16,12 @@ def print_counts(cls, splits: DatasetSplitMap) -> None: Args: splits (DatasetSplitMap): the dataset splits - containing the pairs for each class. + containing the items for each class. """ for split, cls_dict in splits.items(): print(f"{split.upper()}:") - for cls, pairs in cls_dict.items(): - print(f" {cls:5s}: {len(pairs)}") + for cls, items in cls_dict.items(): + print(f" {cls:5s}: {len(items)}") print() @classmethod @@ -34,7 +34,7 @@ def plot_splits(cls, Args: splits (DatasetSplitMap): the dataset splits - containing the pairs for each class. + containing the items for each class. title (str): the title of the plot. """ classes = list(splits['train'].keys()) @@ -51,40 +51,8 @@ def plot_splits(cls, # total for each class, simply adding counts of each split totals = [t+v+te for t, v, te in zip(counts_train, counts_val, counts_test)] - - """ - # drawing bars - train_bars = plt.bar([i - width for i in x], - counts_train, - width, - label='Train') - val_bars = plt.bar(x, counts_val, width, label='Val') - test_bars = plt.bar([i + width for i in x], - counts_test, - width, - label='Test') - - # adding perc labels on top of each of the bar - for bars, counts in [(train_bars, counts_train), - (val_bars, counts_val), - (test_bars, counts_test)]: - for bar, count, total in zip(bars, counts, totals): - perc = count / total * 100 - plt.text( - bar.get_x() + bar.get_width() / 2, # middle of the bar, - bar.get_height(), # on top of the bar, - f'{perc:.1f}%', # percentage, formatted to 1 decimal - ha='center', va='bottom' - ) - - plt.xticks(x, classes) - plt.ylabel('No. of Samples') - plt.title(title) - plt.legend() - plt.tight_layout() - plt.show() - """ - fig, ax = plt.subplots(figsize=(num_classes, 6)) + + _, ax = plt.subplots(figsize=(num_classes, 6)) bar1 = ax.bar(x - width, counts_train, width, label='Train') bar2 = ax.bar(x, counts_val, width, label='Val') bar3 = ax.bar(x + width, counts_test, width, label='Test') diff --git a/recaptcha_classifier/detection_labels.py b/recaptcha_classifier/detection_labels.py index 2ca79204d7..1cbb610f0a 100644 --- a/recaptcha_classifier/detection_labels.py +++ b/recaptcha_classifier/detection_labels.py @@ -83,5 +83,5 @@ def dataset_classnames(cls) -> list: Returns: list: List of class names. """ - return [name.capitalize().replace("_", " ") + return [name.replace("_", " ").title() for name in cls.__members__.keys()] diff --git a/tests/data/notyet-test_pair_loader.py b/tests/data/notyet-test_pair_loader.py index 987cdf3af3..e7ae89a983 100644 --- a/tests/data/notyet-test_pair_loader.py +++ b/tests/data/notyet-test_pair_loader.py @@ -2,10 +2,10 @@ from pathlib import Path from unittest.mock import patch -from recaptcha_classifier.data.pair_loader import ImageLabelLoader +from recaptcha_classifier.data.pair_loader import ImagePathsLoader -class TestImageLabelLoader(unittest.TestCase): +class TestImagePathsLoader(unittest.TestCase): @patch("recaptcha_classifier.data.pair_loader.Path.glob") @patch("recaptcha_classifier.data.pair_loader.Path.exists", return_value=True) @@ -20,8 +20,8 @@ def test_load_pairs_with_all_labels(self, Path("data/images/class1/img2.png"), ] # 2 images found in the png glob - loader = ImageLabelLoader(["class1"]) - pairs = loader.find_pairs() + loader = ImagePathsLoader(["class1"]) + pairs = loader.find_image_paths() expected_pairs = { (Path("data/images/class1/img1.png"), @@ -49,8 +49,8 @@ def test_load_pairs_with_missing_labels(self, # label folder exists, img1.txt exists but img2.txt does not exists_mock.side_effect = [True, True, False] - loader = ImageLabelLoader(["class1"]) - pairs = loader.find_pairs() + loader = ImagePathsLoader(["class1"]) + pairs = loader.find_image_paths() self.assertIn("class1", pairs) expected_pair = { @@ -65,14 +65,14 @@ def test_load_pairs_with_missing_labels(self, @patch("recaptcha_classifier.data.pair_loader.Path.exists") @patch("recaptcha_classifier.data.pair_loader.Path.is_dir") def test_caching(self, is_dir_mock, exists_mock, glob_mock): - loader = ImageLabelLoader(["class1"]) + loader = ImagePathsLoader(["class1"]) loader._pairs = {"test": [(Path("test.png"), Path("test.txt"))]} # if any mocked method is called, then the cache is not used for method in (is_dir_mock, exists_mock, glob_mock): method.side_effect = AssertionError(f"{method} called, error!") - pairs = loader.find_pairs() + pairs = loader.find_image_paths() self.assertIs(pairs, loader._pairs) self.assertEqual(pairs, {"test": [(Path("test.png"), From 8a953d2e11119b718d75c87c5cf5971ffed88086 Mon Sep 17 00:00:00 2001 From: Sinan Date: Sun, 25 May 2025 00:34:27 +0200 Subject: [PATCH 04/32] started redoing tests --- main.py | 5 ++-- recaptcha_classifier/data/augment.py | 20 +++++++--------- recaptcha_classifier/data/dataset.py | 16 ++++++------- recaptcha_classifier/data/loader_factory.py | 24 +++++++++---------- recaptcha_classifier/data/pipeline.py | 7 +++--- recaptcha_classifier/detection_labels.py | 2 +- ...notyet-test_augment.py => test_augment.py} | 16 +++---------- ...notyet-test_dataset.py => test_dataset.py} | 18 ++++---------- ...ader_factory.py => test_loader_factory.py} | 0 ...est_pair_loader.py => test_pair_loader.py} | 0 ...t_preprocessor.py => test_preprocessor.py} | 0 .../{notyet-test_scaler.py => test_scaler.py} | 0 ...tyet-test_splitter.py => test_splitter.py} | 0 ...-test_visualizer.py => test_visualizer.py} | 0 14 files changed, 42 insertions(+), 66 deletions(-) rename tests/data/{notyet-test_augment.py => test_augment.py} (66%) rename tests/data/{notyet-test_dataset.py => test_dataset.py} (71%) rename tests/data/{notyet-test_loader_factory.py => test_loader_factory.py} (100%) rename tests/data/{notyet-test_pair_loader.py => test_pair_loader.py} (100%) rename tests/data/{notyet-test_preprocessor.py => test_preprocessor.py} (100%) rename tests/data/{notyet-test_scaler.py => test_scaler.py} (100%) rename tests/data/{notyet-test_splitter.py => test_splitter.py} (100%) rename tests/data/{notyet-test_visualizer.py => test_visualizer.py} (100%) diff --git a/main.py b/main.py index e00783860d..c06ce2ac04 100644 --- a/main.py +++ b/main.py @@ -9,8 +9,9 @@ def main(): DetectionLabels, balance=True ) + loaders = pipeline.run() - """ + print("Data loaders built successfully.") for split, loader in loaders.items(): @@ -20,7 +21,7 @@ def main(): print(f" - images.shape: {images.shape}") print(f" - labels.shape: {labels.shape}") print(f" - class IDs: {labels.tolist()}") - """ + if __name__ == '__main__': main() diff --git a/recaptcha_classifier/data/augment.py b/recaptcha_classifier/data/augment.py index ca51e07e3a..1dcef38bbf 100644 --- a/recaptcha_classifier/data/augment.py +++ b/recaptcha_classifier/data/augment.py @@ -9,14 +9,14 @@ class Augmentation(ABC): """Abstract class for data augmentation.""" @abstractmethod def augment(self, - image: Image.Image, + image: LoadedImg, annotations: List) -> LoadedImg: """ Apply the transformation of the image and updates the bounding boxes if necessary. Args: - image (Image.Image): The image to be augmented. + image (LoadedImg): The image to be augmented. annotations (List): List of annotations associated with the image. Returns: @@ -32,24 +32,22 @@ def __init__(self, transforms=[]) -> None: self._transforms: List[Augmentation] = transforms def apply_transforms(self, - image: Image.Image) -> LoadedImg: + image: LoadedImg) -> LoadedImg: """ Apply all transformations in the pipeline to the image and annotations. Args: - image (Image.Image): The image to be augmented. - associated with the image. + image (LoadedImg): The image to be augmented. Returns: - LoadedImg: The augmented image and the updated - annotations. + LoadedImg: The augmented image. """ for transform in self._transforms: if hasattr(transform, 'prob') and random.random() > transform.prob: continue - image, annotations = transform.augment(image) - return image, annotations + image = transform.augment(image) + return image class HorizontalFlip(Augmentation): @@ -58,7 +56,7 @@ def __init__(self, p: float = 0.5) -> None: self.prob = p def augment(self, - image: Image.Image) -> LoadedImg: + image: LoadedImg) -> LoadedImg: flipped = image.transpose(Image.FLIP_LEFT_RIGHT) return flipped @@ -73,7 +71,7 @@ def __init__(self, degrees: float = 30.0, p: float = 0.5) -> None: self.prob = p def augment(self, - image: Image.Image) -> LoadedImg: + image: LoadedImg) -> LoadedImg: angle = random.uniform(-self._degrees, self._degrees) rotated = image.rotate(angle) diff --git a/recaptcha_classifier/data/dataset.py b/recaptcha_classifier/data/dataset.py index 8adda5a9a8..4d64cccfb7 100644 --- a/recaptcha_classifier/data/dataset.py +++ b/recaptcha_classifier/data/dataset.py @@ -7,7 +7,7 @@ class ImageDataset(Dataset): """ - A class to handle the pairs of (image, label) for the dataset. + A class to handle the images for the dataset. It makes them ready for training, by applying augmentation for the training set, preprocessing and makes sure that the output format is in PyTorch Tensor format. @@ -17,7 +17,7 @@ class ImageDataset(Dataset): https://docs.pytorch.org/tutorials/beginner/basics/data_tutorial.html """ def __init__(self, - pairs: ImagePathList, + items: ImagePathList, preprocessor: ImagePrep, augmentator: Optional[AugmentationPipeline] = None, class_map: dict = {} @@ -25,13 +25,13 @@ def __init__(self, """ Initializes the ImageDataset with the given parameters. """ - self._pairs = pairs + self._items = items self._prep = preprocessor self._aug = augmentator self._class_map = class_map def __len__(self) -> int: - return len(self._pairs) + return len(self._items) def __getitem__(self, idx: int @@ -47,16 +47,14 @@ def __getitem__(self, preprocessed image in tensor format, the YOLO bound box annotations and the label. """ - # img_path, lbl_path = self._pairs[idx] - img_path = self._pairs[idx] + img_path = self._items[idx] - # Load image and label + # Load image img = self._prep.load_image(img_path) # Apply augmentation if passed if self._aug: - # img, bb = self._aug.apply_transforms(img, bb) - img, _ = self._aug.apply_transforms(img, []) + img = self._aug.apply_transforms(img) # Convert image to tensor tensor = self._prep.to_tensor(img) diff --git a/recaptcha_classifier/data/loader_factory.py b/recaptcha_classifier/data/loader_factory.py index caa452fbee..e5dc61e784 100644 --- a/recaptcha_classifier/data/loader_factory.py +++ b/recaptcha_classifier/data/loader_factory.py @@ -25,7 +25,7 @@ def __init__(self, Args: class_map (dict): A dictionary mapping class names to indices. - preprocessor (ImagePrep): The preprocessor to use. + preprocessor (ImagePrep): The preprocessor to use for data. augmentator (Optional[AugmentationPipeline]): The augmentator used batch_size (int): Batch size for DataLoader. num_workers (int): Number of workers for DataLoader. @@ -35,34 +35,33 @@ def __init__(self, self._aug = augmentator self._batch_size = batch_size self._num_workers = num_workers - self._balance = balance # do we use WeightedRandomSampler?? + self._balance = balance self._class_map = class_map - # OPTIONAL: self._loaders to cache response def create_loaders(self, splits: DatasetSplitMap) -> Dict[str, DataLoader]: loaders: Dict[str, DataLoader] = {} for split_name, cls_dict in splits.items(): - # flatten nested dict of pairs - flat_pairs: ImagePathList = [pair + # flatten nested dict of image_paths + flat_image_paths: ImagePathList = [image_path # traversing over classes - for pairs in cls_dict.values() - # traversing over pairs - for pair in pairs + for image_paths in cls_dict.values() + # traversing over image_paths + for image_path in image_paths ] # augmentatr only for training set augmentator = self._aug if split_name == 'train' else None dataset = ImageDataset( - pairs=flat_pairs, + items=flat_image_paths, preprocessor=self._preprocessor, augmentator=augmentator, class_map=self._class_map ) - sampler = (self._build_sampler(flat_pairs) if self._balance + sampler = (self._build_sampler(flat_image_paths) if self._balance and split_name == "train" else None) loader = DataLoader( @@ -78,14 +77,13 @@ def create_loaders(self, return loaders - def _build_sampler(self, pairs): + def _build_sampler(self, image_paths): """ Builds a sampler for the dataset to balance the classes. """ class_counts = Counter() targets = [] - # for img_path, _ in pairs: - for img_path in pairs: + for img_path in image_paths: cls = img_path.parent.name targets.append(self._class_map[cls]) class_counts[self._class_map[cls]] += 1 diff --git a/recaptcha_classifier/data/pipeline.py b/recaptcha_classifier/data/pipeline.py index b9082fec72..612328a8f2 100644 --- a/recaptcha_classifier/data/pipeline.py +++ b/recaptcha_classifier/data/pipeline.py @@ -39,7 +39,7 @@ def __init__(self, batch_size: int = 32, num_workers: int = 4, balance: bool = False, - show_plots: bool = True) -> None: + show_plots: bool = False) -> None: """ Initializes the DataPreprocessingPipeline with the given parameters. @@ -115,6 +115,5 @@ def run(self) -> Dict[str, DataLoader]: # 4. Create DataLoaders for each split print("e. Creating DataLoaders for each split...") - #loaders = self._creator.create_loaders(splits) - #return loaders - return [] \ No newline at end of file + loaders = self._creator.create_loaders(splits) + return loaders \ No newline at end of file diff --git a/recaptcha_classifier/detection_labels.py b/recaptcha_classifier/detection_labels.py index 1cbb610f0a..e4ae0311c4 100644 --- a/recaptcha_classifier/detection_labels.py +++ b/recaptcha_classifier/detection_labels.py @@ -53,7 +53,7 @@ def to_class_map(cls) -> dict: Returns: dict: Dictionary representation of the enum. """ - return {cl.name.capitalize().replace("_", " "): + return {cl.name.replace("_", " ").title(): cl.value for cl in cls} @classmethod diff --git a/tests/data/notyet-test_augment.py b/tests/data/test_augment.py similarity index 66% rename from tests/data/notyet-test_augment.py rename to tests/data/test_augment.py index b153bf66cd..667a2681c8 100644 --- a/tests/data/notyet-test_augment.py +++ b/tests/data/test_augment.py @@ -7,7 +7,6 @@ HorizontalFlip, RandomRotation ) -from recaptcha_classifier.data.scaler import YOLOScaler class TestAugmentation(unittest.TestCase): @@ -17,39 +16,30 @@ def setUp(self): (1, 100, 3)) ) self.img = self.img.convert("RGB") - self.bb = [(0.5, 0.5, 0.2, 0.2)] def test_horizontal_flip(self): aug = HorizontalFlip(p=1.0) - flipped_img, flipped_bb = aug.augment(self.img, self.bb) + flipped_img = aug.augment(self.img) self.assertFalse(np.array_equal(np.array(flipped_img), np.array(self.img))) - result = YOLOScaler.scale_for_flip(self.bb) - self.assertEqual(flipped_bb, result) - @patch('random.uniform', return_value=30) def test_random_rotation(self, _): augmenter = RandomRotation(degrees=30) - rotated_img, rotated_bb = augmenter.augment(self.img, self.bb) + rotated_img = augmenter.augment(self.img) self.assertFalse(np.array_equal(np.array(rotated_img), np.array(self.img))) - result = YOLOScaler.scale_for_rotation(self.bb, 30, self.img.size) - for i, j in zip(rotated_bb, result): - self.assertAlmostEqual(i, j, places=4) - def test_pipeline(self): pipeline = AugmentationPipeline() pipeline.add_transform(HorizontalFlip(p=1.0)) pipeline.add_transform(RandomRotation(degrees=30)) - new_img, new_bb = pipeline.apply_transforms(self.img, self.bb) + new_img = pipeline.apply_transforms(self.img) self.assertIsInstance(new_img, Image.Image) - self.assertIsInstance(new_bb, list) if __name__ == "__main__": diff --git a/tests/data/notyet-test_dataset.py b/tests/data/test_dataset.py similarity index 71% rename from tests/data/notyet-test_dataset.py rename to tests/data/test_dataset.py index 48ce2e0376..9b8bedc5b8 100644 --- a/tests/data/notyet-test_dataset.py +++ b/tests/data/test_dataset.py @@ -10,19 +10,16 @@ class TestImageDataset(unittest.TestCase): def setUp(self): - self.pairs = [(Path("data/images/c1/i1.png"), - Path("data/labels/c1/i1.txt"))] + self.images = [Path("data/images/c1/i1.png")] self.class_map = {"c1": 0} self.preprocessor = ImagePrep() self.augmentator = AugmentationPipeline() @patch.object(ImagePrep, 'load_image') - @patch.object(ImagePrep, 'load_labels') @patch.object(ImagePrep, 'to_tensor') - def test_loading(self, to_tensor_mock, load_labels_mock, load_image_mock): + def test_loading(self, to_tensor_mock, load_image_mock): # Mock the return values load_image_mock.return_value = MagicMock() - load_labels_mock.return_value = [(0.1, 0.2, 0.3, 0.4)] to_tensor_mock.return_value = torch.rand(3, 224, 224) # expect shape dataset = ImageDataset( @@ -32,18 +29,15 @@ def test_loading(self, to_tensor_mock, load_labels_mock, load_image_mock): class_map=self.class_map ) - tensor, bboxes, cid = dataset[0] + tensor, cid = dataset[0] self.assertIsInstance(tensor, torch.Tensor) self.assertEqual(tensor.shape, (3, 224, 224)) - self.assertIsInstance(bboxes, list) - self.assertEqual(bboxes, [(0.1, 0.2, 0.3, 0.4)]) self.assertIsInstance(cid, int) self.assertEqual(cid, 0) @patch.object(ImagePrep, 'load_image') - @patch.object(ImagePrep, 'load_labels', return_value=[]) - def test_empty_bb(self, _, load_image_mock): + def test_empty_bb(self, load_image_mock): load_image_mock.return_value = MagicMock() dataset = ImageDataset( pairs=self.pairs, @@ -55,9 +49,7 @@ def test_empty_bb(self, _, load_image_mock): dataset[0] @patch.object(ImagePrep, 'load_image') - @patch.object(ImagePrep, 'load_labels', - return_value=[(0.1, 0.2, 0.3, 0.4)]) - def test_no_class(self, load_labels_mock, load_image_mock): + def test_no_class(self, load_image_mock): load_image_mock.return_value = np.ones((224, 224, 3)) dataset = ImageDataset( pairs=self.pairs, diff --git a/tests/data/notyet-test_loader_factory.py b/tests/data/test_loader_factory.py similarity index 100% rename from tests/data/notyet-test_loader_factory.py rename to tests/data/test_loader_factory.py diff --git a/tests/data/notyet-test_pair_loader.py b/tests/data/test_pair_loader.py similarity index 100% rename from tests/data/notyet-test_pair_loader.py rename to tests/data/test_pair_loader.py diff --git a/tests/data/notyet-test_preprocessor.py b/tests/data/test_preprocessor.py similarity index 100% rename from tests/data/notyet-test_preprocessor.py rename to tests/data/test_preprocessor.py diff --git a/tests/data/notyet-test_scaler.py b/tests/data/test_scaler.py similarity index 100% rename from tests/data/notyet-test_scaler.py rename to tests/data/test_scaler.py diff --git a/tests/data/notyet-test_splitter.py b/tests/data/test_splitter.py similarity index 100% rename from tests/data/notyet-test_splitter.py rename to tests/data/test_splitter.py diff --git a/tests/data/notyet-test_visualizer.py b/tests/data/test_visualizer.py similarity index 100% rename from tests/data/notyet-test_visualizer.py rename to tests/data/test_visualizer.py From 944f66d0c5a0460d429835c6f5c980c9ca1bba49 Mon Sep 17 00:00:00 2001 From: Sinan Date: Sun, 25 May 2025 01:12:31 +0200 Subject: [PATCH 05/32] first model trained! --- .gitignore | 3 +- Pipfile | 3 ++ main.py | 53 +++++++++++++++---- recaptcha_classifier/__init__.py | 8 ++- recaptcha_classifier/detection_labels.py | 4 +- recaptcha_classifier/features/__init__.py | 5 ++ .../features/evaluation/__init__.py | 5 ++ .../features/evaluation/evaluate.py | 4 +- .../models/main_model/__init__.py | 5 ++ .../models/main_model/model_class.py | 11 ++-- recaptcha_classifier/train/__init__.py | 3 ++ recaptcha_classifier/train/training.py | 10 ++-- 12 files changed, 86 insertions(+), 28 deletions(-) diff --git a/.gitignore b/.gitignore index 1cdd18d520..ef3137528f 100644 --- a/.gitignore +++ b/.gitignore @@ -6,4 +6,5 @@ venv/ .env private/ -/data/ \ No newline at end of file +/data/ +/models/ \ No newline at end of file diff --git a/Pipfile b/Pipfile index 5f3f93c27c..cc9b6544d7 100644 --- a/Pipfile +++ b/Pipfile @@ -10,6 +10,7 @@ verify_ssl = true [packages] numpy = "*" +pillow= "*" opencv-python = "*" pre-commit = "*" torch = "*" @@ -18,6 +19,8 @@ torchaudio = "*" matplotlib = "*" torchmetrics = "*" tqdm = "*" +alive-progress = "*" +torcheval = "*" [dev-packages] diff --git a/main.py b/main.py index c06ce2ac04..b16cd9cf14 100644 --- a/main.py +++ b/main.py @@ -1,7 +1,11 @@ from recaptcha_classifier import ( DetectionLabels, - DataPreprocessingPipeline + DataPreprocessingPipeline, + MainCNN, + Trainer, + evaluate_model ) +import torch def main(): @@ -12,15 +16,44 @@ def main(): loaders = pipeline.run() - print("Data loaders built successfully.") - - for split, loader in loaders.items(): - print(f"{split.upper()} DataLoader:") - batch = next(iter(loader)) - images, labels = batch - print(f" - images.shape: {images.shape}") - print(f" - labels.shape: {labels.shape}") - print(f" - class IDs: {labels.tolist()}") + model = MainCNN( + n_layers=3, + kernel_size=3, + num_classes=len(DetectionLabels), + ) + + optimizer = torch.optim.Adam(model.parameters(), lr=1e-3) + scheduler = torch.optim.lr_scheduler.StepLR(optimizer, step_size=5, gamma=0.5) + device = torch.device("cuda" if torch.cuda.is_available() else "cpu") + + trainer = Trainer( + train_loader=loaders['train'], + val_loader=loaders['val'], + epochs=10, + optimizer=optimizer, + scheduler=scheduler, + save_folder='models', + device=device + ) + + trainer.train(model) + + history = trainer.loss_acc_history + print("Training completed. Loss and accuracy history:") + print(history) + + results = evaluate_model( + model=model, + test_loader=loaders['test'], + device=device, + num_classes=len(DetectionLabels), + class_names=DetectionLabels.dataset_classnames(), + plot_cm=True + ) + + print("Evaluation results:") + for key, value in results.items(): + print(f"{key}: {value}") if __name__ == '__main__': diff --git a/recaptcha_classifier/__init__.py b/recaptcha_classifier/__init__.py index c1a3c4d72c..61c583b299 100644 --- a/recaptcha_classifier/__init__.py +++ b/recaptcha_classifier/__init__.py @@ -1,9 +1,15 @@ from .models import SimpleCNN +from .models.main_model import MainCNN from .detection_labels import DetectionLabels from .data import DataPreprocessingPipeline +from .train import Trainer +from .features import evaluate_model __all__ = [ "DetectionLabels", "DataPreprocessingPipeline", - 'SimpleCNN' + 'SimpleCNN', + 'MainCNN', + 'Trainer', + 'evaluate_model' ] \ No newline at end of file diff --git a/recaptcha_classifier/detection_labels.py b/recaptcha_classifier/detection_labels.py index e4ae0311c4..f99c72e3ee 100644 --- a/recaptcha_classifier/detection_labels.py +++ b/recaptcha_classifier/detection_labels.py @@ -76,9 +76,7 @@ def from_id(cls, id: int) -> str: @classmethod def dataset_classnames(cls) -> list: """ - Returns a list of class names, only with first letter capitalized. - We use it for the pair loader, as that is the format of the folders - downloaded from the dataset. + Returns a list of class names. Returns: list: List of class names. diff --git a/recaptcha_classifier/features/__init__.py b/recaptcha_classifier/features/__init__.py index e69de29bb2..915c534014 100644 --- a/recaptcha_classifier/features/__init__.py +++ b/recaptcha_classifier/features/__init__.py @@ -0,0 +1,5 @@ +from .evaluation import evaluate_model + +__all__ = [ + "evaluate_model" +] \ No newline at end of file diff --git a/recaptcha_classifier/features/evaluation/__init__.py b/recaptcha_classifier/features/evaluation/__init__.py index e69de29bb2..95daef105c 100644 --- a/recaptcha_classifier/features/evaluation/__init__.py +++ b/recaptcha_classifier/features/evaluation/__init__.py @@ -0,0 +1,5 @@ +from .evaluate import evaluate_model + +__all__ = [ + "evaluate_model" +] \ No newline at end of file diff --git a/recaptcha_classifier/features/evaluation/evaluate.py b/recaptcha_classifier/features/evaluation/evaluate.py index 03d70f6171..031a042591 100644 --- a/recaptcha_classifier/features/evaluation/evaluate.py +++ b/recaptcha_classifier/features/evaluation/evaluate.py @@ -1,4 +1,4 @@ -import recaptcha_classifier.features.evaluation.classification_metrics as cm +from .classification_metrics import evaluate_classification import torch from tqdm import tqdm @@ -44,7 +44,7 @@ def evaluate_model(model: torch.nn.Module, y_pred = torch.cat(all_preds) y_true = torch.cat(all_targets) - class_results = cm.evaluate_classification( + class_results = evaluate_classification( y_pred=y_pred, y_true=y_true, num_classes=num_classes, diff --git a/recaptcha_classifier/models/main_model/__init__.py b/recaptcha_classifier/models/main_model/__init__.py index e69de29bb2..5417bd46f3 100644 --- a/recaptcha_classifier/models/main_model/__init__.py +++ b/recaptcha_classifier/models/main_model/__init__.py @@ -0,0 +1,5 @@ +from .model_class import MainCNN + +__all__ = [ + "MainCNN" +] \ No newline at end of file diff --git a/recaptcha_classifier/models/main_model/model_class.py b/recaptcha_classifier/models/main_model/model_class.py index 5b8a988288..9a8aca59a0 100644 --- a/recaptcha_classifier/models/main_model/model_class.py +++ b/recaptcha_classifier/models/main_model/model_class.py @@ -1,13 +1,15 @@ import torch import torch.nn as nn +from recaptcha_classifier.models.base_model import BaseModel -class MainCNN(nn.Module): # should inherit BaseModel(nn.Module) + +class MainCNN(BaseModel): def __init__(self, n_layers: int, # n_heads: int, kernel_size: int, - num_classes: int = 3, + num_classes: int, input_shape: tuple = (3, 224, 224), base_channels: int = 32 ) -> None: @@ -63,8 +65,5 @@ def forward(self, x) -> torch.Tensor: x = x.view(x.size(0), -1) x = self.classifier(x) - return x - - - + return x # ! return logits, torch.softmax(x, dim=1) if needed for uncertainty diff --git a/recaptcha_classifier/train/__init__.py b/recaptcha_classifier/train/__init__.py index e69de29bb2..3d6a085185 100644 --- a/recaptcha_classifier/train/__init__.py +++ b/recaptcha_classifier/train/__init__.py @@ -0,0 +1,3 @@ +from .training import Trainer + +__all__ = ['Trainer'] \ No newline at end of file diff --git a/recaptcha_classifier/train/training.py b/recaptcha_classifier/train/training.py index 331de15cd5..e5385372df 100644 --- a/recaptcha_classifier/train/training.py +++ b/recaptcha_classifier/train/training.py @@ -17,10 +17,10 @@ def __init__(self, epochs: int, optimizer: torch.optim.Optimizer, scheduler: torch.optim.lr_scheduler, - save_folder: str, - model_file_name='model.pt', - optimizer_file_name='optimizer.pt', - scheduler_file_name='scheduler.pt', + save_folder: str = "models", + model_file_name: str ='model.pt', + optimizer_file_name: str ='optimizer.pt', + scheduler_file_name: str ='scheduler.pt', device: torch.device |None = None ): """ @@ -66,7 +66,7 @@ def select_device(self, device=None): self.device = torch.device('cuda' if torch.cuda.is_available() else 'cpu') - def train(self, model, load_checkpoint: bool) -> None: + def train(self, model, load_checkpoint: bool = False) -> None: """ Main training loop. :param model: Model for training. From bf67dad7b6fb74868d7c015ba56cc66271ed4208 Mon Sep 17 00:00:00 2001 From: Sinan Date: Sun, 25 May 2025 01:38:48 +0200 Subject: [PATCH 06/32] first complete training!!! --- evaluation_results.json | 0 main.py | 3 +- recaptcha_classifier/data/__init__.py | 8 +- recaptcha_classifier/data/augment.py | 69 ++---------------- recaptcha_classifier/data/pipeline.py | 14 ++-- .../evaluation/classification_metrics.py | 11 ++- .../models/main_model/model_class.py | 2 +- reports/figures/first_training.png | Bin 0 -> 77959 bytes 8 files changed, 25 insertions(+), 82 deletions(-) create mode 100644 evaluation_results.json create mode 100644 reports/figures/first_training.png diff --git a/evaluation_results.json b/evaluation_results.json new file mode 100644 index 0000000000..e69de29bb2 diff --git a/main.py b/main.py index b16cd9cf14..f7e2a0808a 100644 --- a/main.py +++ b/main.py @@ -17,7 +17,7 @@ def main(): loaders = pipeline.run() model = MainCNN( - n_layers=3, + n_layers=2,#3, kernel_size=3, num_classes=len(DetectionLabels), ) @@ -55,6 +55,5 @@ def main(): for key, value in results.items(): print(f"{key}: {value}") - if __name__ == '__main__': main() diff --git a/recaptcha_classifier/data/__init__.py b/recaptcha_classifier/data/__init__.py index b4dde44d2b..ab71a0435a 100644 --- a/recaptcha_classifier/data/__init__.py +++ b/recaptcha_classifier/data/__init__.py @@ -6,11 +6,7 @@ from .loader_factory import LoaderFactory from .dataset import ImageDataset from .preprocessor import ImagePrep -from .augment import ( - AugmentationPipeline, - HorizontalFlip, - RandomRotation -) +from .augment import AugmentationPipeline from .collate_batch import collate_batch @@ -33,8 +29,6 @@ "ImageDataset", "ImagePrep", "AugmentationPipeline", - "HorizontalFlip", - "RandomRotation", # Methods "collate_batch", # Types diff --git a/recaptcha_classifier/data/augment.py b/recaptcha_classifier/data/augment.py index 1dcef38bbf..2ee09cfc16 100644 --- a/recaptcha_classifier/data/augment.py +++ b/recaptcha_classifier/data/augment.py @@ -1,41 +1,20 @@ -import random -from abc import ABC, abstractmethod -from PIL import Image from typing import List from .types import LoadedImg - - -class Augmentation(ABC): - """Abstract class for data augmentation.""" - @abstractmethod - def augment(self, - image: LoadedImg, - annotations: List) -> LoadedImg: - """ - Apply the transformation of the image and updates the bounding boxes - if necessary. - - Args: - image (LoadedImg): The image to be augmented. - annotations (List): List of annotations associated with the image. - - Returns: - LoadedImg: The augmented image and the updated - annotations. - """ - pass +from torchvision import transforms class AugmentationPipeline: """Class to manage a series of augmentations in sequence.""" - def __init__(self, transforms=[]) -> None: - self._transforms: List[Augmentation] = transforms + def __init__(self, transforms_list: List) -> None: + """ + Initializes the augmentation pipeline with a list of transformations. + """ + self._pipeline = transforms.Compose(transforms_list) def apply_transforms(self, image: LoadedImg) -> LoadedImg: """ - Apply all transformations in the pipeline to the image and - annotations. + Apply all transformations in the pipeline to the image. Args: image (LoadedImg): The image to be augmented. @@ -43,36 +22,4 @@ def apply_transforms(self, Returns: LoadedImg: The augmented image. """ - for transform in self._transforms: - if hasattr(transform, 'prob') and random.random() > transform.prob: - continue - image = transform.augment(image) - return image - - -class HorizontalFlip(Augmentation): - """Flips the image horizontally, with probability p.""" - def __init__(self, p: float = 0.5) -> None: - self.prob = p - - def augment(self, - image: LoadedImg) -> LoadedImg: - flipped = image.transpose(Image.FLIP_LEFT_RIGHT) - - return flipped - - -class RandomRotation(Augmentation): - """ - Rotates the image by a random angle. - """ - def __init__(self, degrees: float = 30.0, p: float = 0.5) -> None: - self._degrees = degrees - self.prob = p - - def augment(self, - image: LoadedImg) -> LoadedImg: - angle = random.uniform(-self._degrees, self._degrees) - - rotated = image.rotate(angle) - return rotated + return self._pipeline(image) \ No newline at end of file diff --git a/recaptcha_classifier/data/pipeline.py b/recaptcha_classifier/data/pipeline.py index 612328a8f2..0f113a09ef 100644 --- a/recaptcha_classifier/data/pipeline.py +++ b/recaptcha_classifier/data/pipeline.py @@ -6,11 +6,8 @@ from .splitter import DataSplitter from .visualizer import Visualizer from .preprocessor import ImagePrep -from .augment import ( - AugmentationPipeline, - HorizontalFlip, - RandomRotation -) +from .augment import AugmentationPipeline +from torchvision import transforms from .loader_factory import LoaderFactory @@ -78,8 +75,11 @@ def _build_augmentator(self) -> AugmentationPipeline: AugmentationPipeline: The augmentation pipeline. """ return AugmentationPipeline([ - HorizontalFlip(p=0.5), - RandomRotation(degrees=30, p=0.5) + transforms.RandomHorizontalFlip(p=0.5), + transforms.RandomRotation(degrees=15), + transforms.ColorJitter(brightness=0.3, contrast=0.3, saturation=0.3), + transforms.RandomResizedCrop(size=(224, 224), scale=(0.8, 1.0)), + transforms.GaussianBlur(kernel_size=(5, 9), sigma=(0.1, 2.0)), ]) def run(self) -> Dict[str, DataLoader]: diff --git a/recaptcha_classifier/features/evaluation/classification_metrics.py b/recaptcha_classifier/features/evaluation/classification_metrics.py index 0eef54865b..f72741910d 100644 --- a/recaptcha_classifier/features/evaluation/classification_metrics.py +++ b/recaptcha_classifier/features/evaluation/classification_metrics.py @@ -11,7 +11,8 @@ def evaluate_classification(y_pred: Tensor, num_classes: int, average: str = 'macro', cm_plot: bool = True, - class_names: Optional[list[str]] = None) -> dict: + class_names: Optional[list[str]] = None, + ) -> dict: """ Evaluate classification model using torchmetrics @@ -37,10 +38,12 @@ def evaluate_classification(y_pred: Tensor, # Convert logits to predicted labels y_pred = torch.argmax(y_pred, dim=1) + device = torch.device("cuda" if torch.cuda.is_available() else "cpu") + # Initialize Metrics - acc = Accuracy(task="multiclass", num_classes=num_classes) - f1 = F1Score(task="multiclass", num_classes=num_classes, average=average) - confmat = MulticlassConfusionMatrix(num_classes=num_classes) + acc = Accuracy(task="multiclass", num_classes=num_classes).to(device) + f1 = F1Score(task="multiclass", num_classes=num_classes, average=average).to(device) + confmat = MulticlassConfusionMatrix(num_classes=num_classes).to(device) # Compute metrics acc_val = acc(y_pred, y_true) diff --git a/recaptcha_classifier/models/main_model/model_class.py b/recaptcha_classifier/models/main_model/model_class.py index 9a8aca59a0..ae13a81152 100644 --- a/recaptcha_classifier/models/main_model/model_class.py +++ b/recaptcha_classifier/models/main_model/model_class.py @@ -11,7 +11,7 @@ def __init__(self, kernel_size: int, num_classes: int, input_shape: tuple = (3, 224, 224), - base_channels: int = 32 + base_channels: int = 16 #32 ) -> None: super().__init__() diff --git a/reports/figures/first_training.png b/reports/figures/first_training.png new file mode 100644 index 0000000000000000000000000000000000000000..dedddc444b5be1d1faac7279c43325cced13e538 GIT binary patch literal 77959 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 bD-`$GzK(dtiV5ZR(!h_Yp`}47&V}?J7T)Q; literal 0 HcmV?d00001 From 29157176409e88f226b2f7b1b4bae0a16687f95f Mon Sep 17 00:00:00 2001 From: Sinan Date: Mon, 26 May 2025 15:59:14 +0200 Subject: [PATCH 07/32] small diffs --- main.py | 15 +++++++-------- recaptcha_classifier/__init__.py | 3 ++- recaptcha_classifier/models/__init__.py | 3 ++- .../models/main_model/__init__.py | 4 +++- .../models/main_model/model_class.py | 2 +- 5 files changed, 15 insertions(+), 12 deletions(-) diff --git a/main.py b/main.py index f7e2a0808a..88e4a3034b 100644 --- a/main.py +++ b/main.py @@ -17,19 +17,19 @@ def main(): loaders = pipeline.run() model = MainCNN( - n_layers=2,#3, + n_layers=2, kernel_size=3, num_classes=len(DetectionLabels), ) - optimizer = torch.optim.Adam(model.parameters(), lr=1e-3) + optimizer = torch.optim.RAdam(model.parameters(), lr=1e-3) scheduler = torch.optim.lr_scheduler.StepLR(optimizer, step_size=5, gamma=0.5) device = torch.device("cuda" if torch.cuda.is_available() else "cpu") trainer = Trainer( train_loader=loaders['train'], val_loader=loaders['val'], - epochs=10, + epochs=15, optimizer=optimizer, scheduler=scheduler, save_folder='models', @@ -38,11 +38,13 @@ def main(): trainer.train(model) + trainer.delete_checkpoints() + history = trainer.loss_acc_history print("Training completed. Loss and accuracy history:") print(history) - results = evaluate_model( + evaluate_model( model=model, test_loader=loaders['test'], device=device, @@ -50,10 +52,7 @@ def main(): class_names=DetectionLabels.dataset_classnames(), plot_cm=True ) - - print("Evaluation results:") - for key, value in results.items(): - print(f"{key}: {value}") + if __name__ == '__main__': main() diff --git a/recaptcha_classifier/__init__.py b/recaptcha_classifier/__init__.py index 61c583b299..792c58996c 100644 --- a/recaptcha_classifier/__init__.py +++ b/recaptcha_classifier/__init__.py @@ -1,5 +1,5 @@ from .models import SimpleCNN -from .models.main_model import MainCNN +from .models.main_model import MainCNN, HPOptimizer from .detection_labels import DetectionLabels from .data import DataPreprocessingPipeline from .train import Trainer @@ -10,6 +10,7 @@ "DataPreprocessingPipeline", 'SimpleCNN', 'MainCNN', + 'HPOptimizer', 'Trainer', 'evaluate_model' ] \ No newline at end of file diff --git a/recaptcha_classifier/models/__init__.py b/recaptcha_classifier/models/__init__.py index 5f6908ea92..735c490da1 100644 --- a/recaptcha_classifier/models/__init__.py +++ b/recaptcha_classifier/models/__init__.py @@ -1,3 +1,4 @@ from .simple_classifier_model import SimpleCNN +from .main_model import MainCNN, HPOptimizer -__all__ = ['SimpleCNN'] +__all__ = ['SimpleCNN', 'MainCNN', 'HPOptimizer'] diff --git a/recaptcha_classifier/models/main_model/__init__.py b/recaptcha_classifier/models/main_model/__init__.py index 5417bd46f3..405ca63fb8 100644 --- a/recaptcha_classifier/models/main_model/__init__.py +++ b/recaptcha_classifier/models/main_model/__init__.py @@ -1,5 +1,7 @@ from .model_class import MainCNN +from .HPoptimizer import HPOptimizer __all__ = [ - "MainCNN" + "MainCNN", + "HPOptimizer" ] \ No newline at end of file diff --git a/recaptcha_classifier/models/main_model/model_class.py b/recaptcha_classifier/models/main_model/model_class.py index ae13a81152..9a8aca59a0 100644 --- a/recaptcha_classifier/models/main_model/model_class.py +++ b/recaptcha_classifier/models/main_model/model_class.py @@ -11,7 +11,7 @@ def __init__(self, kernel_size: int, num_classes: int, input_shape: tuple = (3, 224, 224), - base_channels: int = 16 #32 + base_channels: int = 32 ) -> None: super().__init__() From dba0789e2a29132ceea548b9776a50b0fda7c0e1 Mon Sep 17 00:00:00 2001 From: Sinan Date: Mon, 26 May 2025 16:19:20 +0200 Subject: [PATCH 08/32] solved unit tests for other parts of code --- Pipfile | 1 + .../features/evaluation/classification_metrics.py | 6 +++++- recaptcha_classifier/models/main_model/HPoptimizer.py | 4 +++- tests/models/test_training.py | 1 + 4 files changed, 10 insertions(+), 2 deletions(-) diff --git a/Pipfile b/Pipfile index cc9b6544d7..c6a1cb4dc6 100644 --- a/Pipfile +++ b/Pipfile @@ -10,6 +10,7 @@ verify_ssl = true [packages] numpy = "*" +pandas = "*" pillow= "*" opencv-python = "*" pre-commit = "*" diff --git a/recaptcha_classifier/features/evaluation/classification_metrics.py b/recaptcha_classifier/features/evaluation/classification_metrics.py index f72741910d..319ba70ecc 100644 --- a/recaptcha_classifier/features/evaluation/classification_metrics.py +++ b/recaptcha_classifier/features/evaluation/classification_metrics.py @@ -37,8 +37,12 @@ def evaluate_classification(y_pred: Tensor, # Convert logits to predicted labels y_pred = torch.argmax(y_pred, dim=1) - device = torch.device("cuda" if torch.cuda.is_available() else "cpu") + y_pred = y_pred.to(device) + y_true = y_true.to(device) + + if num_classes <= 0: + raise ValueError("num_classes must be a positive integer") # Initialize Metrics acc = Accuracy(task="multiclass", num_classes=num_classes).to(device) diff --git a/recaptcha_classifier/models/main_model/HPoptimizer.py b/recaptcha_classifier/models/main_model/HPoptimizer.py index 850c2629fd..84f922990a 100644 --- a/recaptcha_classifier/models/main_model/HPoptimizer.py +++ b/recaptcha_classifier/models/main_model/HPoptimizer.py @@ -6,6 +6,8 @@ from recaptcha_classifier.models.main_model.model_class import MainCNN from recaptcha_classifier.train.training import Trainer +from recaptcha_classifier.detection_labels import DetectionLabels + class HPOptimizer(object): """Class for optimizing hyperparameters.""" @@ -59,7 +61,7 @@ def optimize_hyperparameters(self, def _train_one_model(self, hp_combo) -> None: - model = MainCNN(n_layers=int(hp_combo[0]), kernel_size=int(hp_combo[1])) + model = MainCNN(n_layers=int(hp_combo[0]), kernel_size=int(hp_combo[1]), num_classes=len(DetectionLabels.all())) self._trainer.optimizer = torch.optim.RAdam(model.parameters(), lr=hp_combo[2]) self._trainer.train(model=model, load_checkpoint=False) diff --git a/tests/models/test_training.py b/tests/models/test_training.py index f89b7aac1c..c5a55ea415 100644 --- a/tests/models/test_training.py +++ b/tests/models/test_training.py @@ -49,6 +49,7 @@ def test_train_process(self) -> None: def test_train_load_checkpoint(self): + self.trainer.train(model=self.model, load_checkpoint=False) self.trainer.train(model=self.model, load_checkpoint=True) assert_equal(os.path.exists(os.path.join(self.trainer.save_folder, self.trainer.model_file_name)), True, From 88f31ba5ba85635f6dd2b2bcf0416dac8b77e7e0 Mon Sep 17 00:00:00 2001 From: Sinan Date: Mon, 26 May 2025 16:55:50 +0200 Subject: [PATCH 09/32] fixed data preproc tests --- tests/data/test_augment.py | 29 +++-------- tests/data/test_dataset.py | 28 ++++------- tests/data/test_loader_factory.py | 19 +++---- tests/data/test_pair_loader.py | 83 ------------------------------- tests/data/test_paths_loader.py | 35 +++++++++++++ tests/data/test_preprocessor.py | 7 --- tests/data/test_scaler.py | 20 -------- tests/data/test_splitter.py | 2 +- tests/data/test_visualizer.py | 12 ++--- 9 files changed, 64 insertions(+), 171 deletions(-) delete mode 100644 tests/data/test_pair_loader.py create mode 100644 tests/data/test_paths_loader.py delete mode 100644 tests/data/test_scaler.py diff --git a/tests/data/test_augment.py b/tests/data/test_augment.py index 667a2681c8..de6eeef074 100644 --- a/tests/data/test_augment.py +++ b/tests/data/test_augment.py @@ -2,11 +2,8 @@ import numpy as np from unittest.mock import patch from PIL import Image -from recaptcha_classifier.data.augment import ( - AugmentationPipeline, - HorizontalFlip, - RandomRotation -) +from recaptcha_classifier.data.augment import AugmentationPipeline +from torchvision import transforms class TestAugmentation(unittest.TestCase): @@ -17,25 +14,11 @@ def setUp(self): ) self.img = self.img.convert("RGB") - def test_horizontal_flip(self): - aug = HorizontalFlip(p=1.0) - flipped_img = aug.augment(self.img) - - self.assertFalse(np.array_equal(np.array(flipped_img), - np.array(self.img))) - - @patch('random.uniform', return_value=30) - def test_random_rotation(self, _): - augmenter = RandomRotation(degrees=30) - rotated_img = augmenter.augment(self.img) - - self.assertFalse(np.array_equal(np.array(rotated_img), - np.array(self.img))) - def test_pipeline(self): - pipeline = AugmentationPipeline() - pipeline.add_transform(HorizontalFlip(p=1.0)) - pipeline.add_transform(RandomRotation(degrees=30)) + pipeline = AugmentationPipeline([ + transforms.RandomHorizontalFlip(p=1.0), + transforms.RandomRotation(degrees=30) + ]) new_img = pipeline.apply_transforms(self.img) diff --git a/tests/data/test_dataset.py b/tests/data/test_dataset.py index 9b8bedc5b8..1fd9118067 100644 --- a/tests/data/test_dataset.py +++ b/tests/data/test_dataset.py @@ -10,20 +10,22 @@ class TestImageDataset(unittest.TestCase): def setUp(self): - self.images = [Path("data/images/c1/i1.png")] + self.items = [Path("data/c1/i1.png")] self.class_map = {"c1": 0} self.preprocessor = ImagePrep() - self.augmentator = AugmentationPipeline() + self.augmentator = AugmentationPipeline(transforms_list=[]) @patch.object(ImagePrep, 'load_image') @patch.object(ImagePrep, 'to_tensor') - def test_loading(self, to_tensor_mock, load_image_mock): + @patch.object(ImagePrep, 'class_id_to_tensor') + def test_loading(self, class_id_mock, to_tensor_mock, load_image_mock): # Mock the return values load_image_mock.return_value = MagicMock() to_tensor_mock.return_value = torch.rand(3, 224, 224) # expect shape + class_id_mock.return_value = torch.tensor(0) dataset = ImageDataset( - pairs=self.pairs, + items=self.items, preprocessor=self.preprocessor, augmentator=self.augmentator, class_map=self.class_map @@ -33,26 +35,14 @@ def test_loading(self, to_tensor_mock, load_image_mock): self.assertIsInstance(tensor, torch.Tensor) self.assertEqual(tensor.shape, (3, 224, 224)) - self.assertIsInstance(cid, int) - self.assertEqual(cid, 0) - - @patch.object(ImagePrep, 'load_image') - def test_empty_bb(self, load_image_mock): - load_image_mock.return_value = MagicMock() - dataset = ImageDataset( - pairs=self.pairs, - preprocessor=self.preprocessor, - augmentator=self.augmentator, - class_map=self.class_map - ) - with self.assertRaises(ValueError): - dataset[0] + self.assertIsInstance(cid, torch.Tensor) + self.assertEqual(cid.item(), 0) @patch.object(ImagePrep, 'load_image') def test_no_class(self, load_image_mock): load_image_mock.return_value = np.ones((224, 224, 3)) dataset = ImageDataset( - pairs=self.pairs, + items=self.items, preprocessor=self.preprocessor, augmentator=self.augmentator, ) diff --git a/tests/data/test_loader_factory.py b/tests/data/test_loader_factory.py index cab1187202..18f768b64e 100644 --- a/tests/data/test_loader_factory.py +++ b/tests/data/test_loader_factory.py @@ -12,7 +12,7 @@ class TestLoaderFactory(unittest.TestCase): def test_create_loaders(self, dataset_mock): class_map = {"class1": 0, "class2": 1} preprocessor = ImagePrep() - augmentator = AugmentationPipeline() + augmentator = AugmentationPipeline(transforms_list=[]) factory = LoaderFactory(class_map, preprocessor, augmentator) dataset_mock.return_value = dataset_mock @@ -20,20 +20,17 @@ def test_create_loaders(self, dataset_mock): splits = { "train": { - "class1": [(Path("img1.png"), Path("label1.txt")), - (Path("img2.png"), Path("label2.txt"))], - "class2": [(Path("img3.png"), Path("label3.txt"))] + "class1": [Path("img1.png"), Path("img2.png")], }, "val": { - "class1": [(Path("img4.png"), Path("label4.txt"))], - "class2": [(Path("img5.png"), Path("label5.txt"))] + "class1": + [Path("img3.png")], }, "test": { - "class1": [(Path("img6.png"), Path("label6.txt"))], - "class2": [(Path("img7.png"), Path("label7.txt"))] + "class1": [Path("img4.png"), Path("img5.png"), Path("img6.png")] } } - + loaders = factory.create_loaders(splits) self.assertIn("train", loaders) @@ -45,9 +42,7 @@ def test_create_loaders(self, dataset_mock): # We also need to check that the train set has the augmentator dataset_mock.assert_any_call( - pairs=[(Path("img1.png"), Path("label1.txt")), - (Path("img2.png"), Path("label2.txt")), - (Path("img3.png"), Path("label3.txt"))], + items=[Path("img1.png"),Path("img2.png")], preprocessor=preprocessor, augmentator=augmentator, class_map=class_map diff --git a/tests/data/test_pair_loader.py b/tests/data/test_pair_loader.py deleted file mode 100644 index e7ae89a983..0000000000 --- a/tests/data/test_pair_loader.py +++ /dev/null @@ -1,83 +0,0 @@ -import unittest -from pathlib import Path -from unittest.mock import patch - -from recaptcha_classifier.data.pair_loader import ImagePathsLoader - - -class TestImagePathsLoader(unittest.TestCase): - @patch("recaptcha_classifier.data.pair_loader.Path.glob") - @patch("recaptcha_classifier.data.pair_loader.Path.exists", - return_value=True) - @patch("recaptcha_classifier.data.pair_loader.Path.is_dir", - return_value=True) - def test_load_pairs_with_all_labels(self, - is_dir_mock, - exists_mock, - glob_mock): - glob_mock.return_value = [ - Path("data/images/class1/img1.png"), - Path("data/images/class1/img2.png"), - ] # 2 images found in the png glob - - loader = ImagePathsLoader(["class1"]) - pairs = loader.find_image_paths() - - expected_pairs = { - (Path("data/images/class1/img1.png"), - Path("data/labels/class1/img1.txt")), - (Path("data/images/class1/img2.png"), - Path("data/labels/class1/img2.txt")), - } - - self.assertEqual(set(pairs["class1"]), expected_pairs) - self.assertEqual(len(pairs["class1"]), 2) - - @patch("recaptcha_classifier.data.pair_loader.Path.glob") - @patch("recaptcha_classifier.data.pair_loader.Path.exists") - @patch("recaptcha_classifier.data.pair_loader.Path.is_dir", - return_value=True) - def test_load_pairs_with_missing_labels(self, - is_dir_mock, - exists_mock, - glob_mock): - glob_mock.return_value = [ - Path("data/images/class1/img1.png"), - Path("data/images/class1/img2.png"), - ] # 2 images found in the png glob - - # label folder exists, img1.txt exists but img2.txt does not - exists_mock.side_effect = [True, True, False] - - loader = ImagePathsLoader(["class1"]) - pairs = loader.find_image_paths() - - self.assertIn("class1", pairs) - expected_pair = { - (Path("data/images/class1/img1.png"), - Path("data/labels/class1/img1.txt")), - } - - self.assertEqual(set(pairs["class1"]), expected_pair) - self.assertEqual(len(pairs["class1"]), 1) - - @patch("recaptcha_classifier.data.pair_loader.Path.glob") - @patch("recaptcha_classifier.data.pair_loader.Path.exists") - @patch("recaptcha_classifier.data.pair_loader.Path.is_dir") - def test_caching(self, is_dir_mock, exists_mock, glob_mock): - loader = ImagePathsLoader(["class1"]) - loader._pairs = {"test": [(Path("test.png"), Path("test.txt"))]} - - # if any mocked method is called, then the cache is not used - for method in (is_dir_mock, exists_mock, glob_mock): - method.side_effect = AssertionError(f"{method} called, error!") - - pairs = loader.find_image_paths() - - self.assertIs(pairs, loader._pairs) - self.assertEqual(pairs, {"test": [(Path("test.png"), - Path("test.txt"))]}) - - -if __name__ == "__main__": - unittest.main() diff --git a/tests/data/test_paths_loader.py b/tests/data/test_paths_loader.py new file mode 100644 index 0000000000..3eebc66e82 --- /dev/null +++ b/tests/data/test_paths_loader.py @@ -0,0 +1,35 @@ +import unittest +from pathlib import Path +from unittest.mock import patch + +from recaptcha_classifier.data.paths_loader import ImagePathsLoader + + +class TestImagePathsLoader(unittest.TestCase): + @patch("recaptcha_classifier.data.paths_loader.Path.glob") + @patch("recaptcha_classifier.data.paths_loader.Path.exists", + return_value=True) + @patch("recaptcha_classifier.data.paths_loader.Path.is_dir", + return_value=True) + def test_load_paths(self, + is_dir_mock, + exists_mock, + glob_mock): + glob_mock.return_value = [ + Path("data/images/class1/img1.png"), + Path("data/images/class1/img2.png"), + ] # 2 images found in the png glob + + loader = ImagePathsLoader(["class1"]) + pairs = loader.find_image_paths() + + expected_pairs = { + Path("data/images/class1/img1.png"), + Path("data/images/class1/img2.png"), + } + + self.assertEqual(set(pairs["class1"]), expected_pairs) + self.assertEqual(len(pairs["class1"]), 2) + +if __name__ == "__main__": + unittest.main() diff --git a/tests/data/test_preprocessor.py b/tests/data/test_preprocessor.py index daec7a0f05..9f533df829 100644 --- a/tests/data/test_preprocessor.py +++ b/tests/data/test_preprocessor.py @@ -24,13 +24,6 @@ def test_load_image(self, open_mock): img_mock.convert.assert_called_once_with("RGB") resized_mock.resize.assert_called_once_with((224, 224), Image.LANCZOS) - @patch("builtins.open", new_callable=unittest.mock.mock_open, - read_data="0 0.2 0.3 0.4 0.5\n") - def test_load_labels(self, file_mock): - result = self.prep.load_labels(Path("test")) - - self.assertEqual(result, [(0.2, 0.3, 0.4, 0.5)]) - def test_to_tensor(self): img = Image.new("RGB", (224, 224)) tensor = self.prep.to_tensor(img) diff --git a/tests/data/test_scaler.py b/tests/data/test_scaler.py deleted file mode 100644 index 9a54dd26d4..0000000000 --- a/tests/data/test_scaler.py +++ /dev/null @@ -1,20 +0,0 @@ -import unittest -from recaptcha_classifier.data.scaler import YOLOScaler - - -class TestYOLOScaler(unittest.TestCase): - def test_scale_for_flip(self): - bb = [(0.5, 0.5, 0.2, 0.2), (0.3, 0.4, 0.1, 0.1)] - flipped = YOLOScaler.scale_for_flip(bb) - self.assertEqual(flipped, [(0.5, 0.5, 0.2, 0.2), - (0.7, 0.4, 0.1, 0.1)]) - - def test_scale_for_rotation(self): - bb = [(0.5, 0.5, 0.4, 0.2)] - rot = YOLOScaler.scale_for_rotation(bb, 90, (224, 224)) - self.assertEqual(len(rot), 1) - self.assertTrue(all(0 <= v <= 1 for bb in rot for v in bb)) - - def test_empty_list(self): - self.assertEqual(YOLOScaler.scale_for_flip([]), []) - self.assertEqual(YOLOScaler.scale_for_rotation([], 45, (224, 224)), []) diff --git a/tests/data/test_splitter.py b/tests/data/test_splitter.py index 406b04dc5f..10796cc6d0 100644 --- a/tests/data/test_splitter.py +++ b/tests/data/test_splitter.py @@ -4,7 +4,7 @@ class TestDataSplitter(unittest.TestCase): def test_default_split(self): - data = {"test_class": [(f"img_{i}", f"label_{i}") for i in range(10)]} + data = {"test_class": [f"img_{i}" for i in range(10)]} splits = DataSplitter().split(data) self.assertEqual(len(splits['train']['test_class']), 7) diff --git a/tests/data/test_visualizer.py b/tests/data/test_visualizer.py index 43096a5805..d4f0d9e8e8 100644 --- a/tests/data/test_visualizer.py +++ b/tests/data/test_visualizer.py @@ -8,16 +8,16 @@ def setUp(self): # only written pairs as string, instead of paths, for simplicity self.sample_splits = { "train": { - "class1": [("img1", "label1"), ("img2", "label2")], - "class2": [("img3", "label3"), ("img4", "label4")], + "class1": ["img1", "img2"], + "class2": ["img3", "img4"], }, "val": { - "class1": [("img5", "label5")], - "class2": [("img6", "label6")], + "class1": ["img5"], + "class2": ["img6"], }, "test": { - "class1": [("img7", "label7")], - "class2": [("img8", "label8")], + "class1": ["img7"], + "class2": ["img8"], }, } From b357308ef07f6e60f4febd449c3e82a4203b0757 Mon Sep 17 00:00:00 2001 From: Sinan Date: Mon, 26 May 2025 17:15:22 +0200 Subject: [PATCH 10/32] setup integration tests --- .github/workflows/ci.yml | 32 +++++++++++++ .pre-commit-config.yaml | 2 +- recaptcha_classifier/data/pipeline.py | 2 +- tests/integration/test_checkpoint.py | 48 +++++++++++++++++++ tests/integration/test_data_pipeline.py | 13 +++++ tests/{ => unit}/data/__init__.py | 0 tests/{ => unit}/data/test_augment.py | 0 tests/{ => unit}/data/test_dataset.py | 0 tests/{ => unit}/data/test_loader_factory.py | 0 tests/{ => unit}/data/test_paths_loader.py | 0 tests/{ => unit}/data/test_preprocessor.py | 0 tests/{ => unit}/data/test_splitter.py | 0 tests/{ => unit}/data/test_visualizer.py | 0 tests/{ => unit}/features/__init__.py | 0 .../features/test_classification_metrics.py | 0 tests/{ => unit}/features/test_evaluate.py | 0 tests/{ => unit}/models/__init__.py | 0 tests/{ => unit}/models/test_HPoptimizer.py | 0 tests/{ => unit/models}/test_classifier.py | 0 tests/{ => unit}/models/test_training.py | 0 tests/{ => unit}/models/utils_training_hpo.py | 0 21 files changed, 95 insertions(+), 2 deletions(-) create mode 100644 .github/workflows/ci.yml create mode 100644 tests/integration/test_checkpoint.py create mode 100644 tests/integration/test_data_pipeline.py rename tests/{ => unit}/data/__init__.py (100%) rename tests/{ => unit}/data/test_augment.py (100%) rename tests/{ => unit}/data/test_dataset.py (100%) rename tests/{ => unit}/data/test_loader_factory.py (100%) rename tests/{ => unit}/data/test_paths_loader.py (100%) rename tests/{ => unit}/data/test_preprocessor.py (100%) rename tests/{ => unit}/data/test_splitter.py (100%) rename tests/{ => unit}/data/test_visualizer.py (100%) rename tests/{ => unit}/features/__init__.py (100%) rename tests/{ => unit}/features/test_classification_metrics.py (100%) rename tests/{ => unit}/features/test_evaluate.py (100%) rename tests/{ => unit}/models/__init__.py (100%) rename tests/{ => unit}/models/test_HPoptimizer.py (100%) rename tests/{ => unit/models}/test_classifier.py (100%) rename tests/{ => unit}/models/test_training.py (100%) rename tests/{ => unit}/models/utils_training_hpo.py (100%) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml new file mode 100644 index 0000000000..88812b3b37 --- /dev/null +++ b/.github/workflows/ci.yml @@ -0,0 +1,32 @@ +name: Integration Tests + +on: + pull_request: + branches: + - main + types: + - opened + - synchronize + - reopened + +jobs: + integration-tests: + runs-on: ubuntu-latest + steps: + - name: Checkout code + uses: actions/checkout@v4 + + - name: Set up Python + uses: actions/setup-python@v5 + with: + python-version: '3.10' + + - name: Install dependencies + run: | + python -m pip install --upgrade pip + pip install pipenv + pipenv install --dev + + - name: Run integration tests + run: | + pipenv run python -m unittest discover -s tests/integration \ No newline at end of file diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 5d2bc28c2b..242328f4f0 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -9,6 +9,6 @@ repos: hooks: - id: run-unittests name: Run unittests - entry: pipenv run python -m unittest discover tests + entry: pipenv run python -m unittest discover tests/unit language: system pass_filenames: false diff --git a/recaptcha_classifier/data/pipeline.py b/recaptcha_classifier/data/pipeline.py index 0f113a09ef..46a489f795 100644 --- a/recaptcha_classifier/data/pipeline.py +++ b/recaptcha_classifier/data/pipeline.py @@ -35,7 +35,7 @@ def __init__(self, seed: int = 23, # our group number batch_size: int = 32, num_workers: int = 4, - balance: bool = False, + balance: bool = True, show_plots: bool = False) -> None: """ Initializes the DataPreprocessingPipeline with the given parameters. diff --git a/tests/integration/test_checkpoint.py b/tests/integration/test_checkpoint.py new file mode 100644 index 0000000000..91c7294d09 --- /dev/null +++ b/tests/integration/test_checkpoint.py @@ -0,0 +1,48 @@ +import unittest +import torch +import os +from recaptcha_classifier.models.main_model import MainCNN +from recaptcha_classifier.train.training import Trainer +from recaptcha_classifier.data.pipeline import DataPreprocessingPipeline +from recaptcha_classifier.detection_labels import DetectionLabels + + +class TestCheckpointIntegration(unittest.TestCase): + def test_checkpoint_save_load(self): + pipeline = DataPreprocessingPipeline( + DetectionLabels, + batch_size=2, + num_workers=0 + ) + + loaders = pipeline.run() + + model = MainCNN(n_layers=1, kernel_size=3, num_classes=len(DetectionLabels)) + optimizer = torch.optim.Adam(model.parameters()) + scheduler = torch.optim.lr_scheduler.StepLR(optimizer, step_size=1) + device = torch.device("cpu") + + trainer = Trainer( + train_loader=loaders["train"], + val_loader=loaders["val"], + optimizer=optimizer, + model=model, + scheduler=scheduler, + device=device, + epochs=1, + ) + + trainer.train(model) + + model_state = {k: v.clone() for k, v in model.state_dict().items()} + + model_new = MainCNN(n_layers=1, kernel_size=3, num_classes=len(DetectionLabels)) + + trainer.load_checkpoint_states(model_new) + + for key in model_state: + self.assertTrue( + torch.equal(model_state[key], model_new.state_dict()[key]) + ) + + trainer.delete_checkpoints() \ No newline at end of file diff --git a/tests/integration/test_data_pipeline.py b/tests/integration/test_data_pipeline.py new file mode 100644 index 0000000000..4322638e2f --- /dev/null +++ b/tests/integration/test_data_pipeline.py @@ -0,0 +1,13 @@ +import unittest + +from recaptcha_classifier.data.pipeline import DataPreprocessingPipeline +from recaptcha_classifier.detection_labels import DetectionLabels + + +class TestDataPipeline(unittest.TestCase): + def test_pipeline_runs(self): + pipeline = DataPreprocessingPipeline(DetectionLabels) + loaders = pipeline.run() + + self.assertIn("train", loaders) + self.assertGreater(len(loaders["train"]), 0) \ No newline at end of file diff --git a/tests/data/__init__.py b/tests/unit/data/__init__.py similarity index 100% rename from tests/data/__init__.py rename to tests/unit/data/__init__.py diff --git a/tests/data/test_augment.py b/tests/unit/data/test_augment.py similarity index 100% rename from tests/data/test_augment.py rename to tests/unit/data/test_augment.py diff --git a/tests/data/test_dataset.py b/tests/unit/data/test_dataset.py similarity index 100% rename from tests/data/test_dataset.py rename to tests/unit/data/test_dataset.py diff --git a/tests/data/test_loader_factory.py b/tests/unit/data/test_loader_factory.py similarity index 100% rename from tests/data/test_loader_factory.py rename to tests/unit/data/test_loader_factory.py diff --git a/tests/data/test_paths_loader.py b/tests/unit/data/test_paths_loader.py similarity index 100% rename from tests/data/test_paths_loader.py rename to tests/unit/data/test_paths_loader.py diff --git a/tests/data/test_preprocessor.py b/tests/unit/data/test_preprocessor.py similarity index 100% rename from tests/data/test_preprocessor.py rename to tests/unit/data/test_preprocessor.py diff --git a/tests/data/test_splitter.py b/tests/unit/data/test_splitter.py similarity index 100% rename from tests/data/test_splitter.py rename to tests/unit/data/test_splitter.py diff --git a/tests/data/test_visualizer.py b/tests/unit/data/test_visualizer.py similarity index 100% rename from tests/data/test_visualizer.py rename to tests/unit/data/test_visualizer.py diff --git a/tests/features/__init__.py b/tests/unit/features/__init__.py similarity index 100% rename from tests/features/__init__.py rename to tests/unit/features/__init__.py diff --git a/tests/features/test_classification_metrics.py b/tests/unit/features/test_classification_metrics.py similarity index 100% rename from tests/features/test_classification_metrics.py rename to tests/unit/features/test_classification_metrics.py diff --git a/tests/features/test_evaluate.py b/tests/unit/features/test_evaluate.py similarity index 100% rename from tests/features/test_evaluate.py rename to tests/unit/features/test_evaluate.py diff --git a/tests/models/__init__.py b/tests/unit/models/__init__.py similarity index 100% rename from tests/models/__init__.py rename to tests/unit/models/__init__.py diff --git a/tests/models/test_HPoptimizer.py b/tests/unit/models/test_HPoptimizer.py similarity index 100% rename from tests/models/test_HPoptimizer.py rename to tests/unit/models/test_HPoptimizer.py diff --git a/tests/test_classifier.py b/tests/unit/models/test_classifier.py similarity index 100% rename from tests/test_classifier.py rename to tests/unit/models/test_classifier.py diff --git a/tests/models/test_training.py b/tests/unit/models/test_training.py similarity index 100% rename from tests/models/test_training.py rename to tests/unit/models/test_training.py diff --git a/tests/models/utils_training_hpo.py b/tests/unit/models/utils_training_hpo.py similarity index 100% rename from tests/models/utils_training_hpo.py rename to tests/unit/models/utils_training_hpo.py From 4c0f0bca27b7e0ef0209dc21798548781d339afa Mon Sep 17 00:00:00 2001 From: Sinan Date: Sat, 24 May 2025 22:53:32 +0200 Subject: [PATCH 11/32] started repolishing namings and structure of data pipeline for final structure --- recaptcha_classifier/data/__init__.py | 15 ++++----- recaptcha_classifier/data/augment.py | 14 ++++----- recaptcha_classifier/data/collate_batch.py | 7 +---- recaptcha_classifier/data/dataset.py | 4 +-- recaptcha_classifier/data/downloader.py | 19 +++++++----- recaptcha_classifier/data/loader_factory.py | 6 ++-- recaptcha_classifier/data/pair_loader.py | 6 ++-- recaptcha_classifier/data/splitter.py | 14 ++++----- recaptcha_classifier/data/types.py | 34 +++++++-------------- recaptcha_classifier/data/visualizer.py | 10 +++--- recaptcha_classifier/detection_labels.py | 7 ----- 11 files changed, 56 insertions(+), 80 deletions(-) diff --git a/recaptcha_classifier/data/__init__.py b/recaptcha_classifier/data/__init__.py index c341ff9f94..c7d918d6f9 100644 --- a/recaptcha_classifier/data/__init__.py +++ b/recaptcha_classifier/data/__init__.py @@ -16,11 +16,9 @@ from .collate_batch import collate_batch from .types import ( - FilePair, - FilePairList, - DatasetSplitDict, - BBoxList, - DataPair, + ImagePathList, + DatasetSplitMap, + LoadedImg, DataItem, DataBatch ) @@ -43,10 +41,9 @@ "collate_batch", # Types "FilePair", - "FilePairList", - "DatasetSplitDict", - "BBoxList", - "DataPair", + "ImagePathList", + "DatasetSplitMap", + "LoadedImg", "DataItem", "DataBatch", ] diff --git a/recaptcha_classifier/data/augment.py b/recaptcha_classifier/data/augment.py index ffbf127646..8b622db68f 100644 --- a/recaptcha_classifier/data/augment.py +++ b/recaptcha_classifier/data/augment.py @@ -3,7 +3,7 @@ from PIL import Image from typing import List from .scaler import YOLOScaler -from .types import DataPair, BBoxList +from .types import LoadedImg, BBoxList class Augmentation(ABC): @@ -11,7 +11,7 @@ class Augmentation(ABC): @abstractmethod def augment(self, image: Image.Image, - annotations: List) -> DataPair: + annotations: List) -> LoadedImg: """ Apply the transformation of the image and updates the bounding boxes if necessary. @@ -21,7 +21,7 @@ def augment(self, annotations (List): List of annotations associated with the image. Returns: - DataPair: The augmented image and the updated + LoadedImg: The augmented image and the updated annotations. """ pass @@ -34,7 +34,7 @@ def __init__(self, transforms=[]) -> None: def apply_transforms(self, image: Image.Image, - annotations: BBoxList) -> DataPair: + annotations: BBoxList) -> LoadedImg: """ Apply all transformations in the pipeline to the image and annotations. @@ -45,7 +45,7 @@ def apply_transforms(self, associated with the image. Returns: - DataPair: The augmented image and the updated + LoadedImg: The augmented image and the updated annotations. """ for transform in self._transforms: @@ -62,7 +62,7 @@ def __init__(self, p: float = 0.5) -> None: def augment(self, image: Image.Image, - annotations: BBoxList) -> DataPair: + annotations: BBoxList) -> LoadedImg: flipped = image.transpose(Image.FLIP_LEFT_RIGHT) new_annotations = YOLOScaler.scale_for_flip(annotations) @@ -80,7 +80,7 @@ def __init__(self, degrees: float = 30.0, p: float = 0.5) -> None: def augment(self, image: Image.Image, - annotations: BBoxList) -> DataPair: + annotations: BBoxList) -> LoadedImg: angle = random.uniform(-self._degrees, self._degrees) rotated = image.rotate(angle) diff --git a/recaptcha_classifier/data/collate_batch.py b/recaptcha_classifier/data/collate_batch.py index 55cc67c688..a1f12afab6 100644 --- a/recaptcha_classifier/data/collate_batch.py +++ b/recaptcha_classifier/data/collate_batch.py @@ -10,25 +10,20 @@ def collate_batch(batch: List[DataItem]) -> DataBatch: Args: batch (List[DataItem]): A batch of training items; each item is - a tuple of format (image tensor, bounding boxes, class index). + a tuple of format (image tensor, class index). Returns: Batch: A single tuple containing: - images_tensor (Tensor): Batched images of shape (batch_size, 3, H, W) - - bboxes (List[BBoxList]): A list of - bounding boxes for each image - labels_tensor (Tensor): A tensor of shape containing the class indices. """ images = [item[0] for item in batch] - # bboxes = [item[1] for item in batch] - # labels = [item[2] for item in batch] labels = [item[1] for item in batch] # Stack them as (3, H, W) tensors images_tensor = torch.stack(images) labels_tensor = torch.stack(labels) - # return images_tensor, bboxes, labels_tensor return images_tensor, labels_tensor diff --git a/recaptcha_classifier/data/dataset.py b/recaptcha_classifier/data/dataset.py index 803b4b6027..f14ad87f0b 100644 --- a/recaptcha_classifier/data/dataset.py +++ b/recaptcha_classifier/data/dataset.py @@ -2,7 +2,7 @@ from torch.utils.data import Dataset from .preprocessor import ImagePrep from .augment import AugmentationPipeline -from .types import FilePairList, DataItem +from .types import ImagePathList, DataItem class ImageDataset(Dataset): @@ -17,7 +17,7 @@ class ImageDataset(Dataset): https://docs.pytorch.org/tutorials/beginner/basics/data_tutorial.html """ def __init__(self, - pairs: FilePairList, + pairs: ImagePathList, preprocessor: ImagePrep, augmentator: Optional[AugmentationPipeline] = None, class_map: dict = {} diff --git a/recaptcha_classifier/data/downloader.py b/recaptcha_classifier/data/downloader.py index 0ba4c6a336..e03c87561c 100644 --- a/recaptcha_classifier/data/downloader.py +++ b/recaptcha_classifier/data/downloader.py @@ -1,11 +1,9 @@ import requests import zipfile -import logging +import shutil from pathlib import Path from alive_progress import alive_bar -logger = logging.getLogger(__name__) - class DatasetDownloader: """ @@ -37,11 +35,11 @@ def download(self) -> None: If not, downloads and then unzips it. """ if self._is_downloaded(): - logger.info("Dataset already exists, skipping download.") + print("Dataset already exists, skipping download.") return self._prepare_dest() - logger.info(f"Downloading {self._url} to {self._dest}...") + print(f"Downloading {self._url} to {self._dest}...") self._download_zip() self._unzip_and_cleanup() @@ -82,7 +80,7 @@ def _download_zip(self) -> None: for chunk in resp.iter_content(chunk_size=8192): f.write(chunk) bar(len(chunk)) - logger.info("Download completed successfully.") + print("Download completed successfully.") def _unzip_and_cleanup(self) -> None: """ @@ -92,7 +90,7 @@ def _unzip_and_cleanup(self) -> None: Note: this assumes that the downloaded dataset has exactly the structure of our selected Kaggle dataset for simplicity. """ - logger.info("Extracting...") + print("Extracting...") with zipfile.ZipFile(self._zip_path) as z: z.extractall(self._dest) @@ -101,5 +99,10 @@ def _unzip_and_cleanup(self) -> None: (root / sub).rename(self._dest / sub) root.rmdir() + + label_dir = self._dest / "labels" + if label_dir.exists() and label_dir.is_dir(): + shutil.rmtree(label_dir) + self._zip_path.unlink() - print("Extraction and cleanup completed successfully.") + print("Extraction and cleanup completed successfully.") \ No newline at end of file diff --git a/recaptcha_classifier/data/loader_factory.py b/recaptcha_classifier/data/loader_factory.py index 26e807c834..d104205b98 100644 --- a/recaptcha_classifier/data/loader_factory.py +++ b/recaptcha_classifier/data/loader_factory.py @@ -4,7 +4,7 @@ from .dataset import ImageDataset from .preprocessor import ImagePrep from .augment import AugmentationPipeline -from .types import DatasetSplitDict, FilePairList +from .types import DatasetSplitMap, ImagePathList from .collate_batch import collate_batch @@ -39,12 +39,12 @@ def __init__(self, self._class_map = class_map def create_loaders(self, - splits: DatasetSplitDict) -> Dict[str, DataLoader]: + splits: DatasetSplitMap) -> Dict[str, DataLoader]: loaders: Dict[str, DataLoader] = {} for split_name, cls_dict in splits.items(): # flatten nested dict of pairs - flat_pairs: FilePairList = [pair + flat_pairs: ImagePathList = [pair # traversing over classes for pairs in cls_dict.values() # traversing over pairs diff --git a/recaptcha_classifier/data/pair_loader.py b/recaptcha_classifier/data/pair_loader.py index c8885cbf2f..828727c5d0 100644 --- a/recaptcha_classifier/data/pair_loader.py +++ b/recaptcha_classifier/data/pair_loader.py @@ -1,7 +1,7 @@ import logging from pathlib import Path from typing import List, Dict -from .types import ClassFileDict +from .types import ClassToImgPaths logger = logging.getLogger(__name__) @@ -33,9 +33,9 @@ def __init__(self, self._classes = classes self._images_dir = Path(images_dir) # self._labels_dir = Path(labels_dir) - self._pairs: ClassFileDict = dict() + self._pairs: ClassToImgPaths = dict() - def find_pairs(self) -> ClassFileDict: + def find_pairs(self) -> ClassToImgPaths: """ Returns all pairs loaded for the given classes. It caches the response after first run. diff --git a/recaptcha_classifier/data/splitter.py b/recaptcha_classifier/data/splitter.py index 4e36bbdb76..31416a9274 100644 --- a/recaptcha_classifier/data/splitter.py +++ b/recaptcha_classifier/data/splitter.py @@ -1,6 +1,6 @@ import random from typing import Tuple -from .types import ClassFileDict, DatasetSplitDict, FilePairList +from .types import ClassToImgPaths, DatasetSplitMap, ImagePathList class DataSplitter: @@ -29,8 +29,8 @@ def __init__(self, self._validate_ratios() def split(self, - pairs_by_class: ClassFileDict - ) -> DatasetSplitDict: + pairs_by_class: ClassToImgPaths + ) -> DatasetSplitMap: """ Splits each class into train, validation, and test sets. It shuffles the data if specified and returns a dictionary @@ -40,7 +40,7 @@ def split(self, items (List): List of items to be split. Returns: - DatasetSplitDict: Nested dictionary containing + DatasetSplitMap: Nested dictionary containing splits for each class. """ splits = {'train': {}, 'val': {}, 'test': {}} @@ -69,16 +69,16 @@ def _validate_ratios(self) -> None: if any(ratio < 0 for ratio in self._ratios): raise ValueError("Ratios must be positive.") - def _shuffle_items(self, items: FilePairList) -> FilePairList: + def _shuffle_items(self, items: ImagePathList) -> ImagePathList: """ Returns a shuffled copied version of the items list, using seed if provided. Args: - items (FilePairList): List of items to be shuffled. + items (ImagePathList): List of items to be shuffled. Returns: - FilePairList: Shuffled list of items. + ImagePathList: Shuffled list of items. """ new_items = items.copy() rand = random.Random(self._seed) diff --git a/recaptcha_classifier/data/types.py b/recaptcha_classifier/data/types.py index f5ad6b42d7..f5537406a1 100644 --- a/recaptcha_classifier/data/types.py +++ b/recaptcha_classifier/data/types.py @@ -3,34 +3,22 @@ from pathlib import Path from torch import Tensor -# OBJECT DETECTION TASK CLASSES -# A (image, label) pair containing their system paths/ locations -FilePair = Path # Tuple[Path, Path] - -# A list of (image, label) pairs, for more items in the dataset -FilePairList = List[FilePair] +# A list of image paths, for more items in the dataset +ImagePathList = List[Path] # A dictionary where the keys are class names and -# the values are lists of (image, label) pairs, elements of that class -ClassFileDict = Dict[str, FilePairList] +# the values are lists of all image paths for that class in the dataset +ClassToImgPaths = Dict[str, ImagePathList] # A nested dictionary where main keys are train/val/test -# and the subkeys are class names -DatasetSplitDict = Dict[str, ClassFileDict] - -# YOLO bounding box format (x_center, y_center, width, height) -BBox = Tuple[float, float, float, float] - -# List of bounding boxes for one image from the dataset -BBoxList = List[BBox] +# and the subkeys are class names, containing all image paths of that class +DatasetSplitMap = Dict[str, ClassToImgPaths] -# A dataset item, of (image, annotations), where both features -# now loaded in memory, instead of Paths like in FilePair -DataPair = Tuple[Image.Image, BBoxList] +# A dataset item, where the image is now loaded from disk +LoadedImg = Image.Image -# A final dataset item, that now contains the image tensor, -# the bounding boxes and the class id; ready for model training -DataItem = Tuple[Tensor, BBoxList, int] +# A final dataset item, that now contains the image tensor, and the class id; ready for model training +DataItem = Tuple[Tensor, int] # Output of the dataloader, a batch of data items -DataBatch = Tuple[Tensor, List[BBoxList], Tensor] +DataBatch = Tuple[Tensor, Tensor] diff --git a/recaptcha_classifier/data/visualizer.py b/recaptcha_classifier/data/visualizer.py index cca973d9ef..b0315c9dea 100644 --- a/recaptcha_classifier/data/visualizer.py +++ b/recaptcha_classifier/data/visualizer.py @@ -1,5 +1,5 @@ import matplotlib.pyplot as plt -from .types import DatasetSplitDict +from .types import DatasetSplitMap import numpy as np @@ -10,12 +10,12 @@ class Visualizer: visualization of classes across the splits. """ @classmethod - def print_counts(cls, splits: DatasetSplitDict) -> None: + def print_counts(cls, splits: DatasetSplitMap) -> None: """ Prints the counts of samples in each class, for each split. Args: - splits (DatasetSplitDict): the dataset splits + splits (DatasetSplitMap): the dataset splits containing the pairs for each class. """ for split, cls_dict in splits.items(): @@ -26,14 +26,14 @@ def print_counts(cls, splits: DatasetSplitDict) -> None: @classmethod def plot_splits(cls, - splits: DatasetSplitDict, + splits: DatasetSplitMap, title: str = "Class Distribution in Splits") -> None: """ Plots a bar chart showing the amount and percentage of samples present in each class, for each of the splits. Args: - splits (DatasetSplitDict): the dataset splits + splits (DatasetSplitMap): the dataset splits containing the pairs for each class. title (str): the title of the plot. """ diff --git a/recaptcha_classifier/detection_labels.py b/recaptcha_classifier/detection_labels.py index 5c9fe8872c..2ca79204d7 100644 --- a/recaptcha_classifier/detection_labels.py +++ b/recaptcha_classifier/detection_labels.py @@ -5,13 +5,6 @@ class DetectionLabels(Enum): """ Enum for improving readability of the object classes. """ - - """ - OBJECT DETECTION TASK CLASSES - CROSSWALK = 0 - CHIMNEY = 1 - STAIR = 2 - """ BICYCLE = 0 BRIDGE = 1 BUS = 2 From e799210474afa4abe4bc8f66bcd22306e7bff6f7 Mon Sep 17 00:00:00 2001 From: Sinan Date: Sat, 24 May 2025 23:35:18 +0200 Subject: [PATCH 12/32] revamped download process to cleanup structure of dataset --- recaptcha_classifier/data/__init__.py | 2 - recaptcha_classifier/data/augment.py | 31 +++----- recaptcha_classifier/data/dataset.py | 6 -- recaptcha_classifier/data/downloader.py | 70 +++++++++++++----- recaptcha_classifier/data/pipeline.py | 2 +- recaptcha_classifier/data/preprocessor.py | 23 ------ recaptcha_classifier/data/scaler.py | 90 ----------------------- 7 files changed, 62 insertions(+), 162 deletions(-) delete mode 100644 recaptcha_classifier/data/scaler.py diff --git a/recaptcha_classifier/data/__init__.py b/recaptcha_classifier/data/__init__.py index c7d918d6f9..7fdb9a5551 100644 --- a/recaptcha_classifier/data/__init__.py +++ b/recaptcha_classifier/data/__init__.py @@ -11,7 +11,6 @@ HorizontalFlip, RandomRotation ) -from .scaler import YOLOScaler from .collate_batch import collate_batch @@ -36,7 +35,6 @@ "AugmentationPipeline", "HorizontalFlip", "RandomRotation", - "YOLOScaler", # Methods "collate_batch", # Types diff --git a/recaptcha_classifier/data/augment.py b/recaptcha_classifier/data/augment.py index 8b622db68f..ca51e07e3a 100644 --- a/recaptcha_classifier/data/augment.py +++ b/recaptcha_classifier/data/augment.py @@ -2,8 +2,7 @@ from abc import ABC, abstractmethod from PIL import Image from typing import List -from .scaler import YOLOScaler -from .types import LoadedImg, BBoxList +from .types import LoadedImg class Augmentation(ABC): @@ -33,15 +32,13 @@ def __init__(self, transforms=[]) -> None: self._transforms: List[Augmentation] = transforms def apply_transforms(self, - image: Image.Image, - annotations: BBoxList) -> LoadedImg: + image: Image.Image) -> LoadedImg: """ Apply all transformations in the pipeline to the image and annotations. Args: image (Image.Image): The image to be augmented. - annotations (BBoxList): List of annotations associated with the image. Returns: @@ -51,43 +48,33 @@ def apply_transforms(self, for transform in self._transforms: if hasattr(transform, 'prob') and random.random() > transform.prob: continue - image, annotations = transform.augment(image, annotations) + image, annotations = transform.augment(image) return image, annotations class HorizontalFlip(Augmentation): - """Flips the image horizontally, with probability p and updates bboxes.""" + """Flips the image horizontally, with probability p.""" def __init__(self, p: float = 0.5) -> None: self.prob = p def augment(self, - image: Image.Image, - annotations: BBoxList) -> LoadedImg: + image: Image.Image) -> LoadedImg: flipped = image.transpose(Image.FLIP_LEFT_RIGHT) - new_annotations = YOLOScaler.scale_for_flip(annotations) - return flipped, new_annotations + return flipped class RandomRotation(Augmentation): """ - Rotates the image by a random angle, - also updates bboxes to reflect the rotation. + Rotates the image by a random angle. """ def __init__(self, degrees: float = 30.0, p: float = 0.5) -> None: self._degrees = degrees self.prob = p def augment(self, - image: Image.Image, - annotations: BBoxList) -> LoadedImg: + image: Image.Image) -> LoadedImg: angle = random.uniform(-self._degrees, self._degrees) rotated = image.rotate(angle) - new_annotations = (YOLOScaler - .scale_for_rotation(annotations, - angle, - image.size) - ) - - return rotated, new_annotations + return rotated diff --git a/recaptcha_classifier/data/dataset.py b/recaptcha_classifier/data/dataset.py index f14ad87f0b..8adda5a9a8 100644 --- a/recaptcha_classifier/data/dataset.py +++ b/recaptcha_classifier/data/dataset.py @@ -52,10 +52,6 @@ def __getitem__(self, # Load image and label img = self._prep.load_image(img_path) - # bb = self._prep.load_labels(lbl_path) - - # if not bb: - # raise ValueError(f"Bounding box list is empty for {lbl_path}") # Apply augmentation if passed if self._aug: @@ -71,6 +67,4 @@ def __getitem__(self, raise KeyError(f"Class name '{c_name}' not found in classes.") c_id = self._class_map[c_name] - # Return image tensor, bounding box and class index return tensor, self._prep.class_id_to_tensor(c_id) - # return tensor, bb, c_id diff --git a/recaptcha_classifier/data/downloader.py b/recaptcha_classifier/data/downloader.py index e03c87561c..1e597b6b7e 100644 --- a/recaptcha_classifier/data/downloader.py +++ b/recaptcha_classifier/data/downloader.py @@ -3,6 +3,7 @@ import shutil from pathlib import Path from alive_progress import alive_bar +from recaptcha_classifier.detection_labels import DetectionLabels class DatasetDownloader: @@ -28,6 +29,7 @@ def __init__(self, self._dest: Path = Path(dest) self._zip_path: Path = self._dest / "dataset.zip" self._progress = alive_bar + self._expected_folder_names = DetectionLabels.dataset_classnames() def download(self) -> None: """ @@ -41,16 +43,25 @@ def download(self) -> None: self._prepare_dest() print(f"Downloading {self._url} to {self._dest}...") self._download_zip() - self._unzip_and_cleanup() + self._extract_zip() + self._move_subfolders() + self._delete_labels() + self._flatten_images_folder() + self._zip_path.unlink() + print("Download and extraction completed successfully.") def _is_downloaded(self) -> bool: """ - Checks if the dataset is already downloaded. + Checks if the dataset is already downloaded and in the expected format. Returns: bool: True if the dataset is already downloaded, False otherwise. """ - return self._dest.exists() and any(self._dest.iterdir()) + if not self._dest.exists(): + return False + folders = {p.name for p in self._dest.iterdir() if p.is_dir()} + expected = set(self._expected_folder_names) + return expected.issubset(folders) def _prepare_dest(self) -> None: """ @@ -80,29 +91,52 @@ def _download_zip(self) -> None: for chunk in resp.iter_content(chunk_size=8192): f.write(chunk) bar(len(chunk)) - print("Download completed successfully.") - - def _unzip_and_cleanup(self) -> None: + + def _extract_zip(self) -> None: """ - Unzips the downloaded dataset. - Then it cleans up the zip file and its main extracted directory. - - Note: this assumes that the downloaded dataset has exactly - the structure of our selected Kaggle dataset for simplicity. + Extracts the downloaded zip file to the destination directory. """ - print("Extracting...") with zipfile.ZipFile(self._zip_path) as z: z.extractall(self._dest) - root = next(p for p in self._dest.iterdir() if p.is_dir()) + def _move_subfolders(self) -> None: + """ + Moves the main images and labels subfolders + from the extracted directory to the destination directory. + Finally, it removes the main extracted directory that is now empty. + """ + root = next(p for p in self._dest.iterdir() if p.is_dir() and p.name not in self._expected_folder_names) + for sub in ("images", "labels"): - (root / sub).rename(self._dest / sub) - - root.rmdir() + source = root / sub + if source.exists(): + target = self._dest / sub + if target.exists(): + shutil.rmtree(target) + source.rename(target) + if root.exists() and root.is_dir(): + root.rmdir() + + def _delete_labels(self) -> None: + """ + Deletes the labels directory if it exists. + """ label_dir = self._dest / "labels" if label_dir.exists() and label_dir.is_dir(): shutil.rmtree(label_dir) + + def _flatten_images_folder(self) -> None: + images_dir = self._dest / "images" + if not images_dir.exists(): + return + + for subfolder in images_dir.iterdir(): + if subfolder.is_dir(): + target_path = self._dest / subfolder.name + if target_path.exists(): + shutil.rmtree(target_path) + subfolder.rename(target_path) - self._zip_path.unlink() - print("Extraction and cleanup completed successfully.") \ No newline at end of file + if not any(images_dir.iterdir()): + images_dir.rmdir() diff --git a/recaptcha_classifier/data/pipeline.py b/recaptcha_classifier/data/pipeline.py index a2949c67c6..961e44a513 100644 --- a/recaptcha_classifier/data/pipeline.py +++ b/recaptcha_classifier/data/pipeline.py @@ -114,4 +114,4 @@ def run(self) -> Dict[str, DataLoader]: # 4. Create DataLoaders for each split print("e. Creating DataLoaders for each split...") loaders = self._creator.create_loaders(splits) - return loaders + return loaders \ No newline at end of file diff --git a/recaptcha_classifier/data/preprocessor.py b/recaptcha_classifier/data/preprocessor.py index c171503a2e..51cd4dd0ea 100644 --- a/recaptcha_classifier/data/preprocessor.py +++ b/recaptcha_classifier/data/preprocessor.py @@ -3,7 +3,6 @@ from PIL import Image import numpy as np import torch -from .types import BBoxList class ImagePrep: @@ -39,28 +38,6 @@ def load_image(self, img_path: Path) -> Image.Image: loaded = Image.open(img_path).convert("RGB") return self._resize(loaded) - def load_labels(self, lbl_path: Path) -> BBoxList: - """ - Parses the list of YOLO format labels from the file at the given path. - - Args: - lbl_path (Path): Path to the label file. - - Returns: - BBoxList: List of bounding boxes in YOLO format - (x_center, y_center, width, height). - """ - bounding_boxes = [] - with open(lbl_path, "r") as f: - for line in f: - parts = line.strip().split() - if len(parts) < 5: - continue # invalid line skipped - _, x_center, y_center, width, height = map(float, parts) - bounding_boxes.append((x_center, y_center, width, height)) - - return bounding_boxes - def _resize(self, img: Image.Image) -> Image.Image: """ Resizes the image to the target size. diff --git a/recaptcha_classifier/data/scaler.py b/recaptcha_classifier/data/scaler.py deleted file mode 100644 index f35a303125..0000000000 --- a/recaptcha_classifier/data/scaler.py +++ /dev/null @@ -1,90 +0,0 @@ -from typing import Tuple -from .types import BBoxList - - -class YOLOScaler: - """ - This class is responsible for scaling the YOLO format bounding boxes - based on the transform applied to the image. It is only - used inside the AugmentationPipeline class, to adjust - the coordinates of the bounding boxes after applying - transformations to the image. - """ - @staticmethod - def scale_for_flip(bboxes: BBoxList) -> BBoxList: - """ - Adjusts the bounding boxes for horizontal flip. - - Args: - bboxes (BBoxList): List of bounding boxes in YOLO format - (x_center, y_center, width, height). - - Returns: - BBoxList: List of scaled bounding boxes. - """ - return [(1 - x, y, w, h) for (x, y, w, h) in bboxes] - - @staticmethod - def scale_for_rotation(bboxes: BBoxList, - angle: float, - size: Tuple[int, int]) -> BBoxList: - """ - Adjusts the bounding boxes for rotation. - - Args: - bboxes (BBoxList): List of bounding boxes in YOLO format - (x_center, y_center, width, height). - angle (float): Angle of rotation. - size (Tuple[int, int]): Size of the image. (width, height) - - Returns: - BBoxList: List of scaled bounding boxes. - """ - import math - width, height = size - angle_rad = math.radians(angle) - c_x, c_y = width / 2, height / 2 # center coordinates of the image - - n_ann = [] # the new bounding boxes, that we will return - - for x, y, w, h in bboxes: - # calculate pixel coordinates from bb - x0, y0 = x * width, y * height - bw, bh = w * width, h * height - - # calculate corner coordinates - corners = [ - (x0 - bw / 2, y0 - bh / 2), - (x0 + bw / 2, y0 - bh / 2), - (x0 + bw / 2, y0 + bh / 2), - (x0 - bw / 2, y0 + bh / 2) - ] - - # rotate the corners - new_corners = [] - for cx, cy in corners: - # rotate the corners around the center of the image - # formula at https://en.wikipedia.org/wiki/Rotation_matrix - x_rot = (math.cos(angle_rad) * (cx - c_x) - - math.sin(angle_rad) * (cy - c_y) + - c_x) - y_rot = (math.sin(angle_rad) * (cx - c_x) + - math.cos(angle_rad) * (cy - c_y) + - c_y) - - new_corners.append((x_rot, y_rot)) - - # calculate new bounding box - x_min = min(c[0] for c in new_corners) - x_max = max(c[0] for c in new_corners) - y_min = min(c[1] for c in new_corners) - y_max = max(c[1] for c in new_corners) - - new_x = max(0, min(1, (x_min + x_max) / (2 * width))) - new_y = max(0, min(1, (y_min + y_max) / (2 * height))) - new_w = max(0, min(1, (x_max - x_min) / width)) - new_h = max(0, min(1, (y_max - y_min) / height)) - - n_ann.append((new_x, new_y, new_w, new_h)) - - return n_ann From 799d3142387050ded9ab368a1536d888481254b3 Mon Sep 17 00:00:00 2001 From: Sinan Date: Sun, 25 May 2025 00:01:21 +0200 Subject: [PATCH 13/32] polished some more types and docs, given the uodates --- main.py | 1 - recaptcha_classifier/data/__init__.py | 4 +- recaptcha_classifier/data/downloader.py | 5 +- recaptcha_classifier/data/pair_loader.py | 121 ---------------------- recaptcha_classifier/data/paths_loader.py | 93 +++++++++++++++++ recaptcha_classifier/data/pipeline.py | 23 ++-- recaptcha_classifier/data/splitter.py | 3 +- recaptcha_classifier/data/visualizer.py | 44 ++------ recaptcha_classifier/detection_labels.py | 2 +- tests/data/notyet-test_pair_loader.py | 16 +-- 10 files changed, 128 insertions(+), 184 deletions(-) delete mode 100644 recaptcha_classifier/data/pair_loader.py create mode 100644 recaptcha_classifier/data/paths_loader.py diff --git a/main.py b/main.py index 5c508c1aa1..d9061c3a73 100644 --- a/main.py +++ b/main.py @@ -71,6 +71,5 @@ def open_api(): # opens endpoint at http://localhost:8000/ uvicorn.run("recaptcha_classifier.server.api:app", reload=True) - if __name__ == '__main__': main() \ No newline at end of file diff --git a/recaptcha_classifier/data/__init__.py b/recaptcha_classifier/data/__init__.py index 7fdb9a5551..b4dde44d2b 100644 --- a/recaptcha_classifier/data/__init__.py +++ b/recaptcha_classifier/data/__init__.py @@ -1,6 +1,6 @@ from .pipeline import DataPreprocessingPipeline from .downloader import DatasetDownloader -from .pair_loader import ImageLabelLoader +from .paths_loader import ImagePathsLoader from .splitter import DataSplitter from .visualizer import Visualizer from .loader_factory import LoaderFactory @@ -26,7 +26,7 @@ # Classes "DataPreprocessingPipeline", "DatasetDownloader", - "ImageLabelLoader", + "ImagePathsLoader", "DataSplitter", "Visualizer", "LoaderFactory", diff --git a/recaptcha_classifier/data/downloader.py b/recaptcha_classifier/data/downloader.py index 1e597b6b7e..219c7f25f0 100644 --- a/recaptcha_classifier/data/downloader.py +++ b/recaptcha_classifier/data/downloader.py @@ -2,8 +2,8 @@ import zipfile import shutil from pathlib import Path +from typing import List from alive_progress import alive_bar -from recaptcha_classifier.detection_labels import DetectionLabels class DatasetDownloader: @@ -15,6 +15,7 @@ class DatasetDownloader: dataset downloading operation, with no other responsibilities. """ def __init__(self, + class_names: List[str], url: str = ("https://www.kaggle.com/api/v1/datasets/" "download/mikhailma/test-dataset"), dest: str = "data") -> None: @@ -29,7 +30,7 @@ def __init__(self, self._dest: Path = Path(dest) self._zip_path: Path = self._dest / "dataset.zip" self._progress = alive_bar - self._expected_folder_names = DetectionLabels.dataset_classnames() + self._expected_folder_names = class_names def download(self) -> None: """ diff --git a/recaptcha_classifier/data/pair_loader.py b/recaptcha_classifier/data/pair_loader.py deleted file mode 100644 index 828727c5d0..0000000000 --- a/recaptcha_classifier/data/pair_loader.py +++ /dev/null @@ -1,121 +0,0 @@ -import logging -from pathlib import Path -from typing import List, Dict -from .types import ClassToImgPaths - -logger = logging.getLogger(__name__) - - -class ImageLabelLoader: - """ - This class loads all image-label pairs for given classes. - - It scans for all matching images and labels and caches the result. - - It follows Single Responsibility Principle (SRP) as it only handles - the loading of the pairs. Also, it uses the Iterator pattern, as - it can be looped over to get the list pairs as tuples by class, - in format (class, [(img_path, lbl_path), ...].) - """ - def __init__(self, - classes: List[str], - images_dir: str = "data/images", - # labels_dir: str = "data/labels" - ) -> None: - """ - Initializes the PairsLoader instance. - - Args: - classes (List[str]): List of class names to load. - images_dir (str): Path to the directory containing images. - labels_dir (str): Path to the directory containing labels. - """ - self._classes = classes - self._images_dir = Path(images_dir) - # self._labels_dir = Path(labels_dir) - self._pairs: ClassToImgPaths = dict() - - def find_pairs(self) -> ClassToImgPaths: - """ - Returns all pairs loaded for the given classes. - It caches the response after first run. - - Returns: - List[Tuple[Path, Path]]: List of tuples - containing image and label paths. (img_path, lbl_path) - """ - if not self._pairs: - self._load_pairs() - return self._pairs - - def __iter__(self): - """ - Iterates over classes and their respective list of pairs. - - Yields: - Tuple[str, List[Tuple[Path, Path]]]: Class name and list of - tuples containing image and label paths. - """ - for cls, pairs in self.find_pairs().items(): - yield cls, pairs - - def __len__(self) -> int: - """ - Returns total number of matched pairs. - - Returns: - int: Number of matched pairs. - """ - return sum(len(pairs) for pairs in self.find_pairs().values()) - - def class_count(self) -> Dict[str, int]: - """ - Returns a dictionary with the count of pairs for each class. - The keys are class names and the values are the counts. - - Returns: - Dict[str, int]: Dictionary with class names as keys and - counts as values. - """ - return {cls: len(pairs) for cls, pairs in self.find_pairs().items()} - - def _load_pairs(self) -> None: - """ - Private method to scan directories of given classes and match all - available image-label pairs. - It ignores images with missing labels. - """ - total_count = 0 - for cls in self._classes: - img_dir = self._images_dir / cls - # lbl_dir = self._labels_dir / cls - # skipped, cls_count = 0, 0 - - if not Path.is_dir(img_dir): # or not Path.exists(lbl_dir) - logger.info(f"Warning: Missing folder for {cls}. Skipping.") - continue - - self._pairs[cls] = list(img_dir.glob("*.png")) # [] - N = len(self._pairs[cls]) - logger.info(f"Found {N} images in {cls}.") - total_count += N - """ - for img_path in img_dir.glob("*.png"): - lbl_path = lbl_dir / img_path.name.replace(".png", ".txt") - cls_count += 1 - - if not Path.exists(lbl_path): - skipped += 1 - continue - - self._pairs[cls].append((img_path, lbl_path)) - - - print(f"Loaded {cls_count - skipped} image-label pairs for {cls}.") - if skipped > 0: - print(f"Warning: {skipped} missing labels in {cls}. Skipped.") - - total_count += cls_count - skipped - """ - - print(f"Total pairs loaded: {total_count}") diff --git a/recaptcha_classifier/data/paths_loader.py b/recaptcha_classifier/data/paths_loader.py new file mode 100644 index 0000000000..712e09ad90 --- /dev/null +++ b/recaptcha_classifier/data/paths_loader.py @@ -0,0 +1,93 @@ +from pathlib import Path +from typing import List, Dict +from .types import ClassToImgPaths + + +class ImagePathsLoader: + """ + This class loads all image paths for given classes. + + It scans for all matching images and caches the result. + + It follows Single Responsibility Principle (SRP) as it only handles + the loading of the image paths. Also, it uses the Iterator pattern, as + it can be looped over to get the list paths as tuples by class, + in format (class, [img_path1, ...]). + """ + def __init__(self, + classes: List[str], + images_dir: str = "data") -> None: + """ + Initializes the ImagePathsLoader instance. + + Args: + classes (List[str]): List of class names to load. + images_dir (str): Path to the directory containing images. + """ + self._classes = classes + self._images_dir = Path(images_dir) + self._paths: ClassToImgPaths = dict() + + def find_image_paths(self) -> ClassToImgPaths: + """ + Returns all image paths loaded for the given classes. + It caches the response after first run. + + Returns: + ClassToImgPaths: Dictionary mapping class names to lists + of image paths. + """ + if not self._paths: + self._load_pairs() + return self._paths + + def __iter__(self): + """ + Iterates over classes and their respective list of pairs. + + Yields: + Tuple[str, List[Path]]: Class name and list of + image paths. + """ + for cls, pairs in self.find_image_paths().items(): + yield cls, pairs + + def __len__(self) -> int: + """ + Returns total number of matched pairs. + + Returns: + int: Number of matched pairs. + """ + return sum(len(pairs) for pairs in self.find_image_paths().values()) + + def class_count(self) -> Dict[str, int]: + """ + Returns a dictionary with the count of pairs for each class. + The keys are class names and the values are the counts. + + Returns: + Dict[str, int]: Dictionary with class names as keys and + counts as values. + """ + return {cls: len(pairs) for cls, pairs in self.find_image_paths().items()} + + def _load_pairs(self) -> None: + """ + Private method to scan directories of given classes and match all + available image paths. + """ + total_count = 0 + for cls in self._classes: + img_dir = self._images_dir / cls + + if not Path.is_dir(img_dir): + print(f"Warning: Missing folder for {cls}. Skipping.") + continue + + self._paths[cls] = sorted(img_dir.glob("*.png")) + N = len(self._paths[cls]) + print(f"Found {N} images in {cls}.") + total_count += N + + print(f"Total image paths found: {total_count}") diff --git a/recaptcha_classifier/data/pipeline.py b/recaptcha_classifier/data/pipeline.py index 961e44a513..b9082fec72 100644 --- a/recaptcha_classifier/data/pipeline.py +++ b/recaptcha_classifier/data/pipeline.py @@ -1,8 +1,8 @@ from typing import Dict, Tuple from torch.utils.data import DataLoader - +from enum import EnumMeta from .downloader import DatasetDownloader -from .pair_loader import ImageLabelLoader +from .paths_loader import ImagePathsLoader from .splitter import DataSplitter from .visualizer import Visualizer from .preprocessor import ImagePrep @@ -33,7 +33,7 @@ class DataPreprocessingPipeline: pipeline and it includes all its components. """ def __init__(self, - class_map: Dict[str, int], + class_enum: EnumMeta, ratios: Tuple[float, float, float] = (0.7, 0.2, 0.1), seed: int = 23, # our group number batch_size: int = 32, @@ -44,7 +44,7 @@ def __init__(self, Initializes the DataPreprocessingPipeline with the given parameters. Args: - class_map (Dict[str, int]): Mappng of class names to indices. + class_enum (EnumMeta): Enum class containing dataset classes. ratios (Tuple[float, float, float]): Ratios for train, val, and test splits. seed (int): Random seed for reproducibility. @@ -53,14 +53,16 @@ def __init__(self, balance (bool): Whether to balance the dataset. show_plots (bool): Whether to show plots. """ - self._downloader = DatasetDownloader() - self._loader = ImageLabelLoader(list(class_map.keys())) + self._class_enum = class_enum + self._downloader = DatasetDownloader(self._class_enum + .dataset_classnames()) + self._loader = ImagePathsLoader(self._class_enum.dataset_classnames()) self._splitter = DataSplitter(ratios, seed=seed) self._show_plots = show_plots self._preproc = ImagePrep() self._augment = self._build_augmentator() self._creator = LoaderFactory( - class_map=class_map, + class_map=self._class_enum.to_class_map(), preprocessor=self._preproc, augmentator=self._augment, batch_size=batch_size, @@ -95,7 +97,7 @@ def run(self) -> Dict[str, DataLoader]: # 2. Finds all pairs of images and YOLO annotations from the dataset print("b. Searching for all the data...") - pairs_by_class = self._loader.find_pairs() + pairs_by_class = self._loader.find_image_paths() # 3a. Splits the data into train, val, and test sets print("c. Splitting the data...") @@ -113,5 +115,6 @@ def run(self) -> Dict[str, DataLoader]: # 4. Create DataLoaders for each split print("e. Creating DataLoaders for each split...") - loaders = self._creator.create_loaders(splits) - return loaders \ No newline at end of file + #loaders = self._creator.create_loaders(splits) + #return loaders + return [] \ No newline at end of file diff --git a/recaptcha_classifier/data/splitter.py b/recaptcha_classifier/data/splitter.py index 31416a9274..dcba9fd3b3 100644 --- a/recaptcha_classifier/data/splitter.py +++ b/recaptcha_classifier/data/splitter.py @@ -15,7 +15,8 @@ class DataSplitter: def __init__(self, ratios: Tuple[float, float, float] = (0.7, 0.2, 0.1), shuffle: bool = True, - seed: int = None) -> None: + seed: int = 23 # our group number + ) -> None: """ Args: ratios (Tuple[float, float, float]): Ratios for diff --git a/recaptcha_classifier/data/visualizer.py b/recaptcha_classifier/data/visualizer.py index b0315c9dea..6a95de7c33 100644 --- a/recaptcha_classifier/data/visualizer.py +++ b/recaptcha_classifier/data/visualizer.py @@ -16,12 +16,12 @@ def print_counts(cls, splits: DatasetSplitMap) -> None: Args: splits (DatasetSplitMap): the dataset splits - containing the pairs for each class. + containing the items for each class. """ for split, cls_dict in splits.items(): print(f"{split.upper()}:") - for cls, pairs in cls_dict.items(): - print(f" {cls:5s}: {len(pairs)}") + for cls, items in cls_dict.items(): + print(f" {cls:5s}: {len(items)}") print() @classmethod @@ -34,7 +34,7 @@ def plot_splits(cls, Args: splits (DatasetSplitMap): the dataset splits - containing the pairs for each class. + containing the items for each class. title (str): the title of the plot. """ classes = list(splits['train'].keys()) @@ -51,40 +51,8 @@ def plot_splits(cls, # total for each class, simply adding counts of each split totals = [t+v+te for t, v, te in zip(counts_train, counts_val, counts_test)] - - """ - # drawing bars - train_bars = plt.bar([i - width for i in x], - counts_train, - width, - label='Train') - val_bars = plt.bar(x, counts_val, width, label='Val') - test_bars = plt.bar([i + width for i in x], - counts_test, - width, - label='Test') - - # adding perc labels on top of each of the bar - for bars, counts in [(train_bars, counts_train), - (val_bars, counts_val), - (test_bars, counts_test)]: - for bar, count, total in zip(bars, counts, totals): - perc = count / total * 100 - plt.text( - bar.get_x() + bar.get_width() / 2, # middle of the bar, - bar.get_height(), # on top of the bar, - f'{perc:.1f}%', # percentage, formatted to 1 decimal - ha='center', va='bottom' - ) - - plt.xticks(x, classes) - plt.ylabel('No. of Samples') - plt.title(title) - plt.legend() - plt.tight_layout() - plt.show() - """ - fig, ax = plt.subplots(figsize=(num_classes, 6)) + + _, ax = plt.subplots(figsize=(num_classes, 6)) bar1 = ax.bar(x - width, counts_train, width, label='Train') bar2 = ax.bar(x, counts_val, width, label='Val') bar3 = ax.bar(x + width, counts_test, width, label='Test') diff --git a/recaptcha_classifier/detection_labels.py b/recaptcha_classifier/detection_labels.py index 2ca79204d7..1cbb610f0a 100644 --- a/recaptcha_classifier/detection_labels.py +++ b/recaptcha_classifier/detection_labels.py @@ -83,5 +83,5 @@ def dataset_classnames(cls) -> list: Returns: list: List of class names. """ - return [name.capitalize().replace("_", " ") + return [name.replace("_", " ").title() for name in cls.__members__.keys()] diff --git a/tests/data/notyet-test_pair_loader.py b/tests/data/notyet-test_pair_loader.py index 987cdf3af3..e7ae89a983 100644 --- a/tests/data/notyet-test_pair_loader.py +++ b/tests/data/notyet-test_pair_loader.py @@ -2,10 +2,10 @@ from pathlib import Path from unittest.mock import patch -from recaptcha_classifier.data.pair_loader import ImageLabelLoader +from recaptcha_classifier.data.pair_loader import ImagePathsLoader -class TestImageLabelLoader(unittest.TestCase): +class TestImagePathsLoader(unittest.TestCase): @patch("recaptcha_classifier.data.pair_loader.Path.glob") @patch("recaptcha_classifier.data.pair_loader.Path.exists", return_value=True) @@ -20,8 +20,8 @@ def test_load_pairs_with_all_labels(self, Path("data/images/class1/img2.png"), ] # 2 images found in the png glob - loader = ImageLabelLoader(["class1"]) - pairs = loader.find_pairs() + loader = ImagePathsLoader(["class1"]) + pairs = loader.find_image_paths() expected_pairs = { (Path("data/images/class1/img1.png"), @@ -49,8 +49,8 @@ def test_load_pairs_with_missing_labels(self, # label folder exists, img1.txt exists but img2.txt does not exists_mock.side_effect = [True, True, False] - loader = ImageLabelLoader(["class1"]) - pairs = loader.find_pairs() + loader = ImagePathsLoader(["class1"]) + pairs = loader.find_image_paths() self.assertIn("class1", pairs) expected_pair = { @@ -65,14 +65,14 @@ def test_load_pairs_with_missing_labels(self, @patch("recaptcha_classifier.data.pair_loader.Path.exists") @patch("recaptcha_classifier.data.pair_loader.Path.is_dir") def test_caching(self, is_dir_mock, exists_mock, glob_mock): - loader = ImageLabelLoader(["class1"]) + loader = ImagePathsLoader(["class1"]) loader._pairs = {"test": [(Path("test.png"), Path("test.txt"))]} # if any mocked method is called, then the cache is not used for method in (is_dir_mock, exists_mock, glob_mock): method.side_effect = AssertionError(f"{method} called, error!") - pairs = loader.find_pairs() + pairs = loader.find_image_paths() self.assertIs(pairs, loader._pairs) self.assertEqual(pairs, {"test": [(Path("test.png"), From ff9428933b40627f9eb718a10d2d7fa6aae15cc6 Mon Sep 17 00:00:00 2001 From: Sinan Date: Sun, 25 May 2025 00:34:27 +0200 Subject: [PATCH 14/32] started redoing tests --- recaptcha_classifier/data/augment.py | 20 ++++++++---------- recaptcha_classifier/data/dataset.py | 16 +++++++------- recaptcha_classifier/data/loader_factory.py | 21 +++++++++---------- recaptcha_classifier/data/pipeline.py | 7 +++---- recaptcha_classifier/detection_labels.py | 2 +- ...notyet-test_augment.py => test_augment.py} | 16 +++----------- ...notyet-test_dataset.py => test_dataset.py} | 18 +++++----------- ...ader_factory.py => test_loader_factory.py} | 0 ...est_pair_loader.py => test_pair_loader.py} | 0 ...t_preprocessor.py => test_preprocessor.py} | 0 .../{notyet-test_scaler.py => test_scaler.py} | 0 ...tyet-test_splitter.py => test_splitter.py} | 0 ...-test_visualizer.py => test_visualizer.py} | 0 13 files changed, 38 insertions(+), 62 deletions(-) rename tests/data/{notyet-test_augment.py => test_augment.py} (66%) rename tests/data/{notyet-test_dataset.py => test_dataset.py} (71%) rename tests/data/{notyet-test_loader_factory.py => test_loader_factory.py} (100%) rename tests/data/{notyet-test_pair_loader.py => test_pair_loader.py} (100%) rename tests/data/{notyet-test_preprocessor.py => test_preprocessor.py} (100%) rename tests/data/{notyet-test_scaler.py => test_scaler.py} (100%) rename tests/data/{notyet-test_splitter.py => test_splitter.py} (100%) rename tests/data/{notyet-test_visualizer.py => test_visualizer.py} (100%) diff --git a/recaptcha_classifier/data/augment.py b/recaptcha_classifier/data/augment.py index ca51e07e3a..1dcef38bbf 100644 --- a/recaptcha_classifier/data/augment.py +++ b/recaptcha_classifier/data/augment.py @@ -9,14 +9,14 @@ class Augmentation(ABC): """Abstract class for data augmentation.""" @abstractmethod def augment(self, - image: Image.Image, + image: LoadedImg, annotations: List) -> LoadedImg: """ Apply the transformation of the image and updates the bounding boxes if necessary. Args: - image (Image.Image): The image to be augmented. + image (LoadedImg): The image to be augmented. annotations (List): List of annotations associated with the image. Returns: @@ -32,24 +32,22 @@ def __init__(self, transforms=[]) -> None: self._transforms: List[Augmentation] = transforms def apply_transforms(self, - image: Image.Image) -> LoadedImg: + image: LoadedImg) -> LoadedImg: """ Apply all transformations in the pipeline to the image and annotations. Args: - image (Image.Image): The image to be augmented. - associated with the image. + image (LoadedImg): The image to be augmented. Returns: - LoadedImg: The augmented image and the updated - annotations. + LoadedImg: The augmented image. """ for transform in self._transforms: if hasattr(transform, 'prob') and random.random() > transform.prob: continue - image, annotations = transform.augment(image) - return image, annotations + image = transform.augment(image) + return image class HorizontalFlip(Augmentation): @@ -58,7 +56,7 @@ def __init__(self, p: float = 0.5) -> None: self.prob = p def augment(self, - image: Image.Image) -> LoadedImg: + image: LoadedImg) -> LoadedImg: flipped = image.transpose(Image.FLIP_LEFT_RIGHT) return flipped @@ -73,7 +71,7 @@ def __init__(self, degrees: float = 30.0, p: float = 0.5) -> None: self.prob = p def augment(self, - image: Image.Image) -> LoadedImg: + image: LoadedImg) -> LoadedImg: angle = random.uniform(-self._degrees, self._degrees) rotated = image.rotate(angle) diff --git a/recaptcha_classifier/data/dataset.py b/recaptcha_classifier/data/dataset.py index 8adda5a9a8..4d64cccfb7 100644 --- a/recaptcha_classifier/data/dataset.py +++ b/recaptcha_classifier/data/dataset.py @@ -7,7 +7,7 @@ class ImageDataset(Dataset): """ - A class to handle the pairs of (image, label) for the dataset. + A class to handle the images for the dataset. It makes them ready for training, by applying augmentation for the training set, preprocessing and makes sure that the output format is in PyTorch Tensor format. @@ -17,7 +17,7 @@ class ImageDataset(Dataset): https://docs.pytorch.org/tutorials/beginner/basics/data_tutorial.html """ def __init__(self, - pairs: ImagePathList, + items: ImagePathList, preprocessor: ImagePrep, augmentator: Optional[AugmentationPipeline] = None, class_map: dict = {} @@ -25,13 +25,13 @@ def __init__(self, """ Initializes the ImageDataset with the given parameters. """ - self._pairs = pairs + self._items = items self._prep = preprocessor self._aug = augmentator self._class_map = class_map def __len__(self) -> int: - return len(self._pairs) + return len(self._items) def __getitem__(self, idx: int @@ -47,16 +47,14 @@ def __getitem__(self, preprocessed image in tensor format, the YOLO bound box annotations and the label. """ - # img_path, lbl_path = self._pairs[idx] - img_path = self._pairs[idx] + img_path = self._items[idx] - # Load image and label + # Load image img = self._prep.load_image(img_path) # Apply augmentation if passed if self._aug: - # img, bb = self._aug.apply_transforms(img, bb) - img, _ = self._aug.apply_transforms(img, []) + img = self._aug.apply_transforms(img) # Convert image to tensor tensor = self._prep.to_tensor(img) diff --git a/recaptcha_classifier/data/loader_factory.py b/recaptcha_classifier/data/loader_factory.py index d104205b98..0aeded526f 100644 --- a/recaptcha_classifier/data/loader_factory.py +++ b/recaptcha_classifier/data/loader_factory.py @@ -25,7 +25,7 @@ def __init__(self, Args: class_map (dict): A dictionary mapping class names to indices. - preprocessor (ImagePrep): The preprocessor to use. + preprocessor (ImagePrep): The preprocessor to use for data. augmentator (Optional[AugmentationPipeline]): The augmentator used batch_size (int): Batch size for DataLoader. num_workers (int): Number of workers for DataLoader. @@ -43,25 +43,25 @@ def create_loaders(self, loaders: Dict[str, DataLoader] = {} for split_name, cls_dict in splits.items(): - # flatten nested dict of pairs - flat_pairs: ImagePathList = [pair + # flatten nested dict of image_paths + flat_image_paths: ImagePathList = [image_path # traversing over classes - for pairs in cls_dict.values() - # traversing over pairs - for pair in pairs + for image_paths in cls_dict.values() + # traversing over image_paths + for image_path in image_paths ] # augmentatr only for training set augmentator = self._aug if split_name == 'train' else None dataset = ImageDataset( - pairs=flat_pairs, + items=flat_image_paths, preprocessor=self._preprocessor, augmentator=augmentator, class_map=self._class_map ) - sampler = (self._build_sampler(flat_pairs) if self._balance + sampler = (self._build_sampler(flat_image_paths) if self._balance and split_name == "train" else None) loader = DataLoader( @@ -77,14 +77,13 @@ def create_loaders(self, return loaders - def _build_sampler(self, pairs): + def _build_sampler(self, image_paths): """ Builds a sampler for the dataset to balance the classes. """ class_counts = Counter() targets = [] - # for img_path, _ in pairs: - for img_path in pairs: + for img_path in image_paths: cls = img_path.parent.name targets.append(self._class_map[cls]) class_counts[self._class_map[cls]] += 1 diff --git a/recaptcha_classifier/data/pipeline.py b/recaptcha_classifier/data/pipeline.py index b9082fec72..612328a8f2 100644 --- a/recaptcha_classifier/data/pipeline.py +++ b/recaptcha_classifier/data/pipeline.py @@ -39,7 +39,7 @@ def __init__(self, batch_size: int = 32, num_workers: int = 4, balance: bool = False, - show_plots: bool = True) -> None: + show_plots: bool = False) -> None: """ Initializes the DataPreprocessingPipeline with the given parameters. @@ -115,6 +115,5 @@ def run(self) -> Dict[str, DataLoader]: # 4. Create DataLoaders for each split print("e. Creating DataLoaders for each split...") - #loaders = self._creator.create_loaders(splits) - #return loaders - return [] \ No newline at end of file + loaders = self._creator.create_loaders(splits) + return loaders \ No newline at end of file diff --git a/recaptcha_classifier/detection_labels.py b/recaptcha_classifier/detection_labels.py index 1cbb610f0a..e4ae0311c4 100644 --- a/recaptcha_classifier/detection_labels.py +++ b/recaptcha_classifier/detection_labels.py @@ -53,7 +53,7 @@ def to_class_map(cls) -> dict: Returns: dict: Dictionary representation of the enum. """ - return {cl.name.capitalize().replace("_", " "): + return {cl.name.replace("_", " ").title(): cl.value for cl in cls} @classmethod diff --git a/tests/data/notyet-test_augment.py b/tests/data/test_augment.py similarity index 66% rename from tests/data/notyet-test_augment.py rename to tests/data/test_augment.py index b153bf66cd..667a2681c8 100644 --- a/tests/data/notyet-test_augment.py +++ b/tests/data/test_augment.py @@ -7,7 +7,6 @@ HorizontalFlip, RandomRotation ) -from recaptcha_classifier.data.scaler import YOLOScaler class TestAugmentation(unittest.TestCase): @@ -17,39 +16,30 @@ def setUp(self): (1, 100, 3)) ) self.img = self.img.convert("RGB") - self.bb = [(0.5, 0.5, 0.2, 0.2)] def test_horizontal_flip(self): aug = HorizontalFlip(p=1.0) - flipped_img, flipped_bb = aug.augment(self.img, self.bb) + flipped_img = aug.augment(self.img) self.assertFalse(np.array_equal(np.array(flipped_img), np.array(self.img))) - result = YOLOScaler.scale_for_flip(self.bb) - self.assertEqual(flipped_bb, result) - @patch('random.uniform', return_value=30) def test_random_rotation(self, _): augmenter = RandomRotation(degrees=30) - rotated_img, rotated_bb = augmenter.augment(self.img, self.bb) + rotated_img = augmenter.augment(self.img) self.assertFalse(np.array_equal(np.array(rotated_img), np.array(self.img))) - result = YOLOScaler.scale_for_rotation(self.bb, 30, self.img.size) - for i, j in zip(rotated_bb, result): - self.assertAlmostEqual(i, j, places=4) - def test_pipeline(self): pipeline = AugmentationPipeline() pipeline.add_transform(HorizontalFlip(p=1.0)) pipeline.add_transform(RandomRotation(degrees=30)) - new_img, new_bb = pipeline.apply_transforms(self.img, self.bb) + new_img = pipeline.apply_transforms(self.img) self.assertIsInstance(new_img, Image.Image) - self.assertIsInstance(new_bb, list) if __name__ == "__main__": diff --git a/tests/data/notyet-test_dataset.py b/tests/data/test_dataset.py similarity index 71% rename from tests/data/notyet-test_dataset.py rename to tests/data/test_dataset.py index 48ce2e0376..9b8bedc5b8 100644 --- a/tests/data/notyet-test_dataset.py +++ b/tests/data/test_dataset.py @@ -10,19 +10,16 @@ class TestImageDataset(unittest.TestCase): def setUp(self): - self.pairs = [(Path("data/images/c1/i1.png"), - Path("data/labels/c1/i1.txt"))] + self.images = [Path("data/images/c1/i1.png")] self.class_map = {"c1": 0} self.preprocessor = ImagePrep() self.augmentator = AugmentationPipeline() @patch.object(ImagePrep, 'load_image') - @patch.object(ImagePrep, 'load_labels') @patch.object(ImagePrep, 'to_tensor') - def test_loading(self, to_tensor_mock, load_labels_mock, load_image_mock): + def test_loading(self, to_tensor_mock, load_image_mock): # Mock the return values load_image_mock.return_value = MagicMock() - load_labels_mock.return_value = [(0.1, 0.2, 0.3, 0.4)] to_tensor_mock.return_value = torch.rand(3, 224, 224) # expect shape dataset = ImageDataset( @@ -32,18 +29,15 @@ def test_loading(self, to_tensor_mock, load_labels_mock, load_image_mock): class_map=self.class_map ) - tensor, bboxes, cid = dataset[0] + tensor, cid = dataset[0] self.assertIsInstance(tensor, torch.Tensor) self.assertEqual(tensor.shape, (3, 224, 224)) - self.assertIsInstance(bboxes, list) - self.assertEqual(bboxes, [(0.1, 0.2, 0.3, 0.4)]) self.assertIsInstance(cid, int) self.assertEqual(cid, 0) @patch.object(ImagePrep, 'load_image') - @patch.object(ImagePrep, 'load_labels', return_value=[]) - def test_empty_bb(self, _, load_image_mock): + def test_empty_bb(self, load_image_mock): load_image_mock.return_value = MagicMock() dataset = ImageDataset( pairs=self.pairs, @@ -55,9 +49,7 @@ def test_empty_bb(self, _, load_image_mock): dataset[0] @patch.object(ImagePrep, 'load_image') - @patch.object(ImagePrep, 'load_labels', - return_value=[(0.1, 0.2, 0.3, 0.4)]) - def test_no_class(self, load_labels_mock, load_image_mock): + def test_no_class(self, load_image_mock): load_image_mock.return_value = np.ones((224, 224, 3)) dataset = ImageDataset( pairs=self.pairs, diff --git a/tests/data/notyet-test_loader_factory.py b/tests/data/test_loader_factory.py similarity index 100% rename from tests/data/notyet-test_loader_factory.py rename to tests/data/test_loader_factory.py diff --git a/tests/data/notyet-test_pair_loader.py b/tests/data/test_pair_loader.py similarity index 100% rename from tests/data/notyet-test_pair_loader.py rename to tests/data/test_pair_loader.py diff --git a/tests/data/notyet-test_preprocessor.py b/tests/data/test_preprocessor.py similarity index 100% rename from tests/data/notyet-test_preprocessor.py rename to tests/data/test_preprocessor.py diff --git a/tests/data/notyet-test_scaler.py b/tests/data/test_scaler.py similarity index 100% rename from tests/data/notyet-test_scaler.py rename to tests/data/test_scaler.py diff --git a/tests/data/notyet-test_splitter.py b/tests/data/test_splitter.py similarity index 100% rename from tests/data/notyet-test_splitter.py rename to tests/data/test_splitter.py diff --git a/tests/data/notyet-test_visualizer.py b/tests/data/test_visualizer.py similarity index 100% rename from tests/data/notyet-test_visualizer.py rename to tests/data/test_visualizer.py From 379dcbd75323e6c9847d0625debe1bc0c6f97d53 Mon Sep 17 00:00:00 2001 From: Sinan Date: Thu, 29 May 2025 18:57:30 +0200 Subject: [PATCH 15/32] pulled from main --- .gitignore | 7 ++----- Pipfile | 1 + recaptcha_classifier/__init__.py | 8 +++++++- recaptcha_classifier/detection_labels.py | 4 +--- recaptcha_classifier/features/__init__.py | 5 +++++ recaptcha_classifier/features/evaluation/__init__.py | 5 +++++ recaptcha_classifier/features/evaluation/evaluate.py | 4 ++-- recaptcha_classifier/models/main_model/__init__.py | 5 +++++ recaptcha_classifier/models/main_model/model_class.py | 9 ++++----- recaptcha_classifier/train/__init__.py | 3 +++ recaptcha_classifier/train/training.py | 8 +++++--- 11 files changed, 40 insertions(+), 19 deletions(-) diff --git a/.gitignore b/.gitignore index adf0e2c2e9..4a3312fbf8 100644 --- a/.gitignore +++ b/.gitignore @@ -5,9 +5,6 @@ __pycache__/ venv/ .env -private/ -/data/ - /models/ -/simple_classifier_checkpoints/ -/main_classifier_checkpoints/ \ No newline at end of file +private/ +/data/ \ No newline at end of file diff --git a/Pipfile b/Pipfile index b646d315d9..d6ff5dc0ce 100644 --- a/Pipfile +++ b/Pipfile @@ -10,6 +10,7 @@ verify_ssl = true [packages] numpy = "*" +pillow= "*" opencv-python = "*" pre-commit = "*" torch = {index = "pytorch-cu128", version = "2.7.0+cu128", markers = "platform_system == 'Windows'"} diff --git a/recaptcha_classifier/__init__.py b/recaptcha_classifier/__init__.py index c1a3c4d72c..61c583b299 100644 --- a/recaptcha_classifier/__init__.py +++ b/recaptcha_classifier/__init__.py @@ -1,9 +1,15 @@ from .models import SimpleCNN +from .models.main_model import MainCNN from .detection_labels import DetectionLabels from .data import DataPreprocessingPipeline +from .train import Trainer +from .features import evaluate_model __all__ = [ "DetectionLabels", "DataPreprocessingPipeline", - 'SimpleCNN' + 'SimpleCNN', + 'MainCNN', + 'Trainer', + 'evaluate_model' ] \ No newline at end of file diff --git a/recaptcha_classifier/detection_labels.py b/recaptcha_classifier/detection_labels.py index e4ae0311c4..f99c72e3ee 100644 --- a/recaptcha_classifier/detection_labels.py +++ b/recaptcha_classifier/detection_labels.py @@ -76,9 +76,7 @@ def from_id(cls, id: int) -> str: @classmethod def dataset_classnames(cls) -> list: """ - Returns a list of class names, only with first letter capitalized. - We use it for the pair loader, as that is the format of the folders - downloaded from the dataset. + Returns a list of class names. Returns: list: List of class names. diff --git a/recaptcha_classifier/features/__init__.py b/recaptcha_classifier/features/__init__.py index e69de29bb2..915c534014 100644 --- a/recaptcha_classifier/features/__init__.py +++ b/recaptcha_classifier/features/__init__.py @@ -0,0 +1,5 @@ +from .evaluation import evaluate_model + +__all__ = [ + "evaluate_model" +] \ No newline at end of file diff --git a/recaptcha_classifier/features/evaluation/__init__.py b/recaptcha_classifier/features/evaluation/__init__.py index e69de29bb2..95daef105c 100644 --- a/recaptcha_classifier/features/evaluation/__init__.py +++ b/recaptcha_classifier/features/evaluation/__init__.py @@ -0,0 +1,5 @@ +from .evaluate import evaluate_model + +__all__ = [ + "evaluate_model" +] \ No newline at end of file diff --git a/recaptcha_classifier/features/evaluation/evaluate.py b/recaptcha_classifier/features/evaluation/evaluate.py index 1224a8922a..db998a1c39 100644 --- a/recaptcha_classifier/features/evaluation/evaluate.py +++ b/recaptcha_classifier/features/evaluation/evaluate.py @@ -1,4 +1,4 @@ -import recaptcha_classifier.features.evaluation.classification_metrics as cm +from .classification_metrics import evaluate_classification import torch from tqdm import tqdm from recaptcha_classifier.detection_labels import DetectionLabels @@ -43,7 +43,7 @@ def evaluate_model(model: torch.nn.Module, y_pred = torch.cat(all_preds) y_true = torch.cat(all_targets) - class_results = cm.evaluate_classification( + class_results = evaluate_classification( y_pred=y_pred, y_true=y_true, class_names=class_names, diff --git a/recaptcha_classifier/models/main_model/__init__.py b/recaptcha_classifier/models/main_model/__init__.py index e69de29bb2..5417bd46f3 100644 --- a/recaptcha_classifier/models/main_model/__init__.py +++ b/recaptcha_classifier/models/main_model/__init__.py @@ -0,0 +1,5 @@ +from .model_class import MainCNN + +__all__ = [ + "MainCNN" +] \ No newline at end of file diff --git a/recaptcha_classifier/models/main_model/model_class.py b/recaptcha_classifier/models/main_model/model_class.py index 96d2b74b01..b24d810212 100644 --- a/recaptcha_classifier/models/main_model/model_class.py +++ b/recaptcha_classifier/models/main_model/model_class.py @@ -1,8 +1,10 @@ import torch import torch.nn as nn from recaptcha_classifier.constants import INPUT_SHAPE +from recaptcha_classifier.models.base_model import BaseModel -class MainCNN(nn.Module): # should inherit BaseModel(nn.Module) + +class MainCNN(BaseModel): def __init__(self, n_layers: int, @@ -64,8 +66,5 @@ def forward(self, x) -> torch.Tensor: x = x.view(x.size(0), -1) x = self.classifier(x) - return x - - - + return x # ! return logits, torch.softmax(x, dim=1) if needed for uncertainty diff --git a/recaptcha_classifier/train/__init__.py b/recaptcha_classifier/train/__init__.py index e69de29bb2..3d6a085185 100644 --- a/recaptcha_classifier/train/__init__.py +++ b/recaptcha_classifier/train/__init__.py @@ -0,0 +1,3 @@ +from .training import Trainer + +__all__ = ['Trainer'] \ No newline at end of file diff --git a/recaptcha_classifier/train/training.py b/recaptcha_classifier/train/training.py index c663c04ecf..9b8053c799 100644 --- a/recaptcha_classifier/train/training.py +++ b/recaptcha_classifier/train/training.py @@ -15,10 +15,12 @@ def __init__(self, train_loader: DataLoader, val_loader: DataLoader, epochs: int, + optimizer: torch.optim.Optimizer, + scheduler: torch.optim.lr_scheduler, save_folder: str, - model_file_name='model.pt', - optimizer_file_name='optimizer.pt', - scheduler_file_name='scheduler.pt', + model_file_name: str ='model.pt', + optimizer_file_name: str ='optimizer.pt', + scheduler_file_name: str ='scheduler.pt', device: torch.device |None = None, early_stop_threshold: int = 5 ): From 9d4f273e5f518ea35abde4e1abb0afaef0b6074b Mon Sep 17 00:00:00 2001 From: Sinan Date: Sun, 25 May 2025 01:38:48 +0200 Subject: [PATCH 16/32] first complete training!!! --- evaluation_results.json | 0 recaptcha_classifier/data/__init__.py | 8 +- recaptcha_classifier/data/augment.py | 69 ++---------------- recaptcha_classifier/data/pipeline.py | 14 ++-- .../evaluation/classification_metrics.py | 5 +- reports/figures/first_training.png | Bin 0 -> 77959 bytes 6 files changed, 20 insertions(+), 76 deletions(-) create mode 100644 evaluation_results.json create mode 100644 reports/figures/first_training.png diff --git a/evaluation_results.json b/evaluation_results.json new file mode 100644 index 0000000000..e69de29bb2 diff --git a/recaptcha_classifier/data/__init__.py b/recaptcha_classifier/data/__init__.py index b4dde44d2b..ab71a0435a 100644 --- a/recaptcha_classifier/data/__init__.py +++ b/recaptcha_classifier/data/__init__.py @@ -6,11 +6,7 @@ from .loader_factory import LoaderFactory from .dataset import ImageDataset from .preprocessor import ImagePrep -from .augment import ( - AugmentationPipeline, - HorizontalFlip, - RandomRotation -) +from .augment import AugmentationPipeline from .collate_batch import collate_batch @@ -33,8 +29,6 @@ "ImageDataset", "ImagePrep", "AugmentationPipeline", - "HorizontalFlip", - "RandomRotation", # Methods "collate_batch", # Types diff --git a/recaptcha_classifier/data/augment.py b/recaptcha_classifier/data/augment.py index 1dcef38bbf..2ee09cfc16 100644 --- a/recaptcha_classifier/data/augment.py +++ b/recaptcha_classifier/data/augment.py @@ -1,41 +1,20 @@ -import random -from abc import ABC, abstractmethod -from PIL import Image from typing import List from .types import LoadedImg - - -class Augmentation(ABC): - """Abstract class for data augmentation.""" - @abstractmethod - def augment(self, - image: LoadedImg, - annotations: List) -> LoadedImg: - """ - Apply the transformation of the image and updates the bounding boxes - if necessary. - - Args: - image (LoadedImg): The image to be augmented. - annotations (List): List of annotations associated with the image. - - Returns: - LoadedImg: The augmented image and the updated - annotations. - """ - pass +from torchvision import transforms class AugmentationPipeline: """Class to manage a series of augmentations in sequence.""" - def __init__(self, transforms=[]) -> None: - self._transforms: List[Augmentation] = transforms + def __init__(self, transforms_list: List) -> None: + """ + Initializes the augmentation pipeline with a list of transformations. + """ + self._pipeline = transforms.Compose(transforms_list) def apply_transforms(self, image: LoadedImg) -> LoadedImg: """ - Apply all transformations in the pipeline to the image and - annotations. + Apply all transformations in the pipeline to the image. Args: image (LoadedImg): The image to be augmented. @@ -43,36 +22,4 @@ def apply_transforms(self, Returns: LoadedImg: The augmented image. """ - for transform in self._transforms: - if hasattr(transform, 'prob') and random.random() > transform.prob: - continue - image = transform.augment(image) - return image - - -class HorizontalFlip(Augmentation): - """Flips the image horizontally, with probability p.""" - def __init__(self, p: float = 0.5) -> None: - self.prob = p - - def augment(self, - image: LoadedImg) -> LoadedImg: - flipped = image.transpose(Image.FLIP_LEFT_RIGHT) - - return flipped - - -class RandomRotation(Augmentation): - """ - Rotates the image by a random angle. - """ - def __init__(self, degrees: float = 30.0, p: float = 0.5) -> None: - self._degrees = degrees - self.prob = p - - def augment(self, - image: LoadedImg) -> LoadedImg: - angle = random.uniform(-self._degrees, self._degrees) - - rotated = image.rotate(angle) - return rotated + return self._pipeline(image) \ No newline at end of file diff --git a/recaptcha_classifier/data/pipeline.py b/recaptcha_classifier/data/pipeline.py index 612328a8f2..0f113a09ef 100644 --- a/recaptcha_classifier/data/pipeline.py +++ b/recaptcha_classifier/data/pipeline.py @@ -6,11 +6,8 @@ from .splitter import DataSplitter from .visualizer import Visualizer from .preprocessor import ImagePrep -from .augment import ( - AugmentationPipeline, - HorizontalFlip, - RandomRotation -) +from .augment import AugmentationPipeline +from torchvision import transforms from .loader_factory import LoaderFactory @@ -78,8 +75,11 @@ def _build_augmentator(self) -> AugmentationPipeline: AugmentationPipeline: The augmentation pipeline. """ return AugmentationPipeline([ - HorizontalFlip(p=0.5), - RandomRotation(degrees=30, p=0.5) + transforms.RandomHorizontalFlip(p=0.5), + transforms.RandomRotation(degrees=15), + transforms.ColorJitter(brightness=0.3, contrast=0.3, saturation=0.3), + transforms.RandomResizedCrop(size=(224, 224), scale=(0.8, 1.0)), + transforms.GaussianBlur(kernel_size=(5, 9), sigma=(0.1, 2.0)), ]) def run(self) -> Dict[str, DataLoader]: diff --git a/recaptcha_classifier/features/evaluation/classification_metrics.py b/recaptcha_classifier/features/evaluation/classification_metrics.py index 0fc17a2e0d..85ac0f5cab 100644 --- a/recaptcha_classifier/features/evaluation/classification_metrics.py +++ b/recaptcha_classifier/features/evaluation/classification_metrics.py @@ -13,7 +13,8 @@ def evaluate_classification(y_pred: Tensor, device: torch.device = torch.device("cuda" if torch.cuda.is_available() else "cpu"), average: str = 'weighted', cm_plot: bool = True, - class_names: Optional[list[str]] = None) -> dict: + class_names: Optional[list[str]] = None, + ) -> dict: """ Evaluate classification model using torchmetrics @@ -40,6 +41,8 @@ def evaluate_classification(y_pred: Tensor, # Convert logits to predicted labels y_pred = torch.argmax(y_pred, dim=1) + device = torch.device("cuda" if torch.cuda.is_available() else "cpu") + # Initialize Metrics acc = Accuracy(task="multiclass", num_classes=num_classes).to(device=device) f1 = F1Score(task="multiclass", num_classes=num_classes, average=average).to(device=device) diff --git a/reports/figures/first_training.png b/reports/figures/first_training.png new file mode 100644 index 0000000000000000000000000000000000000000..dedddc444b5be1d1faac7279c43325cced13e538 GIT binary patch literal 77959 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 bD-`$GzK(dtiV5ZR(!h_Yp`}47&V}?J7T)Q; literal 0 HcmV?d00001 From c9dd0b177ea4edb730bb6e600f878eaaa5313043 Mon Sep 17 00:00:00 2001 From: Sinan Date: Mon, 26 May 2025 15:59:14 +0200 Subject: [PATCH 17/32] pulled from main --- recaptcha_classifier/__init__.py | 3 ++- recaptcha_classifier/models/__init__.py | 3 ++- recaptcha_classifier/models/main_model/__init__.py | 4 +++- 3 files changed, 7 insertions(+), 3 deletions(-) diff --git a/recaptcha_classifier/__init__.py b/recaptcha_classifier/__init__.py index 61c583b299..792c58996c 100644 --- a/recaptcha_classifier/__init__.py +++ b/recaptcha_classifier/__init__.py @@ -1,5 +1,5 @@ from .models import SimpleCNN -from .models.main_model import MainCNN +from .models.main_model import MainCNN, HPOptimizer from .detection_labels import DetectionLabels from .data import DataPreprocessingPipeline from .train import Trainer @@ -10,6 +10,7 @@ "DataPreprocessingPipeline", 'SimpleCNN', 'MainCNN', + 'HPOptimizer', 'Trainer', 'evaluate_model' ] \ No newline at end of file diff --git a/recaptcha_classifier/models/__init__.py b/recaptcha_classifier/models/__init__.py index 5f6908ea92..735c490da1 100644 --- a/recaptcha_classifier/models/__init__.py +++ b/recaptcha_classifier/models/__init__.py @@ -1,3 +1,4 @@ from .simple_classifier_model import SimpleCNN +from .main_model import MainCNN, HPOptimizer -__all__ = ['SimpleCNN'] +__all__ = ['SimpleCNN', 'MainCNN', 'HPOptimizer'] diff --git a/recaptcha_classifier/models/main_model/__init__.py b/recaptcha_classifier/models/main_model/__init__.py index 5417bd46f3..405ca63fb8 100644 --- a/recaptcha_classifier/models/main_model/__init__.py +++ b/recaptcha_classifier/models/main_model/__init__.py @@ -1,5 +1,7 @@ from .model_class import MainCNN +from .HPoptimizer import HPOptimizer __all__ = [ - "MainCNN" + "MainCNN", + "HPOptimizer" ] \ No newline at end of file From 58d93ada0cb5b1210ffc2a5befbed8b059826797 Mon Sep 17 00:00:00 2001 From: Sinan Date: Mon, 26 May 2025 16:19:20 +0200 Subject: [PATCH 18/32] solved unit tests for other parts of code --- Pipfile | 1 + .../features/evaluation/classification_metrics.py | 6 +++++- recaptcha_classifier/models/main_model/HPoptimizer.py | 7 +++++-- tests/models/test_training.py | 1 + 4 files changed, 12 insertions(+), 3 deletions(-) diff --git a/Pipfile b/Pipfile index d6ff5dc0ce..8457549909 100644 --- a/Pipfile +++ b/Pipfile @@ -10,6 +10,7 @@ verify_ssl = true [packages] numpy = "*" +pandas = "*" pillow= "*" opencv-python = "*" pre-commit = "*" diff --git a/recaptcha_classifier/features/evaluation/classification_metrics.py b/recaptcha_classifier/features/evaluation/classification_metrics.py index 85ac0f5cab..f5c3162382 100644 --- a/recaptcha_classifier/features/evaluation/classification_metrics.py +++ b/recaptcha_classifier/features/evaluation/classification_metrics.py @@ -40,8 +40,12 @@ def evaluate_classification(y_pred: Tensor, # Convert logits to predicted labels y_pred = torch.argmax(y_pred, dim=1) - device = torch.device("cuda" if torch.cuda.is_available() else "cpu") + y_pred = y_pred.to(device) + y_true = y_true.to(device) + + if num_classes <= 0: + raise ValueError("num_classes must be a positive integer") # Initialize Metrics acc = Accuracy(task="multiclass", num_classes=num_classes).to(device=device) diff --git a/recaptcha_classifier/models/main_model/HPoptimizer.py b/recaptcha_classifier/models/main_model/HPoptimizer.py index 4bb6490f90..fe280cb67c 100644 --- a/recaptcha_classifier/models/main_model/HPoptimizer.py +++ b/recaptcha_classifier/models/main_model/HPoptimizer.py @@ -6,6 +6,8 @@ from recaptcha_classifier.models.main_model.model_class import MainCNN from recaptcha_classifier.train.training import Trainer +from recaptcha_classifier.detection_labels import DetectionLabels + class HPOptimizer(object): """Class for optimizing hyperparameters.""" @@ -81,9 +83,10 @@ def optimize_hyperparameters(self, return df_opt_data.copy()[:n_models] - def _train_one_model(self, hp_combo, save_checkpoints) -> None: + def _train_one_model(self, hp_combo) -> None: model = MainCNN(n_layers=int(hp_combo[0]), kernel_size=int(hp_combo[1])) - self.trainer.train(model=model, lr=hp_combo[2], load_checkpoint=False, save_checkpoint=save_checkpoints) + self._trainer.optimizer = torch.optim.RAdam(model.parameters(), lr=hp_combo[2]) + self._trainer.train(model=model, lr=hp_combo[2], load_checkpoint=False) def _generate_hp_combinations(self, hp) -> list: diff --git a/tests/models/test_training.py b/tests/models/test_training.py index 14b59f42f7..f63c389480 100644 --- a/tests/models/test_training.py +++ b/tests/models/test_training.py @@ -47,6 +47,7 @@ def test_train_process(self) -> None: def test_train_load_checkpoint(self): + self.trainer.train(model=self.model, load_checkpoint=False) self.trainer.train(model=self.model, load_checkpoint=True) assert_equal(os.path.exists(os.path.join(self.trainer.save_folder, self.trainer.model_file_name)), True, From 6a60723e747289b135d2931e2193de6a6bc8d431 Mon Sep 17 00:00:00 2001 From: Sinan Date: Mon, 26 May 2025 16:55:50 +0200 Subject: [PATCH 19/32] fixed data preproc tests --- tests/data/test_augment.py | 29 +++-------- tests/data/test_dataset.py | 28 ++++------- tests/data/test_loader_factory.py | 19 +++---- tests/data/test_pair_loader.py | 83 ------------------------------- tests/data/test_paths_loader.py | 35 +++++++++++++ tests/data/test_preprocessor.py | 7 --- tests/data/test_scaler.py | 20 -------- tests/data/test_splitter.py | 2 +- tests/data/test_visualizer.py | 12 ++--- 9 files changed, 64 insertions(+), 171 deletions(-) delete mode 100644 tests/data/test_pair_loader.py create mode 100644 tests/data/test_paths_loader.py delete mode 100644 tests/data/test_scaler.py diff --git a/tests/data/test_augment.py b/tests/data/test_augment.py index 667a2681c8..de6eeef074 100644 --- a/tests/data/test_augment.py +++ b/tests/data/test_augment.py @@ -2,11 +2,8 @@ import numpy as np from unittest.mock import patch from PIL import Image -from recaptcha_classifier.data.augment import ( - AugmentationPipeline, - HorizontalFlip, - RandomRotation -) +from recaptcha_classifier.data.augment import AugmentationPipeline +from torchvision import transforms class TestAugmentation(unittest.TestCase): @@ -17,25 +14,11 @@ def setUp(self): ) self.img = self.img.convert("RGB") - def test_horizontal_flip(self): - aug = HorizontalFlip(p=1.0) - flipped_img = aug.augment(self.img) - - self.assertFalse(np.array_equal(np.array(flipped_img), - np.array(self.img))) - - @patch('random.uniform', return_value=30) - def test_random_rotation(self, _): - augmenter = RandomRotation(degrees=30) - rotated_img = augmenter.augment(self.img) - - self.assertFalse(np.array_equal(np.array(rotated_img), - np.array(self.img))) - def test_pipeline(self): - pipeline = AugmentationPipeline() - pipeline.add_transform(HorizontalFlip(p=1.0)) - pipeline.add_transform(RandomRotation(degrees=30)) + pipeline = AugmentationPipeline([ + transforms.RandomHorizontalFlip(p=1.0), + transforms.RandomRotation(degrees=30) + ]) new_img = pipeline.apply_transforms(self.img) diff --git a/tests/data/test_dataset.py b/tests/data/test_dataset.py index 9b8bedc5b8..1fd9118067 100644 --- a/tests/data/test_dataset.py +++ b/tests/data/test_dataset.py @@ -10,20 +10,22 @@ class TestImageDataset(unittest.TestCase): def setUp(self): - self.images = [Path("data/images/c1/i1.png")] + self.items = [Path("data/c1/i1.png")] self.class_map = {"c1": 0} self.preprocessor = ImagePrep() - self.augmentator = AugmentationPipeline() + self.augmentator = AugmentationPipeline(transforms_list=[]) @patch.object(ImagePrep, 'load_image') @patch.object(ImagePrep, 'to_tensor') - def test_loading(self, to_tensor_mock, load_image_mock): + @patch.object(ImagePrep, 'class_id_to_tensor') + def test_loading(self, class_id_mock, to_tensor_mock, load_image_mock): # Mock the return values load_image_mock.return_value = MagicMock() to_tensor_mock.return_value = torch.rand(3, 224, 224) # expect shape + class_id_mock.return_value = torch.tensor(0) dataset = ImageDataset( - pairs=self.pairs, + items=self.items, preprocessor=self.preprocessor, augmentator=self.augmentator, class_map=self.class_map @@ -33,26 +35,14 @@ def test_loading(self, to_tensor_mock, load_image_mock): self.assertIsInstance(tensor, torch.Tensor) self.assertEqual(tensor.shape, (3, 224, 224)) - self.assertIsInstance(cid, int) - self.assertEqual(cid, 0) - - @patch.object(ImagePrep, 'load_image') - def test_empty_bb(self, load_image_mock): - load_image_mock.return_value = MagicMock() - dataset = ImageDataset( - pairs=self.pairs, - preprocessor=self.preprocessor, - augmentator=self.augmentator, - class_map=self.class_map - ) - with self.assertRaises(ValueError): - dataset[0] + self.assertIsInstance(cid, torch.Tensor) + self.assertEqual(cid.item(), 0) @patch.object(ImagePrep, 'load_image') def test_no_class(self, load_image_mock): load_image_mock.return_value = np.ones((224, 224, 3)) dataset = ImageDataset( - pairs=self.pairs, + items=self.items, preprocessor=self.preprocessor, augmentator=self.augmentator, ) diff --git a/tests/data/test_loader_factory.py b/tests/data/test_loader_factory.py index cab1187202..18f768b64e 100644 --- a/tests/data/test_loader_factory.py +++ b/tests/data/test_loader_factory.py @@ -12,7 +12,7 @@ class TestLoaderFactory(unittest.TestCase): def test_create_loaders(self, dataset_mock): class_map = {"class1": 0, "class2": 1} preprocessor = ImagePrep() - augmentator = AugmentationPipeline() + augmentator = AugmentationPipeline(transforms_list=[]) factory = LoaderFactory(class_map, preprocessor, augmentator) dataset_mock.return_value = dataset_mock @@ -20,20 +20,17 @@ def test_create_loaders(self, dataset_mock): splits = { "train": { - "class1": [(Path("img1.png"), Path("label1.txt")), - (Path("img2.png"), Path("label2.txt"))], - "class2": [(Path("img3.png"), Path("label3.txt"))] + "class1": [Path("img1.png"), Path("img2.png")], }, "val": { - "class1": [(Path("img4.png"), Path("label4.txt"))], - "class2": [(Path("img5.png"), Path("label5.txt"))] + "class1": + [Path("img3.png")], }, "test": { - "class1": [(Path("img6.png"), Path("label6.txt"))], - "class2": [(Path("img7.png"), Path("label7.txt"))] + "class1": [Path("img4.png"), Path("img5.png"), Path("img6.png")] } } - + loaders = factory.create_loaders(splits) self.assertIn("train", loaders) @@ -45,9 +42,7 @@ def test_create_loaders(self, dataset_mock): # We also need to check that the train set has the augmentator dataset_mock.assert_any_call( - pairs=[(Path("img1.png"), Path("label1.txt")), - (Path("img2.png"), Path("label2.txt")), - (Path("img3.png"), Path("label3.txt"))], + items=[Path("img1.png"),Path("img2.png")], preprocessor=preprocessor, augmentator=augmentator, class_map=class_map diff --git a/tests/data/test_pair_loader.py b/tests/data/test_pair_loader.py deleted file mode 100644 index e7ae89a983..0000000000 --- a/tests/data/test_pair_loader.py +++ /dev/null @@ -1,83 +0,0 @@ -import unittest -from pathlib import Path -from unittest.mock import patch - -from recaptcha_classifier.data.pair_loader import ImagePathsLoader - - -class TestImagePathsLoader(unittest.TestCase): - @patch("recaptcha_classifier.data.pair_loader.Path.glob") - @patch("recaptcha_classifier.data.pair_loader.Path.exists", - return_value=True) - @patch("recaptcha_classifier.data.pair_loader.Path.is_dir", - return_value=True) - def test_load_pairs_with_all_labels(self, - is_dir_mock, - exists_mock, - glob_mock): - glob_mock.return_value = [ - Path("data/images/class1/img1.png"), - Path("data/images/class1/img2.png"), - ] # 2 images found in the png glob - - loader = ImagePathsLoader(["class1"]) - pairs = loader.find_image_paths() - - expected_pairs = { - (Path("data/images/class1/img1.png"), - Path("data/labels/class1/img1.txt")), - (Path("data/images/class1/img2.png"), - Path("data/labels/class1/img2.txt")), - } - - self.assertEqual(set(pairs["class1"]), expected_pairs) - self.assertEqual(len(pairs["class1"]), 2) - - @patch("recaptcha_classifier.data.pair_loader.Path.glob") - @patch("recaptcha_classifier.data.pair_loader.Path.exists") - @patch("recaptcha_classifier.data.pair_loader.Path.is_dir", - return_value=True) - def test_load_pairs_with_missing_labels(self, - is_dir_mock, - exists_mock, - glob_mock): - glob_mock.return_value = [ - Path("data/images/class1/img1.png"), - Path("data/images/class1/img2.png"), - ] # 2 images found in the png glob - - # label folder exists, img1.txt exists but img2.txt does not - exists_mock.side_effect = [True, True, False] - - loader = ImagePathsLoader(["class1"]) - pairs = loader.find_image_paths() - - self.assertIn("class1", pairs) - expected_pair = { - (Path("data/images/class1/img1.png"), - Path("data/labels/class1/img1.txt")), - } - - self.assertEqual(set(pairs["class1"]), expected_pair) - self.assertEqual(len(pairs["class1"]), 1) - - @patch("recaptcha_classifier.data.pair_loader.Path.glob") - @patch("recaptcha_classifier.data.pair_loader.Path.exists") - @patch("recaptcha_classifier.data.pair_loader.Path.is_dir") - def test_caching(self, is_dir_mock, exists_mock, glob_mock): - loader = ImagePathsLoader(["class1"]) - loader._pairs = {"test": [(Path("test.png"), Path("test.txt"))]} - - # if any mocked method is called, then the cache is not used - for method in (is_dir_mock, exists_mock, glob_mock): - method.side_effect = AssertionError(f"{method} called, error!") - - pairs = loader.find_image_paths() - - self.assertIs(pairs, loader._pairs) - self.assertEqual(pairs, {"test": [(Path("test.png"), - Path("test.txt"))]}) - - -if __name__ == "__main__": - unittest.main() diff --git a/tests/data/test_paths_loader.py b/tests/data/test_paths_loader.py new file mode 100644 index 0000000000..3eebc66e82 --- /dev/null +++ b/tests/data/test_paths_loader.py @@ -0,0 +1,35 @@ +import unittest +from pathlib import Path +from unittest.mock import patch + +from recaptcha_classifier.data.paths_loader import ImagePathsLoader + + +class TestImagePathsLoader(unittest.TestCase): + @patch("recaptcha_classifier.data.paths_loader.Path.glob") + @patch("recaptcha_classifier.data.paths_loader.Path.exists", + return_value=True) + @patch("recaptcha_classifier.data.paths_loader.Path.is_dir", + return_value=True) + def test_load_paths(self, + is_dir_mock, + exists_mock, + glob_mock): + glob_mock.return_value = [ + Path("data/images/class1/img1.png"), + Path("data/images/class1/img2.png"), + ] # 2 images found in the png glob + + loader = ImagePathsLoader(["class1"]) + pairs = loader.find_image_paths() + + expected_pairs = { + Path("data/images/class1/img1.png"), + Path("data/images/class1/img2.png"), + } + + self.assertEqual(set(pairs["class1"]), expected_pairs) + self.assertEqual(len(pairs["class1"]), 2) + +if __name__ == "__main__": + unittest.main() diff --git a/tests/data/test_preprocessor.py b/tests/data/test_preprocessor.py index daec7a0f05..9f533df829 100644 --- a/tests/data/test_preprocessor.py +++ b/tests/data/test_preprocessor.py @@ -24,13 +24,6 @@ def test_load_image(self, open_mock): img_mock.convert.assert_called_once_with("RGB") resized_mock.resize.assert_called_once_with((224, 224), Image.LANCZOS) - @patch("builtins.open", new_callable=unittest.mock.mock_open, - read_data="0 0.2 0.3 0.4 0.5\n") - def test_load_labels(self, file_mock): - result = self.prep.load_labels(Path("test")) - - self.assertEqual(result, [(0.2, 0.3, 0.4, 0.5)]) - def test_to_tensor(self): img = Image.new("RGB", (224, 224)) tensor = self.prep.to_tensor(img) diff --git a/tests/data/test_scaler.py b/tests/data/test_scaler.py deleted file mode 100644 index 9a54dd26d4..0000000000 --- a/tests/data/test_scaler.py +++ /dev/null @@ -1,20 +0,0 @@ -import unittest -from recaptcha_classifier.data.scaler import YOLOScaler - - -class TestYOLOScaler(unittest.TestCase): - def test_scale_for_flip(self): - bb = [(0.5, 0.5, 0.2, 0.2), (0.3, 0.4, 0.1, 0.1)] - flipped = YOLOScaler.scale_for_flip(bb) - self.assertEqual(flipped, [(0.5, 0.5, 0.2, 0.2), - (0.7, 0.4, 0.1, 0.1)]) - - def test_scale_for_rotation(self): - bb = [(0.5, 0.5, 0.4, 0.2)] - rot = YOLOScaler.scale_for_rotation(bb, 90, (224, 224)) - self.assertEqual(len(rot), 1) - self.assertTrue(all(0 <= v <= 1 for bb in rot for v in bb)) - - def test_empty_list(self): - self.assertEqual(YOLOScaler.scale_for_flip([]), []) - self.assertEqual(YOLOScaler.scale_for_rotation([], 45, (224, 224)), []) diff --git a/tests/data/test_splitter.py b/tests/data/test_splitter.py index 406b04dc5f..10796cc6d0 100644 --- a/tests/data/test_splitter.py +++ b/tests/data/test_splitter.py @@ -4,7 +4,7 @@ class TestDataSplitter(unittest.TestCase): def test_default_split(self): - data = {"test_class": [(f"img_{i}", f"label_{i}") for i in range(10)]} + data = {"test_class": [f"img_{i}" for i in range(10)]} splits = DataSplitter().split(data) self.assertEqual(len(splits['train']['test_class']), 7) diff --git a/tests/data/test_visualizer.py b/tests/data/test_visualizer.py index 43096a5805..d4f0d9e8e8 100644 --- a/tests/data/test_visualizer.py +++ b/tests/data/test_visualizer.py @@ -8,16 +8,16 @@ def setUp(self): # only written pairs as string, instead of paths, for simplicity self.sample_splits = { "train": { - "class1": [("img1", "label1"), ("img2", "label2")], - "class2": [("img3", "label3"), ("img4", "label4")], + "class1": ["img1", "img2"], + "class2": ["img3", "img4"], }, "val": { - "class1": [("img5", "label5")], - "class2": [("img6", "label6")], + "class1": ["img5"], + "class2": ["img6"], }, "test": { - "class1": [("img7", "label7")], - "class2": [("img8", "label8")], + "class1": ["img7"], + "class2": ["img8"], }, } From 7d660912c6557e513be3262ed6fb9d90d88388a0 Mon Sep 17 00:00:00 2001 From: Sinan Date: Mon, 26 May 2025 17:15:22 +0200 Subject: [PATCH 20/32] setup integration tests --- .github/workflows/ci.yml | 32 +++++++++++++ .pre-commit-config.yaml | 2 +- recaptcha_classifier/data/pipeline.py | 2 +- tests/integration/test_checkpoint.py | 48 +++++++++++++++++++ tests/integration/test_data_pipeline.py | 13 +++++ tests/{ => unit}/data/__init__.py | 0 tests/{ => unit}/data/test_augment.py | 0 tests/{ => unit}/data/test_dataset.py | 0 tests/{ => unit}/data/test_loader_factory.py | 0 tests/{ => unit}/data/test_paths_loader.py | 0 tests/{ => unit}/data/test_preprocessor.py | 0 tests/{ => unit}/data/test_splitter.py | 0 tests/{ => unit}/data/test_visualizer.py | 0 tests/{ => unit}/features/__init__.py | 0 .../features/test_classification_metrics.py | 0 tests/{ => unit}/features/test_evaluate.py | 0 tests/{ => unit}/models/__init__.py | 0 tests/{ => unit}/models/test_HPoptimizer.py | 0 tests/{ => unit/models}/test_classifier.py | 0 tests/{ => unit}/models/test_training.py | 0 tests/{ => unit}/models/utils_training_hpo.py | 0 21 files changed, 95 insertions(+), 2 deletions(-) create mode 100644 .github/workflows/ci.yml create mode 100644 tests/integration/test_checkpoint.py create mode 100644 tests/integration/test_data_pipeline.py rename tests/{ => unit}/data/__init__.py (100%) rename tests/{ => unit}/data/test_augment.py (100%) rename tests/{ => unit}/data/test_dataset.py (100%) rename tests/{ => unit}/data/test_loader_factory.py (100%) rename tests/{ => unit}/data/test_paths_loader.py (100%) rename tests/{ => unit}/data/test_preprocessor.py (100%) rename tests/{ => unit}/data/test_splitter.py (100%) rename tests/{ => unit}/data/test_visualizer.py (100%) rename tests/{ => unit}/features/__init__.py (100%) rename tests/{ => unit}/features/test_classification_metrics.py (100%) rename tests/{ => unit}/features/test_evaluate.py (100%) rename tests/{ => unit}/models/__init__.py (100%) rename tests/{ => unit}/models/test_HPoptimizer.py (100%) rename tests/{ => unit/models}/test_classifier.py (100%) rename tests/{ => unit}/models/test_training.py (100%) rename tests/{ => unit}/models/utils_training_hpo.py (100%) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml new file mode 100644 index 0000000000..88812b3b37 --- /dev/null +++ b/.github/workflows/ci.yml @@ -0,0 +1,32 @@ +name: Integration Tests + +on: + pull_request: + branches: + - main + types: + - opened + - synchronize + - reopened + +jobs: + integration-tests: + runs-on: ubuntu-latest + steps: + - name: Checkout code + uses: actions/checkout@v4 + + - name: Set up Python + uses: actions/setup-python@v5 + with: + python-version: '3.10' + + - name: Install dependencies + run: | + python -m pip install --upgrade pip + pip install pipenv + pipenv install --dev + + - name: Run integration tests + run: | + pipenv run python -m unittest discover -s tests/integration \ No newline at end of file diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 5d2bc28c2b..242328f4f0 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -9,6 +9,6 @@ repos: hooks: - id: run-unittests name: Run unittests - entry: pipenv run python -m unittest discover tests + entry: pipenv run python -m unittest discover tests/unit language: system pass_filenames: false diff --git a/recaptcha_classifier/data/pipeline.py b/recaptcha_classifier/data/pipeline.py index 0f113a09ef..46a489f795 100644 --- a/recaptcha_classifier/data/pipeline.py +++ b/recaptcha_classifier/data/pipeline.py @@ -35,7 +35,7 @@ def __init__(self, seed: int = 23, # our group number batch_size: int = 32, num_workers: int = 4, - balance: bool = False, + balance: bool = True, show_plots: bool = False) -> None: """ Initializes the DataPreprocessingPipeline with the given parameters. diff --git a/tests/integration/test_checkpoint.py b/tests/integration/test_checkpoint.py new file mode 100644 index 0000000000..91c7294d09 --- /dev/null +++ b/tests/integration/test_checkpoint.py @@ -0,0 +1,48 @@ +import unittest +import torch +import os +from recaptcha_classifier.models.main_model import MainCNN +from recaptcha_classifier.train.training import Trainer +from recaptcha_classifier.data.pipeline import DataPreprocessingPipeline +from recaptcha_classifier.detection_labels import DetectionLabels + + +class TestCheckpointIntegration(unittest.TestCase): + def test_checkpoint_save_load(self): + pipeline = DataPreprocessingPipeline( + DetectionLabels, + batch_size=2, + num_workers=0 + ) + + loaders = pipeline.run() + + model = MainCNN(n_layers=1, kernel_size=3, num_classes=len(DetectionLabels)) + optimizer = torch.optim.Adam(model.parameters()) + scheduler = torch.optim.lr_scheduler.StepLR(optimizer, step_size=1) + device = torch.device("cpu") + + trainer = Trainer( + train_loader=loaders["train"], + val_loader=loaders["val"], + optimizer=optimizer, + model=model, + scheduler=scheduler, + device=device, + epochs=1, + ) + + trainer.train(model) + + model_state = {k: v.clone() for k, v in model.state_dict().items()} + + model_new = MainCNN(n_layers=1, kernel_size=3, num_classes=len(DetectionLabels)) + + trainer.load_checkpoint_states(model_new) + + for key in model_state: + self.assertTrue( + torch.equal(model_state[key], model_new.state_dict()[key]) + ) + + trainer.delete_checkpoints() \ No newline at end of file diff --git a/tests/integration/test_data_pipeline.py b/tests/integration/test_data_pipeline.py new file mode 100644 index 0000000000..4322638e2f --- /dev/null +++ b/tests/integration/test_data_pipeline.py @@ -0,0 +1,13 @@ +import unittest + +from recaptcha_classifier.data.pipeline import DataPreprocessingPipeline +from recaptcha_classifier.detection_labels import DetectionLabels + + +class TestDataPipeline(unittest.TestCase): + def test_pipeline_runs(self): + pipeline = DataPreprocessingPipeline(DetectionLabels) + loaders = pipeline.run() + + self.assertIn("train", loaders) + self.assertGreater(len(loaders["train"]), 0) \ No newline at end of file diff --git a/tests/data/__init__.py b/tests/unit/data/__init__.py similarity index 100% rename from tests/data/__init__.py rename to tests/unit/data/__init__.py diff --git a/tests/data/test_augment.py b/tests/unit/data/test_augment.py similarity index 100% rename from tests/data/test_augment.py rename to tests/unit/data/test_augment.py diff --git a/tests/data/test_dataset.py b/tests/unit/data/test_dataset.py similarity index 100% rename from tests/data/test_dataset.py rename to tests/unit/data/test_dataset.py diff --git a/tests/data/test_loader_factory.py b/tests/unit/data/test_loader_factory.py similarity index 100% rename from tests/data/test_loader_factory.py rename to tests/unit/data/test_loader_factory.py diff --git a/tests/data/test_paths_loader.py b/tests/unit/data/test_paths_loader.py similarity index 100% rename from tests/data/test_paths_loader.py rename to tests/unit/data/test_paths_loader.py diff --git a/tests/data/test_preprocessor.py b/tests/unit/data/test_preprocessor.py similarity index 100% rename from tests/data/test_preprocessor.py rename to tests/unit/data/test_preprocessor.py diff --git a/tests/data/test_splitter.py b/tests/unit/data/test_splitter.py similarity index 100% rename from tests/data/test_splitter.py rename to tests/unit/data/test_splitter.py diff --git a/tests/data/test_visualizer.py b/tests/unit/data/test_visualizer.py similarity index 100% rename from tests/data/test_visualizer.py rename to tests/unit/data/test_visualizer.py diff --git a/tests/features/__init__.py b/tests/unit/features/__init__.py similarity index 100% rename from tests/features/__init__.py rename to tests/unit/features/__init__.py diff --git a/tests/features/test_classification_metrics.py b/tests/unit/features/test_classification_metrics.py similarity index 100% rename from tests/features/test_classification_metrics.py rename to tests/unit/features/test_classification_metrics.py diff --git a/tests/features/test_evaluate.py b/tests/unit/features/test_evaluate.py similarity index 100% rename from tests/features/test_evaluate.py rename to tests/unit/features/test_evaluate.py diff --git a/tests/models/__init__.py b/tests/unit/models/__init__.py similarity index 100% rename from tests/models/__init__.py rename to tests/unit/models/__init__.py diff --git a/tests/models/test_HPoptimizer.py b/tests/unit/models/test_HPoptimizer.py similarity index 100% rename from tests/models/test_HPoptimizer.py rename to tests/unit/models/test_HPoptimizer.py diff --git a/tests/test_classifier.py b/tests/unit/models/test_classifier.py similarity index 100% rename from tests/test_classifier.py rename to tests/unit/models/test_classifier.py diff --git a/tests/models/test_training.py b/tests/unit/models/test_training.py similarity index 100% rename from tests/models/test_training.py rename to tests/unit/models/test_training.py diff --git a/tests/models/utils_training_hpo.py b/tests/unit/models/utils_training_hpo.py similarity index 100% rename from tests/models/utils_training_hpo.py rename to tests/unit/models/utils_training_hpo.py From 05eef4abd0e41a42fdd084c5d4b1043ffa688925 Mon Sep 17 00:00:00 2001 From: Sinan Date: Wed, 28 May 2025 21:37:55 +0200 Subject: [PATCH 21/32] preparing for final submission --- recaptcha_classifier/__init__.py | 4 +++- recaptcha_classifier/api/load_model.py | 12 ++++++++++++ 2 files changed, 15 insertions(+), 1 deletion(-) create mode 100644 recaptcha_classifier/api/load_model.py diff --git a/recaptcha_classifier/__init__.py b/recaptcha_classifier/__init__.py index 792c58996c..5ef9d74f96 100644 --- a/recaptcha_classifier/__init__.py +++ b/recaptcha_classifier/__init__.py @@ -4,6 +4,7 @@ from .data import DataPreprocessingPipeline from .train import Trainer from .features import evaluate_model +from .api import load_model __all__ = [ "DetectionLabels", @@ -12,5 +13,6 @@ 'MainCNN', 'HPOptimizer', 'Trainer', - 'evaluate_model' + 'evaluate_model', + 'load_model' ] \ No newline at end of file diff --git a/recaptcha_classifier/api/load_model.py b/recaptcha_classifier/api/load_model.py new file mode 100644 index 0000000000..215a865066 --- /dev/null +++ b/recaptcha_classifier/api/load_model.py @@ -0,0 +1,12 @@ +import torch +from recaptcha_classifier import MainCNN, DetectionLabels + +def load_model(path="models/model.pt"): + model = MainCNN( + n_layers=2, + kernel_size=3, + num_classes=len(DetectionLabels), + ) + model.load_state_dict(torch.load(path, map_location=torch.device('cpu'))) + model.eval() + return model \ No newline at end of file From b69c69ddc2ffb272864d34257b76522e9aea56cb Mon Sep 17 00:00:00 2001 From: Sinan Date: Thu, 29 May 2025 19:48:27 +0200 Subject: [PATCH 22/32] only one random augmentation applied per epoch --- recaptcha_classifier/api/load_model.py | 12 ------------ recaptcha_classifier/data/augment.py | 8 +++++--- recaptcha_classifier/data/loader_factory.py | 2 +- recaptcha_classifier/data/pipeline.py | 14 ++++++++++++-- 4 files changed, 18 insertions(+), 18 deletions(-) delete mode 100644 recaptcha_classifier/api/load_model.py diff --git a/recaptcha_classifier/api/load_model.py b/recaptcha_classifier/api/load_model.py deleted file mode 100644 index 215a865066..0000000000 --- a/recaptcha_classifier/api/load_model.py +++ /dev/null @@ -1,12 +0,0 @@ -import torch -from recaptcha_classifier import MainCNN, DetectionLabels - -def load_model(path="models/model.pt"): - model = MainCNN( - n_layers=2, - kernel_size=3, - num_classes=len(DetectionLabels), - ) - model.load_state_dict(torch.load(path, map_location=torch.device('cpu'))) - model.eval() - return model \ No newline at end of file diff --git a/recaptcha_classifier/data/augment.py b/recaptcha_classifier/data/augment.py index 2ee09cfc16..9a84e1538b 100644 --- a/recaptcha_classifier/data/augment.py +++ b/recaptcha_classifier/data/augment.py @@ -1,3 +1,4 @@ +import random from typing import List from .types import LoadedImg from torchvision import transforms @@ -9,12 +10,12 @@ def __init__(self, transforms_list: List) -> None: """ Initializes the augmentation pipeline with a list of transformations. """ - self._pipeline = transforms.Compose(transforms_list) + self._transforms_list = transforms_list def apply_transforms(self, image: LoadedImg) -> LoadedImg: """ - Apply all transformations in the pipeline to the image. + Apply a random transformation from the pipeline to the image. Args: image (LoadedImg): The image to be augmented. @@ -22,4 +23,5 @@ def apply_transforms(self, Returns: LoadedImg: The augmented image. """ - return self._pipeline(image) \ No newline at end of file + transform = random.choice(self._transforms_list) + return transform(image) \ No newline at end of file diff --git a/recaptcha_classifier/data/loader_factory.py b/recaptcha_classifier/data/loader_factory.py index 10e7a580ed..0aeded526f 100644 --- a/recaptcha_classifier/data/loader_factory.py +++ b/recaptcha_classifier/data/loader_factory.py @@ -4,7 +4,7 @@ from .dataset import ImageDataset from .preprocessor import ImagePrep from .augment import AugmentationPipeline -from .types import DatasetSplitDict, FilePairList +from .types import DatasetSplitMap, ImagePathList from .collate_batch import collate_batch diff --git a/recaptcha_classifier/data/pipeline.py b/recaptcha_classifier/data/pipeline.py index 46a489f795..63c474c774 100644 --- a/recaptcha_classifier/data/pipeline.py +++ b/recaptcha_classifier/data/pipeline.py @@ -70,16 +70,26 @@ def __init__(self, def _build_augmentator(self) -> AugmentationPipeline: """ Builds the augmentation pipeline. + One of these augmentations will be applied randomly + in the dataset, for any image in the training set. Returns: AugmentationPipeline: The augmentation pipeline. """ return AugmentationPipeline([ + # 50 % chance to flip the image horizontally transforms.RandomHorizontalFlip(p=0.5), + # rotates image randomly within +-15 degrees transforms.RandomRotation(degrees=15), + # randomly changes brightness, contrast and saturation transforms.ColorJitter(brightness=0.3, contrast=0.3, saturation=0.3), - transforms.RandomResizedCrop(size=(224, 224), scale=(0.8, 1.0)), - transforms.GaussianBlur(kernel_size=(5, 9), sigma=(0.1, 2.0)), + # Translates/ shifts image up to 10% of its size + # in both x and y directions + transforms.RandomAffine(degrees=0, translate=(0.1, 0.1)), + # Mimics a camera lens blur effect + transforms.GaussianBlur(kernel_size=3, sigma=(0.1, 1.5)), + # Crops image 90% then resizes it to 224x224 (zoom in effect) + transforms.RandomResizedCrop(size=(224, 224), scale=(0.9, 1.0)), ]) def run(self) -> Dict[str, DataLoader]: From 4ca3332ba13a37261ccc70e1433dbbc1d138657e Mon Sep 17 00:00:00 2001 From: Sinan Date: Thu, 29 May 2025 20:40:13 +0200 Subject: [PATCH 23/32] made kfold print summary of folds --- recaptcha_classifier/__init__.py | 2 +- .../evaluation/classification_metrics.py | 7 ++- .../models/main_model/HPoptimizer.py | 12 ++--- .../models/main_model/kfold_validation.py | 46 +++++++++++++------ .../pipeline/main_model_pipeline.py | 3 -- recaptcha_classifier/train/training.py | 2 - 6 files changed, 42 insertions(+), 30 deletions(-) diff --git a/recaptcha_classifier/__init__.py b/recaptcha_classifier/__init__.py index 5ef9d74f96..3e5b54a524 100644 --- a/recaptcha_classifier/__init__.py +++ b/recaptcha_classifier/__init__.py @@ -4,7 +4,7 @@ from .data import DataPreprocessingPipeline from .train import Trainer from .features import evaluate_model -from .api import load_model +from .server import load_model __all__ = [ "DetectionLabels", diff --git a/recaptcha_classifier/features/evaluation/classification_metrics.py b/recaptcha_classifier/features/evaluation/classification_metrics.py index f5c3162382..8581ac6b2a 100644 --- a/recaptcha_classifier/features/evaluation/classification_metrics.py +++ b/recaptcha_classifier/features/evaluation/classification_metrics.py @@ -1,7 +1,7 @@ import torch from torch import Tensor from torchmetrics import Accuracy, F1Score -from torchmetrics.classification import MulticlassConfusionMatrix +from torchmetrics.classification import MulticlassConfusionMatrix, MulticlassAccuracy from typing import Optional from recaptcha_classifier.detection_labels import DetectionLabels import matplotlib.pyplot as plt @@ -50,11 +50,13 @@ def evaluate_classification(y_pred: Tensor, # Initialize Metrics acc = Accuracy(task="multiclass", num_classes=num_classes).to(device=device) f1 = F1Score(task="multiclass", num_classes=num_classes, average=average).to(device=device) + topk_acc = MulticlassAccuracy(num_classes=num_classes, top_k=3).to(device=device) confmat = MulticlassConfusionMatrix(num_classes=num_classes).to(device=device) # Compute metrics acc_val = acc(y_pred, y_true) f1_val = f1(y_pred, y_true) + topk_acc_val = topk_acc(y_pred, y_true) cm = confmat(y_pred, y_true) if cm_plot: @@ -64,5 +66,6 @@ def evaluate_classification(y_pred: Tensor, return { 'Accuracy': acc_val.item(), 'F1-score': f1_val.item(), - 'Confusion Matrix': cm + 'Confusion Matrix': cm, + 'Top-3 Accuracy': topk_acc_val.item() } diff --git a/recaptcha_classifier/models/main_model/HPoptimizer.py b/recaptcha_classifier/models/main_model/HPoptimizer.py index fe280cb67c..f9517388ac 100644 --- a/recaptcha_classifier/models/main_model/HPoptimizer.py +++ b/recaptcha_classifier/models/main_model/HPoptimizer.py @@ -1,13 +1,10 @@ import itertools import random - import pandas as pd from recaptcha_classifier.models.main_model.model_class import MainCNN from recaptcha_classifier.train.training import Trainer -from recaptcha_classifier.detection_labels import DetectionLabels - class HPOptimizer(object): """Class for optimizing hyperparameters.""" @@ -30,9 +27,9 @@ def get_history(self)->pd.DataFrame: def optimize_hyperparameters(self, - n_layers: list = list(range(1,3)), - kernel_sizes: list = [3, 4, 5], - learning_rates: list = [1e-3, 1e-4], + n_layers: list = [1, 2, 3], + kernel_sizes: list = [3, 5], + learning_rates: list = [1e-2, 1e-3, 1e-4], save_checkpoints: bool = True, n_models: int = 1, n_combos: int = 8, # Number of random samples @@ -85,8 +82,7 @@ def optimize_hyperparameters(self, def _train_one_model(self, hp_combo) -> None: model = MainCNN(n_layers=int(hp_combo[0]), kernel_size=int(hp_combo[1])) - self._trainer.optimizer = torch.optim.RAdam(model.parameters(), lr=hp_combo[2]) - self._trainer.train(model=model, lr=hp_combo[2], load_checkpoint=False) + self.trainer.train(model=model, lr=hp_combo[2], load_checkpoint=False) def _generate_hp_combinations(self, hp) -> list: diff --git a/recaptcha_classifier/models/main_model/kfold_validation.py b/recaptcha_classifier/models/main_model/kfold_validation.py index 2465030f75..542182eb9d 100644 --- a/recaptcha_classifier/models/main_model/kfold_validation.py +++ b/recaptcha_classifier/models/main_model/kfold_validation.py @@ -2,7 +2,7 @@ from sklearn.model_selection import KFold from sympy.printing.pytorch import torch from torch.utils.data import DataLoader, Subset - +import matplotlib.pyplot as plt from recaptcha_classifier import DetectionLabels from recaptcha_classifier.features.evaluation.evaluate import evaluate_model from recaptcha_classifier.train.training import Trainer @@ -36,7 +36,6 @@ def __init__(self, self.device = device if device is None: self.device = torch.device("cuda" if torch.cuda.is_available() else "cpu") - self.results = None def run_cross_validation(self, @@ -89,23 +88,42 @@ def run_cross_validation(self, df_results = pd.DataFrame(results) - self.results = df_results + self.print_summary(df_results) + self.plot_results(df_results) - def print_summary(self) -> None: + @staticmethod + def print_summary(results: pd.DataFrame) -> None: """ Prints a summary of the cross-validation results. """ - if self.results is None: - print("No results to display. Run cross-validation first.") - return - - print("\n--- Cross-Validation Summary ---") - print(self.results) + print("\n~~ Cross-Validation Summary ~~") + print(results.round(3)) - mean_results = self.results.mean() + res = results.drop(columns=["fold"]) + + means = res.mean() print("\nMean Results Across Folds:") - print(mean_results) + print(means.round(3)) - std_results = self.results.std() + stds = res.std() print("\nStandard Deviation Across Folds:") - print(std_results) \ No newline at end of file + print(stds.round(3)) + + @staticmethod + def plot_results(results: pd.DataFrame) -> None: + """ + Plots the results of the cross-validation. + """ + metrics = [col for col in results.columns if col != 'fold'] + mean_vals = results[metrics].mean() + std_vals = results[metrics].std() + + fig, ax = plt.subplots(figsize=(10, 6)) + mean_vals.plot(kind="bar", yerr=std_vals, capsize=5, ax=ax) + + ax.set_title("Cross-Validation Metrics (mean +- std)") + ax.set_ylabel("Score") + ax.set_xticklabels(metrics, rotation=45, ha='right') + plt.tight_layout() + plt.grid(axis='y') + plt.show() \ No newline at end of file diff --git a/recaptcha_classifier/pipeline/main_model_pipeline.py b/recaptcha_classifier/pipeline/main_model_pipeline.py index 65f7aceaa3..223d659897 100644 --- a/recaptcha_classifier/pipeline/main_model_pipeline.py +++ b/recaptcha_classifier/pipeline/main_model_pipeline.py @@ -61,9 +61,6 @@ def _run_kfold_cross_validation(self) -> None: best_hp = self._hp_optimizer.get_best_hp() self._kfold.run_cross_validation(hp=best_hp) - - print("\n~~ Cross-Validation Summary ~~") - self._kfold.print_summary() self.lr = best_hp[2] self._model = self._initialize_model( diff --git a/recaptcha_classifier/train/training.py b/recaptcha_classifier/train/training.py index 9b8053c799..661bf3a34e 100644 --- a/recaptcha_classifier/train/training.py +++ b/recaptcha_classifier/train/training.py @@ -15,8 +15,6 @@ def __init__(self, train_loader: DataLoader, val_loader: DataLoader, epochs: int, - optimizer: torch.optim.Optimizer, - scheduler: torch.optim.lr_scheduler, save_folder: str, model_file_name: str ='model.pt', optimizer_file_name: str ='optimizer.pt', From 587d719da0399f169d000c80ed0746a54f54a49d Mon Sep 17 00:00:00 2001 From: Sinan Date: Thu, 29 May 2025 21:03:13 +0200 Subject: [PATCH 24/32] now will start training both models --- recaptcha_classifier/data/collate_batch.py | 10 ++-- recaptcha_classifier/data/types.py | 2 +- .../models/main_model/kfold_validation.py | 59 +++++++++++-------- 3 files changed, 40 insertions(+), 31 deletions(-) diff --git a/recaptcha_classifier/data/collate_batch.py b/recaptcha_classifier/data/collate_batch.py index a1f12afab6..05b0050130 100644 --- a/recaptcha_classifier/data/collate_batch.py +++ b/recaptcha_classifier/data/collate_batch.py @@ -1,16 +1,16 @@ from typing import List -from .types import DataBatch, DataItem +from .types import DataItem, DataBatch import torch def collate_batch(batch: List[DataItem]) -> DataBatch: """ - Custom collate function used in the PyTorch DataLoader to handle - the non-uniform length of bounding box lists. + Custom collate function used in the PyTorch DataLoader to combine + the list of data items into a stacked batch for model training. Args: - batch (List[DataItem]): A batch of training items; each item is - a tuple of format (image tensor, class index). + batch (List[DataItem]): A list of dataset items, tensors of + image and label pair. Returns: Batch: A single tuple containing: diff --git a/recaptcha_classifier/data/types.py b/recaptcha_classifier/data/types.py index f5537406a1..ade77d8f66 100644 --- a/recaptcha_classifier/data/types.py +++ b/recaptcha_classifier/data/types.py @@ -18,7 +18,7 @@ LoadedImg = Image.Image # A final dataset item, that now contains the image tensor, and the class id; ready for model training -DataItem = Tuple[Tensor, int] +DataItem = Tuple[Tensor, Tensor] # Output of the dataloader, a batch of data items DataBatch = Tuple[Tensor, Tensor] diff --git a/recaptcha_classifier/models/main_model/kfold_validation.py b/recaptcha_classifier/models/main_model/kfold_validation.py index 542182eb9d..7513e6166f 100644 --- a/recaptcha_classifier/models/main_model/kfold_validation.py +++ b/recaptcha_classifier/models/main_model/kfold_validation.py @@ -3,7 +3,6 @@ from sympy.printing.pytorch import torch from torch.utils.data import DataLoader, Subset import matplotlib.pyplot as plt -from recaptcha_classifier import DetectionLabels from recaptcha_classifier.features.evaluation.evaluate import evaluate_model from recaptcha_classifier.train.training import Trainer from recaptcha_classifier.models.main_model.model_class import MainCNN @@ -29,14 +28,13 @@ def __init__(self, :param hp_optimizer: Instance of HPOptimizer :param device: Optional torch device """ - self._class_map = DetectionLabels.all() # class labels self.train_loader = train_loader self.val_loader = val_loader self.k_folds = k_folds self.device = device if device is None: - self.device = torch.device("cuda" if torch.cuda.is_available() else "cpu") - + self.device = torch.device("cuda" if torch.cuda.is_available() + else "cpu") def run_cross_validation(self, hp: list, @@ -59,38 +57,49 @@ def run_cross_validation(self, dataset = self.train_loader.dataset kf = KFold(n_splits=self.k_folds, shuffle=True, random_state=42) - + results = [] - + n_layers, kernel_sizes, learning_rates = hp - for fold_index, (train_idx, val_idx) in enumerate(kf.split(all_indices)): + for fold_index, (t_idx, val_idx) in enumerate(kf.split(all_indices)): + print(f"\n--- Fold {fold_index + 1}/{self.k_folds} ---") - train_subset = Subset(dataset, [all_indices[i] for i in train_idx]) + train_subset = Subset(dataset, [all_indices[i] + for i in t_idx]) val_subset = Subset(dataset, [all_indices[i] for i in val_idx]) - fold_train_loader = DataLoader(train_subset, batch_size=batch_size, shuffle=False) - fold_val_loader = DataLoader(val_subset, batch_size=batch_size, shuffle=False) - + fold_train_loader = DataLoader(train_subset, + batch_size=batch_size, + shuffle=False) + fold_val_loader = DataLoader(val_subset, + batch_size=batch_size, + shuffle=False) + model = MainCNN(n_layers=n_layers, kernel_size=kernel_sizes) - + trainer = Trainer(fold_train_loader, fold_val_loader, epochs=20, save_folder=MODELS_FOLDER, device=self.device) - trainer.train(model, lr=learning_rates, save_checkpoint=save_checkpoints, + + trainer.train(model, + lr=learning_rates, + save_checkpoint=save_checkpoints, load_checkpoint=load_checkpoints) - - metrics = evaluate_model(model, fold_val_loader, device=self.device) - metrics.pop('Confusion Matrix') + + metrics = evaluate_model(model, fold_val_loader, + device=self.device) + metrics.pop('Confusion Matrix') metrics["fold"] = fold_index + 1 results.append(metrics) - + df_results = pd.DataFrame(results) - + + df_results.to_csv(f"{MODELS_FOLDER}/kfold_results.csv", index=False) self.print_summary(df_results) self.plot_results(df_results) - + @staticmethod def print_summary(results: pd.DataFrame) -> None: """ @@ -98,17 +107,17 @@ def print_summary(results: pd.DataFrame) -> None: """ print("\n~~ Cross-Validation Summary ~~") print(results.round(3)) - + res = results.drop(columns=["fold"]) means = res.mean() print("\nMean Results Across Folds:") print(means.round(3)) - + stds = res.std() print("\nStandard Deviation Across Folds:") print(stds.round(3)) - + @staticmethod def plot_results(results: pd.DataFrame) -> None: """ @@ -117,13 +126,13 @@ def plot_results(results: pd.DataFrame) -> None: metrics = [col for col in results.columns if col != 'fold'] mean_vals = results[metrics].mean() std_vals = results[metrics].std() - + fig, ax = plt.subplots(figsize=(10, 6)) mean_vals.plot(kind="bar", yerr=std_vals, capsize=5, ax=ax) - + ax.set_title("Cross-Validation Metrics (mean +- std)") ax.set_ylabel("Score") ax.set_xticklabels(metrics, rotation=45, ha='right') plt.tight_layout() plt.grid(axis='y') - plt.show() \ No newline at end of file + plt.show() From 57bd014d62a11a6bde16834298b9051cef1e0679 Mon Sep 17 00:00:00 2001 From: Sinan Date: Thu, 29 May 2025 21:24:54 +0200 Subject: [PATCH 25/32] starting training now --- .gitignore | 3 +-- .../evaluation/classification_metrics.py | 3 ++- recaptcha_classifier/pipeline/base_pipeline.py | 6 +++--- .../pipeline/simple_cnn_pipeline.py | 1 - reports/figures/first_training.png | Bin 77959 -> 0 bytes reports/{ => figures}/main-model.png | Bin reports/{ => figures}/simple-model.png | Bin 7 files changed, 6 insertions(+), 7 deletions(-) delete mode 100644 reports/figures/first_training.png rename reports/{ => figures}/main-model.png (100%) rename reports/{ => figures}/simple-model.png (100%) diff --git a/.gitignore b/.gitignore index 8799711320..1a48f990df 100644 --- a/.gitignore +++ b/.gitignore @@ -6,6 +6,5 @@ venv/ .env /models/ -private/ /data/ -/models/ \ No newline at end of file +/private/ \ No newline at end of file diff --git a/recaptcha_classifier/features/evaluation/classification_metrics.py b/recaptcha_classifier/features/evaluation/classification_metrics.py index 8581ac6b2a..3ad76ba0c9 100644 --- a/recaptcha_classifier/features/evaluation/classification_metrics.py +++ b/recaptcha_classifier/features/evaluation/classification_metrics.py @@ -37,6 +37,7 @@ def evaluate_classification(y_pred: Tensor, dict: accuracy, f1, confusion_matrix """ + logits = y_pred # Convert logits to predicted labels y_pred = torch.argmax(y_pred, dim=1) @@ -56,7 +57,7 @@ def evaluate_classification(y_pred: Tensor, # Compute metrics acc_val = acc(y_pred, y_true) f1_val = f1(y_pred, y_true) - topk_acc_val = topk_acc(y_pred, y_true) + topk_acc_val = topk_acc(logits, y_true) cm = confmat(y_pred, y_true) if cm_plot: diff --git a/recaptcha_classifier/pipeline/base_pipeline.py b/recaptcha_classifier/pipeline/base_pipeline.py index 7ec64c0fab..674422b396 100644 --- a/recaptcha_classifier/pipeline/base_pipeline.py +++ b/recaptcha_classifier/pipeline/base_pipeline.py @@ -25,7 +25,7 @@ def __init__(self, self.optimizer_file_name = optimizer_file_name self.scheduler_file_name = scheduler_file_name - self._class_map = DetectionLabels.to_class_map() + self._class_map = DetectionLabels self._loaders = None self._data = None self._model = None @@ -59,14 +59,14 @@ def _initialize_trainer(self) -> Trainer: @property def class_map_length(self): - return len(self._class_map) + return len(self._class_map.all()) def evaluate(self, plot_cm: bool = False) -> dict: eval_results = evaluate_model( model=self._model, test_loader=self._loaders['test'], device=self._trainer.device, - class_names=list(self._class_map.keys()), + class_names=self._class_map.dataset_classnames, plot_cm=plot_cm ) return eval_results diff --git a/recaptcha_classifier/pipeline/simple_cnn_pipeline.py b/recaptcha_classifier/pipeline/simple_cnn_pipeline.py index efd3a792f6..0b8cbd7816 100644 --- a/recaptcha_classifier/pipeline/simple_cnn_pipeline.py +++ b/recaptcha_classifier/pipeline/simple_cnn_pipeline.py @@ -1,7 +1,6 @@ import torch from recaptcha_classifier.models.simple_classifier_model import SimpleCNN from recaptcha_classifier.pipeline.base_pipeline import BasePipeline -from recaptcha_classifier.train.training import Trainer import os from recaptcha_classifier.constants import ( MODELS_FOLDER, SIMPLE_MODEL_FILE_NAME, diff --git a/reports/figures/first_training.png b/reports/figures/first_training.png deleted file mode 100644 index dedddc444b5be1d1faac7279c43325cced13e538..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 77959 zcma%?W0WOLx1h_mZ5v%ymu=g2RhMnsR+qYL+eVjdciEiN@B7`Ed)Lg5`BCfSI(aHr zMr35f-p_s_LP1U(9tH;n2nYyXQbI%t2nfU<2ncuw3IgyA+uX(h;Dh^zsKyUvd$S*I zhEAqHvW7n#Z0vv7SQ-(znmRdK+S{=*Ff%c*(h*tw_~GEp&B$o`zkk7C?_|zsM&1+& zxC*p`gr+kP5TfDVKj1QfQcECUARtK*K^6DxiyTM|Wp&(|?U7@sProkr)t{9 zhK`7+sGzVgD4W$n{QW;?OOq9TEO)qFDRj8nE>w=$@?{qS{*f9P#n0t@Ddpbq?-fe3 zgTulG1_wpv!}`ht|2bX%j_fLo`CU%2ZGMDfza*D|Ow{$jdX&T|6aodC3=B%+?cU*d4VFkdu_i$s_pm|f2-DqRCBp6C@O#<_uv{NFWn3919R1Djq@*C5qcANH(tZkzuTH% ziq`ci6B=1`b24A)&>q<6W@v=}R*+xmAACag?D?|0v-TUxgQ6Sgx;<44i?x!& zH{a?L{ssJ!RI$WJYh1q#Y9LWeoAhvOeuTM=266E^I9=EeEV7I!5S& z+wmI(MHPQP6Syskl3JGs>+`-z2~5pZuJrB~u+ChFA6JUydKD$zdgUW7 zKU(?=dwN3Zf1&ih7a<>gL72f?_5JK7HY!A6s(R9TEYi`cCnb^YstHU4QPb4}>o{7v z5DyrvPRIbZ$pXZe$r8ki?DAQB|TN+jzd6yqULZ zPoKMS`?Ys|DAJR%kzGXx!>8AJird=K!dS2G2vVC`PSC8MK6d$=FgC4wcXa9{o)T}n z^r!Kz+xpFg;EfU0h=7FAT`C7F{k?l%9_j8@ongWUKPAuz!%4^%sWvemJu1ImO@0|! z-0-DZzIT`@s@v{kNU3OU5TT)_jV7MIcGj;jV|BPsXHW?;5X-&bUwYYy$B%Yth1Y`G+;>(}HaCOUt$sD4$z z*8q}oTJ8p8eHY!^&MxgU7nq%%6zQ zeO_jlLxV}eN6Nvz_?M5BxWNW(M&^#+Nn_oc}{f30Qf^TLQ;Pb|A1;s&U9@2+@wu%o3&jp}?{10&jX8l`rR zxPjcM(Nc{X-tKjutkgi8RpSLK;;R!;^0Y|JtX~;I3B4CypGk6d{JG9lzwHG6W@k7w zsIT!_KDeJ>MzR;j z>HfjozeORWFAa~;)NLbpzGDMV4(`DV9Vw(CV-ym1{(}GY_h(IqD1{(J1=c@fJeysJ zq&7Of1y&bO^n(`Gxyc$9bQlj&cS1)6Nn7KluP=p`C8)=X*F@y3e!wR+AALw5g)L_b z?d&6QHrIuvG=!+cd~or;L09^z$GWRea1p+r-(UF!M92Cum3Z}eQ9EZMxwl)!rI#a(-0 zMgD%X15707Io(!jm;1YbS5s{VB%YL|-WjYvUZ+pLP+M8MnU@^od>b>4mR7rV5{*tY zBqb?8s(qeX^=}-XUP>l!F%5w(Q){a! zUU0s)jwav?KwKmNVE9A8p<7}1*2Mi^Y;Q{F zukwG! z69uS<2O9-#(^s=C`!mYbfU z{yDmLC7iR*_W|yoo4z=vVL#Vb&2g4&JiUa%_kkxHF5U@|FH@OX&u`w*qM@#%A*DyX zN>nN+s2cx7`q?!@b%*u7wl@l+10;pr*49?0sr(+Q>(=3EVV3m8_>G1E8rC&?D5{px zywv66kYg5y9bha)!zq6(=ZV+fJ-kc44vks(EKNO zY3kBN_pi*8^^@{4SUpzKrf0&|`#1sbUpVt@u8sKry%{7ATQ+r7;Vh8a{^nu5{etAGC7eKj`w#eXpP3&FlQ7U1NqyUc$gk4Q#CT-wyTbo`R5? z4il8q*?ls<%jqF(rR5P5Q()IeCPso_*x^Cg>bh)nUZa_Rw$Yvn?>TU)ViAC9z_+=a zW1ygdJG%BCfEKK*wAd3;$Yus_cDRx9ciVsqv$C*g{OJj7VN6I&4G05HNbkI%$_?x8 z3nrj9P@g%88Y9Fc=%i7tm+diD(E+{UYh7m-imMkYBy21|N$xL;G?woF&_+Lg zr*^t!W47N^(BN4Z`u*1TZ2Rh4p6#~wCewFAm8L@>c4};6=TYf*;qiBfK zVq#*8)q3)!YF$_k4h|}+CF~_kvufry zf&9S5SMG|6iThweGSd3Ii`V6?_c6cYR;!3y3F%|ITR=tl&1Q`S9ePZmA9^dZ!Hpqb zLZud0^5(01^0(HfSmWXo)SPtEzfHX;Q_!76H1lO6A>$2LfvWuka#GE66PeXEF*6a= z)(7k%66*gS~W6iP2HhGazZY*f2WvypziGu zV?DXVyK8Zvhs6d!@V0Z4Q1gRxM3t+nDdui^MwK!W@oy|{L^F0WYzoq22pjQI##sV>j%a{ zV5irqN88BGzl%>TjIf<{{kx(!q2^aGv(Ettg`)e-o+w63X-!RBZ7owY0bi!(#pOm@ z5WRMD@A^9KX1fb*%3I4fPOXK|jvVas7EPp=Og82+S>qqpW>Jlkl}6TY)U=~Tw#L$R zCSPl9ngx=Okgl;XLJ`1xBQD=NYP5;?*g%&Npd-s76FrxP6%=UbYRCAR9L`(llYRXmap1r_ub4+3E+CN ze?c|)_bI}*!jMj$bosooH(YjkJt1Uq*oy^0z|Xe3SZ{QBrEO|8S%nY_hZ+DHZlh0G z`mVuIj|Rxb;@e`!JT;JI;G1sluBRp3(;|-z?Gx_#;n&V+YJXMD=yaTBtUg;FbBR!* z*%~jF)R8YFqu+cxfTzWvmpa+Lji%owW~RUfjuVQ z(5@i4$32IBl0$%U$LaG8Z+ciX+#Upp9EVwHdBkHNK=ILLA@)im2zBOxTxqH8i&Z}d zm)$=<9ax&mWQ);fdvzVtK+-`Oh0MpTD`T>~ZAOLgR4UR9_yIPgj zP^B;6#)Q>Udo%B+IJS3g4<{yJBtUANkEb;n%-~V|o2(WC>*|747l9KJW?$4?eqmrqkNI=r61Wsw240VVu4)31e+LAhvl5<34Di9{6*2 z+pvO;?*-TU2pq2dM7rHjbGCfIxYE5t))^snsv6n|2`UoMcR+uh=X93?|Xo|3j)-W|`}EUN48h7tN2TyDx| zFd2gAb$gG2k!XHM09EUCWtv_ul`BIoSL+)0$JKkCFVcFQ6G?#-EIVN}!8t{y=({gC z91BYXX1)f>@JPnvD|(DEt3q=XMQWB#NXz{=918J`%vQY#_)HOu?K#j1zcmvTwKI*! zM}g2Jc@xH{-s4P6OdQ9XnM#0nhszJ9Vql1~dP^|9D$VmXileS({Pqp4%k#k~PO{>+ ztOYn8yA2i!3d*fif4l;(-zV?UbWT((J`V{Ik^dq%UEK5jzigt*0kbxk>f{VP)qQSh zX&5m7Gnq|dHS;quA(@$(T|YgUV+gLrUD%q^EIQ<{M{HZ&M>5GT_K z)#z&C3;pFB1G_3dtu$q165;5LuZoYC7po07_ou&wRx8M!sl3wixqVfZLh3!C3&OoV zUfH4^NIjn};YCG3QZyBNJvLMy*wblNQ~E$JkKMDWNRCo|b#K_TD_ z&Shm~Z$3Y~D3Ea3tOj-37M5gHRa1sQQP7Stk&%Jb)zyy(FxtZf4u98E{Rhi{all8! zkB&;H`EyZ~S@<`_#Ub+Y@$Ft*MBA=6GgsgZUAA>}7>_4YSC%zIM#7*5s9c6bvY_4_ zO&P24OFf^=7sECn>9sk58;``Ag6D>C@q$N*I{({VZv;EwN}&D~K4J(6h?BV@NGrg@ zw-AWj#bq`Equswf&hukYXKJ%eNlDSHU`O#$E|nV!1c#k15Qe1F>*Ty@qoAOutYI|0 zQ|i%2@ke{36yfZi?f(xpx+8A|s{G!?U+^p?DH+h-&W-9~Qb#)f`*%h!sl>tD2Rg0V zcX4sbE0>e6v2J7~I;*-3*>7GTteS`-e%%dF(LkI1$PG z;&eKIYFL5C`4&EO16^RFL$gCiMneOWnwl!}4z1fvwi67ZBr6Lag+br{e6#PkHwdk^ z9yh4(NRAVtCeHBrZ_|@8?SSbZdE72dQu*s|WwSXG77y2J4M9=OO~#5e1f(|7*==!9 z-F_4mjtf=j^~Dk<1M+!ava=JmvB*}(eX-!+U?w+vRf!8G-aW_{oX8Y@J2g~X36P;) zj(9XSG@377*cYNc&iM`Zxu?3Svr!{vGZIQAmSy#+I09_R_$=yI-yQt}VFsd1_G>)q z5N)7|(XB2oW~T#*)O1079xA=KwJJq8LQOE}dWhk{L4&W)k4iH_^3d7tQu#avvb_`L zCTrRggcAOqWMUni0*n7JJjGsc1MGspi9hQt=GYxV60n&9P6m)E-knl^;c zZ8bfgI7>@Q=>mTIOEvm&+AVg>m^TMwNq1oR3TR4n#3}A`i;F2McwGOCKnV^Vpj70u z(YX>?TweEUAV$5;UO;e}zIX{3FqMCB$Q?I2+z?sKCJorW5b!vA2RGVX&S!i4{Eqkm zu?v&7{CQbU7E(PF;2aWQv74X;{SRRzwX*W2V)*TUH{|I@c0?fHJ2~Fy{j}+J;ppwn z+wiXF(;WnfSnvM5{#volydEo&Ek2Sr$!n#bqz4%gmKFU(KuI~Ep`bNZ_20v2YHXa9 z^GY`{0>aG{UNQo5Hrh93fv61)PR!&tF%`l{7{cnsabh#2fnfSO6CDu;ZkmcpiQ=g0u;1(rLqZW;3{8H9 zWdCKS6usw}gb1Hrn5ULQeq*j@8&G_2t@(Ovje-=WeAs{%MpTWIhEHSYxirdLCkbo9B?w4^O z)bIq|KVc(bs!bm`*j)kPs!M-!s6qrq3BwS z&+`h#heaS_km}si%{JC%{}zxG#)iYWBpM`EGFwwTwd7Wp{<(VGbafngDZFWDWf^=? ziTBJ0GxQmQdDBn%Q>$mjhte&ytAlOw&$-b0cT|{h7>RJXz?w8dglfK+cynYWHvk4e zWB{5N0}LC-{bBf{W^!N&HGx1DpK@DV&G)jD^`ogQP2iKg_$4IiORd z*S$PYFp;+omPWA@vUIiXyM`N`H!$c0qUz$4FUDjC2ZuXfWN`9V;4H{1wU$2LY}5$l z=y45(4s?;&Y$4l>*Yb>4K4vi!T7AYLe`V6tIyVeo=e-vF={-uH^QA^HP9#PMoji}i z=+aSKm2ldEk%H0iIHg=+YcUHvU%Gi?VqyJ$oRDj=GzGrRkr&0$XD`ZcXBy4tt_0cY zW}{JZ9IZPG+a=yC81o+hxcGR-cQTTzpr$Nz{o4Ud0M`8c zY&A3g0|~K_U?-G3XnFV2FYf0VAQ&P5yq&1O&c#8l)|#x-zgeO&qNeuW{-XZ5izpb2iHfJTF*^|cg|8C0{D&Kr=jr0c?IQa3X0ELbX30{ z*^PjT5rPBT3Tw+?)e+zC>)q9y9bfDKb{a-CHa6aOT9xKtGN2j!CVE)4%(gU~Xv}JG zXcFsK5xTp)n?;16w{at0YmcS3g*YgE_5A{RqnA!)Pb~3{fTf2J)nLZ1Yw$u&I^mN) zr|RlS*ezO{50AB3wij+jl!UN_W%$txD-fxNs?!r?+K#952+*AcgCDppwMKi4)SrAM z5BhOs@fyur!{r2O7g_#!^iz(Y@zU?>jj-aFGB`|AE&I;gr_OBA zFJzDqnJ_Kz!Fsy>jb3~8yB_}+>84+P?`^yJQw;BoIIkbZ*x=95P?&aCYn@ADl7v#@ z{D^D+6QRAhe3vvtuj96G(WwwVJq?bVD5TbSDuwmR019>e)1USKgiJ!hDJIN_a@VFI=3m%6S+%Zs3_8iS`b z=T@&5E)=6}AIGomBJGAlfh>lD2!Z}9Ln^te4sdvS)gQ0F+-JmHp1lQn_Vq>HkxQF( zgj5W^{fKE)oN10#(fvzNVS8b|V|?f1<4gU+<9sBO3MMp^7ZK?4PNvwYnF<9LZYJ#{ zXlYdrb4fcsFea%W@%F0PLo$P0?d;q_^VF)Dv0A*w$hFHylaxSx!eZ z?Xlis0pw!KCIZnZxKmVkW2+Z-Ra>l$I5%=5<#e%^e?D2)M&9AK-$Nmyj5Z zKIiwsgCdcP-@Dap1^o^yEAcv|<&2-sGtcS>e+36Wg~w1^&0YJEkBT&w*=t-nZU+yO z&~S!of~?=WQtyF0UK~fdL0>tZ?;{$SDo$)L$>%E-`d0#OUz>BucmgPIufy&64Gi|t z8R>$O-WW41B81o(k#e|O_#&$i*b?KwzTK zR+n^8NePUK3R*&{+M^RuTN`1m=b0Vd8W%zXd(8Lkx?C9~t5fS*vUsL1j_2(KK8Y7H zX3>rNTrvmDErGAr1vM$l@7Z_k-`ET$5x;m)BSlTz!V26pxdtM>*86B1G* z+$76qXVpR?Gg0P6va7T+@~%FWJL(S*rJ$H?6>!ZSDZ*nSM{bdO8qV_u%BLNgNJv_* zLA~vJ`d2sg+iJDtbW%1Cq)L zSYtKsisBFc&Ot^V{s+Z32-=>_X#X&DT4z880N!eqKti<|Y=lUzs%Q3e`wpiQxX}Z- zlpXL@g!Jy@juSe47V+h(Cw^q^%Q+6x=5aiE6_Uwjy%QIchJ;3yi-MO|#1OY7|#f}`= zGdNhkyisJdq_WYzBa8N|;}NUQ-v@YNK`~Jl^<)nZY}e=Gf|DU$*S+|7Tx6rI=p>CN z)U;_j<3gIEwe)OF-b+1eM-b`Vu2hMrO4q-y1#Zq1shs^1aNP$gL0i50(lo(%5S)Tq z=PIg#OexI-mDTaXmU8>*VvdPmRhBw`D}iOSNc|dWu^_`FPSBdJr&M*hFp4|w+VNC; zoop)pNmGjwIsQs{I`j1QQ-!zd0p{+lvY$4O$h)DIuu_-ocX{{#GCF{5L*LhMvarDsD z0;44z3kr%t4@=DlRwjYW-E@XhHNA*oRgOBhLx*a~L)g&5K1b}}?S+j2hh0c>l_aX48%))X8~$x7Rwu-I>;3vka^eSUNUADBxWRGu2`laODUX6j;{VcsR4U{Eb;9{J3e+6NNY{qW%(eI6dg3!zA)ou}4Q~t5 zDOO*n*p_|6LwHV3Y>s9#@To{jT24mN=JXeQ<){>upP`#Qj+40Mgqu@&^`?({WFsln z8kb>l7)SH7jTNY&)8|=FtpV0Pk6b>E9NUHM=BvmXJfz`4?`2WK_B{=A_T47rldFfp*vPU^kUc8s%X>+=Y24+*! z>kfIyhRH~G6rsF){txG+o3ma_pKA3zAdr%_GgB+p*Ip&8cVK&hf;~n~;Rqwuy=Gxj zg#!l%r}(Va?}=Sm(KtDW{nUMXey(lhufI%~?eKh{0idm2fYW2&Xt#+2fM_S{EsOxv zn)2kC3?{TBBrYzVK;hyYxgBJXz2O>S`l7{?QI{?t6%FNygHW^oJ0-Q2ezhY zi}Ul1OC)0MgSH${;Pi5U%@5B1tv5ULJ+7g?KEJFioL0S>36>y&Z~Tn>^LZcf{q?*? z21@D6(CB7wh|_To41-RiS7fK&bYg}7cd5Mi)8*#P>x1?CmEV`JmKF}X%?gnEa$=7J ztrajDBsjf)ilio;e_=@@#&<^r4A}fK>MC%Bh2$X+Dp_wU;7S=OW5Cafn}8yN>NS8w z!CQcwy20XrlYtx^9X+7&gg#`A;s*)&N}8Q)*Bblkj7QSlNdcfGIust4<7T#l}G#Tmr%{OPe8AB!tJ_UZPo3~jE|33zQYRk^_!5}ER-RmrkTi+^-;-? z{VT5x?EcD6MAF)!8XK6MjbHqOs`X^C4ggE(2Im`XtWh`Tixrqrhee5ufNG-Q8@*N| zREO&yBk(*)CI+2Wq=&P`pwd!G5D<_^`VBfpM*UwTv;T5g6G#RiQ3=PFZ~c2yLa=yk z*5ypB&|LLr&@CaMq4ih-fFoiG~clJ|42p8T3`|AJ13ZZJ%LlJ6FqJOFHJ z-6tj#2g45Q=;UP7A7k~(=kuDZ`A8)eCqhK-jg(whQCJuVK!?X|PQpS$E7rg7%2M?O zM-QbLOlV$ZG`kn>(u~0V#e)A{)}O^wRz^mnz-)Yp-0|t{s;42A0BqE2Gys(-S=R;n9eZriPXBFxR@M(1miIwsTyfsUtf(r-(Tn;0EjZ| zWJPh7Jm_CYcr1M_G>aa*cQ@KDe&ZozO^~A`HA@7GuX$ExirZvggyTcD7A%@yPC!&u zu#9SSy%=z`;J5aE*bAT50xlX zryM*uELMI0gi%m?BZ;h+n;j!k?Fus6 zfG`n;FOS7N>zif&-g7Wi2!Scm9ggq615pHC9fQQ+`Fgj~G%G3!_HUVl*?5>~nX%1D zvNr~wCpChL=B?gtv%OPjS_BZvXx^9?ytNXcosU>QJN`37kl8xz!lBBPOA)kM?52#4 zs3>XyfKKKhBO{}(-3I_bO>cY;P$&}IAI?;dxG{wOZ1Bh2{9Aq*K*rRu4G*6y5@#iL zd%d@O)X+$ZL-}6DhC2kM#Fajk#Zjc$U^dhZf$8_}-O_;C0iz+SRELcBC`2_&CQ;N~ zXgd9=)@e&zd~T?#Gki<%YUMVz)Q6nDRI`{ZW2nSk3<;f2*EhF5m*&Ts9r?6bjv^&lvPNjv9< zVL{gNqz9TW4c55U%Ol)rH~Je_SNYB>*xm)R+-*2)b0a^&*KoP|RHl&F6hIv_#;#@I zhMV&n!lxt{o*Xr~vIg3^Ky7sB=X#s?;biVtUJ-7C6tJmiXpOG3`7Kyy@k2{q-7)h( zuAJj)jr)KBfq?X8Ya*W4Pz2%JXpvyV`Z2sl5M)|z%G(3|AFF}3HgcSAm7vy;s90D& zoz?ia6-eF@hkh}^N!mWHeim=&;V}$f=Gqdv(xh;?8^E#jmH7q5q0cR2tzM_tkJVgJ z2f^=wZ-pX~qR0&Rcsomr*pKv;h9)6~nY@^c^!^R6Zw4%+@%;Z{l;aO;HyLBk zjN=_Ewq)1bOHPJQd1MgiYbC@rfvR%bePRs^My!d~M0&G_#j%kD?Mrr%tcp)GWI$p6 z;#Fd3v}Cl}+j;p~Yr84mcsu+aL{e7J9|WfnW}7IsPAf4!zuSGLY;CO;+|59 zH^`VN8U@QAo;J9xUTZ0DFZiBZ%r}(dOc>&Bv~;g;+Ef)Z#FGLzGgo1~m4fv5cylOI zJnVywTh^<2l8V1w<9SLxsGo}nUP(||3d*AEUGf!8UfU$CN{llPNoXLD$v8D&NAM@{4 z@STvk2yaKKiz}lRvVW)qW4gT~U*LwOJ%QfL^B{SFa4<~>nlhRA3Os^7Tr5dXUN)kg zUV%HG{!g~5#9IAx1f$-iPf#RmB;wsgpBF*=y27H<6DL4jp0w_6<36$GTz+<5aVQ;I z_j;#9KxXpZP(>W+w+?c0GCGs<2NT8TV>MqC-xp6-i|zeJ?uo!OslE;K|J|T`yaU(O4;wMwIs@KM+A zJ6_F0wTEUxA%+icu?6y<2)R~rhHw}tD&u}s{sFF&dRXm|MWVm;BbXh^`L7KNs(Up+ZA;3gPOSj&e=5yH5|rS)9i39 z@(;8{&B^VU^in7pkC?N_kfL5A+#O}kBgOEZxSf&J{(@YUXxh^{*m=uz$ZtOuZit^i zrKMV3UWtuJ$dk|jg)#rJJ1-Kt6Z!_CNEB05K`LP>C9S&kBvf!{qPpAXK%3Gl5_yAr|doA?DqH35UIXn^P+ef9h?Z!Lw~PeCZ$GH8vdfkV(ng*P?s z8NaQq(AI0SPve^L{XGjZWNBG#E+{b@WRE40xwjF$yJ|ZmLWr1jl4nQZ8Q$hrAmA*# zpd45`m5kSj6pYy_6rg_^=}5-u#@CqNQ{6wW=Rq2L{Zd|OIYXn#vZrVcs*6DI#cM-f zT`l}WnsYw~Z>u51Ti~cGINVVnIkXQyA#^1P@^tZW7ncE*{O_v)qA;ExOKPH9G0q(J zI;@4oqkb*iZ2jSZfl5V_+34nZ{w3Jt9Ey4DHb zPc8<$r+_P;IX&&*t4$)fOt@ChH9=+ZDuppjK2mzlrbe?T)x|;Q?x=yL22$=go2lUK zY0uW>DSE$^tf!UTN4H^3$Fh$_Gzw#)=%mwwlV_9{cw=9?18aaPMEv$C>q3rtAV0Z6Y znz-h?xRWc*bm!x6-Y1ClRJ>;{2_Hr5eZALyE_JuYS-D3ipLR{l3Pl_Mu=LO zt6x({9UNE#)T)A7*RrV6r=Xkl`wt|{t0bkH`z6}Rs|J`F@4)M+e&1SGW=xOAtI|t- z5_(G3t53h0s3k_8P84hVkG~ETfUvjOm8j_*3G%P#o8JK@C@nA?^b*|RE>=TljEJWf{t3(W`AvU%1%!rsKRQf2Nih3D$Bry ziy!Voy!7>a-1I#iV_6_iAtncA%oW6K2~FsW$Qcx`?RI+ye+AS;+oAN~jRjB#zST7GU;ap3lMgQ1O?e*;go(L`mJ z5q=E~V6L8r#Pnax26#7rxuJrRQvb`{v7w1c@M48JtGXy4ZP3=&I-1N-F0+Q#1Okxl zWaBa_RE&F(bpp9?s93`$;BECift3T!fG9O-{cne6!8roeBfO?Ym&#kmKS^omW66Wc zXrcCiVTJzeEC>ukV>Du@VFnVwCc4r|mx+qepRM?I7br0UR`E}iv$bC~8G1q+>74mj zX=1Wc&1LJ{nf2sDP-Cmh^sP6b(VNLrjLK`ZF zfTj3#2c$MrK_{v>JRIu&&pPJ$a*g>%Soz})VEfng$J2I$3DVwJ66GWbr|CFaSq1E8 zP8wLmP(9S}Jd3xL_&wHM?8zwDBz7CRLJammKj2ICA{;_EXbeTuT4KZUGK=1P_~O=i}E)zP%$jE_-H2WwzvY>mIJ$w3iIO^m>O1jPrrxs$(T0<@=_- za>GsGz(qai_<7;kW$AZ)KABSy*EOhbLRJ5+407&p2IxnCd{jabIa!$KMtqNfNnuts zD6%LsJ>B5;U~HvP5($dm!;u zJb`DhUoegI)GN&`9asL5SHno?9|0P|tg{Ye<6Ap;L#z%D8C9VcGLEHoerhDcRtaT4 zWr4~0K!F3J$&p!6oFwduB}^r8aB3h8h4#aE6!_#T(b_F`3NZQ-f43x35Sz!o#Zt92 zR8vJEVfBc5USChJmf4~$V7;+jZ{*K(vz<(2gfUW4M}&2gH_0e!nc)?&QJHfHI+NkE zHm0H)!z?GN{VU7Dx&ma`QM!^hdC`J9Uh^k1&L&$ZeMW)z_eCv}s9Gh#evn0Msod35@`<8E=8^ma!qvPc}%3qpJ0c;l^7u#a=R!592rTm~b*~-tB-H8aH^#Rk z#V29U{)t2PS@&R-H$2gVwvYy06JWg3Fs7yk7fqA-Rb`=RW5_Fb4(&oC^Z$i=HS`o6 zxN3UZ$m}N+!MBYs^Ei7bPSZZ;XK-(Z5b^;1I~Awif~>BD!oX3OFC-k6>|obyorEOMHqN zLd^?bZL=%WZMOM)?Bh`kfDnt!!pf?NGMq>*y|cF$2moj-uv^Wx>r6hcKPZ4GWzzit z1fUNfT31&GGE(vI@W5d)0RdR02}nG5eEeek-WNb@yA`iCj6_8P*J`(_wavDlL-|We zh0Cme9kf;b`QIKYG4S)?1mc@IURY2VJgUz^VZ>UuXxMQ7c^OCSmoI`MiNhDoLcFl` zfY?y8FD(Mi3rY$`T+P%P3vIrUEVvp%=-PmyZtwj@AueBikSq2qU- zG;?=`;TbWKqo(XAw?&xSJ%?%TT`-P+@(zZv zvCl?dR7G0h3Q!m$m&IX2BtYaZe|wURU!_?-UYSl&QPE+{Wi>lIMsDAe!q zb-zDBzJGYg08#-|M9P&*Z}&hf3FYPGbpU#0m3E66!!BU61q&t%xub#-R23sFfR}g0 ztS{z)cyP^)K=?)=?uKO5sbic{#t>1X@rX*;*c_+{xczVulk0G*ca18Jts4xjP9XD~ zQ^9d6Bv0ddBd)kj>Z%to*`vLctjq$G1y5DF9m)*u-0uVJZ7q~xOXm0fQ_2~R}wu^4GP^Ks| zX}j5u4vWbUoSRGF{FL}yWP%`dTU%TE9k5<&X6xJ7(0LIIfhXcVk0y}H@WLbsXJ^AK zp8&Sd{KY!Nf2Yd+`T0cXF4YRP0>vV+N%V`|-9D*?_lITu$=BspN8|U~3C2m?jUsVG zjC1kA8-SQer*|z~toI*C*$Q3@BZ%Feyl8E2ANN;K1hj7A+=C0?@|u{M0_see{1Cmc3QFu5-h+LJgmJ{bbEo12^Ke@GQI z?FNX(Q2t=-RrecU;fzm-)2KB77KNTQY7Ijo3 zVr)O6Oopop0KrV*Hgn-sz^7>0@7b(z!c=9S9R`E{Xr9xTGitgXH7&v&3$QTa^Fk9W$ytS>k{F}rID z$6fHWevNbTTXWujQ$fba3b*UAVXTi!Xy#s9(d_M8z(RP4HZ;qR0^n(bgOZ?DLM%m- zuZw4n(MSXU8NhbGr^B@0@-;XgAVkY!#(to{#H>E9^&#Y&5;Zu1^nM0<@6d#v%7W5* zP6>W0!n1kif#vUR8yyF!q_Z*_R3Flh73Ry?Rp~*fv^p0S7e<%5bqe=Mi-i0{Uh%l$ z2Purqbq1wpi@scdyL2^UT2d3A4nu^BlN?ML0lwfW$JV4`l%k1rINbqish+O2H-slu z_Bl8@AYoy`;fVFzziwxh@XHH`G+6A#VyMQjFwn+jZ*DR)peplQ-7>a}n3{uCt@N3EE)r`W2<=6fA9_Vts)?*9}Eo&N0!{;3BeQ%EY)$R zr|9SM4e>R2(tCR)qN=Vgn0gj!>fUP-9GogbS#CX=5o&~vm)(1;T(Up#lU`T zp^stVAz=DvvoLqMv6fLykP^|c%U@(mz>tG;eRC-bD~Xw8vY(RF=^<1&+JCMyr#&ev_jb)gh^b_%aLZEaFeCD=#Qz_KbDV-7hUJI5G{u2bRCO-*(kDM&)A0uogKqDm5Lh98J4m5 zx}XeLw@h?jh=iX76;;2IGGP(UH+nLl5MKFkvle*|JWSQYW4?n9b_aXy$6H}}!cY<# z-s(}$FU-R?);DVUc5b6-LQ-N%M#wKpLw8Tc_=w(6+4Ca9hF1rxRUlf5E0i$}ywyS&?eF}d#G36?jCd0rBsY9wBjknm3o137 zOJ8rH*IG$WY3=FMqaU?A-G5@{cW6us@{0Rr;w?s+u~Z%~r#~?;S51MlzC8H8&EohW zcWEBSM|=uuMmH@Yhfr%;Ow#TS!_b%Big?1qjZX}Wm~Wv%7zyEWmwPvGwC_%U5i@zw zfBasN8k^2jhVP3rkDPO4JH~UY?AciDea?OFbH@Lx9Y@9dJ{%2sg1gxb3m4+y)oFV-&Rmoq8)drc2s6F zd4nTRw~d0q?uk1-O80w;@}{O1589a5Exg_fHIe)t!P+)Qvv6utJJ>bQ>20$D(Iyk_ zC6bO%SZASW%#@SK`~07R?q?9psjjeg|CzEqv2>WI=J-)yD4b!ftCl+NP4LzR%PWc} zwc-a1Km!QSwHv#y{V+s4*nq?^B-+?OLc~xnc<$t&-%liQ2qK?;T5S?6LhWtLLtCRY zWEbho+Gqw9Mofew#1OVCWvhMu!|-i|!+uk8!LOdjhldtttV#!|zLj=jB+~59Hr&1I z=Cts*=6xjW|JitXrd1D8t+-p`ZF7fMNApq1k_dIWlFx+T=^s)bch7OfX6sqQ(gVwN z(ws)7e+4krrZ#`3d4R3B0+v3BWy>zy?Z-4d2h5{J}J&dvpm zH>PyIZ*wlr_q7A-g&$u6v5Pd%bht@9GlXZ&&VnAiI91_GFeQ~e|MLU%#``PC^T@vG%Q$nMe5E{a<=JlUXg8?9$9j2 z8`q3nfV2VVzGn059Sd9c)&v{m8<61F$Q2z{!pLY7wn%q++AlZOPe5x=Zu-98MHg%O zv9H^SnAvaY>o3+8Lx-d-3;o`98Mjpx8s>Ra*esj;@Iq52neAo?- zF_@@=W1M9iccS|2SdPGy@YL&h`jX_>1RCXYWg49(^x%n4T;h-Jbo66BP2Y$sFtTGl z$3Amo@*i$dt^HQR%04D;<_P&?VM9~RwiQ;SbOY&F+q9OPc?oSlrR=ubM$n%p&)6pE zQtYB6=~9c3wh(sC`EVf@r%{ksgVcFf6FySzi-MA3+uF4t8unSuTmpyvp#?2%`>i$4 zOZ$q^h6EUa@WEtOL@%7E-})>^T*X(H6`3V$CfpYR4Swhbzm+~P@L^sWKzl#Ko$A?mKSV%t5 zdKUI*5Ijwu7zuYGaAX@l8_J4`qiRFwl9~k{Q!#em&iNGQqdl*&K}2zP5I~9g6x$?# zKUo?PUSu^eZ}l< zKI~Is;lW-`6fWm@wAxcV0HZ@p+v^|P?;0hnc_Ak8`A5PpyoedSi{{^qm6z{mHIr;a zTU^XS_(wCZ9wyj2zqOYBI%Q_rcjsU}bM4HzIBGaTEW{=j`3Pem8!|VCqAuvfjlEA8 zq>=I_<52xAHEi`6r#iZ1(j&p0?GAAcCA`onp}hDKP^nQvo7>u6fz~fRsE~;tE;Ne^ z$+Jta*YS<@yWjCq!wK8ops@HaZWL4e2>>Uzf7$6%P*J=qdUiL{5fbl^-1$odYpeV!(Cd(2BV0124@=?7QRr8UHIM zBQ@3M-y*$QSR1ZJ(y8Ktp(ckSw*)FUF}y&OUes7biaZ%_nh|5FIvJ+v5PRrie~e04 z9_;X>ETA)Tt(ne}#0UA9m7S`Ae#zczJG9yJ%(8`+gnJ3F0<5_o3!C+(2>%cC< z{+-99E*3s|H2g30A3m^kY~>S;R`?5A^o_R-|GrRjP2IfoXRCbVW1PETD#A~$Ac%>N z2@IONKe{pW;0$`F_;kq<3wZ>CPW4kcYi4Gqth$=XMI(6l`2J-(N>f7t`cBG@q4MI! zmEkDq-Ssfio&fa>pUTe}{G_=qq6LU$o&)VMcru3Y*$+ufoXT-Y=eAZj+;{gKudD>@ zpfrc)`)8weU~G~`f|?KCJqSEc_mMQ=%sFBC?CzEk5)b#O^x;LOElkNrMTJ@srhyqD zU(cb3)XEnkEh{ci@R=XIX(AUS9z|@DcuQ#}N^Vf1hyz|ps3B3bSR=h-zZb>EHuu@l zkk=l2L3jA;c(R}p%e^?t8z0M}uUv`69n#~ZW^|R$;{BG11$gMA)WU?Y>e%{C%SlQ* zQ!`7zEDg;nq;7G*N1RzyZep5p7SW$FGb9cRFrV>I2qLIGxchZ4h+Lg6QC1yQ`A7J} z-Fbvf;4&c;yVvW@Jbr2$rh#sip9>jDEX_163iMhP7`4^n=diE+ai(KQKlo!PM#mLh zk~3hg0dw_|uD7@MYVq+QNXYYs6{61NP=~{8Bx7uag@VGr`Az$5V#Wzm)IB-del3M7 z+ST(#&vD2a>^D{^BU#_?l23^GNcx#`ynZ^pEKR4u1WJByrcB;YUjAC6=Wl6N1Fs&m z)Jpslsze+z?yHVAN!gow+O$AGm^fh4v!&iP9rk9%dni=%Nn#!Hs-yvf$CRaP&C3-N^!)3Y*lpuX(r+4+<% zdg(<8Q*q0ove13MS%*&Co6}0YbnB9CsIt$CGSwgbz*-vk@eLnrm{hRG(?Q1g{ z5kCSN8u9SO=jU@XQ>+NKw>t7>i@)WGvzbhJ(txzs_xX_#=n}AESnYPo<8EQGJ**=7X4?0m#x1o6>rcA%+*6i4U;GhM zSNIxrkdG%rpIs((!6{qR(2fTPf>2;tv%4z>>ct_v70+VTXPe!*G2CoyY;c7^Do)67cs%N za%*UiM1LjhmTvFKPb|r>HV9ox-8IWwt8S%G`W4Dj#I*MyvRRo$TS%ur9g@si$?iKj z>aNc3#Xvdun`U`WH5&OV(bP$Ahk_)AqcyjMfzFDvrbIi5SZMq$;SYJ@K6`kDBD$b~ zxgX>jnu=6!NfW5Pu`=-&N)KzsnRzrl(0_r>dSuhz_JqURKNL2pDzx-$K|l|BhhK0} znj3x!B5Q`A^URpQLPZ4=ghFCsGXp|NWS~xbx{2`7I8DF}-SB64JBaEje3vIeB;t#M z6J=pxsj`}`7*l-(z#GmCEb`8Nkex9kmgR~+xBX4^Ke$x*d{5y-zEitGsJCXftZ6zX zJ*q6-Wwn+o-_1uHsE5gx*@Mx-GT$WYhF;nZ^hj}J4G7{Ld5btkpv6g|@AS4SLE9Mu7r*WKzuR)+Br zrMuH=3O5^PQpb+A(a`J?)(m#DLzTZZB*K4PbVf+anUIgjcL&$I*OlbOu9enEc5scXZ649MahpB#z*aNQLS> zbRhM?VivVU$Ls34uzLOAii{pHf+KmhmQ?w>^~``xS#_a*<#&=wD}DK6D>t&Ork9iX zM2K;x+RZgHpKz^z$IUNllJXigX*+CrR3XC0lqzB@a-Zq-k^l_LPh@GRG5eD4wr>iG zOoSMbFfk7=ZzkXZsMmcU zdya_v3~6?~Wp2uPvi}=__Zkt=E1vBq=H1)t_NTnVTafBNjORjAKMa;*AM* z!h;>Q?dr+ygPSl?hc7#dy6}>AtCzofOD1DxHeGylz9LlrgMivMp*CX?T`?p}iSr(J zD;wGmg+qs%A#qW1Ud?GQ3{PQ1ChSd=Nq?oyiwv7WUdn7ptZED|UmqmVEz-mCIghj+ zC86N5Q#?*Vi9w5}ZQg%@bK>+hI6aH*7-PG>7k8?%&>MdniYv^IVQ6ZSRWHM(>9VSW z9J>HPf36K*?aR(N_H(#{kK#d_p5Zd~iH%}G&xkOkph@%>8|4zPQQ+rx$5>pqAHvj+ ztzFJOR7(7Q{;g;a!O7U%NC#nuUV86%5vOkvy?8Sq@@Q+n?|`POxnu3Tt9EJTr8`UP zYM&ZngNB0)B2mmEl%(}~Fj9)dTJdb<>hs-u84hQzjw+4$qrOAV-RGi$+5~(_Wi#Fj zE>Pt*=XlC--?YE7I_fOm87g6Vc$NSc=`ksRsJ)qey^@2=+B~A&a27lH^oyY3>BRn` z2h3$tAIpNw%Ihr$S6t1ER&k2d!wVu019Af)+!3c01E=+F+(gJI_OxygM|%Xdpcgv0~7nSbMd=^z*1j4gWcnUr^)&0 z+1ZoXck~1q0nWbJVvArU4XGWA#JK3TL-r$=r)yW6`)nmFfjNa{r-)kN+@p9f|d zhy-p?(MGR{=mLu7ov=zp4M$oH4XN+8cqtKUN$Q~%(LW-DL5jQa@=P$ybhfmV>nz+y zZ+WQo1%-30b+-QOc-zE)lD<)!8Ya`@;$ULpNn0fs?9IAQF;&{4p}1=~>3qU-riN6s zm=GA$QZH-vUSq5?xOA=jPVoT+-EL7BQge90xI~BkFe|&{$4pBM!sQBu_g?D_>N3fE zt7wS;T;~YZt@^Ur4uWh~#y0O5(^}K8%wF?Jbp2+3*eB!G~}HyAFfaWe4xihGLW3RS|-6Jft`VG=?9#z`+T|Ajk| z;mG$pxf%Yw)64dn#LE+Ok%E&Zsk)f;lAb!4iBTbFV?vUvTRtoNaq+JoKbAQZ^@|cO zyry3I5fa2xaf!Vm)L_fdSCiX)yyC^$fPCs>oZY|*Jj|*%$E$yJFI=~8zB%ydX8n8t zT&)7c-HBqu1c8E03CI$jer?cLn0*6BApCGep;@cf4#}ulgXrw+?0R!XS5a9R6Bh?9 z*z8gMPL&q!wTujc+zoSJlW5oH=Y7)B*1fN31=6nhDFaVqkI@NcKNPNcsoQJAuqnsO zMexTW2uD{|$4yO@ZeTkns!>n0N>a-_`loL3lgB4~NxzdSM67UNw>uuhzTkg_Mb4A@ zcA3pb1<}~p_{l7yrv4P|zPk3LDfT{G#)+g<75x z7kDk0hZ1mjl#uz2FN|;h9K&)P&flJs#CQ3xsOadB!otF;IMMH${=Db1GA8(c*0^3v zW8*HHKQk}71!l}<;H+7$)v_0ISZn}xiL6DBl!?}g zSVG2G6lL`9-|Nn3+SKG+*S2#sl|IH0nZE!HaxBqv!KEfuXFqW0z#$$R`n?+U&!9U9t{sSfuFT@FlC#i>o4Hs{8Z! z08>59(f$cc?dcXOJArm)<_qTk?h%dpHK1?h2Qf=g%9RU1q;|GSb^fH|pYn`akRvT6 zrBT^T!R4!bQFYFllydp*Sfc48F1d(e)i z0FI!{cJ$fKj72b_tACq>xc6N%@}j;>EirR6%pXx!n&X@!XUF3=O2wv+iKKga-Zz;o8fNcc7jjcZCAFfy>XM_(G@!3yi0j2AaTIgt7m4>mcSO%_2RVqO()z%Aq{w^ zZ9%D}pY`4kY#6@399!sm{UyUag97=xJDM$5MQ0@j_9Lp_xh(G}+U6%$7DX~`>ud>I z;rhnqa9C*{+2%&{FIq+xkviji02C(B5FAQ?fkQ;>%n20s`)Uh1&Dh^HX(Tv~tGa(C z-?EOF@U_@|@fRZ)7Sm8c`^_B*E**?Nha&Yc!aC?VGq&tqvy7{*`RWO{Rv3b{pFNco z%w}ttWW-t2bvipbpkAO5cLJE19f&hjSxzE9JUpxj5hjZq7}9;dJcQ9rZJK$z{x3Ny zzTnDoNC^5G-T)#u1fLT|mmqGgVz(B9r}`d0c17uki#&}gP)pd01mI$}gv+yYP~zXo zv9XSJ9bJ1KPxYhVT+GKkkD)xnXvGMht{dbR$@Nh)xA%q2KnfIZjnsQPq`BVFS7JldX zhbNUYIY`YjeCo~y*c?vdc4qr{gtM;lR)$h19ykNCO$xH;!K`eZ?ShYAN)P_JD!Ou1 z#K=l%gOG&x3eL)}-3Mc^I*Flh@$fylQxT`9zuyol!32hjyPsM7Njn{{wDdH zFH=qC1%v#c(Pd)P4F;U8%%kbK%+cT9YYV_478Dc&8vF*aS0o%99O{RFsi~gK+(*`3{RI&YEcLO+=%bS~tmsgAWm$>cRD(!U$?+Gy%HBRf5XrQ&1q_WAO%+cV{DY2z5llJ4EBi+wx08T+ zcTZ1GHtz?IjjTPe+{nDwX>#=ixU1mX4+WfO{xX}9FTXu)5zlug*+sMqRhc$~xH&jF z0c8{w6SMQ<6*VR0TGtC~92#2bipmN*kIxy>k82UxnOdZu0^j3~CjQ+z_bl9ze2PN< zi46AKI6XWi22EAuFRF8fFWZs)u70wG-H1)nK1M+rKU@$jrp*gio_-k{&`|A85Wjl5 zh%G!&9X`_5lWJ>gC#S?_EYmWmo8^;>ii+y#>-&O3Uj@M+^vz{u%bu^`gBcO>JBP;) zkufZSRaZIK`#K&j5NT*=Y=L!J~b(tfdRQ4V< z+i^9CTj}%f(0SYby=F;fYSj`RfEdfF3cm5^-^L&Yp=(e^2BCtjg@z)Lz5F=nvEdsm)sHBwn&dvZ& zi0EhxZVfz*q<>H=`x^98XT4zfu^;@W&_h#HF|9r2rnne(LZ7xRXcFPEs8I_gR0p;0 z+sjG_Z95-Y0*lg`FoqAFXYC%TnyuMHpj`R*(CG72?(#7cqrTOCD>d}=b&s7>Ng16t zZ}earfXcFT(2+H*ddWJt00){G0_GYqY3j{?Q{O$@9)g9o@X^J-<`X{=P}%xr00)6V z1HKjjjsc$mfW6IQU(tiAG_P@)QgH0A_s_aS{_0B?VZTyTdJjQ; zgbl=+2iyXDNhm3%m(l(0e(j?AfDs^#EN^dzqIbIL?YgY> z#EeW#FjG>}B^!vs77rk=?}LJbJnBFZW<8cr zZ3D5`@|BsYuR2lCB=w8EFpPW8fBd}*pu>K(^ID`tQu+*4R5TaZXpH&3&9iNa4nHP}=xYn>fL8(MS7aW_64~ zMizNfhUZbG7RA|vn=b@|COR!`h|#&GriSq+3p@Mgad}M9a!=*Ws8FMnSQ{Z4yPI0Sx}nv zNQ;Yp11sABNLp(JsRY01!pq&#R+9;V(B<95R^k2wD%!NDqL0cfinKG5xDAbst55vb z6A33QP^RsH7k-y;2-LFUrFJN62KBN9lCDSV#g?!2u~?Xt(QWO2omdHH7*dToT73~% z<2%2E+Rxadb@4{Wfour^L|Z#*$uC8nDk(`cz< zrRx=9B}5XhL7Z{gA+Y#FQsMJb_JnoZIs+q-7%aPIZ`Xr)x%J=uZfP{1wVPadQc+3R zm?^aPMFG(a>T*o2CGVa57@dvmC?<mfHo)eWp39)3`isLL_6bq{ zHkv95_)JDOToe-I3z@WLe4KCu1N%MWUM9vp8nkn%)WzrfE7JSlc|0X5D-vLP6Y|usA2*> zM_Pv>6ITiou`-` z3X50a8dDg@sIL;}mr(OWakUA2O;G&3=smjk*sS{2P;Nj190qmS2}9hLX!T{!ij>n0 zJa+xJ93=W>QM?9GLPEkL@H16$^-6T>%>&26A0`8`1m@ARinpY`65N?iQL*oK#_~HD zU5qlbZvvHPM|ykVfC6mn$3;fws{x&##M!l_xQ&%rMiI6ExC8L@9@}nX>50;fuw)RD z$)f=Zq^F(8f3DE+797?P8vEQ|6*=l$1yM9o0Y47GgJ!D6nxIfk@<7X?`^`#3EPq}# zk;Y|DwG3M%utiLmZRm$Z{i2~ssT!mE6JL`RH+tQFT$rQ1by;X~EBdJ`0uTT8?;F5^ z=~=pvXJ3Vnlz=B)h=`KS+svyh*DgSLD$D9uPD*}Fgq24fBf;jSJQP^uBz%ANE-gN) zuOXTny@8m(hc+rpBmG1uENF)<;GZ)Fq2 zo1LNt<0i9>>Ydlk=V z`hV&$IJPgPj6GWlXPS5`{J~D@SFTKQe_|~0Q(L6Sg7TRZD;fj@#6ZKFb$uW&LrDQZ z3igCr;#@V1I}XAsWUrH@we=vQ8wU;jgX}<>AORqFnC#MRL>2k0z5G_ee3r!M-=+Aq zlVmhAp2_6x-oU|&pzf7#F3-l4<3>ilakUWb`Cc$tvzb2#OUg%K}kuFTq1n4`!rpthH;4vUhf``od5bU2p``BnfU z|Jj|*^eeNSbr*JY?t}>0^1VemS?Etq!JYe+QJDpq;U6ceM@+RE!J{UgbF;>Oj&1*~ z2vvZ>mt8uNJd1+1p+Wo(@13X`s1(CDx4DwxQY9DZhXowAkyd2R5RxT|p-vRXYJ^5| z+32*&cdqSzFi-0PN*JdlQUqeBd0JQvv>r}5KWo71z~#0f+?^6h5cZR99XWmkIe*3& z|B1yWx6kMMSmchLYNp(_&hpHW>F(j)Z|5b`bnU+4@W4q`^ zV`O~&-Pl0d+&00l8NnP;5ntxN{ zTSqopd)9WjciLjMMLox>c6DmFZ$y1zm^Z+?`H$|qX25@U-^XuGwZ}IGW0*Q^{IYav zfvD~ll^+04#+W1kQ6)?|A38O*nV3xSSMsZjDdjTw8CwE4P@-}uoT#a*m_m#_}+}?~mmWxf3sHoO8lVtJCH2d<^ z95#&O^Gv)LYzMmsp%`^bi~1_%t*$6fx_xcfU3> zGmG&I!_bMB>W(DRIFV?_wn~S8h3@tQG&@*HWYOKQrl~yy%iAQMIB`g!1~JJob90Yr z?aA`$rMK<%h3mBsXsXD57rp=^j4j35*E`g8dfUF|bsC8r&9UDA8@c=|IaK4Z1ygLS z>2sTs^R|MQu(_haViv?$^*Act(R1IZP$WO_W6V2=;Pohq3X|A6XcQ(f8$C zCQ^tW(9~5g=jf*Indv_b9l#i}Llr&h34X=p40rc`})3?6b^j)bc!u0 zb)E3EZ)v26-}$`${L}_b!DQ6eyOEVk+}mvj-L&~ax$Wj zl`j+(l=r5}{juzcUmC$;_;rP-YcMTn&8kPYeFpKCHJ9|-D%@?$w$X`zefRBex`97c zRI!w&-bzU`IJ?_twHc_PA!N{IbjEy&*e&PPZB}O+z2y~X;QR!Tp^zROs24jC>OcRt zEQ%IvgYcr=YrTC&l7vE&XSJupl+kz9L186GrkSs&uebekF!x^lVy$Ilm*A|vXo`uM zUJ;7o6AaVqGvCVHLW=80O@V{+x8)XEC>HqiuxCi7QY3OS)2of3RKH^DKk9D9zlCoe z;^ZMUaP2VmlG~`iXuv8VNs&l+QN?b7a9XV8WsyZ3tf)bVbb-PxyG8z+>6*AJ5siv5 zx~HPmXKFb=%1mUUvBA|R%&*K*h7yZ1?0ZE;G_W=40;SN?nJGn0eooG6@=`^WQ0~vD z#RGJuCB5>9ms5yjnF_pDGM6QQ~Q~f-{)m_4ma=hz3+2s6~Y?th40vdPgpNhCZ0S-6UaWQ=S)ERB?Jb zNqdkVrpojOUv*-5HWn$=d)j9Yva&my1^`4_{B_ zT89|~YX0A70Q%NMI}rf@XnyS6e8^Sr`ALkF6n3yPI@~>=0*&R=UJvjDLiwWV-~3)G z_jin#$eS4%g!4)ak+JRfd!us1$4@C~s^If>T3 z276u-XE&YU&mtpfF_u}}aWxg{nP*mHloa$GP=_SwFcW8Qmk3+{l`PSBKg+nAss2($&hv;^qn zWbq>~im8Y&ytbAbaJ;%3Y$Wk-x*OduZM2$PW1LP&5vVj^dgbJz0;L!Oa=OyXP}s22 zs2O&FJUeArFnGB!NTBL+4AxHug3o{RHK_Ac6I9yVx9rkGiW&$twpIs{4dj1QmFBbg z_(Ijy^(Pw$A zZ#aBYn+W-7;Kr+DUo$ZwnY1_J5JqOB$Xve2+~{7ThvDVba>oj%`d$JXEA;D^UFRmA zYFgt^jrXLq9uX7DbLs|Mp;i`R_v_^!Eh%}j79Le5N=uI=i}Fij7nTDj_x~xlOLoVv zrG{nO_`gr8_Jj=Ue)vnSS1pQDauQM99E_<@{QyITfFJ)hKQtOPCu=CZCYQnz9}Zs1 z^fXpHwr^D7*vejtMXN4aLm|JF)mZCuG<70peMx?hojI9|b$b92{lY!(nbUarM}2QI zi@c@u)de2czRfU0M=R^$1T~xHyR5>xD#rT*TgHWBqQ3|S{hDvPGV)Q+j*{o2l`^>W zjenPfkzw(Y_EWUi>J4BvwUf}OUk8->2aTcMV zqJVwji3fg{;*|qkGFu7^*78&7Iu~c3uVK9XUvi*>`z1&+x+IRVc39E^f`1KYC|5rU zNoX@rsPiR}b5ec9g2kq>?SNL**?A3218Tpz{@Tp++UGbDngj@xxw)0lk$$vZO&8%D zod8D%$B-ovC^R9K23*WSwH2y-~GY4H(LahD@%YW`_oEb5^%G!uWiCSDPLp7eNk zRTl?C3<1U$soPmlH@13=rAxXp@F7^8b;pXVeZ>6Pggrw{9W0gok}d0XtiwaqZ4clu zXkedA6RDLOCuD@Ya=PmaW zfq3`x3x>N3_w9%H2A(lP)s9Kq&qr7FW`^;!TsuFvyZ2O4(E|bwt&bf$uh8X7Hb&S- z+EVg5;awiD2={Ne4f|6}iODF6Kky4FVw|Z*-`i-25^*q?w)3K#FnyuY#Rq$V(Xpnj z&BzQRlx42ms4P}03XJ7rDMq~4yM_XKB3s!k;TVi&OpFZpGZr7LG(045L%T6-FyJW_ z(XK|%hMa&%(H39qM~sWyR&r(gM&H;oLDE{*I14~yvT;^qk3h>wJt1ZCIvrEj>O z!6mmTfv{%G_Wm)r#uHlGoH=azUPhqt8l5iWP_g?L?m5w*y8fJB)t`A}34&-{ChcDg zCMQQHI&-(5vXdUuET|pxUz2&7@YL1KxViGX7=z7|HDDvlA3ZQ z1Gld0zwb4293RXm>TO(Ja}2L8<1?kf*E@2u*sXx?kO3ZQPm|dCp}!5$Q$pt}%a!Uh ztJ9^;RG;oH#ZQo{r}8_Do*kI|$|EKgMChJdj3nc~J%#xgsNlP$3J4Vs)9~2d4{=~t zSulKrxayl0pDZXT4_css ztc4PTY+7TQr)8TFjG_qYC6GXPZ|pgcl%uL~mVNzateJ!y;a0fNvvY9A)4-nKLACE%16aq)~?2kWFX<%$qfPSXU z{+}QSHm{ljj}|Ik+sHjKdx$!-n5|oiK6=q*2}oODl?|NBOph7eXG5dVmy0zglxo%c zIq%Pu?^!&Ve9tCP*R@u;c(5z=Q|qXv6!YV(S;milBUHX7<1s7 zXK+6HrKaphwBj|K$PX^r-I|CY&DzVu-8z@b4>4MoTwDq&@B9`kC0~1l|3)`G=^oF% z409Ai!};gfv+1aKMM2N01`P@@D_Aw+fhZoX>w$^S4Nu11sqJ7?ciHaBvZ=qv=iU6w zh~m5U${w>Kp~rP}ah3Q&iKgXW6Uao*>kUsM#9fz(I)xUUOU|cH2AEk~zh2p+t2$-= z6+C3R()trTqzL<$o5Wnv;MGDU2sY%3oE7*y;cw@wVq#(j3M9x&0R&VQ6|5K%yRHUP zc`<;pA5d7xPzdF^JHe!SVGP#qNrLWNPla|1yjbys0Mc=eoVDoC48@Fuv?t8L;^Q16 z`7{2|(R=sXR2GNJ_fzAav@sl1t1ccmg$ZY!ky>2niEqE>7Q<`4@a1nNg_#@`>caA- zW)2&@mp#Zzm`{aXCB$yMRg+YGgKhFpPQwB8zc>vw0ptxU0WWgvpmsvT4-O5tMZ(FVvDa2?6olXk~G8G^}GugZf& z1N`!MoLK}JvB@a&hih}dN`MLYgO$CcAgC=OA~FCYMf57v-(pW(T)g~?Zz~E73plY^ zpR!!hX{0-X;3+$lUH8rqd&5(x@>1yGENEfhtEy5zahIufNp~x9^Yb;fR~-t34Bp!2 z)>z2P%NM;9mm1-k37Si&Fga#`?YfEyjDnZ{`~f&9CQPT2cR;?47{AJ%60%=b(&Y0i z@TZ(zoc!L_XW!`8>7JPI@k z>{s*|a>Y|UH=BEV1B~CHjc;NlpHRc26~;0bbpXG`*#6`+`(7_WdJFQo<{bUjGej@It4>IUYL#P zSYRqqZm~Awaj}QjIO_Rc;@;Gp1dN*!HQ0Zr#uBeUR=5xVhoMe`Og~BV*LxoP$lhhG)_{3-4P1H%5h>8Eu6^g~UoA`I$Pa4OMx)2*qu);?X4#VCP zI{+Ga`~_%{k?xEa2JL|1685L?+RdY*qhRJ0QY*+FRx%UE!DyqRJ5qiGF~d*@sOPMS zEMR!xoFW{q{eJALZ_ubZeSv@6yy`t@QRWe#qP)}>%ax%;6U8Nw6trr+ztG&;k}SEh z5hq;+>}&oB$U~46E;8(9X}$X)U8nHld3}x4Kn$zLy5b`k+A<2>{?(ZKJ%!`*Kmtw+d8yegKC;HyrZpCI$iAW?2!60C> zsyUmOP!9|YumX^k>O>$s-)F37bdfwg_VI^oo>%YRhLAWeE-nfWhXTMgZ6@B|K~R{^ z1$cRU(tbiJClC6E0B!C(<$e%92=!#PrjQ|yuOkZs;(?&XHI97{M%>mhILq{OHvz>< zGv+9tgJD=Dl(1hRuuF#SWuW#@a>96f(WgKI$67KUCi;E5c9-1(d?bY%f}YbGv*boF zvDjfZRz17M3J(u&pjd`H>NR1#4cyL-h|_hR2SU76<{C;^vtM3VD~Tx=_Vi17i>l3hF5;#rUt=+bJa`Z}|)zt~Fh1j8neT6s8F zXzrI@8Zkr9 zi5@Hl$8%!Ak8xBcPmHa2i+cNY_`+>dE31K;N8p)*53Vt&Jk&Kd7FP^LCz{&%Q6kS) znRlRzNM0$9nr7z3nW&~-p+yKtW7qwe%{JWmvL>s7{Q2;609i?g-0~Bt&fVCtzbK+`m>;QVfuJ@uJSj>60gdC2sN#1vd>&jPGcj z-55d8W9K#;19Pl~T8eQQlcscr z@RFHck#UudmQHUyH(hUX?u#ey0-!9tF?qfv@C#$Gp?>Lh!{L~^?rvS>`CAK=OJpRO z=2i)s7vWzQ5mw&JotA_eq9jk^Fnz2S)#^0KSu*PMH>m9$&kHGWN}ak7I==4Zo8RHd zE{-AcQ(9OtH{emKWLK0?Az(rzD4SjMRXe^dJRLvrs3ls9H1~#ww%ZDmoD*7yQjx;~ z1U18}xA&vYdJY3-t(YNfy?;UO63(2!BEd)|*<+7a!2B1bs!=kZoq`rImi4!^2FkIg z_Kd-u*X=%cjc~jLm}y}QM=JR7Oiw%&j{ZWnMBDy2_LZ`dlIZK#&=?UIH1YwAm4DE; zDl@KP8<&Q<3ICo$rZZxfyNEK+W^?{wV2TTZT>u9_o$rZ1k%K9HI@h08$3MgF)p5ND zm8P16_aGvU%APG<9mi;Yz`#ELB-8(V+-5<=ZtuULyp|z(u%0CTT`fUj^BX0$gV0MU zRj&nthilg!h+JV>1q4HL7)5s9kX^i|zG~h!3okPZbWn^!OG4M)A99gbMV7a14xk_y zBA8wlTz-HJT*!EhY;d+U z3eZn_}+;Sn2 z%FcC=4q6D=*O0(noj0*{uDhK{t=xUyZLawa60ZEPrS5TFX1LsVT1rds8dfp0991GS zw=c;>h48km4`Zr+i}WD%L`2kaJppb<={uX|1I~Hg$W3Bz=@YX-LkXI>$;40s=R5hVu`;9Av$2{VTj`3GZH6ifKAO4$wdzIT(5O zU@mr`(>M6@Yu&Sa{>AD6r0JIo>?u@qlDBp7Y|bj4 zrM$mFrVO8{6{)ue;MA-2-b-t@zRYBA?f28`q}{%H;dCrUTD_gZ!V{<>f%DimbB6AkyTr7VxRj$dHux#51fX) zf2rZ@s{o-}9{lMX`c?`dHWeK6DRVV}_vp=JtTEKASj#uU@c8Oc$?iCfhXf(P(4H|@ zaMaCUUQGPuu&{}rok9ziYY`!6w1I17d^dlK_z zGq6pp)7u1Sy$7d*NEcc2iNTHNz%#D;Ov!is7c4(|Xq+O0yrfZV7_X2}T73?XO!_Kf zdQT*a=V^Nld!or0iK7j$1E<@^VbDt{pDYE9WPBEu(w89?(i2kh^30AHFhXi+tMPAH zIjY9>R>}WA&fYREtL|MFMWj2VB?Y7#38lMJxIq&Z~7vQyt7NIPfm@J!~owD#fO2@Q~kIafk3TUMc*ZGIz$EBwsgHd40f(@4DhkdMF z=V&5T3`5meaBC2NTO%Bg)!$(@!#5aEsmbPm05mZtESk0D_Ta5D%#$e{;oGOsZ(&qp z;_>^kmK%ikg!=E&M{;C)2=Vax4!i&>lFt7aP%-yX;(Pq0yGr~x6uONH&5A0GUEwR} zHz>Ajs5CX=#NZ?xqzQC+JG52mnEye@EU0mt>ZYSPtf0iAI;7ureRlg5Z z3~>D0Jy_Y?^xyGeN{jf3=Ri$LOPM+jEDOsar`U+~f2H~uTS`8kWSN+lpaXY_hU?D9 ztq)xXu_-bOrdLOR&@}GK8?$WB$d)p zx0%Aa(-TuBK~x+2tqB6!|0g3m$T%8bHQG?>=83-=s|c5*RZ1>Sg?M#$edk8|0Ie{ z6sun~;ZII>v@c+)JI?L1qM{Q}%nGOP6`Q|YvX5IxZ?a*FLG1h~ze|3D;o*LccPEu! z^j`bv#@R@DyPfc@wh$$A{OdBI>YY|K*5t@sHhhdJwv44|u1U}#0o=n;+=TA;!gQ;K zwBLJaP2+oV1nxWq9*c&%q7LeB zGM2L3IR;z8a8^7fBNnEaN7~x?{C``pRxtTC%%s-2P~HbE;-2Qs}s-Kb3k|ov~X4YYR+Q zJmf5kT`QRud|U%0SMhWus47WMLVLwhf{v$PDodNE=Miw6JYe*c@? z71)esFCZQm$y5+(6tRN<&!_*{G&nIEmk+~sLNp=+-|vaFd>`t!AVoFa0I2VS$kOl} z07~itp0H}uSQrb+fyrrdDgw5cAII<(f->foDh0JtZU!O^izxz z-#ez<{^3TN@RE`;__+^0);>Njw8Ud9y}Xgy&3tM?}<#*ubIOb1IW~EBMYe{;s*%ZCgJIfmtF9`2JIPXxkZuNY((- z_|GkRwbxOV&IL{^^-e=WL+UW>=S;aL3@`G;`iML|J&W13vqZyY?5Om=(0Rx73bG?f zyf~Pvi>UV_eEE_I#8j~ms{*e0Y6i&GQaCMuE@z<*ftD|%$^ije?mr=^Ec*?fyceO$aWGI*7QOIHIl%u&v0I>pK)?>GF5YUXN+Uhl+d#8e$H2R0@7Om=9;; za>c?jfL8L$vGQQyyUmw+hi&$JBSZ?{m%Q!?>JR`k>%*{a0ZkWK$-!R-)ZRCs2n3oU zs@NmUTE?e*Vo;pMwN;o-7QVwNvvxUL5-4J9D4^KC>_}33%eFk1>)% zZl{ddrKF^&6{nB1t6EC~Qq~^~B1Sk?9^UA>vaYTtQzV2W46nHzw-w|it&V&Xa(=ku zvKx-xqHv-&zQ%sxHBod0^bd3`5uL;W6uRI&kwB18>@A463OsE?0(lr7{(hu;I8D}z z-Yy9n%gIz9iAnMk@xiAUCHQ#&VOGUC-CiD=%t_ouO@8gKdf_WsBaf;JY65Og<;w_z zXMj>Yv~w4A9+8p2t3UKFjoy?3LZUNrCJJWY{STB^KA%uzjE^csS|A&E%M_%|{ zQAtVNoS@#jfPFrG6`BGWzJXDF4(NM+Xw;yC+NvFh71ZHzc7~-O!%a^Wet&I!xCz-8 z%Cuf?`&>*&L`ruspDls;;XVtu4cK#G(^9{K`G=}YlM1DnH1>+zG{33~==&97(KTjS zSXk5qHdi+V;E7)R7NeibrywZFigRk`b_as#L5e1q(+|>V@9;!LMT_$o)nN)aSd0Y` zrB>0}qZ!%_Mx)CwHd^GwEW`S@{nW8~;Ul8YNUe=BaP4G_jf28clNmlrP)UijM$x`q z(tjx%TMjBl10|(#11segUj*#0$>Y=0I8#$o`Tdbhn!w!xJmJ#n!Vx$SNo_<`WI!h2 z=9H|sxOY_*E2b}W7n8$T29j?79afyxw`p>Y??(>2IeK|$|12(;#PK~k1{qIDiFks7 z%E1#CRWlBO<7;e$RulOu97YBC71f)FgRc-xAPn&>s?q%`cX#)b)w_HLf*Q>%6e7V( z#=o3n*_v92=-3d+cxADs=H}A_M8m^;I9By5s)8@87rpFKeo5nFV>6Dn+lmtL%gcI{ zTlrN14Gr983!=wf?HZmq3|e}|`uWv}0=FljI)5)tkraa3%mIi}@-3f~C{MJ10~y>) zG5KP$`p?u~Klh3l4HOAHW#K2)1(Fa#_;)mI(o|kZGn8hOvBuwQ=_jUD0f}Dy9Ik&ehm)`W zBX*n}DI&nWNe5tZf46wvSVS)h7yT8XnTpNibmaiTt5WWpg?Lff4<-P!IQso%K zM6)5_g%;->ZjELo@@E$tOX_`FY>~eIV33lk(m#ry42Sx> z8B1fV@HrU3(fLO-Sj$u6(Co9APEbu1yn6R8rQWH)7ZF})MQgfPy<-Og7zZ*jGG{kL zr=$#GD9_R$dAo-Vzs|QO`UNxo@Ur6|pe0bax61k-7OrU$4cuu+XXu4g6z*58ont;D z_~LwBK>>+pMGTBS+ID6a8{&My0Bb9^_}~X>p1(|CPt6@0Gb*Y$Q$C=hdU?u#NJiWk zlOuplm==!RtQAtsSVu)A^a)L2xSq~f?*aLiu-{Y?&J#a;CIT`etpt8YV_Hzj9ohbc}OWVW1Sy5n~L+S^0K(n@?=>F&5o@xe(W`0Ub$ zxbFX^QwMtP^(G3gi;%Yua2LoO1Gk@@w+RDzJ`*I=WV1xBU+bJr;$QJ038;vwd^=xg zH4Er76Vux{%wq1eMX2Hr!GF*h=&tFC;t{rdR$X5HBz!xXvueeXG^ZM3s!o^Jdymyy z_+(}TWFgQXfSQ(k>#0rRc8P=|XBLk%jtu7d8o57RMr_mtdBtU(tZuMy-qkBTENpde z6_pzMip4AeKw&7NR(=h&_}yHOfo)=b11R^`))XM7LSOK!`q3pkMgg?Eq8;{hlT?UW zH-m}I@;G&40u@@_d-0cc*zZZV_FIbN8(Yx3*;5DTd^4*dPA(g6bTsE7dkgFlNhTXZ zf`a^3PxoXfO~pd8H(hrv_24h1d^XT^Z{svejQ(vy-Bz2qB?2EAX|8nQdLz6 zJs|x9ksUdM}cVD&*xA?`A=)vW{t>{Nba(wLfXDw|OX*L!mD}%+=A$C*tQN3H_?29q9l7z!Q_S)JwjkBX zxV?-%yci^PQGTp6jeNw`b&w>XyeL4HDc|5NPSTITI5?nbdt&w z&^F3=1zqu)=yCcAlk#y%TvD!!?~hYLk_%aW|B03V#dLU_Jc3(%=g104x2!oLK?#Cx&Uef&tO@!oa0us-@7Mi6P4J{RZytKY{Ru*LFXNfMk7jH5*Sog*ERa zVL>}mD=#+(!Ojg6DRCUOh;gtdTm-hls|#L&m?)ql%Hf%~!|9d^wl8*3Aq|9|xQ3d7 zD2_297&kSJxMFyMI9O?CC3t)qbs@h3P-Lo^3W)C?_|hNxs<&;3l+~huyccL0ZQ%3n z2GjEBO#uy7W2%<1@J{2{G*8ivUEEny(HK`5UEyrX((9bn?Gn|5B@?VE z9qm9X?2C>sQZ3Hs{AjNzOHNCX{@yoa5MXK;flaRwxRzs_2Bu#X4PTz--+ATo^9!wB z={1?G92_PaCZIa>19Xrl$jW?ajGz7hn^8w=#c<+(1nZ#T0IC_@Zx{#rkcHC63b(eo zk-I^A8~MaP*VfNfCSACbVpnNry+5L@_Exj4r08oev>pICIak2bT?H)!;_T>1uVj=n zGobS&eAAOb3p~GmSQo%MvH>t z8e7CW|2<1BF+HuAszTuwVznK?L4U)L5Za^hNYrH3@NjzDNI ztvIiI=&4A)Q#l$C6sp8^4-ABWAu7aJm?@(okKm5x-u8Ci?1abt`Ly&z>>Dl-35g&q zBzk9BL|{>%_TQ*V3UrA<&@TTg5~nEQoSOc&zG5TwD{3c83;EMgkh)2c@+o|00*k6y z>rQy4lKbze2Cg#gk#6yk@i@4q67xB^xtSomL3$4!(<0x*1~~gvE!HFPLJHZZ=i?nXB>jj{*y!sm7DvWm9ntw<01T%S8VPqDWK=&XqL%8J$)~ z-V^sCI;AQ#87s-&nkssd@ik+>Ke^+PX-S{W0uBP*%^!45!4@?KHetQ&$_m3}_G$DM zfvb0Vs;hk$vL(KblxT8*aXZu#D4$slaHA(33ebA{g6=03D-WA;Ql4%ZBn7Lj!chbV z7GY@}N~ALBpL6Uzgc&Z1%gE9?w$fK!l5sJJWLu2p{{t_mWVpBjCHVJuVRKS*2(r<3 z*gbz>0S05Y^8bbk9H!tn-2>+?bw{YnpYL9La$~X?tgkLv=0>sz!qaRKGLUA0B=RdC zj?PWv9weCZ)1fCc$~HFmlgOQY#x4#%J$(7l;k>EVnU?csJjhc*IQ~@2!1J&YKy1Cz z4qZ!LlwMiM0w&LBz(|0_-NM{FA?FaFkb~^f7Q0juog*V?fKKoOWc{jI1JXUEu)lZx5egh@712#6{>+0RiTE$ zGpcQ_5Z6Uxk!-_w#TWK>x$K^IZg(b^>RdahDN7DA>zVu$b_^vDY=tJ?46ighesuUd zBz&9lnYak!Rf;I(={a5+CM^s-SgM5M_i3G;Je*wxcG=tD7c&M1Mr1-lZ>0-pS%N|2 zSEX+Rn1W;fil=h}R*L$~0b!w`);kl_*r#IcQQUDOWJ zLE{;fj9naO&I@R=ywFVkm>89(zd_)SBKvY!Lk>)ZPAQyUqrodETC{-ESxKkczu4?@ zN;y|1m(J@l=jWO_-w0la{(gSYDqu#T)fodl422;`Kc zzkcv78~9g$NUDXUj3M~#_2Bv|1+fE7{RQ6svm6WZbr29T!)?2(D4otUr<%|yF#IHc zjZ94J!w~RC!fPMA>mqg6{#*OMB%O!Wl7N${PvP}QqHna{ZeL}Iqf_?*l!q*E0_4HS z61yn?62$Gc6u&ANsNq_YBelj9Y2>6tTh-{y(sU5%91Q36#Hz zIuA!+RbuG@0{?QaN6*@@&h(@t{6?%7p)U}x9`4;#I=SAwiF3Fm*-b~oaGl5vV{u2M z{BrcKh;`#LEgZVwYS?`JlfHR>x_3b;=p~>UTv!N#O@K;aqICv(S!ohQ7FhVE<>lq3 z``~Kt=&x)4Wa7xyuVA;?lGqP<_JahS3hNcYt=(NiK%zK0I~y$8cLI@hWR`P)Np+#I zv$M0uY<#Nt%)}OU+)NqEv{zv}u^%Ue*p_^4DsFt*FS|Dg%PQdW@bTxKC>5IeM0H?v z<`odq*R}zl;Gr9xtz1MjSQcNK@Ud2BDc6EAjAAMRz+JJVD|wc|!uZcOzUqzGhCvd)58{mwC^Sb{$jN=-IVyKR^9&wN=-Al)fLoQ; zwXa~&0=~7%F?wi`S2d1L1`7%2)}WV_UUqSD(FaAhITFTaHp=GkfB+DU_xH0qVIrQ< z!R&MkRJcD$fbrB1#IqHiZ32dExlsh*hE$Oa+H2OBVl@(i$3AF9O~}H1we|E!cBaeH zGI-{14}l)pzzosc3Rfv240Cs`jU|4Luf;SR3y zV${+08Djc4Aap&+o*a%cY#EH8v(s9X*PZ|vztxBQCPW^F(ikZR98ZtePX5h}?1THe zf^+l4+fwDej@x&;lJYkV0=mvPx{lET028?fi+wACz`|i3g&1N1Sw+mkJusF~wf zwH+rXXZdfXFkV(x*7-)Kh*;Ne4#4@sh#i8Jg=NN(bWH2J%SZ&zx8hvi<-8R*Ps-l2 zA`>&83!!%-uHFRycB0qcg*MMcLUgVrIASG2O2ZRQd&GPD27WwB;HE7i8j+RjkKcTp zDdtVlc+(c#*C@IlZRFj|dK;eV0>jVLImH-gHMO!o0SV2*Gsx$mMgn{*brtwU?7VEAz@M=5xgv@3cid zFQWN|yRHh3s)kB4R>+U= zxw!F*x2na8Fu5Od-m%%*pt?H$rmmsCN$nbFQ@hc^{>8!+cc&eI>+?zM$hMR|q~+8{ zuMV3(kR`@kKs6lHVzzzQ)~lWHj+dGKx>LpKTwibwv;yCKx3ydE{Q*xXKF{Bz6HPAh z-HLIs)Z^Cw7Vi(nylR@(DQ3rf6oc4&F#wm z)50lT(711ii`v6Y&kpw(wt5v+%kzPJ{;>Y|1dqPSI`rl?E^7?0(w`QBULZ$QbayHI zeZcBuz4YNyjh=4j0NwIYXcC$ingwva%3(K~O2~ax@th15o&b1euCdUI->5aavd24Y zFE+m&N@NW%yDxBVZudhR2gvd-jVpk|*bH!}Ax7Psg*OMb%AsStG;sGcaSum@60_CR zfr5&ruXjD6$3;1x!^BGjK5i~wGrbebrASVid19u z`(mguO+aU`2I@TX<3^h`n0+uUGMy^!gU1bA6RHdVdc6=~a{D&uz0J+;99o1F)sieSKHDE;yw(|jW(tn{l7eXmqS z`{OuRd0K8`MC%@jJ+9Eagjs5h-4FfFI_^r@tTo(BuS6)YeW}O4TVwPNNnLKoFNSPQ zvKr>$YAUqmjlOqr9taZGr1p#W`m+NU=VnXxia9C5ia5PZ*Y=-8lN;`)5$bpdT;#*AseCz(g)R$mMe70WMslAaTSsI{^YAwj(hI(rPaI4id2)a4MTlx z6PwhkfU;5V?yp<7zf${(ykYque0n1GdF3uGd26?CUKA-rn%Ywh;{;yw6_;;UuCYo) zzdf}Z**%i7&L4Xzp&feKMWM*emCWMChl}(Q_WSXTi{$FExP?dX2#$-?$aj_+u7g8! z{w+Ea)Oh8I8P?Wqhu#3xvjKRooAXeIPW2so>ikGu23O*fG{;gDLMMXs;inZB3xE}i z5Aqja8=bYk2jm1zY`g98Jd+S2oh)5Ot%j|R5NwP5OIZDc*K$M;C~C04QEVuVCzeO6 zK#^jc#lXx!%v`-ppvG(Sw-NPM#fzrIg2`nVHqYHUw!?spvs+vbpQ{p)Ygdu{L&lP= zyV2b4An6FJ%HbLM zL#-H+ldOf68MX^+?HG^x8Fq8{EFPInJTFeA=WyuL^l^Q}Y^WYcGBdeaNnj#1mW_+X z-y9Uv#ikN^&*G-oXfeR4G!w1-Q9eeBfpFH-AJSYNf#OZdz`7H?T8OhrgJ6?RUo=I1 zj8wDh`4$)i?jgWzEri^7V9_Y+?GoJ_v?C+MGrFD{v@ABe*%`o9x-Bd$bWFfPRR|dQ z!A>FWeh2fdaGOw?e08{@f(vv^$n@6SaW}@XI^^!rt}NAp{mZc4MsderMx(*BxEmRB z8AeE*(Gf@1t<__K7iXJPnLtXCVXifXVr zR87Wzl527?9=vgoywR1Bqqj!)nPz+(KnbVK@UAkr-Y7pVqsmj?bmd zNEydH_n!twwbzs1V4l+}Gsrf28Fti5drteX2RT8aWx%nn-l2_i=(j`3-rxeoBVV3W zd^jSjYKcbd?(P0UV?3+pXgjjamtGiANH~Ls!D}@_0nO*TYa_O&{Rfwe@amAmI&>~P{%2X3xV!j<&A^g3yzx&9L z8NA?yPfvX<>8#?kz+MaDGq`_9WGcYiu^dag8*fIt} z`+lTREq$puW+0_fhT)3lG3$$IkNLy#`j%i<^qU-WE$kdz(I&8DmIgzs)l0G}dh1vr z&Z3?Y%{atW&*OCpzC3A6FNEmazUBVm&**9%kh-{xxqV8M6|>xJCv2t*5dCqlS+C1@ z#W5R)YG*6=5{q729#xsFyhN`xUAvaW`yC=ha*M4+XoUNl+~{ z^Tg^}~`<1c(LZR)QKmr40$_gRMrFu4wIe!3A zvo3GZ)&kQy79oCCkO5(4E+WoOa&nt<)MpW0cp!lgY2& zbKkrxtJv*c4SA#tKvRfs>Y^4g$g?&>2xl;+Q?0y{91M}9J&8RdzofG~+p>fpeEdzO zj>+@q8E!7IH~66(;_BG*xC>`*RwU$2Cj`y2Z2Gy(+--|}MAYi9LbeFA)AxXWG9Qi7 zRSaI(;1NzVF5gvlkAm|^|J-Smps)_tlQi_)Pa&cunO_JV8@<(i zX!ESbgSWku)-2!W|K9qb+^DTydjESfE!sWUC{H*DW2{@`am3~^|Mj|iaKl1Tl&*&% z|J~_p#)9c}l$DVu)oxR4F^$4oWaGJ`Zd!+DDl^d3P^8#8}Dn*pqBovBlC)eWdz= zt~VF#7WFPsDq373nwHv?`BK&9^oH$S7qL@Lk2@|5pgjB)jV8pj52y1J-XkvbH})#(@f#TVZ=LD= zX<^8}nk4T?)8v}{ufC}HwF&v30KJ*uYgt?=LT4A3<7zzC4@bLTu3(KvP@VIILjIW( zbKEhonoP?5u@hi0t$^j_LjvbR(y6=J458fV?`VPMg7)#rVRM>Jn2(a zg!k{GQoR-F&^&uN{U=cv)ZuuIjxG+D(t$aF#CWbu7J#yC?|I3cQ@9<10piw^E|$#Q zdGW-m(EU9SA2Byh+9x#m3`*Qlf!X`dmBOu%LXL>GExff_d}lgSPTI3h`WCNL!)S*r zcetbD%U7P|i>`Mc4PigSF$X?$iGTQE|2-(4UbA~iJyRI|z%}|!i6^t!-!=0HL(B$- z;<4J2i2*#a(H#q@uvY1eK|8C{Bu@C&aUF%i8ak_npfX;cLRo#NrKN?C!ZBpRZanVKd)YN(@WO#;$Fw^TTEN6q2auhf zCL1cjd|)dD&{i4AARHhpm!1Umw73VlfBq0klZ@G$`?DqC)&lLHrR$k>LgpKamjAs{ zqCjZ*ayVHD45g^T>jI;p_L#a*j(WH3Z>WcSBdN5gTj}7bw2>Wl_J*m~bUH6HrJl$)7Hkgs{O}Po21JHMOWKP>R$-)$^Z=drm&v$2700S#+&;JrFQ4!Y{rr& zqDFOPPx-r~=Lz+uM1Al^c6H){v+MhSO(VoJd=pp8x|;a?L`K%@8yBTgA9X6#XQ+~2 zf5hf_#bqst@u}u0Fc&T~u(>!lD2X4raeLjt-1DAWaXX#4hEDhC^d#!(212}`6YSc| zDL4!rzJ4V5eK68;E=K(+m^>ygo1=zcHi701t;l^*Xtqg&j+*3x%9$$YH{%}?`z$Kynrk(O4Hrzsg=mz@aJN6F ztw|5>*Ira=o)t^x6m5iznY3J$5=|b;hcC9ch>6`ZDjK|oJ)vKd@w;#Oq#OZVZMD0N zTk^Fzob^Ww+A}IO-^(9|pE~v|s!jVT8yp@=Ke_Ilm9iPgc%7UK$!ho1d;?%t)m5dY z$6TOXoXLewhQOrQ*j!uPyJm&-*$D^tw>x}!Z_mrlo~tJdZrCZ+sCZWEAIKI?e)E}( z&zpAKbtxX+Zxx=FUC2fZC>!w;QvEm@OKnbH0qq*j%2PKtJHYF;$F0rh70-~?)D_|K zAVgK6)M=cD5PNKJe0leYLcwst2m8u-V+8k4?|4Ul{MU}R z|DRB%ipFk5PEKz5W+L{hM`|9EWZ5{q4c~L+8VeU1HBv?wte*n$;W`v%Q8T}yT9Rhs z=nZHottuNJChQu|xu^8DXB@I&T_aeVYv-^Bba?H=Vm7U23f}QOGFub@pz5^8a>Sp) zP5q^cm4=VX&@ZfghstRi${Cz!C^EIwqSsa@tOprN-_>h-Z5LVUsp`#G8tkrlwYiAi zT=aNtczQCZ=fSCeo%*u#r{l0qhd$%&yb;kg9B`aFQQMU;Us$?;(Q1a4ayfZ?ymMd~ zynQrhk)>7OLhTYUN`bExhRV=$U@?;G8UAr7$eHjfdYvjEHl0z-n!|m2i+#=gI_y;a z%bp9XQdZjJ$AMKId)Fn3O6UsxMhlPPbMo2)E4Np6;r4q(o)=dXJS~s!25;l#2pT<% zF8jo7(D6)s=+U3y$_cpD&j{Y9iN<%Y(7#s0Rf`7a?fQB&^@w$iXvxzcg|Wzn*DU6A zn>B~0+4aHygI8Usx!>YkNl(Sp*O`Xa{jh30rDHqiRaB^E7cs{4R!W@d>?hc#i~a8s z^xiE43g}&E?N3tD?vPSb#!Fy;Fy0YxV;qY04-jFX_kc=E_i+C&3^5R%Z%h6JL#QXY z>MG}IiP)B3x#6LS*9so)Ppv&|-2Bpoc(hTsaA-!|5A>gyXR|Zf)C>sx|Ewv%+ym zB>ux2?ng6dXTmShAbHA8%gk0hB{26)oxfIeczEMGU7UM_wb7jAlE`DY;KD$ey^yr6cocNwCEyA<)(1i zjYaT1hy&A!+$Ys4um}-C#HQ1mJ_P5B^BEMw3J3=r0|@Rm;0LoA!-_$x;U5!X5hh0p z`9w4_?OqnkF7yx>t~{vQ1?sdqV;;Cy!O-X8$dRd%%%GVgU4=`Zo*lpS1g%Qg&^W2& zd{yqs^fT0Sv3eI*!T1D+a_dX$Ea<%MO6c-0hs#tR7aarfQEAg-bE$32w#~97w#Bd1 z>nfdPbWM4(zim5|8gO01Y|npwM6hPWuL(x6WYg&2>kGJS*te3zeUsAhVU7@*uGt(G zH7i*~q1eFE^jB5kc8${yy1V<2`?+c?4zz@`Hb2j9LJoIq2I}g{zHDvr#rHDXkUhPL zIb*|jD!eZ&cnCk#lX4tx)egCaRy}nE*)`@=frT7m<*4K^^1q#VORhA*NP^vTA`nP} zJ0PvcvLzv*V@q{I8|BT@8oz9M zJi_vXXXKk|iIwe7ovJnFN(j_TIr}A+ruw@S|5v@Clq>sl>aAV%I8#Ji;;X5#*ea(2 z**O;Fnc)zhFH7SUJUH8z1zzLzSd zXwJ?$t)4uZP|u$~zvc;SuQ2S11__)L64AbXi{EY5)S!U#NNklC!1hcEwHx1nxm4(r zbq*M<>tN?p_uC0g0Q=R0lB| zoUaD4*1?I1m~li6HW)Ueo^%~-sn8SQ$HocpRBIugqDa7x>%waWI;C%b>8Aw#f%@H0 z8c#CxtNDiCKT{3k4-Xa^`<9l{TQ5TxTQUG4&xW3#+=~PqieYst@Za)x6>{oX=k8EC zUpgTEh=P$t1~B$1&q21BkL(K(j_~$|aJ@Vrq>xQXfPPhM15z$B0PDv#2nRxyxpN_j z-7Mez`ec1=O$ZEDzX2F{w>ju<>2J`TbLKp;kB*dg;Hm+;^FX1wl`vx-WFes07pWQ&YSz$01?q9{}~~twf8_%8wR*@L}_%IIDyeu zNJ?s|gSzV9#sr59!Vo7Gs2FkJ~uI{msEdp>&Un#Kj_tr=KkNP7Ep| zoTL$_WOn3odbZgW`p>1l04)>@kNe->mYRHEZ6^@?sd-4 zUiWwrw;E;gEUOhIjn-_4Emp7KB6Owv>{)lq0J|+R5-Vf>TAxSTT5!5`r8T9fk`*7Ion1hN{8H<=W|iXQi=`NS9p^*HkJu4 z^V(mP{N8aYG#Qojpk51%^i+T>So;T ze&qAG@{!$Zl_p*!P~)63)rqE=U=Ut%N|Z7Q*NQlwBUEn)H5H|vkDfD9|KIvRM&JKK zA6P`EIec%iyZ_K&$t4*}*C+P7K%*@LW~vNr&A4VuJyue604HbI{aBl)aOvC2W{=bG z@EhIiPFa+7&F*A`TbJ>CBE*kNQ{&7Uc`WRRW6RrwR8c}?4fZFLsl3j9B6=;WT-3Tj zDeIbVaKg50yjSONarO|=y?sQSiSN?h3@(Zm<(g+|{yt2~&67Qpd^L6UnsI4w!a!5M#;ZpyBdMjJdsXSLgoKz1W%|ek8d;fadggXJY8FV%sMlykb#GZ-(4;39)|_M^ z+c_zgpfKY+PtTmJ3%hD}{%{D5uk1nIjO;!zPHs5>@laJy%gyF4C}G>o&)<26hg?X!)iKFYBn7K!x@ z!cdGLPOiMVt?zj^TfZ5&-AtEuz-B2!TsTvXYjNX2oI39ov^xIpj?OgnrW0?zYkmyP z^<uL9GCIlj_k2<5;7{D2Ap_@YXPcbSSE|xej(*nJ7`CAO$~@0o^I${!DxP{ zBD?c-8BhNja=&E6U$lolf?GdZap>yoZCbh9b_LFW3j+i4E>cvHu%s!dVTc z;j#bzPODNDJy>qWwz2U&tw^F$UN-tbokH(zf_i?*v;Wi_-)z+QEcpqWw%|PbZ1ic* zAr$e31d=IDpGo+Vciz>gH|?jrfe2Govkz31Jzm(e13BW|iJL(|qv7O4{PSW_P3*&O z`y!V;;OEGwnG@BUXf82s zM^rwyyJ*a!!Fu=E?~A`YSVZ0Mz|cLnahj-tqFnHPL=bVDhMh)~@j2wD!&Zaw=$Tzy zoHGHfwqtoCs>N0>xgd<~ncXwpolCX`HWVu^^zFvoD{;SBXzKU)IIb7#SgCJazpS3% z?*6Ynd+@`6%y0d@jVB_4dSrwZAM?-n_dPm4ZR{IuX0N`fzjwW+4D`HaHK!6YC$;ES zzI&N=^<2PeadhB!Szi}BHHE~cuvMml%W?mQG zdY5h9`!P54X4enbEejXi-OwIrh6bwYQy z`B?6=7t6M-G={S@vGRx`z1YtD*(UbCTJzc4})P#6TU3vBP)X>Qf|>f zr%~=sMk3}NsaW{Ey*Tt}fJz_K9Bv~(4BE_BBv*ieDh%Qm*(_$+`qOyc0?c2%?!51x z**(k%yn%PrO-A;^x}_<(l;7L4N_?V2a#gb7JCP&fmWM@tuWs zrP$#lIvu~OD$dTi`X_GtYwQvpdzHh4^9a{=G^zM?s z4g3$&ch+C8f?i^bMM{+Pz4gN8_PD~opFea89yohBo%9mpbtbh(vC{K>Lx3SvOLI{m zFIBGHm394gU>or2R4Hy=5W;^?CKlk!X3+H}c)tmrP2(~pEW(}|`F`?J04*pw(X$EzYcvo*9emJ3PTj(fN?EyI0%&IZmW z>+sh%H;O=cT>v~mV`$zNiuXW<6Y(SgfaOEfPqZu!q*bI*nN!EH;?E&{L;}THkLgK@ z$0xk@*L#HcvkpWikUhqdo+^PlyolL4n6v)W@XzO2vNM@?VUHfO-u}$+4v0e(aQ-`& zkKvxb9KV}OTw6h2b3cYQvvTrl;@axktvl0R>;IPf+L29%Xe>usw$;IE@X@Q;`W27M zr|X%z-jgj$Z|ZEa%y3}_>aSx1CS_Ul5ChY+gHz2NZ-0)?c`fv5I6k_BN-7V@0eZlg zKlEcHMa48T!`2tLHcsC!U!LbkCq_R}_aT`8*?8^#X%ce2Gnu7PV|u&`(qYDye|-70 z(I0!Z5w9!CjSiK*Jy%z@%}(d74yXxZ+^Z-K6AjN|=;c0~=z2s_Iws3pNM0W9D0XqT z*x$~+Dn-U!Xs@n7#+f!!E-#kz#nC%Y!|;n+-GMh;4gIeHeb)jML38a}5k(W4O}q!I zg5TBlpaXYZUi{m%t~@m*m-tT&0q2E!ER&U=wE-dPcBGNFo~$% z)ehUKrjbECfErmwsj4>2h1+o?1M(^LXw1mdCL0MfG)~EcylH)N{2FxxGf|E}He+N>JA%)qi7+ zyEnx}R3)ClzE|t^4(P^^`M88jwCEQ~p++=c>n^9`M!4p~D~_Z`x7h(}NUBm|O{W3vx96}3pT)^DAaA` z1szRF9HZ{fRVLu^@Pq|QrgF1`9<`TdSF6sA9ndpFAH5#dDbADTN$0OWAZti7rm5B9 z+8-yiOt^{*jhh8{Rh|A{jJ)qmhW&UJC^YhDrVmAxsW>{&Kh*UU`z9?7292t^24 zm1Kp`wX+o!$(CLA-uwI1`*Y6k`~9BpAHRR6vq_CLz*@;S*JN-JBu81`zo!TMBPBy)5CJr1+gwl5tx zba}0|$J0YExfpW5@NM$ML)h>?D8_MgdgQC1=3tt}Z?zcT?D@;2%{nEgzZ-wk+y;ja z-@d*yV+Ap5$OIU!L8p$y<1-`Lw|xKj(Fp?z*pC-9c{F6SW=g=dY;P z)$TDO&K0}Q1(QWFPrFSpxfq;Z*U)Rg5~vJ>S|XZyZEnDi%eQj>&ArzB^J2rYuL8e; zDo+ozi#c2d+n6GgMV$;2aj%2olJ&{Dl&cFc(G7SgJmMa4SZMV_vkV2I)-AMz=i5&F zJf}>W)P_i-jOB5M49@yD8m|&O5j(Kc^Gt^fLN@JZ9o}a= z2E7W!Ie0=>DP)=~xb-;gS6|%*KksgDAEnds*RclucP`g>))qT`M3dI*?zL#2XMJaS zI<`sNcdah%J5#fY=)}=8`!K%L+wUapmvY8KUW)NgsW%Lp{J>{qOsav)s#;>6Hnp!o z*G@pUp;zrAkj4nghpDJ1Sj&-?_K|u@A&B7{*g1MhznWQ&qW}V2btU9y<b_9OnIq|YgFAK9hG z1OX1;d2CQi@AJ0+5K!uS91h|=^yo5KHAsm>;g0heP)$O*RhpfaWhV$B*CH&B+y+}eAb%@ zfk|Q`_e-z4G8ctcQ{nr;If6y4t5(&ZEh-uD`#=GC0<=ddzj~0l4I2 z6g*9#+AX3N5$-XG_24@zH>+#&2*MyDdyfEqz5qCw&OaRhegDy$;K^j4{FXFOsNE7SKbG#=5J8&AXCMuhBv zMBDXGOiU1%a{fbX0+sHBDLv22LoxvJ#BV-S-7qXO(BBR8&}4VUk2YO-J*gh4U03Ki z2j#GnX=P@0o@U(?QUG0?$9uLMZ|D+3&P4ozFWeQiH0QDWlihKwioGF!Wh4hu-ykhv%<3-yn#t?=#wesA5pm^F}G< z65|tSD!eN5=y~?-faDymobUFX5i_O{3DbGPxhDA6AuYI?f6&oH;B zrmT#V{Tt+wM`5tfNkX7^qP8brBR(aCI^7HxShVK*%!fS5Sm9$078X{gk(s8rAnsPN z>5P-ok(7pT*2uzc+#`%T@sN2;7dD(rl-X(91IF9$m z)Ccg6CtD5y@4|tLk&wp7zSu6w`KDVL3Y>xkpoZH^@XFku)$!ooJ&c3JmlfJ8n5@+V zgL8(!TI!a30RG!aH8O}G@cv5$frcA>&>l?;szi-8!Ef5R?O%K;@OLCmtOFmi+h-}- z1PtKWq~U)YqF5#y|NCUaD$tph$$Fo7^|HJV0(_p2Y7Tb(sBd%)l$%R#w8!5GbSHYy z6!7Bg{QTJKt=fwMgrG$QyN*+h;wzaz!}vM_?a2SM7y-zy^AA9nT33*_t{=s#O{V|By5d=Z1)XZ9Njsdvgq*wr^QK3Fv7$J=l8pU55zwwhn=n_SRa$XqLO|B{`` z>&6lM#z(t#u!Qz__I0lMLUQto7`GO!)51Hxv7b6^kwi;hUZ;DX9BF3C21#DBP%|79 z%Rg3sqIb|}d<6q~di%Z!CH%3RdimHy#skk>)+@_7Oug9l)@eT%i1sjI(IK2}H8eF0 zyKiypp~0s1_fM960VX1|g7E?qCGNgFX6&-ZG`nxiIz8`?_qj?n6)SpGas2;Mhj6L% z9yRIe@w~jP9=Z9+^y72iX{>9&xk2pxUk$d)u*KZqBjnRDn?}{dvc;4n;fKFR^gjz4 zMQ&0!h=e6NOH!|I9`%fG@(1}rFAn`1+W+2W8E6tZuwv`C(J0j_;Jhr-SXh^!O<+m#23+Nv0_ zswo_2*JvI^W5Jn>(Ug!=GgAq;tO~$sqH^>JAf`Zwx zO%;rUZT3i~@X`vV$Fqd=jjQf*xEwDWR-ZIVy(pide(U3-`YcjK+evZiu}D?Fq;J3f z^Y0n#qC-WC>RBU9@s&8v7e3~mT7Q9rW4ge}-gUdWwC<+f3&UXjW>r;&0c$Fzqg4sx zEP>aVy2PIrY)Nut0_088Hz*F9t`M<5U7g35gM?(Gc6lJO+mDVX0mmsd@9ou1@^Q&=AsYYEFPBROZI=){D+M}sL&9L|v{vrFbh0^*Q>2NiRrFF zkMcnD`Gh%DOv=X_WEqpu+Qadjt_8|1<}C%USimuukkN(zW*piH0yo0 zWz405vRHOM1!~5=Vx=1m>6?^K2|;TL=Ejs?zTgdc$RPcWT6Enk(brW%V8QpLqL@&eN-Nl@P8y~Pj)k6$J@4r)*|0E!r z*GVlF6W}(LvAeCM?`ma5N zdC*ebocJ?U$4?5XCp`^z@8?^OIgAY7taeWmer?u!E@{QjF);i8a>1Fx%H*x>@18hX zo7Sq8Wi)=_fNa_5JK9!XtfPH;c&1yxJNp??7JqhZ^?mU4$D)#qo7_?#AZuQ7krY36ncZ_EJO{f8>_O&~5V&L2GvV)GHpTmxKaUqHc}jP?1i) zf>{cMoyFBLtI7Y_fGN;J3LKSc&V(*S#M8f$q3I!xZlGtkWR2VWh}b4<++m!L(PC+& zs-?B)A-wZs6#cwezvhgF5k@4Om}1FmCge0M*!RQfQmFQ9Fe2G<9!v2U5pZVu{D0Ra zS-jgIAD5`RKSZ*1IbnF6?aAzls?7rrG^-xAPA3pm{z@B)5}*>~3tfTYwnh;;uCil+|D9RM4pM%he1N=axg`_~fj6T82z295p?Nk_Yw8m4S_J>Tu*{&_bu1HV)IqpH&S z-dT@nPlSTP)L_(2Kdy#%k}pa!W6bo}Uw0>2{A_-;IQGg}ljjqc;>)I+{p;$7SEf8e zW0fm|knRu1YP)UbJ58HZbx-K2S@+sL7^2jk_r(h2)~kvOn%HOA+_+Z7Xu5>oLXw}Z z-JUcUPyB7??~M^Q)uZeHeq?B^S(Ap}1C{F~CU?~7zNU!r-@Yx;f1WPru=v!;`((D= z-%IBGR96ykmT&@?mGk%7Z}$Y|-;W)K?1;n5+%XhH*T4R(bs7;^8GJ|4eTNK(Kb_dzN^7(U-Y^4Jp)P4P8%+CPS<1XUIIts$t0&aINn zj>tebaro~s^?WC%d0~aimfQ)SN?5Xh*U_eF3KeZ&&j&{fwS{ENPdoRwlTlKm)}jte zj)`TJY}Z8I&K{(E?SYd*c-wJf6_>!+!8A*X=3 zTb_{0@Afbt@R0kd>!s|;R_jhP>)F(ahQklU;@I@HhWRDUwpic)>-xvOiPx$6=-Q{MH}KqgZh=uSK%``UeiOT5QWLKGaR*FtdaxgX+}cS6G6nK-i&Puj zrJ?xaGKIs7!!+fF5kav@3ubwLLVWX4m-ec| z#GPLI9u7a%wjbPPDLU^g3?BWiim0|8kRcA-Pg6@j%xA`vv9a=B9M(JWssrgNafV;C zn_pb?ay5p`Q$AgnN)cc={oowK{{9f$bqv@DpRS65P!A&aE9U{A!M`cF31~CGGz*N5)^v;pSV8 zmU`tP}L?uj;<>^wL31DLHL$UUa$v>N>ka_k)FW`E-o4d8%#LDIzG4 z@*N=xN`f@0o5j7-eGkjg1V^tTVy|DNP>u+O;=?^cdT_)z3&h+i@!9VU)If#fz(wOv zhRUu4gSwaZ;p2Mm6*_j!4UHRgf$~%M(xXr3^&}Wu3|H;Q;(xt63i?Z4PK)k7d~#AS z#Wr65musJSI5}F&dJ8V9bjp$6;2h2)c~U&r}D|tS7GN-A>Y}y zfGdw&FaX(O%R>Fd5FT7V6U3Y|$V=#fo&37Q=Qh`&Vgqjw%UYX3#v+q_1<60GtMz=Z zLgyua0%|#nb@Cxsm(uI=7?*fwV}=gZf|A$+N?H})q{lXle0sxV{n9<40&&CSHDH^qy-Cs@<}wok;}(Eb6~FaohblrS)Cn}qA;W=4Nc#$LikI)$K@H#u zm{vcENY*RY)z;Rgiw0Sr-(O|nQ$GlX8c=jfnW6!#CT}GzME9I z*?gL4KK0e^f_v*4Y-MR@OABYeNH|Sxq(}R4ob95dEp)-{K~Ut^z=wVPjUL>-&?UFi zUHTy>^Gr2^ubL%)V_e4&)XbBYALP~|+~Yb4n#c-6IX`f{F!l7*e3h^daT;WSjCEUm67@2J;0cS&yb5RZaz6g?1313)m` z1jjXG9QozPS*UFF43u2USYlPIUW!7ofBSg5Bctx6$kR%fSL|W?uPu#pVZD__ht9LCC6idF?B@rdylr2|x zl|ZLf>gAV^efi8$ms-uVte?x%+YDdcB+^`dXGvj89u1UH-N;PMoo-HiEbJ%WpCZAF zL(4-4wI&QF-wp=aUwdA9v;3vsB)w_1wj_TeI^j{dx~D&+O3-esHkFD5H=mm#BPmxU zg3tWF+cFDVLq{Y^(Uq)zf)3z1_lDZQ@>*y6&o^qTZBeGVvO&sEF7BvShCwWAR*3eLvNz zofYTnXFdth;gzP)Z|_I*xMbau7B?PrcaYz5wtKL{y`dUYo1Hh{QaH|*Bj)p*O`q@e z0I%nX?!s4u!H?xHKkWH*$T@0XY0m_IH~NVtn%ij+rN>8=7qpC5!ix@L3clal2%W(L z2eU$8R`fV;=G4x~aD+e%j;>jg7%IUTJElS0^3sZd&Gab9no`N|4tG(Qydjbqa{lfjjgAu{7uJ^*S4|LgTuV(w+9qs+r;pRbwxhPEUGdwIoQ7CS;#`& zkR}aO&MG&HOIb{r?DV?-J~K*#$ecK#W)pFc$M)rw!*rL;-ch;TofBBNMaN^3JUZAN z^dW~{4!?01RP{De#}4^Ox5b--mqFw#$NAHi*8^*3bO-%Gy8X`#e7(1 zw7*JQFSu-uk(E;!>GU%QCl`gl(y{!H_LRYuURu^%CMYXMCQ!cI;kn*a!R=96tnJ*x zlPAi3Uvp@{rQXE5>`_3Oh(9OavSa4KX#$hC`ysxk)?ClaW^=;fLUsGi{&_+{f0M#e zhn^enD*XklFAtp0y;%n5f-Qa(dmRh)zPk0{qr>>zScAkWLJE7BP?#_!7t~y$K5y@r z=f&x!J5z%EswYJ9)>~68{rm5?V=0%31I;~vpgZ}oqVR*cDX+Kt!(cpE(xOZ~zvc*% zbkE`gq5phCU)+eYlfh2+_o0}woIm7oBYSU$6&W%uB-zArWH|pn`bgM$M_Z$4qKV;n zNZfVx|L|Ng6Y8=zIPowTl@Y{?(Rc(?(kQ*W|0eHj`RB8b5jV{J&oT(yCag1MVm zeL%k?Naa@NWSLy75_VO6t~2Gm!AG9k5~TiH{7lvDTfIRsjUM@3#^aXEz!>V+4YF}^wC#?mdhQyVQl zeO6L2``3q)=O47TGA%A2ItWXCfE_3|QiZxdV4bJkKW)Cec;Xw+dO%^PQ$5@=8eUg| zpi_rouMGPlBy;qPw;p}2IqUZ<2l7{dUUFWLnvA#B$Cf_Cm|;T6 zE9R{=2jn;ALqtQs$AJ12qbZHRyO(f?3WP!}`bWG&`;ItM3KvrkrOB`+T`t7D!E z9&SK&J{|;viAR0=_U#>~>KM~Xcfp2Go3{VW9jeSS9-6;C5y6-QR#ueBY0Ee7k+zNL z(Z5vuroE%l9;a#(*bwu7UQq=^I3eJYx*3GiiP@p;HnTl12B<>2H>Y1maVF`Z4oUSt zOH04?_AC%bwR#^6+!*&?X63*0I9$D*E2k+)agc(AZF=t-T8i#V^0O&MQt#DcFO$ay zq~C7)MlwU2;{fi1kqB8}IKORAXn!1V`fxs@<CYKXh0%X+G_Y1YO zWTL8~|LK;i=cOBurKNsrUQXBVaH*`ajj|At)?$5XQ6PYP&R@0zhgC(O)^E-K>`2&@ zXZAS(s6;6Q<~5HUd~5ny^5_!3uc_P%o15BTiGK}XeD`(5U&4`Fid`QeX`t=Y&r{_V zAEtteoL(q&m0g9FE$s6SnqAMx^WLN5(a+je?!pA76PXm>yTtSZl4s18GUT(Y|Ni{Q zPV_ulGouoJlSLZUbT4oIcpqE24BXDKznHvi?W#oV``0KZ#IOdo zYIXzPyxuz>fZHAkER&ym+@mFwmJu3vHDz&}9(i~C2T*UP`*Y&hn0Axz<}_k9^=>&0 zCoszd-UCedTmAR*`0m1z*bD3H2x{8u?c`^3+MX{q9P-1dRV=RNN&fw z)&kPyY@CpH(q}HO?EUMqp^q(pD`vBpwG?ANQbmIqzevvcAJ;G-56z>!4eBl5O*hol zy)1lR9|Cv3!n*)^;tBi|5OLSZdsmr{kCRCuyjFnN_s{$vqbKH*DzXw6y$u!`pqG{| z08ZhI4i>(^3fKwgOEo6wX0PYigDwuVQg6)1FawJ1pMT+~u6 zReW1%Wcs0YPD}^vXteS%KL>S4` zhw{!W*0{4jhniz*U9TWoV6<|lbN64L-7B~92lLvLcqM1m7h2TQSPM_Sb*g*6}>H-ys3(l|!%Q|zB4QzlgLyOaIMzMXP< z43tXaY!`CQlp#2rPcwWeTUFIRh$;PF-!6WFFHQCzohgDj2UiP9%?zP1lG}=;ZN&tK z0##3R8Fj7(1++B{FrK*Z$6RZHfa}{B4;z);JN!pe$@k+?hKhL;zTS&(`nBoh={aZU z<6oO@H!&|Zv0An6k+tqKc7XjB9)wgy{x?8;qu&txV`Pz|*I04>6Nj&n0*&s!QQ#X5 z`;UQ!N-9G0{u>ytP!JvX+_igaN$OwDEA7l(YM38Y7Ik|@jdlXT$nsxJ8~X*6V)T`y z=k>m{HvV?zisKltSxwOQvtROi#89i{JyN*{z|Fm4FrY6szccRjU&}FnDY!0&cBlq2K zndu;K{1gFk(InL^pOs$&Cjtq%SJe+d4X^}$zEmL;B?7#(-ghZTFm<$DnG;db8?|@1 zOYCw$(JsTSM{Dr8@!S#;qYuLk zc_)D}1XM%~&G{seKzcx9R>0np{1uf0Cgjd&57;NFzXJ?h<}I+^3<7J*d$kul3p2=b zrpO-f+%ckvTp)iN0L$hvTRAjTG?JlN&)CX+7F}gaWJ&>oXCDs4Q`eHv5t%K4xGN`W z-3Yt3aL}ya!vHQ8B$=a@RwpzY8yor!zAjffs|Wo+i()(U=gMI?akJc_DYKig3$F{G zxxBpG(D-2(1fL6u3mynRqeVe<+ zSsv}Wjs6~)x4k&bn*xSbR?COifnRG)>k3x$XDXHUwT`<(vC#K2iEs1Mvg*ffAt8I{Zcb)UtS@|3 z3lj|5 zus_~gdWO=@k&1>AQPBtNuXGNAvM16?qFooG)k6^Os0h>wWT1VdV*Fx2;3N1Cfoq`8 z!LJ=eu^c^<^mAzZY_DR=oTf2LC-Mpfzqrp(82al8tY@os*+u-Zm>g~rnw(TIy+2xK5fMplXm*?iJ z>2G_!LDhzD?}9Eiok4(JKlI;Ud0{oz5i7-f6@l9hCdZBQAmTvsQ%g`XW0i)2v4A<6 zCkeKS*C_D4qpt?PHU^9}5wP`ix*u&>mGFCk6o6<@#Kj2yZvTL~lbxMS3Zktmdk%AN zdqsbm1+SSz^c)QT!uVoQtzpnP&L;&@g&uP0eVg+|_M`#$>haREpz25`13Q07WBG&u1iXy znD*wXbdbrd!LRULdnFq(l`I<)++OQ4akLLMUQ+J+Z)WDH2ktmrc8dYe=Ez*R;ef*KR6;9j^KNY^Dj9PJdlQj$=tAFqSB;fIu?A0Ei2z?`QV z>#V?Je#U^14%nO79RYWvvc?02Km}lS)?=3Php({i?Jesm0_zSLX!ZE(AR*j#vUL@N z0o8POuju@jk{*3!GQ8|orKXDnO*=cQKtt3H4ryJ2MRLq*sRqjujC61+-P-`Cc@oylu*ny7{})<$R`u$ zG*W&|N?O_&>ZsfcsNzzQDO%u?6eF^%3C0q&$q>?ugJ_4veH^$s(3O$wGdar6cwL@O zU;1Buw*G+63LhR4Y(5HC4%X1^)wl5nw*@}^Au%q>BXAjKgg_vql~G^>R$=wP(1~&L z75+Z69Xg~X{IHMyKw(BIEDJfA*ZkyApikxqJa|&s^F3-)7X-hFZ9oh7V9{)<&nxha z;H(iaxz>j>8{i$+=1*R)IGSaJ8IhOX#2#_=;+c6?&)eR18s=*ULLWtb zY6QL6766Ic=WZ7F>To)nT?g5|&P%}5`(O!d5BBu89>H@fW?A*ZN2n(7)gjQE0^z__ zcXM6Rtk24!*rcX-gH?#ti8JxGfGyU2=)GRdA!TVOoM)6k-LwsHFRlDd=9iYU0kq`bjYpI#^hpS-6 zfYh<(N@!NNOu-_vuncb+XGDbt=>wZ~I|u+*2tx4v0Y&!CcF|jH-=ga}gTd*q@f)zd z*`)@nv*4W)OylQAGV-DIhA!IN=vTv&<42JY)SB>jp+aPlMvs8PuV{O|6EV8%4fEIm0q~%3I;?GqiJIcI8!pZmBA5|xLl*89ZNHicTas< zm6S)30aDvr%n}qZD z`0DXQFq|1wl9<2Eks>^#QlCuyTn&xq>&iET&IOt2c$|T3Z>k&lrRH?5B9_}>TZ(4a zU5SyHRGk<*_-gncg-6AVFsE2K4A>zux7;+1<%KQ4qj$TPEg?MphFd5Ync%ToP@^;} zi8OI<$3=?GWP)+-N>7Oix;BQbEy5wWqa@nZI7|hj1U2P7!$D=827+4+F;A|y$WNZN z8I)i{E@ey!rKP0Mdr>ilXR{5SAg|O$wQfW2ty-s{3O^7|%z1^^*c3%!lfZCYd87>T z!cx#%v#c0@iO|v4q}N0;_zWbLXBVR4Dfp-dn&_Pb3xMp^$|=fI6f${x9vX14X6$KU{gbncU- zz(QZ^QzmOs+|pQ#aCE%+6TD&!No8{Q1vprhl56#iryN~%+S5_kzUNuRiL@$EiajdW zy%K$?G9egzwquK7zbv+pA~Kg}UUW}-k~=$DU^d3iO#ae$po+$$y z-fYuNo&p=1V5k5Y=5)sud))HmmBo?8$HEb?z8Aa*Q`OR_IK7*QFnVgxmM8r6Cy-l; zlX$==z5HbP?BQV*ky+i{#_c=r&b4Y}JtD#x^T3%hvx_>CahPbBXELxAKhY)XA>D*mn2W3uz~4bR3;N9=uWcTh^C&SptJKyY`ci|(KSxP|940&jXRi&`0NtJ6NE!q zB!cq5ZBr$4WqAN|q&8{pI zsgV8L(+)+hU$^?}B4(d#SOM0Vk(WrMM6SeM`gXiezYM0|cHM?f_`NW`DjRmjreTPF zWC|{KeePk8VT){}IK1>4*F)ed&B`55(t!JeYNC7>xhI2eFm% z_cQ3$oP1uFGXJ%FvYqvL)8u@Zw>M!0FMpO^Hhu{1T+kjFx_95unEq-Iq({rhH_< zsZB}Co1JMSt>}z;NZ{d4Jjo5-TdI18?xK8q1G7*fI!0vSnz5gk z>S@5~`2hzx0T zJ*u$UT0d6KMI(y0etRXiiE z&tkJj#UlZEOoS4(ql<8-ii~1LwKHZE#6#eg;KzuypakO=LU`$d!HgN&OgHsaFBQy-=i-rFN32DW|#AcRz~;+QT@TfOueB^HhjjJ5W_{Q`#cEu90o`?kx>>)@D5HW| zr)rxu@gaMuvdJo=Kov|DFHk3`Qy_);rD$|8mR1!eZ?X z83i^973|?G)OCXwf>KGAg2dO0UGNoV#JGL-Ddw}pyoflGKf+7A;p(Z+=i$ul1$E+v zGDDHp7w+Y)Zs=E8?ysXCBkK(Uxw<5!Xum2atw-_!Rgix)4Po*yMY~M{VQp@DT!NHS6bFxFdq4OmLCeSYBu9 zT9yJDggf??$83etdQFkFfZDSy99`mc*12V5&g@0FDdijuf*)o-iBn|r@IAf68UhBH z49vR~O$Em$x7~@0;pr{FI1U&2N}+z?C{VjfnDtm044UK^e4*&GcM*Se{FGE}-EwU9 zsbbCawq#V)?Sg*7j*rotkY}HxBAL*wuT5+3GI=Es)dZduZUw<};r>VG)}@~fK78JU zv`C&cZ!`Ayu9uh#xC+gP7`gg3e!7yiIbtdGS!r8HIv~X(M?v#av200TFRrZUyiL7% z;}@O+Rk-eNw)www)3~kVhm}Yr-~|d-7H$b`|C3KfKa#^YyQ8yvx!uhoTptnB6FMV@ zbH_UBR}>U+1R^|kI@OIkwkAuBZwlOn$U{iJb<0JwK~4-RO-pZfkV11jN92@>+nOsN zC?um{ZU$Xyv?!6a-ukC4C{-;X;&F$FLMl%@#$sj;Kb@w7u7j&o6fRv6*ZMoD&yH|yN#Arq_QVid+9*gVac| zA!imyN+=*Zvx)MgNV!%kH13Tmsn$4HYk2%qI@rBHLMYqDcaKkr)Hek{28neR|7=>T zGVTN(uY%G{K`H-%vlBSPsp5_jqTS4vx)Pas1^BQ_FL1)pp%tO=M(cj+X)>*c1st!$ zW(nxnp$jGPoP2NYafPnlF8c^?fyy)^R4`O5a`4Uu+X&pCFEw{6pBK79Gp7MJNcSK!B z@Sy#-NaG3l!qlJzBfP_SoDZZ*sKtj{r7ko`CLg+&%ypE^WxKa(WZvRN+%#mDcCXZz zRc)r(&!m(8Rk4{jienlVl+r-W&(ThD{|%uk>% zf1q>3z7W!xpqGEqD~*s~E;CX|En&`pD{@_sSP``1 zw}uBiabM0r;3o2!41`KZP8yedr}U>tQ${bj-#6&(pDLSYB}f+#c%J(;HA=FZ&`vV-NfcAl9vnJqgb=`2qI48e zK$_H3R53^vABoAC(NEg(JfYeSkqE{XSxe6VoFd9l^6$)WmnKfL(No^*A!P={hF0iN zp{cT=u^WxkI1k*?y{|-SU zviLj`ztobjA?g|FH6xc-%4MUSWcUhdZ$8FYl3h*R)=3Dy6~1`%V+7yHI|JnJ^8AM3^nzww!v6q0X7B&NSL z3fJ|4G?h$W{1e4{j|Ati8MAX_`F2x@y(Yr$u&VAT8Y1EfPbMkAvG*~siB zs{OaT4JbTez3-s_fTH<6lQMW{RO_VLBO;_gYA*soie>T$`)NYxkspo|8sB?=81aW( zB~+Tv&E4yUYP+E!cJ%(LhIt>d0>xI5TVu`A&CCkh4p|8=ujr5vx+KTYp#xASmZ+#d z6#H{XC1HWrcnWwz<+A}solmWXl)kB9C^1?-8r4j9g_t&GmeAN6@yGtrrZnAn;*u5m z6o9%Mxt8YYlE0J0E;joqJ|QTq-Mii&$=a(Q(B2Ikki$2hO(c--Kxd<{245q zktx(C(t!4hVF0?G7T!^D7E&6SL=^@b>7PoNLtFhubam<=NR*fdXHxOmxUc)LN&|(93t7Ja#jpTcU@PGb z#H@nyW8{4)VsOtE{>5>Lrb=azzZX_K9plOAo<7U^^hmhlqkOhPhw`T`wq`=(Cd@7v z`-YkwBGwa@UxX21!*pxo(=1RyVvUj@oDy*h#amVn^2{h}A58ctWQ|6y>&=KqxT22u zTX|r;)O@dwnB+-KiU*-)J1{#rdwT32N-tO#;Q)F1ZIMT)NkBDx-@P$!z&QP$u)=We zkbK~z(noj0a?R^Z>{rI;=61Bw9A$^-kz_K*g$OMyF^K< zM>8_IGVW16=ouQl+wJwW%^@D#S4jK0f8KNF2F zuDc>__ob^jJc?+JE>^$ddhuo`yh8-3#P|C?{FKyC2qtaI)b3MOwdAo@VUZ@~hKAS; zMc;dR589>>XA{Oq?lVYCA~DBJxmkq2sMY~#b0H0jJ+^9!+UnYbiDOqLIbg$b&^$PQ zGFUh%)Le=*)Ym(&aZ1BPF+$lhHTWK{!w(AL&;qu;#T`^;c{8j(&HjtH(ZS-o|p<&+aON4SSc|JBBh^dA98+|cE4n0rm+kE<@Y{_ zxR;-=762JtbD|wLo+lIp46=euEbyz^>4cC}291+LC~;8)CD|(ll)qDM&-EgC^a;uh zy=UIUlT<&}3y>}q*(O`RP@H6kN=(Mx+X~z7ovb0&P1JDdP%z$UD{CfcBPSk;jW#15fXdv$@UqQ=?6O{xJ&vn!(58Y}f(s{C2uH`79n`vqWPa*tNT93SY zppa+ssZ}~kL=cP3vM1*mz%8MeN^f}Bn@@|tEJik{;eVPhh&u7b{81Po;)g#RzIP^{ zz!1Jfd4ukm0mWN?okxf-(7*FyJeKE_vT+^6yFPM!h+srw3o+=N(hF~4!g8Y*qi^C~t>UWfAsz6=y#hD_m6V~@YxCI=j5yizZB9$Dfr;`6|LKdBHA62@w~7y!dARs)7bttIcjX!}>u$3DbMumD^|guC9gcvP!Ka zfmSMpze80QKfK*M634;YANGM*M(x>WPk2_9?L3dW7teNCM{ZTAgL-Zr8{u355@KL} zlRu3{8HL9z_(MO-S!%om35iIC^6glOd$@PN(V!mPb>-Na|AMg4_D;A!Rf68$?7D< zSX%yj<#NJa=x@$;$L((9ExePK+?E3oqEs#&a0JOLl$Ti~w5A0YJ#`~RnS}DG^G4Bu zN-d3qQIYdQ>3(K)v$YAEKF3m)?1u%4P0y6 zIL#|6Ci7kC1^9So@eoYeN5~^MW7K^?OoPHQ(XPtkGqSes!@SZsF)Jt{2y2|GOqeEz z!AY&eIHK%Pj(Gyvo?h0MNerTK<`u=Axx)cak~~px!(+=+D?<(tb~6v#cLcQ!5p5-C z%q-P%3_f&kQyT<#W4g}(9_Aq2(6e)B7XjrDjI2>L%E(C3(jd&Bo}nKt18xbD?<2gm zLLooT-QTsR$J)a7H?y1nGa@qiXwY>{#6!H^{8Xdr+FzsJfjT<`X?Ff9aP2r~_UO_M z-JI;zduy=t2n*ZS-1k&&;<1%)vnD*`lcApK=3L+$+P}Wrsc-G69_|U^b93D)Ebf>U^o&55kIrOMTI(4kdIU}fE(Xp{%E-yq z3X?qioSM)Q>R}Z$>P8i^{SGEW4x4M>fK(67BA*=Pa!(0cfH{i?vp5EtHuQX?Tk|_( zwp`acFS&8@O7-uPh5p(Xde;VlB!P(fucgDY5Bv-Q-D>^RS9`Eo#l006$T&zzdCQ|k zf?(W8ob?<|JCVDNDvboREYbdL$}nNYCzRpwdaG*6(V~tSFXQDpUgtk5dn=fDB!jkq z^S+IvY9}K@TuUP&5uO`EYUyTNvk@+yqnNwza)U3p;+EshXw;6Si(#dEZXDi6n(;q* z`}AhXuo@ld!e=x2cV>!4O)236cwac&TOGsHnZ&Wh)z1#-+dqa0KZ-psvlnNP@4vdV z7zm&-a%@jO|EvTYQ%+%Ko+3_xT61mC3wRJRu=)#b#ge@LjSJO@9kp1w1bi1x(%pKm zv^OA9)|oQ;1;K_kTOjfbr_vRZ1;Gr&JHAJh%67r$mW6x`enPuM95q?rB zIg?lpb@&#W#7l6%-{Q2<5^-U^7^Hck96x80;FirX&9>@%hG0AjBXa^&!pRgUs|^+9 zF{=P2v3^BD$7yVqK{*lyX(3L1FLzLhmbND6(}160mLodJTrU85`EehwJOmv+fU9Wy z7zIJ$2q%dRe>b5cW%JE@ck2%NQ3Ee>3`Qg;9DCSa`#GE}3|~Hqk>S@+`)JowvN*oi z3#5_xWz!X3ltm42P+*(nH-#d24qk?7*txx(k)e>YuP!j&&8rpar$ z)rBiE>ygunmnNSbpS-}a$c&P?QgXZyK-UO>1WVg^mJTkh$>EiCrzCJ0o{#Y)Qyxh!NDBRxhA zT?0;56EO*<2{+ktEvJjxMur*O{^EZe2p|`?Rtv;|>7Q|I>j0BDIv7tE+D$j?LMusx zdb%COy4&+rS%lz#o39l+rc6bXh~Gx)zIy|F(&npAS|fliB@qayP|5dJP>M2)Dhiv& zNN9Ip^EGKB$xp@vjH}pw1;WGV2`gZkBU~Tfz#@Gptf|GBTwPtIKt@M z?y>;Vao^ug%RM5@rF*B^BdOvjj}AE%BiT6AJI|=5wr&kW0EvJQigam$^s0c; z6FLGCK?y~gAW{v=QHrz>x*~iBm7+8SA|ld@(u?8P5Jjodr6^Spq~E#y#x3Lixj!y{ zVT8SR)?Rb2wVwBVrJN4Aq{hTpA9Pgxg1>p>hqsfyWrxWW_NfWMcDyIDv#-0mn~U@N z_;{tr^6c>Dkz0JtKCdY0@+Okk(tRCAcuM+8@nQ_1 z^~njA*B+)H(_YPPzK2>QbPS2|>Dx_zSmIL-jy`ZmpDtWu zwp!*be@0P8r+O-&XiH#!>(+Obqjc-C^lFi1=VD6m69=}38fH}eUyNmcd1`Xa@jz)f z*Q!y8aN@m3-AZn<>`PCq#4wB3J)+c%|Miai8%QeA_jN9YJDn_b1iU>%z@;v!&m=M< znk`L3dFfS|-;mnVh0r=e&&U~{g7$sn4E^N@F*R0|a7SF})vfe>eguRm=!93L&s_v4!M;NnW&1XmR*4>i7R$T`Ox<0wyM z7{KMBeZ3yMhZFsJmNep`D^7r z<>RT*=u9;~eoyMFs*ck^ImUK(!B?0cyq(x(G&%n|=6V2TIF^XGs|bSN9UI?JAR+mE^$2^hbC_e|6?`XwZ}c7$tsRc z?P$fuo_6d6KV}piofilZZJybynx%+f(}MwrFow*JM?i|;YyH0l2@Q+KsxK7*`a>sS zo}L21h6ccLj`anvPD`+{v6*G4NE{N<>OXM>wmO6P5aAI-r{lq7Zw{0=YkkfUcsPmg zI*WiRh2|MJKoTT?{!DDzU3ZYNE@XLLq)M$H28g-2hD>h z)goIfqiK6l(=1C(9>isD*6apGaSg^-7-Z0-Ur|4pp;+i$K5lyVE~|oVLtJL0cSxXo zB73aQYODS^_AXPE`EwN3&L@S177mR$bn^L!n0P8BLeX(?*M-|Y#;K?i zjl~w(ORsbki4M@OhCwcK!CG>TI#JUqK13S4ux4%w=w(UBJBy~VF8WB#ff z4o>wp4?9TMlsMGBPm#7hW=%a9X&=`=V}im@(EX!CNuoHN?l+w35js8Wm@c^_@`&HV zNd$#wR?;${XIeKv?eq*WNyEJd@9$~9d#HK;#f>FPfb`N7opr9C(TUi5kh3vApr4$a ztmSMz?2N#HK&9aUuJQsm>B2ctQPE=?GQ<8r9A2{Z1yoncFp=vqk3G+`g2<;ww)0>_ z?1TVA)9?;p-EV#|Z#Dn`ZeU{-)-h*f6+};lB+mtuNmfybU^gSELj-(5C=TRU1xRjI zC>YD+gR-D!b9LqkusprsTK}-m5wq4_;i-+qxXEKUfr3K(an8at`a3^?_a)$M5FEKp z*ug&TvrM}i>mBTh()0op9gd&oYVrg+vzsn@mQyZAZ&=M*9xQP?et1uq(4E@ zO^`hRdxT}tS8NlCCr_qBhAa-iz&r$p1fwI)=}?0m!(CqRU`#_?XeNDNuRZYRl@1iO>q~}YA zb`~dztQf{?42ny{&w}FoBVcF<3M$v3=vb#WGBTnyQr-4RGXzwC z_kPVSBzOz5k1OM>9q^>uikZ|1g&Z@VGMjciK~Acg7EiY!DHk=KV3~%ClHlNRNz+VFq-S7Qxdv{7 z18pb3KSn{3bXE~y#K!<@m5=T2tajJWGt1>+q{NK!518>k5lBMhU8cf3JUreXA0K?# zC%T4lNX%lfwY62)yt=*a2N77_lz_`#8_gme67FlvBp1Aj$Lo#&0&tYmyt?$0vONb| zf#Mwfka$@3q!2rgi%%lnp$q^H>qZ?(Ny+e1n_jcLtbJdmompGOgExm-P?z6z;_|qhX*I1&WZV0_8YbobKFKRb)`gI>dy8nhNzS~ zBt3Dv7Lo`-(2+boZP=onXmwxaT;hX=59a_6p5G!f_xAPOpU}WWARYqyco%_kU_`>_ zKN|MT00y;8ch5>(3b9=J+D;5}IL>^ki6r)|Yb53~7hfY{ zmh#)Uy0_o}k}mhGqL~2;U%v=}*3qT;_XIO*M;tFAq#dJQ0q55F_migs6nN%)1o+-#N*gPI2@83nK1hAzkj{pw$NxlSY*U$R3Y#xTO zb^1jaP>?DZFarx+Ek@HX!8jg$n*cm_naoOK_E)f=s;@rc)}2C*T!<-P@^E&UGf+V} zj`TBv>;262AmEhzDn0LrTzdhQ5H_GfC@n|3e2%6uFlhzs3xTQ|&WQdjd(Q4p!yZo- zpZD}TVur;)nIyS*RN*9>5{1$Puz1gSHo`urA%X@G2*{KPGId$17u*sOwa^lgLp0T^=zJsiq0jmGW?@sO;+vf&*t zxj75`X8q0go;hK!5>E8j#;;gc%dxMXl?- z!a#A;S)Nv0Ak-nmq*ELx)1u^{Z`do0a7J*S|RM%`_!9($^+n*Iq~hHO^{uNUtuj+N3S+ zKmX#q!Nh)vB=Vbj(YlCK@=-Ky3XnXo`sy%EVYqd&72HOsKhElt{+x3FY9AmJu+4<) z1%&0_*72SuE{Xqxw*LeDzyI&w{<-e|<=y^&!uuUZImAsc-=l=80PeP_@wG$LmU9<2 z!oVW0!3hXyeaalLEi-fAVfMJrAcpC1P~&swX8*fO-S^}LBAVfP3Xq#vNrA_@@(7l8 zX^@&bSn0_YwzoOcTj6m!@wtJx0!RqRWhRpb*!UthlQ)3nKY?(8HL7q#nG{gbYMsY` z4YYt-AqGuKqpcyR85A(gxg!0v@PP$TJC^rh;hWcQ6wAVg;PJo#7H<^8Q|V{;Q=6B6 z{rWZo4M|2}t4HF=N+4`|;N`F*gVGESvKWiihVb1?`M}>dArNE}F|x}9teE_U^rjvG z>A^ToIUd02BA7=FPVQ6PXdoqG4w%)#e8Z%(iCofB;H27+4V-W;kieO4D;j~bE&FT& zYH~J$kLy*}z`w7dLC&sXx#q>om)3yN&Oy}7c>ynTitxfdm<1qvQ@bQ*@RDFR6l25CECy`+g?MHHW}XZX{LkYGd-lM|w~(&D z|Ty7v&FKAPp|Xg9M(frp(FHy*LHleR7-Z#6-Xk2QQ5QeZcm z$fIa^>grT3r=+D7q}}~KjvoiyN~(DSI_QkT=Af0f6g5Bl>^%@D#Mtk(MA2Ei5zq*{O!-#j z)0bSd1fX*78i>eN*T8g*->K;)-}gp@)wXU3_(4DgYeqT5jv9rP$Q4|-)7~!-6u@Cx9H#g0P9)&Gklyj_0ndwefeuVJHc_X2_ zTh*@c(DMpCpgSI(tVcX%otf9e5Q=;c`nR|cHc0WqqW*x%7Sw>A#Z@Tqyodw<6mqf9 z;m2mGgG{9i+C&q-B8X@dhA9X#)6n*HPCSw0%ip-?E&T&iB)eyqZxz$LeW-G?H9@XyAZ?$`sL zcZNt*S{QB4*}w9o#4dev{Ccc|Mg<&n+?RC|%Tn_*Q~n0c2Qbm%AS~da(zP|}KosPs zP%QGN(=_O-tc~#_LUrdvKjix`I6}seE-aEB zBJStk8}HQnimnC7ol6j|BlYlIf<|Ka7&5{OgI!f2E&3XZ<2Xo}##K}?DN-^DFp!L3 zAi=eyIrlIncjZpZy4a~Odf1CO%&_Jz3hE6{z-zIVHOe>EOT^rdxG%_#)mofd%+E|1Hl|f z7zKO`u7 z%|~J3jXNkugk>PHneDteVFjaG3%9Ja^hix)IE&I*H2dY5fNs`v%?Wx?0{JA$n3T`} zuKevq$aHzy<$(?9lwLY0&4*uuy7mDykFgoTF%96>@V4#KutaI{!wO3`0d}3p?)2wF z$wxGC?_|+9LF1(p?pRETGvo5b)knQu)Yl{sW&OMf4Mi-|VHsZg8ecEUxu@KHZ>?2P z?|gbC%+lgV!3dk22MeKCMCbgl5wtK`W`EJ(vy{O5d&dbjBDhF2KDUQG_&Zu8x`H%&oiw;)%gNR-AvrdUM96Q0fHWpTJS8wZ?t3U6K z`{t@KLj8$%W7tOhu!0#?>TVP*m?=+my9jS?Zm#ULhIiiJmLn|x?ct)}Sm$DIJ8xUP zM_KJ_j@VbCo&cF!k?a1&FlF5u>=DpWRPezHP_TH7ZJph0Y>d$N)VAAZh`n04`j-g2 zx-X#vBL|TSz~_VU3}rG8e~;260FZX0))be(SoA9%TsFQU`}p4ts*5IMU;SH_`v2%q bD-`$GzK(dtiV5ZR(!h_Yp`}47&V}?J7T)Q; diff --git a/reports/main-model.png b/reports/figures/main-model.png similarity index 100% rename from reports/main-model.png rename to reports/figures/main-model.png diff --git a/reports/simple-model.png b/reports/figures/simple-model.png similarity index 100% rename from reports/simple-model.png rename to reports/figures/simple-model.png From 6c3cd1b80ff9cffbd76670bf7a935afdf5ad1415 Mon Sep 17 00:00:00 2001 From: Sinan Date: Thu, 29 May 2025 21:26:35 +0200 Subject: [PATCH 26/32] now --- recaptcha_classifier/constants.py | 2 +- recaptcha_classifier/pipeline/simple_cnn_pipeline.py | 1 + 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/recaptcha_classifier/constants.py b/recaptcha_classifier/constants.py index 943487486f..a09a783239 100644 --- a/recaptcha_classifier/constants.py +++ b/recaptcha_classifier/constants.py @@ -5,7 +5,7 @@ IMAGE_SIZE = (IMAGE_WIDTH, IMAGE_HEIGHT) INPUT_SHAPE = (IMAGE_CHANNELS, IMAGE_HEIGHT, IMAGE_WIDTH) -MODELS_FOLDER="models" # Folder where models are saved +MODELS_FOLDER="models/final" # Folder where models are saved MAIN_MODEL_FILE_NAME = "main_model.pt" SIMPLE_MODEL_FILE_NAME = "simple_model.pt" OPTIMIZER_FILE_NAME = "optimizer.pt" diff --git a/recaptcha_classifier/pipeline/simple_cnn_pipeline.py b/recaptcha_classifier/pipeline/simple_cnn_pipeline.py index 0b8cbd7816..4f9b3496d6 100644 --- a/recaptcha_classifier/pipeline/simple_cnn_pipeline.py +++ b/recaptcha_classifier/pipeline/simple_cnn_pipeline.py @@ -40,4 +40,5 @@ def _initialize_model(self) -> SimpleCNN: return SimpleCNN(num_classes=self.class_map_length) def save_model(self): + os.makedirs(self.save_folder, exist_ok=True) torch.save(self._model.state_dict(), os.path.join(self.save_folder, self.model_file_name)) \ No newline at end of file From 692ee5f81c6b65587473f9dc853df6b2efa12c8b Mon Sep 17 00:00:00 2001 From: Sinan Date: Thu, 29 May 2025 22:16:33 +0200 Subject: [PATCH 27/32] api working --- main.py | 10 +++++-- .../pipeline/base_pipeline.py | 2 +- .../pipeline/main_model_pipeline.py | 1 + recaptcha_classifier/server/api.py | 13 ++++----- recaptcha_classifier/server/app.py | 26 ++++++++++++++---- recaptcha_classifier/server/load_model.py | 9 ++---- reports/figures/simple-model.png | Bin 78418 -> 80765 bytes 7 files changed, 39 insertions(+), 22 deletions(-) diff --git a/main.py b/main.py index d9061c3a73..e34ca1c52b 100644 --- a/main.py +++ b/main.py @@ -49,10 +49,14 @@ def handle_action(choice: str): handle_action(choice) def ui(): - from recaptcha_classifier.server.app import StreamlitApp + import subprocess + import os + root_dir = os.path.dirname(os.path.abspath(__file__)) + streamlit_file = os.path.join(root_dir, "recaptcha_classifier", "server", "app.py") - app = StreamlitApp() - app.render() + subprocess.Popen(["uvicorn", "recaptcha_classifier.server.api:app", "--reload"]) + + subprocess.run(["streamlit", "run", streamlit_file]) def train_simple_cnn(): from recaptcha_classifier.pipeline.simple_cnn_pipeline import SimpleClassifierPipeline diff --git a/recaptcha_classifier/pipeline/base_pipeline.py b/recaptcha_classifier/pipeline/base_pipeline.py index 674422b396..fec7ac4df6 100644 --- a/recaptcha_classifier/pipeline/base_pipeline.py +++ b/recaptcha_classifier/pipeline/base_pipeline.py @@ -66,7 +66,7 @@ def evaluate(self, plot_cm: bool = False) -> dict: model=self._model, test_loader=self._loaders['test'], device=self._trainer.device, - class_names=self._class_map.dataset_classnames, + class_names=self._class_map.dataset_classnames(), plot_cm=plot_cm ) return eval_results diff --git a/recaptcha_classifier/pipeline/main_model_pipeline.py b/recaptcha_classifier/pipeline/main_model_pipeline.py index 223d659897..31538aed0a 100644 --- a/recaptcha_classifier/pipeline/main_model_pipeline.py +++ b/recaptcha_classifier/pipeline/main_model_pipeline.py @@ -73,6 +73,7 @@ def _initialize_model(self, n_layers: int, kernel_size: int) -> MainCNN: num_classes=self.class_map_length) def save_model(self): + os.makedirs(self.save_folder, exist_ok=True) torch.save({ "model_state_dict": self._model.state_dict(), "config": { diff --git a/recaptcha_classifier/server/api.py b/recaptcha_classifier/server/api.py index e952408e36..33ed2668a4 100644 --- a/recaptcha_classifier/server/api.py +++ b/recaptcha_classifier/server/api.py @@ -4,7 +4,7 @@ from fastapi import FastAPI, File, UploadFile, Response from fastapi.responses import JSONResponse from PIL import Image -from .load_model import load_main_model +from .load_model import load_main_model, load_simple_model from recaptcha_classifier.detection_labels import DetectionLabels from recaptcha_classifier.constants import IMAGE_SIZE from pydantic import BaseModel @@ -24,15 +24,14 @@ class PredictionResponse(BaseModel): def load_models(): """Load the models into memory at startup.""" global model - model = load_main_model(device) - model.to(device) + model = load_simple_model(device) @app.post("/predict", response_model=PredictionResponse) async def predict(response: Response, file: UploadFile = File(...)) -> PredictionResponse: try: data = await file.read() img = Image.open(io.BytesIO(data)).convert("RGB") - result = predict(model, device, img) + result = inference(model, device, img) response.headers["Cache-Control"] = "max-age=3600" @@ -43,7 +42,7 @@ async def predict(response: Response, file: UploadFile = File(...)) -> Predictio content={"error": str(e)} ) -def predict(model: torch.nn.Module, device: torch.device, image: Image.Image) -> dict: +def inference(model: torch.nn.Module, device: torch.device, image: Image.Image) -> dict: """ Handles the prediction logic for the uploaded image. """ @@ -58,7 +57,7 @@ def predict(model: torch.nn.Module, device: torch.device, image: Image.Image) -> label = DetectionLabels.from_id(id) return { - "label": label.name, - "confidence": float(output.max().item()), + "label": label, + "confidence": f"{float(output.max().item()) * 100:.2f}%", "class_id": id } \ No newline at end of file diff --git a/recaptcha_classifier/server/app.py b/recaptcha_classifier/server/app.py index 2a2c770da7..732cc5daf3 100644 --- a/recaptcha_classifier/server/app.py +++ b/recaptcha_classifier/server/app.py @@ -1,6 +1,7 @@ import streamlit as st import torch from typing import Literal +import requests class StreamlitApp: @@ -54,10 +55,25 @@ def render_inference_tab(self) -> None: st.title("Inference") st.write("Please upload an image for inference.") - uploaded_file = st.file_uploader("Choose an image...", type=["jpg", "jpeg", "png"]) + file = st.file_uploader("Choose an image...", type=["jpg", "jpeg", "png"]) - if uploaded_file is not None: - # process and call api - st.image(uploaded_file, caption='Uploaded Image.', use_column_width=True) + if file is not None: + st.image(file, caption='Uploaded Image.', use_column_width=True) if st.button("Run Inference"): - pass \ No newline at end of file + files = {"file": {file.getvalue()}} + + try: + resp = requests.post("http://localhost:8000/predict", files=files) + resp.raise_for_status() + result = resp.json() + + st.success("Prediction successful!") + st.write(f"Label: {result['label']}") + st.write(f"Confidence: {result['confidence']:.2f}") + st.write(f"Class ID: {result['class_id']}") + except Exception as e: + st.error(f"Error during inference: {str(e)}") + +if __name__ == "__main__": + app = StreamlitApp() + app.render() \ No newline at end of file diff --git a/recaptcha_classifier/server/load_model.py b/recaptcha_classifier/server/load_model.py index c8d62eb58f..0020ce0982 100644 --- a/recaptcha_classifier/server/load_model.py +++ b/recaptcha_classifier/server/load_model.py @@ -1,9 +1,6 @@ import torch -from torchvision import transforms -from PIL import Image - +import os from ..models.main_model.model_class import MainCNN -from recaptcha_classifier.detection_labels import DetectionLabels from recaptcha_classifier.constants import ( MODELS_FOLDER, @@ -18,7 +15,7 @@ def load_simple_model(device: torch.device = torch.device("cpu")): """ from ..models.simple_classifier_model import SimpleCNN model = SimpleCNN() - path = MODELS_FOLDER + "/" + SIMPLE_MODEL_FILE_NAME + path = os.path.join(MODELS_FOLDER, SIMPLE_MODEL_FILE_NAME) model.load_state_dict(torch.load(path, map_location=device)) model.to(device) model.eval() @@ -28,7 +25,7 @@ def load_main_model(device: torch.device = torch.device("cpu")): """ Load the main CNN model for image classification. """ - path = MODELS_FOLDER + "/" + MAIN_MODEL_FILE_NAME + path = os.path.join(MODELS_FOLDER, MAIN_MODEL_FILE_NAME) checkpoint = torch.load(path, map_location=device) config = checkpoint['config'] model = MainCNN( diff --git a/reports/figures/simple-model.png b/reports/figures/simple-model.png index 8004263166a5aa75c4ae12de17b94427cdfb128e..55b6774af41e3ff063d12e844f3d64037bfe0cc9 100644 GIT binary patch literal 80765 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 HEByZe#hI4y literal 78418 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

4=DqRHsUpXkpm z=%A6^@-a_LR~xw&#V_e8!cptKl*H+* z7Y`i`Be`}cpepa~OQ>W_3lGP_8#zQXH)mUDAPj(QBQ|^4pF?_l*?x)3x1%l^-zyKR z(_I}^?@;t%igai7co*bC9N4?|Z^@GD&OT%tBVV_+aW=Zzi=L&ulTB}-&R0z@KDt>@ zW|V~9F!&ZVqw}Yhp|7{f_Ep?Y-hDJ&5%3lQik8FmiRszyH2VHrO(2kh?(Xi&0Vd(X+s)1G^Z_ z(F;;+*Clerfzs1>1s0mH@Q3{VkLGU2bzg#_tIwJRJIjm(jApu>)#ie=+AInpe528& zJMOh}g*iaDfb_zJk68DOk4;aUkWYZu0w_cmGoA3LObVE4|L8i|AeWB2!CL3=@NfZb1b+GY|=I8x`H=aKS7kvVfhoJ8H z3~e9sh+fnL?9Q~lee>LDbv>6n@_uL{%D&7rSR%TMWp`S1=~a{YR^yAA`S^3_Os-wQ zW?Hcm1*L4f4@6!Qfgxs#kkB0hx+@1yFRmu7WnF8mQe7PV0{BphvZukbF}S?4dqe`> zFTQ>IX03aF1C5^+ACLWJU&MC#NhSs15($8H87tOg12#)|ATb672g72Af{}mklNlH6 zlt(%vt82;|75dWqYC}A^rKl1ZWORIwv341k9(EiG33iN>4yQiJJbcW@w9{ZQTs17;3yQ9(Zzi*952>zcBo(->nY=hH5Lr>4D3e#UK~PV3Pq@n z_4)p^wb_`nz%0z;w^sQ>M;Ufpa?#I`xzGHEI}wV*ygFwvq383g4mU<`ExA;KDhy#E zcMj8+6d!JUZ}eXB^MvK0v!DZClkmZL! zMVca-?9w!j+tbTilD@WC_~buR9g#Jj*q<}P!*Wl02)#GU7-AeYq z2BjJy^)9=3*e)PgGKlGSo$cP9CzG_g*y1(3v$eJs2vYpn>ScOdZU?WPbdutaz}7&I z7X$N!zMCYwusCQAqKZ^pce}uub zTKI=0Dfk$K$By@WY39yPgEER>>8&GUQ{&(s=R3G+c3(4+cxV)^Sy>frE#M;wD_Jjk zGDtkbvYO72P&^`Z-rs|BwkG$1?0Il-ZD0*f|3%*FXfn< zd%UUB%WS_ZPFPYz&dP-i>Pw!vQ1`6l@ZZ{{9StF{!wjg)tCj-~%i8qShYR!9_EgXZ5!NlP>iSs+j_OQx@yl>wsZ4#g>geb^$>;zf_k=NkgVPUEbNE6TGnn|M%XX(sQTg_yixWvF2x`?lW#-7U$KXF4&h0qT64^bJr=ZKW=K+?(s_54 zFhrY}=C1`T9qw`Z7mC?)i99J~BD%UnDJd!Lw`wspN!oXaE<99LF=M=57xb%ZYYr=& zu+8qr&_M7vtnf>q$tf<@G5R;F1-cw%ywX(W^%^IZzl>K7omC%)XimmDBmW9I$j=7h2W;YKAvDtR zh&BYE&{3$bapC>w2qDg_X>yAW0Yz_ ziWO4olOIT!U`A-4)jf0k`xn!^(E0!I$7VEA3HpNnnfZWAUE#3*+j@Db6IcFxZ&{;^ z;4?kS%nf9)g=}<-VW>ivaL#a`HLJB@sWz{#!l{S0BD(C8Q)$7p#{UHe;mElK&|d!+ z*Y!5mOJGDjVr5QOPV!e$Qt2SU-|WoGetxTb)^_vi^^~~1twHn^XV_K6itM{KZ;Dtf zT&GKg0QD_T*r}wezV9zg4Z3|7dxRE@@$*qWi*aC|5zH&E*M>{ujx~GvxcX_0svb3A z(oC^3pRjMS5^0A_Yd8$H-(6aw{=;c7((kKsQIp>+cP_2X!>R*wa3v2W_5sVPpB2V-w>a0W+(b&VqE$gYs`$!(oIopB`L@%8Lc)ep-%u zGZ%CHxc^`xNEz=oDg5~uI|OHVBAClEYhL=U*Vpw82Ge~PhCR~@D7U~0es_-BX~x5+ zt4$GLe6$s`PhK}^v)Ku=oYr4Y9@e&+Q}A@bXBH0C$d3$ZSbwpd79TP%z^_5jJFjX) z$43pJt6~O*(#_4y)~(8T)phuV`8*kQ0D3hK zIAULB^i>;Psz?7$KBbb{)AvQ`h!#;QvT&Jcnw2)W(K{a3uK zF^Qtur3T|APNc!o81#JEL0b=mnCLf|0wCKB?WC*h#Yya@?mYzGyY={bi)EZv-uMos z7SjvYi>sMII)^A%p9gRPD8k9yy?*-rw#6Vkyp3~y>tJTb@}y}π9dvzb^!Tr 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:B=-3s7s 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 From 3cb29f0bc81c9106f150fedef93f665d6bd626dd Mon Sep 17 00:00:00 2001 From: Sinan Date: Thu, 29 May 2025 22:25:57 +0200 Subject: [PATCH 28/32] streamlit working --- recaptcha_classifier/server/api.py | 7 +++++-- recaptcha_classifier/server/app.py | 6 +++--- 2 files changed, 8 insertions(+), 5 deletions(-) diff --git a/recaptcha_classifier/server/api.py b/recaptcha_classifier/server/api.py index 33ed2668a4..48e84d623e 100644 --- a/recaptcha_classifier/server/api.py +++ b/recaptcha_classifier/server/api.py @@ -3,6 +3,7 @@ import torch from fastapi import FastAPI, File, UploadFile, Response from fastapi.responses import JSONResponse +import torch.nn.functional as F from PIL import Image from .load_model import load_main_model, load_simple_model from recaptcha_classifier.detection_labels import DetectionLabels @@ -13,7 +14,7 @@ class PredictionResponse(BaseModel): label: str - confidence: float + confidence: str class_id: int @@ -53,11 +54,13 @@ def inference(model: torch.nn.Module, device: torch.device, image: Image.Image) with torch.no_grad(): output = model(tensor) + prob = F.softmax(output, dim=1) + conf = prob.argmax(dim=1).item() id = output.argmax(dim=1).item() label = DetectionLabels.from_id(id) return { "label": label, - "confidence": f"{float(output.max().item()) * 100:.2f}%", + "confidence": f"{conf * 100:.2f}%", "class_id": id } \ No newline at end of file diff --git a/recaptcha_classifier/server/app.py b/recaptcha_classifier/server/app.py index 732cc5daf3..18e917c83b 100644 --- a/recaptcha_classifier/server/app.py +++ b/recaptcha_classifier/server/app.py @@ -60,16 +60,16 @@ def render_inference_tab(self) -> None: if file is not None: st.image(file, caption='Uploaded Image.', use_column_width=True) if st.button("Run Inference"): - files = {"file": {file.getvalue()}} + files = {"file": (file.name, file.getvalue(), file.type)} try: - resp = requests.post("http://localhost:8000/predict", files=files) + resp = requests.post("http://127.0.0.1:8000/predict", files=files) resp.raise_for_status() result = resp.json() st.success("Prediction successful!") st.write(f"Label: {result['label']}") - st.write(f"Confidence: {result['confidence']:.2f}") + st.write(f"Confidence: {result['confidence']}") st.write(f"Class ID: {result['class_id']}") except Exception as e: st.error(f"Error during inference: {str(e)}") From eef2c1391df946823b66947f1553ac39541d7949 Mon Sep 17 00:00:00 2001 From: Sinan Date: Thu, 29 May 2025 22:32:00 +0200 Subject: [PATCH 29/32] training real model --- recaptcha_classifier/models/main_model/HPoptimizer.py | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/recaptcha_classifier/models/main_model/HPoptimizer.py b/recaptcha_classifier/models/main_model/HPoptimizer.py index f9517388ac..7f669ce0ad 100644 --- a/recaptcha_classifier/models/main_model/HPoptimizer.py +++ b/recaptcha_classifier/models/main_model/HPoptimizer.py @@ -1,7 +1,7 @@ import itertools import random import pandas as pd - +from typing import List from recaptcha_classifier.models.main_model.model_class import MainCNN from recaptcha_classifier.train.training import Trainer @@ -80,9 +80,10 @@ def optimize_hyperparameters(self, return df_opt_data.copy()[:n_models] - def _train_one_model(self, hp_combo) -> None: + def _train_one_model(self, hp_combo: List, save_checkpoints: bool) -> None: model = MainCNN(n_layers=int(hp_combo[0]), kernel_size=int(hp_combo[1])) - self.trainer.train(model=model, lr=hp_combo[2], load_checkpoint=False) + self.trainer.train(model=model, lr=hp_combo[2], load_checkpoint=False, + save_checkpoint=save_checkpoints) def _generate_hp_combinations(self, hp) -> list: From a44af7ab08cee9ddfe04b00bbadf66e107c84b20 Mon Sep 17 00:00:00 2001 From: Sinan Date: Thu, 29 May 2025 22:37:45 +0200 Subject: [PATCH 30/32] trying to train on vm --- recaptcha_classifier/data/visualizer.py | 4 +++- .../features/evaluation/classification_metrics.py | 4 +++- recaptcha_classifier/models/main_model/kfold_validation.py | 4 +++- 3 files changed, 9 insertions(+), 3 deletions(-) diff --git a/recaptcha_classifier/data/visualizer.py b/recaptcha_classifier/data/visualizer.py index 6a95de7c33..650f7e41d5 100644 --- a/recaptcha_classifier/data/visualizer.py +++ b/recaptcha_classifier/data/visualizer.py @@ -77,4 +77,6 @@ def annotate(bars, counts): ax.grid(axis='y', linestyle='--', alpha=0.7) plt.tight_layout() - plt.show() + #plt.show() + plt.savefig("outputs/plot1.png", dpi=300, bbox_inches='tight') + plt.close() \ No newline at end of file diff --git a/recaptcha_classifier/features/evaluation/classification_metrics.py b/recaptcha_classifier/features/evaluation/classification_metrics.py index 3ad76ba0c9..4464c230c1 100644 --- a/recaptcha_classifier/features/evaluation/classification_metrics.py +++ b/recaptcha_classifier/features/evaluation/classification_metrics.py @@ -62,7 +62,9 @@ def evaluate_classification(y_pred: Tensor, if cm_plot: fig_, ax_ = confmat.plot(labels=class_names if class_names else None) - plt.show() + #plt.show() + plt.savefig("outputs/plot3.png", dpi=300, bbox_inches='tight') + plt.close() return { 'Accuracy': acc_val.item(), diff --git a/recaptcha_classifier/models/main_model/kfold_validation.py b/recaptcha_classifier/models/main_model/kfold_validation.py index 7513e6166f..f55a4e4e06 100644 --- a/recaptcha_classifier/models/main_model/kfold_validation.py +++ b/recaptcha_classifier/models/main_model/kfold_validation.py @@ -135,4 +135,6 @@ def plot_results(results: pd.DataFrame) -> None: ax.set_xticklabels(metrics, rotation=45, ha='right') plt.tight_layout() plt.grid(axis='y') - plt.show() + #plt.show() + plt.savefig("outputs/plot5.png", dpi=300, bbox_inches='tight') + plt.close() From 06ed77316ac098d497ef1177d0262fb5a0d5ea24 Mon Sep 17 00:00:00 2001 From: Sinan Date: Thu, 29 May 2025 23:44:32 +0200 Subject: [PATCH 31/32] last changes --- README.md | 150 +++++++++++------- main.py | 2 +- recaptcha_classifier/data/visualizer.py | 4 +- .../evaluation/classification_metrics.py | 4 +- .../models/main_model/kfold_validation.py | 4 +- .../pipeline/base_pipeline.py | 8 +- .../pipeline/main_model_pipeline.py | 2 +- recaptcha_classifier/server/api.py | 21 ++- recaptcha_classifier/server/app.py | 15 +- recaptcha_classifier/server/load_model.py | 16 +- recaptcha_classifier/train/training.py | 8 +- 11 files changed, 155 insertions(+), 79 deletions(-) diff --git a/README.md b/README.md index 3d251d83b5..268f6a09d8 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,6 @@ -# reCAPTCHA Solver with Multi-Task Learning 🛠️ +# reCAPTCHA Solver 🛠️ -This project is part of the **[Applied Machine Learning](https://ocasys.rug.nl/current/catalog/course/WBAI065-05#WBAI065-05.2024-2025.1)** course at the **University of Groningen**, developed by **Group 23**. Our project goal is to build an AI system that is able to automatically solve reCAPTCHA by combining **image classification** and **object detection** using **Multi-Task Learning**. +This project is part of the **[Applied Machine Learning](https://ocasys.rug.nl/current/catalog/course/WBAI065-05#WBAI065-05.2024-2025.1)** course at the **University of Groningen**, developed by **Group 23**. Our project goal is to build an AI system that is able to automatically solve reCAPTCHA tests through **image classification techniques**. --- @@ -13,94 +13,136 @@ This project is part of the **[Applied Machine Learning](https://ocasys.rug.nl/c Before getting started with our project, we encourage you to carefully read the sections below. - - ## Prerequisites Make sure you have the following installed: -- **Pipenv**: Pipenv is used for dependency management. This tools enables users to easily create and manage virtual environments. To install Pipenv, use the following command: +- **Pipenv**: Pipenv is used for dependency management. This tool enables users to easily create and manage virtual environments. To install Pipenv, use the following command: ```bash $ pip install --user pipenv ``` For detailed installation instructions, [click here](https://pipenv.pypa.io/en/latest/installation.html). ## Getting Started -### Setting up your own repository -1. Fork this repository. -2. Clone your fork locally. -3. Configure a remote pointing to the upstream repository to sync changes between your fork and the original repository. - ```bash - git remote add upstream https://github.com/ivopascal/Applied-ML-Template - ``` - **Don't skip this step.** We might update the original repository, so you should be able to easily pull our changes. - - To update your forked repo follow these steps: - 1. `git fetch upstream` - 2. `git rebase upstream/main` - 3. `git push origin main` - - Sometimes you may need to use `git push --force origin main`. Only use this flag the first time you push after you rebased, and be careful as you might overwrite your teammates' changes. - -### Pipenv -This tool is incredibly easy to use. Let's **install** our first package, which you will all need in your projects. +## Installing Dependencies +To install the project dependencies run: ```bash -pipenv install +pipenv install ``` -After running this command, you will notice that two files were modified, namely, _Pipfile_ and _Pipfile.lock_. _Pipfile_ is the configuration file that specifies all the dependencies in your virtual environment. +This will automatically create a virtual environment. + +To **activate** the virtual environment, run: -To **uninstall** a package, you can run the command: ```bash -pipenv uninstall +pipenv shell ``` -To **activate** the virtual environment, run `pipenv shell`. You can now use the environment as you wish. To **deactivate** the environment run the command `exit`. - -If you **already have access to a Pipfile**, you can install the dependencies using `pipenv install`. - -For a comprehensive list of commands, consult the [official documentation](https://pipenv.pypa.io/en/latest/cli.html). +To **deactivate** the virtual environment, run: -### Unit testing -You are expected to test your code using unit testing, which is a technique where small individual components of your code are tested in isolation. +```bash +exit +``` -An **example** is given in _tests/test_main.py_, which uses the standard _unittest_ Python module to test whether the function _hello_world_ from _main.py_ works as expected. +## Testing +You can run all the unit and integration tests which use the standard _unittest_ Python module with the following command: -To run all the tests developed using _unittest_, simply use: ```bash python -m unittest discover tests ``` If you wish to see additional details, run it in verbose mode: + ```bash python -m unittest discover -v tests ``` -## Get Coding -You are now ready to start working on your projects. +## Repository Structure + +To make navigating through the repository easier, you can find its structure below, with additional comments. -We recommend following the same folder structure as in the original repository. This will make it easier for you to have cleaner and consistent code, and easier for us to follow your progress and help you. -Your repository should look something like this: ```bash -├───data # Stores .csv -├───models # Stores .pkl -├───notebooks # Contains experimental .ipynbs -├───project_name -│ ├───data # For data processing, not storing .csv -│ ├───features -│ └───models # For model creation, not storing .pkl +├───data # Stores the .csv dataset +├───models # Stores the .pkl models +├───notebooks # Empty +├───recaptcha_classifier +│ ├───data # Data processing +│ ├───features # Evaluation class +│ └───models # Model classes ├───reports ├───tests -│ ├───data -│ ├───features -│ └───models +│ ├───data # Unit tests for data processing +│ ├───features # Unit tests for evaluation +│ ├───integration # Integration tests +│ └───models # Unit tests for models ├───.gitignore ├───.pre-commit-config.yaml -├───main.py +├───main.py ├───train_model.py -├───Pipfile +├───Pipfile # Dependencies ├───Pipfile.lock -├───README.md +├───README.md # Instructions +``` + +## **🚀 Usage Guide** + +### Running the App + +1. Activate pipenv environment (if not already activated) + +```bash +pipenv shell +``` + +2. To launch any component of our project, run: +```bash +python main.py [OPTION] ``` -**Good luck and happy coding! 🚀** \ No newline at end of file +Available list of options: +--streamlit - Launches Streamlit UI +--api - starts the FastAPI backend +--train-simple-cnn - Trains the simple baseline model +--train-main-cnn - Trains our main model + +If no argument has been passed, an interactive menu will appear to let you choose the action. + +## API Documentation + +### Example API call and response format + +You can make a call to the api using curl, by running the command below. Make sure to include a valid file path. The path can either be absolute (full) or relative to your +current location from command terminal. + +```bash +curl -X POST "http://localhost:8000/predict" -H "accept: application/json" -H "Content-Type: multipart/form-data" -F "file=@" +``` +You will get a response in the following format: + +```json + { + "class_id":1, + "class_name":"Bridge", + "confidence": "99.9%" + } +``` + +The API is stateless, initializing the model on launching, and caches responses for 1 hour. + + +After running the server, you can access the Documentation: + +Interactive API docs (Swagger UI): http://localhost:8000/docs + +ReDoc documentation: http://localhost:8000/redoc + +These interfaces allow you to test predictions and inspect the request/response formats. + + + +### Possible error Responses + +| Status code | Description +|--------------|----------------------------------------------------------------| +| 200 | Succesful Prediction | +| 422 | Validation error (eg. file not provided or malformed request) | \ No newline at end of file diff --git a/main.py b/main.py index e34ca1c52b..bcfe6274a5 100644 --- a/main.py +++ b/main.py @@ -67,7 +67,7 @@ def train_simple_cnn(): def train_main_classifier(): from recaptcha_classifier.pipeline.main_model_pipeline import MainClassifierPipeline - pipeline = MainClassifierPipeline(epochs=1, k_folds=2) + pipeline = MainClassifierPipeline() pipeline.run(save_train_checkpoints=False) def open_api(): diff --git a/recaptcha_classifier/data/visualizer.py b/recaptcha_classifier/data/visualizer.py index 650f7e41d5..cd3801a83f 100644 --- a/recaptcha_classifier/data/visualizer.py +++ b/recaptcha_classifier/data/visualizer.py @@ -77,6 +77,4 @@ def annotate(bars, counts): ax.grid(axis='y', linestyle='--', alpha=0.7) plt.tight_layout() - #plt.show() - plt.savefig("outputs/plot1.png", dpi=300, bbox_inches='tight') - plt.close() \ No newline at end of file + plt.show() \ No newline at end of file diff --git a/recaptcha_classifier/features/evaluation/classification_metrics.py b/recaptcha_classifier/features/evaluation/classification_metrics.py index 4464c230c1..3ad76ba0c9 100644 --- a/recaptcha_classifier/features/evaluation/classification_metrics.py +++ b/recaptcha_classifier/features/evaluation/classification_metrics.py @@ -62,9 +62,7 @@ def evaluate_classification(y_pred: Tensor, if cm_plot: fig_, ax_ = confmat.plot(labels=class_names if class_names else None) - #plt.show() - plt.savefig("outputs/plot3.png", dpi=300, bbox_inches='tight') - plt.close() + plt.show() return { 'Accuracy': acc_val.item(), diff --git a/recaptcha_classifier/models/main_model/kfold_validation.py b/recaptcha_classifier/models/main_model/kfold_validation.py index f55a4e4e06..7513e6166f 100644 --- a/recaptcha_classifier/models/main_model/kfold_validation.py +++ b/recaptcha_classifier/models/main_model/kfold_validation.py @@ -135,6 +135,4 @@ def plot_results(results: pd.DataFrame) -> None: ax.set_xticklabels(metrics, rotation=45, ha='right') plt.tight_layout() plt.grid(axis='y') - #plt.show() - plt.savefig("outputs/plot5.png", dpi=300, bbox_inches='tight') - plt.close() + plt.show() diff --git a/recaptcha_classifier/pipeline/base_pipeline.py b/recaptcha_classifier/pipeline/base_pipeline.py index fec7ac4df6..8355855936 100644 --- a/recaptcha_classifier/pipeline/base_pipeline.py +++ b/recaptcha_classifier/pipeline/base_pipeline.py @@ -14,7 +14,8 @@ def __init__(self, save_folder: str = "checkpoints", model_file_name: str = "model.pt", optimizer_file_name: str = "optimizer.pt", - scheduler_file_name: str = "scheduler.pt" + scheduler_file_name: str = "scheduler.pt", + early_stopping: bool = True ): self.lr = lr @@ -24,7 +25,7 @@ def __init__(self, self.model_file_name = model_file_name self.optimizer_file_name = optimizer_file_name self.scheduler_file_name = scheduler_file_name - + self.early_stopping = early_stopping self._class_map = DetectionLabels self._loaders = None self._data = None @@ -55,7 +56,8 @@ def _initialize_trainer(self) -> Trainer: model_file_name=self.model_file_name, optimizer_file_name=self.optimizer_file_name, scheduler_file_name=self.scheduler_file_name, - device=self.device) + device=self.device, + early_stopping=self.early_stopping) @property def class_map_length(self): diff --git a/recaptcha_classifier/pipeline/main_model_pipeline.py b/recaptcha_classifier/pipeline/main_model_pipeline.py index 31538aed0a..95f46efd5a 100644 --- a/recaptcha_classifier/pipeline/main_model_pipeline.py +++ b/recaptcha_classifier/pipeline/main_model_pipeline.py @@ -55,11 +55,11 @@ def _run_kfold_cross_validation(self) -> None: train_loader=self._loaders["train"], val_loader=self._loaders["val"], k_folds=self.k_folds, - hp_optimizer=self._hp_optimizer, device=self.device ) best_hp = self._hp_optimizer.get_best_hp() + self._kfold.run_cross_validation(hp=best_hp) self.lr = best_hp[2] diff --git a/recaptcha_classifier/server/api.py b/recaptcha_classifier/server/api.py index 48e84d623e..8b7b30c2c5 100644 --- a/recaptcha_classifier/server/api.py +++ b/recaptcha_classifier/server/api.py @@ -5,11 +5,12 @@ from fastapi.responses import JSONResponse import torch.nn.functional as F from PIL import Image -from .load_model import load_main_model, load_simple_model +from .load_model import load_main_model, get_model_path from recaptcha_classifier.detection_labels import DetectionLabels from recaptcha_classifier.constants import IMAGE_SIZE from pydantic import BaseModel from typing import Literal +import os class PredictionResponse(BaseModel): @@ -20,15 +21,29 @@ class PredictionResponse(BaseModel): app = FastAPI() device = torch.device("cuda" if torch.cuda.is_available() else "cpu") +model = None @app.on_event("startup") def load_models(): """Load the models into memory at startup.""" global model - model = load_simple_model(device) + model_path = get_model_path("main") + if os.path.exists(model_path): + model = load_main_model(device) + else: + print("Model file not found. API will return an error for predictions" + "until model is available.") @app.post("/predict", response_model=PredictionResponse) -async def predict(response: Response, file: UploadFile = File(...)) -> PredictionResponse: +async def predict(response: Response, + file: UploadFile = File(...) + ) -> PredictionResponse: + if model is None: + return JSONResponse( + status_code=503, + content={"error": "Model not loaded. Please train " + "or download first."} + ) try: data = await file.read() img = Image.open(io.BytesIO(data)).convert("RGB") diff --git a/recaptcha_classifier/server/app.py b/recaptcha_classifier/server/app.py index 18e917c83b..e75e889683 100644 --- a/recaptcha_classifier/server/app.py +++ b/recaptcha_classifier/server/app.py @@ -2,6 +2,8 @@ import torch from typing import Literal import requests +from recaptcha_classifier.pipeline.main_model_pipeline import MainClassifierPipeline +from recaptcha_classifier.pipeline.simple_cnn_pipeline import SimpleClassifierPipeline class StreamlitApp: @@ -46,7 +48,18 @@ def render_training_tab(self) -> None: help="CUDA is not available on this machine.") if st.button("Start Training"): - # logic here + if self.model_type == "Simple": + pipeline = SimpleClassifierPipeline(lr=self.lr, + epochs=self.epochs, + early_stopping=self.early_stopping, + device=self.device) + pipeline.run() + else: + pipeline = MainClassifierPipeline(lr=self.lr, + epochs=self.epochs, + early_stopping=self.early_stopping, + device=self.device) + pipeline.run() st.success(f"Started training {self.model_type} model with " f"learning rate {self.lr}, epochs {self.epochs}, " f"early stopping: {self.early_stopping}, on {self.device}.") diff --git a/recaptcha_classifier/server/load_model.py b/recaptcha_classifier/server/load_model.py index 0020ce0982..e8fa22bf75 100644 --- a/recaptcha_classifier/server/load_model.py +++ b/recaptcha_classifier/server/load_model.py @@ -15,7 +15,7 @@ def load_simple_model(device: torch.device = torch.device("cpu")): """ from ..models.simple_classifier_model import SimpleCNN model = SimpleCNN() - path = os.path.join(MODELS_FOLDER, SIMPLE_MODEL_FILE_NAME) + path = get_model_path("simple") model.load_state_dict(torch.load(path, map_location=device)) model.to(device) model.eval() @@ -25,7 +25,7 @@ def load_main_model(device: torch.device = torch.device("cpu")): """ Load the main CNN model for image classification. """ - path = os.path.join(MODELS_FOLDER, MAIN_MODEL_FILE_NAME) + path = get_model_path("main") checkpoint = torch.load(path, map_location=device) config = checkpoint['config'] model = MainCNN( @@ -35,4 +35,14 @@ def load_main_model(device: torch.device = torch.device("cpu")): model.load_state_dict(checkpoint['model_state_dict']) model.to(device) model.eval() - return model \ No newline at end of file + return model + +def get_model_path(model_type: str) -> str: + """ + Get the path to the model file based on the model type. + """ + if model_type == "simple": + return os.path.join(MODELS_FOLDER, SIMPLE_MODEL_FILE_NAME) + elif model_type == "main": + return os.path.join(MODELS_FOLDER, MAIN_MODEL_FILE_NAME) + return None \ No newline at end of file diff --git a/recaptcha_classifier/train/training.py b/recaptcha_classifier/train/training.py index 661bf3a34e..83ee75e507 100644 --- a/recaptcha_classifier/train/training.py +++ b/recaptcha_classifier/train/training.py @@ -20,7 +20,8 @@ def __init__(self, optimizer_file_name: str ='optimizer.pt', scheduler_file_name: str ='scheduler.pt', device: torch.device |None = None, - early_stop_threshold: int = 5 + early_stop_threshold: int = 5, + early_stopping: bool = True ): """ Constructor for Trainer class. @@ -47,7 +48,7 @@ def __init__(self, self._loss_acc_history = [] self.optimizer = None self.scheduler = None - + self._does_early_stop = early_stopping self._early_stop_threshold = early_stop_threshold self._best_val_loss = float('inf') self._stagnation_counter = 0 @@ -126,12 +127,11 @@ def train(self, val_acc = val_accuracy_counter.compute().item() print(f"Epoch {epoch+1} - Val loss: {val_loss:.4f}, Val accuracy: {val_acc:.4f}") - if self._early_stop(val_loss): + if self._does_early_stop and self._early_stop(val_loss): print(f"Early stopping at epoch {epoch + 1}.") print(f"Best validation loss: {self._best_val_loss:.4f}") break - def _train_one_epoch(self, model, train_accuracy_counter, train_loss_counter, train_progress_bar): model.train() for data, targets in train_progress_bar: From 0a47525f89e88c82de9325b7ec0ef6efb6ee4bdb Mon Sep 17 00:00:00 2001 From: Sinan Date: Fri, 30 May 2025 00:20:21 +0200 Subject: [PATCH 32/32] new img --- .github/workflows/ci.yml | 32 -------------------------------- reports/figures/main-model.png | Bin 75501 -> 75513 bytes 2 files changed, 32 deletions(-) delete mode 100644 .github/workflows/ci.yml diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml deleted file mode 100644 index 88812b3b37..0000000000 --- a/.github/workflows/ci.yml +++ /dev/null @@ -1,32 +0,0 @@ -name: Integration Tests - -on: - pull_request: - branches: - - main - types: - - opened - - synchronize - - reopened - -jobs: - integration-tests: - runs-on: ubuntu-latest - steps: - - name: Checkout code - uses: actions/checkout@v4 - - - name: Set up Python - uses: actions/setup-python@v5 - with: - python-version: '3.10' - - - name: Install dependencies - run: | - python -m pip install --upgrade pip - pip install pipenv - pipenv install --dev - - - name: Run integration tests - run: | - pipenv run python -m unittest discover -s tests/integration \ No newline at end of file diff --git a/reports/figures/main-model.png b/reports/figures/main-model.png index 4d076e623dd1a23abf0a343449b4184b313f8aab..726336548d7dac8e2a43cdd63616551081442fc8 100644 GIT binary patch literal 75513 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 b!k8&}hAy?|%g2>;8T>Xfw>jjj^Wy#+CTeIZ literal 75501 zcma&N19YVA*XA3W9lPUn$4|+qP}nwrzK8+qUhBr}};W-<SSnVZ)$DDOvlJT$4v9p#KFPFo|B&5@_)X9&f3nH-iW+0 z4A=^+jku~k2ne$7-yhHtzG71lP!JFaVF3k~jPp!L7kOp8p-)-%Q`-4&j#R&?5-@2m zJ9GO6uMyXJt-??XTv$sA`9A)hv z9%d_-9{?Y|eLFTB_(;L|3<}8)p#NL~?-ame`2cVJeGV?Dt^(PI)EoE~5)u+HPEO7e zrOLDlHCWxu_HvM8rVgqXOBV zRLll+_#2q&M@Z5`v7oAi^y%Ia&tvfT@I>s!GThIfR}@Tm>aX+isgaqRMOEX2N?cDW zM*7t1>0=h>=gQVOCI+PNOSZoqG>>co{deIdCkLjhKk24^73dPx$?Hv0Xv4o^Mk zD6Mz0?azJnHxdI^%UBGeQ;%yK!e6?8YJz`0HGY&|59HVX-K9RrkHW#tQq1+V1z&wl zdyYRTeaj6{>RU6O0=2KmcymyFou=};XBR6@1nEmz)ys^n3qk7c${ek0Ju@9h5wr*^ zFG!={#E1$Dii{>oL=DfgXcS7K;$f159n8GToYwSuq`QFS1Ab{moC)0J?s8j#d1odu z>3rPbi3uSU18%x4*zT;or`V)e#dG__gtC0?;-KT@UYtcEs1l_PT)o}6n|^wHyvKEZ zDG$5nCm7RaHN58*O~_JtaBVY69e>d73)IH=eRRv|c-*-sA4(m3a+u;QPVz|0=~{`oqe-#*z5!I_0tXm89YrlDeBcZ+D2Ln7-YuRS2`B zq*Ui5FnfD;-B&Zlny?jF9#7wa@Fdb`19xxjupagKds?)nsCfGX?o#N6HwI?-8M0qa zVJ@69G;_rBEIdJb27_&nR)3ca?KL+6tz$*T8quVq*)GAm3NHo>Xn5Mo@#Z4N5Irw- zGl<1&A!SkJXWEAM1zwaoC82=47&sl_BQ|dr->TOPJ~JnsXm8uoL6-CDB3;UlR^~RM zTM8)lOQ<^6C407^V`AFr&O~O4sny7U{^+~QeNC*&G_IO=&-YCdR$4SxX@{Me0;D#} z&nrwet1c+lk0`{ndBQA$)5Mb>`bYP|SFyCQSxKS6OYga5MSh}MNH*p>J_GShdyCIU z7`#=4O0om-pjF{~~yE zO#Ex@L~Cj&-S1hZF;A%kF83Te`J{qg)BsyD@2QE_x7gM+YEBvrkr118j>gx#64y8|woWC17~!4$ zF&~62HVari)%yF7T#vUi<^k;<@SnxG*WO=(DwT~G1(d%a@?4|>BqJny8bHlaZ*;AG zMOM@r9C1j!Z(+I291AuxQcry zF7gO2gpY)c7;@|rH2|T($_&}CFglt3>M+VgV+sz->X{KVMf(jEbtY6?>L>LRW9FJG zVmXC1-PezhmSKpB%s+`6)-&a>35p4$JcB3UQ?(WwD`30PA(1jYVGl>ko^_AksJ%0s z`E49fZ^QsET>}GW^l}|;tSn{Lkidy_v1Ws_rkpvD8QvKZE%9>x;VIT=FvjDZg%!$| z8^c|DhUwtII&%_Uguz)<_VJ&2lwl{kG={qBrUru?Asizk9(%Ebv~DCBO&IyStVg!} zz$KTFQxz3t;yLA1WzqUMFC?D6P3gDWYk*O%OO>kE=1V*?F!?HPU<@CYW^8znSH@2m z3^j4F{C-v(j(LMwWqx?^JJ%NdnQKrEzXd*p)E1YmG?EjEHMguRBIWq0#hDodlzuCZ zNq?<7jTW2;3agE0dxyHmtIF>ku)N!|t`PGzCNb0hY2+lYKN{-Sq11IdOoT82HK`Hm ze0A=K9%%IjvhX(a4i&7~0vjs6yb&Axd@_VludDF^y7>)tL}eptyE

`xO=`$#-hZ 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 z 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 O_}Ok|WtzMBV8lQEmTA}k