code: def perspective_transform(
      image: Union[str, Image.Image],
      output_path: Optional[str] = None,
      sigma: float = 50.0,
      dx: float = 0.0,
      dy: float = 0.0,
      seed: Optional[int] = 42,
      crop_out_black_border: bool = False,
      metadata: Optional[List[Dict[str, Any]]] = None,
      bboxes: Optional[List[Tuple]] = None,
      bbox_format: Optional[str] = None,
  ) -> Image.Image:
      """
      Apply a perspective transform to the image so it looks like it was taken
      as a photo from another device (e.g. taking a picture from your phone of a
      picture on a computer).
  
      @param image: the path to an image or a variable of type PIL.Image.Image
          to be augmented
  
      @param output_path: the path in which the resulting image will be stored.
          If None, the resulting PIL Image will still be returned
  
      @param sigma: the standard deviation of the distribution of destination
          coordinates. the larger the sigma value, the more intense the transform
  
      @param dx: change in x for the perspective transform; instead of providing
          `sigma` you can provide a scalar value to be precise
  
      @param dy: change in y for the perspective transform; instead of providing
          `sigma` you can provide a scalar value to be precise
  
      @param seed: if provided, this will set the random seed to ensure consistency
          between runs
  
      @param crop_out_black_border: if True, will crop out the black border resulting
          from the perspective transform by cropping to the largest center rectangle
          with no black
  
      @param metadata: if set to be a list, metadata about the function execution
          including its name, the source & dest width, height, etc. will be appended
          to the inputted list. If set to None, no metadata will be appended or returned
  
      @param bboxes: a list of bounding boxes can be passed in here if desired. If
          provided, this list will be modified in place such that each bounding box is
          transformed according to this function
  
      @param bbox_format: signifies what bounding box format was used in `bboxes`. Must
          specify `bbox_format` if `bboxes` is provided. Supported bbox_format values are
          "pascal_voc", "pascal_voc_norm", "coco", and "yolo"
  
      @returns: the augmented PIL Image
      """
      assert sigma >= 0, "Expected 'sigma' to be nonnegative"
      assert isinstance(
          dx, (int, float)
      ), "Expected 'dx' to be a number"
      assert isinstance(
          dy, (int, float)
      ), "Expected 'dy' to be a number"
      assert seed is None or isinstance(
          seed, int
      ), "Expected 'seed' to be an integer or set to None"
  
      image = imutils.validate_and_load_image(image)
  
      func_kwargs = imutils.get_func_kwargs(metadata, locals())
      src_mode = image.mode
  
      rng = (
          np.random.RandomState(seed) if seed is not None else np.random
      )
      width, height = image.size
  
      src_coords = [(0, 0), (width, 0), (width, height), (0, height)]
      dst_coords = [
          (
              rng.normal(point[0], sigma) + dx,
              rng.normal(point[1], sigma) + dy,
          )
          for point in src_coords
      ]
  
      perspective_transform_coeffs = imutils.compute_transform_coeffs(
          src_coords, dst_coords
      )
      aug_image = image.transform(
          (width, height),
          Image.PERSPECTIVE,
          perspective_transform_coeffs,
          Image.BICUBIC,
      )
  
      if crop_out_black_border:
          top_left, top_right, bottom_right, bottom_left = dst_coords
          new_left = max(0, top_left[0], bottom_left[0])
          new_right = min(width, top_right[0], bottom_right[0])
          new_top = max(0, top_left[1], top_right[1])
          new_bottom = min(height, bottom_left[1], bottom_right[1])
  
          if new_left >= new_right or new_top >= new_bottom:
              raise Exception(
                  "Cannot crop out black border of a perspective transform this intense"
              )
  
          aug_image = aug_image.crop(
              (new_left, new_top, new_right, new_bottom)
          )
  
      imutils.get_metadata(
          metadata=metadata,
          function_name="perspective_transform",
          aug_image=aug_image,
          **func_kwargs,
      )
  
      return imutils.ret_and_save_image(
          aug_image, output_path, src_mode
      )
  
imports: import io
  import math
  import os
  import pickle
  from copy import deepcopy
  from typing import Any, Callable, Dict, List, Optional, Tuple, Union
  import numpy
  from augly import utils
  from augly.image import utils
  from augly.image.utils.bboxes import spatial_bbox_helper
  from PIL import Image, ImageDraw, ImageEnhance, ImageFilter, ImageFont
label_desc: Use image node_id 25 for calling a global function with example usage: perspective_transform(image, output_path, sigma, dx, dy, seed, crop_out_black_border, metadata, bboxes, bbox_format) and returns: imutils