from _operator import itemgetter from functools import partial import numpy as np from cv_analysis.utils.structures import Rectangle def make_formatter(dpi, page_size, rotation): rotation = rotation // 90 if rotation not in [0, 1, 2, 3] else rotation def format_(key2pixel): convert = partial(convert_pixel_to_inch, dpi=dpi) x, y, w, h = map(convert, itemgetter("x", "y", "width", "height")(key2pixel)) x1, y1 = x + w, y + h matrix = np.vstack([[x, y], [x1, y1]]).T new_matrix = rotate_and_shift(matrix, rotation, page_size) x1, x2 = sorted(new_matrix[0, :]) y1, y2 = sorted(new_matrix[1, :]) return Rectangle.from_xyxy((x1, y1, x2, y2), discrete=False).json_xywh() return format_ def convert_pixel_to_inch(pixel, dpi): return pixel / dpi * 72 def rotate(input_matrix, radians): rotation_matrix = np.vstack([[np.cos(radians), -np.sin(radians)], [np.sin(radians), np.cos(radians)]]) return np.dot(rotation_matrix, input_matrix) def rotate_and_shift(matrix, rotation, size, debug=False): """Rotates a matrix against (!) a specified rotation. That is, the rotation is applied negatively. The matrix is also shifted to ensure it contains points (columns) in quadrant I. Procedure: 1) Rotate the matrix clockwise according to rotation value 2) Shift the matrix back into quadrant I 3) Set x_i and y_i to new lower left and upper right corners, since the corner vectors are no longer at these corners due to the rotation Args: matrix: matrix to transform rotation: any of 0, 1, 2, or 3, where 1 = 90 degree CLOCKWISE rotation etc. size: the size of the page as a tuple (, ) debug: Visualizes the transformations for later re-understanding of the code """ def shift_to_quadrant_1(matrix): # TODO: generalize if rotation == 0: back_shift = np.zeros_like(np.eye(2)) elif rotation == 1: back_shift = np.array([[0, 0], [1, 1]]) * size[1] elif rotation == 2: back_shift = np.array([[1, 1], [1, 1]]) * size elif rotation == 3: back_shift = np.array([[1, 1], [0, 0]]) * size[0] else: raise ValueError(f"Unexpected rotation value '{rotation}'. Expected any of 0, 1, 2, or 3.") matrix_shifted = matrix + back_shift return matrix_shifted # PDF rotations are clockwise, hence subtract the radian value of the rotation from 2 pi radians = (2 * np.pi) - (np.pi * (rotation / 2)) matrix_rotated = rotate(matrix, radians) matrix_rotated_and_shifted = shift_to_quadrant_1(matrix_rotated) if debug: __show_matrices(size, radians, matrix, matrix_rotated, matrix_rotated_and_shifted) return matrix_rotated_and_shifted def __show_matrices(size, radians, matrix, matrix_rotated, matrix_rotated_and_shifted): import matplotlib.pyplot as plt from copy import deepcopy m1 = matrix m2 = matrix_rotated m3 = matrix_rotated_and_shifted m1, m2, m3 = map(deepcopy, (m1, m2, m3)) frame = np.eye(2) * size frame_rotated = rotate(frame, radians) f1 = frame f2 = frame_rotated f1 *= 0.005 * 1 f2 *= 0.005 * 1 m1 *= 0.005 * 1 m2 *= 0.005 * 1 m3 *= 0.005 * 1 fig, axes = plt.subplots(1, 2, figsize=(8, 4)) axes = axes.ravel() axes[0].quiver([0, 0], [0, 0], f1[0, :], f1[1, :], scale=5, scale_units="inches", color="red") axes[1].quiver([0, 0], [0, 0], f2[0, :], f2[1, :], scale=5, scale_units="inches", color="red") axes[0].quiver([0, 0], [0, 0], m1[0, :], m1[1, :], scale=5, scale_units="inches") axes[1].quiver([0, 0], [0, 0], m2[0, :], m2[1, :], scale=5, scale_units="inches", color="green") axes[1].quiver([0, 0], [0, 0], m3[0, :], m3[1, :], scale=5, scale_units="inches", color="blue") plt.show()