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