Module taulu.table_indexer
Defines an abstract class TableIndexer, which provides methods for mapping pixel coordinates in an image to table cell indices and for cropping images to specific table cells or regions.
Classes
class TableIndexer-
Expand source code
class TableIndexer(ABC): """ Subclasses implement methods for going from a pixel in the input image to a table cell index, and cropping an image to the given table cell index. """ def __init__(self): self._col_offset = 0 @property def col_offset(self) -> int: return self._col_offset @col_offset.setter def col_offset(self, value: int): assert value >= 0 self._col_offset = value @property @abstractmethod def cols(self) -> int: pass @property @abstractmethod def rows(self) -> int: pass def cells(self) -> Generator[tuple[int, int], None, None]: for row in range(self.rows): for col in range(self.cols): yield (row, col) def _check_row_idx(self, row: int): if row < 0: raise TauluException("row number needs to be positive or zero") if row >= self.rows: raise TauluException(f"row number too high: {row} >= {self.rows}") def _check_col_idx(self, col: int): if col < 0: raise TauluException("col number needs to be positive or zero") if col >= self.cols: raise TauluException(f"col number too high: {col} >= {self.cols}") @abstractmethod def cell(self, point: tuple[float, float]) -> tuple[int, int]: """ Returns the coordinate (row, col) of the cell that contains the given position Args: point (tuple[float, float]): a location in the input image Returns: tuple[int, int]: the cell index (row, col) that contains the given point """ pass @abstractmethod def cell_polygon( self, cell: tuple[int, int] ) -> tuple[tuple[int, int], tuple[int, int], tuple[int, int], tuple[int, int]]: """returns the polygon (used in e.g. opencv) that enscribes the cell at the given cell position""" pass def _highlight_cell( self, image: MatLike, cell: tuple[int, int], color: tuple[int, int, int] = (0, 0, 255), thickness: int = 2, ): polygon = self.cell_polygon(cell) points = np.int32(list(polygon)) # type:ignore cv.polylines(image, [points], True, color, thickness, cv.LINE_AA) # type:ignore cv.putText( image, str(cell), (int(polygon[3][0] + 10), int(polygon[3][1] - 10)), cv.FONT_HERSHEY_PLAIN, 2.0, (255, 255, 255), 2, ) def highlight_all_cells( self, image: MatLike, color: tuple[int, int, int] = (0, 0, 255), thickness: int = 1, ) -> MatLike: img = np.copy(image) for cell in self.cells(): self._highlight_cell(img, cell, color, thickness) return img def select_one_cell( self, image: MatLike, window: str = WINDOW, color: tuple[int, int, int] = (255, 0, 0), thickness: int = 2, ) -> tuple[int, int] | None: clicked = None def click_event(event, x, y, flags, params): nonlocal clicked img = np.copy(image) _ = flags _ = params if event == cv.EVENT_LBUTTONDOWN: cell = self.cell((x, y)) if cell[0] >= 0: clicked = cell else: return self._highlight_cell(img, cell, color, thickness) cv.imshow(window, img) imu.show(image, click_event=click_event, title="select one cell", window=window) return clicked def show_cells( self, image: MatLike | os.PathLike[str] | str, window: str = WINDOW ) -> list[tuple[int, int]]: if not isinstance(image, np.ndarray): image = cv.imread(os.fspath(image)) img = np.copy(image) cells = [] def click_event(event, x, y, flags, params): _ = flags _ = params if event == cv.EVENT_LBUTTONDOWN: cell = self.cell((x, y)) if cell[0] >= 0: cells.append(cell) else: return self._highlight_cell(img, cell) cv.imshow(window, img) imu.show( img, click_event=click_event, title="click to highlight cells", window=window, ) return cells @abstractmethod def region( self, start: tuple[int, int], end: tuple[int, int], ) -> tuple[Point, Point, Point, Point]: """ Get the bounding box for the rectangular region that goes from start to end Returns: 4 points: lt, rt, rb, lb, in format (x, y) """ pass def crop_region( self, image: MatLike, start: tuple[int, int], end: tuple[int, int], margin: int = 0, margin_top: int | None = None, margin_bottom: int | None = None, margin_left: int | None = None, margin_right: int | None = None, margin_y: int | None = None, margin_x: int | None = None, ) -> MatLike: """Crop the input image to a rectangular region with the start and end cells as extremes""" region = self.region(start, end) lt, rt, rb, lb = _apply_margin( *region, margin=margin, margin_top=margin_top, margin_bottom=margin_bottom, margin_left=margin_left, margin_right=margin_right, margin_y=margin_y, margin_x=margin_x, ) # apply margins according to priority: # margin_top > margin_y > margin (etc.) w = (rt[0] - lt[0] + rb[0] - lb[0]) / 2 h = (rb[1] - rt[1] + lb[1] - lt[1]) / 2 # crop by doing a perspective transform to the desired quad src_pts = np.array([lt, rt, rb, lb], dtype="float32") dst_pts = np.array([[0, 0], [w, 0], [w, h], [0, h]], dtype="float32") M = cv.getPerspectiveTransform(src_pts, dst_pts) warped = cv.warpPerspective(image, M, (int(w), int(h))) # type:ignore return warped @abstractmethod def text_regions( self, img: MatLike, row: int, margin_x: int = 0, margin_y: int = 0 ) -> list[tuple[tuple[int, int], tuple[int, int]]]: """ Split the row into regions of continuous text Returns list[tuple[int, int]]: a list of spans (start col, end col) """ pass def crop_cell(self, image, cell: tuple[int, int], margin: int = 0) -> MatLike: return self.crop_region(image, cell, cell, margin)Subclasses implement methods for going from a pixel in the input image to a table cell index, and cropping an image to the given table cell index.
Ancestors
- abc.ABC
Subclasses
Instance variables
prop col_offset : int-
Expand source code
@property def col_offset(self) -> int: return self._col_offset prop cols : int-
Expand source code
@property @abstractmethod def cols(self) -> int: pass prop rows : int-
Expand source code
@property @abstractmethod def rows(self) -> int: pass
Methods
def cell(self, point: tuple[float, float]) ‑> tuple[int, int]-
Expand source code
@abstractmethod def cell(self, point: tuple[float, float]) -> tuple[int, int]: """ Returns the coordinate (row, col) of the cell that contains the given position Args: point (tuple[float, float]): a location in the input image Returns: tuple[int, int]: the cell index (row, col) that contains the given point """ passReturns the coordinate (row, col) of the cell that contains the given position
Args
point:tuple[float, float]- a location in the input image
Returns
tuple[int, int]- the cell index (row, col) that contains the given point
def cell_polygon(self, cell: tuple[int, int]) ‑> tuple[tuple[int, int], tuple[int, int], tuple[int, int], tuple[int, int]]-
Expand source code
@abstractmethod def cell_polygon( self, cell: tuple[int, int] ) -> tuple[tuple[int, int], tuple[int, int], tuple[int, int], tuple[int, int]]: """returns the polygon (used in e.g. opencv) that enscribes the cell at the given cell position""" passreturns the polygon (used in e.g. opencv) that enscribes the cell at the given cell position
def cells(self) ‑> Generator[tuple[int, int], None, None]-
Expand source code
def cells(self) -> Generator[tuple[int, int], None, None]: for row in range(self.rows): for col in range(self.cols): yield (row, col) def crop_cell(self, image, cell: tuple[int, int], margin: int = 0) ‑> cv2.Mat | numpy.ndarray-
Expand source code
def crop_cell(self, image, cell: tuple[int, int], margin: int = 0) -> MatLike: return self.crop_region(image, cell, cell, margin) def crop_region(self,
image: cv2.Mat | numpy.ndarray,
start: tuple[int, int],
end: tuple[int, int],
margin: int = 0,
margin_top: int | None = None,
margin_bottom: int | None = None,
margin_left: int | None = None,
margin_right: int | None = None,
margin_y: int | None = None,
margin_x: int | None = None) ‑> cv2.Mat | numpy.ndarray-
Expand source code
def crop_region( self, image: MatLike, start: tuple[int, int], end: tuple[int, int], margin: int = 0, margin_top: int | None = None, margin_bottom: int | None = None, margin_left: int | None = None, margin_right: int | None = None, margin_y: int | None = None, margin_x: int | None = None, ) -> MatLike: """Crop the input image to a rectangular region with the start and end cells as extremes""" region = self.region(start, end) lt, rt, rb, lb = _apply_margin( *region, margin=margin, margin_top=margin_top, margin_bottom=margin_bottom, margin_left=margin_left, margin_right=margin_right, margin_y=margin_y, margin_x=margin_x, ) # apply margins according to priority: # margin_top > margin_y > margin (etc.) w = (rt[0] - lt[0] + rb[0] - lb[0]) / 2 h = (rb[1] - rt[1] + lb[1] - lt[1]) / 2 # crop by doing a perspective transform to the desired quad src_pts = np.array([lt, rt, rb, lb], dtype="float32") dst_pts = np.array([[0, 0], [w, 0], [w, h], [0, h]], dtype="float32") M = cv.getPerspectiveTransform(src_pts, dst_pts) warped = cv.warpPerspective(image, M, (int(w), int(h))) # type:ignore return warpedCrop the input image to a rectangular region with the start and end cells as extremes
def highlight_all_cells(self,
image: cv2.Mat | numpy.ndarray,
color: tuple[int, int, int] = (0, 0, 255),
thickness: int = 1) ‑> cv2.Mat | numpy.ndarray-
Expand source code
def highlight_all_cells( self, image: MatLike, color: tuple[int, int, int] = (0, 0, 255), thickness: int = 1, ) -> MatLike: img = np.copy(image) for cell in self.cells(): self._highlight_cell(img, cell, color, thickness) return img def region(self, start: tuple[int, int], end: tuple[int, int]) ‑> tuple[typing.Tuple[int, int], typing.Tuple[int, int], typing.Tuple[int, int], typing.Tuple[int, int]]-
Expand source code
@abstractmethod def region( self, start: tuple[int, int], end: tuple[int, int], ) -> tuple[Point, Point, Point, Point]: """ Get the bounding box for the rectangular region that goes from start to end Returns: 4 points: lt, rt, rb, lb, in format (x, y) """ passGet the bounding box for the rectangular region that goes from start to end
Returns
4 points- lt, rt, rb, lb, in format (x, y)
def select_one_cell(self,
image: cv2.Mat | numpy.ndarray,
window: str = 'taulu',
color: tuple[int, int, int] = (255, 0, 0),
thickness: int = 2) ‑> tuple[int, int] | None-
Expand source code
def select_one_cell( self, image: MatLike, window: str = WINDOW, color: tuple[int, int, int] = (255, 0, 0), thickness: int = 2, ) -> tuple[int, int] | None: clicked = None def click_event(event, x, y, flags, params): nonlocal clicked img = np.copy(image) _ = flags _ = params if event == cv.EVENT_LBUTTONDOWN: cell = self.cell((x, y)) if cell[0] >= 0: clicked = cell else: return self._highlight_cell(img, cell, color, thickness) cv.imshow(window, img) imu.show(image, click_event=click_event, title="select one cell", window=window) return clicked def show_cells(self,
image: cv2.Mat | numpy.ndarray | os.PathLike[str] | str,
window: str = 'taulu') ‑> list[tuple[int, int]]-
Expand source code
def show_cells( self, image: MatLike | os.PathLike[str] | str, window: str = WINDOW ) -> list[tuple[int, int]]: if not isinstance(image, np.ndarray): image = cv.imread(os.fspath(image)) img = np.copy(image) cells = [] def click_event(event, x, y, flags, params): _ = flags _ = params if event == cv.EVENT_LBUTTONDOWN: cell = self.cell((x, y)) if cell[0] >= 0: cells.append(cell) else: return self._highlight_cell(img, cell) cv.imshow(window, img) imu.show( img, click_event=click_event, title="click to highlight cells", window=window, ) return cells def text_regions(self, img: cv2.Mat | numpy.ndarray, row: int, margin_x: int = 0, margin_y: int = 0) ‑> list[tuple[tuple[int, int], tuple[int, int]]]-
Expand source code
@abstractmethod def text_regions( self, img: MatLike, row: int, margin_x: int = 0, margin_y: int = 0 ) -> list[tuple[tuple[int, int], tuple[int, int]]]: """ Split the row into regions of continuous text Returns list[tuple[int, int]]: a list of spans (start col, end col) """ passSplit the row into regions of continuous text
Returns list[tuple[int, int]]: a list of spans (start col, end col)