def class_specific_dice(self, y_true, y_seg, i): """ Compute the class specific Dice. :param i: The i-th tissue class, default parameters: 0 for background; 1 for myocardium of the left ventricle; 2 for left atrium; 3 for left ventricle; 4 for right atrium; 5 for right ventricle; 6 for ascending aorta; 7 for pulmonary artery. """ y_seg = utils.get_segmentation(y_seg, self.mode) if self.mode == 'tf': assert y_true.shape[1:] == y_seg.shape[1:], "The ground truth and prediction must be of equal shape! " \ "Ground truth shape: %s, " \ "prediction shape: %s" % (y_true.get_shape().as_list(), y_seg.get_shape().as_list()) top = 2 * tf.reduce_sum(y_true[..., i] * y_seg[..., i]) bottom = tf.reduce_sum(y_true[..., i] + y_seg[..., i]) dice = tf.divide(top, tf.maximum(bottom, self.eps), name='class%s_dice' % i) elif self.mode == 'np': assert y_true.shape == y_seg.shape, "The ground truth and prediction must be of equal shape! " \ "Ground truth shape: %s, prediction shape: %s" % (y_true.shape, y_seg.shape) top = 2 * np.sum(y_true[..., i] * y_seg[..., i]) bottom = np.sum(y_true[..., i] + y_seg[..., i]) dice = np.divide(top, np.maximum(bottom, self.eps)) return dice
def average_surface_distance(predictions, labels, spacing_mm=(1, 1, 1)): """ Return the average surface distances based on the predictions and labels. :param predictions: list of output predictions, of shape [1, *vol_shape, n_class] :param labels: list of ground truths, of shape [1, *vol_shape, n_class] :param spacing_mm: 3-element indicating voxel spacings in x, y, z direction. :return: An array of the ASD metric of each prediction. """ assert len(predictions) == len(labels), "Number of predictions and labels don't equal." n = len(predictions) asd = np.empty([n]) n_class = labels[0].shape[-1] SD = SurfaceDistance(spacing_mm) for i in range(n): class_asd = [] mask_gt = labels[i].squeeze(0) mask_pred = utils.get_segmentation(predictions[i], mode='np').squeeze(0) for k in range(n_class): class_asd.append(SD.compute_average_surface_distance(mask_gt=mask_gt[..., k], mask_pred=mask_pred[..., k]) ) asd[i] = np.mean(class_asd) return asd
def hausdorff_distance(predictions, labels, spacing_mm=(1, 1, 1), percent=100): """ Return the Hausdorff distances based on the predictions and labels. :param predictions: list of output predictions :param labels: list of ground truths :param spacing_mm: 3-element indicating voxel spacings in x, y, z direction. :param percent: percentile of the distances instead of the maximum distance, a value between 0 and 100 :return: An array of the ASD metric of each prediction. """ assert len(predictions) == len(labels), "Number of predictions and labels don't equal." n = len(predictions) hd = np.empty([n]) n_class = labels[0].shape[-1] SD = SurfaceDistance(spacing_mm) for i in range(n): class_hd = [] mask_gt = labels[i].squeeze(0) mask_pred = utils.get_segmentation(predictions[i], mode='np').squeeze(0) for k in range(n_class): class_hd.append(SD.compute_robust_hausdorff(mask_gt=mask_gt[..., k], mask_pred=mask_pred[..., k], percent=percent) ) hd[i] = np.mean(class_hd) return hd
def averaged_foreground_jaccard(self, y_true, y_seg): """ Assume the first class is the background. """ if self.mode == 'tf': assert y_true.shape[1:] == y_seg.shape[1:], "The ground truth and prediction must be of equal shape! " \ "Ground truth shape: %s, " \ "prediction shape: %s" % (y_true.get_shape().as_list(), y_seg.get_shape().as_list()) assert y_seg.get_shape().as_list()[-1] == self.n_class, "The number of classes of the segmentation " \ "should be equal to %s!" % self.n_class elif self.mode == 'np': assert y_true.shape == y_seg.shape, "The ground truth and prediction must be of equal shape! " \ "Ground truth shape: %s, prediction shape: %s" % (y_true.shape, y_seg.shape) assert y_seg.shape[-1], "The number of classes of the segmentation should be equal to %s!" % self.n_class y_seg = utils.get_segmentation(y_seg, self.mode) jaccard = 0. if self.mode == 'tf': y_true = tf.cast(y_true, dtype=tf.bool) y_seg = tf.cast(y_seg, dtype=tf.bool) for i in range(1, self.n_class): top = tf.reduce_sum(tf.cast(tf.logical_and(y_true[..., i], y_seg[..., i]), tf.float32)) bottom = tf.reduce_sum(tf.cast(tf.logical_or(y_true[..., i], y_seg[..., i]), tf.float32)) jaccard += top / (tf.maximum(bottom, self.eps)) return tf.divide(jaccard, tf.cast(self.n_class - 1, dtype=tf.float32), name='averaged_foreground_jaccard') elif self.mode == 'np': y_true = y_true.astype(np.bool) y_seg = y_seg.astype(np.bool) for i in range(1, self.n_class): top = np.sum(np.logical_and(y_true[..., i], y_seg[..., i]).astype(np.float32)) bottom = np.sum(np.logical_or(y_true[..., i], y_seg[..., i]).astype(np.float32)) jaccard += top / (np.maximum(bottom, self.eps)) return np.divide(jaccard, self.n_class - 1)
def averaged_foreground_dice(self, y_true, y_seg): """ Assume the first class is the background. """ if self.mode == 'tf': assert y_true.shape[1:] == y_seg.shape[1:], "The ground truth and prediction must be of equal shape! " \ "Ground truth shape: %s, " \ "prediction shape: %s" % (y_true.get_shape().as_list(), y_seg.get_shape().as_list()) assert y_seg.get_shape().as_list()[-1] == self.n_class, "The number of classes of the segmentation " \ "should be equal to %s!" % self.n_class elif self.mode == 'np': assert y_true.shape == y_seg.shape, "The ground truth and prediction must be of equal shape! " \ "Ground truth shape: %s, prediction shape: %s" % (y_true.shape, y_seg.shape) assert y_seg.shape[-1], "The number of classes of the segmentation should be equal to %s!" % self.n_class y_seg = utils.get_segmentation(y_seg, self.mode) dice = 0. if self.mode == 'tf': for i in range(1, self.n_class): top = 2 * tf.reduce_sum(y_true[..., i] * y_seg[..., i]) bottom = tf.reduce_sum(y_true[..., i] + y_seg[..., i]) dice += top / (tf.maximum(bottom, self.eps)) return tf.divide(dice, tf.cast(self.n_class - 1, dtype=tf.float32), name='averaged_foreground_dice') elif self.mode == 'np': for i in range(1, self.n_class): top = 2 * np.sum(y_true[..., i] * y_seg[..., i]) bottom = np.sum(y_true[..., i] + y_seg[..., i]) dice += top / (np.maximum(bottom, self.eps)) return np.divide(dice, self.n_class - 1)