def performance(output, target): pos_probs = torch.sigmoid(output) pos_preds = (pos_probs > 0.5).float() pos_preds = pos_preds.cpu().numpy().squeeze() target = target.cpu().numpy().squeeze() if target.sum() == 0: # background patch return 0, 0 try: # ACD acd_se = binary.assd(pos_preds, target) # ASD d_sg = np.sqrt(binary.__surface_distances(pos_preds, target, 1)) d_gs = np.sqrt(binary.__surface_distances(target, pos_preds, 1)) asd_se = (d_sg.sum() + d_gs.sum()) / (len(d_sg) + len(d_gs)) except: #pred == 0 acd_se = None asd_se = None # IoU union = ((pos_preds + target) != 0).sum() intersection = (pos_preds * target).sum() iou = intersection / union # dice dice = (2 * intersection) / (pos_preds.sum() + target.sum()) return iou, dice, acd_se, asd_se
def performance_by_slice(output_list, target_list,img_name_list): assert len(output_list) == len(target_list), 'not same list lenths' performance = {} for i in range(len(output_list)): preds = output_list[i] slice_pred = (preds > 0.5).astype('float') slice_target = target_list[i] # slice-level classification performance tp = fp = tn = fn = 0 is_gt_positive = slice_target.max() is_pred_positive = slice_pred.max() if is_gt_positive: if is_pred_positive: tp = 1 else: fn = 1 else: if is_pred_positive: fp = 1 else: tn = 1 # slice-level segmentation performance iou = dice = -1 if is_gt_positive: union = ((slice_pred + slice_target) != 0).sum() intersection = (slice_pred * slice_target).sum() iou = intersection / union dice = (2 * intersection) / (slice_pred.sum() + slice_target.sum()) try: # ACD acd_se = binary.assd(slice_pred, slice_target) # ASD d_sg = np.sqrt(binary.__surface_distances(slice_pred, slice_target, 1)) d_gs = np.sqrt(binary.__surface_distances(slice_target, slice_pred, 1)) asd_se = (d_sg.sum() + d_gs.sum()) / (len(d_sg) + len(d_gs)) except: # pred == 0 acd_se = None asd_se = None # TODO: not need to store gt and pred performance[str(i)] = {'cls': [tp, fp, tn, fn], 'seg': [iou, dice], 'gt': slice_target, 'pred': slice_pred, 'img':img_name_list[i], 'acd_se':acd_se, 'asd_se': asd_se, } #'pixel': [gt_pixel, pred_pixel], return performance
def mod_hausdorff_distance(data1, data2, voxelspacing=None, percentile=95): data1, data2 = to_numpy(data1), to_numpy(data2) hd1 = __surface_distances(data1, data2, voxelspacing, connectivity=1) hd2 = __surface_distances(data2, data1, voxelspacing, connectivity=1) hd95_1 = np.percentile(hd1, percentile) hd95_2 = np.percentile(hd2, percentile) mhd = max(hd95_1, hd95_2) return mhd
def _hd_percentile(result: np.ndarray, reference: np.ndarray, voxelspacing: np.ndarray = None, connectivity: int = 1) -> float: hd1 = mp.__surface_distances(result, reference, voxelspacing, connectivity) hd2 = mp.__surface_distances(reference, result, voxelspacing, connectivity) return np.percentile(np.hstack((hd1, hd2)), percentile)
def getHd95(pred, target): pred = pred.cpu().numpy() target = target.cpu().numpy() if np.count_nonzero(pred) > 0 and np.count_nonzero(target): surDist1 = medpyMetrics.__surface_distances(pred, target) surDist2 = medpyMetrics.__surface_distances(target, pred) hd95 = np.percentile(np.hstack((surDist1, surDist2)), 95) return hd95 else: # Edge cases that medpy cannot handle return -1
def normalized_surface_dice(a: np.ndarray, b: np.ndarray, threshold: float, spacing: tuple = None, connectivity=1): """ This implementation differs from the official surface dice implementation! These two are not comparable!!!!! The normalized surface dice is symmetric, so it should not matter whether a or b is the reference image This implementation natively supports 2D and 3D images. Whether other dimensions are supported depends on the __surface_distances implementation in medpy :param a: image 1, must have the same shape as b :param b: image 2, must have the same shape as a :param threshold: distances below this threshold will be counted as true positives. Threshold is in mm, not voxels! (if spacing = (1, 1(, 1)) then one voxel=1mm so the threshold is effectively in voxels) must be a tuple of len dimension(a) :param spacing: how many mm is one voxel in reality? Can be left at None, we then assume an isotropic spacing of 1mm :param connectivity: see scipy.ndimage.generate_binary_structure for more information. I suggest you leave that one alone :return: """ assert all([i == j for i, j in zip(a.shape, b.shape)]), "a and b must have the same shape. a.shape= %s, " \ "b.shape= %s" % (str(a.shape), str(b.shape)) if spacing is None: spacing = tuple([1 for _ in range(len(a.shape))]) a_to_b = __surface_distances(a, b, spacing, connectivity) b_to_a = __surface_distances(b, a, spacing, connectivity) numel_a = len(a_to_b) numel_b = len(b_to_a) tp_a = np.sum(a_to_b <= threshold) / numel_a tp_b = np.sum(b_to_a <= threshold) / numel_b fp = np.sum(a_to_b > threshold) / numel_a fn = np.sum(b_to_a > threshold) / numel_b dc = (tp_a + tp_b) / (tp_a + tp_b + fp + fn + 1e-8 ) # 1e-8 just so that we don't get div by 0 return dc
def hausdorff_distance(data1, data2, voxelspacing=None): data1, data2 = to_numpy(data1), to_numpy(data2) hd1 = __surface_distances(data1, data2, voxelspacing, connectivity=1) hd2 = __surface_distances(data2, data1, voxelspacing, connectivity=1) hd = max(hd1.max(), hd2.max()) return hd
def getNSD(pred, target): surDist1 = medpyMetrics.__surface_distances(pred, target) surDist2 = medpyMetrics.__surface_distances(target, pred) hd95 = np.percentile(np.hstack((surDist1, surDist2)), 95) return hd95