|
- """
- This script defines several transforms that can be used for (image, label) image pairs.
- Most of the transforms are based on the code of torchvision.
- These transforms are useful when input and label are both images.
-
- Some of the transforms only change the image but keep the label unchanged, e.g. Normalize.
- While others will change image and label simultaneously.
-
- Author: Hui Qu
- """
- import random
- from PIL import Image, ImageOps, ImageEnhance,ImageFilter#ImageEnhance add
- import numpy as np
- import numbers
- import collections
- from mindspore import Tensor
- from skimage import morphology
- # import SimpleITK as sitk
- import mindspore.dataset.vision.py_transforms as py_vision
- import time
- import cv2
-
- class Compose(object):
- """ Composes several transforms together. 一起组成几个变换
- Args:
- transforms (list of ``Transform`` objects): list of transforms to compose.
- """
-
- def __init__(self, transforms, selectorNameList): #add. selectorNameList
- self.transforms = transforms
- self.selectorNameList = selectorNameList
- def __call__(self, imgs):
- number = 0 #20191029add
- for t in self.transforms:
-
- selectorName = str(self.selectorNameList[number])
-
- start_time = time.time()
-
- imgs = t(imgs)
-
- number = number + 1
- return imgs
-
-
- class ToTensor(object):
- """ Convert (img, label) of type ``PIL.Image`` or ``numpy.ndarray`` to tensors.
- Converts img of type 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]
- Converts label of type PIL.Image or numpy.ndarray (H x W) in the range [0, 255]
- to a torch.LongTensor of shape (H x W) in the range [0, 255].
- """
- def __init__(self, index=1):
- self.index = index # index to distinguish between images and labels
- self.totensor = py_vision.ToTensor()
-
- def __call__(self, imgs):
- """
- Args:
- imgs (PIL.Image or numpy.ndarray): Image to be converted to tensor.
- Returns:
- Tensor: Converted image.
- """
- if len(imgs) < self.index:
- raise ValueError('The number of images is smaller than separation index!')
-
- pics = []
-
- # process image
- for i in range(0, self.index):
- img = imgs[i]
- pic = self.totensor(img)
- pics.append(pic)
-
- # process labels:
- for i in range(self.index, len(imgs)):
- # process label
- label = imgs[i]
- # handle PIL Image
- label_tensor = np.frombuffer(label.tobytes(), dtype=np.uint8)
- # PIL image mode: 1, L, P, I, F, RGB, YCbCr, RGBA, CMYK
- if label.mode == 'YCbCr':
- nchannel = 3
- elif label.mode == 'I;16':
- nchannel = 1
- else:
- nchannel = len(label.mode)
- label_tensor = label_tensor.reshape(label.size[1], label.size[0], nchannel)
- # put it from HWC to CHW format
- # yikes, this transpose takes 80% of the loading time/CPU
- label_tensor = np.transpose(label_tensor, (2, 0, 1)).astype(np.int32)
- # label_tensor = label_tensor.view(label.size[1], label.size[0])
- pics.append(Tensor(label_tensor))
- return tuple(pics)
-
-
- class Normalize(object):
- """ Normalize an tensor image with mean and standard deviation.
- Given mean and std, will normalize each channel of the torch.*Tensor,
- i.e. channel = (channel - mean) / std
- Args:
- mean (sequence): Sequence of means for each channel.
- std (sequence): Sequence of standard deviations for each channel.
- ** only normalize the first image, keep the target image unchanged
- """
-
- def __init__(self, mean, std):
- self.mean = mean
- self.std = std
-
- def __call__(self, tensors):
- """
- Args:
- tensors (Tensor): Tensor images of size (C, H, W) to be normalized.
- Returns:
- Tensor: Normalized image.
- """
- tensors = list(tensors)
- for t, m, s in zip(tensors[0], self.mean, self.std):
- t.sub_(m).div_(s)
- return tuple(tensors)
-
-
- class Scale(object):
- """Rescale the input PIL images to the given size. """
-
- 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, imgs):
- pics = []
- for img in imgs:
- if isinstance(self.size, int):
- w, h = img.size
- if (w <= h and w == self.size) or (h <= w and h == self.size):
- pics.append(img)
- continue
- if w < h:
- ow = self.size
- oh = int(self.size * h / w)
- pics.append(img.resize((ow, oh), self.interpolation))
- continue
- else:
- oh = self.size
- ow = int(self.size * w / h)
- pics.append(img.resize((ow, oh), self.interpolation))
- else:
- pics.append(img.resize(self.size, self.interpolation))
- return tuple(pics)
-
-
- class RandomCrop(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 (w, h), 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.
- """
-
- def __init__(self, size, padding=0, fill_val=(0,)):
- if isinstance(size, numbers.Number):
- self.size = (int(size), int(size))
- else:
- self.size = size
- self.padding = padding
- self.fill_val = fill_val
-
- def __call__(self, imgs):
- """
- Args:
- img (PIL.Image): Image to be cropped.
- Returns:
- PIL.Image: Cropped image.
- """
- pics = []
-
- w, h = imgs[0].size
- th, tw = self.size
- x1 = random.randint(0, w - tw)
- y1 = random.randint(0, h - th)
-
- for k in range(len(imgs)):
- img = imgs[k]
- if self.padding > 0:
- img = ImageOps.expand(img, border=self.padding, fill=self.fill_val[k])
-
- if w == tw and h == th:
- pics.append(img)
- continue
-
- pics.append(img.crop((x1, y1, x1 + tw, y1 + th)))
-
-
- return tuple(pics)
-
-
- class RandomHorizontalFlip(object):
- """Horizontally flip the given PIL.Image randomly with a probability of 0.5."""
-
- def __call__(self, imgs):
- """
- Args:
- img (PIL.Image): Image to be flipped.
- Returns:
- PIL.Image: Randomly flipped image.
- """
-
- pics = []
- if random.random() < 0.5:
- for img in imgs:#imgs
- pics.append(img.transpose(Image.FLIP_LEFT_RIGHT))
- return tuple(pics)
- else:
- return imgs
-
-
- class RandomVerticalFlip(object):
- """Horizontally flip the given PIL.Image randomly with a probability of 0.5."""
-
- def __call__(self, imgs):
- """
- Args:
- img (PIL.Image): Image to be flipped.
- Returns:
- PIL.Image: Randomly flipped image.
- """
- pics = []
- if random.random() < 0.5:
- for img in imgs:
- pics.append(img.transpose(Image.FLIP_TOP_BOTTOM))
-
- return tuple(pics)
- else:
- return imgs
-
-
- class RandomElasticDeform(object):
- """ Elastic deformation of the input PIL Image using random displacement vectors
- drawm from a gaussian distribution 弹性变形
- Args:
- sigma: the largest possible deviation of random parameters
- """
- def __init__(self, num_pts=4, sigma=20):
- self.num_pts = num_pts
- self.sigma = sigma
-
- def __call__(self, imgs):
- pics = []
-
- img = np.array(imgs[0])
- if len(img.shape) == 3:
- img = img[:,:,0]
-
- sitkImage = sitk.GetImageFromArray(img, isVector=False)
- mesh_size = [self.num_pts]*sitkImage.GetDimension()
- tx = sitk.BSplineTransformInitializer(sitkImage, mesh_size)
-
- params = tx.GetParameters()
- paramsNp = np.asarray(params, dtype=float)
- paramsNp = paramsNp + np.random.randn(paramsNp.shape[0]) * self.sigma
-
- paramsNp[0:int(len(params)/3)] = 0 # remove z deformations! The resolution in z is too bad
-
- params = tuple(paramsNp)
- tx.SetParameters(params)
-
- resampler = sitk.ResampleImageFilter()
- resampler.SetReferenceImage(sitkImage)
- resampler.SetInterpolator(sitk.sitkLinear)
- resampler.SetDefaultPixelValue(0)
- resampler.SetTransform(tx)
- resampler.SetDefaultPixelValue(0)
-
- for img in imgs:
- is_expand = False
- if not isinstance(img, np.ndarray):
- img = np.array(img)
-
- if len(img.shape) == 2:
- img = np.expand_dims(img, axis=2)
- is_expand = True
-
- img_deformed = np.zeros(img.shape, dtype=img.dtype)
-
- for i in range(img.shape[2]):
- sitkImage = sitk.GetImageFromArray(img[:,:,i], isVector=False)
- outimgsitk = resampler.Execute(sitkImage)
- img_deformed[:,:,i] = sitk.GetArrayFromImage(outimgsitk)
-
- if is_expand:
- img_deformed = img_deformed[:,:,0]
- # print img_deformed.dtype
- pics.append(Image.fromarray(img_deformed))
-
- return tuple(pics)
-
-
- class RandomRotation(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=Image.BILINEAR, 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, imgs):
- """
- imgs (PIL Image): Images to be rotated.
- Returns:
- PIL Image: Rotated image.
- """
-
- angle = self.get_params(self.degrees)
-
- pics = []
- for img in imgs:
- pics.append(img.rotate(angle, self.resample, self.expand, self.center))
-
- return tuple(pics)
-
-
- class RandomResize(object):
- """Randomly Resize the input PIL Image using a scale of lb~ub.
- Args:
- lb (float): lower bound of the scale
- ub (float): upper bound of the scale
- interpolation (int, optional): Desired interpolation. Default is
- ``PIL.Image.BILINEAR``
- """
-
- def __init__(self, lb=0.5, ub=1.5, interpolation=Image.BILINEAR):
- self.lb = lb
- self.ub = ub
- self.interpolation = interpolation
-
- def __call__(self, imgs):
- """
- Args:
- imgs (PIL Images): Images to be scaled.
- Returns:
- PIL Images: Rescaled images.
- """
-
- for img in imgs:
- if not isinstance(img, Image.Image):
- raise TypeError('img should be PIL Image. Got {}'.format(type(img)))
-
- scale = random.uniform(self.lb, self.ub)
- # print scale
-
- w, h = imgs[0].size
- ow = int(w * scale)
- oh = int(h * scale)
-
- if scale < 1:
- padding_l = (w - ow)//2
- padding_t = (h - oh)//2
- padding_r = w - ow - padding_l
- padding_b = h - oh - padding_t
- padding = (padding_l, padding_t, padding_r, padding_b)
-
- pics = []
- for i in range(len(imgs)):
- img = imgs[i]
- img = img.resize((ow, oh), self.interpolation)
- if scale < 1:
- img = ImageOps.expand(img, border=padding, fill=0)
- pics.append(img)
-
-
-
- return tuple(pics)
-
-
- class RandomAffine(object):
- """ Transform the input PIL Image using a random affine transformation 使用随机仿射变换来变换输入的PIL图像
- The parameters of an affine transformation [a, b, c=0
- d, e, f=0]
- are generated randomly according to the bound, and there is no translation
- (c=f=0)
- Args:
- bound: the largest possible deviation of random parameters
- """
-
- def __init__(self, bound):
- if bound < 0 or bound > 0.5:
- raise ValueError("Bound is invalid, should be in range [0, 0.5)")
-
- self.bound = bound
-
- def __call__(self, imgs):
- img = imgs[0]
- x, y = img.size
-
- a = 1 + 2 * self.bound * (random.random() - 0.5)
- b = 2 * self.bound * (random.random() - 0.5)
- d = 2 * self.bound * (random.random() - 0.5)
- e = 1 + 2 * self.bound * (random.random() - 0.5)
-
- # correct the transformation center to image center
- c = -a * x / 2 - b * y / 2 + x / 2
- f = -d * x / 2 - e * y / 2 + y / 2
-
- trans_matrix = [a, b, c, d, e, f]
-
- pics = []
- for img in imgs:
- pics.append(img.transform((x, y), Image.AFFINE, trans_matrix))
-
-
- return tuple(pics)
-
-
-
- class RandomColor(object):
- """ 改变 [亮度,对比,饱和,色调]
- 还有一个函数叫 torchvision.ColorJitter(brightness=0.5, contrast=0.5, hue=0.5)
- brightness contrast saturation hue
- 对图像进行颜色抖动
- :param image: PIL的图像image
- :return: 有颜色色差的图像image
- """
- def __init__(self, randomMin = 1, randomMax = 2):
-
- self.randomMin = randomMin
- self.randomMax = randomMax
-
-
- def __call__(self, imgs):
- """
- random_factor = np.random.randint(0, 31) / 10. # 随机因子
- color_image = ImageEnhance.Color(image).enhance(random_factor) # 调整图像的饱和度
- random_factor = np.random.randint(10, 21) / 10. # 随机因子
- brightness_image = ImageEnhance.Brightness(color_image).enhance(random_factor) # 调整图像的亮度
- random_factor = np.random.randint(10, 21) / 10. # 随机因1子
- contrast_image = ImageEnhance.Contrast(brightness_image).enhance(random_factor) # 调整图像对比度
- random_factor = np.random.randint(0, 31) / 10. # 随机因子
- return ImageEnhance.Sharpness(contrast_image).enhance(random_factor) # 调整图像锐度
- """
- out_imgs = list(imgs)
- img = imgs[0]
- random_factor = np.random.rand() * 2 #随机因子 ok
- # 创建一个增强对象,以调整图像的颜色。增强因子为0.0将产生黑白图像;为1.0将给出原始图像
- color_image = ImageEnhance.Color(img).enhance(random_factor) # 调整图像的饱和度
- random_factor = np.random.rand() * 2 # 随机因子 ok
- # 创建一个增强对象,以调整图像的颜色。增强因子为0.0将产生黑白图像;为1.0将给出原始图像
- brightness_image = ImageEnhance.Brightness(color_image).enhance(random_factor) # 调整图像的亮度
- random_factor = np.random.rand() * 2 # 随机因子 ok
- # 创建一个增强对象,以调整图像的颜色。增强因子为0.0将产生黑白图像;为1.0将给出原始图像
- contrast_image = ImageEnhance.Contrast(brightness_image).enhance(random_factor) # 调整图像对比度
- random_factor = np.random.rand() * 2 # 随机因子 ok #np.random.randint(0, self.randomMax) # 随机因子 ok
- # 创建一个增强对象,以调整图像的颜色。增强因子为0.0将产生黑白图像;为1.0将给出原始图像
- img_output = ImageEnhance.Sharpness(contrast_image).enhance(random_factor) # 调整图像锐度
-
- out_imgs[0] = img_output
-
- return tuple(out_imgs)
- ##############高斯噪声######################333
- class RandomGaussianBlur(object):
- def __init__(self, radius=5):
- self.radius = radius
-
- def __call__(self, imgs):
- pics = []
- if random.random() < 0.5:
- for img in imgs:
-
- # imgUMat = cv2.UMat(img)
- img=img.filter(ImageFilter.GaussianBlur(self.radius))
- # img= cv2.GaussianBlur(img, (self.radius, self.radius), 0)
-
- pics.append(img)
- return tuple(pics)
- else:
- return imgs
- ###########################################
- ###################expand###################20200509addcheng#####
- class Expand(object):
- def __init__(self, max_scale = 2, mean = (0.485, 0.456, 0.406)):
- self.mean = mean
- self.max_scale = max_scale
-
- def __call__(self, imgs):
- #if random.randint(2):
- # return img
- pics = []
- for img in imgs:
-
- h,w = img.size
- ratio = random.uniform(1, self.max_scale)
- y1 = random.uniform(0, h*ratio-h)
- x1 = random.uniform(0, w*ratio-w)
- expand_img = np.zeros(shape=(int(h*ratio), int(w*ratio),c),dtype=img.dtype)
- expand_img[:,:,:] = self.mean
- expand_img[int(y1):int(y1+h), int(x1):int(x1+w)] = img
- img = expand_img
- pics.append(img)
-
-
-
- return tuple(pics)
-
-
-
- class LabelEncoding(object):
- """
- Encoding the label, computes boundary individually
- """
- def __init__(self, radius=1):
- self.radius = radius
-
- def __call__(self, imgs):
- out_imgs = list(imgs)
- label = imgs[-1]
- if not isinstance(label, np.ndarray):
- label = np.array(label)
-
- min_value = 190
- max_value = 210
- half_value = 255 * 0.5 #0
-
-
- # 展示结果
- # new_label5 即为 new_label
- new_label5 = np.zeros((label.shape[0], label.shape[1]), dtype=np.uint8)
- new_label5[label[:, :, 0] > half_value] = 1 # inside
- boun = morphology.dilation(new_label5) & (~morphology.erosion(new_label5, morphology.disk(self.radius)))
- new_label5[boun > 0] = 2 # boundary
-
- label1 = Image.fromarray((new_label5/2*255).astype(np.uint8))
-
- out_imgs[-1] = label1 #label2 #label_final
- #out_imgs[-1].save('imgs2_label_1.png')
- return tuple(out_imgs)
-
-
- selector = {
- 'scale': lambda x: Scale(x),
- 'random_resize': lambda x: RandomResize(x[0], x[1]),
- 'random_color': lambda x: RandomColor(x),
- 'horizontal_flip': lambda x: RandomHorizontalFlip(),
- 'vertical_flip': lambda x: RandomVerticalFlip(),
- 'RandomGaussianBlur':lambda x:RandomGaussianBlur(),
- 'Expand':lambda x:Expand(),
- 'random_affine': lambda x: RandomAffine(x),
- 'random_rotation': lambda x: RandomRotation(x),
- 'random_elastic': lambda x: RandomElasticDeform(x[0], x[1]),
- 'random_crop': lambda x: RandomCrop(x),
- 'label_encoding': lambda x: LabelEncoding(x),
- 'to_tensor': lambda x: ToTensor(x),
- 'normalize': lambda x: Normalize(x[0], x[1])
- }
-
-
- def get_transforms(param_dict):
- """ data transforms for train, validation or test """
- start_time = time.time()
-
- t_list = []
- selectorNameList = []
- selectorName_str = ''
- for k, v in param_dict.items():
- t_list.append(selector[k](v))
- selectorNameList.append(k)
- selectorName_str = selectorName_str + '_' + str(k)
-
- returnValue = Compose(t_list, selectorNameList) #Compose()函数执行各种变换操作 add. selectorNameList
-
- return returnValue
-
|