# Copyright (c) Open-CD. All rights reserved. import os.path as osp from typing import List, Optional, Union import mmcv import mmengine import numpy as np from mmcv.transforms import Compose from mmseg.utils import ConfigType from mmseg.apis import MMSegInferencer class OpenCDInferencer(MMSegInferencer): """Change Detection inferencer, provides inference and visualization interfaces. Note: MMEngine >= 0.5.0 is required. Args: classes (list, optional): Input classes for result rendering, as the prediction of segmentation model is a segment map with label indices, `classes` is a list which includes items responding to the label indices. If classes is not defined, visualizer will take `cityscapes` classes by default. Defaults to None. palette (list, optional): Input palette for result rendering, which is a list of color palette responding to the classes. If palette is not defined, visualizer will take `cityscapes` palette by default. Defaults to None. dataset_name (str, optional): `Dataset name or alias. visulizer will use the meta information of the dataset i.e. classes and palette, but the `classes` and `palette` have higher priority. Defaults to None. scope (str, optional): The scope of the model. Defaults to 'opencd'. """ # noqa def __init__(self, classes: Optional[Union[str, List]] = None, palette: Optional[Union[str, List]] = None, dataset_name: Optional[str] = None, scope: Optional[str] = 'opencd', **kwargs) -> None: super().__init__(scope=scope, **kwargs) classes = classes if classes else self.model.dataset_meta.classes palette = palette if palette else self.model.dataset_meta.palette self.visualizer.set_dataset_meta(classes, palette, dataset_name) def _inputs_to_list(self, inputs: Union[str, np.ndarray]) -> list: """Preprocess the inputs to a list. Preprocess inputs to a list according to its type: - list or tuple: return inputs - str: - Directory path: return all files in the directory - other cases: return a list containing the string. The string could be a path to file, a url or other types of string according to the task. Args: inputs (InputsType): Inputs for the inferencer. Returns: list: List of input for the :meth:`preprocess`. """ return list(inputs) def visualize(self, inputs: list, preds: List[dict], return_vis: bool = False, show: bool = False, wait_time: int = 0, img_out_dir: str = '', opacity: float = 1.0) -> List[np.ndarray]: """Visualize predictions. Args: inputs (list): Inputs preprocessed by :meth:`_inputs_to_list`. preds (Any): Predictions of the model. show (bool): Whether to display the image in a popup window. Defaults to False. wait_time (float): The interval of show (s). Defaults to 0. img_out_dir (str): Output directory of rendering prediction i.e. color segmentation mask. Defaults: '' opacity (int, float): The transparency of segmentation mask. Defaults to 0.8. Returns: List[np.ndarray]: Visualization results. """ if not show and img_out_dir == '' and not return_vis: return None if self.visualizer is None: raise ValueError('Visualization needs the "visualizer" term' 'defined in the config, but got None.') self.visualizer.alpha = opacity results = [] for single_inputs, pred in zip(inputs, preds): img_from_to = [] for single_input in single_inputs: if isinstance(single_input, str): img_bytes = mmengine.fileio.get(single_input) img = mmcv.imfrombytes(img_bytes) img = img[:, :, ::-1] img_name = osp.basename(single_input) elif isinstance(single_input, np.ndarray): img = single_input.copy() img_num = str(self.num_visualized_imgs).zfill(8) + '_vis' img_name = f'{img_num}.jpg' else: raise ValueError('Unsupported input type:' f'{type(single_input)}') img_shape = img.shape img_from_to.append(img) out_file = osp.join(img_out_dir, img_name) if img_out_dir != ''\ else None img_zero_board = np.zeros(img_shape) self.visualizer.add_datasample( img_name, img_zero_board, img_from_to, pred, show=show, wait_time=wait_time, draw_gt=False, draw_pred=True, out_file=out_file) if return_vis: results.append(self.visualizer.get_image()) self.num_visualized_imgs += 1 return results if return_vis else None def _init_pipeline(self, cfg: ConfigType) -> Compose: """Initialize the test pipeline. Return a pipeline to handle various input data, such as ``str``, ``np.ndarray``. It is an abstract method in BaseInferencer, and should be implemented in subclasses. The returned pipeline will be used to process a single data. It will be used in :meth:`preprocess` like this: .. code-block:: python def preprocess(self, inputs, batch_size, **kwargs): ... dataset = map(self.pipeline, dataset) ... """ pipeline_cfg = cfg.test_dataloader.dataset.pipeline # Loading annotations is also not applicable for transform in ('MultiImgLoadAnnotations', 'MultiImgLoadDepthAnnotation'): idx = self._get_transform_idx(pipeline_cfg, transform) if idx != -1: del pipeline_cfg[idx] load_img_idx = self._get_transform_idx(pipeline_cfg, 'MultiImgLoadImageFromFile') if load_img_idx == -1: raise ValueError( 'MultiImgLoadImageFromFile is not found in the test pipeline') pipeline_cfg[load_img_idx]['type'] = 'MultiImgLoadInferencerLoader' return Compose(pipeline_cfg)