Skip to content

Improve metrics calculation #14

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 30 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
30 commits
Select commit Hold shift + click to select a range
6724577
Add metrics
Oct 6, 2023
6c91f85
Update parser
Oct 6, 2023
27f670f
Calcul métrique MSE
moranebienvenu Oct 26, 2023
8ec79eb
Calcul MSE
moranebienvenu Oct 26, 2023
8f502a8
Création de graphes violin pour chaque metric
moranebienvenu Oct 26, 2023
f2a9929
Génération automatique de graphique
moranebienvenu Nov 6, 2023
587204a
Génération automatique de graphique pour chaque métriques
moranebienvenu Nov 6, 2023
d5cdb66
Génération graphique bar + violin
moranebienvenu Nov 14, 2023
f113ca4
garder nnunet_coords
moranebienvenu Nov 27, 2023
be1b237
Valeur pour les courbes ROC
moranebienvenu Nov 27, 2023
cbdf0c3
Appel des graphs bars
moranebienvenu Nov 27, 2023
e5c2039
Graphique bar
moranebienvenu Nov 27, 2023
969bfd2
Graphiques Violin
moranebienvenu Nov 27, 2023
dcbacec
Fonction pour courbe ROC
moranebienvenu Nov 27, 2023
81d147f
ajout des noms de méthodes
moranebienvenu Nov 27, 2023
6cdfcea
Utils
moranebienvenu Nov 27, 2023
9e8d3e1
Filtre des méthodes nnunet
moranebienvenu Nov 27, 2023
fcc19f7
Compute TPR and FPR metrics
moranebienvenu Nov 27, 2023
1a41f6c
Add Acc metric in result_dict
moranebienvenu Nov 27, 2023
ade00d2
code mis a jour
moranebienvenu Nov 27, 2023
46f4792
clean code
NathanMolinier Nov 27, 2023
9f5cec5
add new methods to default
NathanMolinier Nov 27, 2023
459b8c8
remove lines
NathanMolinier Nov 27, 2023
94fc2de
add spaces
NathanMolinier Nov 27, 2023
222d74b
deal with default values
NathanMolinier Nov 27, 2023
91d43ce
reduce hourglass name
NathanMolinier Nov 27, 2023
358b3f7
improve violin code
NathanMolinier Nov 27, 2023
683de81
comment not working plot
NathanMolinier Nov 27, 2023
c740274
use sensitivity and specificity
NathanMolinier Nov 29, 2023
969d0e4
Fix unit
NathanMolinier Nov 29, 2023
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
21 changes: 21 additions & 0 deletions computed_metrics_T1w.csv

Large diffs are not rendered by default.

98 changes: 98 additions & 0 deletions computed_metrics_T2w.csv

Large diffs are not rendered by default.

2,168 changes: 2,168 additions & 0 deletions discs_coords.txt

Large diffs are not rendered by default.

1 change: 0 additions & 1 deletion requirements.txt
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,6 @@ numpy
progress
torch
torchvision
opencv-python
scikit-image
transforms3d
progress
Expand Down
337 changes: 289 additions & 48 deletions src/bcm/run/compute_disc_labeling_comparison.py

Large diffs are not rendered by default.

25 changes: 24 additions & 1 deletion src/bcm/utils/metrics.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@

# Metrics computation
import numpy as np

#from sklearn.metrics import mean_squared_error

# looks for the closest points between real and predicted
def closest_node(node, nodes):
Expand Down Expand Up @@ -92,3 +92,26 @@ def compute_TN_and_FN(missing_gt, missing_pred):
return TN, FN, np.array(false_neg_list)


######## ajout ci-bas___ Morane

#Compute MSE
def compute_MSE(pred,gt):
#mse = mean_squared_error(pred_mask, gt_mask) #Les arguments d'entrée doivent être des tableaux
'''
Compute Mean Squared Error (MSE) between ground truth and predicted coordinates.

gt: numpy array with the ground truth coords of the discs
pred: numpy array with the prediction coords of the discs

Returns: MSE value
'''
# Calculate L2 errors
l2_errors = np.array([np.linalg.norm(gt[i] - pred[i]) for i in range(gt.shape[0])])

