Source code for iaa_od.visualisation.show_annotations

from iaa_od.models import AnnotationProtocol, Image, GroundTruthProtocol, Result, ScaleComplexity
from iaa_od.models.constants import LABEL_OFFSET
from iaa_od.palette import colormap_to_color, colormap_to_macrocat_color
from .show_utils import upsert_handle, get_image_path

import matplotlib.pyplot as plt
import matplotlib.image as mpimg
from matplotlib.patches import Patch, Rectangle
import numpy as np
from typing import Optional
from copy import deepcopy

[docs] def show_gts_with_annotations(result: Result, filepath: str, /, *, show_stats: bool = False, enable_labels: bool = False, full_legend: bool = False, show_image_filename: bool = False) -> None: """ A wrapper for the function "show_image_with_annotations" that runs it for all images in a given ground truth set. Parameters: result (Result): The Result object containing the ground truths and annotations. filepath (str): The filepath where the images are located. show_stats (bool, optional): Whether to show statistics alongside the annotations. Defaults to False. enable_labels (bool, optional): Whether to display category labels above bounding boxes. Defaults to False. full_legend (bool, optional): Whether to include all categories in the legend, even if not present in the image. Defaults to False. show_image_filename (bool, optional): Whether to include the image filename in the title above the image. Defaults to False. """ if filepath is None or filepath == "": raise ValueError("No filepath provided.") # Get GTs from result object gts: list[GroundTruthProtocol] = result.gts # Get all image filenames from the first GT (all GTs have the same images, this is checked earlier) filenames: list[str] = list(gts[0].images.keys()) if not filenames: raise ValueError("No images found in the provided Ground Truths.") for filename in filenames: show_image_with_annotations(result, filename, filepath, show_stats=show_stats, enable_labels=enable_labels, full_legend=full_legend, show_image_filename=show_image_filename) return
[docs] def show_image_with_annotations(result: Result, filename: str, filepath: str, /, *, show_stats: bool = False, enable_labels: bool = False, full_legend: bool = False, show_image_filename: bool = False) -> None: """ Displays the specified image with bounding box annotations from all ground truths in the provided Result object. Parameters: result (Result): The Result object containing the ground truths and annotations. filename (str): The filename of the image to display. filepath (str): The filepath where the image is located. show_stats (bool, optional): Whether to show statistics alongside the annotations. Defaults to False. enable_labels (bool, optional): Whether to display category labels above bounding boxes. Defaults to False. full_legend (bool, optional): Whether to include all categories in the legend, even if not present in the image. Defaults to False. show_image_filename (bool, optional): Whether to include the image filename in the title above the image. Defaults to False. """ if filename is None or filename == "": raise ValueError("No filename provided.") if filepath is None or filepath == "": raise ValueError("No filepath provided.") # Get GTs from result object gts: list[GroundTruthProtocol] = result.gts # Get the selected image from the first GT (all GTs have the same images, this is checked earlier) full_path: str = get_image_path(filename, filepath) image: Image | None = gts[0].images.get(filename) if not image: raise ValueError(f"Image '{filename}' not found in the provided Ground Truths.") # Get all annotations for this image from all GTs annotations: dict[str, list[AnnotationProtocol]] = {} for gt in gts: anns = deepcopy(gt.annotations[filename]) annotations[gt.name] = anns # Order annotations by GT name to ensure consistent ordering of GTs across images annotations = dict(sorted(annotations.items(), key=lambda item: item[0])) # Initialise colour dictionary for categories colour_map = plt.get_cmap('hsv', len(gts[0].categories_dict) + 1) colour_dict = colormap_to_color(colour_map, gts[0].categories_dict) # Initialise the image plot _, axes = plt.subplots(1, gts.__len__(), figsize=(10 * gts.__len__(), 10), sharex=True, sharey=True) img: np.ndarray = mpimg.imread(full_path) # NOTE: This is needed as plt.Rectangle requires the anchor point of the image to be in the lower-left corner for ax in axes: ax.imshow(img, origin='upper') ax.set_xticks([]) ax.set_yticks([]) for spine in ax.spines.values(): spine.set_visible(False) handles = [] # Fills the legend with all categories, regardless whether they are present in the image or not if full_legend: for cat in gts[0].categories_dict.keys(): colours = colour_dict[cat] line_colour = colours.line_color fill_colour = colours.fill_color handles.append(Patch(color=fill_colour, label=gts[0].categories_dict[cat])) # Plot all annotations from all GTs for idx, (gt_name, anns) in enumerate(annotations.items()): # Write the GT name above the image title_str: str if idx == 0 and show_image_filename: title_str = f"{filename} - {gt_name}" else: title_str = gt_name axes[idx].set_title(title_str, fontsize=16, fontweight='bold') for ann in anns: # Get the colour for this category colours = colour_dict[ann.category_id] line_colour = colours.line_color fill_colour = colours.fill_color # Add the legend handle for this category if it was not already added new_handle = Patch(color=line_colour, label=gts[0].categories_dict[ann.category_id]) upsert_handle(handles, new_handle) bbox = ann.bbox_coords.coords rect = Rectangle((bbox.x, bbox.y), bbox.w, bbox.h, linewidth=1, edgecolor=line_colour, facecolor=fill_colour, label=gt_name) axes[idx].add_patch(rect) # Add the label above the bounding box with the category name cat_name = gts[0].categories_dict.get(ann.category_id) if enable_labels: axes[idx].text(bbox.x, bbox.y - LABEL_OFFSET, cat_name, color='white', fontsize=8, backgroundcolor='black') # Add the legend to the right of the last subplot if handles: axes[len(gts) // 2].legend(handles=handles, loc='lower center', bbox_to_anchor=(0.5, -0.15), ncols=len(handles) // 2, fontsize='xx-large') # Add some stats to the side of the legend if not show_stats: plt.tight_layout() plt.show() return stat_text: str = f"Image: {filename}\n\n" if result.alpha: stat_text += f"Global alpha: {result.alpha:.3f}\n" if result.alpha_per_image: stat_text += f"Image alpha: {result.alpha_per_image[filename]:.3f}\n" if result.mean_alpha: stat_text += f"Mean alpha: {result.mean_alpha:.3f}\n" stat_text += "\n" stat_text += f"IoU threshold: {result.iou_thr}\n" stat_text += "\n" if not handles: handles_x = 1.05 else: handles_x = 1.75 axes[-1].text(handles_x, 0.1, stat_text, transform=axes[-1].transAxes, fontsize=12) plt.tight_layout() plt.show() return
[docs] def show_image_with_annotations_macrocategories(result: Result, filename: str, filepath: str, collapsed_categories: dict[str, list[int]], /, *, enable_labels: bool = False, full_legend: bool = False, show_image_filename: bool = False) -> None: """ Displays the specified image with bounding box annotations from all ground truths in the provided Result object. Parameters: result (Result): The Result object containing the ground truths and annotations. filename (str): The filename of the image to display. filepath (str): The filepath where the image is located. collapsed_categories (dict[str, list[int]]): A dictionary mapping macro-category names to lists of category IDs that belong to that macro-category. enable_labels (bool, optional): Whether to display category labels above bounding boxes. Defaults to False. full_legend (bool, optional): Whether to include all macro-categories in the legend, even if not present in the image. Defaults to False. show_image_filename (bool, optional): Whether to include the image filename in the title above the image. Defaults to False. """ if filename is None or filename == "": raise ValueError("No filename provided.") if filepath is None or filepath == "": raise ValueError("No filepath provided.") # Get GTs from result object gts: list[GroundTruthProtocol] = result.gts # Get the selected image from the first GT (all GTs have the same images, this is checked earlier) full_path: str = get_image_path(filename, filepath) image: Image | None = gts[0].images.get(filename) if not image: raise ValueError(f"Image '{filename}' not found in the provided Ground Truths.") # Get all annotations for this image from all GTs annotations: dict[str, list[AnnotationProtocol]] = {} for gt in gts: anns = gt.annotations[filename] annotations[gt.name] = anns # Initialise colour dictionary for categories colour_map = plt.get_cmap('hsv', len(collapsed_categories) + 1) colour_dict = colormap_to_macrocat_color(colour_map, collapsed_categories) # Initialise lookup table for collapsed categories macro_lut: dict[int, str] = {} cat_ids: list[int] = list(gts[0].categories_dict.keys()) for cat_id in cat_ids: for macro_cat, subcats in collapsed_categories.items(): if cat_id in subcats: macro_lut[cat_id] = macro_cat break # Initialise the image plot _, axes = plt.subplots(1, gts.__len__(), figsize=(10 * gts.__len__(), 10), sharex=True, sharey=True) img: np.ndarray = mpimg.imread(full_path) # NOTE: This is needed as plt.Rectangle requires the anchor point of the image to be in the lower-left corner for ax in axes: ax.imshow(img, origin='upper') ax.set_xticks([]) ax.set_yticks([]) for spine in ax.spines.values(): spine.set_visible(False) handles = [] # Fills the legend with all categories, regardless whether they are present in the image or not if full_legend: for macro_cat in collapsed_categories.keys(): colours = colour_dict[macro_cat] line_colour = colours.line_color fill_colour = colours.fill_color handles.append(Patch(color=fill_colour, label=macro_cat)) # Plot all annotations from all GTs for idx, (gt_name, anns) in enumerate(annotations.items()): # Write the GT name above the image title_str: str if idx == 0 and show_image_filename: title_str = f"{filename} - {gt_name}" else: title_str = gt_name axes[idx].set_title(title_str, fontsize=16, fontweight='bold') for ann in anns: # Get the colour for this category macrocat = macro_lut[ann.category_id] colours = colour_dict[macrocat] line_colour = colours.line_color fill_colour = colours.fill_color # Add the legend handle for this category if it was not already added new_handle = Patch(color=line_colour, label=macrocat) upsert_handle(handles, new_handle) bbox = ann.bbox_coords.coords rect = Rectangle((bbox.x, bbox.y), bbox.w, bbox.h, linewidth=1, edgecolor=line_colour, facecolor=fill_colour, label=gt_name) axes[idx].add_patch(rect) # Add the label above the bounding box with the category name cat_name = gts[0].categories_dict.get(ann.category_id) if enable_labels: axes[idx].text(bbox.x, bbox.y - LABEL_OFFSET, cat_name, color='white', fontsize=8, backgroundcolor='black') # Add the legend to the right of the last subplot if handles: axes[len(gts) // 2].legend(handles=handles, loc='lower center', bbox_to_anchor=(0.5, -0.15), fontsize='xx-large') plt.tight_layout() plt.show() return
[docs] def compare_images_with_macrocategories(result: Result, filename: str, filepath: str, collapsed_categories: dict[str, list[int]], /, *, enable_labels: bool = False, show_image_filename: bool = False, sc_data: Optional[ScaleComplexity] = None) -> None: """ Displays the specified image with bounding box annotations from all ground truths in the provided Result object, comparing both the original categories (top row) and the collapsed macro-categories (bottom row). Parameters: result (Result): The Result object containing the ground truths and annotations. filename (str): The filename of the image to display. filepath (str): The filepath where the image is located. collapsed_categories (dict[str, list[int]]): A dictionary mapping macro-category names to lists of category IDs that belong to that macro-category. enable_labels (bool, optional): Whether to display category labels above bounding boxes. Defaults to False. show_image_filename (bool, optional): Whether to include the image filename in the title above the image. Defaults to False. sc_data (ScaleComplexity | None): Optional ScaleComplexity object containing Scale Complexity data for the images, which will be displayed in the title of the bottom row if provided. Defaults to None. """ if filename is None or filename == "": raise ValueError("No filename provided.") if filepath is None or filepath == "": raise ValueError("No filepath provided.") # Get GTs from result object gts: list[GroundTruthProtocol] = result.gts # Get the selected image from the first GT (all GTs have the same images, this is checked earlier) full_path: str = get_image_path(filename, filepath) image: Image | None = gts[0].images.get(filename) if not image: raise ValueError(f"Image '{filename}' not found in the provided Ground Truths.") # Get all annotations for this image from all GTs annotations: dict[str, list[AnnotationProtocol]] = {} for gt in gts: anns = gt.annotations[filename] annotations[gt.name] = anns # Sort this dictionary by GT name to ensure consistent ordering of GTs across images annotations = dict(sorted(annotations.items(), key=lambda item: item[0])) # Initialise colour dictionary for categories and macro-categories colour_map_cat = plt.get_cmap('hsv', len(gts[0].categories_dict) + 1) colour_dict_cat = colormap_to_color(colour_map_cat, gts[0].categories_dict) colour_map_macro = plt.get_cmap('hsv', len(collapsed_categories) + 1) colour_dict_macro = colormap_to_macrocat_color(colour_map_macro, collapsed_categories) # Initialise lookup table for collapsed categories macro_lut: dict[int, str] = {} cat_ids: list[int] = list(gts[0].categories_dict.keys()) for cat_id in cat_ids: for macro_cat, subcats in collapsed_categories.items(): if cat_id in subcats: macro_lut[cat_id] = macro_cat break # Initialise the image plot fig, axes = plt.subplots(2, gts.__len__(), figsize=(10 * gts.__len__(), 10), sharex=True, sharey=True) # Maximise window size fig = plt.gcf() manager = plt.get_current_fig_manager() # Try different methods to maximise the window, as this is platform-dependent try: manager.window.showMaximized() except: try: manager.window.state('zoomed') except: pass img: np.ndarray = mpimg.imread(full_path) # NOTE: This is needed as plt.Rectangle requires the anchor point of the image to be in the lower-left corner for c in range(len(gts)): for r in range(2): ax = axes[r, c] ax.imshow(img, origin='upper') ax.set_xticks([]) ax.set_yticks([]) for spine in ax.spines.values(): spine.set_visible(False) handles_cat = [] handles_macro = [] # Plot all annotations from all GTs for idx, (gt_name, anns) in enumerate(annotations.items()): # Write the GT name above the image title_str: str if idx == 0 and show_image_filename: title_str = f"{filename} - {gt_name}" else: title_str = gt_name axes[0, idx].set_title(title_str, fontsize=16, fontweight='bold') # If Scale Complexity data is provided, show the agreement and SC values for this image in the titles of the bottom row if sc_data is not None: sc_value: float = sc_data.sc_for_image(filename) agreement_value: tuple[float, float] = sc_data.agreement_for_image(filename) title_str_sc: str = f"Agreement: {agreement_value[0]:.2f} -> {agreement_value[1]:.2f}\nSC: {sc_value:.2f}" axes[1, len(gts) // 2].set_title(title_str_sc, fontsize=16, fontweight='bold') for ann in anns: # Top row: categories # Get the colour for this category colours = colour_dict_cat[ann.category_id] line_colour = colours.line_color fill_colour = colours.fill_color # Add the legend handle for this category if it was not already added new_handle = Patch(color=line_colour, label=gts[0].categories_dict[ann.category_id]) upsert_handle(handles_cat, new_handle) bbox = ann.bbox_coords.coords rect = Rectangle((bbox.x, bbox.y), bbox.w, bbox.h, linewidth=1, edgecolor=line_colour, facecolor=fill_colour, label=gt_name) axes[0, idx].add_patch(rect) # Add the label above the bounding box with the category name cat_name = gts[0].categories_dict.get(ann.category_id) if enable_labels: axes[0, idx].text(bbox.x, bbox.y - LABEL_OFFSET, cat_name, color='white', fontsize=8, backgroundcolor='black') # Bottom row: macro-categories # Get the colour for this category macrocat = macro_lut[ann.category_id] colours = colour_dict_macro[macrocat] line_colour = colours.line_color fill_colour = colours.fill_color # Add the legend handle for this category if it was not already added new_handle = Patch(color=line_colour, label=macrocat) upsert_handle(handles_macro, new_handle) bbox = ann.bbox_coords.coords rect = Rectangle((bbox.x, bbox.y), bbox.w, bbox.h, linewidth=1, edgecolor=line_colour, facecolor=fill_colour, label=gt_name) axes[1, idx].add_patch(rect) # Add the label above the bounding box with the category name cat_name = gts[0].categories_dict.get(ann.category_id) if enable_labels: axes[1, idx].text(bbox.x, bbox.y - LABEL_OFFSET, cat_name, color='white', fontsize=8, backgroundcolor='black') # Add the legend to the right of the last subplot if handles_cat: axes[0, len(gts) // 2].legend(handles=handles_cat, loc='lower center', bbox_to_anchor=(0.5, -0.15), ncols=len(handles_cat) // 2 + 1, fontsize='xx-large') if handles_macro: axes[1, len(gts) // 2].legend(handles=handles_macro, loc='lower center', bbox_to_anchor=(0.5, -0.15), ncols=len(handles_macro) // 2 + 1, fontsize='xx-large') plt.tight_layout() plt.show() return