Spaces:
Runtime error
Runtime error
| # Copyright 2020 The HuggingFace Datasets Authors and the current dataset script contributor. | |
| # | |
| # Licensed under the Apache License, Version 2.0 (the "License"); | |
| # you may not use this file except in compliance with the License. | |
| # You may obtain a copy of the License at | |
| # | |
| # http://www.apache.org/licenses/LICENSE-2.0 | |
| # | |
| # Unless required by applicable law or agreed to in writing, software | |
| # distributed under the License is distributed on an "AS IS" BASIS, | |
| # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | |
| # See the License for the specific language governing permissions and | |
| # limitations under the License. | |
| """Dice Coefficient Metric.""" | |
| from typing import Dict, Optional | |
| import numpy as np | |
| import evaluate | |
| import datasets | |
| _DESCRIPTION = """\ | |
| Dice coefficient is 2 times the are of overlap divided by the total number of pixels in both segmentation maps. | |
| """ | |
| _KWARGS_DESCRIPTION = """ | |
| Args: | |
| predictions (`List[ndarray]`): | |
| List of predicted segmentation maps, each of shape (height, width). Each segmentation map can be of a different size. | |
| references (`List[ndarray]`): | |
| List of ground truth segmentation maps, each of shape (height, width). Each segmentation map can be of a different size. | |
| num_labels (`int`): | |
| Number of classes (categories). | |
| ignore_index (`int`): | |
| Index that will be ignored during evaluation. | |
| nan_to_num (`int`, *optional*): | |
| If specified, NaN values will be replaced by the number defined by the user. | |
| label_map (`dict`, *optional*): | |
| If specified, dictionary mapping old label indices to new label indices. | |
| reduce_labels (`bool`, *optional*, defaults to `False`): | |
| Whether or not to reduce all label values of segmentation maps by 1. Usually used for datasets where 0 is used for background, | |
| and background itself is not included in all classes of a dataset (e.g. ADE20k). The background label will be replaced by 255. | |
| Returns: | |
| `Dict[str, float | ndarray]` comprising various elements: | |
| - *dice_score* (`float`): | |
| Dice Coefficient. | |
| Examples: | |
| >>> import numpy as np | |
| >>> dice = evaluate.load("DiceCoefficient") | |
| >>> # suppose one has 3 different segmentation maps predicted | |
| >>> predicted_1 = np.array([[1, 2], [3, 4], [5, 255]]) | |
| >>> actual_1 = np.array([[0, 3], [5, 4], [6, 255]]) | |
| >>> predicted_2 = np.array([[2, 7], [9, 2], [3, 6]]) | |
| >>> actual_2 = np.array([[1, 7], [9, 2], [3, 6]]) | |
| >>> predicted_3 = np.array([[2, 2, 3], [8, 2, 4], [3, 255, 2]]) | |
| >>> actual_3 = np.array([[1, 2, 2], [8, 2, 1], [3, 255, 1]]) | |
| >>> predicted = [predicted_1, predicted_2, predicted_3] | |
| >>> ground_truth = [actual_1, actual_2, actual_3] | |
| >>> results = dice.compute(predictions=predicted, references=ground_truth, num_labels=10, ignore_index=255, reduce_labels=False) | |
| >>> print(results) | |
| {'dice_score': 0.47750000} | |
| """ | |
| _CITATION = """\ | |
| @software{MMSegmentation_Contributors_OpenMMLab_Semantic_Segmentation_2020, | |
| author = {{MMSegmentation Contributors}}, | |
| license = {Apache-2.0}, | |
| month = {7}, | |
| title = {{OpenMMLab Semantic Segmentation Toolbox and Benchmark}}, | |
| url = {https://github.com/open-mmlab/mmsegmentation}, | |
| year = {2020} | |
| }""" | |
| def intersect_and_union( | |
| pred_label, | |
| label, | |
| num_labels, | |
| ignore_index: bool, | |
| label_map: Optional[Dict[int, int]] = None, | |
| reduce_labels: bool = False, | |
| ): | |
| """Calculate intersection and Union. | |
| Args: | |
| pred_label (`ndarray`): | |
| Prediction segmentation map of shape (height, width). | |
| label (`ndarray`): | |
| Ground truth segmentation map of shape (height, width). | |
| num_labels (`int`): | |
| Number of categories. | |
| ignore_index (`int`): | |
| Index that will be ignored during evaluation. | |
| label_map (`dict`, *optional*): | |
| Mapping old labels to new labels. The parameter will work only when label is str. | |
| reduce_labels (`bool`, *optional*, defaults to `False`): | |
| Whether or not to reduce all label values of segmentation maps by 1. Usually used for datasets where 0 is used for background, | |
| and background itself is not included in all classes of a dataset (e.g. ADE20k). The background label will be replaced by 255. | |
| Returns: | |
| area_intersect (`ndarray`): | |
| The intersection of prediction and ground truth histogram on all classes. | |
| area_union (`ndarray`): | |
| The union of prediction and ground truth histogram on all classes. | |
| area_pred_label (`ndarray`): | |
| The prediction histogram on all classes. | |
| area_label (`ndarray`): | |
| The ground truth histogram on all classes. | |
| """ | |
| if label_map is not None: | |
| for old_id, new_id in label_map.items(): | |
| label[label == old_id] = new_id | |
| # turn into Numpy arrays | |
| pred_label = np.array(pred_label) | |
| label = np.array(label) | |
| if reduce_labels: | |
| label[label == 0] = 255 | |
| label = label - 1 | |
| label[label == 254] = 255 | |
| mask = label != ignore_index | |
| mask = np.not_equal(label, ignore_index) | |
| pred_label = pred_label[mask] | |
| label = np.array(label)[mask] | |
| intersect = pred_label[pred_label == label] | |
| area_intersect = np.histogram(intersect, bins=num_labels, range=(0, num_labels - 1))[0] | |
| area_pred_label = np.histogram(pred_label, bins=num_labels, range=(0, num_labels - 1))[0] | |
| area_label = np.histogram(label, bins=num_labels, range=(0, num_labels - 1))[0] | |
| area_union = area_pred_label + area_label - area_intersect | |
| return area_intersect, area_union, area_pred_label, area_label | |
| def total_intersect_and_union( | |
| results, | |
| gt_seg_maps, | |
| num_labels, | |
| ignore_index: bool, | |
| label_map: Optional[Dict[int, int]] = None, | |
| reduce_labels: bool = False, | |
| ): | |
| """Calculate Total Intersection and Union, by calculating `intersect_and_union` for each (predicted, ground truth) pair. | |
| Args: | |
| results (`ndarray`): | |
| List of prediction segmentation maps, each of shape (height, width). | |
| gt_seg_maps (`ndarray`): | |
| List of ground truth segmentation maps, each of shape (height, width). | |
| num_labels (`int`): | |
| Number of categories. | |
| ignore_index (`int`): | |
| Index that will be ignored during evaluation. | |
| label_map (`dict`, *optional*): | |
| Mapping old labels to new labels. The parameter will work only when label is str. | |
| reduce_labels (`bool`, *optional*, defaults to `False`): | |
| Whether or not to reduce all label values of segmentation maps by 1. Usually used for datasets where 0 is used for background, | |
| and background itself is not included in all classes of a dataset (e.g. ADE20k). The background label will be replaced by 255. | |
| Returns: | |
| total_area_intersect (`ndarray`): | |
| The intersection of prediction and ground truth histogram on all classes. | |
| total_area_union (`ndarray`): | |
| The union of prediction and ground truth histogram on all classes. | |
| total_area_pred_label (`ndarray`): | |
| The prediction histogram on all classes. | |
| total_area_label (`ndarray`): | |
| The ground truth histogram on all classes. | |
| """ | |
| total_area_intersect = np.zeros((num_labels,), dtype=np.float64) | |
| total_area_union = np.zeros((num_labels,), dtype=np.float64) | |
| total_area_pred_label = np.zeros((num_labels,), dtype=np.float64) | |
| total_area_label = np.zeros((num_labels,), dtype=np.float64) | |
| for result, gt_seg_map in zip(results, gt_seg_maps): | |
| area_intersect, area_union, area_pred_label, area_label = intersect_and_union( | |
| result, gt_seg_map, num_labels, ignore_index, label_map, reduce_labels | |
| ) | |
| total_area_intersect += area_intersect | |
| total_area_union += area_union | |
| total_area_pred_label += area_pred_label | |
| total_area_label += area_label | |
| return total_area_intersect, total_area_union, total_area_pred_label, total_area_label | |
| def dice_coef( | |
| results, | |
| gt_seg_maps, | |
| num_labels, | |
| ignore_index: bool, | |
| nan_to_num: Optional[int] = None, | |
| label_map: Optional[Dict[int, int]] = None, | |
| reduce_labels: bool = False, | |
| ): | |
| """Calculate Mean Dice Coefficient (mDSC). | |
| Args: | |
| results (`ndarray`): | |
| List of prediction segmentation maps, each of shape (height, width). | |
| gt_seg_maps (`ndarray`): | |
| List of ground truth segmentation maps, each of shape (height, width). | |
| num_labels (`int`): | |
| Number of categories. | |
| ignore_index (`int`): | |
| Index that will be ignored during evaluation. | |
| nan_to_num (`int`, *optional*): | |
| If specified, NaN values will be replaced by the number defined by the user. | |
| label_map (`dict`, *optional*): | |
| Mapping old labels to new labels. The parameter will work only when label is str. | |
| reduce_labels (`bool`, *optional*, defaults to `False`): | |
| Whether or not to reduce all label values of segmentation maps by 1. Usually used for datasets where 0 is used for background, | |
| and background itself is not included in all classes of a dataset (e.g. ADE20k). The background label will be replaced by 255. | |
| Returns: | |
| `Dict[str, float | ndarray]` comprising various elements: | |
| - *mean_dsc* (`float`): | |
| Mean Dice Coefficient (DSC averaged over all categories). | |
| """ | |
| total_area_intersect, _, total_area_pred_label, total_area_label = total_intersect_and_union( | |
| results, gt_seg_maps, num_labels, ignore_index, label_map, reduce_labels | |
| ) | |
| result = dict() | |
| dice = 2 * total_area_intersect / (total_area_pred_label + total_area_label) | |
| result["dice_score"] = np.nanmean(dice) | |
| if nan_to_num is not None: | |
| metrics = dict( | |
| {metric: np.nan_to_num(metric_value, nan=nan_to_num) for metric, metric_value in metrics.items()} | |
| ) | |
| return result | |
| class DiceCoefficient(evaluate.Metric): | |
| def _info(self): | |
| return evaluate.MetricInfo( | |
| module_type="metric", | |
| description=_DESCRIPTION, | |
| citation=_CITATION, | |
| inputs_description=_KWARGS_DESCRIPTION, | |
| features=datasets.Features({ | |
| 'predictions': datasets.Value('int64'), | |
| 'references': datasets.Value('int64'), | |
| }), | |
| reference_urls=["https://github.com/open-mmlab/mmsegmentation/blob/master/mmseg/core/evaluation/metrics.py"] | |
| ) | |
| def _compute( | |
| self, | |
| predictions, | |
| references, | |
| num_labels: int, | |
| ignore_index: bool, | |
| nan_to_num: Optional[int] = None, | |
| label_map: Optional[Dict[int, int]] = None, | |
| reduce_labels: bool = False, | |
| ): | |
| dice = dice_coef( | |
| results=predictions, | |
| ground_truths=references, | |
| num_labels=num_labels, | |
| ignore_index=ignore_index, | |
| nan_to_num=nan_to_num, | |
| label_map=label_map, | |
| reduce_labels=reduce_labels, | |
| ) | |
| return dice | |