Source code for batchflow.models.metrics.segment

""" Contains metrics for segmentation """
import numpy as np

from . import ClassificationMetrics, get_components


[docs]class SegmentationMetricsByPixels(ClassificationMetrics): """ Metrics to assess segmentation models pixel-wise Notes ----- Rate metrics are evaluated for each item independently. So there are two levels of metrics aggregation: - multi-class averaging - dataset aggregation. For instance, if you have a dataset of 100 pictures (each having size of 256x256) of 10 classes and you need to calculate an accuracy of semantic segmentation, then: - `evaluate(['accuracy'], agg=None, multiclass=None)` will return an array of shape (100, 10) containing accuracy of each class for each image separately. - `evaluate(['accuracy'], agg='mean', multiclass=None)` will return a vector of shape (10,) containing an accuracy of each class averaged across all images. - `evaluate(['accuracy'], agg=None, multiclass='macro')` will return a vector of shape (100,) containing an accuracy of each image separately averaged across all classes. - `evaluate(['accuracy'], agg='mean', multiclass='macro')` will return a single value of an average accuracy of all classes and images combined. The default values are `agg='mean', multiclass='macro'`. For multi-class averaging see :class:`~.ClassificationMetrics`. Examples -------- :: metrics = SegmentationMetricsByPixels(targets, predictions, num_classes=10, fmt='labels') metrics.evaluate('specificity') metrics.evaluate(['sensitivity', 'jaccard'], agg='mean', multiclass=None) """ pass
[docs]class SegmentationMetricsByInstances(ClassificationMetrics): """ Metrics to assess segmentation models by instances (i.e. connected components of one class, e.g. cancer nodules, faces, ) Parameters ---------- iot : float if the ratio of a predicted instance size to the corresponding target size >= `iot`, then instance is considered correctly predicted (true postitive). Notes ----- For other parameters see :class:`~.ClassificationMetrics`. """ def __init__(self, targets, predictions, fmt='proba', num_classes=None, axis=None, skip_bg=True, threshold=.5, iot=.5, calc=True): super().__init__(targets, predictions, fmt, num_classes, axis, threshold, skip_bg, calc=False) self.iot = iot self.target_instances = self._get_instances(self.targets) self.predicted_instances = self._get_instances(self.predictions) if calc: self._calc()
[docs] def free(self): """ Free memory allocated for intermediate data """ super().free() self.target_instances = None self.predicted_instances = None
def _get_instances(self, inputs): """ Find instances of each class within inputs Parameters ---------- inputs : np.ndarray labels axis : int a class axis Returns ------- nested list with ndarray of coords num_classes - 1, batch_items, num_instances, inputs.shape, number of pixels """ if self.num_classes == 2: instances = [get_components(inputs, batch=True)] else: instances = [] inputs = self.one_hot(inputs) class_slice = [slice(None)] * inputs.ndim for class_index in range(1, self.num_classes): class_slice[-1] = class_index one_class = get_components(inputs[tuple(class_slice)], batch=True) instances.append(one_class) return instances def _calc(self): self._confusion_matrix = np.zeros((self.targets.shape[0], self.num_classes - 1, 2, 2), dtype=np.intp) for k in range(1, self.num_classes): for i, item_instances in enumerate(self.target_instances[k-1]): for coords in item_instances: targ = len(coords[0]) pred = np.sum(self.predictions[i][coords] == k) if np.sum(pred) / targ >= self.iot: self._confusion_matrix[i, k-1, 1, 1] += 1 else: self._confusion_matrix[i, k-1, 0, 1] += 1 for k in range(1, self.num_classes): for i, item_instances in enumerate(self.predicted_instances[k-1]): for coords in item_instances: pred = len(coords[0]) targ = np.sum(self.targets[i][coords] == k) if targ == 0 or pred / targ < self.iot: self._confusion_matrix[i, k-1, 1, 0] += 1
[docs] def true_positive(self, label=None, *args, **kwargs): _ = args, kwargs return self._count(lambda l: self._confusion_matrix[:, l-1, 1, 1], label)
[docs] def true_negative(self, label=None, *args, **kwargs): raise ValueError("True negative is inapplicable for instance-based metrics")
[docs] def condition_positive(self, label=None, *args, **kwargs): _ = args, kwargs return self._count(lambda l: self._confusion_matrix[:, l-1, :, 1].sum(axis=1), label)
[docs] def prediction_positive(self, label=None, *args, **kwargs): _ = args, kwargs return self._count(lambda l: self._confusion_matrix[:, l-1, 1].sum(axis=1), label)
[docs] def total_population(self, label=None, *args, **kwargs): _ = args, kwargs return self._count(lambda l: self._confusion_matrix[:, l-1].sum(axis=(1, 2)), label)