Module AmpliVision.src.objs.grid.Square
Classes
class Square (tl: int, br: int, index: ast.Tuple, PIN_RATIO: int, PLUS_MINUS: int, img: numpy.ndarray)
-
Square
Class that represents a square in the grid_ds.
Args:
- tl: top left point of the square
- br: bottom right point of the square
- index: index of the square in the grid_ds
Attributes:
- tl: top left point of the square
- br: bottom right point of the square
- index: index of the square in the grid_ds
- block: boolean that indicates if the square is a block
- pin_count: number of pins in the square
Methods:
- add_pin: adds a pin to the square
- draw_pins: draws the pins in the square
- draw_corners: draws the corners of the square
- createImg: creates an image of the square, a cutout of the image around the square
- add_corners: adds the corners of the square to the square object
- is_in_corners: checks if a point is in the corners of the square
- which_corner_is_contour_in: finds which corner of square a contour is in
- get_rgb_avg_of_contour: gets the average RGB of a contour in the image
- get_pins_rgb: gets the average RGB of the pins in the square
Expand source code
class Square: """ ### Square --------------- Class that represents a square in the grid_ds. #### Args: * tl: top left point of the square * br: bottom right point of the square * index: index of the square in the grid_ds #### Attributes: * tl: top left point of the square * br: bottom right point of the square * index: index of the square in the grid_ds * block: boolean that indicates if the square is a block * pin_count: number of pins in the square #### Methods: * add_pin: adds a pin to the square * draw_pins: draws the pins in the square * draw_corners: draws the corners of the square * createImg: creates an image of the square, a cutout of the image around the square * add_corners: adds the corners of the square to the square object * is_in_corners: checks if a point is in the corners of the square * which_corner_is_contour_in: finds which corner of square a contour is in * get_rgb_avg_of_contour: gets the average RGB of a contour in the image * get_pins_rgb: gets the average RGB of the pins in the square """ def __init__(self, tl: int, br: int, index: Tuple, PIN_RATIO: int, PLUS_MINUS: int, img: np.ndarray) -> None: # potential pins self.p_pins = [] # pins self.pins = [] # block or not and type of block self.is_block = False self.block_type = '' # RBG values of the pins in the square (tl, tr, bl, br) self.rgb_sequence = [] # coordinates and index in Grid self.tl = tl self.br = br self.index = index # image and image of the square for visualization if necessary self.img = img.copy() if img is not None: self.sq_img = self.createImg(img.copy()) # corners of the square self.corners = [] self.add_corners(PIN_RATIO, PLUS_MINUS) self.test_area_img = None # rotation of the block. 0 is vertical strip with bkg on bottom. 1 is horizontal strip with bkg on left side. etc. self.rotation = 0 # ratios self.PIN_RATIO = PIN_RATIO self.PLUS_MINUS = PLUS_MINUS ## Get functions ## def get_index(self) -> Tuple: """ Returns the index of the square """ return self.index def get_p_pins(self) -> list[int]: """ Returns the potential pins in the square """ return self.p_pins def get_pins(self) -> list[int]: """ Returns the pins in the square """ return self.pins def get_corners(self) -> list[int]: """ Returns the corners of the square """ return self.corners def get_img(self) -> np.ndarray: """ Returns the image of the square """ return self.img def get_sq_img(self) -> np.ndarray: """ Returns the image of the square """ if self.sq_img is None: self.sq_img = self.createImg(self.img) return self.sq_img def get_test_area_img(self) -> np.ndarray: " Returns the image of squares test area (inner square where test strip can be)" if self.test_area_img is None: self.test_area_img = self.create_test_area_img(self.get_sq_img()) return self.test_area_img def get_block_type(self) -> str: """ Returns the block type of the square """ if self.is_block: return self.block_type else: return "Not a block" def get_rgb_sequence(self) -> list[int]: """ Returns the RGB sequence of the square """ return self.rgb_sequence def createImg(self, img: np.ndarray) -> np.ndarray: """ Creates an image of the square, a cutout of the image around the square""" return img[(self.tl[1]-10):(self.br[1]+10), (self.tl[0]-10):(self.br[0]+10)] def create_test_area_img(self, sq_img: np.ndarray) -> np.ndarray: " Creates an image of the inner test spot" sq_img = self.img corners = self.calculate_corners_pinbased() """ r = sq_img[a:b, c:d] means that the image r is a cutout of the image sq_img. from the top left corner (a, c) to the bottom right corner (b, d). where a, b, c, d are the coordinates of the corners of the square. for example, a = corners[0][1][1] means that a is the y coordinate of the bottom right corner of the top right corner of the square. b = corners[2][0][1] means that b is the y coordinate of the top left corner of the bottom right corner of the square. c = corners[0][1][0] means that c is the x coordinate of the bottom right corner of the top right corner of the square. d = corners[2][0][0] means that d is the x coordinate of the top left corner of the bottom right corner of the square. """ return sq_img[corners[0][1][1]:corners[2][0][1], corners[0][1][0]:corners[2][0][0]] ## Add functions ## def add_pin(self, pin: np.ndarray) -> None: """ Adds a pin to the square """ self.pins.append(pin) def add_p_pin(self, pin: np.ndarray) -> None: """ Adds a potential pin to the square """ self.p_pins.append(pin) def add_corners(self, PIN_RATIO: int, PLUS_MINUS: int, p: int = 3, a: float = 1.8) -> None: """ Adds the corners of the square to the square object #### Args: * PIN_RATIO: ratio of the pin size to the square size * PLUS_MINUS: arbitrary tolerance value * p: "padding" value. Determines size of the corners. * a: skew value. Is the exponential determining how skewed the corners are. """ # top left and bottom right coordinates of the square tl_x, tl_y = self.tl br_x, br_y = self.br # Skewing the corners in relation to the center of the grid to account for perspective. # the further away from the center, the more skewed the corners are (exponential). # Avoiding division by zero SKEW_x, SKEW_y = self.calculate_skew(a) # The following four values: top_right, top_left, bottom_right, bottom_left are the corners of the square. # Each corner contains its top left and bottom right coordinates. # Coordinates are calculated using: # top left and bottom right coordinates of the square, arbitrary plus minus value, the padding value and the skew value. self.corners = self.calculate_corners( tl_x, tl_y, br_x, br_y, PIN_RATIO, PLUS_MINUS, p, SKEW_x, SKEW_y) def calculate_skew(self, a: float) -> Tuple: """ Calculates the skew originated from cellphone cameras |x-4|^a * (x-4)/|x-4| """ if self.index[0] != 4: SKEW_x = int( (abs(self.index[0] - 4) ** a) * ((self.index[0] - 4) / abs(self.index[0] - 4))) else: SKEW_x = 0 # Avoiding division by zero if self.index[1] != 4: SKEW_y = int( (abs(self.index[1] - 4) ** a) * ((self.index[1] - 4) / abs(self.index[1] - 4))) else: SKEW_y = 0 return SKEW_x, SKEW_y def calculate_corners(self, tl_x: int, tl_y: int, br_x: int, br_y: int, PIN_RATIO: int, PLUS_MINUS: int, p: int, SKEW_x: int, SKEW_y: int) -> list[int]: """ Calculates the corners of the square using magic. The "corners" here refer to the space in the ampli block where the pins are located. """ top_right = ( (tl_x - (p*PLUS_MINUS) + SKEW_x, tl_y - (p*PLUS_MINUS) + SKEW_y), (tl_x + PIN_RATIO + (p*PLUS_MINUS) + SKEW_x, tl_y + PIN_RATIO + (p*PLUS_MINUS) + SKEW_y) ) top_left = ( (br_x - PIN_RATIO - (p*PLUS_MINUS) + SKEW_x, tl_y - (p*PLUS_MINUS) + SKEW_y), (br_x + (p*PLUS_MINUS) + SKEW_x, tl_y + PIN_RATIO + (p*PLUS_MINUS) + SKEW_y) ) bottom_right = ( (tl_x - (p*PLUS_MINUS) + SKEW_x, br_y - PIN_RATIO - (p*PLUS_MINUS) + SKEW_y), (tl_x + PIN_RATIO+(p*PLUS_MINUS) + SKEW_x, br_y + (p*PLUS_MINUS) + SKEW_y) ) bottom_left = ( (br_x - PIN_RATIO - (p*PLUS_MINUS) + SKEW_x, br_y - PIN_RATIO - (p*PLUS_MINUS) + SKEW_y), (br_x + (p*PLUS_MINUS) + SKEW_x, br_y + (p*PLUS_MINUS) + SKEW_y) ) return [top_right, top_left, bottom_right, bottom_left] def calculate_corners_pinbased(self) -> list[list[int]]: """ Calculates the corners of the square based on the pins in the square. To be used after the pins have been added to the square. list[[corner_tl, corner_br], ...] in clockwise order starting from top left. """ corners = [] # pin is a list of contours for pin in self.pins: x, y, w, h = cv.boundingRect(pin) # add extra padding to the corners px, py = self.calculate_skew(0.2) px = int(px) py = int(py) # append top left and bottom right points of the test area corners.append([(x-px, y-py), (x+w+px, y+h+py)]) return self.order_corner_points(corners) ## Drawing functions ## def draw_p_pins(self, image: np.ndarray) -> None: """ Draws the potential pins in the square """ for pin in self.p_pins: cv.drawContours(image, pin, -1, (0, 255, 0), 3) def draw_pins(self, image: np.ndarray) -> None: """ Draws the pins in the square """ for pin in self.pins: cv.drawContours(image, pin, -1, (0, 255, 0), 3) def draw_corners(self, img: np.ndarray) -> None: """ Draws the corners of the square """ for corner in self.corners: cv.rectangle(img, corner[0], corner[1], (0, 0, 255), 1) def draw_corners_pinbased(self, img: np.ndarray) -> None: """ Draws the corners of the square based on the pins in the square To be used after the pins have been added to the square.""" # pin is a list of contours for x, y in self.calculate_corners_pinbased(): cv.rectangle(img, x, y, (0, 0, 255), 1) def draw_test_area(self, img: np.ndarray) -> None: "Draws the test area of the square" corners = self.calculate_corners_pinbased() cv.rectangle(img, corners[0][1], corners[2][0], (0, 0, 255), 1) ### Boolean functions ### def is_in_test_bounds(self, x: int, y: int) -> bool: "checks if coordinate is within test bounds (inner square where strip is)" pass def is_in_corners(self, x: int, y: int) -> bool: """ Checks if a point is in the corners of the square. """ # corn = ["top_left", "top_right", "bottom_left", "bottom_right"] i = 0 for corner in self.corners: if x >= corner[0][0] and x <= corner[1][0]: if y >= corner[0][1] and y <= corner[1][1]: # print(corn[i], ": ", round(self.get_rgb_avg_of_contour(contour))) return True i += 1 return False def is_in_corners_skewed(self, x: int, y: int, w: float, h: float) -> bool: """ Checks if a point is in the corners of the square, taking into consideration the skewing that happens.""" return (self.is_in_corners(x, y) or self.is_in_corners(x+int(w), y+int(h)) or self.is_in_corners(x-int(w), y-int(h)) or self.is_in_corners(x+int(w), y-int(h)) or self.is_in_corners(x-int(w), y+int(h))) def which_corner_is_contour_in(self, contour: np.ndarray = None, xy=None) -> str: """ Function that finds which corner of square a contour is in. """ corn = ["top_left", "top_right", "bottom_left", "bottom_right"] if xy is None: x, y = cv.boundingRect(contour)[:2] else: x, y = xy x = int(x) y = int(y) i = 0 for corner in self.corners: if x >= corner[0][0] and x <= corner[1][0]: if y >= corner[0][1] and y <= corner[1][1]: return corn[i] i += 1 # might be unecessary after corner skewing i = 0 for corner in self.corners: if x + (2*self.PLUS_MINUS) >= corner[0][0] and x - (2*self.PLUS_MINUS) <= corner[1][0]: if y + (2*self.PLUS_MINUS) >= corner[0][1] and y - (2*self.PLUS_MINUS) <= corner[1][1]: return corn[i] i += 1 def order_corner_points(self, corners: list[int]) -> list[int]: """ Orders the corners of the square in a clockwise manner starting from the top-left corner. """ # top right, top left, bottom right, bottom left ordered_corners = [None, None, None, None] for xy in corners: mid_x = (xy[0][0] + xy[1][0]) / 2 mid_y = (xy[0][1] + xy[1][1]) / 2 s = self.which_corner_is_contour_in(xy=(mid_x, mid_y)) if s == "top_left": ordered_corners[0] = xy elif s == "top_right": ordered_corners[1] = xy elif s == "bottom_right": ordered_corners[2] = xy elif s == "bottom_left": ordered_corners[3] = xy if None in ordered_corners: print("\nError in ordering corners\n") return None return ordered_corners # set functions def set_rgb_sequence(self) -> None: """ ### Set rgb sequence --------------- Function that sets the rgb sequence of the square. #### Returns: * None """ # get the RGB values of the pins in the square pins_rgb, corner_key = get_pins_rgb(self) # fixing the order from tr,tl,br,bl to clockwise starting from top-right. This might be the ugliest code I've ever written. But it works! set_rgb_sequence_clockwise(self, pins_rgb, corner_key) def set_test_area_img(self, img): " Sets the image of the test area" self.test_area_img = img
Methods
def add_corners(self, PIN_RATIO: int, PLUS_MINUS: int, p: int = 3, a: float = 1.8) ‑> None
-
Adds the corners of the square to the square object
Args:
- PIN_RATIO: ratio of the pin size to the square size
- PLUS_MINUS: arbitrary tolerance value
- p: "padding" value. Determines size of the corners.
- a: skew value. Is the exponential determining how skewed the corners are.
def add_p_pin(self, pin: numpy.ndarray) ‑> None
-
Adds a potential pin to the square
def add_pin(self, pin: numpy.ndarray) ‑> None
-
Adds a pin to the square
def calculate_corners(self, tl_x: int, tl_y: int, br_x: int, br_y: int, PIN_RATIO: int, PLUS_MINUS: int, p: int, SKEW_x: int, SKEW_y: int) ‑> list[int]
-
Calculates the corners of the square using magic. The "corners" here refer to the space in the ampli block where the pins are located.
def calculate_corners_pinbased(self) ‑> list[list[int]]
-
Calculates the corners of the square based on the pins in the square. To be used after the pins have been added to the square. list[[corner_tl, corner_br], …] in clockwise order starting from top left.
def calculate_skew(self, a: float) ‑> ast.Tuple
-
Calculates the skew originated from cellphone cameras
|x-4|^a * (x-4)/|x-4|
def createImg(self, img: numpy.ndarray) ‑> numpy.ndarray
-
Creates an image of the square, a cutout of the image around the square
def create_test_area_img(self, sq_img: numpy.ndarray) ‑> numpy.ndarray
-
Creates an image of the inner test spot
def draw_corners(self, img: numpy.ndarray) ‑> None
-
Draws the corners of the square
def draw_corners_pinbased(self, img: numpy.ndarray) ‑> None
-
Draws the corners of the square based on the pins in the square To be used after the pins have been added to the square.
def draw_p_pins(self, image: numpy.ndarray) ‑> None
-
Draws the potential pins in the square
def draw_pins(self, image: numpy.ndarray) ‑> None
-
Draws the pins in the square
def draw_test_area(self, img: numpy.ndarray) ‑> None
-
Draws the test area of the square
def get_block_type(self) ‑> str
-
Returns the block type of the square
def get_corners(self) ‑> list[int]
-
Returns the corners of the square
def get_img(self) ‑> numpy.ndarray
-
Returns the image of the square
def get_index(self) ‑> ast.Tuple
-
Returns the index of the square
def get_p_pins(self) ‑> list[int]
-
Returns the potential pins in the square
def get_pins(self) ‑> list[int]
-
Returns the pins in the square
def get_rgb_sequence(self) ‑> list[int]
-
Returns the RGB sequence of the square
def get_sq_img(self) ‑> numpy.ndarray
-
Returns the image of the square
def get_test_area_img(self) ‑> numpy.ndarray
-
Returns the image of squares test area (inner square where test strip can be)
def is_in_corners(self, x: int, y: int) ‑> bool
-
Checks if a point is in the corners of the square.
def is_in_corners_skewed(self, x: int, y: int, w: float, h: float) ‑> bool
-
Checks if a point is in the corners of the square, taking into consideration the skewing that happens.
def is_in_test_bounds(self, x: int, y: int) ‑> bool
-
checks if coordinate is within test bounds (inner square where strip is)
def order_corner_points(self, corners: list[int]) ‑> list[int]
-
Orders the corners of the square in a clockwise manner starting from the top-left corner.
def set_rgb_sequence(self) ‑> None
-
Set rgb sequence
Function that sets the rgb sequence of the square.
Returns:
- None
def set_test_area_img(self, img)
-
Sets the image of the test area
def which_corner_is_contour_in(self, contour: numpy.ndarray = None, xy=None) ‑> str
-
Function that finds which corner of square a contour is in.