def _set_cut_mask(self, mask, size): """ Manages the cut mask for the destination image, if present. The mask is turned into a binary image of the same size of the destination image. Args: mask: mask of the destination image size: size of the destination image Returns: mask of the destination image """ if mask is None: self.log.debug('Ymask: None') return # 1 channel if len(mask.shape) == 3: mask = rgb2gray(mask) elif not len(mask.shape) == 2: raise ValueError('Input mask must be 2 or 3 dimensional') # resize it according to size mask = im2double(imresize(mask, size=size)) # binary values mask[mask < 0.01] = 0 mask[mask > 0] = 1 self.log.debug('Ymask: {0}'.format(mask.shape)) return mask
def test_distance_candidates(self): """ Test that all the candidates are good choices. """ # eye matrix img = -np.eye(10) * 2 + 1 img = np.asarray(np.dstack((img, img, img))) # parch is similar to a submatrix of img patch = np.asarray([[1, 1, 1, 1.], [1, 1, 1, 1.], [-1, 1, 0, 0.], [1, -1, 0, 0.]]) patch = im2double(gray2rgb(patch)) # compute distance q = Quilt(img, output_size=[20, 20]) distances = q.distance(patch, tilesize=4, overlap=2, coord=[2, 2]) best = np.min(distances) candidates = np.where(distances <= best) # submatrix of img similar to patch expected = np.asarray([[1, 1, 1, 1], [1, 1, 1, 1], [-1, 1, 1, 1], [1, -1, 1, 1]]) for i in range(len(candidates[0])): sub = [candidates[0][i], candidates[1][i]] result = img[sub[0]:sub[0] + 4, sub[1]:sub[1] + 4, 1] assert_array_equal(expected, result)
def test_distance_submatrix(self): """ Test distance computation. Test a submatrix is found inside a bigger matrix. """ img = np.array([[64, 2, 3, 61, 60, 6, 7, 57], [9, 55, 54, 12, 13, 51, 50, 16], [17, 47, 46, 20, 21, 43, 42, 24], [40, 26, 27, 37, 1, 1, 1, 33], [32, 34, 35, 29, 1, 1, 1, 25], [41, 23, 22, 44, 1, 1, 1, 48], [49, 15, 14, 52, 53, 11, 10, 56]]) img = np.uint8(gray2rgb(img)) patch = np.array([[26, 27, 37], [34, 35, 29], [23, 22, 44]]) patch = im2double(np.uint8(gray2rgb(patch))) q = Quilt(img, output_size=[20, 20]) result = q.distance(patch, tilesize=3, overlap=2, coord=[1, 1]) expected = zeros((5, 6)) self.assertEqual(expected.shape, result.shape) # check where the min is arg_min = np.where(result == np.min(result)) expected = np.asarray([[3], [1]]) assert_array_equal(expected, arg_min)
def test_im2double_uint8(self): """ Test im2double behaviour when given a matrix with uint8 values. """ float_a = np.array([[0, 0.5, 0.8], [1, 0.3, 0.7]]) int_a = np.uint8(float_a * 255) result = im2double(int_a) self.assertEqual('float', result.dtype) # round at the first decimal expected = float_a assert_array_equal(expected, np.around(result, decimals=1))
def test_src_size(self): """ Test the source image is turned to rgb float and is not reshaped. """ q = Quilt(self.x, output_size=self.x.shape) # there is just one image in the stack self.assertEqual(1, len(q.X)) # rgb assert_array_equal((self.x.shape[0], self.x.shape[1], 3), q.X[0].shape) # float self.assertEqual('float', q.X[0].dtype) expected = gray2rgb(im2double(self.x)) assert_array_equal(expected, q.X[0])
def _set_src(self, img, rotate=0, flip=None): """ Manages source image/s: - turns it into a stack of images of the same size - images are set to float values in range [0, 1] - rotated images are added if required Args: img: source image/s: can be a single image or a stack of images of the same size rotate: number of 90 degrees rotations to apply to each image in the stack. flip: list of two booleans for [flip_vertical, flip_horizontal] Returns: - reference image (the first image of the stack with no rotations) - stack of the source images """ # stack of images if not isinstance(img, list): img = [img] reference = None for i in xrange(len(img)): # images in the stack must have the same size if i and not img[i].shape[0:2] == img[0].shape[0:2]: raise ValueError('Chained images must have the same size. Got ' '{0} and {1}'.format(img[i].shape[0:2], img[0].shape[0:2])) # 3 channel images img[i] = gray2rgb(img[i]) # float values in range [0, 1] img[i] = im2double(img[i]) if not i: reference = img[i] # rotation img = [self.create_rotations(i, rotate) for i in img] img = [self.create_flip(i, flip) for i in img] return reference, img
def test_stack(self): """ Test the the source images are edited consistently and a same number of destination images is prepared. """ q = Quilt([self.x, self.x, self.x], output_size=[30, 20]) # there are 3 images in the stacks expected_x = gray2rgb(im2double(self.x)) expected_y = zeros((30, 20, 3)) self.assertEqual(3, len(q.X)) self.assertEqual(3, len(q.Y)) for i in xrange(3): # src assert_array_equal((self.x.shape[0], self.x.shape[1], 3), q.X[i].shape) self.assertEqual('float', q.X[i].dtype) assert_array_equal(expected_x, q.X[i]) # dst assert_array_equal((30, 20, 3), q.Y[i].shape) assert_array_equal(expected_y, q.Y[i])
def _set_src_mask(self, mask, tilesize, reference=None, rotate=0, flip=(0, 0)): """ Manages the mask of the source image (if present): - checks the mask has the same size of the source image - turns it into a 1-channel image with values in {0, infinite} - expands masked areas: for every masked (= white) pixel: all the pixels tilesize-distant from it are masked. In this way all the tiles containing that pixel are removed. This is used in the calculation of the convolution between a patch and the source image. Args: mask: input mask reference: master source image without rotation rotate: number of 90 degrees rotations to be applied to the mask flip: tuple of two flag controlling flip transformations Returns: mask """ # no mask if mask is None: if not rotate and flip == (0, 0): self.log.debug('Xmask: None') return # create the mask to remove the lines between the rotations mask = ones(reference.shape[0:2]) # coherent with reference if reference is None: reference = self.X if not mask.shape[0:2] == reference.shape[0:2]: raise ValueError('X and Xmask cannot have different sizes:' 'X={0} Xmask={1}'.format(reference.shape, mask.shape)) # one channel image mask = rgb2gray(mask) # rotate mask = self.create_rotations(mask, rotate) mask = self.create_flip(mask, flip) # values in {0, inf} mask = im2double(mask) mask[mask < 0.7] = inf mask[mask <= 1] = 0 # expand masked values: # if there is a mask on the input: remove all the points leading to # patches with an overlap on that area. i.e.: mask the area and # anything a tile-size distant on the left or top. # The mask will be summed to the convolution result. Doing so, when # searching the min of the convolution, masked areas will not be # considered since their value is infinite. # change overlap? for i in xrange(mask.shape[0] - self.overlap + 1): for j in xrange(mask.shape[1] - self.overlap + 1): # get the tile starting from the pixel tile = mask[i:i + tilesize, j:j + tilesize] # if it contains a masked value: also the pixel generating the # tile has to be masked if np.any(tile == inf): mask[i, j] = inf self.log.debug('Xmask: {0} values: {1}'.format(mask.shape, np.unique(mask))) return mask