Source code for swtloc.base

# Author : Achintya Gupta
# Purpose : Base Classes

import numpy as np
from cv2 import cv2
from typing import Union
from typing import List
from typing import Dict
from typing import ByteString


[docs]class IndividualComponentBase: """ Base class representing the Individual Components found in an transformed image for example: a *Letter*. """ def __init__(self, label: int, image_height: int, image_width: int): """ Create an ``IndividualComponentBase`` object which will house the components properties such as : - Minimum Bounding Box & its related properties - External Bounding Box & its related properties - Outline (Contour) - Original Image Properties Args: label (int) : A unique identifier for this Component image_height (int) : Height of the image in which this component resides image_width (int) : Width of the image in which this component resides """ # Identifier of this individual component self.label: int = label # Contour of this individual component self.outline: list = [] # Number of pixels in this individual component self.area_pixels: int = -1 # Magnitude of the median color vectors `self.original_color_median` self.color_median_mag: float = 0.0 # Width of the image in which this individual component resides self.image_width: int = image_width # Mean color per-channel in this individual component self.original_color_mean: np.ndarray = np.array([-1, -1, -1]) # Width of the image in which this individual component resides self.image_height: int = image_height # Median color per-channel in this individual component self.original_color_median: np.ndarray = np.array([-1, -1, -1]) # # Minimum Bounding Box # Minimum Bounding Box of this individual component self.min_bbox: np.ndarray = np.array([]) # Minimum Bounding Box Centre-X of this individual component self.min_bbox_cx: int = -1 # Minimum Bounding Box Centre-Y of this individual component self.min_bbox_cy: int = -1 # Minimum Bounding Box Filled Mask with label of this individual component self.min_label_mask: np.ndarray = np.array([]) # Minimum Bounding Box Width of this individual component self.min_bbox_width: int = -1 # Minimum Bounding Box Rotation angle with vertical of this individual component self.min_bbox_angle: float = 0.0 # np.array([label, centre_x, centre_y]) of this individual component self.label_n_centre: np.ndarray = np.array([]) # Minimum Bounding Box Height of this individual component self.min_bbox_height: int = -1 # Minimum Bounding Box Centre co-ordinates of this individual component self.min_bbox_centre: np.ndarray = np.array([]) # Minimum Bounding Box Aspect Ration of this individual component self.min_bbox_aspect_ratio: float = 0.0 # Minimum Bounding Box Top Left Point self.min_bbox_anchor_point: np.ndarray = np.array([]) # Minimum Bounding Box Circum Radius of this individual component self.min_bbox_circum_radii: float = 0.0 # # Extreme Bounding Box # External Bounding Box of this individual component self.ext_bbox: np.ndarray = np.array([]) # External Bounding Box Centre-X of this individual component self.ext_bbox_cx: int = -1 # External Bounding Box Centre-Y of this individual component self.ext_bbox_cy: int = -1 # External Bounding Box Width of this individual component self.ext_bbox_width: int = -1 # External Bounding Box Height of this individual component self.ext_bbox_height: int = -1 # External Bounding Box Aspect Ratio of this individual component self.ext_bbox_aspect_ratio: float = 0.0 # External Bounding Box Top Left Point self.ext_bbox_anchor_point: np.ndarray = np.array([]) def __repr__(self): """Representational String""" return f"IndividualComponent-{self.label}" def _setIcProps(self, area: int, color_mean: np.ndarray, color_median: np.ndarray, outline: Union[List, np.ndarray]): """ Set the Individual Component generic properties, such as the area, color_mean, color_median and outline. These properties are calculated after masking out everything else but the Individual Component. Args: area (int) : Number of pixels corresponding to this individual component color_mean (np.ndarray) : Mean of the individual component in the original image color_median (np.ndarray) : Median of the individual component in the original image outline (Union[List, np.ndarray]) : Outline of the individual component in the original image """ self.area_pixels = area self.original_color_mean = color_mean self.original_color_median = color_median self.color_median_mag = np.linalg.norm(color_median) self.outline = outline def _setMinimumBBoxProps(self, min_height: int, min_width: int, min_cx: int, min_cy: int, min_ar: float, angle: float, anchor: np.ndarray, min_bbox: np.ndarray): """ Set the Individual Component Minimum Bounding Box properties, where a minimum bounding box is a rotated bounding box which completely contains this individual component. All these initialisations are available as attributes in this class, apart from these the following are available as well : - min_bbox_circum_radii (float) : Circum Radius of the Minimum Bounding Box - min_bbox_centre (np.ndarray) : Centre Co-Ordinate of the Minimum Bounding Box - label_n_centre (np.ndarray) : np.array([label, cx, cy]) - min_label_mask (np.ndarray) : Boolean mask of the filled Minimum Bounding Box in a numpy array filled with 0's Args: min_height (int) : Height of the Minimum Bounding Box (Attribute : `min_bbox_height`) min_width (int) : Width of the Minimum Bounding Box (Attribute : `min_bbox_width`) min_cx (int) : Centre-X ordinate of the Minimum Bounding Box (Attribute : `min_bbox_cx`) min_cy (int) : Centre-Y ordinate of the Minimum Bounding Box (Attribute : `min_bbox_cy`) min_ar (float) : Aspect Ratio the Minimum Bounding Box (Attribute : `min_bbox_aspect_ratio`) angle (float) : Angle of the Minimum Bounding Box (Attribute : `min_bbox_angle`) anchor (np.ndarray) : Anchor (Top Left Co-Ordinate) of the Minimum Bounding Box (Attribute : `min_bbox_anchor_point`) min_bbox (np.ndarray) : Minimum Bounding Box (Attribute : `min_bbox`) """ self.min_bbox_height = min_height self.min_bbox_width = min_width self.min_bbox_cx = min_cx self.min_bbox_cy = min_cy self.min_bbox_aspect_ratio = min_ar self.min_bbox_angle = angle self.min_bbox_anchor_point = anchor self.min_bbox = min_bbox self.min_bbox_circum_radii = np.sqrt(min_height ** 2 + min_width ** 2) / 2 self.min_bbox_centre = np.array([min_cx, min_cy]) self.label_n_centre = np.array([self.label, min_cx, min_cy]) self.min_label_mask = np.full(shape=(self.image_height, self.image_width), fill_value=0, dtype=np.uint8) self.min_label_mask = self.addLocalization(image=self.min_label_mask, localize_type='min_bbox', fill=True) self.min_label_mask = self.min_label_mask / 255 self.min_label_mask = self.min_label_mask * self.label def _setExternalBBoxProps(self, ext_height: int, ext_width: int, ext_cx: int, ext_cy: int, ext_ar: float, ext_anchor: list, ext_bbox: np.ndarray): """ Set the Individual Component External Bounding Box properties, where a external bounding box is a erect bounding box which completely contains this individual component,calculated using the individual component extremes. All these initialisations are available as attributes in this class, apart from these the following are available as well : Args: ext_height (int) : Height of the External Bounding Box (Attribute : `ext_bbox_height`) ext_width (int) : Width of the External Bounding Box (Attribute : `ext_bbox_width`) ext_cx (int) : Centre-X ordinate of the External Bounding Box (Attribute : `ext_bbox_cx`) ext_cy (int) : Centre-Y ordinate of the External Bounding Box (Attribute : `ext_bbox_cy`) ext_ar (float) : Aspect Ratio the External Bounding Box (Attribute : `ext_bbox_aspect_ratio`) ext_anchor (np.ndarray) : Anchor (Top Left Co-Ordinate) of the External Bounding Box (Attribute : `ext_bbox_anchor_point`) ext_bbox (np.ndarray) : External Bounding Box (Attribute : `ext_bbox`) """ self.ext_bbox_height = ext_height self.ext_bbox_width = ext_width self.ext_bbox_cx = ext_cx self.ext_bbox_cy = ext_cy self.ext_bbox_aspect_ratio = ext_ar self.ext_bbox_anchor_point = ext_anchor self.ext_bbox = ext_bbox
[docs] def addLocalization(self, image: np.ndarray, localize_type: str, fill: bool, radius_multiplier: float = 1.0) -> np.ndarray: """ Add a specific `localize_type` of localization to the input `image`. `fill` parameter tells whether to fill the component or not. Args: image (np.ndarray) : Image on which localization needs to be added localize_type (str) : Type of the localization that will be added. Can be only one of ['min_bbox', 'ext_bbox', 'outline', 'circular']. Where : - `min_bbox` : Minimum Bounding Box - `ext_bbox` : External Bounding Box - `outline` : Contour - `circular` : Circle - With Minimum Bounding Box Centre coordinate and radius = Minimum Bounding Box Circum Radius * radius_multiplier fill (bool) : Whether to fill the added localization or not radius_multiplier (float) : Minimum Bounding Box Circum Radius inflation parameter. [default = 1.0]. Returns: (np.ndarray) - annotated image """ _color = (0, 0, 255) if fill: _color = (255, 255, 255) _thickness = (np.sqrt(self.image_height ** 2 + self.image_width ** 2)) * (4 / np.sqrt(768 ** 2 + 1024 ** 2)) _thickness = int(_thickness) if _thickness == 0: _thickness = 1 if localize_type == 'min_bbox' and not fill: image = cv2.polylines(img=image, pts=[self.min_bbox], isClosed=True, color=_color, thickness=_thickness) elif localize_type == 'ext_bbox' and not fill: image = cv2.polylines(img=image, pts=[self.ext_bbox], isClosed=True, color=_color, thickness=_thickness) elif localize_type == 'outline' and not fill: image = cv2.polylines(img=image, pts=self.outline, isClosed=True, color=_color, thickness=_thickness) elif localize_type == 'min_bbox' and fill: image = cv2.fillPoly(img=image, pts=[self.min_bbox], color=_color) elif localize_type == 'ext_bbox' and fill: image = cv2.fillPoly(img=image, pts=[self.ext_bbox], color=_color) elif localize_type == 'outline' and fill: image = cv2.fillPoly(img=image, pts=self.outline, color=_color) elif localize_type == 'circular' and fill: image = cv2.circle(img=image, center=tuple(np.uint32(self.min_bbox_centre)), radius=np.uint32(self.min_bbox_circum_radii * radius_multiplier), color=255, thickness=-1) return image
[docs]class GroupedComponentsBase: """ Base class representing the Grouped Components found in an transformed image for example: a Word. """ def __init__(self, label: int, image_height: int, image_width: int): """ Create an ``IndividualComponentBase`` object which will house the grouped components properties such as : - Various Bounding Shapes which house that particular grouped component entirely Args: label (int) : A unique identifier for this Component image_height (int) : Height of the image in which this component resides image_width (int) : Width of the image in which this component resides """ # Initialise image properties self.image_height = image_height self.image_width = image_width # Identifier for this grouped component self.label: int = label # Grouped component Bounding Box self.bbox: np.ndarray = np.ndarray([]) # Grouped component Contour self.polygon: np.ndarray = np.ndarray([]) # Grouped component Bubble self.bubble: np.ndarray = np.ndarray([]) def __repr__(self): """Representational String""" return f"GroupedComponentBase-{self.label}" def _setBBoxProps(self, bbox: np.ndarray): """ Set the Grouped Component External Bounding Box, where a external bounding box is a erect bounding box which completely contains this grouped component, calculated using the grouped component extremes. Args: bbox (np.ndarray) : Bounding Box (Attribute : `bbox`) """ self.bbox = bbox def _setPolygonProps(self, polygon: Union[List, np.ndarray]): """ Set the Grouped Component External Polygon Bounding, where a polygon boundary is a convex polygon completely containing this grouped component, calculated using contour post mask dilation. Args: polygon (np.ndarray) : Bounding Box (Attribute : `bbox`) """ self.polygon = polygon def _setBubbleProps(self, bubble: Union[List, np.ndarray]): """ Set the Grouped Component Bubble Boundary, where a bubble boundary is a convex polygon made by fusing circular masks of each individual component(Components which belong to this Grouped Component) Args: bubble (np.ndarray) : Bubble Boundary (Attribute : `bubble`) """ self.bubble = bubble
[docs] def addLocalization(self, image: np.ndarray, localize_type: str, fill: bool) -> np.ndarray: """ Add a specific `localize_type` of localization to the input `image`. `fill` parameter tells whether to fill the component or not. Args: image (np.ndarray) : Image on which localization needs to be added localize_type (str) : Type of the localization that will be added. Can be only one of ['bbox', 'bubble', 'polygon']. Where - `bbox` : Bounding Box - `bubble` : Bubble Boundary - `polygon` : Contour Boundary fill (bool) : Whether to fill the added localization or not Returns: (np.ndarray) - annotated image """ _color = (0, 0, 255) if fill: _color = (255, 255, 255) _thickness = (np.sqrt(self.image_height ** 2 + self.image_width ** 2)) * (4 / np.sqrt(768 ** 2 + 1024 ** 2)) _thickness = int(_thickness) if _thickness == 0: _thickness = 1 if localize_type == 'bbox' and not fill: image = cv2.polylines(img=image, pts=[self.bbox], isClosed=True, color=_color, thickness=_thickness) elif localize_type == 'bubble' and not fill: image = cv2.polylines(img=image, pts=self.bubble, isClosed=True, color=_color, thickness=_thickness) elif localize_type == 'polygon' and not fill: image = cv2.polylines(img=image, pts=self.polygon, isClosed=True, color=_color, thickness=_thickness) elif localize_type == 'bbox' and fill: image = cv2.fillPoly(img=image, pts=[self.bbox], color=_color) elif localize_type == 'bubble' and fill: image = cv2.fillPoly(img=image, pts=self.bubble, color=_color) elif localize_type == 'polygon' and fill: image = cv2.fillPoly(img=image, pts=self.polygon, color=_color) return image
[docs]class TextTransformBase: """ Base class for various transformation classes. for example: a SWTImage. """ def __init__(self, image: np.ndarray, image_name: str, input_flag: ByteString, cfg: Dict): """ Create a `TextTransformBase` which will house the properties of the input image, its name, its input type flag, transform configuration and various other parameters corresponding to various stages in the transformation process. Args: image (np.ndarray) : Input image on which transformation will be performed image_name (str) : Name of the input images (Needed while saving the post-transformation results) input_flag (ByteString) : Flag of input type. It can be only one of the following - TRANSFORM_INPUT__1C_IMAGE = b'21' - TRANSFORM_INPUT__3C_IMAGE = b'22' These image codes reside in configs.py file cfg (dict) : Configuration of a particular transformation type. """ # Initialisations # Image on which transformation has to be done self.image: np.ndarray = image # Height of the input image self.image_height: int = self.image.shape[0] # Width of the input image self.image_width: int = self.image.shape[1] # Name of the input image self.image_name: str = image_name # Input image type flag self.input_flag: ByteString = input_flag # Configuration which would be managing this transformation self.cfg: dict = cfg # > Parameters for transformImage # Transformation time self.transform_time: str = '' # Flag to reflect if the transform stage is done self.transform_stage_done: bool = False # > Parameters for localizing letters # Number of Connected Components before pruning self.unpruned_num_cc: int = -1 # Image of Connected Components - Single Channel self.unpruned_image_cc_1C: np.ndarray = np.array([]) # Statistics of Connected Components before pruning self.unpruned_cc_stats: np.ndarray = np.array([]) # Centroids of Connected Components before pruning self.unpruned_cc_centroids: np.ndarray = np.array([]) # Image of Connected Components to be pruning self.image_cc_3C_to_be_pruned: np.ndarray = np.array([]) # Pruned Number of Connected Components self.pruned_num_cc: int = -1 # Pruned Image of Connected Components - Single Channel self.pruned_image_cc_1C: np.ndarray = np.array([]) # Statistics of the Pruned Image of Connected Components self.pruned_cc_stats: np.ndarray = np.array([]) # Centroids of the Pruned Image of Connected Components self.pruned_cc_centroids: np.ndarray = np.array([]) # RGB Channel pruned components which qualified as letters self.image_pruned_3C_letter_localized: np.ndarray = np.array([]) # Original image pruned components which qualified as letters self.image_original_letter_localized: np.ndarray = np.array([]) # Original image with only masked letters self.image_original_masked_letter_localized: np.ndarray = np.array([]) # Individual letter annotated on EDGE Image + SWT Image self.individual_letter_localized_edgeswt: np.ndarray = np.array([]) # Individual letter annotated on Original Image self.individual_letter_localized_original: np.ndarray = np.array([]) # Flags for different stages and `localize_by` self.letter_stage_done: bool = False self.letter_min_done: bool = False self.letter_ext_done: bool = False self.letter_outline_done: bool = False # > Parameters for localizing words # RGB Channel pruned components which qualified as words self.image_pruned_3C_word_localized: np.ndarray = np.array([]) # Original image pruned components which qualified as words self.image_original_word_localized: np.ndarray = np.array([]) # Original image with only masked words self.image_original_masked_word_localized: np.ndarray = np.array([]) # Individual word annotated on EDGE Image + SWT Image self.individual_word_localized_edgeswt: np.ndarray = np.array([]) # Individual word annotated on Original Image self.individual_word_localized_original: np.ndarray = np.array([]) # Flags for different stages and `localize_by` self.word_stage_done: bool = False self.word_bubble_done: bool = False self.word_polygon_done: bool = False self.word_bbox_done: bool = False # ######################################### # # TRANSFORM # # ######################################### # def _resetTransformParams(self): """ Resets the Transform stage parameters and the downstream stage parameters : - findAndPrune Parameters - localizeLetters Parameters - localizeWords Parameters """ # Reset downstream functions self._resetLocalizeLettersParams() self._resetLocalizeWordsParams() # > Parameters for transformImage self.transform_time: str = '' self.transform_stage_done: bool = False # ######################################### # # LOCALIZE LETTERS # # ######################################### # def _resetLocalizeLettersParams(self): """ Resets the localizeLetters stage parameters and the downstream stage parameters : - localizeWords Parameters """ # Reset downstream functions self._resetLocalizeWordsParams() # > Parameters for localizing letters self.unpruned_num_cc: int = -1 self.unpruned_image_cc_1C: np.ndarray = np.array([]) self.unpruned_cc_stats: np.ndarray = np.array([]) self.unpruned_cc_centroids: np.ndarray = np.array([]) self.pruned_num_cc: int = -1 self.pruned_image_cc_1C: np.ndarray = np.array([]) self.pruned_cc_stats: np.ndarray = np.array([]) self.pruned_cc_centroids: np.ndarray = np.array([]) self.image_cc_3C_to_be_pruned: np.ndarray = np.array([]) self.image_pruned_3C_letter_localized: np.ndarray = np.array([]) self.image_original_letter_localized: np.ndarray = np.array([]) self.image_original_masked_letter_localized: np.ndarray = np.array([]) self.individual_letter_localized_edgeswt: np.ndarray = np.array([]) self.individual_letter_localized_original: np.ndarray = np.array([]) self.letter_stage_done: bool = False self.letter_min_done: bool = False self.letter_ext_done: bool = False self.letter_outline_done: bool = False # ######################################### # # LOCALIZE WORDS # # ######################################### # def _resetLocalizeWordsParams(self): """ Resets the localizeWords stage parameters and the downstream stage parameters : """ # > Parameters for localizing words self.image_pruned_3C_word_localized: np.ndarray = np.array([]) self.image_original_word_localized: np.ndarray = np.array([]) self.image_original_masked_word_localized: np.ndarray = np.array([]) self.individual_word_localized_edgeswt: np.ndarray = np.array([]) self.individual_word_localized_original: np.ndarray = np.array([]) self.word_stage_done: bool = False self.word_bubble_done: bool = False self.word_polygon_done: bool = False self.word_bbox_done: bool = False