a b/utils.py
1
import glob
2
import matplotlib.patches as patches
3
import json
4
import numpy as np
5
from matplotlib.path import Path
6
import dicom
7
from sklearn.utils import shuffle
8
import cv2
9
10
def get_roi(image, contour, shape_out = 32):
11
    """
12
    Create a binary mask with ROI from contour. 
13
    Extract the maximum square around the contour.
14
    :param image: input image (needed for shape only)
15
    :param contour: numpy array contour (d, 2)
16
    :return: numpy array mask ROI (shape_out, shape_out)
17
    """
18
    X_min, Y_min = contour[:,0].min(), contour[:,1].min()
19
    X_max, Y_max = contour[:,0].max(), contour[:,1].max()  
20
    w = X_max - X_min
21
    h = Y_max - Y_min
22
    mask_roi = np.zeros(image.shape)
23
    if w > h :
24
        mask_roi[int(Y_min - (w -h)/2):int(Y_max + (w -h)/2), int(X_min):int(X_max)] = 1.0
25
    else :
26
        mask_roi[int(Y_min):int(Y_max), int(X_min - (h-w)/2):int(X_max + (h -w)/2)] = 1.0
27
    return cv2.resize(mask_roi, (shape_out, shape_out), interpolation = cv2.INTER_NEAREST)
28
29
def create_dataset(image_shape=64, n_set='train', original_image_shape=256, 
30
                   roi_shape=32, data_path='./Data/'):
31
    """
32
    Creating the dataset from the images and the contour for the CNN.
33
    :param image_shape: image dataset desired size
34
    :param original_image_shape: original image size
35
    :param roi_shape: binary ROI mask shape
36
    :param data_path: path for the dataset
37
    :return: correct size image dataset, full size image dataset, label (contours) dataset
38
    """
39
    
40
    if n_set == 'train':
41
        number_set = 3
42
        name_set = 'Training'
43
    elif n_set == 'test':
44
        number_set = 1
45
        name_set = 'Online'         
46
    # Create dataset
47
    series = json.load(open('series_case.json'))[n_set]
48
    images, images_fullsize, contours, contour_mask = [], [], [], []
49
    # Loop over the series
50
    for case, serie in series.items():
51
        image_path_base = data_path + 'challenge_%s/%s/IM-%s' % (name_set.lower(),case, serie)
52
        contour_path_base = data_path + 'Sunnybrook Cardiac MR Database ContoursPart%s/\
53
%sDataContours/%s/contours-manual/IRCCI-expert/' % (number_set, name_set, case)
54
        contours_list = glob.glob(contour_path_base + '*')
55
        contours_list_series = [k.split('/')[7].split('-')[2] for k in contours_list]
56
        # Loop over the contours/images
57
        for c in contours_list_series:
58
            # Get contours and images path
59
            idx_contour = contours_list_series.index(c)
60
            image_path = image_path_base + '-%s.dcm' % c
61
            contour_path = contours_list[idx_contour]
62
63
            # open image as numpy array and resize to (image_shape, image_shape)
64
            image_part = dicom.read_file(image_path).pixel_array  
65
66
            # open contours as numpy array
67
            contour = []
68
            file = open(contour_path, 'r') 
69
            for line in file: 
70
                contour.append(tuple(map(float, line.split())))
71
            contour = np.array(contour)
72
            # append binary ROI mask 
73
            contours.append(get_roi(image_part, contour))
74
75
            # create mask contour with experts contours
76
            x, y = np.meshgrid(np.arange(256), np.arange(256)) # make a canvas with coordinates
77
            x, y = x.flatten(), y.flatten()
78
            points = np.vstack((x,y)).T 
79
            p = Path(contour) # make a polygon
80
            grid = p.contains_points(points)
81
            mask_contour = grid.reshape(256,256)
82
            mask_contour=mask_contour*1
83
            contour_mask.append(mask_contour)
84
            
85
            # Open image and resize it 
86
            images.append(cv2.resize(image_part, (image_shape, image_shape)))
87
            images_fullsize.append(cv2.resize(image_part, (original_image_shape, original_image_shape)))
88
    X_fullsize = np.array(images_fullsize)
89
    X = np.reshape(np.array(images), [len(images), image_shape, image_shape, 1])
90
    Y = np.reshape(np.array(contours), [len(contours), 1, roi_shape, roi_shape])
91
    print('Dataset shape :', X.shape, Y.shape)
92
    return shuffle(X, X_fullsize, Y, contour_mask, random_state=0)
