Spaces:
Runtime error
Runtime error
# Copyright (c) Open-CD. All rights reserved. | |
import warnings | |
from typing import Dict, Optional, Union | |
import mmcv | |
import mmengine.fileio as fileio | |
import numpy as np | |
from mmcv.transforms import BaseTransform | |
from mmcv.transforms import LoadAnnotations as MMCV_LoadAnnotations | |
from mmcv.transforms import LoadImageFromFile as MMCV_LoadImageFromFile | |
from opencd.registry import TRANSFORMS | |
class MultiImgLoadImageFromFile(MMCV_LoadImageFromFile): | |
"""Load an image pair from files. | |
Required Keys: | |
- img_path | |
Modified Keys: | |
- img | |
- img_shape | |
- ori_shape | |
""" | |
def __init__(self, **kwargs) -> None: | |
super().__init__(**kwargs) | |
def transform(self, results: dict) -> Optional[dict]: | |
"""Functions to load image. | |
Args: | |
results (dict): Result dict from | |
:class:`mmengine.dataset.BaseDataset`. | |
Returns: | |
dict: The dict contains loaded image and meta information. | |
""" | |
filenames = results['img_path'] | |
imgs = [] | |
try: | |
for filename in filenames: | |
if self.file_client_args is not None: | |
file_client = fileio.FileClient.infer_client( | |
self.file_client_args, filename) | |
img_bytes = file_client.get(filename) | |
else: | |
img_bytes = fileio.get( | |
filename, backend_args=self.backend_args) | |
img = mmcv.imfrombytes( | |
img_bytes, flag=self.color_type, backend=self.imdecode_backend) | |
if self.to_float32: | |
img = img.astype(np.float32) | |
imgs.append(img) | |
except Exception as e: | |
if self.ignore_empty: | |
return None | |
else: | |
raise e | |
results['img'] = imgs | |
results['img_shape'] = imgs[0].shape[:2] | |
results['ori_shape'] = imgs[0].shape[:2] | |
return results | |
class MultiImgLoadAnnotations(MMCV_LoadAnnotations): | |
"""Load annotations for change detection provided by dataset. | |
The annotation format is as the following: | |
.. code-block:: python | |
{ | |
# Filename of change detection ground truth file. | |
'seg_map_path': 'a/b/c' | |
} | |
After this module, the annotation has been changed to the format below: | |
.. code-block:: python | |
{ | |
# in str | |
'seg_fields': List | |
# In uint8 type. | |
'gt_seg_map': np.ndarray (H, W) | |
} | |
Required Keys: | |
- seg_map_path (str): Path of change detection ground truth file. | |
Added Keys: | |
- seg_fields (List) | |
- gt_seg_map (np.uint8) | |
Args: | |
reduce_zero_label (bool, optional): Whether reduce all label value | |
by 255. Usually used for datasets where 0 is background label. | |
Defaults to None. | |
imdecode_backend (str): The image decoding backend type. The backend | |
argument for :func:``mmcv.imfrombytes``. | |
See :fun:``mmcv.imfrombytes`` for details. | |
Defaults to 'pillow'. | |
backend_args (dict): Arguments to instantiate a file backend. | |
See https://mmengine.readthedocs.io/en/latest/api/fileio.htm | |
for details. Defaults to None. | |
Notes: mmcv>=2.0.0rc4, mmengine>=0.2.0 required. | |
""" | |
def __init__( | |
self, | |
reduce_zero_label=None, | |
backend_args=None, | |
imdecode_backend='pillow', | |
) -> None: | |
super().__init__( | |
with_bbox=False, | |
with_label=False, | |
with_seg=True, | |
with_keypoints=False, | |
imdecode_backend=imdecode_backend, | |
backend_args=backend_args) | |
self.reduce_zero_label = reduce_zero_label | |
if self.reduce_zero_label is not None: | |
warnings.warn('`reduce_zero_label` will be deprecated, ' | |
'if you would like to ignore the zero label, please ' | |
'set `reduce_zero_label=True` when dataset ' | |
'initialized') | |
self.imdecode_backend = imdecode_backend | |
def _load_seg_map(self, results: dict) -> None: | |
"""Private function to load semantic segmentation annotations. | |
Args: | |
results (dict): Result dict from :obj:``mmcv.BaseDataset``. | |
Returns: | |
dict: The dict contains loaded semantic segmentation annotations. | |
""" | |
img_bytes = fileio.get( | |
results['seg_map_path'], backend_args=self.backend_args) | |
gt_semantic_seg = mmcv.imfrombytes( | |
img_bytes, flag='grayscale', # in mmseg: unchanged | |
backend=self.imdecode_backend).squeeze().astype(np.uint8) | |
# reduce zero_label | |
if self.reduce_zero_label is None: | |
self.reduce_zero_label = results['reduce_zero_label'] | |
assert self.reduce_zero_label == results['reduce_zero_label'], \ | |
'Initialize dataset with `reduce_zero_label` as ' \ | |
f'{results["reduce_zero_label"]} but when load annotation ' \ | |
f'the `reduce_zero_label` is {self.reduce_zero_label}' | |
if self.reduce_zero_label: | |
# avoid using underflow conversion | |
gt_semantic_seg[gt_semantic_seg == 0] = 255 | |
gt_semantic_seg = gt_semantic_seg - 1 | |
gt_semantic_seg[gt_semantic_seg == 254] = 255 | |
# modify to format ann | |
if results.get('format_seg_map', None) is not None: | |
if results['format_seg_map'] == 'to_binary': | |
gt_semantic_seg_copy = gt_semantic_seg.copy() | |
gt_semantic_seg[gt_semantic_seg_copy < 128] = 0 | |
gt_semantic_seg[gt_semantic_seg_copy >= 128] = 1 | |
else: | |
raise ValueError('Invalid value {}'.format(results['format_seg_map'])) | |
# modify if custom classes | |
if results.get('label_map', None) is not None: | |
# Add deep copy to solve bug of repeatedly | |
# replace `gt_semantic_seg`, which is reported in | |
# https://github.com/open-mmlab/mmsegmentation/pull/1445/ | |
gt_semantic_seg_copy = gt_semantic_seg.copy() | |
for old_id, new_id in results['label_map'].items(): | |
gt_semantic_seg[gt_semantic_seg_copy == old_id] = new_id | |
results['gt_seg_map'] = gt_semantic_seg | |
results['seg_fields'].append('gt_seg_map') | |
def __repr__(self) -> str: | |
repr_str = self.__class__.__name__ | |
repr_str += f'(reduce_zero_label={self.reduce_zero_label}, ' | |
repr_str += f"imdecode_backend='{self.imdecode_backend}', " | |
repr_str += f'backend_args={self.backend_args})' | |
return repr_str | |
class MultiImgMultiAnnLoadAnnotations(MMCV_LoadAnnotations): | |
"""Load annotations for semantic change detection provided by dataset. | |
The annotation format is as the following: | |
.. code-block:: python | |
{ | |
# Filename of change detection ground truth file. | |
'seg_map_path': 'a/b/c' | |
} | |
After this module, the annotation has been changed to the format below: | |
.. code-block:: python | |
{ | |
# in str | |
'seg_fields': List | |
# In uint8 type. | |
'gt_seg_map': np.ndarray (H, W) | |
} | |
Required Keys: | |
- seg_map_path (str): Path of change detection ground truth file. | |
Added Keys: | |
- seg_fields (List) | |
- gt_seg_map (np.uint8) | |
Args: | |
reduce_semantic_zero_label (bool, optional): Whether reduce all label value | |
by 255. Usually used for datasets where 0 is background label. | |
Defaults to None. | |
imdecode_backend (str): The image decoding backend type. The backend | |
argument for :func:``mmcv.imfrombytes``. | |
See :fun:``mmcv.imfrombytes`` for details. | |
Defaults to 'pillow'. | |
backend_args (dict): Arguments to instantiate a file backend. | |
See https://mmengine.readthedocs.io/en/latest/api/fileio.htm | |
for details. Defaults to None. | |
Notes: mmcv>=2.0.0rc4, mmengine>=0.2.0 required. | |
""" | |
def __init__( | |
self, | |
reduce_semantic_zero_label=None, | |
backend_args=None, | |
imdecode_backend='pillow', | |
) -> None: | |
super().__init__( | |
with_bbox=False, | |
with_label=False, | |
with_seg=True, | |
with_keypoints=False, | |
imdecode_backend=imdecode_backend, | |
backend_args=backend_args) | |
self.reduce_semantic_zero_label = reduce_semantic_zero_label | |
if self.reduce_semantic_zero_label is not None: | |
warnings.warn('`reduce_semantic_zero_label` will be deprecated, ' | |
'if you would like to ignore the zero label, please ' | |
'set `reduce_semantic_zero_label=True` when dataset ' | |
'initialized') | |
self.imdecode_backend = imdecode_backend | |
def _load_seg_map(self, results: dict) -> None: | |
"""Private function to load semantic segmentation annotations. | |
Args: | |
results (dict): Result dict from :obj:``mmcv.BaseDataset``. | |
Returns: | |
dict: The dict contains loaded semantic segmentation annotations. | |
""" | |
img_bytes = fileio.get( | |
results['seg_map_path'], backend_args=self.backend_args) | |
gt_semantic_seg = mmcv.imfrombytes( | |
img_bytes, flag='grayscale', # in mmseg: unchanged | |
backend=self.imdecode_backend).squeeze().astype(np.uint8) | |
# for semantic anns | |
img_bytes_from = fileio.get( | |
results['seg_map_path_from'], backend_args=self.backend_args) | |
gt_semantic_seg_from = mmcv.imfrombytes( | |
img_bytes_from, flag='grayscale', | |
backend=self.imdecode_backend).squeeze().astype(np.uint8) | |
img_bytes_to = fileio.get( | |
results['seg_map_path_to'], backend_args=self.backend_args) | |
gt_semantic_seg_to = mmcv.imfrombytes( | |
img_bytes_to, flag='grayscale', | |
backend=self.imdecode_backend).squeeze().astype(np.uint8) | |
# reduce zero_label | |
if self.reduce_semantic_zero_label is None: | |
self.reduce_semantic_zero_label = results['reduce_semantic_zero_label'] | |
assert self.reduce_semantic_zero_label == results['reduce_semantic_zero_label'], \ | |
'Initialize dataset with `reduce_semantic_zero_label` as ' \ | |
f'{results["reduce_semantic_zero_label"]} but when load annotation ' \ | |
f'the `reduce_semantic_zero_label` is {self.reduce_semantic_zero_label}' | |
if self.reduce_semantic_zero_label: | |
# avoid using underflow conversion | |
gt_semantic_seg_from[gt_semantic_seg_from == 0] = 255 | |
gt_semantic_seg_from = gt_semantic_seg_from - 1 | |
gt_semantic_seg_from[gt_semantic_seg_from == 254] = 255 | |
gt_semantic_seg_to[gt_semantic_seg_to == 0] = 255 | |
gt_semantic_seg_to = gt_semantic_seg_to - 1 | |
gt_semantic_seg_to[gt_semantic_seg_to == 254] = 255 | |
# modify to format ann | |
if results.get('format_seg_map', None) is not None: | |
if results['format_seg_map'] == 'to_binary': | |
gt_semantic_seg_copy = gt_semantic_seg.copy() | |
gt_semantic_seg[gt_semantic_seg_copy < 128] = 0 | |
gt_semantic_seg[gt_semantic_seg_copy >= 128] = 1 | |
else: | |
raise ValueError('Invalid value {}'.format(results['format_seg_map'])) | |
# modify if custom classes | |
if results.get('label_map', None) is not None: | |
# Add deep copy to solve bug of repeatedly | |
# replace `gt_semantic_seg`, which is reported in | |
# https://github.com/open-mmlab/mmsegmentation/pull/1445/ | |
gt_semantic_seg_copy = gt_semantic_seg.copy() | |
for old_id, new_id in results['label_map'].items(): | |
gt_semantic_seg[gt_semantic_seg_copy == old_id] = new_id | |
if results.get('semantic_label_map', None) is not None: | |
''' Just for semantic anns here ''' | |
# Add deep copy to solve bug of repeatedly | |
# replace `gt_semantic_seg`, which is reported in | |
# https://github.com/open-mmlab/mmsegmentation/pull/1445/ | |
gt_semantic_seg_from_copy = gt_semantic_seg_from.copy() | |
for old_id, new_id in results['label_map'].items(): | |
gt_semantic_seg_from[gt_semantic_seg_from_copy == old_id] = new_id | |
gt_semantic_seg_to_copy = gt_semantic_seg_to.copy() | |
for old_id, new_id in results['label_map'].items(): | |
gt_semantic_seg_to[gt_semantic_seg_to_copy == old_id] = new_id | |
results['gt_seg_map'] = gt_semantic_seg | |
results['gt_seg_map_from'] = gt_semantic_seg_from | |
results['gt_seg_map_to'] = gt_semantic_seg_to | |
results['seg_fields'].extend(['gt_seg_map', | |
'gt_seg_map_from', 'gt_seg_map_to']) | |
def __repr__(self) -> str: | |
repr_str = self.__class__.__name__ | |
repr_str += f'(reduce_semantic_zero_label={self.reduce_semantic_zero_label}, ' | |
repr_str += f"imdecode_backend='{self.imdecode_backend}', " | |
repr_str += f'backend_args={self.backend_args})' | |
return repr_str | |
class MultiImgLoadLoadImageFromNDArray(MultiImgLoadImageFromFile): | |
"""Load an image pair from ``results['img']``. | |
Similar with :obj:`LoadImageFromFile`, but the image has been loaded as | |
:obj:`np.ndarray` in ``results['img']``. Can be used when loading image | |
from webcam. | |
Required Keys: | |
- img | |
Modified Keys: | |
- img | |
- img_path | |
- img_shape | |
- ori_shape | |
Args: | |
to_float32 (bool): Whether to convert the loaded image to a float32 | |
numpy array. If set to False, the loaded image is an uint8 array. | |
Defaults to False. | |
""" | |
def transform(self, results: dict) -> dict: | |
"""Transform function to add image meta information. | |
Args: | |
results (dict): Result dict with Webcam read image in | |
``results['img']``. | |
Returns: | |
dict: The dict contains loaded image and meta information. | |
""" | |
imgs = [] | |
if self.to_float32: | |
for img in results['img']: | |
img = img.astype(np.float32) | |
imgs.append(img) | |
results['img_path'] = None | |
results['img'] = imgs | |
results['img_shape'] = imgs[0].shape[:2] | |
results['ori_shape'] = imgs[0].shape[:2] | |
return results | |
class MultiImgLoadInferencerLoader(BaseTransform): | |
"""Load an image pair from ``results['img']``. | |
Similar with :obj:`LoadImageFromFile`, but the image has been loaded as | |
:obj:`np.ndarray` in ``results['img']``. Can be used when loading image | |
from webcam. | |
Required Keys: | |
- img | |
Modified Keys: | |
- img | |
- img_path | |
- img_shape | |
- ori_shape | |
Args: | |
to_float32 (bool): Whether to convert the loaded image to a float32 | |
numpy array. If set to False, the loaded image is an uint8 array. | |
Defaults to False. | |
""" | |
def __init__(self, **kwargs) -> None: | |
super().__init__() | |
self.from_file = TRANSFORMS.build( | |
dict(type='MultiImgLoadImageFromFile', **kwargs)) | |
self.from_ndarray = TRANSFORMS.build( | |
dict(type='MultiImgLoadLoadImageFromNDArray', **kwargs)) | |
def transform(self, single_input: Union[str, np.ndarray, dict]) -> dict: | |
"""Transform function to add image meta information. | |
Args: | |
results (dict): Result dict with Webcam read image in | |
``results['img']``. | |
Returns: | |
dict: The dict contains loaded image and meta information. | |
""" | |
assert len(single_input) == 2, \ | |
'In `MultiImgLoadInferencerLoader`,' \ | |
'`single_input` contains bi-temporal images' | |
if isinstance(single_input[0], str): | |
inputs = dict(img_path=single_input) | |
elif isinstance(single_input[0], Union[np.ndarray, list]): | |
inputs = dict(img=single_input) | |
elif isinstance(single_input[0], dict): | |
inputs = single_input | |
else: | |
raise NotImplementedError | |
if 'img' in inputs: | |
return self.from_ndarray(inputs) | |
return self.from_file(inputs) | |