# Calculate MSE
mse = np.mean(l2_errors**2)
return mse




176 changes: 112 additions & 64 deletions src/bcm/utils/utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@
#===================================================

import os
import cv2
# import cv2
import re
import numpy as np
import torch
Expand All @@ -19,7 +19,7 @@
import datetime
import shutil

from bcm.utils.metrics import compute_L2_error, compute_z_error, compute_TP_and_FP, compute_TN_and_FN
from bcm.utils.metrics import compute_L2_error, compute_z_error, compute_TP_and_FP, compute_TN_and_FN, compute_MSE
from bcm.utils.image import Image, zeros_like

logger = logging.getLogger(__name__)
Expand Down Expand Up @@ -63,7 +63,7 @@


## Functions
def fetch_img_and_seg_paths(path_list, path_type, seg_suffix='_seg', derivatives_path='/derivatives/labels'):
def fetch_img_and_seg_paths(path_list, path_type, datasets_path='', seg_suffix='_seg', derivatives_path='derivatives/labels'):
"""
:param path_list: List of path in a BIDS compliant dataset
:param path_type: Type of files specified (LABEL or IMAGE)
Expand All @@ -76,6 +76,8 @@ def fetch_img_and_seg_paths(path_list, path_type, seg_suffix='_seg', derivatives
img_paths = []
seg_paths = []
for str_path in path_list:
if datasets_path:
str_path = os.path.join(datasets_path, str_path)
if path_type == 'LABEL':
img_paths.append(get_img_path_from_label_path(str_path))
seg_paths.append(get_seg_path_from_label_path(str_path, seg_suffix=seg_suffix))
Expand All @@ -88,6 +90,7 @@ def fetch_img_and_seg_paths(path_list, path_type, seg_suffix='_seg', derivatives


##

def get_seg_path_from_img_path(img_path, seg_suffix='_seg', derivatives_path='/derivatives/labels'):
"""
This function returns the segmentaion path from an image path. Images need to be stored in a BIDS compliant dataset.
Expand Down Expand Up @@ -221,60 +224,60 @@ def swap_y_origin(coords, img_shape, y_pos=1):
coords[:,y_pos] = y_shape - coords[:,y_pos]
return coords

##
def save_discs_image(input_img, discs_images, out_path, target_th=0.5):
clr_vis_Y = []
hues = np.linspace(0, 179, discs_images.shape[0], dtype=np.uint8)
blank_ch = 255*np.ones_like(discs_images[0], dtype=np.uint8)
# ##
# def save_discs_image(input_img, discs_images, out_path, target_th=0.5):
# clr_vis_Y = []
# hues = np.linspace(0, 179, discs_images.shape[0], dtype=np.uint8)
# blank_ch = 255*np.ones_like(discs_images[0], dtype=np.uint8)

y_colored = np.zeros([discs_images.shape[1], discs_images.shape[2], 3], dtype=np.uint8)
y_all = np.zeros([discs_images.shape[1], discs_images.shape[2]], dtype=np.uint8)
# y_colored = np.zeros([discs_images.shape[1], discs_images.shape[2], 3], dtype=np.uint8)
# y_all = np.zeros([discs_images.shape[1], discs_images.shape[2]], dtype=np.uint8)

for ych, hue_i in zip(discs_images, hues):
ych = ych/np.max(np.max(ych))
# for ych, hue_i in zip(discs_images, hues):
# ych = ych/np.max(np.max(ych))

ych_hue = np.ones_like(ych, dtype=np.uint8)*hue_i
ych = np.uint8(255*ych/np.max(ych))
# ych_hue = np.ones_like(ych, dtype=np.uint8)*hue_i
# ych = np.uint8(255*ych/np.max(ych))