93
94
def compute_roi_pred(X_fullsize, y_pred, contour_mask, idx, roi_shape=32):
95
    """
96
    Computing and cropping a ROI from the original image for further processing in the next stage
97
    :param X_fullsize: full size training set (256x256)
98
    :param y_pred: predictions
99
    :param contour_mask label: (contours) dataset
100
    :param idx: desired image prediction index
101
    :param roi_shape: shape of the binary mask
102
    """
103
    # up sampling from 32x32 to original MR size
104
    pred = cv2.resize(y_pred[idx].reshape((roi_shape, roi_shape)), (256,256), cv2.INTER_NEAREST)
105
    # select the non null pixels
106
    pos_pred = np.array(np.where(pred > 0.5))
107
    # get the center of the mask
108
    X_min, Y_min = pos_pred[0, :].min(), pos_pred[1, :].min()
109
    X_max, Y_max = pos_pred[0, :].max(), pos_pred[1, :].max()  
110
    X_middle = X_min + (X_max - X_min) / 2
111
    Y_middle = Y_min + (Y_max - Y_min) / 2
112
    # Find ROI coordinates
113
    X_top = int(X_middle - 50)
114
    Y_top = int(Y_middle - 50)
115
    X_down = int(X_middle + 50)
116
    Y_down = int(Y_middle + 50)
117
    # crop ROI of size 100x100
118
    mask_roi = np.zeros((256, 256))
119
    mask_roi = cv2.rectangle(mask_roi, (X_top, Y_top), (X_down, Y_down), 1, -1)*255
120
    return X_fullsize[idx][X_top:X_down, Y_top:Y_down], mask_roi, contour_mask[idx][X_top:X_down, Y_top:Y_down]
121
122
def prediction_plot(X, model, idx=None):
123
    """
124
    Compute the Inferred shape binary mask using the trained stacked AE model
125
    :param X: dataset to predict
126
    :param model: trained AE model
127
    :param idx: index of the particular picture to return
128
    :return: inferred shape binary mask, infered shape on the MR image
129
    """
130
    if not idx:
131
        idx= np.random.randint(len(X))
132
    contours = model.predict(X)
133
    contour = contours[idx].reshape((64,64))
134
    # thresholding
135
    binary = cv2.threshold(contour, 0, 1, cv2.INTERSECT_NONE)
136
    return binary[1], binary[1]*X[idx].reshape(64,64), idx
137
138
def dice_metric(X, Y):
139
    """
140
    Dice metric for measuring the contour overlap 
141
    :param X,Y: 2D numpy arrays
142
    :return: metric scalar
143
    """
144
    return np.sum(X[Y==1])*2.0 / (np.sum(X) + np.sum(Y))
145
146
def conformity_coefficient(X, Y):
147
    """
148
    Conformity coefficient for measuring the  ratio of the number of mis-segmented pixels  
149
    :param X,Y: 2D numpy arrays
150
    :return: metric scalar
151
    """
152
    if dice_metric(X,Y) !=0:
153
        return (3*dice_metric(X,Y)-2)/dice_metric(X,Y)
154
155
def stats_results(Y_true, Y_pred, idx=None, print_=False):
156
    """
157
    DM and CC for the whole dataset. If one index is passed, return DM and CC for this index
158
    :param Y_true: True labels (Y_train)
159
    :param Y_pred: Prediction (binarys)
160
    :param print_: Boolean to print stats
161
    :return: DM and CC arrays
162
    """
163
    dm_tot = np.array([dice_metric(Y_true[k].reshape((64,64)), Y_pred[k]) for k in range(len(Y_true))])
164
    cc_tot = np.array([conformity_coefficient(Y_true[k].reshape((64,64)), Y_pred[k]) for k in range(len(Y_true))])
165
    cc_tot = cc_tot[cc_tot != None]
166
    #return dm_tot, cc_tot
167
    if idx:
168
        print('For image %s :\nDice Metric : %.2f\nConformity Coefficient : %.2f\n' % (
169
                idx, dice_metric(Y_true[idx].reshape((64,64)), Y_pred[idx]),
170
                conformity_coefficient(Y_true[idx].reshape((64,64)), Y_pred[idx])))
171
172
    if print_:
173
        print('For the full dataset :\nDice Metric : %.2f\nConformity Coefficient : %.2f' % (
174
                                            dm_tot.mean(),cc_tot.mean()))
175
    return dm_tot, cc_tot