Source code for wormpose.dataset.loaders.resizer

"""
Handles all about the optional resizing of the images of a Dataset

It will modify the FeaturesDataset and the FramesDataset to update with the resized values.

The resize parameter can be specified as a single number (ex: 2 for upscale to twice the size, or 0.5 to downscale half the size)
or as the desired image size, in that case, the resize factor will be calculated depending on the average worm length
in the dataset.
"""

import copy
from typing import Type

import cv2
from wormpose.dataset.base_dataset import BaseFeaturesDataset, BaseFramesDataset

from wormpose.dataset.features import (
    calculate_max_average_worm_length,
    calculate_crop_window_size,
    MINIMUM_IMAGE_SIZE,
)


[docs]class ResizeOptions(object): """ Class handling the possible resize options: either a scaling factor, or a set image size """ def __init__(self, resize_factor: float = None, image_size: int = None, **kwargs): self.resize_factor = resize_factor self._image_size = image_size # validate if self._image_size is not None: if self._image_size % 2 != 0: raise NotImplementedError("If image size is set, it should be an even number") if self._image_size < MINIMUM_IMAGE_SIZE: raise ValueError(f"Image size must be bigger than {MINIMUM_IMAGE_SIZE}") def update_resize_factor(self, features_dataset): if self.resize_factor is not None: return if self._image_size is not None: worm_length = calculate_max_average_worm_length(features_dataset) self.resize_factor = self._image_size / worm_length return self.resize_factor = 1.0 def get_image_shape(self, features_dataset): if self._image_size is not None: return self._image_size, self._image_size return calculate_crop_window_size(features_dataset)
[docs]def add_resizing_arguments(parser): """ For command line arguments, adds two mutually exclusive resize options: either a scaling factor, or a set image size :param parser: Argparse parser """ group = parser.add_mutually_exclusive_group() group.add_argument( "--resize_factor", type=float, help="Option to resize the images. " "Examples: '0.5' downscale half, '2' upscale twice, '1' no resizing (default)", ) group.add_argument( "--image_size", type=int, help="Option to resize the images so that the average worm of the dataset " "fits into a square image of side: image_size", )
class ResizedFrames(object): def __init__(self, scale_factor: float, frames): self.frames = frames self.scale_factor = scale_factor def _resize(self, frame): return cv2.resize(frame, dsize=None, fx=self.scale_factor, fy=self.scale_factor) def __getitem__(self, key): if isinstance(key, slice): raise NotImplementedError("Slicing notation not implemented with resizer.") orig = self.frames[key] return self._resize(orig) def __len__(self): return len(self.frames) def __iter__(self): for orig in self.frames: yield self._resize(orig) class ResizedFramesReader(object): def __init__(self, frames_reader, resize_factor: float): self.frames_reader = frames_reader self.resize_factor = resize_factor def __enter__(self): self.resized_frames = ResizedFrames(self.resize_factor, self.frames_reader.__enter__()) return self.resized_frames def __exit__(self, exc_type, exc_value, traceback): self.resized_frames = None self.frames_reader.__exit__(exc_type, exc_value, traceback)
[docs]def frames_dataset_resizer(frames_dataset_class: Type[BaseFramesDataset], resize_factor: float): """ Modifies a FramesDataset class so that all frames are resized by resize_factor :param frames_dataset_class: original FramesDataset class without resizing :param resize_factor: by how much to rescale the images :return: new FramesDataset with resizing """ old_open = frames_dataset_class.open def resizer_open(*args): frames_reader = old_open(*args) return ResizedFramesReader(frames_reader, resize_factor=resize_factor) frames_dataset_class.open = resizer_open return frames_dataset_class
[docs]def features_dataset_resizer(features_dataset_class: Type[BaseFeaturesDataset], resize_factor: float): """ Modifies a FeaturesDataset class so that all features are resized by resize_factor :param frames_dataset_class: original FeaturesDataset class without resizing :param resize_factor: by how much to rescale the features :return: new FeaturesDataset with resizing """ old_get = features_dataset_class.get_features def resizer_get(*args): features = old_get(*args) new_features = copy.deepcopy(features) new_features["skeletons"] *= resize_factor new_features["head_width"] *= resize_factor new_features["midbody_width"] *= resize_factor new_features["tail_width"] *= resize_factor return new_features features_dataset_class.get_features = resizer_get return features_dataset_class