colored_ych = np.zeros_like(y_colored, dtype=np.uint8)
colored_ych[:, :, 0] = ych_hue
colored_ych[:, :, 1] = blank_ch
colored_ych[:, :, 2] = ych
colored_y = cv2.cvtColor(colored_ych, cv2.COLOR_HSV2BGR)
# colored_ych = np.zeros_like(y_colored, dtype=np.uint8)
# colored_ych[:, :, 0] = ych_hue
# colored_ych[:, :, 1] = blank_ch
# colored_ych[:, :, 2] = ych
# colored_y = cv2.cvtColor(colored_ych, cv2.COLOR_HSV2BGR)

y_colored += colored_y
y_all += ych
# y_colored += colored_y
# y_all += ych

# Normalised image between [0,255] as integer
x = (255*(input_img - np.min(input_img))/np.ptp(input_img)).astype(int)
# # Normalised image between [0,255] as integer
# x = (255*(input_img - np.min(input_img))/np.ptp(input_img)).astype(int)

x_3ch = np.zeros([x.shape[0], x.shape[1], 3])
for i in range(3):
x_3ch[:, :, i] = x[:, :]
# x_3ch = np.zeros([x.shape[0], x.shape[1], 3])
# for i in range(3):
# x_3ch[:, :, i] = x[:, :]

img_mix = np.uint8(x_3ch*0.6 + y_colored*0.4)
clr_vis_Y.append(img_mix)
# img_mix = np.uint8(x_3ch*0.6 + y_colored*0.4)
# clr_vis_Y.append(img_mix)

t = np.array(clr_vis_Y)
t = np.transpose(t, [0, 3, 1, 2])
trgts = make_grid(torch.Tensor(t), nrow=4)
# t = np.array(clr_vis_Y)
# t = np.transpose(t, [0, 3, 1, 2])
# trgts = make_grid(torch.Tensor(t), nrow=4)

res = np.transpose(trgts.numpy(), (1,2,0))
cv2.imwrite(out_path, res)
# res = np.transpose(trgts.numpy(), (1,2,0))
# cv2.imwrite(out_path, res)

##
def visualize_discs(input_img, coords_list, out_path):
coords_list = swap_y_origin(coords=coords_list, img_shape=input_img.shape, y_pos=0).tolist() # The y origin is at the top of the image
discs_images = []
for coord in coords_list:
coord = [int(c) for c in coord]
disc_img = np.zeros_like(input_img[:,:])
disc_img[coord[0]-1:coord[0]+2, coord[1]-1:coord[1]+2] = [255, 255, 255]
disc_img[:, :] = cv2.GaussianBlur(disc_img[:, :],(5,5),cv2.BORDER_DEFAULT)
disc_img[:, :] = disc_img[:, :]/disc_img[:, :].max()*255
discs_images.append(disc_img)
discs_images = np.array(discs_images)
save_discs_image(input_img, discs_images, out_path)
# def visualize_discs(input_img, coords_list, out_path):
# coords_list = swap_y_origin(coords=coords_list, img_shape=input_img.shape, y_pos=0).tolist() # The y origin is at the top of the image
# discs_images = []
# for coord in coords_list:
# coord = [int(c) for c in coord]
# disc_img = np.zeros_like(input_img[:,:])
# disc_img[coord[0]-1:coord[0]+2, coord[1]-1:coord[1]+2] = [255, 255, 255]
# disc_img[:, :] = cv2.GaussianBlur(disc_img[:, :],(5,5),cv2.BORDER_DEFAULT)
# disc_img[:, :] = disc_img[:, :]/disc_img[:, :].max()*255
# discs_images.append(disc_img)
# discs_images = np.array(discs_images)
# save_discs_image(input_img, discs_images, out_path)

