|
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 |