├── .gitignore ├── ISIC_dataset.py ├── README.md ├── datasets ├── ISIC-2017_Test_v2_Data │ └── .gitkeep ├── ISIC-2017_Training_Data │ └── .gitkeep ├── ISIC-2017_Training_Part1_GroundTruth │ └── .gitkeep ├── ISIC-2017_Validation_Data │ └── .gitkeep └── ISIC_Archive │ └── .gitkeep ├── metrics.py ├── models.py ├── pretrained_weights └── .gitkeep ├── results ├── ISIC-2017_Test_v2_Predicted │ └── .gitkeep └── ISIC-2017_Validation_Predicted │ └── .gitkeep ├── segment.py └── weights └── .gitkeep /.gitignore: -------------------------------------------------------------------------------- 1 | weights/* 2 | !weights/.gitkeep 3 | pretrained_weights/* 4 | !pretrained_weights/.gitkeep 5 | datasets/** 6 | !datasets/**/ 7 | *.png 8 | *.pyc 9 | results/** 10 | !results/**/ 11 | !.gitkeep 12 | -------------------------------------------------------------------------------- /ISIC_dataset.py: -------------------------------------------------------------------------------- 1 | import cv2 2 | import os 3 | import numpy as np 4 | import matplotlib.pyplot as plt 5 | import shutil 6 | import yaml 7 | from sklearn.cross_validation import train_test_split 8 | import pandas as pd 9 | np.random.seed(4) 10 | 11 | mean_imagenet = [123.68, 103.939, 116.779] # rgb 12 | 13 | def get_labels(image_list, csv_file): 14 | image_list = [filename.split('.')[0] for filename in image_list] 15 | return pd.read_csv(csv_file,index_col=0).loc[image_list]['melanoma'].values.flatten().astype(np.uint8) 16 | 17 | def get_mask(image_name, mask_folder, rescale_mask=True): 18 | img_mask = cv2.imread(os.path.join(mask_folder, image_name.replace(".jpg","_segmentation.png")), 19 | cv2.IMREAD_GRAYSCALE) 20 | if img_mask is None: 21 | img_mask = cv2.imread(os.path.join(mask_folder, image_name.replace(".jpg",".png")), 22 | cv2.IMREAD_GRAYSCALE) 23 | _,img_mask = cv2.threshold(img_mask,127,255,cv2.THRESH_BINARY) 24 | if rescale_mask: 25 | img_mask = img_mask/255. 26 | return img_mask 27 | 28 | def get_color_image(image_name, image_folder, remove_mean_imagenet=True, use_hsv=False, remove_mean_samplewise=False): 29 | if remove_mean_imagenet and remove_mean_samplewise: 30 | raise Exception("Can't use both sample mean and Imagenet mean") 31 | img = cv2.imread(os.path.join(image_folder, image_name.replace(".jpg",".png"))) 32 | if img is None: 33 | img = cv2.imread(os.path.join(image_folder, image_name)) 34 | img = cv2.cvtColor(img, cv2.COLOR_BGR2RGB).astype(np.float32) 35 | if remove_mean_imagenet: 36 | for channel in [0,1,2]: 37 | img[:,:,channel] -= mean_imagenet[channel] 38 | elif remove_mean_samplewise: 39 | img_channel_axis = 2 40 | img -= np.mean(img, axis=img_channel_axis, keepdims=True) 41 | if use_hsv: 42 | img_all = np.zeros((img.shape[0],img.shape[1],6)) 43 | img_all[:,:,0:3] = img 44 | img_hsv = cv2.cvtColor(img, cv2.COLOR_RGB2HSV) 45 | img_all[:,:,3:] = img_hsv 46 | img = img_all 47 | 48 | img = img.transpose((2,0,1)).astype(np.float32) 49 | return img 50 | 51 | def load_images(images_list, height, width, image_folder, mask_folder, remove_mean_imagenet=True, rescale_mask=True, use_hsv=False, remove_mean_samplewise=False): 52 | if use_hsv: 53 | n_chan = 6 54 | else: 55 | n_chan = 3 56 | img_array = np.zeros((len(images_list), n_chan, height, width), dtype=np.float32) 57 | if mask_folder: 58 | img_mask_array = np.zeros((len(images_list), height, width), dtype=np.float32) 59 | i = 0 60 | for image_name in images_list: 61 | img = get_color_image(image_name, image_folder, remove_mean_imagenet=remove_mean_imagenet,use_hsv=use_hsv,remove_mean_samplewise=remove_mean_samplewise) 62 | img_array[i] = img 63 | if mask_folder: 64 | img_mask = get_mask(image_name, mask_folder, rescale_mask) 65 | img_mask_array[i] =img_mask 66 | i = i+1 67 | if not mask_folder: 68 | return img_array 69 | else: 70 | return (img_array, img_mask_array.astype(np.uint8).reshape((img_mask_array.shape[0],1,img_mask_array.shape[1],img_mask_array.shape[2]))) 71 | 72 | def train_test_from_yaml(yaml_file, csv_file): 73 | with open(yaml_file,"r") as f: 74 | folds = yaml.load(f); 75 | train_list, test_list = folds["Fold_1"] 76 | train_label = get_labels(train_list, csv_file=csv_file) 77 | test_label = get_labels(test_list, csv_file=csv_file) 78 | return train_list, train_label, test_list, test_label 79 | 80 | def train_val_split(train_list, train_labels, seed, val_split = 0.20): 81 | train_list, val_list, train_label, val_label = train_test_split(train_list, train_labels, test_size=val_split, stratify=train_labels, random_state=seed) 82 | return train_list, val_list, train_label, val_label 83 | 84 | def train_val_test_from_txt(train_txt, val_txt, test_txt): 85 | train_list =[]; val_list = []; test_list = []; 86 | with open(train_txt) as t: 87 | for img in t: 88 | img = img.strip() 89 | if img.endswith(".jpg"): 90 | train_list.append(img) 91 | with open(val_txt) as t: 92 | for img in t: 93 | img = img.strip() 94 | if img.endswith(".jpg"): 95 | val_list.append(img) 96 | with open(test_txt) as t: 97 | for img in t: 98 | img = img.strip() 99 | if img.endswith(".jpg"): 100 | test_list.append(img) 101 | print "Found train: {}, val: {}, test: {}.".format(len(train_list),len(val_list),len(test_list)) 102 | return train_list, val_list, test_list 103 | 104 | def list_from_folder(image_folder): 105 | image_list = [] 106 | for image_filename in os.listdir(image_folder): 107 | if image_filename.endswith(".jpg"): 108 | image_list.append(image_filename) 109 | print "Found {} ISIC validation images.".format(len(image_list)) 110 | return image_list 111 | 112 | def move_images(images_list, input_image_folder, input_mask_folder, output_image_folder, output_mask_folder, height=None, width=None, same_name=False): 113 | base_output_folder = output_image_folder 114 | base_output_mask_folder = output_mask_folder 115 | for k in range(len(images_list)): 116 | image_filename = images_list[k] 117 | image_name = os.path.basename(image_filename).split('.')[0] 118 | if not os.path.exists(output_image_folder): 119 | os.makedirs(output_image_folder) 120 | if input_mask_folder and not os.path.exists(output_mask_folder): 121 | os.makedirs(output_mask_folder) 122 | if height and width: 123 | img = cv2.imread(os.path.join(input_image_folder,image_filename)) 124 | img = cv2.resize(img, (width, height), interpolation = cv2.INTER_CUBIC) 125 | cv2.imwrite(os.path.join(output_image_folder,image_name+".png"), img) 126 | if input_mask_folder: 127 | img_mask = get_mask(image_filename, input_mask_folder, rescale_mask=False) 128 | img_mask = cv2.resize(img_mask, (width, height), interpolation = cv2.INTER_CUBIC) 129 | _,img_mask = cv2.threshold(img_mask,127,255,cv2.THRESH_BINARY) 130 | cv2.imwrite(os.path.join(output_mask_folder,image_name+".png"), img_mask) 131 | else: 132 | if not same_name: 133 | shutil.copyfile(os.path.join(input_image_folder, image_filename), os.path.join(output_image_folder,image_name+".jpg")) 134 | else: 135 | img = cv2.imread(os.path.join(input_image_folder,image_filename)) 136 | cv2.imwrite(os.path.join(output_image_folder,image_name+".png"), img) 137 | 138 | if input_mask_folder: 139 | image_mask_filename = image_filename.replace(".jpg","_segmentation.png") 140 | shutil.copyfile(os.path.join(input_mask_folder,image_mask_filename), os.path.join(output_mask_folder,image_name+".png")) 141 | 142 | def resize_images(images_list, input_image_folder, input_mask_folder, output_image_folder, output_mask_folder, height, width): 143 | return move_images(images_list, input_image_folder, input_mask_folder, output_image_folder, output_mask_folder, height, width) 144 | 145 | def get_mask_full_sized(mask_pred, original_shape, output_folder = None, image_name = None): 146 | mask_pred = cv2.resize(mask_pred, (original_shape[1], original_shape[0])) # resize to original mask size 147 | _,mask_pred = cv2.threshold(mask_pred,127,255,cv2.THRESH_BINARY) 148 | if output_folder and image_name: 149 | cv2.imwrite(os.path.join(output_folder,image_name.split('.')[0]+"_segmentation.png"), mask_pred) 150 | return mask_pred 151 | 152 | def show_images_full_sized(image_list, img_mask_pred_array, image_folder, mask_folder, index, output_folder=None, plot=True): 153 | image_name = image_list[index] 154 | img = get_color_image(image_name, image_folder, remove_mean_imagenet=False).astype(np.uint8) 155 | img = img.transpose(1,2,0) 156 | if mask_folder: 157 | mask_true = get_mask(image_name, mask_folder, rescale_mask=False) 158 | mask_pred = get_mask_full_sized(img_mask_pred_array[index][0], img.shape, output_folder=output_folder, image_name = image_name) 159 | if mask_folder: 160 | if plot: 161 | f, ax = plt.subplots(1, 3) 162 | ax[0].imshow(img); ax[0].axis("off"); 163 | ax[1].imshow(mask_true, cmap='Greys_r'); ax[1].axis("off"); 164 | ax[2].imshow(mask_pred, cmap='Greys_r'); ax[2].axis("off"); plt.show() 165 | return img, mask_true, mask_pred 166 | else: 167 | if plot: 168 | f, ax = plt.subplots(1, 2) 169 | ax[0].imshow(img); ax[0].axis("off"); 170 | ax[1].imshow(mask_pred, cmap='Greys_r'); ax[1].axis("off"); plt.show() 171 | return img, mask_pred 172 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # isic-2017-segmentation 2 | Implementation of the segmentation task for ISIC 2017. https://arxiv.org/abs/1703.04819 3 | 4 | View segment.py for parameter options and instructions on where to download the dataset. 5 | -------------------------------------------------------------------------------- /datasets/ISIC-2017_Test_v2_Data/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/learningtitans/isbi2017-part1/b5dbaa8d10663b88fbbe5a2cbe933ff13584277a/datasets/ISIC-2017_Test_v2_Data/.gitkeep -------------------------------------------------------------------------------- /datasets/ISIC-2017_Training_Data/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/learningtitans/isbi2017-part1/b5dbaa8d10663b88fbbe5a2cbe933ff13584277a/datasets/ISIC-2017_Training_Data/.gitkeep -------------------------------------------------------------------------------- /datasets/ISIC-2017_Training_Part1_GroundTruth/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/learningtitans/isbi2017-part1/b5dbaa8d10663b88fbbe5a2cbe933ff13584277a/datasets/ISIC-2017_Training_Part1_GroundTruth/.gitkeep -------------------------------------------------------------------------------- /datasets/ISIC-2017_Validation_Data/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/learningtitans/isbi2017-part1/b5dbaa8d10663b88fbbe5a2cbe933ff13584277a/datasets/ISIC-2017_Validation_Data/.gitkeep -------------------------------------------------------------------------------- /datasets/ISIC_Archive/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/learningtitans/isbi2017-part1/b5dbaa8d10663b88fbbe5a2cbe933ff13584277a/datasets/ISIC_Archive/.gitkeep -------------------------------------------------------------------------------- /metrics.py: -------------------------------------------------------------------------------- 1 | import numpy as np 2 | from keras import backend as K 3 | from sklearn.metrics import jaccard_similarity_score 4 | 5 | smooth_default = 1. 6 | 7 | def dice_coef(y_true, y_pred, smooth = smooth_default, per_batch = True): 8 | if not per_batch: 9 | y_true_f = K.flatten(y_true) 10 | y_pred_f = K.flatten(y_pred) 11 | intersection = K.sum(y_true_f * y_pred_f) 12 | return (2. * intersection + smooth) / (K.sum(y_true_f) + K.sum(y_pred_f) + smooth) 13 | else: 14 | y_true_f = K.batch_flatten(y_true) 15 | y_pred_f = K.batch_flatten(y_pred) 16 | intersec = 2. * K.sum(y_true_f * y_pred_f, axis=1, keepdims=True) + smooth 17 | union = K.sum(y_true_f, axis=1, keepdims=True) + K.sum(y_pred_f, axis=1, keepdims=True) + smooth 18 | return K.mean(intersec / union) 19 | 20 | def jacc_coef(y_true, y_pred, smooth = smooth_default): 21 | y_true_f = K.flatten(y_true) 22 | y_pred_f = K.flatten(y_pred) 23 | intersection = K.sum(y_true_f * y_pred_f) 24 | return (intersection + smooth) / (K.sum(y_true_f) + K.sum(y_pred_f) - intersection + smooth) 25 | 26 | def jacc_loss(y_true, y_pred): 27 | return -jacc_coef(y_true, y_pred) 28 | 29 | def dice_loss(y_true, y_pred): 30 | return -dice_coef(y_true, y_pred) 31 | 32 | def dice_jacc_single(mask_true, mask_pred, smooth = smooth_default): 33 | bool_true = mask_true.reshape(-1).astype(np.bool) 34 | bool_pred = mask_pred.reshape(-1).astype(np.bool) 35 | if bool_true.shape != bool_pred.shape: 36 | raise ValueError("Masks of different sizes.") 37 | 38 | bool_sum = bool_true.sum() + bool_pred.sum() 39 | if bool_sum == 0: 40 | print "Empty mask" 41 | return 0,0 42 | intersec = np.logical_and(bool_true, bool_pred).sum() 43 | dice = 2. * intersec / bool_sum 44 | jacc = jaccard_similarity_score(bool_true.reshape((1, -1)), bool_pred.reshape((1, -1)), normalize=True, sample_weight=None) 45 | return dice, jacc 46 | 47 | def dice_jacc_mean(mask_true, mask_pred, smooth = smooth_default): 48 | dice = 0 49 | jacc = 0 50 | for i in range(mask_true.shape[0]): 51 | current_dice, current_jacc = dice_jacc_single(mask_true=mask_true[i],mask_pred=mask_pred[i], smooth= smooth) 52 | dice = dice + current_dice 53 | jacc = jacc + current_jacc 54 | return dice/mask_true.shape[0], jacc/mask_true.shape[0] -------------------------------------------------------------------------------- /models.py: -------------------------------------------------------------------------------- 1 | import numpy as np 2 | from keras.models import Model 3 | from keras.layers import merge, Flatten, Dense, Input, Dropout, Activation, Reshape 4 | from keras.layers import Convolution2D, MaxPooling2D, ZeroPadding2D, UpSampling2D 5 | from keras.layers import BatchNormalization 6 | from keras.layers.noise import GaussianNoise 7 | import h5py 8 | np.random.seed(4) 9 | 10 | VGG16_WEIGHTS_NOTOP = 'pretrained_weights/vgg16_notop.h5' 11 | # download .h5 weights from https://gist.github.com/baraldilorenzo/07d7802847aaad0a35d3 12 | 13 | def Unet(img_rows, img_cols, loss , optimizer, metrics, fc_size = 8192, channels = 3): 14 | filter_size = 5 15 | filter_size_2 = 11 16 | dropout_a = 0.5 17 | dropout_b = 0.5 18 | dropout_c = 0.5 19 | gaussian_noise_std = 0.025 20 | 21 | inputs = Input((channels, img_rows, img_cols)) 22 | input_with_noise = GaussianNoise(gaussian_noise_std)(inputs) 23 | 24 | conv1 = Convolution2D(32, filter_size, filter_size, activation='relu', border_mode='same')(input_with_noise) 25 | conv1 = Convolution2D(32, filter_size, filter_size, activation='relu', border_mode='same')(conv1) 26 | conv1 = Convolution2D(32, filter_size, filter_size, activation='relu', border_mode='same')(conv1) 27 | pool1 = MaxPooling2D((2, 2), strides=(2, 2))(conv1) 28 | pool1 = GaussianNoise(gaussian_noise_std)(pool1) 29 | 30 | conv2 = Convolution2D(64, filter_size, filter_size, activation='relu', border_mode='same')(pool1) 31 | conv2 = Convolution2D(64, filter_size, filter_size, activation='relu', border_mode='same')(conv2) 32 | conv2 = Convolution2D(64, filter_size, filter_size, activation='relu', border_mode='same')(conv2) 33 | pool2 = MaxPooling2D((2, 2), strides=(2, 2))(conv2) 34 | pool2 = GaussianNoise(gaussian_noise_std)(pool2) 35 | 36 | conv3 = Convolution2D(128, filter_size, filter_size, activation='relu', border_mode='same')(pool2) 37 | conv3 = Convolution2D(128, filter_size, filter_size, activation='relu', border_mode='same')(conv3) 38 | conv3 = Convolution2D(128, filter_size, filter_size, activation='relu', border_mode='same')(conv3) 39 | pool3 = MaxPooling2D((2, 2), strides=(2, 2))(conv3) 40 | pool3 = Dropout(dropout_a)(pool3) 41 | 42 | fc = Flatten()(pool3) 43 | fc = Dense(fc_size, activation='relu')(fc) 44 | fc = Dropout(dropout_b)(fc) 45 | 46 | n = img_rows/2/2/2 47 | fc = Dense(128*n*n, activation='relu')(fc) 48 | fc = GaussianNoise(gaussian_noise_std)(fc) 49 | fc = Reshape((128,n,n))(fc) 50 | 51 | up1 = merge([UpSampling2D(size=(2, 2))(fc), conv3], mode='concat', concat_axis=1) 52 | up1 = Dropout(dropout_c)(up1) 53 | 54 | conv4 = Convolution2D(128, filter_size_2, filter_size_2, activation='relu', border_mode='same')(up1) 55 | conv4 = Convolution2D(128, filter_size, filter_size, activation='relu', border_mode='same')(conv4) 56 | conv4 = Convolution2D(64, filter_size, filter_size, activation='relu', border_mode='same')(conv4) 57 | 58 | up2 = merge([UpSampling2D(size=(2, 2))(conv4), conv2], mode='concat', concat_axis=1) 59 | up2 = Dropout(dropout_c)(up2) 60 | 61 | conv5 = Convolution2D(64, filter_size_2, filter_size_2, activation='relu', border_mode='same')(up2) 62 | conv5 = Convolution2D(64, filter_size, filter_size, activation='relu', border_mode='same')(conv5) 63 | conv5 = Convolution2D(32, filter_size, filter_size, activation='relu', border_mode='same')(conv5) 64 | 65 | up3 = merge([UpSampling2D(size=(2, 2))(conv5), conv1], mode='concat', concat_axis=1) 66 | up3 = Dropout(dropout_c)(up3) 67 | 68 | conv6 = Convolution2D(32, filter_size_2, filter_size_2, activation='relu', border_mode='same')(up3) 69 | conv6 = Convolution2D(32, filter_size, filter_size, activation='relu', border_mode='same')(conv6) 70 | conv6 = Convolution2D(32, filter_size, filter_size, activation='relu', border_mode='same')(conv6) 71 | 72 | conv7 = Convolution2D(1, 1, 1, activation='sigmoid')(conv6) 73 | 74 | model = Model(input=inputs, output=conv7) 75 | 76 | model.compile(optimizer=optimizer, loss=loss, metrics=metrics) 77 | 78 | return model 79 | 80 | def VGG16(img_rows, img_cols, pretrained, freeze_pretrained, loss , optimizer, metrics, channels=3): 81 | inputs = Input((channels, img_rows, img_cols)) 82 | pad1 = ZeroPadding2D((1, 1), input_shape=(channels, img_rows, img_cols))(inputs) 83 | conv1 = Convolution2D(64, 3, 3, activation='relu', name='conv1_1')(pad1) 84 | conv1 = ZeroPadding2D((1, 1))(conv1) 85 | conv1 = Convolution2D(64, 3, 3, activation='relu', name='conv1_2')(conv1) 86 | pool1 = MaxPooling2D((2, 2), strides=(2, 2))(conv1) 87 | 88 | pad2 = ZeroPadding2D((1, 1))(pool1) 89 | conv2 = Convolution2D(128, 3, 3, activation='relu', name='conv2_1')(pad2) 90 | conv2 = ZeroPadding2D((1, 1))(conv2) 91 | conv2 = Convolution2D(128, 3, 3, activation='relu', name='conv2_2')(conv2) 92 | pool2 = MaxPooling2D((2, 2), strides=(2, 2))(conv2) 93 | 94 | pad3 = ZeroPadding2D((1, 1))(pool2) 95 | conv3 = Convolution2D(256, 3, 3, activation='relu', name='conv3_1')(pad3) 96 | conv3 = ZeroPadding2D((1, 1))(conv3) 97 | conv3 = Convolution2D(256, 3, 3, activation='relu', name='conv3_2')(conv3) 98 | conv3 = ZeroPadding2D((1, 1))(conv3) 99 | conv3 = Convolution2D(256, 3, 3, activation='relu', name='conv3_3')(conv3) 100 | pool3 = MaxPooling2D((2, 2), strides=(2, 2))(conv3) 101 | 102 | pad4 = ZeroPadding2D((1, 1))(pool3) 103 | conv4 = Convolution2D(512, 3, 3, activation='relu', name='conv4_1')(pad4) 104 | conv4 = ZeroPadding2D((1, 1))(conv4) 105 | conv4 = Convolution2D(512, 3, 3, activation='relu', name='conv4_2')(conv4) 106 | conv4 = ZeroPadding2D((1, 1))(conv4) 107 | conv4 = Convolution2D(512, 3, 3, activation='relu', name='conv4_3')(conv4) 108 | pool4 = MaxPooling2D((2, 2), strides=(2, 2))(conv4) 109 | 110 | pad5 = ZeroPadding2D((1, 1))(pool4) 111 | conv5 = Convolution2D(512, 3, 3, activation='relu', name='conv5_1')(pad5) 112 | conv5 = ZeroPadding2D((1, 1))(conv5) 113 | conv5 = Convolution2D(512, 3, 3, activation='relu', name='conv5_2')(conv5) 114 | conv5 = ZeroPadding2D((1, 1))(conv5) 115 | conv5 = Convolution2D(512, 3, 3, activation='relu', name='conv5_3')(conv5) 116 | 117 | model = Model(input=inputs, output=conv5) 118 | # load weights 119 | if pretrained: 120 | weights_path = VGG16_WEIGHTS_NOTOP 121 | f = h5py.File(weights_path) 122 | for k in range(f.attrs['nb_layers']): 123 | if k >= (len(model.layers) - 1): 124 | # ignore the last layers in the savefile 125 | break 126 | g = f['layer_{}'.format(k)] 127 | weights = [g['param_{}'.format(p)] for p in range(g.attrs['nb_params'])] 128 | model.layers[k+1].set_weights(weights) 129 | f.close() 130 | print('ImageNet pre-trained weights loaded.') 131 | 132 | if freeze_pretrained: 133 | for layer in model.layers: 134 | layer.trainable = False 135 | 136 | dropout_val = 0.5 137 | up6 = merge([UpSampling2D(size=(2, 2))(conv5), conv4], mode='concat', concat_axis=1) 138 | up6 = Dropout(dropout_val)(up6) 139 | 140 | conv6 = Convolution2D(256, 3, 3, activation='relu', border_mode='same')(up6) 141 | conv6 = Convolution2D(256, 3, 3, activation='relu', border_mode='same')(conv6) 142 | 143 | up7 = merge([UpSampling2D(size=(2, 2))(conv6), conv3], mode='concat', concat_axis=1) 144 | up7 = Dropout(dropout_val)(up7) 145 | 146 | conv7 = Convolution2D(128, 3, 3, activation='relu', border_mode='same')(up7) 147 | conv7 = Convolution2D(128, 3, 3, activation='relu', border_mode='same')(conv7) 148 | 149 | up8 = merge([UpSampling2D(size=(2, 2))(conv7), conv2], mode='concat', concat_axis=1) 150 | up8 = Dropout(dropout_val)(up8) 151 | 152 | conv8 = Convolution2D(64, 3, 3, activation='relu', border_mode='same')(up8) 153 | conv8 = Convolution2D(64, 3, 3, activation='relu', border_mode='same')(conv8) 154 | 155 | up9 = merge([UpSampling2D(size=(2, 2))(conv8), conv1], mode='concat', concat_axis=1) 156 | up9 = Dropout(dropout_val)(up9) 157 | 158 | conv9 = Convolution2D(32, 3, 3, activation='relu', border_mode='same')(up9) 159 | conv9 = Convolution2D(32, 3, 3, activation='relu', border_mode='same')(conv9) 160 | 161 | conv10 = Convolution2D(1, 1, 1, activation='sigmoid')(conv9) 162 | 163 | model = Model(input=inputs, output=conv10) 164 | 165 | model.compile(optimizer=optimizer, loss=loss, metrics=metrics) 166 | 167 | return model 168 | 169 | def Unet2(img_rows, img_cols, loss , optimizer, metrics, fc_size = 0, channels = 3): 170 | filter_size = 5 171 | filter_size_2 = 11 172 | dropout_a = 0.5 173 | dropout_b = 0.5 174 | dropout_c = 0.5 175 | gaussian_noise_std = 0.025 176 | 177 | inputs = Input((channels, img_rows, img_cols)) 178 | input_with_noise = GaussianNoise(gaussian_noise_std)(inputs) 179 | 180 | conv1 = Convolution2D(32, filter_size, filter_size, activation='relu', border_mode='same')(input_with_noise) 181 | conv1 = Convolution2D(32, filter_size, filter_size, activation='relu', border_mode='same')(conv1) 182 | conv1 = Convolution2D(32, filter_size, filter_size, activation='relu', border_mode='same')(conv1) 183 | pool1 = MaxPooling2D((2, 2), strides=(2, 2))(conv1) 184 | pool1 = GaussianNoise(gaussian_noise_std)(pool1) 185 | 186 | conv2 = Convolution2D(64, filter_size, filter_size, activation='relu', border_mode='same')(pool1) 187 | conv2 = Convolution2D(64, filter_size, filter_size, activation='relu', border_mode='same')(conv2) 188 | conv2 = Convolution2D(64, filter_size, filter_size, activation='relu', border_mode='same')(conv2) 189 | pool2 = MaxPooling2D((2, 2), strides=(2, 2))(conv2) 190 | pool2 = GaussianNoise(gaussian_noise_std)(pool2) 191 | 192 | conv3 = Convolution2D(128, filter_size, filter_size, activation='relu', border_mode='same')(pool2) 193 | conv3 = Convolution2D(128, filter_size, filter_size, activation='relu', border_mode='same')(conv3) 194 | conv3 = Convolution2D(128, filter_size, filter_size, activation='relu', border_mode='same')(conv3) 195 | pool3 = MaxPooling2D((2, 2), strides=(2, 2))(conv3) 196 | pool3 = Dropout(dropout_a)(pool3) 197 | if fc_size>0: 198 | fc = Flatten()(pool3) 199 | fc = Dense(fc_size)(fc) 200 | fc = BatchNormalization()(fc) 201 | fc = Activation('relu')(fc) 202 | fc = Dropout(dropout_b)(fc) 203 | 204 | n = img_rows/2/2/2 205 | fc = Dense(img_rows*n*n)(fc) 206 | fc = BatchNormalization()(fc) 207 | fc = Activation('relu')(fc) 208 | fc = GaussianNoise(gaussian_noise_std)(fc) 209 | fc = Reshape((128,n,n))(fc) 210 | else: 211 | fc = Convolution2D(256, filter_size, filter_size, activation='relu', border_mode='same')(pool3) 212 | fc = BatchNormalization()(fc) 213 | fc = Dropout(dropout_b)(fc) 214 | 215 | up1 = merge([UpSampling2D(size=(2, 2))(fc), conv3], mode='concat', concat_axis=1) 216 | up1 = BatchNormalization()(up1) 217 | up1 = Dropout(dropout_c)(up1) 218 | 219 | conv4 = Convolution2D(128, filter_size_2, filter_size_2, activation='relu', border_mode='same')(up1) 220 | conv4 = Convolution2D(128, filter_size, filter_size, activation='relu', border_mode='same')(conv4) 221 | conv4 = Convolution2D(64, filter_size, filter_size, activation='relu', border_mode='same')(conv4) 222 | 223 | up2 = merge([UpSampling2D(size=(2, 2))(conv4), conv2], mode='concat', concat_axis=1) 224 | up2 = BatchNormalization()(up2) 225 | up2 = Dropout(dropout_c)(up2) 226 | 227 | conv5 = Convolution2D(64, filter_size_2, filter_size_2, activation='relu', border_mode='same')(up2) 228 | conv5 = Convolution2D(64, filter_size, filter_size, activation='relu', border_mode='same')(conv5) 229 | conv5 = Convolution2D(32, filter_size, filter_size, activation='relu', border_mode='same')(conv5) 230 | 231 | up3 = merge([UpSampling2D(size=(2, 2))(conv5), conv1], mode='concat', concat_axis=1) 232 | up3 = BatchNormalization()(up3) 233 | up3 = Dropout(dropout_c)(up3) 234 | 235 | conv6 = Convolution2D(32, filter_size_2, filter_size_2, activation='relu', border_mode='same')(up3) 236 | conv6 = Convolution2D(32, filter_size, filter_size, activation='relu', border_mode='same')(conv6) 237 | conv6 = Convolution2D(32, filter_size, filter_size, activation='relu', border_mode='same')(conv6) 238 | 239 | conv7 = Convolution2D(1, 1, 1, activation='sigmoid')(conv6) 240 | 241 | model = Model(input=inputs, output=conv7) 242 | 243 | model.compile(optimizer=optimizer, loss=loss, metrics=metrics) 244 | 245 | return model -------------------------------------------------------------------------------- /pretrained_weights/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/learningtitans/isbi2017-part1/b5dbaa8d10663b88fbbe5a2cbe933ff13584277a/pretrained_weights/.gitkeep -------------------------------------------------------------------------------- /results/ISIC-2017_Test_v2_Predicted/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/learningtitans/isbi2017-part1/b5dbaa8d10663b88fbbe5a2cbe933ff13584277a/results/ISIC-2017_Test_v2_Predicted/.gitkeep -------------------------------------------------------------------------------- /results/ISIC-2017_Validation_Predicted/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/learningtitans/isbi2017-part1/b5dbaa8d10663b88fbbe5a2cbe933ff13584277a/results/ISIC-2017_Validation_Predicted/.gitkeep -------------------------------------------------------------------------------- /segment.py: -------------------------------------------------------------------------------- 1 | import os 2 | import numpy as np 3 | from keras.optimizers import Adam, SGD 4 | from keras.callbacks import ModelCheckpoint 5 | from keras import backend as K 6 | from keras.preprocessing.image import ImageDataGenerator 7 | import matplotlib.pyplot as plt 8 | import pickle as pkl 9 | import ISIC_dataset as ISIC 10 | from metrics import dice_loss, jacc_loss, jacc_coef, dice_jacc_mean 11 | import models 12 | np.random.seed(4) 13 | K.set_image_dim_ordering('th') # Theano dimension ordering: (channels, width, height) 14 | # some changes will be necessary to run with tensorflow 15 | 16 | # Abstract: https://arxiv.org/abs/1703.04819 17 | 18 | # Extract challenge Training / Validation / Test images as below 19 | # Download from https://challenge.kitware.com/#challenge/583f126bcad3a51cc66c8d9a 20 | training_folder = "datasets/ISIC-2017_Training_Data" 21 | training_mask_folder = "datasets/ISIC-2017_Training_Part1_GroundTruth" 22 | training_labels_csv = "datasets/ISIC-2017_Training_Part3_GroundTruth.csv" 23 | training_split_yml = "datasets/isic.yml" 24 | validation_folder = "datasets/ISIC-2017_Validation_Data" 25 | test_folder = "datasets/ISIC-2017_Test_v2_Data" 26 | 27 | # Place ISIC full dataset as below (optional) 28 | isicfull_folder = "datasets/ISIC_Archive/image" 29 | isicfull_mask_folder = "datasets/ISIC_Archive/mask" 30 | isicfull_train_split="datasets/ISIC_Archive/train.txt" 31 | isicfull_val_split="datasets/ISIC_Archive/val.txt" 32 | isicfull_test_split="datasets/ISIC_Archive/test.txt" 33 | 34 | # Folder to store predicted masks 35 | validation_predicted_folder = "results/ISIC-2017_Validation_Predicted" 36 | test_predicted_folder = "results/ISIC-2017_Test_v2_Predicted" 37 | 38 | seed = 1 39 | height, width = 128, 128 40 | nb_epoch = 220 41 | model_name = "model1" 42 | 43 | do_train = True # train network and save as model_name 44 | do_predict = True # use model to predict and save generated masks for Validation/Test 45 | do_ensemble = False # use previously saved predicted masks from multiple models to generate final masks 46 | ensemble_pkl_filenames = ["model1","model2", "model3", "model4"] 47 | model = 'unet' 48 | batch_size = 4 49 | loss_param = 'dice' 50 | optimizer_param = 'adam' 51 | monitor_metric = 'val_jacc_coef' 52 | fc_size = 8192 53 | mean_type = 'imagenet' # 'sample' 'samplewise' 54 | rescale_mask = True 55 | use_hsv = False 56 | dataset='isic' # 'isic' 'isicfull' 'isic_noval_notest' 'isic_other_split' 'isic_notest' 57 | initial_epoch = 0 58 | 59 | metrics = [jacc_coef] 60 | if use_hsv: 61 | n_channels = 6 62 | print "Using HSV" 63 | else: 64 | n_channels = 3 65 | 66 | print "Using {} mean".format(mean_type) 67 | remove_mean_imagenet=False 68 | remove_mean_samplewise=False 69 | remove_mean_dataset=False 70 | if mean_type == 'imagenet': 71 | remove_mean_imagenet = True; 72 | elif mean_type == 'sample': 73 | remove_mean_samplewise = True 74 | elif mean_type == 'dataset': 75 | remove_mean_dataset = True 76 | train_mean = np.array([[[ 180.71656799]],[[ 151.13494873]],[[ 139.89967346]]]); 77 | train_std = np.array([[[1]],[[1]],[[ 1]]]); # not using std 78 | else: 79 | raise Exception("Wrong mean type") 80 | 81 | loss_options = {'BCE': 'binary_crossentropy', 'dice':dice_loss, 'jacc':jacc_loss, 'mse':'mean_squared_error'} 82 | optimizer_options = {'adam': Adam(lr=1e-5), 83 | 'sgd': SGD(lr=0.01, decay=1e-6, momentum=0.9, nesterov=True)} 84 | 85 | loss = loss_options[loss_param] 86 | optimizer = optimizer_options[optimizer_param] 87 | model_filename = "weights/{}.h5".format(model_name) 88 | 89 | print 'Create model' 90 | 91 | if model == 'unet': 92 | model = models.Unet(height,width, loss=loss, optimizer = optimizer, metrics = metrics, fc_size = fc_size, channels=n_channels) 93 | elif model == 'unet2': 94 | model = models.Unet2(height,width, loss=loss, optimizer = optimizer, metrics = metrics, fc_size = fc_size, channels=n_channels) 95 | elif model == 'vgg': 96 | model = models.VGG16(height,width, pretrained=True, freeze_pretrained = False, loss = loss, optimizer = optimizer, metrics = metrics) 97 | else: 98 | print "Incorrect model name" 99 | 100 | def myGenerator(train_generator, train_mask_generator, 101 | remove_mean_imagenet=True, rescale_mask=True, use_hsv=False): 102 | while True: 103 | train_gen = next(train_generator) 104 | train_mask = next(train_mask_generator) 105 | 106 | if False: # use True to show images 107 | mask_true_show = np.where(train_mask>=0.5, 1, 0) 108 | mask_true_show = mask_true_show * 255 109 | mask_true_show = mask_true_show.astype(np.uint8) 110 | for i in range(train_gen.shape[0]): 111 | mask = train_mask[i].reshape((width,height)) 112 | img=train_gen[i] 113 | img = img[0:3] 114 | img = img.astype(np.uint8) 115 | img = img.transpose(1,2,0) 116 | f, ax = plt.subplots(1, 2) 117 | ax[0].imshow(img); ax[0].axis("off"); 118 | ax[1].imshow(mask, cmap='Greys_r'); ax[1].axis("off"); plt.show() 119 | yield (train_gen, train_mask) 120 | 121 | if do_train: 122 | if dataset == 'isicfull': 123 | n_samples = 2000 # per epoch 124 | print "Using ISIC full dataset" 125 | train_list, val_list, test_list = ISIC.train_val_test_from_txt(isicfull_train_split, isicfull_val_split, isicfull_test_split) 126 | # folders for resized images 127 | base_folder = "datasets/isicfull_{}_{}".format(height,width) 128 | image_folder = os.path.join(base_folder,"image") 129 | mask_folder = os.path.join(base_folder,"mask") 130 | if not os.path.exists(base_folder): 131 | print "Begin resizing..." 132 | ISIC.resize_images(train_list+val_list+test_list, input_image_folder=isicfull_folder, 133 | input_mask_folder=isicfull_mask_folder, 134 | output_image_folder=image_folder.format(height,width), output_mask_folder=mask_folder, 135 | height=height, width=width) 136 | print "Done resizing..." 137 | 138 | else: 139 | print "Using ISIC 2017 dataset" 140 | # folders for resized images 141 | base_folder = "datasets/isic_{}_{}".format(height, width) 142 | image_folder = os.path.join(base_folder,"image") 143 | mask_folder = os.path.join(base_folder,"mask") 144 | train_list, train_label, test_list, test_label = ISIC.train_test_from_yaml(yaml_file = training_split_yml, csv_file = training_labels_csv) 145 | train_list, val_list, train_label, val_label = ISIC.train_val_split(train_list, train_label, seed = seed, val_split = 0.20) 146 | if not os.path.exists(base_folder): 147 | ISIC.resize_images(train_list+val_list+test_list, 148 | input_image_folder=training_folder, input_mask_folder=training_mask_folder, 149 | output_image_folder=image_folder, output_mask_folder=mask_folder, 150 | height=height, width=width) 151 | if dataset == "isic_notest": # previous validation split will be used for training 152 | train_list = train_list + val_list 153 | val_list = test_list 154 | elif dataset=="isic_noval_notest": # previous validation/test splits will be used for training 155 | monitor_metric = 'jacc_coef' 156 | train_list = train_list + val_list + test_list 157 | val_list = test_list 158 | elif dataset=="isic_other_split": # different split, uses previous val/test for training 159 | seed = 82 160 | train_list1, train_list2, train_label1, train_label2 = ISIC.train_val_split(train_list, train_label, seed=seed, val_split=0.30) 161 | train_list = val_list+test_list+train_list1 162 | val_list = train_list2 163 | test_list = val_list 164 | n_samples = len(train_list) 165 | 166 | print "Loading images" 167 | train, train_mask = ISIC.load_images(train_list, height, width, 168 | image_folder, mask_folder, 169 | remove_mean_imagenet=remove_mean_imagenet, 170 | rescale_mask=rescale_mask, use_hsv=use_hsv, remove_mean_samplewise=remove_mean_samplewise) 171 | val, val_mask = ISIC.load_images(val_list, height, width, 172 | image_folder, mask_folder, 173 | remove_mean_imagenet=remove_mean_imagenet, 174 | rescale_mask=rescale_mask, use_hsv=use_hsv, remove_mean_samplewise=remove_mean_samplewise) 175 | test, test_mask = ISIC.load_images(test_list, height, width, 176 | image_folder, mask_folder, 177 | remove_mean_imagenet=remove_mean_imagenet, 178 | rescale_mask=rescale_mask, use_hsv=use_hsv, remove_mean_samplewise=remove_mean_samplewise) 179 | print "Done loading images" 180 | 181 | if remove_mean_dataset: 182 | print "\nUsing Train Mean: {} Std: {}".format(train_mean, train_std) 183 | train = (train-train_mean)/train_std 184 | val = (val-train_mean)/train_std 185 | test = (test-train_mean)/train_std 186 | 187 | print "Using batch size = {}".format(batch_size) 188 | print 'Fit model' 189 | model_checkpoint = ModelCheckpoint(model_filename, monitor=monitor_metric, save_best_only=True, verbose=1) 190 | data_gen_args = dict(featurewise_center=False, 191 | samplewise_center=remove_mean_samplewise, 192 | featurewise_std_normalization=False, 193 | samplewise_std_normalization=False, 194 | zca_whitening=False, 195 | rotation_range=270, 196 | width_shift_range=0.1, 197 | height_shift_range=0.1, 198 | horizontal_flip=False, 199 | vertical_flip=False, 200 | zoom_range=0.2, 201 | channel_shift_range=0, 202 | fill_mode='reflect', 203 | dim_ordering=K.image_dim_ordering()) 204 | data_gen_mask_args = dict(data_gen_args.items() + {'fill_mode':'nearest','samplewise_center':False}.items()) 205 | print "Create Data Generator" 206 | train_datagen = ImageDataGenerator(**data_gen_args) 207 | train_mask_datagen = ImageDataGenerator(**data_gen_mask_args) 208 | train_generator = train_datagen.flow(train, batch_size=batch_size, seed=seed) 209 | train_mask_generator = train_mask_datagen.flow(train_mask, batch_size=batch_size, seed=seed) 210 | train_generator_f = myGenerator(train_generator, train_mask_generator, 211 | remove_mean_imagenet=remove_mean_imagenet, 212 | rescale_mask=rescale_mask, use_hsv=use_hsv) 213 | 214 | if dataset=="isic_noval_notest": 215 | print "Not using validation during training" 216 | history = model.fit_generator( 217 | train_generator_f, 218 | samples_per_epoch=n_samples, 219 | nb_epoch=nb_epoch, 220 | callbacks=[model_checkpoint], initial_epoch=initial_epoch) 221 | else: 222 | history = model.fit_generator( 223 | train_generator_f, 224 | samples_per_epoch=n_samples, 225 | nb_epoch=nb_epoch, validation_data=(val,val_mask), 226 | callbacks=[model_checkpoint], initial_epoch=initial_epoch) 227 | 228 | train = None; train_mask = None # clear memory 229 | print "Load best checkpoint" 230 | model.load_weights(model_filename) # load best saved checkpoint 231 | 232 | # evaluate model 233 | mask_pred_val = model.predict(val) 234 | mask_pred_test = model.predict(test) 235 | for pixel_threshold in [0.5]: #np.arange(0.3,1,0.05): 236 | mask_pred_val = np.where(mask_pred_val>=pixel_threshold, 1, 0) 237 | mask_pred_val = mask_pred_val * 255 238 | mask_pred_val = mask_pred_val.astype(np.uint8) 239 | print "Validation Predictions Max: {}, Min: {}".format(np.max(mask_pred_val), np.min(mask_pred_val)) 240 | print model.evaluate(val, val_mask, batch_size = batch_size, verbose=1) 241 | dice, jacc = dice_jacc_mean(val_mask, mask_pred_val, smooth = 0) 242 | print model_filename 243 | print "Resized val dice coef : {:.4f}".format(dice) 244 | print "Resized val jacc coef : {:.4f}".format(jacc) 245 | 246 | mask_pred_test = np.where(mask_pred_test>=pixel_threshold, 1, 0) 247 | mask_pred_test = mask_pred_test * 255 248 | mask_pred_test = mask_pred_test.astype(np.uint8) 249 | print model.evaluate(test, test_mask, batch_size = batch_size, verbose=1) 250 | dice, jacc = dice_jacc_mean(test_mask, mask_pred_test, smooth = 0) 251 | print "Resized test dice coef : {:.4f}".format(dice) 252 | print "Resized test jacc coef : {:.4f}".format(jacc) 253 | else: 254 | print 'Load model' 255 | model.load_weights(model_filename) 256 | 257 | def predict_challenge(challenge_folder, challenge_predicted_folder, mask_pred_challenge=None, plot=True): 258 | challenge_list = ISIC.list_from_folder(challenge_folder) 259 | challenge_resized_folder = challenge_folder+"_{}_{}".format(height,width) 260 | 261 | if not os.path.exists(challenge_resized_folder): 262 | ISIC.resize_images(challenge_list, input_image_folder=challenge_folder, input_mask_folder=None, 263 | output_image_folder=challenge_resized_folder, output_mask_folder=None, 264 | height=height, width=width) 265 | 266 | challenge_resized_list = [name.split(".")[0]+".png" for name in challenge_list] 267 | challenge_images = ISIC.load_images(challenge_resized_list, 268 | height, width, image_folder=challenge_resized_folder, 269 | mask_folder=None, remove_mean_imagenet=True, use_hsv = use_hsv,remove_mean_samplewise=remove_mean_samplewise) 270 | 271 | if remove_mean_dataset: 272 | challenge_images = (challenge_images-train_mean)/train_std 273 | if mask_pred_challenge is None: 274 | mask_pred_challenge = model.predict(challenge_images) 275 | with open('{}.pkl'.format(os.path.join(challenge_predicted_folder,model_name)), 'wb') as f: 276 | pkl.dump(mask_pred_challenge, f) 277 | mask_pred_challenge = np.where(mask_pred_challenge>=0.5, 1, 0) 278 | mask_pred_challenge = mask_pred_challenge * 255 279 | mask_pred_challenge = mask_pred_challenge.astype(np.uint8) 280 | 281 | challenge_predicted_folder = os.path.join(challenge_predicted_folder, model_name) 282 | if not os.path.exists(challenge_predicted_folder): 283 | os.makedirs(challenge_predicted_folder) 284 | 285 | print "Start challenge prediction:" 286 | 287 | for i in range(len(challenge_list)): 288 | print "{}: {}".format(i, challenge_list[i]) 289 | ISIC.show_images_full_sized(image_list = challenge_list, img_mask_pred_array = mask_pred_challenge, 290 | image_folder=challenge_folder, mask_folder=None, index = i, output_folder=challenge_predicted_folder, plot=plot) 291 | 292 | def join_predictions(pkl_folder, pkl_files, binary=False, threshold=0.5): 293 | n_pkl = float(len(pkl_files)) 294 | array = None 295 | for fname in pkl_files: 296 | with open(os.path.join(pkl_folder,fname+".pkl"), "rb") as f: 297 | tmp = pkl.load(f) 298 | if binary: 299 | tmp = np.where(tmp>=threshold, 1, 0) 300 | if array is None: 301 | array = tmp 302 | else: 303 | array = array + tmp 304 | return array/n_pkl 305 | 306 | if do_predict: 307 | # free memory 308 | train = None 309 | train_mask = None 310 | val = None 311 | test = None 312 | 313 | print "Start Challenge Validation" 314 | predict_challenge(challenge_folder=validation_folder, challenge_predicted_folder=validation_predicted_folder, plot=False) 315 | print "Start Challenge Test" 316 | predict_challenge(challenge_folder=test_folder, challenge_predicted_folder=test_predicted_folder, plot=False) 317 | 318 | if do_ensemble: 319 | threshold = 0.5 320 | binary = False 321 | val_array = join_predictions(pkl_folder = validation_predicted_folder, pkl_files=ensemble_pkl_filenames, binary=binary, threshold=threshold) 322 | test_array = join_predictions(pkl_folder = test_predicted_folder, pkl_files=ensemble_pkl_filenames, binary=binary, threshold=threshold) 323 | model_name="ensemble_{}".format(threshold) 324 | for f in ensemble_pkl_filenames: 325 | model_name = model_name + "_" + f 326 | print "Predict Validation:" 327 | plot = True 328 | predict_challenge(challenge_folder=validation_folder, challenge_predicted_folder=validation_predicted_folder, 329 | mask_pred_challenge=val_array, plot=plot) 330 | print "Predict Test:" 331 | plot = False 332 | predict_challenge(challenge_folder=test_folder, challenge_predicted_folder=test_predicted_folder, 333 | mask_pred_challenge=test_array, plot=plot) 334 | -------------------------------------------------------------------------------- /weights/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/learningtitans/isbi2017-part1/b5dbaa8d10663b88fbbe5a2cbe933ff13584277a/weights/.gitkeep --------------------------------------------------------------------------------