code: def rotate_image(
      image: np.ndarray,
      angle: float,
      expand: bool = False,
      preserve_origin_shape: bool = False,
  ) -> np.ndarray:
      """Rotate an image counterclockwise by an given angle.
  
      Args:
      ----
          image: numpy tensor to rotate
          angle: rotation angle in degrees, between -90 and +90
          expand: whether the image should be padded before the rotation
          preserve_origin_shape: if expand is set to True, resizes the final output to the original image size
  
      Returns:
      -------
          Rotated array, padded by 0 by default.
      """
      # Compute the expanded padding
      exp_img: np.ndarray
      if expand:
          exp_shape = compute_expanded_shape(image.shape[:2], angle)  # type: ignore[arg-type]
          h_pad, w_pad = (
              int(max(0, ceil(exp_shape[0] - image.shape[0]))),
              int(max(0, ceil(exp_shape[1] - image.shape[1]))),
          )
          exp_img = np.pad(
              image,
              (
                  (h_pad // 2, h_pad - h_pad // 2),
                  (w_pad // 2, w_pad - w_pad // 2),
                  (0, 0),
              ),
          )
      else:
          exp_img = image
  
      height, width = exp_img.shape[:2]
      rot_mat = cv2.getRotationMatrix2D(
          (width / 2, height / 2), angle, 1.0
      )
      rot_img = cv2.warpAffine(exp_img, rot_mat, (width, height))
      if expand:
          # Pad to get the same aspect ratio
          if (image.shape[0] / image.shape[1]) != (
              rot_img.shape[0] / rot_img.shape[1]
          ):
              # Pad width
              if (rot_img.shape[0] / rot_img.shape[1]) > (
                  image.shape[0] / image.shape[1]
              ):
                  h_pad, w_pad = 0, int(
                      rot_img.shape[0] * image.shape[1] / image.shape[0]
                      - rot_img.shape[1]
                  )
              # Pad height
              else:
                  h_pad, w_pad = (
                      int(
                          rot_img.shape[1]
                          * image.shape[0]
                          / image.shape[1]
                          - rot_img.shape[0]
                      ),
                      0,
                  )
              rot_img = np.pad(
                  rot_img,
                  (
                      (h_pad // 2, h_pad - h_pad // 2),
                      (w_pad // 2, w_pad - w_pad // 2),
                      (0, 0),
                  ),
              )
          if preserve_origin_shape:
              # rescale
              rot_img = cv2.resize(
                  rot_img,
                  image.shape[:-1][::-1],
                  interpolation=cv2.INTER_LINEAR,
              )
  
      return rot_img
  
file_path: doctr/doctr/utils/geometry.py
imports: from copy import deepcopy
  from math import ceil
  from typing import List, Optional, Tuple, Union
  import cv2
  import numpy
  from .common_types import BoundingBox, Polygon4P
label_desc: Use image node_id 10 for calling a global function with example usage: rotate_image(image, angle, expand, preserve_origin_shape) and returns: rot_img