def test_metrics_dict_add_integer() -> None: """ Adding a scalar metric where the value is an integer by accident should still store the metric. """ m = MetricsDict() m.add_metric("foo", 1) assert "foo" in m.values() assert m.values()["foo"] == [1.0]
def test_delete_metric() -> None: """ Deleting a set of metrics from the dictionary. """ m = MetricsDict() m.add_metric(MetricType.LOSS, 1) assert m.values()[MetricType.LOSS.value] == [1.0] m.delete_metric(MetricType.LOSS) assert MetricType.LOSS.value not in m.values()
def test_metrics_dict1() -> None: """ Test insertion of scalar values into a MetricsDict. """ m = MetricsDict() assert m.get_hue_names() == [MetricsDict.DEFAULT_HUE_KEY] name = "foo" v1 = 2.7 v2 = 3.14 m.add_metric(name, v1) m.add_metric(name, v2) assert m.values()[name] == [v1, v2] with pytest.raises(ValueError) as ex: # noinspection PyTypeChecker m.add_metric(name, [1.0]) # type: ignore assert "Expected the metric to be a scalar" in str(ex) assert m.skip_nan_when_averaging[name] is False v3 = 3.0 name2 = "bar" m.add_metric(name2, v3, skip_nan_when_averaging=True) assert m.skip_nan_when_averaging[name2] is True # Expected average: Metric "foo" averages over two values v1 and v2. For "bar", we only inserted one value anyhow average = m.average() mean_v1_v2 = mean([v1, v2]) assert average.values() == {name: [mean_v1_v2], name2: [v3]} num_entries = m.num_entries() assert num_entries == {name: 2, name2: 1}
def aggregate_segmentation_metrics(metrics: MetricsDict) -> MetricsDict: """ Computes aggregate metrics for segmentation models, from a metrics dictionary that contains the results for individual minibatches. Specifically, average Dice scores for only the foreground structures and proportions of foreground voxels are computed. All metrics for the background class will be removed. All other metrics that are already present in the input metrics will be averaged and available in the result. Diagnostic values present in the input will be passed through unchanged. :param metrics: A metrics dictionary that contains the per-minibatch results. """ class_names_with_background = metrics.get_hue_names(include_default=False) has_background_class = class_names_with_background[0] == BACKGROUND_CLASS_NAME foreground_classes = class_names_with_background[1:] if has_background_class else class_names_with_background result = metrics.average(across_hues=False) result.diagnostics = metrics.diagnostics.copy() if has_background_class: result.delete_hue(BACKGROUND_CLASS_NAME) add_average_foreground_dice(result) # Total number of voxels per class, including the background class total_voxels = [] voxel_count = MetricType.VOXEL_COUNT.value for g in class_names_with_background: values = metrics.values(hue=g) if voxel_count in values: total_voxels.append(sum(values[voxel_count])) if len(total_voxels) > 0: # Proportion of voxels in foreground classes only proportion_foreground = np.array(total_voxels[1:], dtype=float) / sum(total_voxels) for i, foreground_class in enumerate(foreground_classes): result.add_metric(MetricType.PROPORTION_FOREGROUND_VOXELS, proportion_foreground[i], hue=foreground_class) result.add_metric(MetricType.PROPORTION_FOREGROUND_VOXELS, np.sum(proportion_foreground).item()) return result