diff --git a/data_2D_manipulation.py b/data_2D_manipulation.py index eb5d791d..3cea4f28 100644 --- a/data_2D_manipulation.py +++ b/data_2D_manipulation.py @@ -10,27 +10,27 @@ from util import load_data_from_dir, foreground_percentage -def load_and_prepare_2D_data(train_path, train_mask_path, test_path, test_mask_path, +def load_and_prepare_2D_data(train_path, train_mask_path, test_path, test_mask_path, image_train_shape, image_test_shape, create_val=True, val_split=0.1, shuffle_val=True, seedValue=42, e_d_data=[], e_d_mask=[], e_d_data_dim=[], - num_crops_per_dataset=0, make_crops=True, crop_shape=None, ov=(0, 0), + num_crops_per_dataset=0, make_crops=True, crop_shape=None, ov=(0, 0), overlap_train=False, check_crop=True, check_crop_path="check_crop"): """Load train, validation and test images from the given paths to create 2D - data. + data. Parameters - ---------- + ---------- train_path : str - Path to the training data. + Path to the training data. train_mask_path : str - Path to the training data masks. + Path to the training data masks. test_path : str - Path to the test data. + Path to the test data. test_mask_path : str - Path to the test data masks. + Path to the test data masks. image_train_shape : 3D tuple Dimensions of the images to load. E.g. ``(x, y, channels)``. @@ -39,7 +39,7 @@ def load_and_prepare_2D_data(train_path, train_mask_path, test_path, test_mask_p Dimensions of the images to load. E.g. ``(x, y, channels)``. create_val : bool, optional - If true validation data is created (from the train data). + If true validation data is created (from the train data). val_split : float, optional % of the train data used as validation (value between ``0`` and ``1``). @@ -52,22 +52,22 @@ def load_and_prepare_2D_data(train_path, train_mask_path, test_path, test_mask_p e_d_data : list of str, optional List of paths where the extra data of other datasets are stored. If - ``make_crops`` is not enabled, these extra datasets must have the - same image shape as the main dataset since they are going to be + ``make_crops`` is not enabled, these extra datasets must have the + same image shape as the main dataset since they are going to be stacked in a unique array. e_d_mask : list of str, optional - List of paths where the extra data mask of other datasets are stored. - Same constraints as ``e_d_data``. + List of paths where the extra data mask of other datasets are stored. + Same constraints as ``e_d_data``. e_d_data_dim : list of 3D int tuple, optional - List of shapes of the extra datasets provided. Same constraints as + List of shapes of the extra datasets provided. Same constraints as ``e_d_data``. num_crops_per_dataset : int, optional - Number of crops per extra dataset to take into account. Useful to - ensure that all the datasets have the same weight during network - trainning. + Number of crops per extra dataset to take into account. Useful to + ensure that all the datasets have the same weight during network + trainning. make_crops : bool, optional To make crops on data. @@ -75,13 +75,13 @@ def load_and_prepare_2D_data(train_path, train_mask_path, test_path, test_mask_p crop_shape : 3D int tuple, optional Shape of the crops. E.g. ``(x, y, channels)``. - ov : 2 floats tuple, optional + ov : 2 floats tuple, optional Amount of minimum overlap on x and y dimensions. The values must be on - range ``[0, 1)``, that is, ``0%`` or ``99%`` of overlap. E. g. ``(x, y)``. + range ``[0, 1)``, that is, ``0%`` or ``99%`` of overlap. E. g. ``(x, y)``. overlap_train : bool, optional - If ``True`` ``ov`` will be used to crop training data. ``False`` to - force minimum overap instead: ``ov=(0,0)``. + If ``True`` ``ov`` will be used to crop training data. ``False`` to + force minimum overap instead: ``ov=(0,0)``. check_crop : bool, optional To save the crops made to ensure they are generating as one wish. @@ -90,19 +90,19 @@ def load_and_prepare_2D_data(train_path, train_mask_path, test_path, test_mask_p Path to save the crop samples. Returns - ------- + ------- X_train : 4D Numpy array Train images. E.g. ``(num_of_images, y, x, channels)``. - + Y_train : 4D Numpy array Train images' mask. E.g. ``(num_of_images, y, x, channels)``. X_val : 4D Numpy array, optional - Validation images (``create_val==True``). E.g. ``(num_of_images, + Validation images (``create_val==True``). E.g. ``(num_of_images, y, x, channels)``. Y_val : 4D Numpy array, optional - Validation images' mask (``create_val==True``). E.g. + Validation images' mask (``create_val==True``). E.g. ``(num_of_images, y, x, channels)``. X_test : 4D Numpy array @@ -123,7 +123,7 @@ def load_and_prepare_2D_data(train_path, train_mask_path, test_path, test_mask_p Examples -------- :: - + # EXAMPLE 1 # Case where we need to load the data (creating a validation split) train_path = "data/train/x" @@ -140,22 +140,22 @@ def load_and_prepare_2D_data(train_path, train_mask_path, test_path, test_mask_p orig_test_shape, norm_value, crops_made = load_and_prepare_2D_data( train_path, train_mask_path, test_path, test_mask_path, img_train_shape, img_test_shape, val_split=0.1, shuffle_val=True, make_crops=False) - + # The function will print the shapes of the generated arrays. In this example: # *** Loaded train data shape is: (148, 768, 1024, 1) # *** Loaded validation data shape is: (17, 768, 1024, 1) # *** Loaded test data shape is: (165, 768, 1024, 1) # - # Notice height and width swap because of Numpy ndarray terminology - + # Notice height and width swap because of Numpy ndarray terminology - # EXAMPLE 2 + + # EXAMPLE 2 # Same as the first example but creating patches of (256x256) X_train, Y_train, X_val, Y_val, X_test, Y_test, orig_test_shape, norm_value, crops_made = load_and_prepare_2D_data( - train_path, train_mask_path, test_path, test_mask_path, img_train_shape, + train_path, train_mask_path, test_path, test_mask_path, img_train_shape, img_test_shape, val_split=0.1, shuffle_val=True, make_crops=True, crop_shape=(256, 256, 1), check_crop=True, check_crop_path="check_folder") @@ -166,25 +166,25 @@ def load_and_prepare_2D_data(train_path, train_mask_path, test_path, test_mask_p # EXAMPLE 3 - # Same as the first example but definig extra datasets to be loaded and stacked together - # with the main dataset. Extra variables to be defined: + # Same as the first example but definig extra datasets to be loaded and stacked together + # with the main dataset. Extra variables to be defined: extra_datasets_data_list.append('/data2/train/x') extra_datasets_mask_list.append('/data2/train/y') extra_datasets_data_dim_list.append((877, 967, 1)) - X_train, Y_train, X_val, - Y_val, X_test, Y_test, - orig_test_shape, norm_value, crops_made = load_and_prepare_2D_data( - train_path, train_mask_path, test_path, test_mask_path, img_train_shape, + X_train, Y_train, X_val, + Y_val, X_test, Y_test, + orig_test_shape, norm_value, crops_made = load_and_prepare_2D_data( + train_path, train_mask_path, test_path, test_mask_path, img_train_shape, mg_test_shape, val_split=0.1, shuffle_val=True, make_crops=True, crop_shape=(256, 256, 1), check_crop=True, check_crop_path="check_folder" - e_d_data=extra_datasets_data_list, e_d_mask=extra_datasets_mask_list, + e_d_data=extra_datasets_data_list, e_d_mask=extra_datasets_mask_list, e_d_data_dim=extra_datasets_data_dim_list) - - """ - + + """ + print("### LOAD ###") - + tr_shape = (image_train_shape[1], image_train_shape[0], image_train_shape[2]) print("0) Loading train images . . .") X_train = load_data_from_dir(train_path, tr_shape) @@ -208,7 +208,7 @@ def load_and_prepare_2D_data(train_path, train_mask_path, test_path, test_mask_p X_train, Y_train, test_size=val_split, shuffle=shuffle_val, random_state=seedValue) - # Save original data shape + # Save original data shape orig_train_shape = Y_train.shape orig_test_shape = Y_test.shape @@ -223,7 +223,7 @@ def load_and_prepare_2D_data(train_path, train_mask_path, test_path, test_mask_p print("4.2) Cropping test data . . .") X_test, Y_test = crop_data_with_overlap( X_test, crop_shape, data_mask=Y_test, overlap=ov) - + if create_val: print("4.3) Cropping validation data . . .") X_val, Y_val = crop_data_with_overlap( @@ -236,7 +236,7 @@ def load_and_prepare_2D_data(train_path, train_mask_path, test_path, test_mask_p check_crops(Y_train, orig_train_shape, t_ov, num_examples=3, out_dir=check_crop_path, prefix="Y_train_") - + crop_made = True else: crop_made = False @@ -245,14 +245,14 @@ def load_and_prepare_2D_data(train_path, train_mask_path, test_path, test_mask_p if e_d_data: print("5) Loading extra datasets . . .") for i in range(len(e_d_data)): - print("5.{}) extra dataset in {} . . .".format(i, e_d_data[i])) + print("5.{}) extra dataset in {} . . .".format(i, e_d_data[i])) train_ids = sorted(next(os.walk(e_d_data[i]))[2]) train_mask_ids = sorted(next(os.walk(e_d_mask[i]))[2]) d_dim = e_d_data_dim[i] e_X_train = np.zeros((len(train_ids), d_dim[1], d_dim[0], d_dim[2]), dtype=np.float32) - e_Y_train = np.zeros((len(train_mask_ids), d_dim[1], d_dim[0], + e_Y_train = np.zeros((len(train_mask_ids), d_dim[1], d_dim[0], d_dim[2]), dtype=np.float32) print("5.{}) Loading data of the extra dataset . . .".format(i)) @@ -280,7 +280,7 @@ def load_and_prepare_2D_data(train_path, train_mask_path, test_path, test_mask_p print("5.{}) Cropping the extra dataset . . .".format(i)) e_X_train, e_Y_train, _ = crop_data( e_X_train, crop_shape, data_mask=e_Y_train) - + if num_crops_per_dataset != 0: e_X_train = e_X_train[:num_crops_per_dataset] e_Y_train = e_Y_train[:num_crops_per_dataset] @@ -314,8 +314,8 @@ def load_and_prepare_2D_data(train_path, train_mask_path, test_path, test_mask_p def crop_data_with_overlap(data, crop_shape, data_mask=None, overlap=(0,0)): """Crop data into small square pieces with overlap. The difference with - :func:`~crop_data` is that this function allows you to create patches with - overlap. + :func:`~crop_data` is that this function allows you to create patches with + overlap. The opposite function is :func:`~merge_data_with_overlap`. @@ -323,13 +323,13 @@ def crop_data_with_overlap(data, crop_shape, data_mask=None, overlap=(0,0)): ---------- data : 4D Numpy array Data to crop. E.g. ``(num_of_images, x, y, channels)``. - + crop_shape : 3 int tuple Shape of the crops to create. E.g. ``(x, y, channels)``. - + data_mask : 4D Numpy array, optional Data mask to crop. E.g. ``(num_of_images, x, y, channels)``. - + overlap : Tuple of 2 floats, optional Amount of minimum overlap on x and y dimensions. The values must be on range ``[0, 1)``, that is, ``0%`` or ``99%`` of overlap. E. g. ``(x, y)``. @@ -347,19 +347,19 @@ def crop_data_with_overlap(data, crop_shape, data_mask=None, overlap=(0,0)): :: # EXAMPLE 1 - # Divide in crops of (256, 256) a given data with the minimum overlap + # Divide in crops of (256, 256) a given data with the minimum overlap X_train = np.ones((165, 768, 1024, 1)) Y_train = np.ones((165, 768, 1024, 1)) X_train, Y_train = crop_data_with_overlap(X_train, (256, 256, 1), Y_train, (0, 0)) - # Notice that as the shape of the data has exact division with the wnanted - # crops shape so no overlap will be made. The function will print the following + # Notice that as the shape of the data has exact division with the wnanted + # crops shape so no overlap will be made. The function will print the following # information: # Minimum overlap selected: (0, 0) # Real overlapping (%): (0.0, 0.0) # Real overlapping (pixels): (0.0, 0.0) - # (3, 4) patches per (x,y) axis + # (3, 4) patches per (x,y) axis # **** New data shape is: (1980, 256, 256, 1) @@ -386,7 +386,7 @@ def crop_data_with_overlap(data, crop_shape, data_mask=None, overlap=(0,0)): # (6, 8) patches per (x,y) axis # **** New data shape is: (7920, 256, 256, 1) - + # EXAMPLE 4 # Same as example 2 but with 50% of overlap only in x axis X_train, Y_train = crop_data_with_overlap(X_train, (256, 256, 1), Y_train, (0.5, 0)) @@ -408,8 +408,8 @@ def crop_data_with_overlap(data, crop_shape, data_mask=None, overlap=(0,0)): raise ValueError("'overlap' values must be floats between range [0, 1)") # Calculate overlapping variables - overlap_x = 1 if overlap[0] == 0 else 1-overlap[0] - overlap_y = 1 if overlap[1] == 0 else 1-overlap[1] + overlap_x = 1 if overlap[0] == 0 else 1-overlap[0] + overlap_y = 1 if overlap[1] == 0 else 1-overlap[1] # X crops_per_x = math.ceil(data.shape[1]/(crop_shape[0]*overlap_x)) @@ -425,7 +425,7 @@ def crop_data_with_overlap(data, crop_shape, data_mask=None, overlap=(0,0)): step_y = int(crop_shape[1]*overlap_y)-ex last_y = 0 if crops_per_y == 1 else excess_y%(crops_per_y-1) - # Real overlap calculation for printing + # Real overlap calculation for printing real_ov_x = (crop_shape[0]-step_x)/crop_shape[0] real_ov_y = (crop_shape[1]-step_y)/crop_shape[1] print("Real overlapping (%): {}".format((real_ov_x,real_ov_y))) @@ -454,7 +454,7 @@ def crop_data_with_overlap(data, crop_shape, data_mask=None, overlap=(0,0)): if data_mask is not None: cropped_data_mask[c] = \ data_mask[z, - x*step_x-d_x:x*step_x+crop_shape[0]-d_x, + x*step_x-d_x:x*step_x+crop_shape[0]-d_x, y*step_y-d_y:y*step_y+crop_shape[1]-d_y] c += 1 @@ -470,7 +470,7 @@ def crop_data_with_overlap(data, crop_shape, data_mask=None, overlap=(0,0)): def merge_data_with_overlap(data, original_shape, data_mask=None, overlap=(0,0), out_dir=None, prefix=""): """Merge data with an amount of overlap. - + The opposite function is :func:`~crop_data_with_overlap`. Parameters @@ -484,25 +484,25 @@ def merge_data_with_overlap(data, original_shape, data_mask=None, overlap=(0,0), data_mask : 4D Numpy array, optional Data mask to merge. E.g. ``(num_of_images, x, y, channels)``. - overlap : Tuple of 2 floats, optional + overlap : Tuple of 2 floats, optional Amount of minimum overlap on x, y and z dimensions. Should be the same as used in :func:`~crop_data_with_overlap`. The values must be on range - ``[0, 1)``, that is, ``0%`` or ``99%`` of overlap. E. g. ``(x, y)``. + ``[0, 1)``, that is, ``0%`` or ``99%`` of overlap. E. g. ``(x, y)``. out_dir : str, optional - If provided an image that represents the overlap made will be saved. - The image will be colored as follows: green region when ``==2`` crops + If provided an image that represents the overlap made will be saved. + The image will be colored as follows: green region when ``==2`` crops overlap, yellow when ``2 < x < 6`` and red when ``=<6`` or more crops are merged. prefix : str, optional - Prefix to save overlap map with. + Prefix to save overlap map with. Returns ------- merged_data : 4D Numpy array Merged image data. E.g. ``(num_of_images, x, y, channels)``. - + merged_data_mask : 4D Numpy array, optional Merged image data mask. E.g. ``(num_of_images, x, y, channels)``. @@ -554,8 +554,8 @@ def merge_data_with_overlap(data, original_shape, data_mask=None, overlap=(0,0), # Real overlapping (pixels): (153.0, 146.0) # (6, 8) patches per (x,y) axis # **** New data shape is: (165, 768, 1024, 1) - - + + # EXAMPLE 4 # Merge the data of example 1 of 'crop_data_with_overlap' function X_train, Y_train = merge_data_with_overlap( @@ -569,7 +569,7 @@ def merge_data_with_overlap(data, original_shape, data_mask=None, overlap=(0,0), # **** New data shape is: (165, 768, 1024, 1) - As example of different overlap maps are presented below. + As example of different overlap maps are presented below. +---------------------------------------+-------------------------------------------+ | .. figure:: img/merged_ov_map_0.png | .. figure:: img/merged_ov_map_0.25.png | @@ -601,9 +601,9 @@ def merge_data_with_overlap(data, original_shape, data_mask=None, overlap=(0,0), if out_dir is not None: crop_grid = np.zeros(original_shape[1:], dtype=np.int32) - # Calculate overlapping variables - overlap_x = 1 if overlap[0] == 0 else 1-overlap[0] - overlap_y = 1 if overlap[1] == 0 else 1-overlap[1] + # Calculate overlapping variables + overlap_x = 1 if overlap[0] == 0 else 1-overlap[0] + overlap_y = 1 if overlap[1] == 0 else 1-overlap[1] # X crops_per_x = math.ceil(original_shape[1]/(data.shape[1]*overlap_x)) @@ -611,7 +611,7 @@ def merge_data_with_overlap(data, original_shape, data_mask=None, overlap=(0,0), ex = 0 if crops_per_x == 1 else int(excess_x/(crops_per_x-1)) step_x = int(data.shape[1]*overlap_x)-ex last_x = 0 if crops_per_x == 1 else excess_x%(crops_per_x-1) - + # Y crops_per_y = math.ceil(original_shape[2]/(data.shape[2]*overlap_y)) excess_y = int((crops_per_y*data.shape[2])-((crops_per_y-1)*overlap[1]*data.shape[2]))-original_shape[2] @@ -619,10 +619,10 @@ def merge_data_with_overlap(data, original_shape, data_mask=None, overlap=(0,0), step_y = int(data.shape[2]*overlap_y)-ex last_y = 0 if crops_per_y == 1 else excess_y%(crops_per_y-1) - # Real overlap calculation for printing - real_ov_x = (data.shape[1]-step_x)/data.shape[1] - real_ov_y = (data.shape[2]-step_y)/data.shape[2] - print("Real overlapping (%): {}".format((real_ov_x,real_ov_y))) + # Real overlap calculation for printing + real_ov_x = (data.shape[1]-step_x)/data.shape[1] + real_ov_y = (data.shape[2]-step_y)/data.shape[2] + print("Real overlapping (%): {}".format((real_ov_x,real_ov_y))) print("Real overlapping (pixels): {}".format((data.shape[1]*real_ov_x, data.shape[2]*real_ov_y))) @@ -631,15 +631,15 @@ def merge_data_with_overlap(data, original_shape, data_mask=None, overlap=(0,0), c = 0 for z in range(original_shape[0]): for x in range(crops_per_x): - for y in range(crops_per_y): + for y in range(crops_per_y): d_x = 0 if (x*step_x+data.shape[1]) < original_shape[1] else last_x d_y = 0 if (y*step_y+data.shape[2]) < original_shape[2] else last_y merged_data[z, - x*step_x-d_x:x*step_x+data.shape[1]-d_x, + x*step_x-d_x:x*step_x+data.shape[1]-d_x, y*step_y-d_y:y*step_y+data.shape[2]-d_y] += data[c] - - if data_mask is not None: + + if data_mask is not None: merged_data_mask[z, x*step_x-d_x:x*step_x+data.shape[1]-d_x, y*step_y-d_y:y*step_y+data.shape[2]-d_y] += data_mask[c] @@ -647,7 +647,7 @@ def merge_data_with_overlap(data, original_shape, data_mask=None, overlap=(0,0), ov_map_counter[z, x*step_x-d_x:x*step_x+data.shape[1]-d_x, y*step_y-d_y:y*step_y+data.shape[2]-d_y] += 1 - + if z == 0 and out_dir is not None: crop_grid[x*step_x-d_x, y*step_y-d_y:y*step_y+data.shape[2]-d_y] = 1 @@ -659,19 +659,19 @@ def merge_data_with_overlap(data, original_shape, data_mask=None, overlap=(0,0), y*step_y+data.shape[2]-d_y-1] = 1 c += 1 - + merged_data = np.true_divide(merged_data, ov_map_counter) if data_mask is not None: merged_data_mask = np.true_divide(merged_data_mask, ov_map_counter) - # Save a copy of the merged data with the overlapped regions colored as: - # green when 2 crops overlap, yellow when (2 < x < 6) and red when more than - # 6 overlaps are merged + # Save a copy of the merged data with the overlapped regions colored as: + # green when 2 crops overlap, yellow when (2 < x < 6) and red when more than + # 6 overlaps are merged if out_dir is not None: os.makedirs(out_dir, exist_ok=True) ov_map = ov_map_counter[0] - ov_map = ov_map.astype('int32') + ov_map = ov_map.astype('int32') ov_map[np.where(ov_map_counter[0] >= 2)] = -3 ov_map[np.where(ov_map_counter[0] >= 3)] = -2 @@ -683,13 +683,13 @@ def merge_data_with_overlap(data, original_shape, data_mask=None, overlap=(0,0), im = im.convert('RGBA') px = im.load() width, height = im.size - for im_i in range(width): + for im_i in range(width): for im_j in range(height): # White borders - if ov_map[im_j, im_i] == -4: + if ov_map[im_j, im_i] == -4: px[im_i, im_j] = (255, 255, 255, 255) # Overlap zone - elif ov_map[im_j, im_i] == -3: + elif ov_map[im_j, im_i] == -3: px[im_i, im_j] = tuple(map(sum, zip((0, 74, 0, 125), px[im_i, im_j]))) # 2 < x < 6 overlaps elif ov_map[im_j, im_i] == -2: @@ -699,11 +699,11 @@ def merge_data_with_overlap(data, original_shape, data_mask=None, overlap=(0,0), px[im_i, im_j] = tuple(map(sum, zip((74, 0, 0, 125), px[im_i, im_j]))) im.save(os.path.join(out_dir, prefix + "merged_ov_map.png")) - + print("**** New data shape is: {}".format(merged_data.shape)) print("### END MERGE-OV-CROP ###") - if data_mask is not None: + if data_mask is not None: return merged_data, merged_data_mask else: return merged_data @@ -711,9 +711,9 @@ def merge_data_with_overlap(data, original_shape, data_mask=None, overlap=(0,0), def check_crops(data, original_shape, ov, num_examples=1, include_crops=True, out_dir="check_crops", prefix=""): - """Check cropped images by the function :func:`~crop_data` and - :func:`~crop_data_with_overlap`. - + """Check cropped images by the function :func:`~crop_data` and + :func:`~crop_data_with_overlap`. + Parameters ---------- data : 4D Numpy array @@ -722,15 +722,15 @@ def check_crops(data, original_shape, ov, num_examples=1, include_crops=True, original_shape : Tuple of 4 ints Shape of the original data. E.g. ``(num_of_images, x, y, channels)``. - ov : Tuple of 2 floats, optional + ov : Tuple of 2 floats, optional Amount of minimum overlap on x and y dimensions. The values must be on - range ``[0, 1)``, that is, ``0%`` or ``99%`` of overlap. E. g. ``(x, y)``. + range ``[0, 1)``, that is, ``0%`` or ``99%`` of overlap. E. g. ``(x, y)``. num_examples : int, optional Number of examples to create. include_crops : bool, optional - To save cropped images or only the image to contruct. + To save cropped images or only the image to contruct. out_dir : str, optional Directory where the images will be save. @@ -752,7 +752,7 @@ def check_crops(data, original_shape, ov, num_examples=1, include_crops=True, check_crops(X_train, original_shape, num_examples=1, out_dir='out') - The example above will store 12 individual crops (4x3, height x width), and + The example above will store 12 individual crops (4x3, height x width), and two images of the original shape: data image and its mask. For instance: +-----------------------------------------------+----------------------------------------------+ @@ -763,7 +763,7 @@ def check_crops(data, original_shape, ov, num_examples=1, include_crops=True, | Original image (the grid should be each crop) | Original mask (the grid should be each crop) | +-----------------------------------------------+----------------------------------------------+ """ - + print("### CHECK-CROPS ###") os.makedirs(out_dir, exist_ok=True) @@ -799,30 +799,30 @@ def check_crops(data, original_shape, ov, num_examples=1, include_crops=True, print("### END CHECK-CROP ###") -def random_crop(image, mask, random_crop_size, val=False, - draw_prob_map_points=False, img_prob=None, weight_map=None): - """Random crop. - +def random_crop(image, mask, random_crop_size, val=False, + draw_prob_map_points=False, img_prob=None, weight_map=None): + """Random crop. + Parameters ---------- - image : Numpy 3D array + image : Numpy 3D array Image. E.g. ``(x, y, channels)``. - mask : Numpy 3D array - Image mask. E.g. ``(x, y, channels)``. - + mask : Numpy 3D array + Image mask. E.g. ``(x, y, channels)``. + random_crop_size : 2 int tuple Size of the crop. E.g. ``(height, width)``. - + val : bool, optional - If the image provided is going to be used in the validation data. + If the image provided is going to be used in the validation data. This forces to crop from the origin, e. g. ``(0, 0)`` point. - + draw_prob_map_points : bool - To return the pixel choosen to be the center of the crop. + To return the pixel choosen to be the center of the crop. img_prob : Numpy 3D array, optional - Probability of each pixel to be choosen as the center of the crop. + Probability of each pixel to be choosen as the center of the crop. E. .g. ``(x, y, channels)``. weight_map : bool, optional @@ -835,90 +835,90 @@ def random_crop(image, mask, random_crop_size, val=False, weight_map : 2D Numpy array, optional Crop of the given image's weigth map. E. g. ``(height, width)``. - + ox : int, optional - X coordinate in the complete image of the choosed central pixel to + X coordinate in the complete image of the choosed central pixel to make the crop. oy : int, optional - Y coordinate in the complete image of the choosed central pixel to + Y coordinate in the complete image of the choosed central pixel to make the crop. x : int, optional - X coordinate in the complete image where the crop starts. + X coordinate in the complete image where the crop starts. y : int, optional Y coordinate in the complete image where the crop starts. - """ - + """ + if weight_map is not None: - img, we = image - else: - img = image - - height, width = img.shape[0], img.shape[1] - dy, dx = random_crop_size - if val == True: - x = 0 - y = 0 - ox = 0 - oy = 0 - else: - if img_prob is not None: - prob = img_prob.ravel() - - # Generate the random coordinates based on the distribution - choices = np.prod(img_prob.shape) - index = np.random.choice(choices, size=1, p=prob) - coordinates = np.unravel_index(index, dims=img_prob.shape) - x = int(coordinates[1][0]) - y = int(coordinates[0][0]) - ox = int(coordinates[1][0]) - oy = int(coordinates[0][0]) - + img, we = image + else: + img = image + + height, width = img.shape[0], img.shape[1] + dy, dx = random_crop_size + if val == True: + x = 0 + y = 0 + ox = 0 + oy = 0 + else: + if img_prob is not None: + prob = img_prob.ravel() + + # Generate the random coordinates based on the distribution + choices = np.prod(img_prob.shape) + index = np.random.choice(choices, size=1, p=prob) + coordinates = np.unravel_index(index, dims=img_prob.shape) + x = int(coordinates[1][0]) + y = int(coordinates[0][0]) + ox = int(coordinates[1][0]) + oy = int(coordinates[0][0]) + # Adjust the coordinates to be the origin of the crop and control to - # not be out of the image - if y < int(random_crop_size[0]/2): - y = 0 - elif y > img.shape[0] - int(random_crop_size[0]/2): - y = img.shape[0] - random_crop_size[0] - else: - y -= int(random_crop_size[0]/2) - - if x < int(random_crop_size[1]/2): - x = 0 - elif x > img.shape[1] - int(random_crop_size[1]/2): - x = img.shape[1] - random_crop_size[1] - else: - x -= int(random_crop_size[1]/2) - else: - ox = 0 - oy = 0 - x = np.random.randint(0, width - dx + 1) - y = np.random.randint(0, height - dy + 1) - - if draw_prob_map_points == True: + # not be out of the image + if y < int(random_crop_size[0]/2): + y = 0 + elif y > img.shape[0] - int(random_crop_size[0]/2): + y = img.shape[0] - random_crop_size[0] + else: + y -= int(random_crop_size[0]/2) + + if x < int(random_crop_size[1]/2): + x = 0 + elif x > img.shape[1] - int(random_crop_size[1]/2): + x = img.shape[1] - random_crop_size[1] + else: + x -= int(random_crop_size[1]/2) + else: + ox = 0 + oy = 0 + x = np.random.randint(0, width - dx + 1) + y = np.random.randint(0, height - dy + 1) + + if draw_prob_map_points == True: return img[y:(y+dy), x:(x+dx), :], mask[y:(y+dy), x:(x+dx), :], ox, oy, x, y - else: + else: if weight_map is not None: return img[y:(y+dy), x:(x+dx), :], mask[y:(y+dy), x:(x+dx), :],\ - weight_map[y:(y+dy), x:(x+dx), :] - else: - return img[y:(y+dy), x:(x+dx), :], mask[y:(y+dy), x:(x+dx), :] - - -def crop_data(data, crop_shape, data_mask=None, force_shape=(0, 0), - d_percentage=0): - """Crop data into smaller pieces of ``crop_shape``. If there is no exact + weight_map[y:(y+dy), x:(x+dx), :] + else: + return img[y:(y+dy), x:(x+dx), :], mask[y:(y+dy), x:(x+dx), :] + + +def crop_data(data, crop_shape, data_mask=None, force_shape=(0, 0), + d_percentage=0): + """Crop data into smaller pieces of ``crop_shape``. If there is no exact division between the data shape and ``crop_shape`` in a specific dimension - zeros will be added. - + zeros will be added. + DEFERRED: use :func:`~crop_data_with_overlap` instead. The opposite function is :func:`~merge_data`. Parameters - ---------- + ---------- data : 4D Numpy array Data to crop. E.g. ``(num_of_images, x, y, channels)``. @@ -929,38 +929,38 @@ def crop_data(data, crop_shape, data_mask=None, force_shape=(0, 0), Data masks to crop. E.g. ``(num_of_images, x, y, channels)``. force_shape : 2D int tuple, optional - Force number of horizontal and vertical crops to the given numbers. - E. g. ``(4, 5)`` should create ``20`` crops: ``4`` rows and ``5`` crop + Force number of horizontal and vertical crops to the given numbers. + E. g. ``(4, 5)`` should create ``20`` crops: ``4`` rows and ``5`` crop per each row. d_percentage : int, optional - Number between ``0`` and ``100``. The images that have less foreground - pixels than the given number will be discarded. Only available if + Number between ``0`` and ``100``. The images that have less foreground + pixels than the given number will be discarded. Only available if ``data_mask`` is provided. - + Returns - ------- - cropped_data : 4D Numpy array. + ------- + cropped_data : 4D Numpy array. Cropped data images. E.g. ``(num_of_images, x, y, channels)``. cropped_data_mask : 4D Numpy array Cropped data masks. E.g. ``(num_of_images, x, y, channels)``. force_shape : 2D int tuple - Number of horizontal and vertical crops made. Useful for future - crop/merge calls. - + Number of horizontal and vertical crops made. Useful for future + crop/merge calls. + Examples -------- :: # EXAMPLE 1 - # Divide in (256, 256, 1) crops a given data - X_train = np.ones((165, 768, 1024, 1)) - Y_train = np.ones((165, 768, 1024, 1)) - + # Divide in (256, 256, 1) crops a given data + X_train = np.ones((165, 768, 1024, 1)) + Y_train = np.ones((165, 768, 1024, 1)) + X_train, Y_train, _ = crop_data(X_train, (256, 256, 1), data_mask=Y_train) - + # The function will print the shape of the created array. In this example: # **** New data shape is: (1980, 256, 256, 1) @@ -976,15 +976,15 @@ def crop_data(data, crop_shape, data_mask=None, force_shape=(0, 0), # **** New data shape is: (1980, 256, 256, 1) # Here some of the crops, concretelly the last in height and width, will # have a black part (which are zeros) - + See :func:`~check_crops` function for a visual example. - """ + """ - print("### CROP ###") + print("### CROP ###") print("Cropping [{},{}] images into {} . . .".format(data.shape[1], \ - data.shape[2], crop_shape)) - - # Calculate the number of images to be generated + data.shape[2], crop_shape)) + + # Calculate the number of images to be generated if force_shape == (0, 0): h_num = math.ceil(data.shape[1] / crop_shape[0]) v_num = math.ceil(data.shape[2] / crop_shape[1]) @@ -994,15 +994,15 @@ def crop_data(data, crop_shape, data_mask=None, force_shape=(0, 0), v_num = force_shape[1] print("Force crops to [{}, {}]".format(h_num, v_num)) - total_cropped = data.shape[0]*h_num*v_num + total_cropped = data.shape[0]*h_num*v_num # Resize data to adjust to a value divisible by height and width - r_data = np.zeros((data.shape[0], h_num*crop_shape[1], v_num*crop_shape[0], - data.shape[3]), dtype=np.float32) + r_data = np.zeros((data.shape[0], h_num*crop_shape[1], v_num*crop_shape[0], + data.shape[3]), dtype=np.float32) r_data[:data.shape[0],:data.shape[1],:data.shape[2],:data.shape[3]] = data if data_mask is not None: - r_data_mask = np.zeros((data_mask.shape[0], h_num*crop_shape[1], - v_num*crop_shape[0], data_mask.shape[3]), + r_data_mask = np.zeros((data_mask.shape[0], h_num*crop_shape[1], + v_num*crop_shape[0], data_mask.shape[3]), dtype=np.float32) r_data_mask[:data_mask.shape[0],:data_mask.shape[1], :data_mask.shape[2],:data_mask.shape[3]] = data_mask @@ -1010,47 +1010,47 @@ def crop_data(data, crop_shape, data_mask=None, force_shape=(0, 0), print("Resized data from {} to {} to be divisible by the shape provided"\ .format(data.shape, r_data.shape)) - discarded = 0 + discarded = 0 cont = 0 selected_images = [] # Discard images from the data set if d_percentage > 0 and data_mask is not None: print("0) Selecting images to discard . . .") - for img_num in tqdm(range(0, r_data.shape[0])): - for i in range(0, h_num): + for img_num in tqdm(range(0, r_data.shape[0])): + for i in range(0, h_num): for j in range(0, v_num): p = foreground_percentage(r_data_mask[ img_num, (i*crop_shape[0]):((i+1)*crop_shape[1]), (j*crop_shape[0]):((j+1)*crop_shape[1])], 255) - if p > d_percentage: + if p > d_percentage: selected_images.append(cont) else: discarded = discarded + 1 cont = cont + 1 - # Crop data - cropped_data = np.zeros(((total_cropped-discarded), crop_shape[1], + # Crop data + cropped_data = np.zeros(((total_cropped-discarded), crop_shape[1], crop_shape[0], r_data.shape[3]), dtype=np.float32) if data_mask is not None: - cropped_data_mask = np.zeros(((total_cropped-discarded), crop_shape[1], - crop_shape[0], r_data_mask.shape[3]), + cropped_data_mask = np.zeros(((total_cropped-discarded), crop_shape[1], + crop_shape[0], r_data_mask.shape[3]), dtype=np.float32) - - cont = 0 + + cont = 0 l_i = 0 print("1) Cropping images . . .") - for img_num in tqdm(range(0, r_data.shape[0])): - for i in range(0, h_num): - for j in range(0, v_num): + for img_num in tqdm(range(0, r_data.shape[0])): + for i in range(0, h_num): + for j in range(0, v_num): if d_percentage > 0 and data_mask is not None \ and len(selected_images) != 0: if selected_images[l_i] == cont \ or l_i == len(selected_images) - 1: cropped_data[l_i] = r_data[ - img_num, (i*crop_shape[0]):((i+1)*crop_shape[1]), + img_num, (i*crop_shape[0]):((i+1)*crop_shape[1]), (j*crop_shape[0]):((j+1)*crop_shape[1]),:] cropped_data_mask[l_i] = r_data_mask[ @@ -1059,21 +1059,21 @@ def crop_data(data, crop_shape, data_mask=None, force_shape=(0, 0), if l_i != len(selected_images) - 1: l_i = l_i + 1 - else: - + else: + cropped_data[cont] = r_data[ - img_num, (i*crop_shape[0]):((i+1)*crop_shape[1]), + img_num, (i*crop_shape[0]):((i+1)*crop_shape[1]), (j*crop_shape[0]):((j+1)*crop_shape[1]),:] - + if data_mask is not None: cropped_data_mask[cont] = r_data_mask[ img_num, (i*crop_shape[0]):((i+1)*crop_shape[1]), (j*crop_shape[0]):((j+1)*crop_shape[1]),:] - cont = cont + 1 - + cont = cont + 1 + if d_percentage > 0 and data_mask is not None: print("**** {} images discarded. New shape after cropping and discarding " - "is {}".format(discarded, cropped_data.shape)) + "is {}".format(discarded, cropped_data.shape)) print("### END CROP ###") else: print("**** New data shape is: {}".format(cropped_data.shape)) @@ -1086,13 +1086,13 @@ def crop_data(data, crop_shape, data_mask=None, force_shape=(0, 0), def merge_data(data, num, out_shape=(1, 1), grid=False): - """Combine images from input data into a bigger one given shape. + """Combine images from input data into a bigger one given shape. The opposite function of :func:`~crop_data`. - + DEFERRED: use :func:`~merge_data_with_overlap` instead. Parameters - ---------- + ---------- data : 4D Numpy array Data to crop. E.g. ``(num_of_images, x, y, channels)``. @@ -1106,7 +1106,7 @@ def merge_data(data, num, out_shape=(1, 1), grid=False): Make the grid in the output image. Returns - ------- + ------- mixed_data : 4D Numpy array Mixed data images. E.g. ``(num_of_images, x, y, channels)``. @@ -1118,20 +1118,20 @@ def merge_data(data, num, out_shape=(1, 1), grid=False): :: # EXAMPLE 1 - # As the first example introduced in crop_data function, the merge after + # As the first example introduced in crop_data function, the merge after # the crop should be done as follows: - X_train = np.ones((165, 768, 1024, 1)) - Y_train = np.ones((165, 768, 1024, 1)) - + X_train = np.ones((165, 768, 1024, 1)) + Y_train = np.ones((165, 768, 1024, 1)) + X_train, Y_train, f_shape = crop_data(X_train, (256, 256, 1), data_mask=Y_train) X_train, Y_train = merge_data( X_train, (256, 256, 1), f_shape, data_mask=Y_train) - + # The function will print the shape of the created array. In this example: # **** New data shape is: (1980, 256, 256, 1) - # f_shape could be calculate as a division between the original data + # f_shape could be calculate as a division between the original data # and the crop shapes. For instance: - h_num = math.ceil(768/256) + h_num = math.ceil(768/256) v_num = math.ceil(1024/265) f_shape = (h_num, v_num) # (3, 4) """ @@ -1139,22 +1139,22 @@ def merge_data(data, num, out_shape=(1, 1), grid=False): print("### MERGE-CROP ###") width = data.shape[1] - height = data.shape[2] + height = data.shape[2] - mixed_data = np.zeros((num, out_shape[1]*width, out_shape[0]*height, + mixed_data = np.zeros((num, out_shape[1]*width, out_shape[0]*height, data.shape[3]), dtype=np.float32) cont = 0 print("0) Merging crops . . .") for img_num in tqdm(range(0, num)): for i in range(0, out_shape[1]): for j in range(0, out_shape[0]): - + if cont == data.shape[0]: return mixed_data - mixed_data[img_num, (i*width):((i+1)*height), + mixed_data[img_num, (i*width):((i+1)*height), (j*width):((j+1)*height)] = data[cont] - + if grid: mixed_data[ img_num,(i*width):((i+1)*height)-1, (j*width)] = 255 diff --git a/generators/custom_da_gen.py b/generators/custom_da_gen.py index 918fcef2..5c0920e7 100644 --- a/generators/custom_da_gen.py +++ b/generators/custom_da_gen.py @@ -7,16 +7,16 @@ from PIL import ImageEnhance from data_2D_manipulation import random_crop from util import img_to_onehot_encoding -import imgaug as ia +import imgaug as ia from imgaug import augmenters as iaa from imgaug.augmentables.segmaps import SegmentationMapsOnImage class ImageDataGenerator(tf.keras.utils.Sequence): """Custom ImageDataGenerator based on `imgaug `_ - transformations. + transformations. - Based on https://github.com/czbiohub/microDL and + Based on https://github.com/czbiohub/microDL and https://stanford.edu/~shervine/blog/keras-how-to-generate-data-on-the-fly Parameters @@ -64,7 +64,7 @@ class ImageDataGenerator(tf.keras.utils.Sequence): To insert gamma constrast changes on images. zoom : float or [lower, upper], optional - Range for random zoom. If a float, ``[lower, upper] = [1-zoom_range, + Range for random zoom. If a float, ``[lower, upper] = [1-zoom_range, 1+zoom_range]``. random_crops_in_DA : bool, optional @@ -88,7 +88,7 @@ class ImageDataGenerator(tf.keras.utils.Sequence): out_number : int, optional Number of output returned by the network. Used to produce same number - of ground truth data on each batch. + of ground truth data on each batch. extra_data_factor : int, optional Factor to multiply the batches yielded in a epoch. It acts as if @@ -98,10 +98,10 @@ class ImageDataGenerator(tf.keras.utils.Sequence): Examples -------- :: - + # EXAMPLE 1 # Define train and val generators to make random rotations between 0 and - # 180 degrees. Notice that DA is disabled in val data + # 180 degrees. Notice that DA is disabled in val data X_train = np.ones((1776, 256, 256, 1)) Y_train = np.ones((1776, 256, 256, 1)) @@ -116,8 +116,8 @@ class ImageDataGenerator(tf.keras.utils.Sequence): X=X_val, Y=Y_val, batch_size=6, shape=(256, 256, 1), shuffle=True, da=False, val=True) - train_generator = ImageDataGenerator(**data_gen_args) - val_generator = ImageDataGenerator(**data_gen_val_args) + train_generator = ImageDataGenerator(**data_gen_args) + val_generator = ImageDataGenerator(**data_gen_val_args) # EXAMPLE 2 @@ -130,13 +130,13 @@ class ImageDataGenerator(tf.keras.utils.Sequence): X_val = np.zeros((17, 768, 1024, 1)) Y_val = np.zeros((17, 768, 1024, 1)) - # Create a prbobability map for each image. Here we define foreground - # probability much higher than the background simulating a class inbalance - # With this, the probability of take the center pixel of the random crop + # Create a prbobability map for each image. Here we define foreground + # probability much higher than the background simulating a class inbalance + # With this, the probability of take the center pixel of the random crop # that corresponds to the foreground class will be so high train_prob = calculate_2D_volume_prob_map( Y_train, 0.94, 0.06, save_file=''prob_map.npy') - + data_gen_args = dict( X=X_train, Y=Y_train, batch_size=6, shape=(256, 256, 1), shuffle=True, rotation_range=True, vflip=True, hflip=True, random_crops_in_DA=True, @@ -165,7 +165,7 @@ def __init__(self, X, Y, batch_size=32, shape=(256,256,1), shuffle=False, if random_crops_in_DA and (shape[0] != shape[1]): raise ValuError("When 'random_crops_in_DA' is selected the shape " "given must be square, e.g. (256, 256, 1)") - + if isinstance(zoom, (float, int)): self.zoom = [1 - zoom, 1 + zoom] elif (len(zoom) == 2 and @@ -175,7 +175,7 @@ def __init__(self, X, Y, batch_size=32, shape=(256,256,1), shuffle=False, raise ValueError('`zoom` should be a float or ' 'a tuple or list of two floats. ' 'Received: %s' % (zoom,)) - + self.shape = shape self.batch_size = batch_size self.X = (X/255).astype(np.float32) if np.max(X) > 2 else (X).astype(np.float32) @@ -194,7 +194,7 @@ def __init__(self, X, Y, batch_size=32, shape=(256,256,1), shuffle=False, else: self.extra_data_factor = 1 self.on_epoch_end() - + da_options = [] self.t_made = '' if rotation90: @@ -210,11 +210,11 @@ def __init__(self, X, Y, batch_size=32, shape=(256,256,1), shuffle=False, da_options.append(iaa.Flipud(0.5)) self.t_made += '_vf' if hflip: - da_options.append(iaa.Fliplr(0.5)) + da_options.append(iaa.Fliplr(0.5)) self.t_made += '_hf' if elastic: da_options.append(iaa.Sometimes(0.5,iaa.ElasticTransformation(alpha=(240, 250), sigma=25, mode="reflect"))) - self.t_made += '_elastic' + self.t_made += '_elastic' if g_blur: da_options.append(iaa.Sometimes(0.5,iaa.GaussianBlur(sigma=(1.0, 2.0)))) self.t_made += '_gblur' @@ -225,32 +225,32 @@ def __init__(self, X, Y, batch_size=32, shape=(256,256,1), shuffle=False, da_options.append(iaa.Sometimes(0.5,iaa.GammaContrast((1.25, 1.75)))) self.t_made += '_gcontrast' if self.zoom[0] != 1 or self.zoom[1] != 1: - da_options.append(iaa.Sometimes(1,iaa.Affine(scale={"x": (self.zoom[0], self.zoom[1]), + da_options.append(iaa.Sometimes(1,iaa.Affine(scale={"x": (self.zoom[0], self.zoom[1]), "y": (self.zoom[0], self.zoom[1])}))) self.t_made += '_zoom' - + self.seq = iaa.Sequential(da_options) self.t_made = '_none' if self.t_made == '' else self.t_made def __len__(self): """Defines the number of batches per epoch.""" - + return int(np.ceil(self.X.shape[0]*self.extra_data_factor/self.batch_size)) def __getitem__(self, index): - """Generation of one batch data. + """Generation of one batch data. Parameters ---------- index : int Batch index counter. - + Returns ------- batch_x : 4D Numpy array - Corresponding X elements of the batch. + Corresponding X elements of the batch. E.g. ``(batch_size, x, y, channels)``. batch_y : 4D Numpy array @@ -274,8 +274,8 @@ def __getitem__(self, index): self.val, img_prob=(self.train_prob[j] if self.train_prob is not None else None)) else: batch_x[i], batch_y[i] = self.X[j], self.Y[j] - - if self.da: + + if self.da: segmap = SegmentationMapsOnImage( batch_y[i], shape=batch_y[i].shape) t_img, t_mask = self.seq( @@ -283,9 +283,9 @@ def __getitem__(self, index): t_mask = t_mask.get_arr() batch_x[i] = t_img batch_y[i] = t_mask - + if self.n_classes > 1: - batch_y_ = np.zeros((len(indexes),) + self.shape[:2] + (self.n_classes,), + batch_y_ = np.zeros((len(indexes),) + self.shape[:2] + (self.n_classes,), dtype=np.uint8) for i in range(len(indexes)): batch_y_[i] = np.asarray(img_to_onehot_encoding( @@ -307,15 +307,15 @@ def on_epoch_end(self): def __draw_grid(self, im, grid_width=50, v=1): - """Draw grid of the specified size on an image. - + """Draw grid of the specified size on an image. + Parameters - ---------- + ---------- im : 3D Numpy array Image to be modified. E. g. ``(x, y, channels)`` - + grid_width : int, optional - Grid's width. + Grid's width. v : int, optional Value to create the grid with. @@ -327,12 +327,12 @@ def __draw_grid(self, im, grid_width=50, v=1): im[:, j] = v - def get_transformed_samples(self, num_examples, save_to_dir=False, - out_dir='aug', save_prefix=None, train=True, + def get_transformed_samples(self, num_examples, save_to_dir=False, + out_dir='aug', save_prefix=None, train=True, random_images=True, force_full_images=False): """Apply selected transformations to a defined number of images from - the dataset. - + the dataset. + Parameters ---------- num_examples : int @@ -347,7 +347,7 @@ def get_transformed_samples(self, num_examples, save_to_dir=False, provided the examples will be generated under a folder ``aug``. save_prefix : str, optional - Prefix to add to the generated examples' name. + Prefix to add to the generated examples' name. train : bool, optional To avoid drawing a grid on the generated images. This should be @@ -355,7 +355,7 @@ def get_transformed_samples(self, num_examples, save_to_dir=False, random_images : bool, optional Randomly select images from the dataset. If ``False`` the examples - will be generated from the start of the dataset. + will be generated from the start of the dataset. force_full_images : bool, optional Force the usage of the entire images. Useful to generate extra @@ -370,44 +370,44 @@ def get_transformed_samples(self, num_examples, save_to_dir=False, batch_y : 4D Numpy array Batch of data mask. E.g. ``(num_examples, x, y, channels)``. - + Examples -------- :: # EXAMPLE 1 # Generate 10 samples following with the example 1 of the class definition - X_train = np.ones((1776, 256, 256, 1)) - Y_train = np.ones((1776, 256, 256, 1)) - - data_gen_args = dict( + X_train = np.ones((1776, 256, 256, 1)) + Y_train = np.ones((1776, 256, 256, 1)) + + data_gen_args = dict( X=X_train, Y=Y_train, batch_size=6, shape=(256, 256, 1), - shuffle=True, rotation_range=True, vflip=True, hflip=True) - - train_generator = ImageDataGenerator(**data_gen_args) + shuffle=True, rotation_range=True, vflip=True, hflip=True) + + train_generator = ImageDataGenerator(**data_gen_args) - train_generator.get_transformed_samples( - 10, save_to_dir=True, train=False, out_dir='da_dir') + train_generator.get_transformed_samples( + 10, save_to_dir=True, train=False, out_dir='da_dir') # EXAMPLE 2 - # If random crop in DA-time is choosen, as the example 2 of the class definition, + # If random crop in DA-time is choosen, as the example 2 of the class definition, # the call should be the same but two more images will be stored: img and mask - # representing the random crop extracted. There a red point is painted representing + # representing the random crop extracted. There a red point is painted representing # the pixel choosen to be the center of the random crop and a blue square which # delimits crop boundaries - train_prob = calculate_2D_volume_prob_map( - Y_train, 0.94, 0.06, save_file=''prob_map.npy') - - data_gen_args = dict( + train_prob = calculate_2D_volume_prob_map( + Y_train, 0.94, 0.06, save_file=''prob_map.npy') + + data_gen_args = dict( X=X_train, Y=Y_train, batch_size=6, shape=(256, 256, 1), shuffle=True, rotation_range=True, vflip=True, hflip=True, random_crops_in_DA=True, - prob_map=True, train_prob=train_prob) + prob_map=True, train_prob=train_prob) train_generator = ImageDataGenerator(**data_gen_args) - - train_generator.get_transformed_samples( + + train_generator.get_transformed_samples( 10, save_to_dir=True, train=False, out_dir='da_dir') - + Example 2 will store two additional images as the following: @@ -419,11 +419,11 @@ def get_transformed_samples(self, num_examples, save_to_dir=False, | Original crop | Original crop mask | +--------------------------------------+-------------------------------------------+ - Together with these images another pair of images will be stored: the crop made and a - transformed version of it, which is really the generator output. - + Together with these images another pair of images will be stored: the crop made and a + transformed version of it, which is really the generator output. + For instance, setting ``elastic=True`` the above extracted crop should be transformed as follows: - + +--------------------------------------+-------------------------------------------+ | .. figure:: img/original_crop_2d.png | .. figure:: img/original_crop_mask_2d.png | | :width: 80% | :width: 70% | @@ -440,7 +440,7 @@ def get_transformed_samples(self, num_examples, save_to_dir=False, The grid is only painted if ``train=False`` which should be used just to display transformations made. Selecting random rotations between 0 and 180 degrees should generate the following: - + +---------------------------------------------+--------------------------------------------------+ | .. figure:: img/original_rd_rot_crop_2d.png | .. figure:: img/original_rd_rot_crop_mask_2d.png | | :width: 80% | :width: 70% | @@ -468,21 +468,21 @@ def get_transformed_samples(self, num_examples, save_to_dir=False, if save_to_dir: p = '_' if save_prefix is None else str(save_prefix) os.makedirs(out_dir, exist_ok=True) - + grid = False if train else True - - # Generate the examples + + # Generate the examples print("0) Creating the examples of data augmentation . . .") for i in tqdm(range(num_examples)): if random_images: - pos = random.randint(1,self.X.shape[0]-1) + pos = random.randint(1,self.X.shape[0]-1) else: pos = i # Apply crops if selected if self.random_crops_in_DA and not force_full_images: batch_x[i], batch_y[i], ox, oy,\ - s_x, s_y = random_crop(self.X[pos], self.Y[pos], self.shape[:2], + s_x, s_y = random_crop(self.X[pos], self.Y[pos], self.shape[:2], self.val, draw_prob_map_points=True, img_prob=(self.train_prob[pos] if self.train_prob is not None else None)) else: @@ -495,24 +495,24 @@ def get_transformed_samples(self, num_examples, save_to_dir=False, if save_to_dir: if self.X.shape[-1] > 1: - o_x = np.copy(batch_x[i]) + o_x = np.copy(batch_x[i]) else: o_x = np.copy(batch_x[i,...,0]) o_y = np.copy(batch_y[i,...,0]) # Apply transformations - if self.da: - segmap = SegmentationMapsOnImage( - batch_y[i], shape=batch_y[i].shape) - t_img, t_mask = self.seq( - image=batch_x[i], segmentation_maps=segmap) - t_mask = t_mask.get_arr() - batch_x[i] = t_img + if self.da: + segmap = SegmentationMapsOnImage( + batch_y[i], shape=batch_y[i].shape) + t_img, t_mask = self.seq( + image=batch_x[i], segmentation_maps=segmap) + t_mask = t_mask.get_arr() + batch_x[i] = t_img batch_y[i] = t_mask if save_to_dir: # Save original images - self.__draw_grid(o_x) + self.__draw_grid(o_x) self.__draw_grid(o_y) o_x = o_x*255 o_y = o_y*255 @@ -520,10 +520,10 @@ def get_transformed_samples(self, num_examples, save_to_dir=False, im = Image.fromarray(o_x, 'RGB') else: im = Image.fromarray(o_x) - im = im.convert('L') + im = im.convert('L') im.save(os.path.join(out_dir,str(pos)+'_orig_x'+self.t_made+".png")) mask = Image.fromarray(o_y) - mask = mask.convert('L') + mask = mask.convert('L') mask.save(os.path.join(out_dir,str(pos)+'_orig_y'+self.t_made+".png")) # Save transformed images @@ -546,56 +546,56 @@ def get_transformed_samples(self, num_examples, save_to_dir=False, a= a.convert('L') a.save(os.path.join(out_dir, str(pos)+"h_mask_"+str(i)+".png")) - # Save the original images with a point that represents the + # Save the original images with a point that represents the # selected coordinates to be the center of the crop if self.random_crops_in_DA and self.train_prob is not None\ and not force_full_images: if self.X.shape[-1] > 1: - im = Image.fromarray(self.X[pos]*255, 'RGB') + im = Image.fromarray(self.X[pos]*255, 'RGB') else: - im = Image.fromarray(self.X[pos*255,:,:,0]) - im = im.convert('RGB') - px = im.load() - + im = Image.fromarray(self.X[pos,:,:,0]*255) + im = im.convert('RGB') + px = im.load() + # Paint the selected point in red p_size=6 for col in range(oy-p_size, oy+p_size): - for row in range(ox-p_size, ox+p_size): + for row in range(ox-p_size, ox+p_size): if col >= 0 and col < self.X.shape[1] and \ row >= 0 and row < self.X.shape[2]: - px[row, col] = (255, 0, 0) - - # Paint a blue square that represents the crop made + px[row, col] = (255, 0, 0) + + # Paint a blue square that represents the crop made for row in range(s_x, s_x+self.shape[0]): px[row, s_y] = (0, 0, 255) px[row, s_y+self.shape[0]-1] = (0, 0, 255) - for col in range(s_y, s_y+self.shape[0]): + for col in range(s_y, s_y+self.shape[0]): px[s_x, col] = (0, 0, 255) px[s_x+self.shape[0]-1, col] = (0, 0, 255) im.save(os.path.join(out_dir, str(pos)+p+'mark_x'+self.t_made+'.png')) - - mask = Image.fromarray(self.Y[pos,:,:,0]) - mask = mask.convert('RGB') - px = mask.load() - + + mask = Image.fromarray(self.Y[pos,:,:,0]) + mask = mask.convert('RGB') + px = mask.load() + # Paint the selected point in red - for col in range(oy-p_size, oy+p_size): - for row in range(ox-p_size, ox+p_size): + for col in range(oy-p_size, oy+p_size): + for row in range(ox-p_size, ox+p_size): if col >= 0 and col < self.Y.shape[1] and \ - row >= 0 and row < self.Y.shape[2]: + row >= 0 and row < self.Y.shape[2]: px[row, col] = (255, 0, 0) # Paint a blue square that represents the crop made - for row in range(s_x, s_x+self.shape[0]): - px[row, s_y] = (0, 0, 255) - px[row, s_y+self.shape[0]-1] = (0, 0, 255) - for col in range(s_y, s_y+self.shape[0]): - px[s_x, col] = (0, 0, 255) + for row in range(s_x, s_x+self.shape[0]): + px[row, s_y] = (0, 0, 255) + px[row, s_y+self.shape[0]-1] = (0, 0, 255) + for col in range(s_y, s_y+self.shape[0]): + px[s_x, col] = (0, 0, 255) px[s_x+self.shape[0]-1, col] = (0, 0, 255) - mask.save(os.path.join(out_dir, str(pos)+p+'mark_y'+self.t_made+'.png')) - + mask.save(os.path.join(out_dir, str(pos)+p+'mark_y'+self.t_made+'.png')) + print("### END TR-SAMPLES ###") return batch_x, batch_y diff --git a/sota_implementations/cheng_2017/loss.py b/sota_implementations/cheng_2017/loss.py index 7a83ee71..1e2edf19 100644 --- a/sota_implementations/cheng_2017/loss.py +++ b/sota_implementations/cheng_2017/loss.py @@ -18,8 +18,10 @@ def jaccard_loss_cheng2017(y_true, y_pred): Jaccard loss score. """ C = 1 + y_pred = tf.cast(y_pred , dtype=tf.float32) + y_true = tf.cast(y_true, dtype=tf.float32) numerator = tf.reduce_sum(y_true[...,1] * y_pred[...,1]) - denominator = tf.reduce_sum(y_true[...,1] + y_pred[...,1]) - numerator + denominator = tf.reduce_sum(y_true[...,1] + y_pred[...,1]) - numerator jac = (numerator + C)/(denominator + C) diff --git a/util.py b/util.py index 25ab946d..18aac13f 100644 --- a/util.py +++ b/util.py @@ -990,7 +990,7 @@ def calculate_2D_volume_prob_map(Y, w_foreground=0.94, w_background=0.06, raise ValueError("'w_foreground' plus 'w_background' can not be greater " "than one") - prob_map = np.copy(Y[...,0]) + prob_map = np.copy(Y[...,0]).astype(np.float32) print("Constructing the probability map . . .") for i in tqdm(range(prob_map.shape[0])): @@ -999,13 +999,12 @@ def calculate_2D_volume_prob_map(Y, w_foreground=0.94, w_background=0.06, # Remove artifacts connected to image border pdf = clear_border(pdf) - foreground_pixels = (pdf == 255).sum() + foreground_pixels = (pdf > 1).sum() background_pixels = (pdf == 0).sum() - pdf[np.where(pdf == 255)] = w_foreground/foreground_pixels + pdf[np.where(pdf > 1)] = w_foreground/foreground_pixels pdf[np.where(pdf == 0)] = w_background/background_pixels - print("pdf {} {}".format(pdf.dtype, pdf.sum().dtype)) - pdf /= pdf.sum() # Necessary to get all probs sum 1 + pdf = pdf/pdf.sum() # Necessary to get all probs sum 1 prob_map[i] = pdf if save_file is not None: