a b/scripts/test/evaluate.py
1
#==============================================================================#
2
#  Author:       Dominik Müller                                                #
3
#  Copyright:    2020 IT-Infrastructure for Translational Medical Research,    #
4
#                University of Augsburg                                        #
5
#                                                                              #
6
#  This program is free software: you can redistribute it and/or modify        #
7
#  it under the terms of the GNU General Public License as published by        #
8
#  the Free Software Foundation, either version 3 of the License, or           #
9
#  (at your option) any later version.                                         #
10
#                                                                              #
11
#  This program is distributed in the hope that it will be useful,             #
12
#  but WITHOUT ANY WARRANTY; without even the implied warranty of              #
13
#  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the               #
14
#  GNU General Public License for more details.                                #
15
#                                                                              #
16
#  You should have received a copy of the GNU General Public License           #
17
#  along with this program.  If not, see <http://www.gnu.org/licenses/>.       #
18
#==============================================================================#
19
#-----------------------------------------------------#
20
#                   Library imports                   #
21
#-----------------------------------------------------#
22
import matplotlib.pyplot as plt
23
import matplotlib.animation as animation
24
import numpy as np
25
import pandas as pd
26
import os
27
from tqdm import tqdm
28
from miscnn.data_loading.interfaces import NIFTI_interface
29
from miscnn import Data_IO
30
from miscnn.evaluation.cross_validation import load_disk2fold
31
from plotnine import *
32
import argparse
33
34
#-----------------------------------------------------#
35
#                      Argparser                      #
36
#-----------------------------------------------------#
37
parser = argparse.ArgumentParser(description="Automated COVID-19 Segmentation")
38
parser.add_argument("-p", "--predictions", help="Path to predictions directory",
39
                    required=True, type=str, dest="pred")
40
args = parser.parse_args()
41
pred_path = args.pred
42
os.environ["CUDA_VISIBLE_DEVICES"] = "1"
43
eval_path = "evaluation.testing"
44
45
#-----------------------------------------------------#
46
#                  Score Calculations                 #
47
#-----------------------------------------------------#
48
def calc_DSC(truth, pred, classes):
49
    dice_scores = []
50
    # Iterate over each class
51
    for i in range(classes):
52
        try:
53
            gt = np.equal(truth, i)
54
            pd = np.equal(pred, i)
55
            # Calculate Dice
56
            dice = 2*np.logical_and(pd, gt).sum() / (pd.sum() + gt.sum())
57
            dice_scores.append(dice)
58
        except ZeroDivisionError:
59
            dice_scores.append(0.0)
60
    # Return computed Dice Similarity Coefficients
61
    return dice_scores
62
63
def calc_IoU(truth, pred, classes):
64
    iou_scores = []
65
    # Iterate over each class
66
    for i in range(classes):
67
        try:
68
            gt = np.equal(truth, i)
69
            pd = np.equal(pred, i)
70
            # Calculate iou
71
            iou = np.logical_and(pd, gt).sum() / (pd.sum() + gt.sum() - np.logical_and(pd, gt).sum())
72
            iou_scores.append(iou)
73
        except ZeroDivisionError:
74
            iou_scores.append(0.0)
75
    # Return computed IoU
76
    return iou_scores
77
78
def calc_Sensitivity(truth, pred, classes):
79
    sens_scores = []
80
    # Iterate over each class
81
    for i in range(classes):
82
        try:
83
            gt = np.equal(truth, i)
84
            pd = np.equal(pred, i)
85
            # Calculate sensitivity
86
            sens = np.logical_and(pd, gt).sum() / gt.sum()
87
            sens_scores.append(sens)
88
        except ZeroDivisionError:
89
            sens_scores.append(0.0)
90
    # Return computed sensitivity scores
91
    return sens_scores
92
93
def calc_Specificity(truth, pred, classes):
94
    spec_scores = []
95
    # Iterate over each class
96
    for i in range(classes):
97
        try:
98
            not_gt = np.logical_not(np.equal(truth, i))
99
            not_pd = np.logical_not(np.equal(pred, i))
100
            # Calculate specificity
101
            spec = np.logical_and(not_pd, not_gt).sum() / (not_gt).sum()
102
            spec_scores.append(spec)
103
        except ZeroDivisionError:
104
            spec_scores.append(0.0)
105
    # Return computed specificity scores
106
    return spec_scores
107
108
def calc_Accuracy(truth, pred, classes):
109
    acc_scores = []
110
    # Iterate over each class
111
    for i in range(classes):
112
        try:
113
            gt = np.equal(truth, i)
114
            pd = np.equal(pred, i)
115
            not_gt = np.logical_not(np.equal(truth, i))
116
            not_pd = np.logical_not(np.equal(pred, i))
117
            # Calculate accuracy
118
            acc = (np.logical_and(pd, gt).sum() + \
119
                   np.logical_and(not_pd, not_gt).sum()) /  gt.size
120
            acc_scores.append(acc)
121
        except ZeroDivisionError:
122
            acc_scores.append(0.0)
123
    # Return computed accuracy scores
124
    return acc_scores
125
126
def calc_Precision(truth, pred, classes):
127
    prec_scores = []
128
    # Iterate over each class
129
    for i in range(classes):
130
        try:
131
            gt = np.equal(truth, i)
132
            pd = np.equal(pred, i)
133
            # Calculate precision
134
            prec = np.logical_and(pd, gt).sum() / pd.sum()
135
            prec_scores.append(prec)
136
        except ZeroDivisionError:
137
            prec_scores.append(0.0)
138
    # Return computed precision scores
139
    return prec_scores
140
141
#-----------------------------------------------------#
142
#                    Run Evaluation                   #
143
#-----------------------------------------------------#
144
# Initialize Data IO Interface for NIfTI data
145
## We are using 4 classes due to [background, lung_left, lung_right, covid-19]
146
interface = NIFTI_interface(channels=1, classes=4)
147
148
# Create Data IO object to load and write samples in the file structure
149
data_io = Data_IO(interface, input_path="data.testing", output_path=pred_path)
150
151
# Access all available samples in our file structure
152
sample_list = data_io.get_indiceslist()
153
sample_list.sort()
154
155
# Initialize dataframe
156
cols = ["index", "score", "background", "infection"]
157
df = pd.DataFrame(data=[], dtype=np.float64, columns=cols)
158
159
# Iterate over each sample
160
for index in tqdm(sample_list):
161
    # Load a sample including its image, ground truth and prediction
162
    sample = data_io.sample_loader(index, load_seg=True, load_pred=True)
163
    # Access image, ground truth and prediction data
164
    image = sample.img_data
165
    truth = sample.seg_data
166
    pred = sample.pred_data
167
168
    pred = np.where(pred==1, 0, pred)
169
    pred = np.where(pred==2, 0, pred)
170
    pred = np.where(pred==3, 1, pred)
171
172
    # Compute diverse Scores
173
    dsc = calc_DSC(truth, pred, classes=2)
174
    df = df.append(pd.Series([index, "DSC"] + dsc, index=cols),
175
                   ignore_index=True)
176
    iou = calc_IoU(truth, pred, classes=2)
177
    df = df.append(pd.Series([index, "IoU"] + iou, index=cols),
178
                   ignore_index=True)
179
    sens = calc_Sensitivity(truth, pred, classes=2)
180
    df = df.append(pd.Series([index, "Sens"] + sens, index=cols),
181
                   ignore_index=True)
182
    spec = calc_Specificity(truth, pred, classes=2)
183
    df = df.append(pd.Series([index, "Spec"] + spec, index=cols),
184
                   ignore_index=True)
185
    prec = calc_Precision(truth, pred, classes=2)
186
    df = df.append(pd.Series([index, "Prec"] + prec, index=cols),
187
                   ignore_index=True)
188
    acc = calc_Accuracy(truth, pred, classes=2)
189
    df = df.append(pd.Series([index, "Acc"] + acc, index=cols),
190
                   ignore_index=True)
191
192
# Output complete dataframe
193
print(df)
194
# Create evaluation directory
195
if not os.path.exists(eval_path) : os.mkdir(eval_path)
196
# Identify cv & fold
197
id = pred_path.split(".")[-1]
198
# Store complete dataframe to disk
199
path_res_detailed = os.path.join(eval_path, "results." + id + ".csv")
200
df.to_csv(path_res_detailed, index=False)
201
202
# Print out average, median std evaluation metrics for the current fold
203
df_avg = df.groupby(by="score").mean()
204
path_out = os.path.join(eval_path, "results." + id + ".mean.csv")
205
df_avg.to_csv(path_out, index=True)
206
df_med = df.groupby(by="score").median()
207
path_out = os.path.join(eval_path, "results." + id + ".median.csv")
208
df_med.to_csv(path_out, index=True)
209
df_std = df.groupby(by="score").std()
210
path_out = os.path.join(eval_path, "results." + id + ".std.csv")
211
df_std.to_csv(path_out, index=True)