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)