DDMR / EvaluationScripts /Evaluate_class.py
andreped's picture
Renamed module to ddmr
a27d55f
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)