File size: 7,765 Bytes
6a4f823 a27d55f 6a4f823 |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 |
from scipy.signal import correlate as cc
from scipy.spatial.distance import euclidean
from skimage.metrics import mean_squared_error as mse
from skimage.metrics import structural_similarity as ssim
from medpy.metric.binary import dc, hd95
import numpy as np
import pandas as pd
import os
from ddmr.utils.constants import EPS
from ddmr.utils.nifti_utils import save_nifti
from skimage.transform import resize
from skimage.measure import regionprops, label
def ncc(y_true, y_pred, eps=EPS):
f_yt = np.reshape(y_true, [-1])
f_yp = np.reshape(y_pred, [-1])
mean_yt = np.mean(f_yt)
mean_yp = np.mean(f_yp)
n_f_yt = f_yt - mean_yt
n_f_yp = f_yp - mean_yp
norm_yt = np.linalg.norm(f_yt, ord=2)
norm_yp = np.linalg.norm(f_yp, ord=2)
numerator = np.sum(np.multiply(n_f_yt, n_f_yp))
denominator = norm_yt * norm_yp + eps
return np.divide(numerator, denominator)
class EvaluationFigures:
def __init__(self, output_folder):
pd.set_option('display.max_columns', None)
self.__metrics_df = pd.DataFrame(columns=['Name', 'Train_ds', 'Eval_ds', 'MSE', 'NCC', 'SSIM',
'DICE_PAR', 'DICE_TUM', 'DICE_VES',
'HD95_PAR', 'HD95_TUM', 'HD95_VES', 'TRE'])
self.__output_folder = output_folder
def add_sample(self, name, train_ds, eval_ds, fix_t, par_t, tum_t, ves_t, centroid_t, fix_p, par_p, tum_p, ves_p, centroid_p, scale_transform=None):
n_fix_t = self.__mean_centred_img(fix_t)
n_fix_p = self.__mean_centred_img(fix_p)
if scale_transform is not None:
s_centroid_t = self.__scale_point(centroid_t, scale_transform)
s_centroid_p = self.__scale_point(centroid_p, scale_transform)
else:
s_centroid_t = centroid_t
s_centroid_p = centroid_p
new_row = {'Name': name,
'Train_ds': train_ds,
'Eval_ds': eval_ds,
'MSE': mse(fix_t, fix_p),
'NCC': ncc(n_fix_t, n_fix_p),
'SSIM': ssim(fix_t, fix_p, multichannel=True),
'DICE_PAR': dc(par_p, par_t),
'DICE_TUM': dc(tum_p, tum_t),
'DICE_VES': dc(ves_p, ves_t),
'HD95_PAR': hd95(par_p, par_t) if np.sum(par_p) else 64,
'HD95_TUM': hd95(tum_p, tum_t) if np.sum(tum_p) else 64,
'HD95_VES': hd95(ves_p, ves_t) if np.sum(ves_p) else 64,
'TRE': euclidean(s_centroid_t, s_centroid_p)}
self.__metrics_df = self.__metrics_df.append(new_row, ignore_index=True)
@staticmethod
def __mean_centred_img(img):
return img - np.mean(img)
@staticmethod
def __scale_point(point, scale_matrix):
assert scale_matrix.shape == (4, 4), 'Transformation matrix is expected to have shape (4, 4)'
aux_aug = np.ones((4,))
aux_aug[:3] = point
return np.matmul(scale_matrix, aux_aug)[:1]
def save_metrics(self, dest_folder=None):
if dest_folder is None:
dest_folder = self.__output_folder
self.__metrics_df.to_csv(os.path.join(dest_folder, 'metrics.csv'))
self.__metrics_df.to_latex(os.path.join(dest_folder, 'table.txt'), sparsify=True)
print('Metrics saved in: ' + os.path.join(dest_folder))
def print_summary(self):
print(self.__metrics_df[['MSE', 'NCC', 'SSIM',
'DICE_PAR', 'DICE_TUM', 'DICE_VES',
'HD95_PAR', 'HD95_TUM', 'HD95_VES', 'TRE']].describe())
def resize_img_to_original_space(img, bb, first_reshape, original_shape, clip_img=False, flow=False):
first_reshape = first_reshape.astype(int)
bb = bb.astype(int)
original_shape = original_shape.astype(int)
if flow:
# Multiply before resizing to reduce the number of multiplications
img = _rescale_flow_values(img, bb, img.shape, first_reshape, original_shape)
min_i, min_j, min_k, bb_i, bb_j, bb_k = bb
max_i = min_i + bb_i
max_j = min_j + bb_j
max_k = min_k + bb_k
img_bb = resize(img, (bb_i, bb_j, bb_k)) # Get the original bounding box shape
# Place the bounding box again in the cubic volume
img_copy = np.zeros((*first_reshape, img.shape[-1]) if len(img.shape) > 3 else first_reshape) # Get channels if any
img_copy[min_i:max_i, min_j:max_j, min_k:max_k, ...] = img_bb
# Now resize to the original shape
resized_img = resize(img_copy, original_shape, preserve_range=True, anti_aliasing=False)
if clip_img or flow:
# clip_mask = np.zeros(img_copy.shape[:3], np.int)
# clip_mask[min_i:max_i, min_j:max_j, min_k:max_k] = 1
# clip_mask = resize(clip_mask, original_shape, preserve_range=True, anti_aliasing=False)
# clip_mask[clip_mask > 0.5] = 1
# clip_mask[clip_mask < 1] = 0
#
# [min_i, min_j, min_k, max_i, max_j, max_k] = regionprops(label(clip_mask))[0].bbox
#
# resized_img = resized_img[min_i:max_i, min_j:max_j, min_k:max_k, ...]
# Compute the coordinates of the boundix box in the upsampled volume, instead of resizing a mask image
S = resize_transformation(img.shape, bb=None, first_reshape=first_reshape, original_shape=original_shape, translate=True)
bb_coords = np.asarray([[min_i, min_j, min_k], [max_i, max_j, max_k]])
bb_coords = np.hstack([bb_coords, np.ones((2, 1))])
upsamp_bbox_coords = np.around(np.matmul(S, bb_coords.T)[:-1, :].T).astype(np.int)
min_i = upsamp_bbox_coords[0][0]
min_j = upsamp_bbox_coords[0][1]
min_k = upsamp_bbox_coords[0][2]
max_i = upsamp_bbox_coords[1][0]
max_j = upsamp_bbox_coords[1][1]
max_k = upsamp_bbox_coords[1][2]
resized_img = resized_img[min_i:max_i, min_j:max_j, min_k:max_k, ...]
if flow:
# Return also the origin of the bb in the resized volume for the following interpolation
return resized_img, np.asarray([min_i, min_j, min_k])
return resized_img
# This is supposed to be an isotropic image with voxel size 1 mm
def _rescale_flow_values(flow, bb, current_img_shape, first_reshape, original_shape):
S = resize_transformation(current_img_shape, bb, first_reshape, original_shape, translate=False)
[si, sj, sk] = np.diag(S[:3, :3])
flow[..., 0] *= si
flow[..., 1] *= sj
flow[..., 2] *= sk
return flow
def resize_pts_to_original_space(pt, bb, current_img_shape, first_reshape, original_shape):
T = resize_transformation(current_img_shape, bb, first_reshape, original_shape)
if len(pt.shape) > 1:
pt_aug = np.ones((4, pt.shape[0]))
pt_aug[0:3, :] = pt.T
else:
pt_aug = np.ones((4,))
pt_aug[0:3] = pt
trf_pt = np.matmul(T, pt_aug)[:-1, ...].T
return trf_pt
def resize_transformation(current_img_shape, bb=None, first_reshape=None, original_shape=None, translate=True):
first_reshape = first_reshape.astype(int)
original_shape = original_shape.astype(int)
first_resize_trf = np.eye(4)
if bb is not None:
bb = bb.astype(int)
min_i, min_j, min_k, bb_i, bb_j, bb_k = bb
np.fill_diagonal(first_resize_trf, [bb_i / current_img_shape[0], bb_j / current_img_shape[1], bb_k / current_img_shape[2], 1])
if translate:
first_resize_trf[:3, -1] = np.asarray([min_i, min_j, min_k])
original_resize_trf = np.eye(4)
np.fill_diagonal(original_resize_trf, [original_shape[0] / first_reshape[0], original_shape[1] / first_reshape[1], original_shape[2] / first_reshape[2], 1])
return np.matmul(original_resize_trf, first_resize_trf)
|