Commit 7036cc9f authored by ChaimZhu's avatar ChaimZhu Committed by ZwwWayne
Browse files

[Fix] fix seg metric issues and circle ci (#1962)

* fix circle ci

* update

* update
parent 2bd4f07e
...@@ -316,7 +316,6 @@ class S3DISSegDataset(_S3DISSegDataset): ...@@ -316,7 +316,6 @@ class S3DISSegDataset(_S3DISSegDataset):
ignore_index=ignore_index, ignore_index=ignore_index,
scene_idxs=scene_idxs[i], scene_idxs=scene_idxs[i],
test_mode=test_mode, test_mode=test_mode,
serialize_data=False,
**kwargs) for i in range(len(ann_files)) **kwargs) for i in range(len(ann_files))
] ]
......
...@@ -39,6 +39,10 @@ class Seg3DDataset(BaseDataset): ...@@ -39,6 +39,10 @@ class Seg3DDataset(BaseDataset):
Defaults to None. Defaults to None.
test_mode (bool): Whether the dataset is in test mode. test_mode (bool): Whether the dataset is in test mode.
Defaults to False. Defaults to False.
serialize_data (bool, optional): Whether to hold memory using
serialized objects, when enabled, data loader workers can use
shared RAM from master process instead of making a copy. Defaults
to False for 3D Segmentation datasets.
load_eval_anns (bool): Whether to load annotations in test_mode, load_eval_anns (bool): Whether to load annotations in test_mode,
the annotation will be save in `eval_ann_infos`, which can be used the annotation will be save in `eval_ann_infos`, which can be used
in Evaluator. Defaults to True. in Evaluator. Defaults to True.
...@@ -66,6 +70,7 @@ class Seg3DDataset(BaseDataset): ...@@ -66,6 +70,7 @@ class Seg3DDataset(BaseDataset):
ignore_index: Optional[int] = None, ignore_index: Optional[int] = None,
scene_idxs: Optional[Union[str, np.ndarray]] = None, scene_idxs: Optional[Union[str, np.ndarray]] = None,
test_mode: bool = False, test_mode: bool = False,
serialize_data=False,
load_eval_anns: bool = True, load_eval_anns: bool = True,
file_client_args: dict = dict(backend='disk'), file_client_args: dict = dict(backend='disk'),
**kwargs) -> None: **kwargs) -> None:
...@@ -115,6 +120,7 @@ class Seg3DDataset(BaseDataset): ...@@ -115,6 +120,7 @@ class Seg3DDataset(BaseDataset):
data_prefix=data_prefix, data_prefix=data_prefix,
pipeline=pipeline, pipeline=pipeline,
test_mode=test_mode, test_mode=test_mode,
serialize_data=serialize_data,
**kwargs) **kwargs)
self.metainfo['seg_label_mapping'] = self.seg_label_mapping self.metainfo['seg_label_mapping'] = self.seg_label_mapping
......
# Copyright (c) OpenMMLab. All rights reserved. # Copyright (c) OpenMMLab. All rights reserved.
import warnings import os.path as osp
from typing import Sequence import tempfile
from typing import Dict, Optional, Sequence
from mmengine.logging import print_log import mmcv
from mmeval.metrics import MeanIoU import numpy as np
from terminaltables import AsciiTable from mmengine.evaluator import BaseMetric
from mmengine.logging import MMLogger
from mmdet3d.evaluation import seg_eval
from mmdet3d.registry import METRICS from mmdet3d.registry import METRICS
@METRICS.register_module() @METRICS.register_module()
class SegMetric(MeanIoU): class SegMetric(BaseMetric):
"""A wrapper of ``mmeval.MeanIoU`` for 3D semantic segmentation. """3D semantic segmentation evaluation metric.
This wrapper implements the `process` method that parses predictions and
labels from inputs. This enables ``mmengine.Evaluator`` to handle the data
flow of different tasks through a unified interface.
In addition, this wrapper also implements the ``evaluate`` method that
parses metric results and print pretty table of metrics per class.
Args: Args:
dist_backend (str | None): The name of the distributed communication collect_device (str, optional): Device name used for collecting
backend. Refer to :class:`mmeval.BaseMetric`. results from different ranks during distributed training.
Defaults to 'torch_cuda'. Must be 'cpu' or 'gpu'. Defaults to 'cpu'.
**kwargs: Keyword parameters passed to :class:`mmeval.MeanIoU`. prefix (str): The prefix that will be added in the metric
names to disambiguate homonymous metrics of different evaluators.
If prefix is not provided in the argument, self.default_prefix
will be used instead. Default: None.
pklfile_prefix (str, optional): The prefix of pkl files, including
the file path and the prefix of filename, e.g., "a/b/prefix".
If not specified, a temp file will be created. Default: None.
submission_prefix (str, optional): The prefix of submission data.
If not specified, the submission data will not be generated.
Default: None.
""" """
def __init__(self, dist_backend='torch_cpu', **kwargs): def __init__(self,
iou_metrics = kwargs.pop('iou_metrics', None) collect_device: str = 'cpu',
if iou_metrics is not None: prefix: Optional[str] = None,
warnings.warn( pklfile_prefix: str = None,
'DeprecationWarning: The `iou_metrics` parameter of ' submission_prefix: str = None,
'`IoUMetric` is deprecated, defaults return all metrics now!') **kwargs):
collect_device = kwargs.pop('collect_device', None) self.pklfile_prefix = pklfile_prefix
self.submission_prefix = submission_prefix
if collect_device is not None: super(SegMetric, self).__init__(
warnings.warn( prefix=prefix, collect_device=collect_device)
'DeprecationWarning: The `collect_device` parameter of '
'`IoUMetric` is deprecated, use `dist_backend` instead.')
# Changes the default value of `classwise_results` to True.
super().__init__(
classwise_results=True, dist_backend=dist_backend, **kwargs)
def process(self, data_batch: dict, data_samples: Sequence[dict]) -> None: def process(self, data_batch: dict, data_samples: Sequence[dict]) -> None:
"""Process one batch of data samples and predictions. """Process one batch of data samples and predictions.
...@@ -55,60 +55,83 @@ class SegMetric(MeanIoU): ...@@ -55,60 +55,83 @@ class SegMetric(MeanIoU):
data_samples (Sequence[dict]): A batch of outputs from data_samples (Sequence[dict]): A batch of outputs from
the model. the model.
""" """
predictions, labels = [], []
for data_sample in data_samples: for data_sample in data_samples:
# (num_points, ) -> (num_points, 1) pred_3d = data_sample['pred_pts_seg']
pred = data_sample['pred_pts_seg']['pts_semantic_mask'].unsqueeze( eval_ann_info = data_sample['eval_ann_info']
-1) cpu_pred_3d = dict()
label = data_sample['gt_pts_seg']['pts_semantic_mask'].unsqueeze( for k, v in pred_3d.items():
-1) if hasattr(v, 'to'):
predictions.append(pred) cpu_pred_3d[k] = v.to('cpu').numpy()
labels.append(label) else:
self.add(predictions, labels) cpu_pred_3d[k] = v
self.results.append((eval_ann_info, cpu_pred_3d))
def evaluate(self, *args, **kwargs):
"""Returns metric results and print pretty table of metrics per class. def format_results(self, results):
r"""Format the results to txt file. Refer to `ScanNet documentation
This method would be invoked by ``mmengine.Evaluator``. <http://kaldir.vc.in.tum.de/scannet_benchmark/documentation>`_.
Args:
outputs (list[dict]): Testing results of the dataset.
Returns:
tuple: (outputs, tmp_dir), outputs is the detection results,
tmp_dir is the temporal directory created for saving submission
files when ``submission_prefix`` is not specified.
""" """
metric_results = self.compute(*args, **kwargs)
self.reset() submission_prefix = self.submission_prefix
if submission_prefix is None:
classwise_results = metric_results['classwise_results'] tmp_dir = tempfile.TemporaryDirectory()
del metric_results['classwise_results'] submission_prefix = osp.join(tmp_dir.name, 'results')
mmcv.mkdir_or_exist(submission_prefix)
# Ascii table of the metric results per class. ignore_index = self.dataset_meta['ignore_index']
header = ['Class'] # need to map network output to original label idx
header += classwise_results.keys() cat2label = np.zeros(len(self.dataset_meta['label2cat'])).astype(
classes = self.dataset_meta['classes'] np.int)
table_data = [header] for original_label, output_idx in self.dataset_meta['label2cat'].items(
for i in range(self.num_classes): ):
row_data = [classes[i]] if output_idx != ignore_index:
for _, value in classwise_results.items(): cat2label[output_idx] = original_label
row_data.append(f'{value[i]*100:.2f}')
table_data.append(row_data) for i, (eval_ann, result) in enumerate(results):
sample_idx = eval_ann['point_cloud']['lidar_idx']
table = AsciiTable(table_data) pred_sem_mask = result['semantic_mask'].numpy().astype(np.int)
print_log('per class results:', logger='current') pred_label = cat2label[pred_sem_mask]
print_log('\n' + table.table, logger='current') curr_file = f'{submission_prefix}/{sample_idx}.txt'
np.savetxt(curr_file, pred_label, fmt='%d')
# Ascii table of the metric results overall.
header = ['Class'] def compute_metrics(self, results: list) -> Dict[str, float]:
header += metric_results.keys() """Compute the metrics from processed results.
table_data = [header] Args:
row_data = ['results'] results (list): The processed results of each batch.
for _, value in metric_results.items():
row_data.append(f'{value*100:.2f}') Returns:
table_data.append(row_data) Dict[str, float]: The computed metrics. The keys are the names of
table = AsciiTable(table_data) the metrics, and the values are corresponding results.
table.inner_footing_row_border = True """
print_log('overall results:', logger='current') logger: MMLogger = MMLogger.get_current_instance()
print_log('\n' + table.table, logger='current')
if self.submission_prefix:
# Multiply value by 100 to convert to percentage and rounding. self.format_results(results)
evaluate_results = { return None
k: round(v * 100, 2)
for k, v in metric_results.items() label2cat = self.dataset_meta['label2cat']
} ignore_index = self.dataset_meta['ignore_index']
return evaluate_results
gt_semantic_masks = []
pred_semantic_masks = []
for eval_ann, sinlge_pred_results in results:
gt_semantic_masks.append(eval_ann['pts_semantic_mask'])
pred_semantic_masks.append(
sinlge_pred_results['pts_semantic_mask'])
ret_dict = seg_eval(
gt_semantic_masks,
pred_semantic_masks,
label2cat,
ignore_index,
logger=logger)
return ret_dict
...@@ -39,7 +39,7 @@ def _generate_scannet_seg_dataset_config(): ...@@ -39,7 +39,7 @@ def _generate_scannet_seg_dataset_config():
[227, 119, 194], [227, 119, 194],
[82, 84, 163], [82, 84, 163],
] ]
scene_idxs = [0 for _ in range(20)] scene_idxs = [0]
modality = dict(use_lidar=True, use_camera=False) modality = dict(use_lidar=True, use_camera=False)
pipeline = [ pipeline = [
dict( dict(
...@@ -83,22 +83,39 @@ def _generate_scannet_dataset_config(): ...@@ -83,22 +83,39 @@ def _generate_scannet_dataset_config():
'bookshelf', 'picture', 'counter', 'desk', 'curtain', 'bookshelf', 'picture', 'counter', 'desk', 'curtain',
'refrigerator', 'showercurtrain', 'toilet', 'sink', 'bathtub', 'refrigerator', 'showercurtrain', 'toilet', 'sink', 'bathtub',
'garbagebin') 'garbagebin')
# TODO add pipline
from mmcv.transforms.base import BaseTransform
from mmengine.registry import TRANSFORMS
if 'Identity' not in TRANSFORMS:
@TRANSFORMS.register_module()
class Identity(BaseTransform):
def transform(self, info):
if 'ann_info' in info:
info['gt_labels_3d'] = info['ann_info']['gt_labels_3d']
return info
modality = dict(use_lidar=True, use_camera=False) modality = dict(use_lidar=True, use_camera=False)
pipeline = [ pipeline = [
dict(type='Identity'), dict(
type='LoadPointsFromFile',
coord_type='DEPTH',
shift_height=True,
load_dim=6,
use_dim=[0, 1, 2]),
dict(
type='LoadAnnotations3D',
with_bbox_3d=True,
with_label_3d=True,
with_mask_3d=True,
with_seg_3d=True),
dict(type='GlobalAlignment', rotation_axis=2),
dict(type='PointSegClassMapping'),
dict(type='PointSample', num_points=5),
dict(
type='RandomFlip3D',
sync_2d=False,
flip_ratio_bev_horizontal=1.0,
flip_ratio_bev_vertical=1.0),
dict(
type='GlobalRotScaleTrans',
rot_range=[-0.087266, 0.087266],
scale_ratio_range=[1.0, 1.0],
shift_height=True),
dict(
type='Pack3DDetInputs',
keys=[
'points', 'pts_semantic_mask', 'gt_bboxes_3d', 'gt_labels_3d',
'pts_instance_mask'
])
] ]
data_prefix = dict( data_prefix = dict(
pts='points', pts='points',
...@@ -113,7 +130,7 @@ class TestScanNetDataset(unittest.TestCase): ...@@ -113,7 +130,7 @@ class TestScanNetDataset(unittest.TestCase):
np.random.seed(0) np.random.seed(0)
data_root, ann_file, classes, data_prefix, \ data_root, ann_file, classes, data_prefix, \
pipeline, modality, = _generate_scannet_dataset_config() pipeline, modality, = _generate_scannet_dataset_config()
register_all_modules()
scannet_dataset = ScanNetDataset( scannet_dataset = ScanNetDataset(
data_root, data_root,
ann_file, ann_file,
......
# Copyright (c) OpenMMLab. All rights reserved. # Copyright (c) OpenMMLab. All rights reserved.
import unittest import unittest
import numpy as np
import torch import torch
from mmengine.structures import BaseDataElement from mmengine.structures import BaseDataElement
...@@ -12,18 +13,19 @@ class TestSegMetric(unittest.TestCase): ...@@ -12,18 +13,19 @@ class TestSegMetric(unittest.TestCase):
def _demo_mm_model_output(self): def _demo_mm_model_output(self):
"""Create a superset of inputs needed to run test or train batches.""" """Create a superset of inputs needed to run test or train batches."""
pred_pts_semantic_mask = torch.LongTensor([ pred_pts_semantic_mask = torch.Tensor([
0, 0, 1, 0, 0, 2, 1, 3, 1, 2, 1, 0, 2, 2, 2, 2, 1, 3, 0, 3, 3, 3, 3 0, 0, 1, 0, 0, 2, 1, 3, 1, 2, 1, 0, 2, 2, 2, 2, 1, 3, 0, 3, 3, 3, 3
]) ])
pred_pts_seg_data = dict(pts_semantic_mask=pred_pts_semantic_mask) pred_pts_seg_data = dict(pts_semantic_mask=pred_pts_semantic_mask)
data_sample = Det3DDataSample() data_sample = Det3DDataSample()
data_sample.pred_pts_seg = PointData(**pred_pts_seg_data) data_sample.pred_pts_seg = PointData(**pred_pts_seg_data)
gt_pts_semantic_mask = torch.LongTensor(([ gt_pts_semantic_mask = np.array([
0, 0, 0, 4, 0, 0, 1, 1, 1, 4, 1, 1, 2, 2, 2, 2, 2, 3, 3, 3, 3, 3, 4 0, 0, 0, 255, 0, 0, 1, 1, 1, 255, 1, 1, 2, 2, 2, 2, 2, 3, 3, 3, 3,
])) 3, 255
gt_pts_seg_data = dict(pts_semantic_mask=gt_pts_semantic_mask) ])
data_sample.gt_pts_seg = PointData(**gt_pts_seg_data) ann_info_data = dict(pts_semantic_mask=gt_pts_semantic_mask)
data_sample.eval_ann_info = ann_info_data
batch_data_samples = [data_sample] batch_data_samples = [data_sample]
...@@ -38,8 +40,14 @@ class TestSegMetric(unittest.TestCase): ...@@ -38,8 +40,14 @@ class TestSegMetric(unittest.TestCase):
def test_evaluate(self): def test_evaluate(self):
data_batch = {} data_batch = {}
predictions = self._demo_mm_model_output() predictions = self._demo_mm_model_output()
dataset_meta = dict(classes=('car', 'bicyle', 'motorcycle', 'truck')) label2cat = {
seg_metric = SegMetric(ignore_index=len(dataset_meta['classes'])) 0: 'car',
1: 'bicycle',
2: 'motorcycle',
3: 'truck',
}
dataset_meta = dict(label2cat=label2cat, ignore_index=255)
seg_metric = SegMetric()
seg_metric.dataset_meta = dataset_meta seg_metric.dataset_meta = dataset_meta
seg_metric.process(data_batch, predictions) seg_metric.process(data_batch, predictions)
res = seg_metric.evaluate(1) res = seg_metric.evaluate(1)
......
Markdown is supported
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment