def apply_transformation(
    x: np.ndarray,
    mask: np.ndarray,
    pert: np.ndarray,
    angle: float,
    dist: float,
    gamma: float,
    blur: int,
    crop_percent: float,
    crop_off_x: float,
    crop_off_y: float,
    net_size: tuple[int, int],
    obj_width: float,
    focal: float,
    clip_min: float,
    clip_max: float,
    pts: np.ndarray | None = None,
) -> np.ndarray:
    """
    Apply transformation to input image.

    :param x: Input image.
    :param mask: Input mask.
    :param pert: Input perturbation.
    :param angle: Angle to rotate image.
    :param dist: Distance in ft for perspective transform.
    :param gamma: Factor for gamma transform.
    :param blur: Kernel width for blurring.
    :param crop_percent: Percent to crop image at.
    :param crop_off_x: x offset for crop.
    :param crop_off_y: y offset for crop.
    :param net_size: Size of the image for the network.
    :param obj_width: Estimated width of object in inches for perspective transform.
    :param focal: Estimated focal length in ft for perspective transform.
    :param clip_min: Minimum value of an example.
    :param clip_max: Maximum value of an example.
    :param pts: A set of points that will set the crop size in the perspective transform.
    :return: Transformed image in network form.
    """
    import cv2

    if blur != 0:
        pert = cv2.GaussianBlur(pert, (blur, blur), 0)

    if len(pert.shape) < 3:
        pert = pert[:, :, np.newaxis]

    att = np.where(mask > 0.5, pert, x)
    att = np.clip(att, 0.0, 1.0)

    dist = dist2pixels(dist, att.shape[1], obj_width)
    focal = dist2pixels(focal, att.shape[1], obj_width)
    att = get_perspective_transform(
        att, angle, att.shape[1], att.shape[0], focal, dist, crop_percent, crop_off_x, crop_off_y, pts
    )

    if len(att.shape) < 3:
        att = att[:, :, np.newaxis]

    # Gamma
    att_uint = (att * 255.0).astype(np.uint8)
    table = np.empty((256), np.uint8)
    for i in range(256):
        table[i] = np.clip(pow(i / 255.0, gamma) * 255.0, 0, 255)
    att_uint = cv2.LUT(att_uint, table)  # type: ignore
    att = (att_uint / 255.0).astype(np.float32)
    att = np.clip(att, 0.0, 1.0)

    return convert_to_network(att, net_size, clip_min, clip_max)

    Apply transformation to input image.

    :param x: Input image.
    :param mask: Input mask.
    :param pert: Input perturbation.
    :param angle: Angle to rotate image.
    :param dist: Distance in ft for perspective transform.
    :param gamma: Factor for gamma transform.
    :param blur: Kernel width for blurring.
    :param crop_percent: Percent to crop image at.
    :param crop_off_x: x offset for crop.
    :param crop_off_y: y offset for crop.
    :param net_size: Size of the image for the network.
    :param obj_width: Estimated width of object in inches for perspective transform.
    :param focal: Estimated focal length in ft for perspective transform.
    :param clip_min: Minimum value of an example.
    :param clip_max: Maximum value of an example.
    :param pts: A set of points that will set the crop size in the perspective transform.
    :return: Transformed image in network form.
    
art/attacks/evasion/graphite/utils.py