| | """ |
| | Code borrowed from https://github.com/zijundeng/pytorch-semantic-segmentation |
| | MIT License |
| | |
| | Copyright (c) 2017 ZijunDeng |
| | |
| | Permission is hereby granted, free of charge, to any person obtaining a copy |
| | of this software and associated documentation files (the "Software"), to deal |
| | in the Software without restriction, including without limitation the rights |
| | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell |
| | copies of the Software, and to permit persons to whom the Software is |
| | furnished to do so, subject to the following conditions: |
| | |
| | The above copyright notice and this permission notice shall be included in all |
| | copies or substantial portions of the Software. |
| | |
| | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR |
| | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, |
| | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE |
| | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER |
| | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, |
| | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE |
| | SOFTWARE. |
| | """ |
| |
|
| | import random |
| | import math |
| | import numbers |
| |
|
| | import numpy as np |
| | import torchvision.transforms as torch_tr |
| | import torch |
| | from PIL import Image, ImageFilter, ImageOps |
| |
|
| |
|
| | from skimage.filters import gaussian |
| |
|
| | class RandomGaussianBlur(object): |
| | """ |
| | Apply Gaussian Blur |
| | """ |
| | def __call__(self, imgs, mask): |
| | img, imgB = imgs[0], imgs[1] |
| | sigma = 0.15 + random.random() * 1.15 |
| | blurred_img = gaussian(np.array(img), sigma=sigma, channel_axis=-1) |
| | blurred_img *= 255 |
| | blurred_imgB = gaussian(np.array(imgB), sigma=sigma, channel_axis=-1) |
| | blurred_imgB *= 255 |
| | return Image.fromarray(blurred_img.astype(np.uint8)), Image.fromarray(blurred_imgB.astype(np.uint8)), mask |
| |
|
| |
|
| |
|
| | class RandomScale(object): |
| | def __init__(self, scale_list=[0.75, 1.0, 1.25], mode='value'): |
| | self.scale_list = scale_list |
| | self.mode = mode |
| |
|
| | def __call__(self, img, mask): |
| | oh, ow = img.size |
| | scale_amt = 1.0 |
| | if self.mode == 'value': |
| | scale_amt = np.random.choice(self.scale_list, 1) |
| | elif self.mode == 'range': |
| | scale_amt = random.uniform(self.scale_list[0], self.scale_list[-1]) |
| | h = int(scale_amt * oh) |
| | w = int(scale_amt * ow) |
| | return img.resize((w, h), Image.BICUBIC), mask.resize((w, h), Image.NEAREST) |
| |
|
| | class SmartCropV1(object): |
| | def __init__(self, crop_size=512, |
| | max_ratio=0.75, |
| | ignore_index=12, nopad=False): |
| | self.crop_size = crop_size |
| | self.max_ratio = max_ratio |
| | self.ignore_index = ignore_index |
| | self.crop = RandomCrop(crop_size, ignore_index=ignore_index, nopad=nopad) |
| |
|
| | def __call__(self, img, mask): |
| | assert img.size == mask.size |
| | count = 0 |
| | while True: |
| | img_crop, mask_crop = self.crop(img.copy(), mask.copy()) |
| | count += 1 |
| | labels, cnt = np.unique(np.array(mask_crop), return_counts=True) |
| | cnt = cnt[labels != self.ignore_index] |
| | if len(cnt) > 1 and np.max(cnt) / np.sum(cnt) < self.max_ratio: |
| | break |
| | if count > 10: |
| | break |
| |
|
| | return img_crop, mask_crop |
| |
|
| |
|
| |
|
| | class DeNormalize(object): |
| | def __init__(self, mean, std): |
| | self.mean = mean |
| | self.std = std |
| |
|
| | def __call__(self, tensor): |
| | for t, m, s in zip(tensor, self.mean, self.std): |
| | t.mul_(s).add_(m) |
| | return tensor |
| |
|
| |
|
| | class MaskToTensor(object): |
| | def __call__(self, img): |
| | return torch.from_numpy(np.array(img, dtype=np.int32)).long() |
| |
|
| |
|
| | class FreeScale(object): |
| | def __init__(self, size, interpolation=Image.BILINEAR): |
| | self.size = tuple(reversed(size)) |
| | self.interpolation = interpolation |
| |
|
| | def __call__(self, img): |
| | return img.resize(self.size, self.interpolation) |
| |
|
| |
|
| | class FlipChannels(object): |
| | def __call__(self, img): |
| | img = np.array(img)[:, :, ::-1] |
| | return Image.fromarray(img.astype(np.uint8)) |
| |
|
| | |
| | class Compose(object): |
| | def __init__(self, transforms): |
| | self.transforms = transforms |
| |
|
| | def __call__(self, imgs, mask): |
| | img ,imgB = imgs[0], imgs[1] |
| | assert img.size == mask.size |
| | for t in self.transforms: |
| | img, imgB, mask = t([img, imgB], mask) |
| | return img, imgB, mask |
| |
|
| | class RandomCrop(object): |
| | """ |
| | Take a random crop from the image. |
| | |
| | First the image or crop size may need to be adjusted if the incoming image |
| | is too small... |
| | |
| | If the image is smaller than the crop, then: |
| | the image is padded up to the size of the crop |
| | unless 'nopad', in which case the crop size is shrunk to fit the image |
| | |
| | A random crop is taken such that the crop fits within the image. |
| | If a centroid is passed in, the crop must intersect the centroid. |
| | """ |
| | def __init__(self, size, ignore_index=0, nopad=True): |
| | if isinstance(size, numbers.Number): |
| | self.size = (int(size), int(size)) |
| | else: |
| | self.size = size |
| | self.ignore_index = ignore_index |
| | self.nopad = nopad |
| | self.pad_color = (0, 0, 0) |
| |
|
| | def __call__(self, imgs, mask, centroid=None): |
| | img, imgB = imgs[0], imgs[1] |
| | assert img.size == mask.size |
| | w, h = img.size |
| | |
| | th, tw = self.size |
| | if w == tw and h == th: |
| | return img, imgB, mask |
| |
|
| | if self.nopad: |
| | if th > h or tw > w: |
| | |
| | shorter_side = min(w, h) |
| | th, tw = shorter_side, shorter_side |
| | else: |
| | |
| | if th > h: |
| | pad_h = (th - h) // 2 + 1 |
| | else: |
| | pad_h = 0 |
| | if tw > w: |
| | pad_w = (tw - w) // 2 + 1 |
| | else: |
| | pad_w = 0 |
| | border = (pad_w, pad_h, pad_w, pad_h) |
| | if pad_h or pad_w: |
| | img = ImageOps.expand(img, border=border, fill=self.pad_color) |
| | imgB = ImageOps.expand(imgB, border=border, fill=self.pad_color) |
| | mask = ImageOps.expand(mask, border=border, fill=self.ignore_index) |
| | w, h = img.size |
| |
|
| | if centroid is not None: |
| | |
| | |
| | c_x, c_y = centroid |
| | max_x = w - tw |
| | max_y = h - th |
| | x1 = random.randint(c_x - tw, c_x) |
| | x1 = min(max_x, max(0, x1)) |
| | y1 = random.randint(c_y - th, c_y) |
| | y1 = min(max_y, max(0, y1)) |
| | else: |
| | if w == tw: |
| | x1 = 0 |
| | else: |
| | x1 = random.randint(0, w - tw) |
| | if h == th: |
| | y1 = 0 |
| | else: |
| | y1 = random.randint(0, h - th) |
| | return img.crop((x1, y1, x1 + tw, y1 + th)), imgB.crop((x1, y1, x1 + tw, y1 + th)), mask.crop((x1, y1, x1 + tw, y1 + th)) |
| |
|
| | class CenterCrop(object): |
| | def __init__(self, size): |
| | if isinstance(size, numbers.Number): |
| | self.size = (int(size), int(size)) |
| | else: |
| | self.size = size |
| |
|
| | def __call__(self, img, mask): |
| | assert img.size == mask.size |
| | w, h = img.size |
| | th, tw = self.size |
| | x1 = int(round((w - tw) / 2.)) |
| | y1 = int(round((h - th) / 2.)) |
| | return img.crop((x1, y1, x1 + tw, y1 + th)), mask.crop((x1, y1, x1 + tw, y1 + th)) |
| |
|
| |
|
| | class RandomHorizontallyFlip(object): |
| | def __init__(self, p): |
| | self.p = p |
| |
|
| | def __call__(self, imgs, mask): |
| | img, imgB = imgs[0], imgs[1] |
| | if random.random() < self.p: |
| | return img.transpose(Image.FLIP_LEFT_RIGHT), imgB.transpose(Image.FLIP_LEFT_RIGHT), mask.transpose( |
| | Image.FLIP_LEFT_RIGHT) |
| | return img, imgB, mask |
| |
|
| |
|
| | class RandomVerticalFlip(object): |
| | def __init__(self, p): |
| | self.p = p |
| | |
| | def __call__(self, imgs, mask): |
| | img, imgB = imgs[0], imgs[1] |
| | if random.random() < self.p: |
| | return img.transpose(Image.FLIP_TOP_BOTTOM), imgB.transpose(Image.FLIP_TOP_BOTTOM), mask.transpose( |
| | Image.FLIP_TOP_BOTTOM) |
| | return img, imgB, mask |
| |
|
| |
|
| | class FreeScale(object): |
| | def __init__(self, size): |
| | self.size = tuple(reversed(size)) |
| |
|
| | def __call__(self, img, mask): |
| | assert img.size == mask.size |
| | return img.resize(self.size, Image.BILINEAR), mask.resize(self.size, Image.NEAREST) |
| |
|
| |
|
| | class Scale(object): |
| | def __init__(self, size): |
| | self.size = size |
| |
|
| | def __call__(self, img, mask): |
| | assert img.size == mask.size |
| | w, h = img.size |
| | if (w >= h and w == self.size) or (h >= w and h == self.size): |
| | return img, mask |
| | if w > h: |
| | ow = self.size |
| | oh = int(self.size * h / w) |
| | return img.resize((ow, oh), Image.BILINEAR), mask.resize((ow, oh), Image.NEAREST) |
| | else: |
| | oh = self.size |
| | ow = int(self.size * w / h) |
| | return img.resize((ow, oh), Image.BILINEAR), mask.resize((ow, oh), Image.NEAREST) |
| |
|
| |
|
| | class RandomSizedCrop(object): |
| | def __init__(self, size): |
| | self.size = size |
| |
|
| | def __call__(self, img, mask): |
| | assert img.size == mask.size |
| | for attempt in range(10): |
| | area = img.size[0] * img.size[1] |
| | target_area = random.uniform(0.45, 1.0) * area |
| | aspect_ratio = random.uniform(0.5, 2) |
| |
|
| | w = int(round(math.sqrt(target_area * aspect_ratio))) |
| | h = int(round(math.sqrt(target_area / aspect_ratio))) |
| |
|
| | if random.random() < 0.5: |
| | w, h = h, w |
| |
|
| | if w <= img.size[0] and h <= img.size[1]: |
| | x1 = random.randint(0, img.size[0] - w) |
| | y1 = random.randint(0, img.size[1] - h) |
| |
|
| | img = img.crop((x1, y1, x1 + w, y1 + h)) |
| | mask = mask.crop((x1, y1, x1 + w, y1 + h)) |
| | assert (img.size == (w, h)) |
| |
|
| | return img.resize((self.size, self.size), Image.BILINEAR), mask.resize((self.size, self.size), |
| | Image.NEAREST) |
| |
|
| | |
| | scale = Scale(self.size) |
| | crop = CenterCrop(self.size) |
| | return crop(*scale(img, mask)) |
| |
|
| |
|
| | class RandomRotate(object): |
| | def __init__(self, degree): |
| | self.degree = degree |
| |
|
| | def __call__(self, img, mask): |
| | rotate_degree = random.random() * 2 * self.degree - self.degree |
| | return img.rotate(rotate_degree, Image.BILINEAR), mask.rotate(rotate_degree, Image.NEAREST) |
| |
|
| |
|
| | class RandomSized(object): |
| | def __init__(self, size): |
| | self.size = size |
| | self.scale = Scale(self.size) |
| | self.crop = RandomCrop(self.size) |
| |
|
| | def __call__(self, img, mask): |
| | assert img.size == mask.size |
| |
|
| | w = int(random.uniform(0.5, 2) * img.size[0]) |
| | h = int(random.uniform(0.5, 2) * img.size[1]) |
| |
|
| | img, mask = img.resize((w, h), Image.BILINEAR), mask.resize((w, h), Image.NEAREST) |
| |
|
| | return self.crop(*self.scale(img, mask)) |
| |
|
| |
|
| | class SlidingCropOld(object): |
| | def __init__(self, crop_size, stride_rate, ignore_label): |
| | self.crop_size = crop_size |
| | self.stride_rate = stride_rate |
| | self.ignore_label = ignore_label |
| |
|
| | def _pad(self, img, mask): |
| | h, w = img.shape[: 2] |
| | pad_h = max(self.crop_size - h, 0) |
| | pad_w = max(self.crop_size - w, 0) |
| | img = np.pad(img, ((0, pad_h), (0, pad_w), (0, 0)), 'constant') |
| | mask = np.pad(mask, ((0, pad_h), (0, pad_w)), 'constant', constant_values=self.ignore_label) |
| | return img, mask |
| |
|
| | def __call__(self, img, mask): |
| | assert img.size == mask.size |
| |
|
| | w, h = img.size |
| | long_size = max(h, w) |
| |
|
| | img = np.array(img) |
| | mask = np.array(mask) |
| |
|
| | if long_size > self.crop_size: |
| | stride = int(math.ceil(self.crop_size * self.stride_rate)) |
| | h_step_num = int(math.ceil((h - self.crop_size) / float(stride))) + 1 |
| | w_step_num = int(math.ceil((w - self.crop_size) / float(stride))) + 1 |
| | img_sublist, mask_sublist = [], [] |
| | for yy in range(h_step_num): |
| | for xx in range(w_step_num): |
| | sy, sx = yy * stride, xx * stride |
| | ey, ex = sy + self.crop_size, sx + self.crop_size |
| | img_sub = img[sy: ey, sx: ex, :] |
| | mask_sub = mask[sy: ey, sx: ex] |
| | img_sub, mask_sub = self._pad(img_sub, mask_sub) |
| | img_sublist.append(Image.fromarray(img_sub.astype(np.uint8)).convert('RGB')) |
| | mask_sublist.append(Image.fromarray(mask_sub.astype(np.uint8)).convert('P')) |
| | return img_sublist, mask_sublist |
| | else: |
| | img, mask = self._pad(img, mask) |
| | img = Image.fromarray(img.astype(np.uint8)).convert('RGB') |
| | mask = Image.fromarray(mask.astype(np.uint8)).convert('P') |
| | return img, mask |
| |
|
| |
|
| | class SlidingCrop(object): |
| | def __init__(self, crop_size, stride_rate, ignore_label): |
| | self.crop_size = crop_size |
| | self.stride_rate = stride_rate |
| | self.ignore_label = ignore_label |
| |
|
| | def _pad(self, img, mask): |
| | h, w = img.shape[: 2] |
| | pad_h = max(self.crop_size - h, 0) |
| | pad_w = max(self.crop_size - w, 0) |
| | img = np.pad(img, ((0, pad_h), (0, pad_w), (0, 0)), 'constant') |
| | mask = np.pad(mask, ((0, pad_h), (0, pad_w)), 'constant', constant_values=self.ignore_label) |
| | return img, mask, h, w |
| |
|
| | def __call__(self, img, mask): |
| | assert img.size == mask.size |
| |
|
| | w, h = img.size |
| | long_size = max(h, w) |
| |
|
| | img = np.array(img) |
| | mask = np.array(mask) |
| |
|
| | if long_size > self.crop_size: |
| | stride = int(math.ceil(self.crop_size * self.stride_rate)) |
| | h_step_num = int(math.ceil((h - self.crop_size) / float(stride))) + 1 |
| | w_step_num = int(math.ceil((w - self.crop_size) / float(stride))) + 1 |
| | img_slices, mask_slices, slices_info = [], [], [] |
| | for yy in range(h_step_num): |
| | for xx in range(w_step_num): |
| | sy, sx = yy * stride, xx * stride |
| | ey, ex = sy + self.crop_size, sx + self.crop_size |
| | img_sub = img[sy: ey, sx: ex, :] |
| | mask_sub = mask[sy: ey, sx: ex] |
| | img_sub, mask_sub, sub_h, sub_w = self._pad(img_sub, mask_sub) |
| | img_slices.append(Image.fromarray(img_sub.astype(np.uint8)).convert('RGB')) |
| | mask_slices.append(Image.fromarray(mask_sub.astype(np.uint8)).convert('P')) |
| | slices_info.append([sy, ey, sx, ex, sub_h, sub_w]) |
| | return img_slices, mask_slices, slices_info |
| | else: |
| | img, mask, sub_h, sub_w = self._pad(img, mask) |
| | img = Image.fromarray(img.astype(np.uint8)).convert('RGB') |
| | mask = Image.fromarray(mask.astype(np.uint8)).convert('P') |
| | return [img], [mask], [[0, sub_h, 0, sub_w, sub_h, sub_w]] |
| |
|
| | class PadImage(object): |
| | def __init__(self, size, ignore_index): |
| | self.size = size |
| | self.ignore_index = ignore_index |
| |
|
| | |
| | def __call__(self, img, mask): |
| | assert img.size == mask.size |
| | th, tw = self.size, self.size |
| |
|
| | |
| | w, h = img.size |
| | |
| | if w > tw or h > th : |
| | wpercent = (tw/float(w)) |
| | target_h = int((float(img.size[1])*float(wpercent))) |
| | img, mask = img.resize((tw, target_h), Image.BICUBIC), mask.resize((tw, target_h), Image.NEAREST) |
| |
|
| | w, h = img.size |
| | |
| | img = ImageOps.expand(img, border=(0,0,tw-w, th-h), fill=0) |
| | mask = ImageOps.expand(mask, border=(0,0,tw-w, th-h), fill=self.ignore_index) |
| | |
| | return img, mask |
| |
|
| | class Resize(object): |
| | """ |
| | Resize image to exact size of crop |
| | """ |
| |
|
| | def __init__(self, size): |
| | self.size = (size, size) |
| |
|
| | def __call__(self, img, mask): |
| | assert img.size == mask.size |
| | w, h = img.size |
| | if (w == h and w == self.size): |
| | return img, mask |
| | return (img.resize(self.size, Image.BICUBIC), |
| | mask.resize(self.size, Image.NEAREST)) |
| |
|
| | class ResizeImage(object): |
| | """ |
| | Resize image to exact size of crop |
| | """ |
| |
|
| | def __init__(self, size): |
| | self.size = (size, size) |
| |
|
| | def __call__(self, img, mask): |
| | assert img.size == mask.size |
| | w, h = img.size |
| | if (w == h and w == self.size): |
| | return img, mask |
| | return (img.resize(self.size, Image.BICUBIC), |
| | mask) |
| |
|
| | class RandomSizeAndCrop(object): |
| | def __init__(self, size, crop_nopad, |
| | scale_min=1, scale_max=1.2, ignore_index=0, pre_size=None): |
| | self.size = size |
| | self.crop = RandomCrop(self.size, ignore_index=ignore_index, nopad=crop_nopad) |
| | self.scale_min = scale_min |
| | self.scale_max = scale_max |
| | self.pre_size = pre_size |
| |
|
| | def __call__(self, imgs, mask, centroid=None): |
| | img, imgB = imgs[0], imgs[1] |
| | assert img.size == mask.size |
| |
|
| | |
| | if self.pre_size is None: |
| | scale_amt = 1. |
| | elif img.size[1] < img.size[0]: |
| | scale_amt = self.pre_size / img.size[1] |
| | else: |
| | scale_amt = self.pre_size / img.size[0] |
| | scale_amt *= random.uniform(self.scale_min, self.scale_max) |
| | w, h = [int(i * scale_amt) for i in img.size] |
| |
|
| | if centroid is not None: |
| | centroid = [int(c * scale_amt) for c in centroid] |
| |
|
| | img, imgB, mask = img.resize((w, h), Image.BICUBIC), imgB.resize((w, h), Image.BICUBIC), mask.resize((w, h), Image.NEAREST) |
| |
|
| | return self.crop([img, imgB], mask, centroid) |
| |
|
| | class ColorJitter(object): |
| | """Randomly change the brightness, contrast and saturation of an image. |
| | |
| | Args: |
| | brightness (float): How much to jitter brightness. brightness_factor |
| | is chosen uniformly from [max(0, 1 - brightness), 1 + brightness]. |
| | contrast (float): How much to jitter contrast. contrast_factor |
| | is chosen uniformly from [max(0, 1 - contrast), 1 + contrast]. |
| | saturation (float): How much to jitter saturation. saturation_factor |
| | is chosen uniformly from [max(0, 1 - saturation), 1 + saturation]. |
| | hue(float): How much to jitter hue. hue_factor is chosen uniformly from |
| | [-hue, hue]. Should be >=0 and <= 0.5. |
| | """ |
| | def __init__(self, brightness=0, contrast=0, saturation=0, hue=0): |
| | self.brightness = brightness |
| | self.contrast = contrast |
| | self.saturation = saturation |
| | self.hue = hue |
| |
|
| | @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 > 0: |
| | brightness_factor = np.random.uniform(max(0, 1 - brightness), 1 + brightness) |
| | transforms.append( |
| | torch_tr.Lambda(lambda img: adjust_brightness(img, brightness_factor))) |
| |
|
| | if contrast > 0: |
| | contrast_factor = np.random.uniform(max(0, 1 - contrast), 1 + contrast) |
| | transforms.append( |
| | torch_tr.Lambda(lambda img: adjust_contrast(img, contrast_factor))) |
| |
|
| | if saturation > 0: |
| | saturation_factor = np.random.uniform(max(0, 1 - saturation), 1 + saturation) |
| | transforms.append( |
| | torch_tr.Lambda(lambda img: adjust_saturation(img, saturation_factor))) |
| |
|
| | if hue > 0: |
| | hue_factor = np.random.uniform(-hue, hue) |
| | transforms.append( |
| | torch_tr.Lambda(lambda img: adjust_hue(img, hue_factor))) |
| |
|
| | np.random.shuffle(transforms) |
| | transform = torch_tr.Compose(transforms) |
| |
|
| | return transform |
| |
|
| | def __call__(self, img): |
| | """ |
| | 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) |
| |
|
| |
|
| |
|