##
def coord2list(coords):
Expand Down Expand Up @@ -437,14 +440,20 @@ def edit_subject_lines_txt_file(coords, txt_lines, subject_name, contrast, metho
intermediate_line = new_line[:]
max_ref_disc += 1
intermediate_line[2] = str(max_ref_disc)
txt_lines.insert(last_index, intermediate_line) # Add intermediate lines to txt_file lines
txt_lines.insert(last_index, intermediate_line) # Add intermediate lines to txt_file lines --



idx = np.where(coords[:,-1] == disc_num)[0][0]
new_line[method_idx] = '[' + str(coords[idx, 0]) + ',' + str(coords[idx, 1]) + ']' + end_of_line
last_index += 1
txt_lines.insert(last_index, new_line) # Add new disc detection to txt_file lines
max_ref_disc = disc_num
return txt_lines




##
def edit_metric_csv(result_dict, txt_lines, subject_name, contrast, method_name, nb_subjects):
'''
Expand All @@ -460,6 +469,8 @@ def edit_metric_csv(result_dict, txt_lines, subject_name, contrast, method_name,
methods = txt_lines[0,:]
methods[-1] = methods[-1].replace('\n','')
method_idx = np.where(methods==method_name)[0][0]

#Note: If all nnunet methods have the same syntax as other methods, you can remove the conditional check above and simply use the next line:
method_short = method_name.split('_coords')[0] # Remove '_coords' suffix

subject_idx = np.where(methods=='subject_name')[0][0]
Expand Down Expand Up @@ -502,42 +513,56 @@ def edit_metric_csv(result_dict, txt_lines, subject_name, contrast, method_name,
l2_pred = compute_L2_error(gt=gt_coords_list, pred=pred_coords_list)

# Compute L2 error mean and std
l2_pred_mean = np.mean(l2_pred) if l2_pred.size != 0 else 0
l2_pred_std = np.std(l2_pred) if l2_pred.size != 0 else 0
l2_pred_mean = np.mean(l2_pred) if l2_pred.size != 0 else -1
l2_pred_std = np.std(l2_pred) if l2_pred.size != 0 else -1

#--------------------------------#
# Compute Z error
#--------------------------------#
z_err_pred = compute_z_error(gt=gt_coords_list, pred=pred_coords_list)

# Compute z error mean and std
z_err_pred_mean = np.mean(z_err_pred) if z_err_pred.size != 0 else 0
z_err_pred_std = np.std(z_err_pred) if z_err_pred.size != 0 else 0
z_err_pred_mean = np.mean(z_err_pred) if z_err_pred.size != 0 else -1
z_err_pred_std = np.std(z_err_pred) if z_err_pred.size != 0 else -1

#----------------------------------------------------#
# Compute true and false positive rate (TPR and FPR)
#----------------------------------------------------#
#------------------------------------------------------------------------------------------------------#
# Compute true and false positive rate (TPR and FPR) & true and false negative rate (TNR and FNR)
#------------------------------------------------------------------------------------------------------#
gt_discs = discs_list[~np.in1d(discs_list, gt_missing_discs)]
pred_discs = discs_list[~np.in1d(discs_list, pred_missing_discs)]

TP_pred, FP_pred, FP_list_pred = compute_TP_and_FP(discs_gt=gt_discs, discs_pred=pred_discs)
TN_pred, FN_pred, FN_list_pred = compute_TN_and_FN(missing_gt=gt_missing_discs, missing_pred=pred_missing_discs)

FP_TN_pred= FP_pred + TN_pred
TP_FN_pred= TP_pred + FN_pred

FPR_pred = FP_pred/FP_TN_pred if FP_TN_pred != 0 else 1 #minimal rate
TPR_pred = TP_pred/TP_FN_pred if TP_FN_pred != 0 else 0 #minimal rate

FPR_pred = FP_pred/total_pred if total_pred != 0 else 0
TPR_pred = TP_pred/total_pred if total_pred != 0 else 0
FNR_pred = FN_pred/TP_FN_pred if TP_FN_pred != 0 else 1 #minimal rate
TNR_pred = TN_pred/FP_TN_pred if FP_TN_pred != 0 else 0 #minimal rate

sensitivity = TP_pred/(TP_pred + FN_pred) if (TP_pred + FN_pred) != 0 else 0 #minimal rate
specificity = TN_pred/(TN_pred + FP_pred) if (TN_pred + FP_pred) != 0 else 0 #minimal rate

#----------------------------------------------------#
# Compute true and false negative rate (TNR and FNR)
# Compute accuracy ACC #### MORANE
#----------------------------------------------------#
TN_pred, FN_pred, FN_list_pred = compute_TN_and_FN(missing_gt=gt_missing_discs, missing_pred=pred_missing_discs)

FNR_pred = FN_pred/total_pred if total_pred != 0 else 1
TNR_pred = TN_pred/total_pred if total_pred != 0 else 1


ACC_pred = (TN_pred+ TP_pred)/(TN_pred+ FN_pred + TP_pred + FP_pred)

#-------------------------------------------#
# Compute dice score : DSC=2TP/(2TP+FP+FN)
#-------------------------------------------#
DSC_pred = 2*TP_pred/(2*TP_pred+FP_pred+FN_pred)

#----------------------------------------------------#
# Compute MSE #### MORANE
#----------------------------------------------------#
mse_pred = compute_MSE(gt=gt_coords_list, pred=pred_coords_list)


###################################
# Add computed metrics to subject #
###################################
Expand All @@ -554,23 +579,34 @@ def edit_metric_csv(result_dict, txt_lines, subject_name, contrast, method_name,
result_dict[subject_name][f'TPR_{method_short}'] = TPR_pred

# Add false positive rate and FP list
# result_dict[subject_name][f'FP_list_{method_short}'] = FP_list_pred
result_dict[subject_name][f'FPR_{method_short}'] = FPR_pred

# Add true negative rate
result_dict[subject_name][f'TNR_{method_short}'] = TNR_pred

# Add false negative rate and FN list
# result_dict[subject_name][f'FN_list_{method_short}'] = FN_list_pred
result_dict[subject_name][f'FNR_{method_short}'] = FNR_pred

# Add sensitivity
result_dict[subject_name][f'sensitivity_{method_short}'] = sensitivity

# Add specificity
result_dict[subject_name][f'specificity_{method_short}'] = specificity

# Add accuracy
result_dict[subject_name][f'ACC_{method_short}'] = ACC_pred

# Add dice score
result_dict[subject_name][f'DSC_{method_short}'] = DSC_pred

# Add total number of discs
result_dict[subject_name]['tot_discs'] = total_discs
result_dict[subject_name][f'tot_pred_{method_short}'] = total_pred


# Add MSE
result_dict[subject_name][f'MSE_{method_short}'] = mse_pred


######################################
# Add total mean of computed metrics #
######################################
Expand Down Expand Up @@ -599,9 +635,15 @@ def edit_metric_csv(result_dict, txt_lines, subject_name, contrast, method_name,

# Init false negative rate
result_dict['total'][f'FNR_{method_short}'] = 0

# Init accuracy
result_dict['total'][f'ACC_{method_short}'] = 0

# Init dice score
result_dict['total'][f'DSC_{method_short}'] = 0

#Init MSE
result_dict['total'][f'MSE_{method_short}'] = 0

# Add L2 error
result_dict['total'][f'l2_mean_{method_short}'] += l2_pred_mean/nb_subjects
Expand All @@ -622,9 +664,15 @@ def edit_metric_csv(result_dict, txt_lines, subject_name, contrast, method_name,

# Add false negative rate
result_dict['total'][f'FNR_{method_short}'] += FNR_pred/nb_subjects

# Add accuracy
result_dict['total'][f'ACC_{method_short}'] += ACC_pred/nb_subjects

# Add dice score
result_dict['total'][f'DSC_{method_short}'] += DSC_pred/nb_subjects

#Add MSE
result_dict['total'][f'MSE_{method_short}'] += mse_pred/nb_subjects

return result_dict, pred_discs_list

Expand Down