From 05e980cff492067498bd402dbb37cb9d4b12345e Mon Sep 17 00:00:00 2001 From: KerwinKai <101576779+KerwinKai@users.noreply.github.com> Date: Tue, 1 Aug 2023 22:22:11 +0800 Subject: [PATCH 01/46] first commit for original design --- mmengine/visualization/utils.py | 28 ++++++ mmengine/visualization/visualizer.py | 130 +++++++++++++++++---------- 2 files changed, 111 insertions(+), 47 deletions(-) diff --git a/mmengine/visualization/utils.py b/mmengine/visualization/utils.py index 3e6b7d8ba9..07d0c5b139 100644 --- a/mmengine/visualization/utils.py +++ b/mmengine/visualization/utils.py @@ -120,6 +120,34 @@ def color_val_matplotlib( raise TypeError(f'Invalid type for color: {type(colors)}') +def color_val_opencv( + colors: Union[str, tuple, List[Union[str, tuple]]] +) -> Union[str, tuple, List[Union[str, tuple]]]: + """Convert various input in BGR order to normalized BGR opencv color + tuples, + Args: + colors (Union[str, tuple, List[Union[str, tuple]]]): Color inputs + Returns: + Union[str, tuple, List[Union[str, tuple]]]: A tuple of 3 ints + indicating BGR channels. + """ + if isinstance(colors, str): + return color_str2rgb(colors) + elif isinstance(colors, tuple): + assert len(colors) == 3 + for channel in colors: + assert 0 <= channel <= 255 + return colors + elif isinstance(colors, list): + colors = [ + color_val_opencv(color) # type:ignore + for color in colors + ] + return colors + else: + raise TypeError(f'Invalid type for color: {type(colors)}') + + def color_str2rgb(color: str) -> tuple: """Convert Matplotlib str color to an RGB color which range is 0 to 255, silently dropping the alpha channel. diff --git a/mmengine/visualization/visualizer.py b/mmengine/visualization/visualizer.py index edd957a53b..769c12b0ec 100644 --- a/mmengine/visualization/visualizer.py +++ b/mmengine/visualization/visualizer.py @@ -18,6 +18,7 @@ from mmengine.utils import ManagerMixin from mmengine.visualization.utils import (check_type, check_type_and_length, color_str2rgb, color_val_matplotlib, + color_val_opencv, convert_overlay_heatmap, img_from_canvas, tensor2ndarray, value2list, wait_continue) @@ -149,15 +150,14 @@ class Visualizer(ManagerMixin): >>> pass """ - def __init__( - self, - name='visualizer', - image: Optional[np.ndarray] = None, - vis_backends: Optional[List[Dict]] = None, - save_dir: Optional[str] = None, - fig_save_cfg=dict(frameon=False), - fig_show_cfg=dict(frameon=False) - ) -> None: + def __init__(self, + name='visualizer', + image: Optional[np.ndarray] = None, + vis_backends: Optional[List[Dict]] = None, + save_dir: Optional[str] = None, + fig_save_cfg=dict(frameon=False), + fig_show_cfg=dict(frameon=False), + backend: str = 'matplotlib') -> None: super().__init__(name) self._dataset_meta: Optional[dict] = None self._vis_backends: Union[Dict, Dict[str, 'BaseVisBackend']] = dict() @@ -190,6 +190,12 @@ def __init__( vis_backend.setdefault('save_dir', save_dir) self._vis_backends[name] = VISBACKENDS.build(vis_backend) + if backend not in ['matplotlib', 'cv2']: + raise ValueError('backend should be "matplotlib" or "cv2", ' + f'but got {backend} instead') + + self.backend = backend + self.fig_save = None self.fig_save_cfg = fig_save_cfg self.fig_show_cfg = fig_show_cfg @@ -257,9 +263,10 @@ def show(self, # will be updated with `win_name`. cv2.namedWindow(winname=f'{id(self)}') cv2.setWindowTitle(f'{id(self)}', win_name) - cv2.imshow( - str(id(self)), - self.get_image() if drawn_img is None else drawn_img) + bgr_image = cv2.cvtColor( + self.get_image(), + cv2.COLOR_RGB2BGR) if drawn_img is None else drawn_img + cv2.imshow(str(id(self)), bgr_image) cv2.waitKey(int(np.ceil(wait_time * 1000))) else: raise ValueError('backend should be "matplotlib" or "cv2", ' @@ -279,17 +286,20 @@ def set_image(self, image: np.ndarray) -> None: self._default_font_size = max( np.sqrt(self.height * self.width) // 90, 10) - # add a small 1e-2 to avoid precision lost due to matplotlib's - # truncation (https://github.com/matplotlib/matplotlib/issues/15363) - self.fig_save.set_size_inches( # type: ignore - (self.width + 1e-2) / self.dpi, (self.height + 1e-2) / self.dpi) - # self.canvas = mpl.backends.backend_cairo.FigureCanvasCairo(fig) - self.ax_save.cla() - self.ax_save.axis(False) - self.ax_save.imshow( - image, - extent=(0, self.width, self.height, 0), - interpolation='none') + if self.backend == 'matplotlib': + # add a small 1e-2 to avoid precision lost due to matplotlib's + # truncation + # (https://github.com/matplotlib/matplotlib/issues/15363) + self.fig_save.set_size_inches( # type: ignore + (self.width + 1e-2) / self.dpi, + (self.height + 1e-2) / self.dpi) + # self.canvas = mpl.backends.backend_cairo.FigureCanvasCairo(fig) + self.ax_save.cla() + self.ax_save.axis(False) + self.ax_save.imshow( + image, + extent=(0, self.width, self.height, 0), + interpolation='none') @master_only def get_image(self) -> np.ndarray: @@ -299,7 +309,10 @@ def get_image(self) -> np.ndarray: np.ndarray: the drawn image which channel is RGB. """ assert self._image is not None, 'Please set image using `set_image`' - return img_from_canvas(self.fig_save_canvas) # type: ignore + if self.backend == 'matplotlib': + return img_from_canvas(self.fig_save_canvas) # type: ignore + else: + return self._image def _initialize_fig(self, fig_cfg) -> tuple: """Build figure according to fig_cfg. @@ -533,18 +546,19 @@ def draw_texts( check_type_and_length('bboxes', bboxes, (dict, list), num_text) bboxes = value2list(bboxes, dict, num_text) - for i in range(num_text): - self.ax_save.text( - positions[i][0], - positions[i][1], - texts[i], - size=font_sizes[i], # type: ignore - bbox=bboxes[i], # type: ignore - verticalalignment=vertical_alignments[i], - horizontalalignment=horizontal_alignments[i], - family=font_families[i], - fontproperties=font_properties[i], - color=colors[i]) + if self.backend == 'matplotlib': + for i in range(num_text): + self.ax_save.text( + positions[i][0], + positions[i][1], + texts[i], + size=font_sizes[i], # type: ignore + bbox=bboxes[i], # type: ignore + verticalalignment=vertical_alignments[i], + horizontalalignment=horizontal_alignments[i], + family=font_families[i], + fontproperties=font_properties[i], + color=colors[i]) return self @master_only @@ -552,8 +566,8 @@ def draw_lines( self, x_datas: Union[np.ndarray, torch.Tensor], y_datas: Union[np.ndarray, torch.Tensor], - colors: Union[str, tuple, List[str], List[tuple]] = 'g', - line_styles: Union[str, List[str]] = '-', + colors: Union[str, tuple, List[Union[str, tuple]]] = 'g', + line_styles: Union[str, List[str]] = None, line_widths: Union[Union[int, float], List[Union[int, float]]] = 2 ) -> 'Visualizer': """Draw single or multiple line segments. @@ -575,14 +589,14 @@ def draw_lines( value, all the lines will have the same linestyle. Reference to https://matplotlib.org/stable/api/collections_api.html?highlight=collection#matplotlib.collections.AsteriskPolygonCollection.set_linestyle - for more details. Defaults to '-'. + for more details. Defaults to '-' when backend is 'matplotlib', + and 'cv2.LINE_8' when backend is 'cv2'. line_widths (Union[Union[int, float], List[Union[int, float]]]): The linewidth of lines. ``line_widths`` can have the same length with lines or just single value. If ``line_widths`` is single value, all the lines will have the same linewidth. Defaults to 2. """ - from matplotlib.collections import LineCollection check_type('x_datas', x_datas, (np.ndarray, torch.Tensor)) x_datas = tensor2ndarray(x_datas) check_type('y_datas', y_datas, (np.ndarray, torch.Tensor)) @@ -595,19 +609,41 @@ def draw_lines( if len(x_datas.shape) == 1: x_datas = x_datas[None] y_datas = y_datas[None] - colors = color_val_matplotlib(colors) # type: ignore lines = np.concatenate( (x_datas.reshape(-1, 2, 1), y_datas.reshape(-1, 2, 1)), axis=-1) if not self._is_posion_valid(lines): warnings.warn( 'Warning: The line is out of bounds,' ' the drawn line may not be in the image', UserWarning) - line_collect = LineCollection( - lines.tolist(), - colors=colors, - linestyles=line_styles, - linewidths=line_widths) - self.ax_save.add_collection(line_collect) + if self.backend == 'matplotlib': + from matplotlib.collections import LineCollection + if line_styles is None: + line_styles = '-' + colors = color_val_matplotlib(colors) + line_collect = LineCollection( + lines.tolist(), + colors=colors, + linestyles=line_styles, + linewidths=line_widths) + self.ax_save.add_collection(line_collect) + else: + lines = lines.tolist() + if line_styles is None: + line_styles = [cv2.LINE_8 for _ in range(len(lines))] + check_type_and_length('line_styles', line_styles, (int, list), + len(lines)) + colors = color_val_opencv(colors) + for i, line in enumerate(lines): + st_pos = (line[0][0], line[0][1]) + ed_pos = (line[1][0], line[1][1]) + cv2.line( + img=self._image, + pt1=st_pos, + pt2=ed_pos, + color=colors[i], + thickness=int(line_widths[i]) if isinstance( + line_widths, list) else int(line_widths), + lineType=line_styles[i]) return self @master_only From 5df7d5ad46056673bae0f612050a73024df4eafb Mon Sep 17 00:00:00 2001 From: KerwinKai <101576779+KerwinKai@users.noreply.github.com> Date: Wed, 2 Aug 2023 10:10:07 +0800 Subject: [PATCH 02/46] update draw_circle function and fix example --- mmengine/visualization/visualizer.py | 61 ++++++++++++++++++++-------- 1 file changed, 43 insertions(+), 18 deletions(-) diff --git a/mmengine/visualization/visualizer.py b/mmengine/visualization/visualizer.py index 769c12b0ec..6f080de716 100644 --- a/mmengine/visualization/visualizer.py +++ b/mmengine/visualization/visualizer.py @@ -104,9 +104,9 @@ class Visualizer(ManagerMixin): >>> vis.draw_texts(text=['MMEngine','OpenMMLab'], >>> position=np.array([[2, 2], [5, 5]]), >>> colors=['b', 'b']) - >>> vis.draw_circles(circle_coord=np.array([2, 2]), radius=np.array[1]) - >>> vis.draw_circles(circle_coord=np.array([[2, 2], [3, 5]), - >>> radius=np.array[1, 2], colors=['g', 'r']) + >>> vis.draw_circles(center=np.array([2, 2]), radius=np.array([1])) + >>> vis.draw_circles(center=np.array([[2, 2], [3, 5]]), + >>> radius=np.array[1, 2], edge_colors=['g', 'r']) >>> square = np.array([[0, 0], [100, 0], [100, 100], [0, 100]]) >>> vis.draw_polygons(polygons=square, edge_colors='g') >>> squares = [np.array([[0, 0], [100, 0], [100, 100], [0, 100]]), @@ -651,8 +651,8 @@ def draw_circles( self, center: Union[np.ndarray, torch.Tensor], radius: Union[np.ndarray, torch.Tensor], - edge_colors: Union[str, tuple, List[str], List[tuple]] = 'g', - line_styles: Union[str, List[str]] = '-', + edge_colors: Union[str, tuple, List[Union[str, tuple]]] = 'g', + line_styles: Union[str, List[str]] = None, line_widths: Union[Union[int, float], List[Union[int, float]]] = 2, face_colors: Union[str, tuple, List[str], List[tuple]] = 'none', alpha: Union[float, int] = 0.8, @@ -676,7 +676,8 @@ def draw_circles( value, all the lines will have the same linestyle. Reference to https://matplotlib.org/stable/api/collections_api.html?highlight=collection#matplotlib.collections.AsteriskPolygonCollection.set_linestyle - for more details. Defaults to '-'. + for more details. Defaults to '-' when backend is 'matplotlib', + and 'cv2.LINE_8' when backend is 'cv2'. line_widths (Union[Union[int, float], List[Union[int, float]]]): The linewidth of lines. ``line_widths`` can have the same length with lines or just single value. @@ -687,7 +688,6 @@ def draw_circles( alpha (Union[int, float]): The transparency of circles. Defaults to 0.8. """ - from matplotlib.collections import PatchCollection from matplotlib.patches import Circle check_type('center', center, (np.ndarray, torch.Tensor)) center = tensor2ndarray(center) @@ -708,26 +708,51 @@ def draw_circles( center = center.tolist() radius = radius.tolist() - edge_colors = color_val_matplotlib(edge_colors) # type: ignore - face_colors = color_val_matplotlib(face_colors) # type: ignore circles = [] for i in range(len(center)): circles.append(Circle(tuple(center[i]), radius[i])) - if isinstance(line_widths, (int, float)): line_widths = [line_widths] * len(circles) line_widths = [ min(max(linewidth, 1), self._default_font_size / 4) for linewidth in line_widths ] - p = PatchCollection( - circles, - alpha=alpha, - facecolors=face_colors, - edgecolors=edge_colors, - linewidths=line_widths, - linestyles=line_styles) - self.ax_save.add_collection(p) + if self.backend == 'matplotlib': + from matplotlib.collections import PatchCollection + if line_styles is None: + line_styles = '-' + edge_colors = color_val_matplotlib(edge_colors) # type: ignore + face_colors = color_val_matplotlib(face_colors) # type: ignore + p = PatchCollection( + circles, + alpha=alpha, + facecolors=face_colors, + edgecolors=edge_colors, + linewidths=line_widths, + linestyles=line_styles) + self.ax_save.add_collection(p) + else: + warnings.warn( + 'When using cv2 as the backend for visualizer, ' + 'because `cv.circle(img, center, radius, ' + 'color[, thickness[, lineType[, shift]]]) -> img`, ' + 'the parameters `face_colors` and `alpha` ' + 'will be discarded and not called.', UserWarning) + edge_colors = color_val_opencv(edge_colors) + if line_styles is None: + line_styles = [cv2.LINE_8 for _ in range(len(circles))] + check_type_and_length('line_styles', line_styles, (int, list), + len(circles)) + check_type_and_length('line_widths', line_widths, + (int, float, list), len(line_widths)) + for i in range(len(circles)): + cv2.circle( + img=self._image, + center=(int(center[i][0]), int(center[i][1])), + radius=int(radius[i]), + color=edge_colors[i], + lineType=int(line_styles[i]), + thickness=int(line_widths[i])) return self @master_only From 1333ca70b814035bd5ff98e270dcf1f0722a9f01 Mon Sep 17 00:00:00 2001 From: KerwinKai <101576779+KerwinKai@users.noreply.github.com> Date: Wed, 2 Aug 2023 15:47:28 +0800 Subject: [PATCH 03/46] update draw_bboxes function and fix example --- mmengine/visualization/visualizer.py | 68 +++++++++++++++++++++------- 1 file changed, 52 insertions(+), 16 deletions(-) diff --git a/mmengine/visualization/visualizer.py b/mmengine/visualization/visualizer.py index 6f080de716..3fd1276d46 100644 --- a/mmengine/visualization/visualizer.py +++ b/mmengine/visualization/visualizer.py @@ -90,7 +90,7 @@ class Visualizer(ManagerMixin): >>> # Basic drawing methods >>> vis = Visualizer(image=image) >>> vis.draw_bboxes(np.array([0, 0, 1, 1]), edge_colors='g') - >>> vis.draw_bboxes(bbox=np.array([[1, 1, 2, 2], [2, 2, 3, 3]]), + >>> vis.draw_bboxes(bboxes=np.array([[1, 1, 2, 2], [2, 2, 3, 3]]), >>> edge_colors=['g', 'r']) >>> vis.draw_lines(x_datas=np.array([1, 3]), >>> y_datas=np.array([1, 3]), @@ -736,7 +736,7 @@ def draw_circles( 'When using cv2 as the backend for visualizer, ' 'because `cv.circle(img, center, radius, ' 'color[, thickness[, lineType[, shift]]]) -> img`, ' - 'the parameters `face_colors` and `alpha` ' + 'the parameters `face_colors` ' 'will be discarded and not called.', UserWarning) edge_colors = color_val_opencv(edge_colors) if line_styles is None: @@ -746,6 +746,7 @@ def draw_circles( check_type_and_length('line_widths', line_widths, (int, float, list), len(line_widths)) for i in range(len(circles)): + overlay = self._image.copy() cv2.circle( img=self._image, center=(int(center[i][0]), int(center[i][1])), @@ -753,14 +754,16 @@ def draw_circles( color=edge_colors[i], lineType=int(line_styles[i]), thickness=int(line_widths[i])) + cv2.addWeighted(self._image, alpha, overlay, 1 - alpha, 0, + self._image) return self @master_only def draw_bboxes( self, bboxes: Union[np.ndarray, torch.Tensor], - edge_colors: Union[str, tuple, List[str], List[tuple]] = 'g', - line_styles: Union[str, List[str]] = '-', + edge_colors: Union[str, tuple, List[Union[str, tuple]]] = 'g', + line_styles: Union[str, List[str]] = None, line_widths: Union[Union[int, float], List[Union[int, float]]] = 2, face_colors: Union[str, tuple, List[str], List[tuple]] = 'none', alpha: Union[int, float] = 0.8, @@ -807,18 +810,51 @@ def draw_bboxes( warnings.warn( 'Warning: The bbox is out of bounds,' ' the drawn bbox may not be in the image', UserWarning) - poly = np.stack( - (bboxes[:, 0], bboxes[:, 1], bboxes[:, 2], bboxes[:, 1], - bboxes[:, 2], bboxes[:, 3], bboxes[:, 0], bboxes[:, 3]), - axis=-1).reshape(-1, 4, 2) - poly = [p for p in poly] - return self.draw_polygons( - poly, - alpha=alpha, - edge_colors=edge_colors, - line_styles=line_styles, - line_widths=line_widths, - face_colors=face_colors) + if self.backend == 'matplotlib': + if line_styles is None: + line_styles = '-' + poly = np.stack( + (bboxes[:, 0], bboxes[:, 1], bboxes[:, 2], bboxes[:, 1], + bboxes[:, 2], bboxes[:, 3], bboxes[:, 0], bboxes[:, 3]), + axis=-1).reshape(-1, 4, 2) + poly = [p for p in poly] + return self.draw_polygons( + poly, + alpha=alpha, + edge_colors=edge_colors, + line_styles=line_styles, + line_widths=line_widths, + face_colors=face_colors) + else: + warnings.warn( + 'When using cv2 as the backend for visualizer, ' + 'because `cv.rectangle( img, pt1, pt2, ' + 'color[, thickness[, lineType[, shift]]])->img`, ' + 'the parameters `face_colors` ' + 'will be discarded and not called.', UserWarning) + bboxes = bboxes.tolist() + edge_colors = color_val_opencv(edge_colors) + if line_styles is None: + line_styles = [cv2.LINE_8 for _ in range(len(bboxes))] + check_type_and_length('line_styles', line_styles, (int, list), + len(bboxes)) + check_type_and_length('line_widths', line_widths, + (int, float, list), len(bboxes)) + for i, bbox in enumerate(bboxes): + overlay = self._image.copy() + pt1 = (bbox[0], bbox[1]) + pt2 = (bbox[2], bbox[3]) + cv2.rectangle( + img=self._image, + pt1=pt1, + pt2=pt2, + color=edge_colors[i], + lineType=line_styles[i], + thickness=int(line_widths[i]) if isinstance( + line_widths, list) else int(line_widths)) + cv2.addWeighted(self._image, alpha, overlay, 1 - alpha, 0, + self._image) + return self @master_only def draw_polygons( From a567a305e0fa13d9622d64d4b6f1bb235a5c1d43 Mon Sep 17 00:00:00 2001 From: KerwinKai <101576779+KerwinKai@users.noreply.github.com> Date: Wed, 2 Aug 2023 16:02:07 +0800 Subject: [PATCH 04/46] add support for parameter alpha and face_colors in cv2 vis backend --- mmengine/visualization/visualizer.py | 36 ++++++++++++++++------------ 1 file changed, 21 insertions(+), 15 deletions(-) diff --git a/mmengine/visualization/visualizer.py b/mmengine/visualization/visualizer.py index 3fd1276d46..e5cd81d9ba 100644 --- a/mmengine/visualization/visualizer.py +++ b/mmengine/visualization/visualizer.py @@ -106,7 +106,7 @@ class Visualizer(ManagerMixin): >>> colors=['b', 'b']) >>> vis.draw_circles(center=np.array([2, 2]), radius=np.array([1])) >>> vis.draw_circles(center=np.array([[2, 2], [3, 5]]), - >>> radius=np.array[1, 2], edge_colors=['g', 'r']) + >>> radius=np.array([1, 2]), edge_colors=['g', 'r']) >>> square = np.array([[0, 0], [100, 0], [100, 100], [0, 100]]) >>> vis.draw_polygons(polygons=square, edge_colors='g') >>> squares = [np.array([[0, 0], [100, 0], [100, 100], [0, 100]]), @@ -654,7 +654,7 @@ def draw_circles( edge_colors: Union[str, tuple, List[Union[str, tuple]]] = 'g', line_styles: Union[str, List[str]] = None, line_widths: Union[Union[int, float], List[Union[int, float]]] = 2, - face_colors: Union[str, tuple, List[str], List[tuple]] = 'none', + face_colors: Union[str, tuple, List[Union[str, tuple]]] = 'none', alpha: Union[float, int] = 0.8, ) -> 'Visualizer': """Draw single or multiple circles. @@ -732,12 +732,6 @@ def draw_circles( linestyles=line_styles) self.ax_save.add_collection(p) else: - warnings.warn( - 'When using cv2 as the backend for visualizer, ' - 'because `cv.circle(img, center, radius, ' - 'color[, thickness[, lineType[, shift]]]) -> img`, ' - 'the parameters `face_colors` ' - 'will be discarded and not called.', UserWarning) edge_colors = color_val_opencv(edge_colors) if line_styles is None: line_styles = [cv2.LINE_8 for _ in range(len(circles))] @@ -747,6 +741,15 @@ def draw_circles( (int, float, list), len(line_widths)) for i in range(len(circles)): overlay = self._image.copy() + face_colors = color_val_opencv(face_colors) + if face_colors is not None: + cv2.circle( + img=self._image, + center=(int(center[i][0]), int(center[i][1])), + radius=int(radius[i]), + color=face_colors[i], + lineType=int(line_styles[i]), + thickness=-1) cv2.circle( img=self._image, center=(int(center[i][0]), int(center[i][1])), @@ -765,7 +768,7 @@ def draw_bboxes( edge_colors: Union[str, tuple, List[Union[str, tuple]]] = 'g', line_styles: Union[str, List[str]] = None, line_widths: Union[Union[int, float], List[Union[int, float]]] = 2, - face_colors: Union[str, tuple, List[str], List[tuple]] = 'none', + face_colors: Union[str, tuple, List[Union[str, tuple]]] = 'none', alpha: Union[int, float] = 0.8, ) -> 'Visualizer': """Draw single or multiple bboxes. @@ -826,12 +829,6 @@ def draw_bboxes( line_widths=line_widths, face_colors=face_colors) else: - warnings.warn( - 'When using cv2 as the backend for visualizer, ' - 'because `cv.rectangle( img, pt1, pt2, ' - 'color[, thickness[, lineType[, shift]]])->img`, ' - 'the parameters `face_colors` ' - 'will be discarded and not called.', UserWarning) bboxes = bboxes.tolist() edge_colors = color_val_opencv(edge_colors) if line_styles is None: @@ -844,6 +841,15 @@ def draw_bboxes( overlay = self._image.copy() pt1 = (bbox[0], bbox[1]) pt2 = (bbox[2], bbox[3]) + if face_colors is not None: + face_colors = color_val_opencv(face_colors) + cv2.rectangle( + img=self._image, + pt1=pt1, + pt2=pt2, + color=face_colors[i], + lineType=line_styles[i], + thickness=-1) cv2.rectangle( img=self._image, pt1=pt1, From 5acb08932936a760ace05df0ac087a5376e89585 Mon Sep 17 00:00:00 2001 From: KerwinKai <101576779+KerwinKai@users.noreply.github.com> Date: Wed, 2 Aug 2023 16:41:08 +0800 Subject: [PATCH 05/46] update function draw_polugons and generate face_colors to BGR in function just once --- mmengine/visualization/visualizer.py | 88 ++++++++++++++++++++-------- 1 file changed, 63 insertions(+), 25 deletions(-) diff --git a/mmengine/visualization/visualizer.py b/mmengine/visualization/visualizer.py index e5cd81d9ba..d5cc8cccff 100644 --- a/mmengine/visualization/visualizer.py +++ b/mmengine/visualization/visualizer.py @@ -739,10 +739,13 @@ def draw_circles( len(circles)) check_type_and_length('line_widths', line_widths, (int, float, list), len(line_widths)) - for i in range(len(circles)): - overlay = self._image.copy() + overlay = self._image.copy() + if face_colors != 'none': + if isinstance(face_colors, str): + face_colors = [face_colors] * len(circles) face_colors = color_val_opencv(face_colors) - if face_colors is not None: + for i in range(len(circles)): + if face_colors != 'none': cv2.circle( img=self._image, center=(int(center[i][0]), int(center[i][1])), @@ -757,8 +760,8 @@ def draw_circles( color=edge_colors[i], lineType=int(line_styles[i]), thickness=int(line_widths[i])) - cv2.addWeighted(self._image, alpha, overlay, 1 - alpha, 0, - self._image) + cv2.addWeighted(self._image, alpha, overlay, 1 - alpha, 0, + self._image) return self @master_only @@ -837,12 +840,15 @@ def draw_bboxes( len(bboxes)) check_type_and_length('line_widths', line_widths, (int, float, list), len(bboxes)) + overlay = self._image.copy() + if face_colors != 'none': + if isinstance(face_colors, str): + face_colors = [face_colors] * len(bboxes) + face_colors = color_val_opencv(face_colors) for i, bbox in enumerate(bboxes): - overlay = self._image.copy() pt1 = (bbox[0], bbox[1]) pt2 = (bbox[2], bbox[3]) - if face_colors is not None: - face_colors = color_val_opencv(face_colors) + if face_colors != 'none': cv2.rectangle( img=self._image, pt1=pt1, @@ -858,8 +864,8 @@ def draw_bboxes( lineType=line_styles[i], thickness=int(line_widths[i]) if isinstance( line_widths, list) else int(line_widths)) - cv2.addWeighted(self._image, alpha, overlay, 1 - alpha, 0, - self._image) + cv2.addWeighted(self._image, alpha, overlay, 1 - alpha, 0, + self._image) return self @master_only @@ -867,10 +873,10 @@ def draw_polygons( self, polygons: Union[Union[np.ndarray, torch.Tensor], List[Union[np.ndarray, torch.Tensor]]], - edge_colors: Union[str, tuple, List[str], List[tuple]] = 'g', - line_styles: Union[str, List[str]] = '-', + edge_colors: Union[str, tuple, List[Union[str, tuple]]] = 'g', + line_styles: Union[str, List[str]] = None, line_widths: Union[Union[int, float], List[Union[int, float]]] = 2, - face_colors: Union[str, tuple, List[str], List[tuple]] = 'none', + face_colors: Union[str, tuple, List[Union[str, tuple]]] = 'none', alpha: Union[int, float] = 0.8, ) -> 'Visualizer': """Draw single or multiple bboxes. @@ -902,10 +908,7 @@ def draw_polygons( alpha (Union[int, float]): The transparency of polygons. Defaults to 0.8. """ - from matplotlib.collections import PolyCollection check_type('polygons', polygons, (list, np.ndarray, torch.Tensor)) - edge_colors = color_val_matplotlib(edge_colors) # type: ignore - face_colors = color_val_matplotlib(face_colors) # type: ignore if isinstance(polygons, (np.ndarray, torch.Tensor)): polygons = [polygons] @@ -926,15 +929,50 @@ def draw_polygons( min(max(linewidth, 1), self._default_font_size / 4) for linewidth in line_widths ] - polygon_collection = PolyCollection( - polygons, - alpha=alpha, - facecolor=face_colors, - linestyles=line_styles, - edgecolors=edge_colors, - linewidths=line_widths) - - self.ax_save.add_collection(polygon_collection) + if self.backend == 'matplotlib': + from matplotlib.collections import PolyCollection + edge_colors = color_val_matplotlib(edge_colors) # type: ignore + face_colors = color_val_matplotlib(face_colors) # type: ignore + if line_styles is None: + line_styles = '-' + polygon_collection = PolyCollection( + polygons, + alpha=alpha, + facecolor=face_colors, + linestyles=line_styles, + edgecolors=edge_colors, + linewidths=line_widths) + self.ax_save.add_collection(polygon_collection) + else: + edge_colors = color_val_opencv(edge_colors) + if line_styles is None: + line_styles = [cv2.LINE_8 for _ in range(len(polygons))] + check_type_and_length('line_styles', line_styles, (int, list), + len(polygons)) + check_type_and_length('line_widths', line_widths, + (int, float, list), len(polygons)) + overlay = self._image.copy() + if face_colors != 'none': + if isinstance(face_colors, str): + face_colors = [face_colors] * len(polygons) + face_colors = color_val_opencv(face_colors) + for i, polygon in enumerate(polygons): + polygon = polygon.reshape((-1, 1, 2)) + if face_colors != 'none': + cv2.fillPoly( + img=self._image, + pts=[polygon], + color=face_colors[i], + lineType=line_styles[i]) + cv2.polylines( + img=self._image, + pts=[polygon], + isClosed=True, + color=edge_colors[i], + lineType=line_styles[i], + thickness=line_widths[i]) + cv2.addWeighted(self._image, alpha, overlay, 1 - alpha, 0, + self._image) return self @master_only From 0cb52b84dec652145dcf4f6f3e5ea5e0051403be Mon Sep 17 00:00:00 2001 From: KerwinKai <101576779+KerwinKai@users.noreply.github.com> Date: Wed, 2 Aug 2023 17:05:49 +0800 Subject: [PATCH 06/46] update function draw_binary_masks --- mmengine/visualization/visualizer.py | 13 +++++++++---- 1 file changed, 9 insertions(+), 4 deletions(-) diff --git a/mmengine/visualization/visualizer.py b/mmengine/visualization/visualizer.py index d5cc8cccff..e9db961113 100644 --- a/mmengine/visualization/visualizer.py +++ b/mmengine/visualization/visualizer.py @@ -1003,6 +1003,8 @@ def draw_binary_masks( f'but got {binary_masks.dtype}') binary_masks = binary_masks.astype('uint8') * 255 img = self.get_image() + if self.backend == 'cv2': + img = cv2.cvtColor(img, cv2.COLOR_BGR2RGB) if binary_masks.ndim == 2: binary_masks = binary_masks[None] assert img.shape[:2] == binary_masks.shape[ @@ -1034,10 +1036,13 @@ def draw_binary_masks( img, img, mask=binary_mask_complement) rgb = rgb + img_complement img = cv2.addWeighted(img, 1 - alpha, rgb, alpha, 0) - self.ax_save.imshow( - img, - extent=(0, self.width, self.height, 0), - interpolation='nearest') + if self.backend == 'matplotlib': + self.ax_save.imshow( + img, + extent=(0, self.width, self.height, 0), + interpolation='nearest') + else: + self._image = img return self @staticmethod From 07cadc568b47fb884938b358f57951234eefe510 Mon Sep 17 00:00:00 2001 From: KerwinKai <101576779+KerwinKai@users.noreply.github.com> Date: Wed, 2 Aug 2023 23:00:04 +0800 Subject: [PATCH 07/46] update function draw_points and examples --- mmengine/visualization/visualizer.py | 50 +++++++++++++++++++++++++--- 1 file changed, 45 insertions(+), 5 deletions(-) diff --git a/mmengine/visualization/visualizer.py b/mmengine/visualization/visualizer.py index e9db961113..1d5ef16a06 100644 --- a/mmengine/visualization/visualizer.py +++ b/mmengine/visualization/visualizer.py @@ -89,6 +89,7 @@ class Visualizer(ManagerMixin): >>> # Basic drawing methods >>> vis = Visualizer(image=image) + >>> vis.draw_points(np.array([[1, 3], [2, 4]]), colors=['g', 'r']) >>> vis.draw_bboxes(np.array([0, 0, 1, 1]), edge_colors='g') >>> vis.draw_bboxes(bboxes=np.array([[1, 1, 2, 2], [2, 2, 3, 3]]), >>> edge_colors=['g', 'r']) @@ -384,8 +385,8 @@ def _is_posion_valid(self, position: np.ndarray) -> bool: @master_only def draw_points(self, positions: Union[np.ndarray, torch.Tensor], - colors: Union[str, tuple, List[str], List[tuple]] = 'g', - marker: Optional[str] = None, + colors: Union[str, tuple, List[Union[str, tuple]]] = 'g', + marker: Optional[Union[str, int]] = None, sizes: Optional[Union[np.ndarray, torch.Tensor]] = None): """Draw single or multiple points. @@ -411,9 +412,46 @@ def draw_points(self, assert positions.shape[-1] == 2, ( 'The shape of `positions` should be (N, 2), ' f'but got {positions.shape}') - colors = color_val_matplotlib(colors) # type: ignore - self.ax_save.scatter( - positions[:, 0], positions[:, 1], c=colors, s=sizes, marker=marker) + if self.backend == 'matplotlib': + assert isinstance(marker, str) or marker is None, ( + 'The type of `marker` in `matplotlib` should be str, ' + f'but got {type(marker)}') + colors = color_val_matplotlib(colors) # type: ignore + self.ax_save.scatter( + positions[:, 0], + positions[:, 1], + c=colors, + s=sizes, + marker=marker) + else: + positions = positions.tolist() + if isinstance(colors, str): + colors = [colors] * len(positions) + colors = color_val_opencv(colors) + if sizes is not None: + sizes = tensor2ndarray(sizes).tolist() + if marker is None: + for i, pos in enumerate(positions): + pos = (int(pos[0]), int(pos[1])) + cv2.circle( + img=self._image, + center=pos, + radius=int(sizes[i]) if sizes is not None else 6, + color=colors[i], + thickness=-1) + else: + assert isinstance( + marker, + int), ('The type of `marker` in `cv2` should be int, ' + f'but got {type(marker)}') + for i, pos in enumerate(positions): + cv2.drawMarker( + img=self._image, + position=pos, + color=colors[i], + markerType=marker, + markerSize=int(sizes[i]) if sizes is not None else 20) + return self @master_only @@ -559,6 +597,8 @@ def draw_texts( family=font_families[i], fontproperties=font_properties[i], color=colors[i]) + else: + pass return self @master_only From 35c5aa52373819a190bb39a80b3ad180febec80b Mon Sep 17 00:00:00 2001 From: KerwinKai <101576779+KerwinKai@users.noreply.github.com> Date: Thu, 3 Aug 2023 14:44:03 +0800 Subject: [PATCH 08/46] update function draw_text and fix example --- mmengine/visualization/visualizer.py | 106 +++++++++++++++++++-------- 1 file changed, 74 insertions(+), 32 deletions(-) diff --git a/mmengine/visualization/visualizer.py b/mmengine/visualization/visualizer.py index 1d5ef16a06..5dd9b742b3 100644 --- a/mmengine/visualization/visualizer.py +++ b/mmengine/visualization/visualizer.py @@ -99,12 +99,13 @@ class Visualizer(ManagerMixin): >>> vis.draw_lines(x_datas=np.array([[1, 3], [2, 4]]), >>> y_datas=np.array([[1, 3], [2, 4]]), >>> colors=['r', 'r'], line_widths=[1, 2]) - >>> vis.draw_texts(text='MMEngine', - >>> position=np.array([2, 2]), + >>> vis.draw_texts(texts='MMEngine', + >>> positions=np.array([2, 2]), >>> colors='b') - >>> vis.draw_texts(text=['MMEngine','OpenMMLab'], - >>> position=np.array([[2, 2], [5, 5]]), - >>> colors=['b', 'b']) + >>> vis.draw_texts(texts=['MMEngine','OpenMMLab'], + >>> positions=np.array([[2, 2], [5, 5]]), + >>> colors=['b', 'b'], + >>> bboxes=dict(facecolor='r', alpha=0.6)) >>> vis.draw_circles(center=np.array([2, 2]), radius=np.array([1])) >>> vis.draw_circles(center=np.array([[2, 2], [3, 5]]), >>> radius=np.array([1, 2]), edge_colors=['g', 'r']) @@ -459,11 +460,11 @@ def draw_texts( self, texts: Union[str, List[str]], positions: Union[np.ndarray, torch.Tensor], - font_sizes: Optional[Union[int, List[int]]] = None, - colors: Union[str, tuple, List[str], List[tuple]] = 'g', + font_sizes: Optional[Union[int, float, List[int], List[float]]] = None, + colors: Union[str, tuple, List[Union[str, tuple]]] = 'g', vertical_alignments: Union[str, List[str]] = 'top', horizontal_alignments: Union[str, List[str]] = 'left', - font_families: Union[str, List[str]] = 'sans-serif', + font_families: Union[str, int, List[str], List[int]] = 'sans-serif', bboxes: Optional[Union[dict, List[dict]]] = None, font_properties: Optional[Union['FontProperties', List['FontProperties']]] = None @@ -529,7 +530,6 @@ def draw_texts( Defaults to None. `New in version 0.6.0.` """ # noqa: E501 - from matplotlib.font_manager import FontProperties check_type('texts', texts, (str, list)) if isinstance(texts, str): texts = [texts] @@ -555,28 +555,6 @@ def draw_texts( check_type_and_length('colors', colors, (str, tuple, list), num_text) colors = value2list(colors, (str, tuple), num_text) - colors = color_val_matplotlib(colors) # type: ignore - - check_type_and_length('vertical_alignments', vertical_alignments, - (str, list), num_text) - vertical_alignments = value2list(vertical_alignments, str, num_text) - - check_type_and_length('horizontal_alignments', horizontal_alignments, - (str, list), num_text) - horizontal_alignments = value2list(horizontal_alignments, str, - num_text) - - check_type_and_length('font_families', font_families, (str, list), - num_text) - font_families = value2list(font_families, str, num_text) - - if font_properties is None: - font_properties = [None for _ in range(num_text)] # type: ignore - else: - check_type_and_length('font_properties', font_properties, - (FontProperties, list), num_text) - font_properties = value2list(font_properties, FontProperties, - num_text) if bboxes is None: bboxes = [None for _ in range(num_text)] # type: ignore @@ -585,6 +563,28 @@ def draw_texts( bboxes = value2list(bboxes, dict, num_text) if self.backend == 'matplotlib': + from matplotlib.font_manager import FontProperties + colors = color_val_matplotlib(colors) # type: ignore + check_type_and_length('vertical_alignments', vertical_alignments, + (str, list), num_text) + vertical_alignments = value2list(vertical_alignments, str, + num_text) + + check_type_and_length('horizontal_alignments', + horizontal_alignments, (str, list), num_text) + horizontal_alignments = value2list(horizontal_alignments, str, + num_text) + check_type_and_length('font_families', font_families, (str, list), + num_text) + font_families = value2list(font_families, str, num_text) + if font_properties is None: + font_properties = [None + for _ in range(num_text)] # type: ignore + else: + check_type_and_length('font_properties', font_properties, + (FontProperties, list), num_text) + font_properties = value2list(font_properties, FontProperties, + num_text) for i in range(num_text): self.ax_save.text( positions[i][0], @@ -598,7 +598,49 @@ def draw_texts( fontproperties=font_properties[i], color=colors[i]) else: - pass + warnings.warn( + 'When using cv2 as the backend for visualizer, ' + 'because `cv.putText(img, text, org, fontFace, ' + 'fontScale, color[, thickness[, lineType[, ' + 'bottomLeftOrigin]]])->img`, ' + 'the parameters `fontproperties`、' + '`vertical_alignments` and `horizontal_alignments` ' + 'will be discarded and not called.', UserWarning) + colors = color_val_opencv(colors) + if font_families == 'sans-serif': + font_families = cv2.FONT_HERSHEY_SIMPLEX + font_families = value2list(font_families, int, num_text) + font_sizes = [font_size / 20.0 for font_size in font_sizes] + for i in range(num_text): + (text_width, text_height) = cv2.getTextSize( + texts[i], font_families[i], font_sizes[i], thickness=2)[0] + pos = (int(positions[i][0]), + int(positions[i][1] + text_height)) + cv2.putText( + self._image, + text=texts[i], + org=pos, + color=colors[i], + fontFace=font_families[i], + fontScale=font_sizes[i], + thickness=2) + if bboxes is not None: + x1 = int(positions[i][0]) + y1 = int(positions[i][1] - 5 * font_sizes[i]) + x2 = int(x1 + text_width) + y2 = int(y1 + text_height + 15 * font_sizes[i]) + self.draw_bboxes( + bboxes=np.array([x1, y1, x2, y2]), + line_styles=bboxes[i]['linestyle'] + if 'linestyle' in bboxes[i] else None, + line_widths=bboxes[i]['linewidth'] + if 'linewidth ' in bboxes[i] else 2, + edge_colors=bboxes[i]['edgecolor'] + if 'edgecolor' in bboxes[i] else 'g', + face_colors=bboxes[i]['facecolor'] + if 'facecolor' in bboxes[i] else 'none', + alpha=bboxes[i]['alpha'] + if 'alpha' in bboxes[i] else 1) return self @master_only From 29ade87ef666f901e2d912f3a0848567b6880d60 Mon Sep 17 00:00:00 2001 From: KerwinKai <101576779+KerwinKai@users.noreply.github.com> Date: Thu, 3 Aug 2023 15:28:40 +0800 Subject: [PATCH 09/46] add pytest and fix code --- mmengine/visualization/visualizer.py | 14 ++- tests/test_visualizer/test_visualizer.py | 153 +++++++++++++++-------- 2 files changed, 113 insertions(+), 54 deletions(-) diff --git a/mmengine/visualization/visualizer.py b/mmengine/visualization/visualizer.py index 5dd9b742b3..ceca492007 100644 --- a/mmengine/visualization/visualizer.py +++ b/mmengine/visualization/visualizer.py @@ -430,7 +430,7 @@ def draw_points(self, colors = [colors] * len(positions) colors = color_val_opencv(colors) if sizes is not None: - sizes = tensor2ndarray(sizes).tolist() + sizes = tensor2ndarray(sizes) if marker is None: for i, pos in enumerate(positions): pos = (int(pos[0]), int(pos[1])) @@ -624,7 +624,7 @@ def draw_texts( fontFace=font_families[i], fontScale=font_sizes[i], thickness=2) - if bboxes is not None: + if bboxes[i] is not None: x1 = int(positions[i][0]) y1 = int(positions[i][1] - 5 * font_sizes[i]) x2 = int(x1 + text_width) @@ -634,7 +634,7 @@ def draw_texts( line_styles=bboxes[i]['linestyle'] if 'linestyle' in bboxes[i] else None, line_widths=bboxes[i]['linewidth'] - if 'linewidth ' in bboxes[i] else 2, + if 'linewidth' in bboxes[i] else 2, edge_colors=bboxes[i]['edgecolor'] if 'edgecolor' in bboxes[i] else 'g', face_colors=bboxes[i]['facecolor'] @@ -918,6 +918,8 @@ def draw_bboxes( edge_colors = color_val_opencv(edge_colors) if line_styles is None: line_styles = [cv2.LINE_8 for _ in range(len(bboxes))] + if isinstance(line_styles, int): + line_styles = [line_styles] * len(bboxes) check_type_and_length('line_styles', line_styles, (int, list), len(bboxes)) check_type_and_length('line_widths', line_widths, @@ -928,8 +930,8 @@ def draw_bboxes( face_colors = [face_colors] * len(bboxes) face_colors = color_val_opencv(face_colors) for i, bbox in enumerate(bboxes): - pt1 = (bbox[0], bbox[1]) - pt2 = (bbox[2], bbox[3]) + pt1 = (int(bbox[0]), int(bbox[1])) + pt2 = (int(bbox[2]), int(bbox[3])) if face_colors != 'none': cv2.rectangle( img=self._image, @@ -1029,6 +1031,8 @@ def draw_polygons( edge_colors = color_val_opencv(edge_colors) if line_styles is None: line_styles = [cv2.LINE_8 for _ in range(len(polygons))] + if isinstance(line_styles, int): + line_styles = [line_styles] * len(polygons) check_type_and_length('line_styles', line_styles, (int, list), len(polygons)) check_type_and_length('line_widths', line_widths, diff --git a/tests/test_visualizer/test_visualizer.py b/tests/test_visualizer/test_visualizer.py index e2e13b3d13..fb6514fc47 100644 --- a/tests/test_visualizer/test_visualizer.py +++ b/tests/test_visualizer/test_visualizer.py @@ -5,10 +5,12 @@ from unittest import TestCase from unittest.mock import MagicMock, call, patch +import cv2 import numpy as np import pytest import torch import torch.nn as nn +from parameterized import parameterized from mmengine import VISBACKENDS, Config from mmengine.visualization import Visualizer @@ -64,8 +66,9 @@ def setUp(self): dict(type='MockVisBackend', name='mock2') ] - def test_init(self): - visualizer = Visualizer(image=self.image) + @parameterized.expand([['cv2'], ['matplotlib']]) + def test_init(self, backend): + visualizer = Visualizer(image=self.image, backend=backend) visualizer.get_image() # build visualizer without `save_dir` @@ -120,26 +123,36 @@ def test_init(self): visualizer_any = Visualizer.get_instance(instance_name) assert visualizer_any == visualizer - def test_set_image(self): - visualizer = Visualizer() + @parameterized.expand([['cv2'], ['matplotlib']]) + def test_set_image(self, backend): + visualizer = Visualizer(backend=backend) visualizer.set_image(self.image) with pytest.raises(AssertionError): visualizer.set_image(None) - def test_get_image(self): - visualizer = Visualizer(image=self.image) + @parameterized.expand([['cv2'], ['matplotlib']]) + def test_get_image(self, backend): + visualizer = Visualizer(image=self.image, backend=backend) visualizer.get_image() - def test_draw_bboxes(self): - visualizer = Visualizer(image=self.image) + @parameterized.expand([['cv2'], ['matplotlib']]) + def test_draw_bboxes(self, backend): + visualizer = Visualizer(image=self.image, backend=backend) # only support 4 or nx4 tensor and numpy visualizer.draw_bboxes(torch.tensor([1, 1, 2, 2])) # valid bbox visualizer.draw_bboxes(torch.tensor([1, 1, 1, 2])) bboxes = torch.tensor([[1, 1, 2, 2], [1, 2, 2, 2.5]]) + if backend == 'matplotlib': + line_styles = '-' + else: + line_styles = cv2.LINE_4 visualizer.draw_bboxes( - bboxes, alpha=0.5, edge_colors=(255, 0, 0), line_styles='-') + bboxes, + alpha=0.5, + edge_colors=(255, 0, 0), + line_styles=line_styles) bboxes = bboxes.numpy() visualizer.draw_bboxes(bboxes) @@ -159,9 +172,11 @@ def test_draw_bboxes(self): with pytest.raises(TypeError): visualizer.draw_bboxes([1, 1, 2, 2]) - def test_close(self): + @parameterized.expand([['cv2'], ['matplotlib']]) + def test_close(self, backend): visualizer = Visualizer( image=self.image, + backend=backend, vis_backends=copy.deepcopy(self.vis_backend_cfg), save_dir='temp_dir') @@ -171,8 +186,9 @@ def test_close(self): for name in ['mock1', 'mock2']: assert visualizer.get_backend(name)._close is True - def test_draw_points(self): - visualizer = Visualizer(image=self.image) + @parameterized.expand([['cv2'], ['matplotlib']]) + def test_draw_points(self, backend): + visualizer = Visualizer(image=self.image, backend=backend) with pytest.raises(TypeError): visualizer.draw_points(positions=[1, 2]) @@ -182,14 +198,19 @@ def test_draw_points(self): visualizer.draw_points( positions=torch.tensor([[1, 1], [3, 3]]), colors=['g', (255, 255, 0)]) + if backend == 'matplotlib': + marker = '.' + else: + marker = cv2.MARKER_CROSS visualizer.draw_points( positions=torch.tensor([[1, 1], [3, 3]]), colors=['g', (255, 255, 0)], - marker='.', + marker=marker, sizes=[1, 5]) - def test_draw_texts(self): - visualizer = Visualizer(image=self.image) + @parameterized.expand([['cv2'], ['matplotlib']]) + def test_draw_texts(self, backend): + visualizer = Visualizer(image=self.image, backend=backend) # only support tensor and numpy visualizer.draw_texts( @@ -226,14 +247,16 @@ def test_draw_texts(self): visualizer.draw_texts(['text1', 'test2'], positions=torch.tensor([[5, 5], [3, 3]]), colors=['r']) - with pytest.raises(AssertionError): - visualizer.draw_texts(['text1', 'test2'], - positions=torch.tensor([[5, 5], [3, 3]]), - vertical_alignments=['top']) - with pytest.raises(AssertionError): - visualizer.draw_texts(['text1', 'test2'], - positions=torch.tensor([[5, 5], [3, 3]]), - horizontal_alignments=['left']) + if backend == 'matplotlib': + with pytest.raises(AssertionError): + visualizer.draw_texts(['text1', 'test2'], + positions=torch.tensor([[5, 5], [3, 3]]), + vertical_alignments=['top']) + if backend == 'matplotlib': + with pytest.raises(AssertionError): + visualizer.draw_texts(['text1', 'test2'], + positions=torch.tensor([[5, 5], [3, 3]]), + horizontal_alignments=['left']) with pytest.raises(AssertionError): visualizer.draw_texts(['text1', 'test2'], positions=torch.tensor([[5, 5], [3, 3]]), @@ -245,8 +268,9 @@ def test_draw_texts(self): positions=torch.tensor([[5, 5], [3, 3]]), font_sizes='b') - def test_draw_lines(self): - visualizer = Visualizer(image=self.image) + @parameterized.expand([['cv2'], ['matplotlib']]) + def test_draw_lines(self, backend): + visualizer = Visualizer(image=self.image, backend=backend) # only support tensor and numpy visualizer.draw_lines( @@ -254,11 +278,15 @@ def test_draw_lines(self): visualizer.draw_lines( x_datas=np.array([[1, 5], [2, 4]]), y_datas=np.array([[2, 6], [4, 7]])) + if backend == 'matplotlib': + line_styles = ['-', '-.'] + else: + line_styles = [cv2.LINE_4, cv2.LINE_AA] visualizer.draw_lines( x_datas=np.array([[1, 5], [2, 4]]), y_datas=np.array([[2, 6], [4, 7]]), colors='r', - line_styles=['-', '-.'], + line_styles=line_styles, line_widths=[1, 2]) # test out of bounds with pytest.warns( @@ -280,8 +308,9 @@ def test_draw_lines(self): x_datas=torch.tensor([1, 5]), y_datas=torch.tensor([[2, 6], [4, 7]])) - def test_draw_circles(self): - visualizer = Visualizer(image=self.image) + @parameterized.expand([['cv2'], ['matplotlib']]) + def test_draw_circles(self, backend): + visualizer = Visualizer(image=self.image, backend=backend) # only support tensor and numpy visualizer.draw_circles(torch.tensor([1, 5]), torch.tensor([1])) @@ -297,11 +326,15 @@ def test_draw_circles(self): edge_colors=(255, 0, 0)) # test config + if backend == 'matplotlib': + line_styles = ['-', '-.'] + else: + line_styles = [cv2.LINE_4, cv2.LINE_AA] visualizer.draw_circles( torch.tensor([[1, 5], [2, 6]]), radius=torch.tensor([1, 2]), edge_colors=['g', 'r'], - line_styles=['-', '-.'], + line_styles=line_styles, line_widths=[1, 2]) # test out of bounds @@ -325,8 +358,9 @@ def test_draw_circles(self): visualizer.draw_circles( torch.tensor([[1, 5]]), radius=torch.tensor([1, 2])) - def test_draw_polygons(self): - visualizer = Visualizer(image=self.image) + @parameterized.expand([['cv2'], ['matplotlib']]) + def test_draw_polygons(self, backend): + visualizer = Visualizer(image=self.image, backend=backend) # shape Nx2 or list[Nx2] visualizer.draw_polygons(torch.tensor([[1, 1], [2, 2], [3, 4]])) visualizer.draw_polygons(np.array([[1, 1], [2, 2], [3, 4]])) @@ -341,13 +375,17 @@ def test_draw_polygons(self): ], face_colors=(255, 0, 0), edge_colors=(255, 0, 0)) + if backend == 'matplotlib': + line_styles = '-' + else: + line_styles = cv2.LINE_AA visualizer.draw_polygons( polygons=[ np.array([[1, 1], [2, 2], [3, 4]]), torch.tensor([[1, 1], [2, 2], [3, 4]]) ], edge_colors=['r', 'g'], - line_styles='-', + line_styles=line_styles, line_widths=[2, 1]) # test out of bounds @@ -357,14 +395,15 @@ def test_draw_polygons(self): ' the drawn polygon may not be in the image'): visualizer.draw_polygons(torch.tensor([[1, 1], [2, 2], [16, 4]])) - def test_draw_binary_masks(self): + @parameterized.expand([['cv2'], ['matplotlib']]) + def test_draw_binary_masks(self, backend): binary_mask = np.random.randint(0, 2, size=(10, 10)).astype(bool) - visualizer = Visualizer(image=self.image) + visualizer = Visualizer(image=self.image, backend=backend) visualizer.draw_binary_masks(binary_mask) visualizer.draw_binary_masks(torch.from_numpy(binary_mask)) # multi binary binary_mask = np.random.randint(0, 2, size=(2, 10, 10)).astype(bool) - visualizer = Visualizer(image=self.image) + visualizer = Visualizer(image=self.image, backend=backend) visualizer.draw_binary_masks(binary_mask, colors=['r', (0, 255, 0)]) # test the error that the size of mask and image are different. with pytest.raises(AssertionError): @@ -384,8 +423,9 @@ def test_draw_binary_masks(self): with pytest.raises(AssertionError): visualizer.draw_binary_masks(binary_mask) - def test_draw_featmap(self): - visualizer = Visualizer() + @parameterized.expand([['cv2'], ['matplotlib']]) + def test_draw_featmap(self, backend): + visualizer = Visualizer(backend=backend) image = np.random.randint(0, 256, size=(3, 3, 3), dtype='uint8') # must be Tensor @@ -487,8 +527,9 @@ def test_draw_featmap(self): arrangement=(2, 2)) assert featmap.shape[:2] == (6, 6) - def test_chain_call(self): - visualizer = Visualizer(image=self.image) + @parameterized.expand([['cv2'], ['matplotlib']]) + def test_chain_call(self, backend): + visualizer = Visualizer(image=self.image, backend=backend) binary_mask = np.random.randint(0, 2, size=(10, 10)).astype(bool) visualizer.draw_bboxes(torch.tensor([1, 1, 2, 2])). \ draw_texts('test', torch.tensor([5, 5])). \ @@ -498,16 +539,20 @@ def test_chain_call(self): draw_polygons(torch.tensor([[1, 1], [2, 2], [3, 4]])). \ draw_binary_masks(binary_mask) - def test_get_backend(self): + @parameterized.expand([['cv2'], ['matplotlib']]) + def test_get_backend(self, backend): visualizer = Visualizer( image=self.image, + backend=backend, vis_backends=copy.deepcopy(self.vis_backend_cfg), save_dir='temp_dir') for name in ['mock1', 'mock2']: assert isinstance(visualizer.get_backend(name), MockVisBackend) - def test_add_config(self): + @parameterized.expand([['cv2'], ['matplotlib']]) + def test_add_config(self, backend): visualizer = Visualizer( + backend=backend, vis_backends=copy.deepcopy(self.vis_backend_cfg), save_dir='temp_dir') @@ -516,8 +561,10 @@ def test_add_config(self): for name in ['mock1', 'mock2']: assert visualizer.get_backend(name)._add_config is True - def test_add_graph(self): + @parameterized.expand([['cv2'], ['matplotlib']]) + def test_add_graph(self, backend): visualizer = Visualizer( + backend=backend, vis_backends=copy.deepcopy(self.vis_backend_cfg), save_dir='temp_dir') @@ -534,9 +581,11 @@ def forward(self, x, y=None): for name in ['mock1', 'mock2']: assert visualizer.get_backend(name)._add_graph is True - def test_add_image(self): + @parameterized.expand([['cv2'], ['matplotlib']]) + def test_add_image(self, backend): image = np.random.randint(0, 256, size=(10, 10, 3)).astype(np.uint8) visualizer = Visualizer( + backend=backend, vis_backends=copy.deepcopy(self.vis_backend_cfg), save_dir='temp_dir') @@ -544,16 +593,20 @@ def test_add_image(self): for name in ['mock1', 'mock2']: assert visualizer.get_backend(name)._add_image is True - def test_add_scalar(self): + @parameterized.expand([['cv2'], ['matplotlib']]) + def test_add_scalar(self, backend): visualizer = Visualizer( + backend=backend, vis_backends=copy.deepcopy(self.vis_backend_cfg), save_dir='temp_dir') visualizer.add_scalar('map', 0.9, step=0) for name in ['mock1', 'mock2']: assert visualizer.get_backend(name)._add_scalar is True - def test_add_scalars(self): + @parameterized.expand([['cv2'], ['matplotlib']]) + def test_add_scalars(self, backend): visualizer = Visualizer( + backend=backend, vis_backends=copy.deepcopy(self.vis_backend_cfg), save_dir='temp_dir') input_dict = {'map': 0.7, 'acc': 0.9} @@ -573,15 +626,17 @@ def __init__(self, name): visualizer3 = DetLocalVisualizer.get_current_instance() assert id(visualizer1) == id(visualizer2) == id(visualizer3) - def test_data_info(self): - visualizer = Visualizer() + @parameterized.expand([['cv2'], ['matplotlib']]) + def test_data_info(self, backend): + visualizer = Visualizer(backend=backend) visualizer.dataset_meta = {'class': 'cat'} assert visualizer.dataset_meta['class'] == 'cat' - def test_show(self): + @parameterized.expand([['cv2'], ['matplotlib']]) + def test_show(self, backend): cv2 = MagicMock() wait_continue = MagicMock() - visualizer = Visualizer('test_show') + visualizer = Visualizer('test_show', backend=backend) img = np.ones([1, 1, 1]) with patch('mmengine.visualization.visualizer.cv2', cv2), \ patch('mmengine.visualization.visualizer.wait_continue', From 3aff1b807a06e11cb3c7b13b46302e34706651a4 Mon Sep 17 00:00:00 2001 From: Longkai Cheng Date: Sun, 6 Aug 2023 21:04:07 +0800 Subject: [PATCH 10/46] Update mmengine/visualization/utils.py Co-authored-by: Mashiro <57566630+HAOCHENYE@users.noreply.github.com> --- mmengine/visualization/utils.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/mmengine/visualization/utils.py b/mmengine/visualization/utils.py index 07d0c5b139..1097fa9648 100644 --- a/mmengine/visualization/utils.py +++ b/mmengine/visualization/utils.py @@ -122,7 +122,7 @@ def color_val_matplotlib( def color_val_opencv( colors: Union[str, tuple, List[Union[str, tuple]]] -) -> Union[str, tuple, List[Union[str, tuple]]]: +) -> Union[tuple, List[tuple]]: """Convert various input in BGR order to normalized BGR opencv color tuples, Args: From 4aaaa5a24a046ebcbfc291230a0153f40bfb6795 Mon Sep 17 00:00:00 2001 From: Longkai Cheng Date: Sun, 6 Aug 2023 21:04:37 +0800 Subject: [PATCH 11/46] Update mmengine/visualization/visualizer.py Co-authored-by: Mashiro <57566630+HAOCHENYE@users.noreply.github.com> --- mmengine/visualization/visualizer.py | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/mmengine/visualization/visualizer.py b/mmengine/visualization/visualizer.py index ceca492007..1dc049ff2f 100644 --- a/mmengine/visualization/visualizer.py +++ b/mmengine/visualization/visualizer.py @@ -431,14 +431,16 @@ def draw_points(self, colors = color_val_opencv(colors) if sizes is not None: sizes = tensor2ndarray(sizes) + else: + sizes = [6] * len(positions) if marker is None: - for i, pos in enumerate(positions): + for pos, color, size in zip(positions, colors, sizes): pos = (int(pos[0]), int(pos[1])) cv2.circle( img=self._image, center=pos, - radius=int(sizes[i]) if sizes is not None else 6, - color=colors[i], + radius=int(size, + color=colors, thickness=-1) else: assert isinstance( From 6cb5cf9e40c4627f14d3706856c9a3fe93d8dab8 Mon Sep 17 00:00:00 2001 From: Longkai Cheng Date: Sun, 6 Aug 2023 21:05:31 +0800 Subject: [PATCH 12/46] Update mmengine/visualization/visualizer.py Co-authored-by: Mashiro <57566630+HAOCHENYE@users.noreply.github.com> --- mmengine/visualization/visualizer.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/mmengine/visualization/visualizer.py b/mmengine/visualization/visualizer.py index 1dc049ff2f..00dd0f4e9f 100644 --- a/mmengine/visualization/visualizer.py +++ b/mmengine/visualization/visualizer.py @@ -819,7 +819,7 @@ def draw_circles( edge_colors = color_val_opencv(edge_colors) if line_styles is None: line_styles = [cv2.LINE_8 for _ in range(len(circles))] - check_type_and_length('line_styles', line_styles, (int, list), + check_type_and_length('circles', line_styles, (int, list), len(circles)) check_type_and_length('line_widths', line_widths, (int, float, list), len(line_widths)) From a5a03ae2d26c7eec749f373c1f5697459b01eae7 Mon Sep 17 00:00:00 2001 From: Longkai Cheng Date: Sun, 6 Aug 2023 21:05:38 +0800 Subject: [PATCH 13/46] Update mmengine/visualization/visualizer.py Co-authored-by: Mashiro <57566630+HAOCHENYE@users.noreply.github.com> --- mmengine/visualization/visualizer.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/mmengine/visualization/visualizer.py b/mmengine/visualization/visualizer.py index 00dd0f4e9f..ea8fe9e7d4 100644 --- a/mmengine/visualization/visualizer.py +++ b/mmengine/visualization/visualizer.py @@ -822,7 +822,7 @@ def draw_circles( check_type_and_length('circles', line_styles, (int, list), len(circles)) check_type_and_length('line_widths', line_widths, - (int, float, list), len(line_widths)) + (int, float, list), len(circles)) overlay = self._image.copy() if face_colors != 'none': if isinstance(face_colors, str): From efc30b8d04606cda9882cdc033f9b1d93b2782b9 Mon Sep 17 00:00:00 2001 From: Longkai Cheng Date: Sun, 6 Aug 2023 21:15:22 +0800 Subject: [PATCH 14/46] Update mmengine/visualization/visualizer.py Co-authored-by: Mashiro <57566630+HAOCHENYE@users.noreply.github.com> --- mmengine/visualization/visualizer.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/mmengine/visualization/visualizer.py b/mmengine/visualization/visualizer.py index ea8fe9e7d4..db8fa8cf06 100644 --- a/mmengine/visualization/visualizer.py +++ b/mmengine/visualization/visualizer.py @@ -466,7 +466,7 @@ def draw_texts( colors: Union[str, tuple, List[Union[str, tuple]]] = 'g', vertical_alignments: Union[str, List[str]] = 'top', horizontal_alignments: Union[str, List[str]] = 'left', - font_families: Union[str, int, List[str], List[int]] = 'sans-serif', + font_families: Union[str, int, List[Union[str, int]] = 'sans-serif', bboxes: Optional[Union[dict, List[dict]]] = None, font_properties: Optional[Union['FontProperties', List['FontProperties']]] = None From 4ec978b31cd39ad378c4106f7ed7281f654320d7 Mon Sep 17 00:00:00 2001 From: KerwinKai <101576779+KerwinKai@users.noreply.github.com> Date: Sun, 6 Aug 2023 23:03:21 +0800 Subject: [PATCH 15/46] fix my code mention above --- mmengine/visualization/utils.py | 2 +- mmengine/visualization/visualizer.py | 138 ++++++++++++++------------- 2 files changed, 75 insertions(+), 65 deletions(-) diff --git a/mmengine/visualization/utils.py b/mmengine/visualization/utils.py index 1097fa9648..e14005fdf3 100644 --- a/mmengine/visualization/utils.py +++ b/mmengine/visualization/utils.py @@ -269,4 +269,4 @@ def img_from_canvas(canvas: 'FigureCanvasAgg') -> np.ndarray: buffer = np.frombuffer(s, dtype='uint8') img_rgba = buffer.reshape(height, width, 4) rgb, alpha = np.split(img_rgba, [3], axis=2) - return rgb.astype('uint8') + return rgb.astype('uint8') \ No newline at end of file diff --git a/mmengine/visualization/visualizer.py b/mmengine/visualization/visualizer.py index db8fa8cf06..ef2eb1f43d 100644 --- a/mmengine/visualization/visualizer.py +++ b/mmengine/visualization/visualizer.py @@ -426,34 +426,36 @@ def draw_points(self, marker=marker) else: positions = positions.tolist() - if isinstance(colors, str): + if isinstance(colors, str) or isinstance(colors, tuple): colors = [colors] * len(positions) colors = color_val_opencv(colors) if sizes is not None: sizes = tensor2ndarray(sizes) - else: - sizes = [6] * len(positions) if marker is None: - for pos, color, size in zip(positions, colors, sizes): + for i, pos in enumerate(positions): pos = (int(pos[0]), int(pos[1])) cv2.circle( img=self._image, center=pos, - radius=int(size, - color=colors, + radius=int(sizes[i]) if sizes is not None else 6, + color=colors[i], thickness=-1) else: assert isinstance( marker, int), ('The type of `marker` in `cv2` should be int, ' f'but got {type(marker)}') - for i, pos in enumerate(positions): - cv2.drawMarker( - img=self._image, - position=pos, - color=colors[i], - markerType=marker, - markerSize=int(sizes[i]) if sizes is not None else 20) + if sizes is None: + sizes = [20] * len(positions) + for pos, color, size in zip(positions, colors, sizes): + kwargs = { + 'img': self._image, + 'position': pos, + 'color': color, + 'markerType': marker, + 'markerSize': int(size) + } + cv2.drawMarker(**kwargs) return self @@ -466,7 +468,7 @@ def draw_texts( colors: Union[str, tuple, List[Union[str, tuple]]] = 'g', vertical_alignments: Union[str, List[str]] = 'top', horizontal_alignments: Union[str, List[str]] = 'left', - font_families: Union[str, int, List[Union[str, int]] = 'sans-serif', + font_families: Union[str, int, List[Union[str, int]]] = 'sans-serif', bboxes: Optional[Union[dict, List[dict]]] = None, font_properties: Optional[Union['FontProperties', List['FontProperties']]] = None @@ -819,31 +821,33 @@ def draw_circles( edge_colors = color_val_opencv(edge_colors) if line_styles is None: line_styles = [cv2.LINE_8 for _ in range(len(circles))] - check_type_and_length('circles', line_styles, (int, list), + check_type_and_length('line_styles', line_styles, (int, list), len(circles)) check_type_and_length('line_widths', line_widths, - (int, float, list), len(circles)) + (int, float, list), len(line_widths)) overlay = self._image.copy() if face_colors != 'none': if isinstance(face_colors, str): face_colors = [face_colors] * len(circles) face_colors = color_val_opencv(face_colors) - for i in range(len(circles)): - if face_colors != 'none': - cv2.circle( - img=self._image, - center=(int(center[i][0]), int(center[i][1])), - radius=int(radius[i]), - color=face_colors[i], - lineType=int(line_styles[i]), - thickness=-1) - cv2.circle( - img=self._image, - center=(int(center[i][0]), int(center[i][1])), - radius=int(radius[i]), - color=edge_colors[i], - lineType=int(line_styles[i]), - thickness=int(line_widths[i])) + else: + face_colors = ['None'] * len(circles) + for ct, radiu, line_style, face_color, edge_color, line_width in \ + zip(center, radius, line_styles, + face_colors, edge_colors, line_widths): + kwargs = { + 'img': self._image, + 'center': (int(ct[0]), int(ct[1])), + 'radius': int(radiu), + 'lineType': int(line_style) + } + if face_color != 'None': + kwargs['color'] = face_color + kwargs['thickness'] = -1 + cv2.circle(**kwargs) + kwargs['color'] = edge_color + kwargs['thickness'] = int(line_width) + cv2.circle(**kwargs) cv2.addWeighted(self._image, alpha, overlay, 1 - alpha, 0, self._image) return self @@ -931,25 +935,28 @@ def draw_bboxes( if isinstance(face_colors, str): face_colors = [face_colors] * len(bboxes) face_colors = color_val_opencv(face_colors) - for i, bbox in enumerate(bboxes): + else: + face_colors = ['None'] * len(bboxes) + if not isinstance(line_widths, list): + line_widths = [line_widths] * len(bboxes) + for bbox, edge_color, face_color, line_style, line_width in zip( + bboxes, edge_colors, face_colors, line_styles, + line_widths): pt1 = (int(bbox[0]), int(bbox[1])) pt2 = (int(bbox[2]), int(bbox[3])) - if face_colors != 'none': - cv2.rectangle( - img=self._image, - pt1=pt1, - pt2=pt2, - color=face_colors[i], - lineType=line_styles[i], - thickness=-1) - cv2.rectangle( - img=self._image, - pt1=pt1, - pt2=pt2, - color=edge_colors[i], - lineType=line_styles[i], - thickness=int(line_widths[i]) if isinstance( - line_widths, list) else int(line_widths)) + kwargs = { + 'img': self._image, + 'pt1': pt1, + 'pt2': pt2, + 'lineType': line_style + } + if face_color != 'None': + kwargs['color'] = face_color + kwargs['thickness'] = -1 + cv2.rectangle(**kwargs) + kwargs['color'] = edge_color + kwargs['thickness'] = line_width + cv2.rectangle(**kwargs) cv2.addWeighted(self._image, alpha, overlay, 1 - alpha, 0, self._image) return self @@ -1044,21 +1051,24 @@ def draw_polygons( if isinstance(face_colors, str): face_colors = [face_colors] * len(polygons) face_colors = color_val_opencv(face_colors) - for i, polygon in enumerate(polygons): + else: + face_colors = ['None'] * len(polygons) + for polygon, line_style, face_color, edge_color, line_width in zip( + polygons, line_styles, face_colors, edge_colors, + line_widths): polygon = polygon.reshape((-1, 1, 2)) - if face_colors != 'none': - cv2.fillPoly( - img=self._image, - pts=[polygon], - color=face_colors[i], - lineType=line_styles[i]) - cv2.polylines( - img=self._image, - pts=[polygon], - isClosed=True, - color=edge_colors[i], - lineType=line_styles[i], - thickness=line_widths[i]) + kwargs = { + 'img': self._image, + 'pts': [polygon], + 'lineType': line_style + } + if face_color != 'None': + kwargs['color'] = face_color, + cv2.fillPoly(**kwargs) + kwargs['isClosed'] = True + kwargs['color'] = edge_color + kwargs['thickness'] = line_width + cv2.polylines(**kwargs) cv2.addWeighted(self._image, alpha, overlay, 1 - alpha, 0, self._image) return self @@ -1403,4 +1413,4 @@ def get_instance(cls, name: str, **kwargs) -> 'Visualizer': """ instance = super().get_instance(name, **kwargs) Visualizer._instance_dict[name] = instance - return instance + return instance \ No newline at end of file From b4f7881528f9a422cface7ab66ed42557a8995c8 Mon Sep 17 00:00:00 2001 From: KerwinKai <101576779+KerwinKai@users.noreply.github.com> Date: Mon, 7 Aug 2023 09:14:25 +0800 Subject: [PATCH 16/46] update the docstring --- mmengine/visualization/utils.py | 6 +-- mmengine/visualization/visualizer.py | 66 +++++++++++++++++----------- 2 files changed, 44 insertions(+), 28 deletions(-) diff --git a/mmengine/visualization/utils.py b/mmengine/visualization/utils.py index e14005fdf3..91bbe0e798 100644 --- a/mmengine/visualization/utils.py +++ b/mmengine/visualization/utils.py @@ -122,8 +122,8 @@ def color_val_matplotlib( def color_val_opencv( colors: Union[str, tuple, List[Union[str, tuple]]] -) -> Union[tuple, List[tuple]]: - """Convert various input in BGR order to normalized BGR opencv color +) -> Union[str, tuple, List[Union[str, tuple]]]: + """Convert various input in BGR order to BGR opencv color tuples, Args: colors (Union[str, tuple, List[Union[str, tuple]]]): Color inputs @@ -269,4 +269,4 @@ def img_from_canvas(canvas: 'FigureCanvasAgg') -> np.ndarray: buffer = np.frombuffer(s, dtype='uint8') img_rgba = buffer.reshape(height, width, 4) rgb, alpha = np.split(img_rgba, [3], axis=2) - return rgb.astype('uint8') \ No newline at end of file + return rgb.astype('uint8') diff --git a/mmengine/visualization/visualizer.py b/mmengine/visualization/visualizer.py index ef2eb1f43d..a975066a8d 100644 --- a/mmengine/visualization/visualizer.py +++ b/mmengine/visualization/visualizer.py @@ -389,7 +389,7 @@ def draw_points(self, colors: Union[str, tuple, List[Union[str, tuple]]] = 'g', marker: Optional[Union[str, int]] = None, sizes: Optional[Union[np.ndarray, torch.Tensor]] = None): - """Draw single or multiple points. + """Draw single or multiple points use backend define by self.backend. Args: positions (Union[np.ndarray, torch.Tensor]): Positions to draw. @@ -401,7 +401,9 @@ def draw_points(self, for more details. Defaults to 'g. marker (str, optional): The marker style. See :mod:`matplotlib.markers` for more information about - marker styles. Defaults to None. + marker styles in `matplotlib` backend. See: `cv::MarkerTypes` + for more information about marker styles in `cv2` backend. + Defaults to None. sizes (Optional[Union[np.ndarray, torch.Tensor]]): The marker size. Defaults to None. """ @@ -473,7 +475,8 @@ def draw_texts( font_properties: Optional[Union['FontProperties', List['FontProperties']]] = None ) -> 'Visualizer': - """Draw single or multiple text boxes. + """Draw single or multiple text boxes use backend define by + self.backend. Args: texts (Union[str, List[str]]): Texts to draw. @@ -498,7 +501,7 @@ def draw_texts( texts or just single value. If ``vertical_alignments`` is single value, all the texts will have the same verticalalignment. verticalalignment can be 'center' or - 'top', 'bottom' or 'baseline'. Defaults to 'top'. + 'top', 'bottom' or 'baseline'. This parameter is only valid in the `matplotlib` drawing backend. Defaults to 'top'. horizontal_alignments (Union[str, List[str]]): The horizontalalignment of texts. Horizontalalignment controls whether the x positional argument for the text indicates the @@ -507,13 +510,16 @@ def draw_texts( the same length with texts or just single value. If ``horizontal_alignments`` is single value, all the texts will have the same horizontalalignment. Horizontalalignment - can be 'center','right' or 'left'. Defaults to 'left'. + can be 'center','right' or 'left'. This parameter is only valid in the `matplotlib` drawing backend. Defaults to 'left'. font_families (Union[str, List[str]]): The font family of texts. ``font_families`` can have the same length with texts or just single value. If ``font_families`` is single value, all the texts will have the same font family. font_familiy can be 'serif', 'sans-serif', 'cursive', 'fantasy' - or 'monospace'. Defaults to 'sans-serif'. + or 'monospace' in `matplotlib` backend and 'cv2.FONT_HERSHEY_SIMPLEX', + 'cv2.FONT_HERSHEY_PLAIN', 'cv2.FONT_HERSHEY_DUPLEX', 'cv2.FONT_HERSHEY_COMPLEX', + 'cv2.FONT_HERSHEY_TRIPLEX', 'cv2.FONT_HERSHEY_COMPLEX_SMALL', 'cv2.FONT_HERSHEY_SCRIPT_SIMPLEX', + 'cv2.FONT_HERSHEY_SCRIPT_COMPLEX', 'cv2.FONT_ITALIC' or 'sans-serif' in `cv2` backend. Defaults to 'sans-serif'. bboxes (Union[dict, List[dict]], optional): The bounding box of the texts. If bboxes is None, there are no bounding box around texts. ``bboxes`` can have the same length with texts or @@ -531,7 +537,7 @@ def draw_texts( ``font_properties`` can have the same length with texts or just single value. If ``font_properties`` is single value, all the texts will have the same font properties. - Defaults to None. + Defaults to None. This parameter is only valid in the `matplotlib` drawing backend. `New in version 0.6.0.` """ # noqa: E501 check_type('texts', texts, (str, list)) @@ -614,7 +620,7 @@ def draw_texts( if font_families == 'sans-serif': font_families = cv2.FONT_HERSHEY_SIMPLEX font_families = value2list(font_families, int, num_text) - font_sizes = [font_size / 20.0 for font_size in font_sizes] + font_sizes = [font_size / 22.0 for font_size in font_sizes] for i in range(num_text): (text_width, text_height) = cv2.getTextSize( texts[i], font_families[i], font_sizes[i], thickness=2)[0] @@ -627,12 +633,12 @@ def draw_texts( color=colors[i], fontFace=font_families[i], fontScale=font_sizes[i], - thickness=2) + thickness=1) if bboxes[i] is not None: - x1 = int(positions[i][0]) - y1 = int(positions[i][1] - 5 * font_sizes[i]) - x2 = int(x1 + text_width) - y2 = int(y1 + text_height + 15 * font_sizes[i]) + x1 = int(positions[i][0] - 10 * font_sizes[i]) + y1 = int(positions[i][1] - 10 * font_sizes[i]) + x2 = int(x1 + text_width + 20 * font_sizes[i]) + y2 = int(y1 + text_height + 20 * font_sizes[i]) self.draw_bboxes( bboxes=np.array([x1, y1, x2, y2]), line_styles=bboxes[i]['linestyle'] @@ -656,7 +662,8 @@ def draw_lines( line_styles: Union[str, List[str]] = None, line_widths: Union[Union[int, float], List[Union[int, float]]] = 2 ) -> 'Visualizer': - """Draw single or multiple line segments. + """Draw single or multiple line segments use backend define by + self.backend. Args: x_datas (Union[np.ndarray, torch.Tensor]): The x coordinate of @@ -675,7 +682,9 @@ def draw_lines( value, all the lines will have the same linestyle. Reference to https://matplotlib.org/stable/api/collections_api.html?highlight=collection#matplotlib.collections.AsteriskPolygonCollection.set_linestyle - for more details. Defaults to '-' when backend is 'matplotlib', + for more details when use `matplotlib` backend and + see 'cv::LineTypes' for mare details when use `cv2` backend. + Defaults to '-' when backend is 'matplotlib', and 'cv2.LINE_8' when backend is 'cv2'. line_widths (Union[Union[int, float], List[Union[int, float]]]): The linewidth of lines. ``line_widths`` can have @@ -743,7 +752,7 @@ def draw_circles( face_colors: Union[str, tuple, List[Union[str, tuple]]] = 'none', alpha: Union[float, int] = 0.8, ) -> 'Visualizer': - """Draw single or multiple circles. + """Draw single or multiple circles use backend define by self.backend. Args: center (Union[np.ndarray, torch.Tensor]): The x coordinate of @@ -762,7 +771,9 @@ def draw_circles( value, all the lines will have the same linestyle. Reference to https://matplotlib.org/stable/api/collections_api.html?highlight=collection#matplotlib.collections.AsteriskPolygonCollection.set_linestyle - for more details. Defaults to '-' when backend is 'matplotlib', + for more details when use `matplotlib` backend and + see 'cv::LineTypes' for mare details when use `cv2` backend. + Defaults to '-' when backend is 'matplotlib', and 'cv2.LINE_8' when backend is 'cv2'. line_widths (Union[Union[int, float], List[Union[int, float]]]): The linewidth of lines. ``line_widths`` can have @@ -862,7 +873,7 @@ def draw_bboxes( face_colors: Union[str, tuple, List[Union[str, tuple]]] = 'none', alpha: Union[int, float] = 0.8, ) -> 'Visualizer': - """Draw single or multiple bboxes. + """Draw single or multiple bboxes use backend define by self.backend. Args: bboxes (Union[np.ndarray, torch.Tensor]): The bboxes to draw with @@ -879,7 +890,10 @@ def draw_bboxes( value, all the lines will have the same linestyle. Reference to https://matplotlib.org/stable/api/collections_api.html?highlight=collection#matplotlib.collections.AsteriskPolygonCollection.set_linestyle - for more details. Defaults to '-'. + for more details when use `matplotlib` backend and + see 'cv::LineTypes' for mare details when use `cv2` backend. + Defaults to '-' when backend is 'matplotlib', + and 'cv2.LINE_8' when backend is 'cv2'. line_widths (Union[Union[int, float], List[Union[int, float]]]): The linewidth of lines. ``line_widths`` can have the same length with lines or just single value. @@ -972,7 +986,7 @@ def draw_polygons( face_colors: Union[str, tuple, List[Union[str, tuple]]] = 'none', alpha: Union[int, float] = 0.8, ) -> 'Visualizer': - """Draw single or multiple bboxes. + """Draw single or multiple bboxes use backend define by self.backend. Args: polygons (Union[Union[np.ndarray, torch.Tensor],\ @@ -990,7 +1004,10 @@ def draw_polygons( value, all the lines will have the same linestyle. Reference to https://matplotlib.org/stable/api/collections_api.html?highlight=collection#matplotlib.collections.AsteriskPolygonCollection.set_linestyle - for more details. Defaults to '-'. + for more details when use `matplotlib` backend and + see 'cv::LineTypes' for mare details when use `cv2` backend. + Defaults to '-' when backend is 'matplotlib', + and 'cv2.LINE_8' when backend is 'cv2'. line_widths (Union[Union[int, float], List[Union[int, float]]]): The linewidth of lines. ``line_widths`` can have the same length with lines or just single value. @@ -1079,7 +1096,8 @@ def draw_binary_masks( binary_masks: Union[np.ndarray, torch.Tensor], colors: Union[str, tuple, List[str], List[tuple]] = 'g', alphas: Union[float, List[float]] = 0.8) -> 'Visualizer': - """Draw single or multiple binary masks. + """Draw single or multiple binary masks use backend define by + self.backend. Args: binary_masks (np.ndarray, torch.Tensor): The binary_masks to draw @@ -1101,8 +1119,6 @@ def draw_binary_masks( f'but got {binary_masks.dtype}') binary_masks = binary_masks.astype('uint8') * 255 img = self.get_image() - if self.backend == 'cv2': - img = cv2.cvtColor(img, cv2.COLOR_BGR2RGB) if binary_masks.ndim == 2: binary_masks = binary_masks[None] assert img.shape[:2] == binary_masks.shape[ @@ -1413,4 +1429,4 @@ def get_instance(cls, name: str, **kwargs) -> 'Visualizer': """ instance = super().get_instance(name, **kwargs) Visualizer._instance_dict[name] = instance - return instance \ No newline at end of file + return instance From 96fac848b4b5dd29c6feb80d943ba8c76d2b5915 Mon Sep 17 00:00:00 2001 From: KerwinKai <101576779+KerwinKai@users.noreply.github.com> Date: Mon, 7 Aug 2023 12:22:52 +0800 Subject: [PATCH 17/46] update code to ignore some unnecessary mypy check --- mmengine/visualization/utils.py | 4 ++-- mmengine/visualization/visualizer.py | 18 +++++++++--------- 2 files changed, 11 insertions(+), 11 deletions(-) diff --git a/mmengine/visualization/utils.py b/mmengine/visualization/utils.py index 91bbe0e798..fa7f9357db 100644 --- a/mmengine/visualization/utils.py +++ b/mmengine/visualization/utils.py @@ -122,7 +122,7 @@ def color_val_matplotlib( def color_val_opencv( colors: Union[str, tuple, List[Union[str, tuple]]] -) -> Union[str, tuple, List[Union[str, tuple]]]: +) -> Union[tuple, List[tuple]]: """Convert various input in BGR order to BGR opencv color tuples, Args: @@ -143,7 +143,7 @@ def color_val_opencv( color_val_opencv(color) # type:ignore for color in colors ] - return colors + return colors # type:ignore else: raise TypeError(f'Invalid type for color: {type(colors)}') diff --git a/mmengine/visualization/visualizer.py b/mmengine/visualization/visualizer.py index a975066a8d..902bcd2e0a 100644 --- a/mmengine/visualization/visualizer.py +++ b/mmengine/visualization/visualizer.py @@ -430,7 +430,7 @@ def draw_points(self, positions = positions.tolist() if isinstance(colors, str) or isinstance(colors, tuple): colors = [colors] * len(positions) - colors = color_val_opencv(colors) + colors = color_val_opencv(colors) # type:ignore if sizes is not None: sizes = tensor2ndarray(sizes) if marker is None: @@ -616,7 +616,7 @@ def draw_texts( 'the parameters `fontproperties`、' '`vertical_alignments` and `horizontal_alignments` ' 'will be discarded and not called.', UserWarning) - colors = color_val_opencv(colors) + colors = color_val_opencv(colors) # type:ignore if font_families == 'sans-serif': font_families = cv2.FONT_HERSHEY_SIMPLEX font_families = value2list(font_families, int, num_text) @@ -727,7 +727,7 @@ def draw_lines( line_styles = [cv2.LINE_8 for _ in range(len(lines))] check_type_and_length('line_styles', line_styles, (int, list), len(lines)) - colors = color_val_opencv(colors) + colors = color_val_opencv(colors) # type:ignore for i, line in enumerate(lines): st_pos = (line[0][0], line[0][1]) ed_pos = (line[1][0], line[1][1]) @@ -829,7 +829,7 @@ def draw_circles( linestyles=line_styles) self.ax_save.add_collection(p) else: - edge_colors = color_val_opencv(edge_colors) + edge_colors = color_val_opencv(edge_colors) # type:ignore if line_styles is None: line_styles = [cv2.LINE_8 for _ in range(len(circles))] check_type_and_length('line_styles', line_styles, (int, list), @@ -840,7 +840,7 @@ def draw_circles( if face_colors != 'none': if isinstance(face_colors, str): face_colors = [face_colors] * len(circles) - face_colors = color_val_opencv(face_colors) + face_colors = color_val_opencv(face_colors) # type:ignore else: face_colors = ['None'] * len(circles) for ct, radiu, line_style, face_color, edge_color, line_width in \ @@ -935,7 +935,7 @@ def draw_bboxes( face_colors=face_colors) else: bboxes = bboxes.tolist() - edge_colors = color_val_opencv(edge_colors) + edge_colors = color_val_opencv(edge_colors) # type:ignore if line_styles is None: line_styles = [cv2.LINE_8 for _ in range(len(bboxes))] if isinstance(line_styles, int): @@ -948,7 +948,7 @@ def draw_bboxes( if face_colors != 'none': if isinstance(face_colors, str): face_colors = [face_colors] * len(bboxes) - face_colors = color_val_opencv(face_colors) + face_colors = color_val_opencv(face_colors) # type:ignore else: face_colors = ['None'] * len(bboxes) if not isinstance(line_widths, list): @@ -1054,7 +1054,7 @@ def draw_polygons( linewidths=line_widths) self.ax_save.add_collection(polygon_collection) else: - edge_colors = color_val_opencv(edge_colors) + edge_colors = color_val_opencv(edge_colors) # type:ignore if line_styles is None: line_styles = [cv2.LINE_8 for _ in range(len(polygons))] if isinstance(line_styles, int): @@ -1067,7 +1067,7 @@ def draw_polygons( if face_colors != 'none': if isinstance(face_colors, str): face_colors = [face_colors] * len(polygons) - face_colors = color_val_opencv(face_colors) + face_colors = color_val_opencv(face_colors) # type:ignore else: face_colors = ['None'] * len(polygons) for polygon, line_style, face_color, edge_color, line_width in zip( From 07c16853f6168652d80733b6cc4aaa7bd71a8956 Mon Sep 17 00:00:00 2001 From: KerwinKai <101576779+KerwinKai@users.noreply.github.com> Date: Mon, 7 Aug 2023 12:34:07 +0800 Subject: [PATCH 18/46] update vis docs --- docs/en/design/visualization.md | 1 + docs/zh_cn/design/visualization.md | 1 + 2 files changed, 2 insertions(+) diff --git a/docs/en/design/visualization.md b/docs/en/design/visualization.md index f9161d1447..3848c5ec46 100644 --- a/docs/en/design/visualization.md +++ b/docs/en/design/visualization.md @@ -13,6 +13,7 @@ Based on the above requirements, we proposed the `Visualizer` and various `VisBa - For convenience, the APIs provided by the `Visualizer` implement the drawing and storage functions. As an internal property of `Visualizer`, `VisBackend` will be called by `Visualizer` to write data to different backends. - Considering that you may want to write data to multiple backends after drawing, `Visualizer` can be configured with multiple backends. When the user calls the storage API of the `Visualizer`, it will traverse and call all the specified APIs of `VisBackend` internally. +- Considering the difference in drawing performance, the drawing `backend` is an internal property of `Visualizer`, and users can choose a drawing backend according to the usage scenario to achieve a balance between drawing performance and richness of drawing functions. The UML diagram of the two is as follows. diff --git a/docs/zh_cn/design/visualization.md b/docs/zh_cn/design/visualization.md index e219110b97..4d3d4725d7 100644 --- a/docs/zh_cn/design/visualization.md +++ b/docs/zh_cn/design/visualization.md @@ -13,6 +13,7 @@ - 为了方便调用,Visualizer 提供的接口实现了绘制和存储的功能。可视化存储后端 VisBackend 作为 Visualizer 的内部属性,会在需要的时候被 Visualizer 调用,将数据存到不同的后端 - 考虑到绘制后会希望存储到多个后端,Visualizer 可以配置多个 VisBackend,当用户调用 Visualizer 的存储接口时候,Visualizer 内部会遍历的调用 VisBackend 存储接口 +- 考虑到绘图性能的差异,绘图后端 backend 作为 Visualizer 的内部属性,用户可根据使用场景选择绘图后端,以达到绘图性能和绘图功能丰富性的平衡 两者的 UML 关系图如下 From 1d5dce3e742e58ab3907eaed22de2273b94e0fd2 Mon Sep 17 00:00:00 2001 From: Longkai Cheng Date: Mon, 7 Aug 2023 14:46:48 +0800 Subject: [PATCH 19/46] Update mmengine/visualization/visualizer.py Co-authored-by: Mashiro <57566630+HAOCHENYE@users.noreply.github.com> --- mmengine/visualization/visualizer.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/mmengine/visualization/visualizer.py b/mmengine/visualization/visualizer.py index 902bcd2e0a..520edaeec2 100644 --- a/mmengine/visualization/visualizer.py +++ b/mmengine/visualization/visualizer.py @@ -729,8 +729,8 @@ def draw_lines( len(lines)) colors = color_val_opencv(colors) # type:ignore for i, line in enumerate(lines): - st_pos = (line[0][0], line[0][1]) - ed_pos = (line[1][0], line[1][1]) + st_pos = (line[i][0], line[i][1]) + ed_pos = (line[i][0], line[i][1]) cv2.line( img=self._image, pt1=st_pos, From 7fe6813c8540add03d1dba4205374e5776053e21 Mon Sep 17 00:00:00 2001 From: Longkai Cheng Date: Mon, 7 Aug 2023 14:52:44 +0800 Subject: [PATCH 20/46] Update mmengine/visualization/visualizer.py Co-authored-by: Mashiro <57566630+HAOCHENYE@users.noreply.github.com> --- mmengine/visualization/visualizer.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/mmengine/visualization/visualizer.py b/mmengine/visualization/visualizer.py index 520edaeec2..ee7f542c47 100644 --- a/mmengine/visualization/visualizer.py +++ b/mmengine/visualization/visualizer.py @@ -747,7 +747,7 @@ def draw_circles( center: Union[np.ndarray, torch.Tensor], radius: Union[np.ndarray, torch.Tensor], edge_colors: Union[str, tuple, List[Union[str, tuple]]] = 'g', - line_styles: Union[str, List[str]] = None, + line_styles: Union[str, List[str], None] = None, line_widths: Union[Union[int, float], List[Union[int, float]]] = 2, face_colors: Union[str, tuple, List[Union[str, tuple]]] = 'none', alpha: Union[float, int] = 0.8, From 67c6e9756259ff51ac70490c465562fa1e4ecd87 Mon Sep 17 00:00:00 2001 From: Longkai Cheng Date: Mon, 7 Aug 2023 14:53:10 +0800 Subject: [PATCH 21/46] Update mmengine/visualization/visualizer.py Co-authored-by: Mashiro <57566630+HAOCHENYE@users.noreply.github.com> --- mmengine/visualization/visualizer.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/mmengine/visualization/visualizer.py b/mmengine/visualization/visualizer.py index ee7f542c47..b8f459df6b 100644 --- a/mmengine/visualization/visualizer.py +++ b/mmengine/visualization/visualizer.py @@ -100,7 +100,7 @@ class Visualizer(ManagerMixin): >>> y_datas=np.array([[1, 3], [2, 4]]), >>> colors=['r', 'r'], line_widths=[1, 2]) >>> vis.draw_texts(texts='MMEngine', - >>> positions=np.array([2, 2]), + >>> positions=np.array([2, 2]), >>> colors='b') >>> vis.draw_texts(texts=['MMEngine','OpenMMLab'], >>> positions=np.array([[2, 2], [5, 5]]), From 746594d100e9e82b5681d0efc020220c7628b072 Mon Sep 17 00:00:00 2001 From: Longkai Cheng Date: Mon, 7 Aug 2023 14:53:23 +0800 Subject: [PATCH 22/46] Update docs/en/design/visualization.md Co-authored-by: Mashiro <57566630+HAOCHENYE@users.noreply.github.com> --- docs/en/design/visualization.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/en/design/visualization.md b/docs/en/design/visualization.md index 3848c5ec46..7a3cf479de 100644 --- a/docs/en/design/visualization.md +++ b/docs/en/design/visualization.md @@ -13,7 +13,7 @@ Based on the above requirements, we proposed the `Visualizer` and various `VisBa - For convenience, the APIs provided by the `Visualizer` implement the drawing and storage functions. As an internal property of `Visualizer`, `VisBackend` will be called by `Visualizer` to write data to different backends. - Considering that you may want to write data to multiple backends after drawing, `Visualizer` can be configured with multiple backends. When the user calls the storage API of the `Visualizer`, it will traverse and call all the specified APIs of `VisBackend` internally. -- Considering the difference in drawing performance, the drawing `backend` is an internal property of `Visualizer`, and users can choose a drawing backend according to the usage scenario to achieve a balance between drawing performance and richness of drawing functions. +- Considering the difference in drawing performance, the drawing `backend` is an internal property of `Visualizer`, and users can choose a drawing backend according to the usage scenario to balance drawing performance and richness of drawing functions. The UML diagram of the two is as follows. From 66f3b04e3a9568e42ff5b05a76698850275e72d8 Mon Sep 17 00:00:00 2001 From: KerwinKai <101576779+KerwinKai@users.noreply.github.com> Date: Mon, 7 Aug 2023 14:59:36 +0800 Subject: [PATCH 23/46] update the backend argument in docstring --- mmengine/visualization/visualizer.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/mmengine/visualization/visualizer.py b/mmengine/visualization/visualizer.py index b8f459df6b..83f6fc0b47 100644 --- a/mmengine/visualization/visualizer.py +++ b/mmengine/visualization/visualizer.py @@ -79,6 +79,8 @@ class Visualizer(ManagerMixin): Defaults to empty dict. fig_show_cfg (dict): Keyword parameters of figure for showing. Defaults to empty dict. + backend (str): Draw backend config. It can be 'matplotlib' or 'cv2'. + Defaults to 'matplotlib'. Examples: >>> # Basic info methods From 3fe021496dcdc9d542e1bb8677e9ae88d491fc2b Mon Sep 17 00:00:00 2001 From: KerwinKai <101576779+KerwinKai@users.noreply.github.com> Date: Mon, 7 Aug 2023 15:12:34 +0800 Subject: [PATCH 24/46] update code to follow the google python style to raise the exeception --- mmengine/visualization/visualizer.py | 15 ++++++++------- 1 file changed, 8 insertions(+), 7 deletions(-) diff --git a/mmengine/visualization/visualizer.py b/mmengine/visualization/visualizer.py index 83f6fc0b47..48569b131e 100644 --- a/mmengine/visualization/visualizer.py +++ b/mmengine/visualization/visualizer.py @@ -418,9 +418,10 @@ def draw_points(self, 'The shape of `positions` should be (N, 2), ' f'but got {positions.shape}') if self.backend == 'matplotlib': - assert isinstance(marker, str) or marker is None, ( - 'The type of `marker` in `matplotlib` should be str, ' - f'but got {type(marker)}') + if not (isinstance(marker, str) or marker is None): + raise TypeError( + 'The type of `marker` in `matplotlib` should be str, ' + f'but got {type(marker)}') colors = color_val_matplotlib(colors) # type: ignore self.ax_save.scatter( positions[:, 0], @@ -445,10 +446,10 @@ def draw_points(self, color=colors[i], thickness=-1) else: - assert isinstance( - marker, - int), ('The type of `marker` in `cv2` should be int, ' - f'but got {type(marker)}') + if not isinstance(marker, int): + raise TypeError( + 'The type of `marker` in `cv2` should be int, ' + f'but got {type(marker)}') if sizes is None: sizes = [20] * len(positions) for pos, color, size in zip(positions, colors, sizes): From 3e6c69e24fc312173457d530566412ba402f95dc Mon Sep 17 00:00:00 2001 From: KerwinKai <101576779+KerwinKai@users.noreply.github.com> Date: Mon, 7 Aug 2023 15:16:33 +0800 Subject: [PATCH 25/46] change 'None' to 'none' --- mmengine/visualization/visualizer.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/mmengine/visualization/visualizer.py b/mmengine/visualization/visualizer.py index 48569b131e..620235e0bd 100644 --- a/mmengine/visualization/visualizer.py +++ b/mmengine/visualization/visualizer.py @@ -845,7 +845,7 @@ def draw_circles( face_colors = [face_colors] * len(circles) face_colors = color_val_opencv(face_colors) # type:ignore else: - face_colors = ['None'] * len(circles) + face_colors = [face_colors] * len(circles) for ct, radiu, line_style, face_color, edge_color, line_width in \ zip(center, radius, line_styles, face_colors, edge_colors, line_widths): @@ -855,7 +855,7 @@ def draw_circles( 'radius': int(radiu), 'lineType': int(line_style) } - if face_color != 'None': + if face_color != 'none': kwargs['color'] = face_color kwargs['thickness'] = -1 cv2.circle(**kwargs) From 7a31868fb5b3850a651eb749a9de6cf50d57bed1 Mon Sep 17 00:00:00 2001 From: KerwinKai <101576779+KerwinKai@users.noreply.github.com> Date: Mon, 7 Aug 2023 15:51:54 +0800 Subject: [PATCH 26/46] update my code mentioned by some previews --- mmengine/visualization/visualizer.py | 85 +++++++++++++++------------- 1 file changed, 47 insertions(+), 38 deletions(-) diff --git a/mmengine/visualization/visualizer.py b/mmengine/visualization/visualizer.py index 620235e0bd..81d8d4aeaa 100644 --- a/mmengine/visualization/visualizer.py +++ b/mmengine/visualization/visualizer.py @@ -473,7 +473,8 @@ def draw_texts( colors: Union[str, tuple, List[Union[str, tuple]]] = 'g', vertical_alignments: Union[str, List[str]] = 'top', horizontal_alignments: Union[str, List[str]] = 'left', - font_families: Union[str, int, List[Union[str, int]]] = 'sans-serif', + font_families: Union[str, List[str]] = 'sans-serif', + font_faces: Union[int, List[int]] = cv2.FONT_HERSHEY_SIMPLEX, bboxes: Optional[Union[dict, List[dict]]] = None, font_properties: Optional[Union['FontProperties', List['FontProperties']]] = None @@ -515,14 +516,19 @@ def draw_texts( will have the same horizontalalignment. Horizontalalignment can be 'center','right' or 'left'. This parameter is only valid in the `matplotlib` drawing backend. Defaults to 'left'. font_families (Union[str, List[str]]): The font family of - texts. ``font_families`` can have the same length with texts or + texts when use 'matplotlib' backend. ``font_families`` can have the same length with texts or just single value. If ``font_families`` is single value, all the texts will have the same font family. font_familiy can be 'serif', 'sans-serif', 'cursive', 'fantasy' - or 'monospace' in `matplotlib` backend and 'cv2.FONT_HERSHEY_SIMPLEX', + or 'monospace'. Defaults to 'sans-serif'. + font_faces (Union[int, List[int]]): The font family of + texts when use 'cv2' backend. ``font_face`` can have the same length with texts or + just single value. If ``font_face`` is single value, all + the texts will have the same font family. + font_face can be 'cv2.FONT_HERSHEY_SIMPLEX', 'cv2.FONT_HERSHEY_PLAIN', 'cv2.FONT_HERSHEY_DUPLEX', 'cv2.FONT_HERSHEY_COMPLEX', 'cv2.FONT_HERSHEY_TRIPLEX', 'cv2.FONT_HERSHEY_COMPLEX_SMALL', 'cv2.FONT_HERSHEY_SCRIPT_SIMPLEX', - 'cv2.FONT_HERSHEY_SCRIPT_COMPLEX', 'cv2.FONT_ITALIC' or 'sans-serif' in `cv2` backend. Defaults to 'sans-serif'. + 'cv2.FONT_HERSHEY_SCRIPT_COMPLEX', 'cv2.FONT_ITALIC' in `cv2` backend. Defaults to 'cv2.FONT_HERSHEY_SIMPLEX'. bboxes (Union[dict, List[dict]], optional): The bounding box of the texts. If bboxes is None, there are no bounding box around texts. ``bboxes`` can have the same length with texts or @@ -617,43 +623,46 @@ def draw_texts( 'fontScale, color[, thickness[, lineType[, ' 'bottomLeftOrigin]]])->img`, ' 'the parameters `fontproperties`、' - '`vertical_alignments` and `horizontal_alignments` ' + '`vertical_alignments`、`font_families` ' + 'and `horizontal_alignments` ' 'will be discarded and not called.', UserWarning) colors = color_val_opencv(colors) # type:ignore - if font_families == 'sans-serif': - font_families = cv2.FONT_HERSHEY_SIMPLEX - font_families = value2list(font_families, int, num_text) + font_faces = value2list(font_faces, int, num_text) font_sizes = [font_size / 22.0 for font_size in font_sizes] - for i in range(num_text): + for position, text, font_face, font_size, color, bbox in zip( + positions, texts, font_faces, font_sizes, colors, bboxes): (text_width, text_height) = cv2.getTextSize( - texts[i], font_families[i], font_sizes[i], thickness=2)[0] - pos = (int(positions[i][0]), - int(positions[i][1] + text_height)) - cv2.putText( - self._image, - text=texts[i], - org=pos, - color=colors[i], - fontFace=font_families[i], - fontScale=font_sizes[i], - thickness=1) - if bboxes[i] is not None: - x1 = int(positions[i][0] - 10 * font_sizes[i]) - y1 = int(positions[i][1] - 10 * font_sizes[i]) - x2 = int(x1 + text_width + 20 * font_sizes[i]) - y2 = int(y1 + text_height + 20 * font_sizes[i]) - self.draw_bboxes( - bboxes=np.array([x1, y1, x2, y2]), - line_styles=bboxes[i]['linestyle'] - if 'linestyle' in bboxes[i] else None, - line_widths=bboxes[i]['linewidth'] - if 'linewidth' in bboxes[i] else 2, - edge_colors=bboxes[i]['edgecolor'] - if 'edgecolor' in bboxes[i] else 'g', - face_colors=bboxes[i]['facecolor'] - if 'facecolor' in bboxes[i] else 'none', - alpha=bboxes[i]['alpha'] - if 'alpha' in bboxes[i] else 1) + text, font_face, font_size, thickness=2)[0] + pos = (int(position[0]), int(position[1] + text_height)) + kwargs = { + 'img': self._image, + 'text': text, + 'org': pos, + 'color': color, + 'fontFace': font_face, + 'fontScale': font_size, + 'thickness': 1 + } + cv2.putText(**kwargs) + if bbox is not None: + x1 = int(position[0] - 10 * font_size) + y1 = int(position[1] - 10 * font_size) + x2 = int(x1 + text_width + 20 * font_size) + y2 = int(y1 + text_height + 20 * font_size) + kwargs = { + 'bboxes': np.array([x1, y1, x2, y2]), + } + if 'linestyle' in bbox: + kwargs['line_styles'] = bbox['linestyle'] + if 'linewidth' in bbox: + kwargs['line_widths'] = bbox['linewidth'] + if 'edgecolor' in bbox: + kwargs['edge_colors'] = bbox['edgecolor'] + if 'facecolor' in bbox: + kwargs['face_colors'] = bbox['facecolor'] + if 'alpha' in bbox: + kwargs['alpha'] = bbox['alpha'] + self.draw_bboxes(**kwargs) return self @master_only @@ -845,7 +854,7 @@ def draw_circles( face_colors = [face_colors] * len(circles) face_colors = color_val_opencv(face_colors) # type:ignore else: - face_colors = [face_colors] * len(circles) + face_colors = [face_colors] * len(circles) # type:ignore for ct, radiu, line_style, face_color, edge_color, line_width in \ zip(center, radius, line_styles, face_colors, edge_colors, line_widths): From 75832e5ee7462b57ed21aec60ff33aa74ff3c82a Mon Sep 17 00:00:00 2001 From: KerwinKai <101576779+KerwinKai@users.noreply.github.com> Date: Mon, 7 Aug 2023 16:16:33 +0800 Subject: [PATCH 27/46] update code, unit test and docstring for line_widths parameter --- mmengine/visualization/visualizer.py | 40 ++++++++++++++---------- tests/test_visualizer/test_visualizer.py | 9 +++--- 2 files changed, 28 insertions(+), 21 deletions(-) diff --git a/mmengine/visualization/visualizer.py b/mmengine/visualization/visualizer.py index 81d8d4aeaa..5cbbc36906 100644 --- a/mmengine/visualization/visualizer.py +++ b/mmengine/visualization/visualizer.py @@ -702,7 +702,8 @@ def draw_lines( The linewidth of lines. ``line_widths`` can have the same length with lines or just single value. If ``line_widths`` is single value, all the lines will - have the same linewidth. Defaults to 2. + have the same linewidth. When use 'cv2' backend, + it will round value to int format. Defaults to 2. """ check_type('x_datas', x_datas, (np.ndarray, torch.Tensor)) x_datas = tensor2ndarray(x_datas) @@ -740,17 +741,19 @@ def draw_lines( check_type_and_length('line_styles', line_styles, (int, list), len(lines)) colors = color_val_opencv(colors) # type:ignore - for i, line in enumerate(lines): - st_pos = (line[i][0], line[i][1]) - ed_pos = (line[i][0], line[i][1]) - cv2.line( - img=self._image, - pt1=st_pos, - pt2=ed_pos, - color=colors[i], - thickness=int(line_widths[i]) if isinstance( - line_widths, list) else int(line_widths), - lineType=line_styles[i]) + line_widths = value2list(line_widths, (int, float), len(lines)) + for line, color, line_width, line_style in zip(lines, colors, line_widths, line_styles): + st_pos = (line[0][0], line[0][1]) + ed_pos = (line[1][0], line[1][1]) + kwargs = { + 'img': self._image, + 'pt1': st_pos, + 'pt2': ed_pos, + 'color': color, + 'thickness': int(line_width), + 'lineType': line_style + } + cv2.line(**kwargs) return self @master_only @@ -791,7 +794,8 @@ def draw_circles( The linewidth of lines. ``line_widths`` can have the same length with lines or just single value. If ``line_widths`` is single value, all the lines will - have the same linewidth. Defaults to 2. + have the same linewidth. When use 'cv2' backend, + it will round value to int format. Defaults to 2. face_colors (Union[str, tuple, List[str], List[tuple]]): The face colors. Defaults to None. alpha (Union[int, float]): The transparency of circles. @@ -910,7 +914,8 @@ def draw_bboxes( The linewidth of lines. ``line_widths`` can have the same length with lines or just single value. If ``line_widths`` is single value, all the lines will - have the same linewidth. Defaults to 2. + have the same linewidth. When use 'cv2' backend, + it will round value to int format. Defaults to 2. face_colors (Union[str, tuple, List[str], List[tuple]]): The face colors. Defaults to None. alpha (Union[int, float]): The transparency of bboxes. @@ -981,7 +986,7 @@ def draw_bboxes( kwargs['thickness'] = -1 cv2.rectangle(**kwargs) kwargs['color'] = edge_color - kwargs['thickness'] = line_width + kwargs['thickness'] = int(line_width) cv2.rectangle(**kwargs) cv2.addWeighted(self._image, alpha, overlay, 1 - alpha, 0, self._image) @@ -1024,7 +1029,8 @@ def draw_polygons( The linewidth of lines. ``line_widths`` can have the same length with lines or just single value. If ``line_widths`` is single value, all the lines will - have the same linewidth. Defaults to 2. + have the same linewidth. When use 'cv2' backend, + it will round value to int format. Defaults to 2. face_colors (Union[str, tuple, List[str], List[tuple]]): The face colors. Defaults to None. alpha (Union[int, float]): The transparency of polygons. @@ -1096,7 +1102,7 @@ def draw_polygons( cv2.fillPoly(**kwargs) kwargs['isClosed'] = True kwargs['color'] = edge_color - kwargs['thickness'] = line_width + kwargs['thickness'] = int(line_width) cv2.polylines(**kwargs) cv2.addWeighted(self._image, alpha, overlay, 1 - alpha, 0, self._image) diff --git a/tests/test_visualizer/test_visualizer.py b/tests/test_visualizer/test_visualizer.py index fb6514fc47..ea156e4033 100644 --- a/tests/test_visualizer/test_visualizer.py +++ b/tests/test_visualizer/test_visualizer.py @@ -152,7 +152,8 @@ def test_draw_bboxes(self, backend): bboxes, alpha=0.5, edge_colors=(255, 0, 0), - line_styles=line_styles) + line_styles=line_styles, + line_widths=[1, 2.2]) bboxes = bboxes.numpy() visualizer.draw_bboxes(bboxes) @@ -287,7 +288,7 @@ def test_draw_lines(self, backend): y_datas=np.array([[2, 6], [4, 7]]), colors='r', line_styles=line_styles, - line_widths=[1, 2]) + line_widths=[1, 2.1]) # test out of bounds with pytest.warns( UserWarning, @@ -335,7 +336,7 @@ def test_draw_circles(self, backend): radius=torch.tensor([1, 2]), edge_colors=['g', 'r'], line_styles=line_styles, - line_widths=[1, 2]) + line_widths=[1, 2.1]) # test out of bounds with pytest.warns( @@ -386,7 +387,7 @@ def test_draw_polygons(self, backend): ], edge_colors=['r', 'g'], line_styles=line_styles, - line_widths=[2, 1]) + line_widths=[2, 1.1]) # test out of bounds with pytest.warns( From a078ea60c04c903cecf6547954eb04b7f428d397 Mon Sep 17 00:00:00 2001 From: KerwinKai <101576779+KerwinKai@users.noreply.github.com> Date: Mon, 7 Aug 2023 16:29:20 +0800 Subject: [PATCH 28/46] resolve conflicts --- mmengine/visualization/visualizer.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/mmengine/visualization/visualizer.py b/mmengine/visualization/visualizer.py index 0f0aee6a06..e874fe20f2 100644 --- a/mmengine/visualization/visualizer.py +++ b/mmengine/visualization/visualizer.py @@ -756,7 +756,8 @@ def draw_lines( len(lines)) colors = color_val_opencv(colors) # type:ignore line_widths = value2list(line_widths, (int, float), len(lines)) - for line, color, line_width, line_style in zip(lines, colors, line_widths, line_styles): + for line, color, line_width, line_style in zip( + lines, colors, line_widths, line_styles): st_pos = (line[0][0], line[0][1]) ed_pos = (line[1][0], line[1][1]) kwargs = { From cb61b70b77ecf58d884f8b7e5709edd56ef00ca2 Mon Sep 17 00:00:00 2001 From: KerwinKai <101576779+KerwinKai@users.noreply.github.com> Date: Mon, 7 Aug 2023 16:43:21 +0800 Subject: [PATCH 29/46] setting 20 as a class attribute --- mmengine/visualization/visualizer.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/mmengine/visualization/visualizer.py b/mmengine/visualization/visualizer.py index e874fe20f2..ee76d402d1 100644 --- a/mmengine/visualization/visualizer.py +++ b/mmengine/visualization/visualizer.py @@ -225,6 +225,9 @@ def __init__(self, if image is not None: self.set_image(image) + # When set to 20, the markers drawn by cv2 and matplotlib look similar + self._default_cv2_markersize = 20 + @property # type: ignore @master_only def dataset_meta(self) -> Optional[dict]: @@ -465,7 +468,7 @@ def draw_points(self, 'The type of `marker` in `cv2` should be int, ' f'but got {type(marker)}') if sizes is None: - sizes = [20] * len(positions) + sizes = [self._default_cv2_markersize] * len(positions) for pos, color, size in zip(positions, colors, sizes): kwargs = { 'img': self._image, From 74e515cc7e74a976a5a39322fa18f61c03fd9e9c Mon Sep 17 00:00:00 2001 From: Longkai Cheng Date: Tue, 8 Aug 2023 14:04:37 +0800 Subject: [PATCH 30/46] Update mmengine/visualization/visualizer.py Co-authored-by: Zaida Zhou <58739961+zhouzaida@users.noreply.github.com> --- mmengine/visualization/visualizer.py | 1 + 1 file changed, 1 insertion(+) diff --git a/mmengine/visualization/visualizer.py b/mmengine/visualization/visualizer.py index ee76d402d1..b15fb3bfca 100644 --- a/mmengine/visualization/visualizer.py +++ b/mmengine/visualization/visualizer.py @@ -85,6 +85,7 @@ class Visualizer(ManagerMixin): Defaults to empty dict. backend (str): Draw backend config. It can be 'matplotlib' or 'cv2'. Defaults to 'matplotlib'. + `New in version 0.8.5.` Examples: >>> # Basic info methods From f4fd6abdd5aab8ef30de28680290ecc7a4b88568 Mon Sep 17 00:00:00 2001 From: KerwinKai <101576779+KerwinKai@users.noreply.github.com> Date: Wed, 9 Aug 2023 09:15:26 +0800 Subject: [PATCH 31/46] Compatible with backend parameter for show function --- mmengine/visualization/visualizer.py | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/mmengine/visualization/visualizer.py b/mmengine/visualization/visualizer.py index b15fb3bfca..fa7364969c 100644 --- a/mmengine/visualization/visualizer.py +++ b/mmengine/visualization/visualizer.py @@ -83,8 +83,8 @@ class Visualizer(ManagerMixin): Defaults to empty dict. fig_show_cfg (dict): Keyword parameters of figure for showing. Defaults to empty dict. - backend (str): Draw backend config. It can be 'matplotlib' or 'cv2'. - Defaults to 'matplotlib'. + backend (str, optional): Draw backend config. + It can be 'matplotlib' or 'cv2'. Defaults to 'matplotlib'. `New in version 0.8.5.` Examples: @@ -247,7 +247,7 @@ def show(self, win_name: str = 'image', wait_time: float = 0., continue_key: str = ' ', - backend: str = 'matplotlib') -> None: + backend: Optional[str] = None) -> None: """Show the drawn image. Args: @@ -262,6 +262,8 @@ def show(self, backend (str): The backend to show the image. Defaults to 'matplotlib'. `New in version 0.7.3.` """ + if backend is None: + backend = self.backend if backend == 'matplotlib': import matplotlib.pyplot as plt is_inline = 'inline' in plt.get_backend() From ef133dccadfa860a567cdb23248c33b6bde9a020 Mon Sep 17 00:00:00 2001 From: KerwinKai <101576779+KerwinKai@users.noreply.github.com> Date: Wed, 9 Aug 2023 09:50:05 +0800 Subject: [PATCH 32/46] Update visualizer.py --- mmengine/visualization/visualizer.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/mmengine/visualization/visualizer.py b/mmengine/visualization/visualizer.py index fa7364969c..4d93e9e2c0 100644 --- a/mmengine/visualization/visualizer.py +++ b/mmengine/visualization/visualizer.py @@ -83,7 +83,7 @@ class Visualizer(ManagerMixin): Defaults to empty dict. fig_show_cfg (dict): Keyword parameters of figure for showing. Defaults to empty dict. - backend (str, optional): Draw backend config. + backend (str): Draw backend config. It can be 'matplotlib' or 'cv2'. Defaults to 'matplotlib'. `New in version 0.8.5.` From 217c51192ad570db413bdc8f103ad37121a4a110 Mon Sep 17 00:00:00 2001 From: KerwinKai <101576779+KerwinKai@users.noreply.github.com> Date: Wed, 9 Aug 2023 13:48:24 +0800 Subject: [PATCH 33/46] try to fix ci/circleci --- mmengine/visualization/visualizer.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/mmengine/visualization/visualizer.py b/mmengine/visualization/visualizer.py index 4d93e9e2c0..3a06307d92 100644 --- a/mmengine/visualization/visualizer.py +++ b/mmengine/visualization/visualizer.py @@ -263,7 +263,10 @@ def show(self, 'matplotlib'. `New in version 0.7.3.` """ if backend is None: - backend = self.backend + if self.backend is None: + backend = 'matplotlib' + else: + backend = self.backend if backend == 'matplotlib': import matplotlib.pyplot as plt is_inline = 'inline' in plt.get_backend() From a35aa4c863f0cd66ba3f42c307b884f252103290 Mon Sep 17 00:00:00 2001 From: Longkai Cheng Date: Wed, 9 Aug 2023 19:27:17 +0800 Subject: [PATCH 34/46] Update mmengine/visualization/visualizer.py Co-authored-by: Zaida Zhou <58739961+zhouzaida@users.noreply.github.com> --- mmengine/visualization/visualizer.py | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/mmengine/visualization/visualizer.py b/mmengine/visualization/visualizer.py index 3a06307d92..4d93e9e2c0 100644 --- a/mmengine/visualization/visualizer.py +++ b/mmengine/visualization/visualizer.py @@ -263,10 +263,7 @@ def show(self, 'matplotlib'. `New in version 0.7.3.` """ if backend is None: - if self.backend is None: - backend = 'matplotlib' - else: - backend = self.backend + backend = self.backend if backend == 'matplotlib': import matplotlib.pyplot as plt is_inline = 'inline' in plt.get_backend() From 65a052ec27c6ac729da1f8883978674163ae1f0a Mon Sep 17 00:00:00 2001 From: KerwinKai <101576779+KerwinKai@users.noreply.github.com> Date: Fri, 11 Aug 2023 15:08:52 +0800 Subject: [PATCH 35/46] update `color_val_opencv` function and set self._image order `rgb` in 'matplotlib' backend and `bgr` in `cv2` backend --- mmengine/visualization/utils.py | 4 +- mmengine/visualization/visualizer.py | 59 +++++++++++++++++----------- 2 files changed, 38 insertions(+), 25 deletions(-) diff --git a/mmengine/visualization/utils.py b/mmengine/visualization/utils.py index fa7f9357db..5dbf96aeb6 100644 --- a/mmengine/visualization/utils.py +++ b/mmengine/visualization/utils.py @@ -132,7 +132,9 @@ def color_val_opencv( indicating BGR channels. """ if isinstance(colors, str): - return color_str2rgb(colors) + colors = color_str2rgb(colors) + colors = (colors[2], colors[1], colors[0]) + return colors elif isinstance(colors, tuple): assert len(colors) == 3 for channel in colors: diff --git a/mmengine/visualization/visualizer.py b/mmengine/visualization/visualizer.py index 4d93e9e2c0..5243dc1ebc 100644 --- a/mmengine/visualization/visualizer.py +++ b/mmengine/visualization/visualizer.py @@ -287,10 +287,9 @@ def show(self, # will be updated with `win_name`. cv2.namedWindow(winname=f'{id(self)}') cv2.setWindowTitle(f'{id(self)}', win_name) - bgr_image = cv2.cvtColor( - self.get_image(), - cv2.COLOR_RGB2BGR) if drawn_img is None else drawn_img - cv2.imshow(str(id(self)), bgr_image) + cv2.imshow( + str(id(self)), + self.get_image() if drawn_img is None else drawn_img) cv2.waitKey(int(np.ceil(wait_time * 1000))) else: raise ValueError('backend should be "matplotlib" or "cv2", ' @@ -305,7 +304,6 @@ def set_image(self, image: np.ndarray) -> None: """ assert image is not None image = image.astype('uint8') - self._image = image self.width, self.height = image.shape[1], image.shape[0] self._default_font_size = max( np.sqrt(self.height * self.width) // 90, 10) @@ -314,6 +312,7 @@ def set_image(self, image: np.ndarray) -> None: # add a small 1e-2 to avoid precision lost due to matplotlib's # truncation # (https://github.com/matplotlib/matplotlib/issues/15363) + self._image = image self.fig_save.set_size_inches( # type: ignore (self.width + 1e-2) / self.dpi, (self.height + 1e-2) / self.dpi) @@ -324,6 +323,8 @@ def set_image(self, image: np.ndarray) -> None: image, extent=(0, self.width, self.height, 0), interpolation='none') + else: + self._image = cv2.cvtColor(image, cv2.COLOR_RGB2BGR) @master_only def get_image(self) -> np.ndarray: @@ -1146,8 +1147,8 @@ def draw_binary_masks( colors (np.ndarray): The colors which binary_masks will convert to. ``colors`` can have the same length with binary_masks or just single value. If ``colors`` is single value, all the - binary_masks will convert to the same colors. The colors format - is RGB. Defaults to np.array([0, 255, 0]). + binary_masks will convert to the same colors. + Defaults to np.array([0, 255, 0]). alphas (Union[int, List[int]]): The transparency of masks. Defaults to 0.8. """ @@ -1168,33 +1169,43 @@ def draw_binary_masks( check_type_and_length('colors', colors, (str, tuple, list), binary_mask_len) colors = value2list(colors, (str, tuple), binary_mask_len) - colors = [ - color_str2rgb(color) if isinstance(color, str) else color - for color in colors - ] - for color in colors: - assert len(color) == 3 - for channel in color: - assert 0 <= channel <= 255 # type: ignore if isinstance(alphas, float): alphas = [alphas] * binary_mask_len - for binary_mask, color, alpha in zip(binary_masks, colors, alphas): - binary_mask_complement = cv2.bitwise_not(binary_mask) - rgb = np.zeros_like(img) - rgb[...] = color - rgb = cv2.bitwise_and(rgb, rgb, mask=binary_mask) - img_complement = cv2.bitwise_and( - img, img, mask=binary_mask_complement) - rgb = rgb + img_complement - img = cv2.addWeighted(img, 1 - alpha, rgb, alpha, 0) if self.backend == 'matplotlib': + colors = [ + color_str2rgb(color) if isinstance(color, str) else color + for color in colors + ] + for color in colors: + assert len(color) == 3 + for channel in color: + assert 0 <= channel <= 255 # type: ignore + for binary_mask, color, alpha in zip(binary_masks, colors, alphas): + binary_mask_complement = cv2.bitwise_not(binary_mask) + rgb = np.zeros_like(img) + rgb[...] = color + rgb = cv2.bitwise_and(rgb, rgb, mask=binary_mask) + img_complement = cv2.bitwise_and( + img, img, mask=binary_mask_complement) + rgb = rgb + img_complement + img = cv2.addWeighted(img, 1 - alpha, rgb, alpha, 0) self.ax_save.imshow( img, extent=(0, self.width, self.height, 0), interpolation='nearest') else: + colors = color_val_opencv(colors) #type: ignore + for binary_mask, color, alpha in zip(binary_masks, colors, alphas): + binary_mask_complement = cv2.bitwise_not(binary_mask) + bgr = np.zeros_like(img) + bgr[...] = color + bgr = cv2.bitwise_and(bgr, bgr, mask=binary_mask) + img_complement = cv2.bitwise_and( + img, img, mask=binary_mask_complement) + bgr = bgr + img_complement + img = cv2.addWeighted(img, 1 - alpha, bgr, alpha, 0) self._image = img return self From d705ffeeba1357600cac6ac9f328b07c4ed5f0b0 Mon Sep 17 00:00:00 2001 From: KerwinKai <101576779+KerwinKai@users.noreply.github.com> Date: Fri, 11 Aug 2023 15:13:13 +0800 Subject: [PATCH 36/46] fix ci --- mmengine/visualization/visualizer.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/mmengine/visualization/visualizer.py b/mmengine/visualization/visualizer.py index 5243dc1ebc..0b351a0f52 100644 --- a/mmengine/visualization/visualizer.py +++ b/mmengine/visualization/visualizer.py @@ -1196,7 +1196,7 @@ def draw_binary_masks( extent=(0, self.width, self.height, 0), interpolation='nearest') else: - colors = color_val_opencv(colors) #type: ignore + colors = color_val_opencv(colors) # type:ignore for binary_mask, color, alpha in zip(binary_masks, colors, alphas): binary_mask_complement = cv2.bitwise_not(binary_mask) bgr = np.zeros_like(img) From 2c8fa19d90873574546230ddb1d863ada74be70e Mon Sep 17 00:00:00 2001 From: KerwinKai <101576779+KerwinKai@users.noreply.github.com> Date: Fri, 11 Aug 2023 15:14:41 +0800 Subject: [PATCH 37/46] fix docstring --- mmengine/visualization/utils.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/mmengine/visualization/utils.py b/mmengine/visualization/utils.py index 5dbf96aeb6..d670f1721e 100644 --- a/mmengine/visualization/utils.py +++ b/mmengine/visualization/utils.py @@ -95,7 +95,7 @@ def color_val_matplotlib( colors: Union[str, tuple, List[Union[str, tuple]]] ) -> Union[str, tuple, List[Union[str, tuple]]]: """Convert various input in RGB order to normalized RGB matplotlib color - tuples, + tuples. Args: colors (Union[str, tuple, List[Union[str, tuple]]]): Color inputs Returns: @@ -124,7 +124,7 @@ def color_val_opencv( colors: Union[str, tuple, List[Union[str, tuple]]] ) -> Union[tuple, List[tuple]]: """Convert various input in BGR order to BGR opencv color - tuples, + tuples. Args: colors (Union[str, tuple, List[Union[str, tuple]]]): Color inputs Returns: From 2c051b15e62413e6521c3f0c9763466d92d15e53 Mon Sep 17 00:00:00 2001 From: KerwinKai <101576779+KerwinKai@users.noreply.github.com> Date: Fri, 11 Aug 2023 15:18:22 +0800 Subject: [PATCH 38/46] fix ci --- mmengine/visualization/utils.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/mmengine/visualization/utils.py b/mmengine/visualization/utils.py index d670f1721e..b79e0a879e 100644 --- a/mmengine/visualization/utils.py +++ b/mmengine/visualization/utils.py @@ -96,6 +96,7 @@ def color_val_matplotlib( ) -> Union[str, tuple, List[Union[str, tuple]]]: """Convert various input in RGB order to normalized RGB matplotlib color tuples. + Args: colors (Union[str, tuple, List[Union[str, tuple]]]): Color inputs Returns: @@ -123,8 +124,8 @@ def color_val_matplotlib( def color_val_opencv( colors: Union[str, tuple, List[Union[str, tuple]]] ) -> Union[tuple, List[tuple]]: - """Convert various input in BGR order to BGR opencv color - tuples. + """Convert various input in BGR order to BGR opencv color tuples. + Args: colors (Union[str, tuple, List[Union[str, tuple]]]): Color inputs Returns: From a0fe3ce9f6def1f634dcec001d0f9cb9615bb9dc Mon Sep 17 00:00:00 2001 From: KerwinKai <101576779+KerwinKai@users.noreply.github.com> Date: Mon, 14 Aug 2023 09:22:12 +0800 Subject: [PATCH 39/46] standard the input format to be RGB for all interfaces --- mmengine/visualization/utils.py | 5 +- mmengine/visualization/visualizer.py | 69 +++++++++++++++------------- 2 files changed, 38 insertions(+), 36 deletions(-) diff --git a/mmengine/visualization/utils.py b/mmengine/visualization/utils.py index b79e0a879e..e89b4e4db7 100644 --- a/mmengine/visualization/utils.py +++ b/mmengine/visualization/utils.py @@ -124,17 +124,16 @@ def color_val_matplotlib( def color_val_opencv( colors: Union[str, tuple, List[Union[str, tuple]]] ) -> Union[tuple, List[tuple]]: - """Convert various input in BGR order to BGR opencv color tuples. + """Convert various input in RGB order to RGB color tuples. Args: colors (Union[str, tuple, List[Union[str, tuple]]]): Color inputs Returns: Union[str, tuple, List[Union[str, tuple]]]: A tuple of 3 ints - indicating BGR channels. + indicating RGB channels. """ if isinstance(colors, str): colors = color_str2rgb(colors) - colors = (colors[2], colors[1], colors[0]) return colors elif isinstance(colors, tuple): assert len(colors) == 3 diff --git a/mmengine/visualization/visualizer.py b/mmengine/visualization/visualizer.py index 0b351a0f52..1c8865e8c2 100644 --- a/mmengine/visualization/visualizer.py +++ b/mmengine/visualization/visualizer.py @@ -287,9 +287,10 @@ def show(self, # will be updated with `win_name`. cv2.namedWindow(winname=f'{id(self)}') cv2.setWindowTitle(f'{id(self)}', win_name) - cv2.imshow( - str(id(self)), - self.get_image() if drawn_img is None else drawn_img) + bgr_image = cv2.cvtColor( + self.get_image(), + cv2.COLOR_RGB2BGR) if drawn_img is None else drawn_img + cv2.imshow(str(id(self)), bgr_image) cv2.waitKey(int(np.ceil(wait_time * 1000))) else: raise ValueError('backend should be "matplotlib" or "cv2", ' @@ -297,7 +298,7 @@ def show(self, @master_only def set_image(self, image: np.ndarray) -> None: - """Set the image to draw. + """Set the image to draw. The format is RGB. Args: image (np.ndarray): The image to draw. @@ -324,7 +325,7 @@ def set_image(self, image: np.ndarray) -> None: extent=(0, self.width, self.height, 0), interpolation='none') else: - self._image = cv2.cvtColor(image, cv2.COLOR_RGB2BGR) + self._image = image @master_only def get_image(self) -> np.ndarray: @@ -417,9 +418,10 @@ def draw_points(self, Args: positions (Union[np.ndarray, torch.Tensor]): Positions to draw. colors (Union[str, tuple, List[str], List[tuple]]): The colors - of points. ``colors`` can have the same length with points or - just single value. If ``colors`` is single value, all the - points will have the same colors. Reference to + of points which format is RGB. ``colors`` can have the same + length with points or just single value. If ``colors`` is + single value, all the points will have the same colors. + Reference to https://matplotlib.org/stable/gallery/color/named_colors.html for more details. Defaults to 'g. marker (str, optional): The marker style. @@ -513,7 +515,7 @@ def draw_texts( just single value. If ``font_sizes`` is single value, all the texts will have the same font size. Defaults to None. colors (Union[str, tuple, List[str], List[tuple]]): The colors - of texts. ``colors`` can have the same length with texts or + of texts which format is RGB. ``colors`` can have the same length with texts or just single value. If ``colors`` is single value, all the texts will have the same colors. Reference to https://matplotlib.org/stable/gallery/color/named_colors.html @@ -704,9 +706,9 @@ def draw_lines( y_datas (Union[np.ndarray, torch.Tensor]): The y coordinate of each line' start and end points. colors (Union[str, tuple, List[str], List[tuple]]): The colors of - lines. ``colors`` can have the same length with lines or just - single value. If ``colors`` is single value, all the lines - will have the same colors. Reference to + lines which format is RGB. ``colors`` can have the same length + with lines or just single value. If ``colors`` is single value, + all the lines will have the same colors. Reference to https://matplotlib.org/stable/gallery/color/named_colors.html for more details. Defaults to 'g'. line_styles (Union[str, List[str]]): The linestyle @@ -797,9 +799,10 @@ def draw_circles( radius (Union[np.ndarray, torch.Tensor]): The y coordinate of each line' start and end points. edge_colors (Union[str, tuple, List[str], List[tuple]]): The - colors of circles. ``colors`` can have the same length with - lines or just single value. If ``colors`` is single value, - all the lines will have the same colors. Reference to + colors of circles which format is RGB. ``colors`` can have + the same length with lines or just single value. If ``colors`` + is single value, all the lines will have the same colors. + Reference to https://matplotlib.org/stable/gallery/color/named_colors.html for more details. Defaults to 'g. line_styles (Union[str, List[str]]): The linestyle @@ -819,7 +822,7 @@ def draw_circles( have the same linewidth. When use 'cv2' backend, it will round value to int format. Defaults to 2. face_colors (Union[str, tuple, List[str], List[tuple]]): - The face colors. Defaults to None. + The face colors which format is RGB. Defaults to None. alpha (Union[int, float]): The transparency of circles. Defaults to 0.8. """ @@ -917,11 +920,11 @@ def draw_bboxes( bboxes (Union[np.ndarray, torch.Tensor]): The bboxes to draw with the format of(x1,y1,x2,y2). edge_colors (Union[str, tuple, List[str], List[tuple]]): The - colors of bboxes. ``colors`` can have the same length with - lines or just single value. If ``colors`` is single value, all - the lines will have the same colors. Refer to `matplotlib. - colors` for full list of formats that are accepted. - Defaults to 'g'. + colors of bboxes which format is RGB. ``colors`` can have + the same length with lines or just single value. If ``colors`` + is single value, all the lines will have the same colors. + Refer to `matplotlib. colors` for full list of formats + that are accepted. Defaults to 'g'. line_styles (Union[str, List[str]]): The linestyle of lines. ``line_styles`` can have the same length with texts or just single value. If ``line_styles`` is single @@ -939,7 +942,7 @@ def draw_bboxes( have the same linewidth. When use 'cv2' backend, it will round value to int format. Defaults to 2. face_colors (Union[str, tuple, List[str], List[tuple]]): - The face colors. Defaults to None. + The face colors which format is RGB. Defaults to None. alpha (Union[int, float]): The transparency of bboxes. Defaults to 0.8. """ @@ -1032,11 +1035,11 @@ def draw_polygons( List[Union[np.ndarray, torch.Tensor]]]): The polygons to draw with the format of (x1,y1,x2,y2,...,xn,yn). edge_colors (Union[str, tuple, List[str], List[tuple]]): The - colors of polygons. ``colors`` can have the same length with - lines or just single value. If ``colors`` is single value, - all the lines will have the same colors. Refer to - `matplotlib.colors` for full list of formats that are accepted. - Defaults to 'g. + colors of polygons which format is RGB. ``colors`` can + have the same length with lines or just single value. + If ``colors`` is single value, all the lines will have + the same colors. Refer to `matplotlib.colors` for full + list of formats that are accepted. Defaults to 'g. line_styles (Union[str, List[str]]): The linestyle of lines. ``line_styles`` can have the same length with texts or just single value. If ``line_styles`` is single @@ -1054,7 +1057,7 @@ def draw_polygons( have the same linewidth. When use 'cv2' backend, it will round value to int format. Defaults to 2. face_colors (Union[str, tuple, List[str], List[tuple]]): - The face colors. Defaults to None. + The face colors which format is RGB. Defaults to None. alpha (Union[int, float]): The transparency of polygons. Defaults to 0.8. """ @@ -1144,11 +1147,11 @@ def draw_binary_masks( with of shape (N, H, W), where H is the image height and W is the image width. Each value in the array is either a 0 or 1 value of uint8 type. - colors (np.ndarray): The colors which binary_masks will convert to. - ``colors`` can have the same length with binary_masks or just - single value. If ``colors`` is single value, all the - binary_masks will convert to the same colors. - Defaults to np.array([0, 255, 0]). + colors (np.ndarray): The colors order in RGB channel which + binary_masks will convert to. ``colors`` can have the same + length with binary_masks or just single value. If ``colors`` + is single value, all the binary_masks will convert to + the same colors. Defaults to np.array([0, 255, 0]). alphas (Union[int, List[int]]): The transparency of masks. Defaults to 0.8. """ From 530a34f910eed0f3ab11cc85346457c1b8030058 Mon Sep 17 00:00:00 2001 From: KerwinKai <101576779+KerwinKai@users.noreply.github.com> Date: Mon, 14 Aug 2023 10:57:44 +0800 Subject: [PATCH 40/46] update marker type in matplotlib backend --- mmengine/visualization/visualizer.py | 13 +++++-------- 1 file changed, 5 insertions(+), 8 deletions(-) diff --git a/mmengine/visualization/visualizer.py b/mmengine/visualization/visualizer.py index 1c8865e8c2..434ad3cb7a 100644 --- a/mmengine/visualization/visualizer.py +++ b/mmengine/visualization/visualizer.py @@ -424,11 +424,12 @@ def draw_points(self, Reference to https://matplotlib.org/stable/gallery/color/named_colors.html for more details. Defaults to 'g. - marker (str, optional): The marker style. + marker (Union[str, int], optional): The marker style. See :mod:`matplotlib.markers` for more information about - marker styles in `matplotlib` backend. See: `cv::MarkerTypes` - for more information about marker styles in `cv2` backend. - Defaults to None. + marker styles in `matplotlib` backend, Reference to + https://matplotlib.org/stable/api/markers_api.html. + See: `cv::MarkerTypes` for more information about marker + styles in `cv2` backend. Defaults to None. sizes (Optional[Union[np.ndarray, torch.Tensor]]): The marker size. Defaults to None. """ @@ -441,10 +442,6 @@ def draw_points(self, 'The shape of `positions` should be (N, 2), ' f'but got {positions.shape}') if self.backend == 'matplotlib': - if not (isinstance(marker, str) or marker is None): - raise TypeError( - 'The type of `marker` in `matplotlib` should be str, ' - f'but got {type(marker)}') colors = color_val_matplotlib(colors) # type: ignore self.ax_save.scatter( positions[:, 0], From 7b90df1175ece0ac9f632db343bb54677638dd97 Mon Sep 17 00:00:00 2001 From: KerwinKai <101576779+KerwinKai@users.noreply.github.com> Date: Mon, 14 Aug 2023 11:14:39 +0800 Subject: [PATCH 41/46] mv parameter font_faces to the end --- mmengine/visualization/visualizer.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/mmengine/visualization/visualizer.py b/mmengine/visualization/visualizer.py index 434ad3cb7a..3034e13072 100644 --- a/mmengine/visualization/visualizer.py +++ b/mmengine/visualization/visualizer.py @@ -494,10 +494,10 @@ def draw_texts( vertical_alignments: Union[str, List[str]] = 'top', horizontal_alignments: Union[str, List[str]] = 'left', font_families: Union[str, List[str]] = 'sans-serif', - font_faces: Union[int, List[int]] = cv2.FONT_HERSHEY_SIMPLEX, bboxes: Optional[Union[dict, List[dict]]] = None, font_properties: Optional[Union['FontProperties', - List['FontProperties']]] = None + List['FontProperties']]] = None, + font_faces: Union[int, List[int]] = cv2.FONT_HERSHEY_SIMPLEX, ) -> 'Visualizer': """Draw single or multiple text boxes use backend define by self.backend. From 1f25984dfb4e7d2e889e4c758b6c500b4d34f8bd Mon Sep 17 00:00:00 2001 From: Longkai Cheng Date: Mon, 14 Aug 2023 11:21:53 +0800 Subject: [PATCH 42/46] Update mmengine/visualization/visualizer.py Co-authored-by: Zaida Zhou <58739961+zhouzaida@users.noreply.github.com> --- mmengine/visualization/visualizer.py | 1 + 1 file changed, 1 insertion(+) diff --git a/mmengine/visualization/visualizer.py b/mmengine/visualization/visualizer.py index 3034e13072..473d1c39c6 100644 --- a/mmengine/visualization/visualizer.py +++ b/mmengine/visualization/visualizer.py @@ -549,6 +549,7 @@ def draw_texts( 'cv2.FONT_HERSHEY_PLAIN', 'cv2.FONT_HERSHEY_DUPLEX', 'cv2.FONT_HERSHEY_COMPLEX', 'cv2.FONT_HERSHEY_TRIPLEX', 'cv2.FONT_HERSHEY_COMPLEX_SMALL', 'cv2.FONT_HERSHEY_SCRIPT_SIMPLEX', 'cv2.FONT_HERSHEY_SCRIPT_COMPLEX', 'cv2.FONT_ITALIC' in `cv2` backend. Defaults to 'cv2.FONT_HERSHEY_SIMPLEX'. + `New in version 0.8.5.` bboxes (Union[dict, List[dict]], optional): The bounding box of the texts. If bboxes is None, there are no bounding box around texts. ``bboxes`` can have the same length with texts or From e3378f1f80811a97ac7c7613a8d65223b8f3033e Mon Sep 17 00:00:00 2001 From: KerwinKai <101576779+KerwinKai@users.noreply.github.com> Date: Mon, 14 Aug 2023 11:25:10 +0800 Subject: [PATCH 43/46] also mv font_faces docstring to the end --- mmengine/visualization/visualizer.py | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/mmengine/visualization/visualizer.py b/mmengine/visualization/visualizer.py index 473d1c39c6..a6524a6826 100644 --- a/mmengine/visualization/visualizer.py +++ b/mmengine/visualization/visualizer.py @@ -541,15 +541,6 @@ def draw_texts( the texts will have the same font family. font_familiy can be 'serif', 'sans-serif', 'cursive', 'fantasy' or 'monospace'. Defaults to 'sans-serif'. - font_faces (Union[int, List[int]]): The font family of - texts when use 'cv2' backend. ``font_face`` can have the same length with texts or - just single value. If ``font_face`` is single value, all - the texts will have the same font family. - font_face can be 'cv2.FONT_HERSHEY_SIMPLEX', - 'cv2.FONT_HERSHEY_PLAIN', 'cv2.FONT_HERSHEY_DUPLEX', 'cv2.FONT_HERSHEY_COMPLEX', - 'cv2.FONT_HERSHEY_TRIPLEX', 'cv2.FONT_HERSHEY_COMPLEX_SMALL', 'cv2.FONT_HERSHEY_SCRIPT_SIMPLEX', - 'cv2.FONT_HERSHEY_SCRIPT_COMPLEX', 'cv2.FONT_ITALIC' in `cv2` backend. Defaults to 'cv2.FONT_HERSHEY_SIMPLEX'. - `New in version 0.8.5.` bboxes (Union[dict, List[dict]], optional): The bounding box of the texts. If bboxes is None, there are no bounding box around texts. ``bboxes`` can have the same length with texts or @@ -569,6 +560,15 @@ def draw_texts( all the texts will have the same font properties. Defaults to None. This parameter is only valid in the `matplotlib` drawing backend. `New in version 0.6.0.` + font_faces (Union[int, List[int]]): The font family of + texts when use 'cv2' backend. ``font_face`` can have the same length with texts or + just single value. If ``font_face`` is single value, all + the texts will have the same font family. + font_face can be 'cv2.FONT_HERSHEY_SIMPLEX', + 'cv2.FONT_HERSHEY_PLAIN', 'cv2.FONT_HERSHEY_DUPLEX', 'cv2.FONT_HERSHEY_COMPLEX', + 'cv2.FONT_HERSHEY_TRIPLEX', 'cv2.FONT_HERSHEY_COMPLEX_SMALL', 'cv2.FONT_HERSHEY_SCRIPT_SIMPLEX', + 'cv2.FONT_HERSHEY_SCRIPT_COMPLEX', 'cv2.FONT_ITALIC' in `cv2` backend. Defaults to 'cv2.FONT_HERSHEY_SIMPLEX'. + `New in version 0.8.5.` """ # noqa: E501 check_type('texts', texts, (str, list)) if isinstance(texts, str): From ca7f3ab3ded0e905c302c28841dcc8210a14d53c Mon Sep 17 00:00:00 2001 From: Longkai Cheng Date: Mon, 14 Aug 2023 11:26:06 +0800 Subject: [PATCH 44/46] Update mmengine/visualization/visualizer.py Co-authored-by: Zaida Zhou <58739961+zhouzaida@users.noreply.github.com> --- mmengine/visualization/visualizer.py | 10 +++------- 1 file changed, 3 insertions(+), 7 deletions(-) diff --git a/mmengine/visualization/visualizer.py b/mmengine/visualization/visualizer.py index a6524a6826..925f208713 100644 --- a/mmengine/visualization/visualizer.py +++ b/mmengine/visualization/visualizer.py @@ -639,14 +639,10 @@ def draw_texts( color=colors[i]) else: warnings.warn( - 'When using cv2 as the backend for visualizer, ' - 'because `cv.putText(img, text, org, fontFace, ' - 'fontScale, color[, thickness[, lineType[, ' - 'bottomLeftOrigin]]])->img`, ' - 'the parameters `fontproperties`、' - '`vertical_alignments`、`font_families` ' + 'The parameters `font_properties`, ' + '`vertical_alignments`, `font_families` ' 'and `horizontal_alignments` ' - 'will be discarded and not called.', UserWarning) + 'will be ignored when using cv2 backend.', UserWarning) colors = color_val_opencv(colors) # type:ignore font_faces = value2list(font_faces, int, num_text) font_sizes = [font_size / 22.0 for font_size in font_sizes] From 7a88458b256b5eb13582d1f87029bc621778032d Mon Sep 17 00:00:00 2001 From: KerwinKai <101576779+KerwinKai@users.noreply.github.com> Date: Mon, 14 Aug 2023 12:34:41 +0800 Subject: [PATCH 45/46] explain default font size 22 --- mmengine/visualization/visualizer.py | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/mmengine/visualization/visualizer.py b/mmengine/visualization/visualizer.py index 925f208713..a59c8b6ab4 100644 --- a/mmengine/visualization/visualizer.py +++ b/mmengine/visualization/visualizer.py @@ -228,6 +228,9 @@ def __init__(self, # When set to 20, the markers drawn by cv2 and matplotlib look similar self._default_cv2_markersize = 20 + # When set to 22, the font size drawn by + # cv2 and matplotlib look similar + self._default_cv2_fontsize = 22 @property # type: ignore @master_only @@ -645,7 +648,9 @@ def draw_texts( 'will be ignored when using cv2 backend.', UserWarning) colors = color_val_opencv(colors) # type:ignore font_faces = value2list(font_faces, int, num_text) - font_sizes = [font_size / 22.0 for font_size in font_sizes] + font_sizes = [ + font_size / self._default_font_size for font_size in font_sizes + ] for position, text, font_face, font_size, color, bbox in zip( positions, texts, font_faces, font_sizes, colors, bboxes): (text_width, text_height) = cv2.getTextSize( From 2398154dd2492dd9956c7609baad686bee99d6a9 Mon Sep 17 00:00:00 2001 From: KerwinKai <101576779+KerwinKai@users.noreply.github.com> Date: Mon, 14 Aug 2023 13:05:01 +0800 Subject: [PATCH 46/46] fix draw_binary_masks --- mmengine/visualization/visualizer.py | 43 +++++++++------------------- 1 file changed, 14 insertions(+), 29 deletions(-) diff --git a/mmengine/visualization/visualizer.py b/mmengine/visualization/visualizer.py index a59c8b6ab4..9d34ae982f 100644 --- a/mmengine/visualization/visualizer.py +++ b/mmengine/visualization/visualizer.py @@ -18,7 +18,7 @@ from mmengine.structures import BaseDataElement from mmengine.utils import ManagerMixin, is_seq_of from mmengine.visualization.utils import (check_type, check_type_and_length, - color_str2rgb, color_val_matplotlib, + color_val_matplotlib, color_val_opencv, convert_overlay_heatmap, img_from_canvas, tensor2ndarray, @@ -649,7 +649,8 @@ def draw_texts( colors = color_val_opencv(colors) # type:ignore font_faces = value2list(font_faces, int, num_text) font_sizes = [ - font_size / self._default_font_size for font_size in font_sizes + font_size / self._default_cv2_fontsize + for font_size in font_sizes ] for position, text, font_face, font_size, color, bbox in zip( positions, texts, font_faces, font_sizes, colors, bboxes): @@ -1175,39 +1176,23 @@ def draw_binary_masks( if isinstance(alphas, float): alphas = [alphas] * binary_mask_len + colors = color_val_opencv(colors) # type:ignore + for binary_mask, color, alpha in zip(binary_masks, colors, alphas): + binary_mask_complement = cv2.bitwise_not(binary_mask) + rgb = np.zeros_like(img) + rgb[...] = color + rgb = cv2.bitwise_and(rgb, rgb, mask=binary_mask) + img_complement = cv2.bitwise_and( + img, img, mask=binary_mask_complement) + rgb = rgb + img_complement + img = cv2.addWeighted(img, 1 - alpha, rgb, alpha, 0) + if self.backend == 'matplotlib': - colors = [ - color_str2rgb(color) if isinstance(color, str) else color - for color in colors - ] - for color in colors: - assert len(color) == 3 - for channel in color: - assert 0 <= channel <= 255 # type: ignore - for binary_mask, color, alpha in zip(binary_masks, colors, alphas): - binary_mask_complement = cv2.bitwise_not(binary_mask) - rgb = np.zeros_like(img) - rgb[...] = color - rgb = cv2.bitwise_and(rgb, rgb, mask=binary_mask) - img_complement = cv2.bitwise_and( - img, img, mask=binary_mask_complement) - rgb = rgb + img_complement - img = cv2.addWeighted(img, 1 - alpha, rgb, alpha, 0) self.ax_save.imshow( img, extent=(0, self.width, self.height, 0), interpolation='nearest') else: - colors = color_val_opencv(colors) # type:ignore - for binary_mask, color, alpha in zip(binary_masks, colors, alphas): - binary_mask_complement = cv2.bitwise_not(binary_mask) - bgr = np.zeros_like(img) - bgr[...] = color - bgr = cv2.bitwise_and(bgr, bgr, mask=binary_mask) - img_complement = cv2.bitwise_and( - img, img, mask=binary_mask_complement) - bgr = bgr + img_complement - img = cv2.addWeighted(img, 1 - alpha, bgr, alpha, 0) self._image = img return self