| | import collections |
| | import torchvision |
| | import torch |
| | import torchvision.transforms.functional as F |
| | import random |
| | import numbers |
| | import numpy as np |
| | from PIL import Image |
| |
|
| |
|
| | |
| | |
| | |
| | class ExtRandomHorizontalFlip(object): |
| | """Horizontally flip the given PIL Image randomly with a given probability. |
| | Args: |
| | p (float): probability of the image being flipped. Default value is 0.5 |
| | """ |
| |
|
| | def __init__(self, p=0.5): |
| | self.p = p |
| |
|
| | def __call__(self, img, lbl): |
| | """ |
| | Args: |
| | img (PIL Image): Image to be flipped. |
| | Returns: |
| | PIL Image: Randomly flipped image. |
| | """ |
| | if random.random() < self.p: |
| | return F.hflip(img), F.hflip(lbl) |
| | return img, lbl |
| |
|
| | def __repr__(self): |
| | return self.__class__.__name__ + '(p={})'.format(self.p) |
| |
|
| |
|
| |
|
| | class ExtCompose(object): |
| | """Composes several transforms together. |
| | Args: |
| | transforms (list of ``Transform`` objects): list of transforms to compose. |
| | Example: |
| | >>> transforms.Compose([ |
| | >>> transforms.CenterCrop(10), |
| | >>> transforms.ToTensor(), |
| | >>> ]) |
| | """ |
| |
|
| | def __init__(self, transforms): |
| | self.transforms = transforms |
| |
|
| | def __call__(self, img, lbl): |
| | for t in self.transforms: |
| | img, lbl = t(img, lbl) |
| | return img, lbl |
| |
|
| | def __repr__(self): |
| | format_string = self.__class__.__name__ + '(' |
| | for t in self.transforms: |
| | format_string += '\n' |
| | format_string += ' {0}'.format(t) |
| | format_string += '\n)' |
| | return format_string |
| |
|
| |
|
| | class ExtCenterCrop(object): |
| | """Crops the given PIL Image at the center. |
| | Args: |
| | size (sequence or int): Desired output size of the crop. If size is an |
| | int instead of sequence like (h, w), a square crop (size, size) is |
| | made. |
| | """ |
| |
|
| | def __init__(self, size): |
| | if isinstance(size, numbers.Number): |
| | self.size = (int(size), int(size)) |
| | else: |
| | self.size = size |
| |
|
| | def __call__(self, img, lbl): |
| | """ |
| | Args: |
| | img (PIL Image): Image to be cropped. |
| | Returns: |
| | PIL Image: Cropped image. |
| | """ |
| | return F.center_crop(img, self.size), F.center_crop(lbl, self.size) |
| |
|
| | def __repr__(self): |
| | return self.__class__.__name__ + '(size={0})'.format(self.size) |
| |
|
| |
|
| | class ExtRandomScale(object): |
| | def __init__(self, scale_range, interpolation=Image.BILINEAR): |
| | self.scale_range = scale_range |
| | self.interpolation = interpolation |
| |
|
| | def __call__(self, img, lbl): |
| | """ |
| | Args: |
| | img (PIL Image): Image to be scaled. |
| | lbl (PIL Image): Label to be scaled. |
| | Returns: |
| | PIL Image: Rescaled image. |
| | PIL Image: Rescaled label. |
| | """ |
| | assert img.size == lbl.size |
| | scale = random.uniform(self.scale_range[0], self.scale_range[1]) |
| | target_size = ( int(img.size[1]*scale), int(img.size[0]*scale) ) |
| | return F.resize(img, target_size, self.interpolation), F.resize(lbl, target_size, Image.NEAREST) |
| |
|
| | def __repr__(self): |
| | interpolate_str = _pil_interpolation_to_str[self.interpolation] |
| | return self.__class__.__name__ + '(size={0}, interpolation={1})'.format(self.size, interpolate_str) |
| |
|
| | class ExtScale(object): |
| | """Resize the input PIL Image to the given scale. |
| | Args: |
| | Scale (sequence or int): scale factors |
| | interpolation (int, optional): Desired interpolation. Default is |
| | ``PIL.Image.BILINEAR`` |
| | """ |
| |
|
| | def __init__(self, scale, interpolation=Image.BILINEAR): |
| | self.scale = scale |
| | self.interpolation = interpolation |
| |
|
| | def __call__(self, img, lbl): |
| | """ |
| | Args: |
| | img (PIL Image): Image to be scaled. |
| | lbl (PIL Image): Label to be scaled. |
| | Returns: |
| | PIL Image: Rescaled image. |
| | PIL Image: Rescaled label. |
| | """ |
| | assert img.size == lbl.size |
| | target_size = ( int(img.size[1]*self.scale), int(img.size[0]*self.scale) ) |
| | return F.resize(img, target_size, self.interpolation), F.resize(lbl, target_size, Image.NEAREST) |
| |
|
| | def __repr__(self): |
| | interpolate_str = _pil_interpolation_to_str[self.interpolation] |
| | return self.__class__.__name__ + '(size={0}, interpolation={1})'.format(self.size, interpolate_str) |
| |
|
| |
|
| | class ExtRandomRotation(object): |
| | """Rotate the image by angle. |
| | Args: |
| | degrees (sequence or float or int): Range of degrees to select from. |
| | If degrees is a number instead of sequence like (min, max), the range of degrees |
| | will be (-degrees, +degrees). |
| | resample ({PIL.Image.NEAREST, PIL.Image.BILINEAR, PIL.Image.BICUBIC}, optional): |
| | An optional resampling filter. |
| | See http://pillow.readthedocs.io/en/3.4.x/handbook/concepts.html#filters |
| | If omitted, or if the image has mode "1" or "P", it is set to PIL.Image.NEAREST. |
| | expand (bool, optional): Optional expansion flag. |
| | If true, expands the output to make it large enough to hold the entire rotated image. |
| | If false or omitted, make the output image the same size as the input image. |
| | Note that the expand flag assumes rotation around the center and no translation. |
| | center (2-tuple, optional): Optional center of rotation. |
| | Origin is the upper left corner. |
| | Default is the center of the image. |
| | """ |
| |
|
| | def __init__(self, degrees, resample=False, expand=False, center=None): |
| | if isinstance(degrees, numbers.Number): |
| | if degrees < 0: |
| | raise ValueError("If degrees is a single number, it must be positive.") |
| | self.degrees = (-degrees, degrees) |
| | else: |
| | if len(degrees) != 2: |
| | raise ValueError("If degrees is a sequence, it must be of len 2.") |
| | self.degrees = degrees |
| |
|
| | self.resample = resample |
| | self.expand = expand |
| | self.center = center |
| |
|
| | @staticmethod |
| | def get_params(degrees): |
| | """Get parameters for ``rotate`` for a random rotation. |
| | Returns: |
| | sequence: params to be passed to ``rotate`` for random rotation. |
| | """ |
| | angle = random.uniform(degrees[0], degrees[1]) |
| |
|
| | return angle |
| |
|
| | def __call__(self, img, lbl): |
| | """ |
| | img (PIL Image): Image to be rotated. |
| | lbl (PIL Image): Label to be rotated. |
| | Returns: |
| | PIL Image: Rotated image. |
| | PIL Image: Rotated label. |
| | """ |
| |
|
| | angle = self.get_params(self.degrees) |
| |
|
| | return F.rotate(img, angle, self.resample, self.expand, self.center), F.rotate(lbl, angle, self.resample, self.expand, self.center) |
| |
|
| | def __repr__(self): |
| | format_string = self.__class__.__name__ + '(degrees={0}'.format(self.degrees) |
| | format_string += ', resample={0}'.format(self.resample) |
| | format_string += ', expand={0}'.format(self.expand) |
| | if self.center is not None: |
| | format_string += ', center={0}'.format(self.center) |
| | format_string += ')' |
| | return format_string |
| |
|
| | class ExtRandomHorizontalFlip(object): |
| | """Horizontally flip the given PIL Image randomly with a given probability. |
| | Args: |
| | p (float): probability of the image being flipped. Default value is 0.5 |
| | """ |
| |
|
| | def __init__(self, p=0.5): |
| | self.p = p |
| |
|
| | def __call__(self, img, lbl): |
| | """ |
| | Args: |
| | img (PIL Image): Image to be flipped. |
| | Returns: |
| | PIL Image: Randomly flipped image. |
| | """ |
| | if random.random() < self.p: |
| | return F.hflip(img), F.hflip(lbl) |
| | return img, lbl |
| |
|
| | def __repr__(self): |
| | return self.__class__.__name__ + '(p={})'.format(self.p) |
| |
|
| |
|
| | class ExtRandomVerticalFlip(object): |
| | """Vertically flip the given PIL Image randomly with a given probability. |
| | Args: |
| | p (float): probability of the image being flipped. Default value is 0.5 |
| | """ |
| |
|
| | def __init__(self, p=0.5): |
| | self.p = p |
| |
|
| | def __call__(self, img, lbl): |
| | """ |
| | Args: |
| | img (PIL Image): Image to be flipped. |
| | lbl (PIL Image): Label to be flipped. |
| | Returns: |
| | PIL Image: Randomly flipped image. |
| | PIL Image: Randomly flipped label. |
| | """ |
| | if random.random() < self.p: |
| | return F.vflip(img), F.vflip(lbl) |
| | return img, lbl |
| |
|
| | def __repr__(self): |
| | return self.__class__.__name__ + '(p={})'.format(self.p) |
| |
|
| | class ExtPad(object): |
| | def __init__(self, diviser=32): |
| | self.diviser = diviser |
| | |
| | def __call__(self, img, lbl): |
| | h, w = img.size |
| | ph = (h//32+1)*32 - h if h%32!=0 else 0 |
| | pw = (w//32+1)*32 - w if w%32!=0 else 0 |
| | im = F.pad(img, ( pw//2, pw-pw//2, ph//2, ph-ph//2) ) |
| | lbl = F.pad(lbl, ( pw//2, pw-pw//2, ph//2, ph-ph//2)) |
| | return im, lbl |
| |
|
| | class ExtToTensor(object): |
| | """Convert a ``PIL Image`` or ``numpy.ndarray`` to tensor. |
| | Converts a PIL Image or numpy.ndarray (H x W x C) in the range |
| | [0, 255] to a torch.FloatTensor of shape (C x H x W) in the range [0.0, 1.0]. |
| | """ |
| | def __init__(self, normalize=True, target_type='uint8'): |
| | self.normalize = normalize |
| | self.target_type = target_type |
| | def __call__(self, pic, lbl): |
| | """ |
| | Note that labels will not be normalized to [0, 1]. |
| | Args: |
| | pic (PIL Image or numpy.ndarray): Image to be converted to tensor. |
| | lbl (PIL Image or numpy.ndarray): Label to be converted to tensor. |
| | Returns: |
| | Tensor: Converted image and label |
| | """ |
| | if self.normalize: |
| | return F.to_tensor(pic), torch.from_numpy( np.array( lbl, dtype=self.target_type) ) |
| | else: |
| | return torch.from_numpy( np.array( pic, dtype=np.float32).transpose(2, 0, 1) ), torch.from_numpy( np.array( lbl, dtype=self.target_type) ) |
| |
|
| | def __repr__(self): |
| | return self.__class__.__name__ + '()' |
| |
|
| | class ExtNormalize(object): |
| | """Normalize a tensor image with mean and standard deviation. |
| | Given mean: ``(M1,...,Mn)`` and std: ``(S1,..,Sn)`` for ``n`` channels, this transform |
| | will normalize each channel of the input ``torch.*Tensor`` i.e. |
| | ``input[channel] = (input[channel] - mean[channel]) / std[channel]`` |
| | Args: |
| | mean (sequence): Sequence of means for each channel. |
| | std (sequence): Sequence of standard deviations for each channel. |
| | """ |
| |
|
| | def __init__(self, mean, std): |
| | self.mean = mean |
| | self.std = std |
| |
|
| | def __call__(self, tensor, lbl): |
| | """ |
| | Args: |
| | tensor (Tensor): Tensor image of size (C, H, W) to be normalized. |
| | tensor (Tensor): Tensor of label. A dummy input for ExtCompose |
| | Returns: |
| | Tensor: Normalized Tensor image. |
| | Tensor: Unchanged Tensor label |
| | """ |
| | return F.normalize(tensor, self.mean, self.std), lbl |
| |
|
| | def __repr__(self): |
| | return self.__class__.__name__ + '(mean={0}, std={1})'.format(self.mean, self.std) |
| |
|
| |
|
| | class ExtRandomCrop(object): |
| | """Crop the given PIL Image at a random location. |
| | Args: |
| | size (sequence or int): Desired output size of the crop. If size is an |
| | int instead of sequence like (h, w), a square crop (size, size) is |
| | made. |
| | padding (int or sequence, optional): Optional padding on each border |
| | of the image. Default is 0, i.e no padding. If a sequence of length |
| | 4 is provided, it is used to pad left, top, right, bottom borders |
| | respectively. |
| | pad_if_needed (boolean): It will pad the image if smaller than the |
| | desired size to avoid raising an exception. |
| | """ |
| |
|
| | def __init__(self, size, padding=0, pad_if_needed=False): |
| | if isinstance(size, numbers.Number): |
| | self.size = (int(size), int(size)) |
| | else: |
| | self.size = size |
| | self.padding = padding |
| | self.pad_if_needed = pad_if_needed |
| |
|
| | @staticmethod |
| | def get_params(img, output_size): |
| | """Get parameters for ``crop`` for a random crop. |
| | Args: |
| | img (PIL Image): Image to be cropped. |
| | output_size (tuple): Expected output size of the crop. |
| | Returns: |
| | tuple: params (i, j, h, w) to be passed to ``crop`` for random crop. |
| | """ |
| | w, h = img.size |
| | th, tw = output_size |
| | if w == tw and h == th: |
| | return 0, 0, h, w |
| |
|
| | i = random.randint(0, h - th) |
| | j = random.randint(0, w - tw) |
| | return i, j, th, tw |
| |
|
| | def __call__(self, img, lbl): |
| | """ |
| | Args: |
| | img (PIL Image): Image to be cropped. |
| | lbl (PIL Image): Label to be cropped. |
| | Returns: |
| | PIL Image: Cropped image. |
| | PIL Image: Cropped label. |
| | """ |
| | assert img.size == lbl.size, 'size of img and lbl should be the same. %s, %s'%(img.size, lbl.size) |
| | if self.padding > 0: |
| | img = F.pad(img, self.padding) |
| | lbl = F.pad(lbl, self.padding) |
| |
|
| | |
| | if self.pad_if_needed and img.size[0] < self.size[1]: |
| | img = F.pad(img, padding=int((1 + self.size[1] - img.size[0]) / 2)) |
| | lbl = F.pad(lbl, padding=int((1 + self.size[1] - lbl.size[0]) / 2)) |
| |
|
| | |
| | if self.pad_if_needed and img.size[1] < self.size[0]: |
| | img = F.pad(img, padding=int((1 + self.size[0] - img.size[1]) / 2)) |
| | lbl = F.pad(lbl, padding=int((1 + self.size[0] - lbl.size[1]) / 2)) |
| |
|
| | i, j, h, w = self.get_params(img, self.size) |
| |
|
| | return F.crop(img, i, j, h, w), F.crop(lbl, i, j, h, w) |
| |
|
| | def __repr__(self): |
| | return self.__class__.__name__ + '(size={0}, padding={1})'.format(self.size, self.padding) |
| |
|
| |
|
| | class ExtResize(object): |
| | """Resize the input PIL Image to the given size. |
| | Args: |
| | size (sequence or int): Desired output size. If size is a sequence like |
| | (h, w), output size will be matched to this. If size is an int, |
| | smaller edge of the image will be matched to this number. |
| | i.e, if height > width, then image will be rescaled to |
| | (size * height / width, size) |
| | interpolation (int, optional): Desired interpolation. Default is |
| | ``PIL.Image.BILINEAR`` |
| | """ |
| |
|
| | def __init__(self, size, interpolation=Image.BILINEAR): |
| | assert isinstance(size, int) or (isinstance(size, collections.Iterable) and len(size) == 2) |
| | self.size = size |
| | self.interpolation = interpolation |
| |
|
| | def __call__(self, img, lbl): |
| | """ |
| | Args: |
| | img (PIL Image): Image to be scaled. |
| | Returns: |
| | PIL Image: Rescaled image. |
| | """ |
| | return F.resize(img, self.size, self.interpolation), F.resize(lbl, self.size, Image.NEAREST) |
| |
|
| | def __repr__(self): |
| | interpolate_str = _pil_interpolation_to_str[self.interpolation] |
| | return self.__class__.__name__ + '(size={0}, interpolation={1})'.format(self.size, interpolate_str) |
| | |
| | class ExtColorJitter(object): |
| | """Randomly change the brightness, contrast and saturation of an image. |
| | Args: |
| | brightness (float or tuple of float (min, max)): How much to jitter brightness. |
| | brightness_factor is chosen uniformly from [max(0, 1 - brightness), 1 + brightness] |
| | or the given [min, max]. Should be non negative numbers. |
| | contrast (float or tuple of float (min, max)): How much to jitter contrast. |
| | contrast_factor is chosen uniformly from [max(0, 1 - contrast), 1 + contrast] |
| | or the given [min, max]. Should be non negative numbers. |
| | saturation (float or tuple of float (min, max)): How much to jitter saturation. |
| | saturation_factor is chosen uniformly from [max(0, 1 - saturation), 1 + saturation] |
| | or the given [min, max]. Should be non negative numbers. |
| | hue (float or tuple of float (min, max)): How much to jitter hue. |
| | hue_factor is chosen uniformly from [-hue, hue] or the given [min, max]. |
| | Should have 0<= hue <= 0.5 or -0.5 <= min <= max <= 0.5. |
| | """ |
| | def __init__(self, brightness=0, contrast=0, saturation=0, hue=0): |
| | self.brightness = self._check_input(brightness, 'brightness') |
| | self.contrast = self._check_input(contrast, 'contrast') |
| | self.saturation = self._check_input(saturation, 'saturation') |
| | self.hue = self._check_input(hue, 'hue', center=0, bound=(-0.5, 0.5), |
| | clip_first_on_zero=False) |
| |
|
| | def _check_input(self, value, name, center=1, bound=(0, float('inf')), clip_first_on_zero=True): |
| | if isinstance(value, numbers.Number): |
| | if value < 0: |
| | raise ValueError("If {} is a single number, it must be non negative.".format(name)) |
| | value = [center - value, center + value] |
| | if clip_first_on_zero: |
| | value[0] = max(value[0], 0) |
| | elif isinstance(value, (tuple, list)) and len(value) == 2: |
| | if not bound[0] <= value[0] <= value[1] <= bound[1]: |
| | raise ValueError("{} values should be between {}".format(name, bound)) |
| | else: |
| | raise TypeError("{} should be a single number or a list/tuple with lenght 2.".format(name)) |
| |
|
| | |
| | |
| | if value[0] == value[1] == center: |
| | value = None |
| | return value |
| |
|
| | @staticmethod |
| | def get_params(brightness, contrast, saturation, hue): |
| | """Get a randomized transform to be applied on image. |
| | Arguments are same as that of __init__. |
| | Returns: |
| | Transform which randomly adjusts brightness, contrast and |
| | saturation in a random order. |
| | """ |
| | transforms = [] |
| |
|
| | if brightness is not None: |
| | brightness_factor = random.uniform(brightness[0], brightness[1]) |
| | transforms.append(Lambda(lambda img: F.adjust_brightness(img, brightness_factor))) |
| |
|
| | if contrast is not None: |
| | contrast_factor = random.uniform(contrast[0], contrast[1]) |
| | transforms.append(Lambda(lambda img: F.adjust_contrast(img, contrast_factor))) |
| |
|
| | if saturation is not None: |
| | saturation_factor = random.uniform(saturation[0], saturation[1]) |
| | transforms.append(Lambda(lambda img: F.adjust_saturation(img, saturation_factor))) |
| |
|
| | if hue is not None: |
| | hue_factor = random.uniform(hue[0], hue[1]) |
| | transforms.append(Lambda(lambda img: F.adjust_hue(img, hue_factor))) |
| |
|
| | random.shuffle(transforms) |
| | transform = Compose(transforms) |
| |
|
| | return transform |
| |
|
| | def __call__(self, img, lbl): |
| | """ |
| | Args: |
| | img (PIL Image): Input image. |
| | Returns: |
| | PIL Image: Color jittered image. |
| | """ |
| | transform = self.get_params(self.brightness, self.contrast, |
| | self.saturation, self.hue) |
| | return transform(img), lbl |
| |
|
| | def __repr__(self): |
| | format_string = self.__class__.__name__ + '(' |
| | format_string += 'brightness={0}'.format(self.brightness) |
| | format_string += ', contrast={0}'.format(self.contrast) |
| | format_string += ', saturation={0}'.format(self.saturation) |
| | format_string += ', hue={0})'.format(self.hue) |
| | return format_string |
| |
|
| | class Lambda(object): |
| | """Apply a user-defined lambda as a transform. |
| | Args: |
| | lambd (function): Lambda/function to be used for transform. |
| | """ |
| |
|
| | def __init__(self, lambd): |
| | assert callable(lambd), repr(type(lambd).__name__) + " object is not callable" |
| | self.lambd = lambd |
| |
|
| | def __call__(self, img): |
| | return self.lambd(img) |
| |
|
| | def __repr__(self): |
| | return self.__class__.__name__ + '()' |
| |
|
| |
|
| | class Compose(object): |
| | """Composes several transforms together. |
| | Args: |
| | transforms (list of ``Transform`` objects): list of transforms to compose. |
| | Example: |
| | >>> transforms.Compose([ |
| | >>> transforms.CenterCrop(10), |
| | >>> transforms.ToTensor(), |
| | >>> ]) |
| | """ |
| |
|
| | def __init__(self, transforms): |
| | self.transforms = transforms |
| |
|
| | def __call__(self, img): |
| | for t in self.transforms: |
| | img = t(img) |
| | return img |
| |
|
| | def __repr__(self): |
| | format_string = self.__class__.__name__ + '(' |
| | for t in self.transforms: |
| | format_string += '\n' |
| | format_string += ' {0}'.format(t) |
| | format_string += '\n)' |
| | return format_string |
| |
|