U
    hʉ                 $   @  s*	  d dl mZ d dlZd dlmZmZmZmZmZm	Z	m
Z
 d dlZd dlZd dlZd dlmZmZmZmZmZmZ d dlmZ d dlmZmZ d dlmZmZmZm Z  d dl!m"Z"m#Z#m$Z$m%Z%m&Z&m'Z'm(Z( d	d	d
dddddddddddddddddddddd d!d"d#d$d%d&d'd(d)d*d+g$Z)d,Z*d,Z+d-Z,ed.d/d.d0d1d!Z-ed.d2d.d3d4dZ.eed.d/d5d.d6d7d"Z/ed.d2d5d8d.d9d:d Z0ed"d.d;d/d/d<d.d=d>dZ1ed.d;d?d5d.d@dAdZ2eed.d;d5d.dBdCdZ3ed.d5d/d.dDdEdZ4ed.d;d/d.dFdGdZ5ed.d;d;d.dHdIdJZ6d.d/d/dKd.dLdMdZ7ed.d/d/d.dNdOdZ8ed.d/d/d.dNdPdZ9ed.d.d/d/dQd/dRd/d.dS	dTdZ:ed.d5d.d/d/dRd.dUdVdWZ;d.dRd;dXdYdZ<eed.d5d.d/d/dRd.dZd[d\Z=d]dRd^d_dZ>d.d.d`d/d/dad.dbdcddZ?ed.d]d/dad/d`d.dedfdZ@eed.d]d5dgd/d.dhdidjZAd]d`dkdldmdnZBed.d]d.dodpdqZCed.d]d.dodrdsZDed.d]d?d5d/d5d.dtdudvZEed.dwd/dxd;d.dydzdZFd#d.d5dRd.d|d}dZGd~ddddZHd5d.ddRddddZId$d.dRd~dd.dddZJed.dwd5d;d.dddZKed.dd5d;d.dddZLd.d2d.dddZMed.d/d.dddZNd.d.dddZOd.d/d.dddZPed.d.ddd%ZQed.d.ddd'ZRed.d/d.dddZSed.d.ddd#ZTeed.d/d.ddd&ZUeed.d/d.ddd(ZVeed.d/d5d.dddZWeed.d.ddd$ZXed.d/d/d/d<d.dddZYdad/ddddZZd.d/d/d/d/d/dad.dddZ[ed.d/d/d/d/d/d<d.dddZ\ed%d.d.d.d/d/d<d.ddd	Z]ed.d.d.d5d.ddd
Z^ed.d.d.d5d/d.dddZ_d5d;d;dRd5dddddZ`ed.d/d/d/d/d/d5d.dddÄZad.d`d.dĜddƄZbd.d.d.dǜddɄZcd/d/d/d/d5dd˜dd̈́Zdd&d.dd5dRd.dΜddЄZeed'd.dRdRd5d.dҜddԄZfed.d.d/d.d՜ddׄZged.d.d5d.d؜ddڄZhed.d.d5d.dۜdd݄Zid.d/d.dޜddZjed.d/d/d/d/d/d5d.dddZkd.d5d.dddZld.d.d.dddZmd(d.dd5dRd.dddZned)d.dRdRd5d.dddZoG dd de	ZpG dd de	ZqG dd de	Zrdddd;dd]dddZsd]d5ddlddZtd]ddddd Zud5dddd)Zvd5dddd*Zwd5ddd/dddd+Zxd5d/ddddd	d
Zyd/d/d.dddZzd*d/d/dd.dddZ{d+d5d5dd.dddZ|d,d5d;dd.dddZ}d.d.dddZ~d.ddddZd.d5ddd d!ZdS (-      )annotationsN)AnyCallableLiteralSequenceTuple	TypedDictcast)clippedget_num_channelshflipmaybe_process_in_chunkspreserve_channel_dimvflip)random_utils)angle_2pi_rangehandle_empty_array)bboxes_from_masksdenormalize_bboxesmasks_from_bboxesnormalize_bboxes)MONO_CHANNEL_DIMENSIONS'NUM_KEYPOINTS_COLUMNS_IN_ALBUMENTATIONSNUM_MULTI_CHANNEL_DIMENSIONSREFLECT_BORDER_MODES	ColorTypeD4Type
ScalarType
distortiondistortion_keypointsdistortion_bboxespadpad_with_paramsrotateresizescale_func_max_sizelongest_max_sizesmallest_max_sizeperspective!rotation2d_matrix_to_euler_anglesis_identity_matrixwarp_affinepiecewise_affineto_distance_mapsfrom_distance_maps	transposed4bboxes_rotatekeypoints_rotate	bboxes_d4keypoints_d4bboxes_rot90keypoints_rot90bboxes_transposekeypoints_transposebboxes_vflipkeypoints_vflipbboxes_hflipkeypoints_hflipcentercenter_bboxgenerate_grid      
np.ndarrayint)bboxesfactorreturnc                 C  sl  |dkrt d|dkr| S |  }| dddf | dddf | dddf | dddf f\}}}}|dkr||dddf< d| |dddf< ||dddf< d| |dddf< n|tkrd| |dddf< d| |dddf< d| |dddf< d| |dddf< nR|tkrhd| |dddf< ||dddf< d| |dddf< ||dddf< |S )a  Rotates bounding boxes by 90 degrees CCW (see np.rot90)

    Args:
        bboxes: A numpy array of bounding boxes with shape (num_bboxes, 4+).
                Each row represents a bounding box (x_min, y_min, x_max, y_max, ...).
        factor: Number of CCW rotations. Must be in set {0, 1, 2, 3} See np.rot90.

    Returns:
        np.ndarray: A numpy array of rotated bounding boxes with the same shape as input.

    Raises:
        ValueError: If factor is not in set {0, 1, 2, 3}.
       r      rA   rB   ,Parameter factor must be in set {0, 1, 2, 3}r   NrI   rA   rB   )
ValueErrorcopyROT90_180_FACTORROT90_270_FACTOR)rE   rF   Zrotated_bboxesx_miny_minx_maxy_max rS   U/tmp/pip-unpacked-wheel-e8onvpoz/albumentations/augmentations/geometric/functional.pyr6   E   s,    D

r   )rE   group_memberrG   c              	   C  s\   dd dd dd dd dd dd dd d	d d
}||krJ|| | S t d| dS )a  Applies a `D_4` symmetry group transformation to a bounding box.

    The function transforms a bounding box according to the specified group member from the `D_4` group.
    These transformations include rotations and reflections, specified to work on an image's bounding box given
    its dimensions.

    Parameters:
    -  bboxes: A numpy array of bounding boxes with shape (num_bboxes, 4+).
                Each row represents a bounding box (x_min, y_min, x_max, y_max, ...).
    - group_member (D4Type): A string identifier for the `D_4` group transformation to apply.
        Valid values are 'e', 'r90', 'r180', 'r270', 'v', 'hvt', 'h', 't'.

    Returns:
    - BoxInternalType: The transformed bounding box.

    Raises:
    - ValueError: If an invalid group member is specified.

    Examples:
    - Applying a 90-degree rotation:
      `bbox_d4((10, 20, 110, 120), 'r90')`
      This would rotate the bounding box 90 degrees within a 100x100 image.
    c                 S  s   | S NrS   xrS   rS   rT   <lambda>       zbboxes_d4.<locals>.<lambda>c                 S  s
   t | dS NrI   r6   rW   rS   rS   rT   rY      rZ   c                 S  s
   t | dS NrA   r\   rW   rS   rS   rT   rY      rZ   c                 S  s
   t | dS NrB   r\   rW   rS   rS   rT   rY      rZ   c                 S  s   t | S rV   )r:   rW   rS   rS   rT   rY      rZ   c                 S  s   t t| dS r]   )r8   r6   rW   rS   rS   rT   rY      rZ   c                 S  s   t | S rV   )r<   rW   rS   rS   rT   rY      rZ   c                 S  s   t | S rV   )r8   rW   rS   rS   rT   rY      rZ   eZr90Zr180Zr270vZhvthtInvalid group member: NrK   )rE   rU   transformationsrS   rS   rT   r4   p   s    ztuple[int, int])	keypointsrF   image_shaperG   c           	      C  s^  |dkrt d|dkr| S |dd \}}|  tj}| dddf | dddf | dddf   }}}|dkr||dddf< |d | |dddf< |tjd  |dddf< n|tkr|d | |dddf< |d | |dddf< |tj |dddf< nL|tkrZ|d | |dddf< ||dddf< |tjd  |dddf< |S )a0  Rotate keypoints by 90 degrees counter-clockwise (CCW) a specified number of times.

    Args:
        keypoints (np.ndarray): An array of keypoints with shape (N, 4+) in the format (x, y, angle, scale, ...).
        factor (int): The number of 90 degree CCW rotations to apply. Must be in the range [0, 3].
        image_shape (tuple[int, int]): The shape of the image (height, width).

    Returns:
        np.ndarray: The rotated keypoints with the same shape as the input.

    Raises:
        ValueError: If the factor is not in the set {0, 1, 2, 3}.
    rH   rJ   r   NrA   rI   )rK   rL   astypenpfloat32pirM   rN   )	rg   rF   rh   heightwidthrotated_keypointsrX   yanglerS   rS   rT   r7      s(    4

r   )rg   rU   rh   paramsrG   c              	     s   dd \ dd fddfddfddfddfd	d fd
ddd d}||krr|| | S t d| dS )a  Applies a `D_4` symmetry group transformation to a keypoint.

    This function adjusts a keypoint's coordinates according to the specified `D_4` group transformation,
    which includes rotations and reflections suitable for image processing tasks. These transformations account
    for the dimensions of the image to ensure the keypoint remains within its boundaries.

    Parameters:
    - keypoints (np.ndarray): An array of keypoints with shape (N, 4+) in the format (x, y, angle, scale, ...).
    -group_member (D4Type): A string identifier for the `D_4` group transformation to apply.
        Valid values are 'e', 'r90', 'r180', 'r270', 'v', 'hv', 'h', 't'.
    - image_shape (tuple[int, int]): The shape of the image.
    - params (Any): Not used

    Returns:
    - KeypointInternalType: The transformed keypoint.

    Raises:
    - ValueError: If an invalid group member is specified, indicating that the specified transformation does not exist.

    Examples:
    - Rotating a keypoint by 90 degrees in a 100x100 image:
      `keypoint_d4((50, 30), 'r90', 100, 100)`
      This would move the keypoint from (50, 30) to (70, 50) assuming standard coordinate transformations.
    NrA   c                 S  s   | S rV   rS   rW   rS   rS   rT   rY      rZ   zkeypoints_d4.<locals>.<lambda>c                   s   t | d S r[   r7   rW   rh   rS   rT   rY      rZ   c                   s   t | d S r]   rs   rW   rt   rS   rT   rY      rZ   c                   s   t | d S r^   rs   rW   rt   rS   rT   rY      rZ   c                   s
   t |  S rV   )r;   rW   )rowsrS   rT   rY      rZ   c                   s   t t| d S r]   )r9   r7   rW   rt   rS   rT   rY      rZ   c                   s
   t |  S rV   )r=   rW   )colsrS   rT   rY      rZ   c                 S  s   t | S rV   )r9   rW   rS   rS   rT   rY      rZ   r_   rd   re   )rg   rU   rh   rr   rf   rS   )rv   rh   ru   rT   r5      s    





floatzColorType | None)imgrq   interpolationborder_modevaluerG   c                 C  sL   | j d d }t|}t||d}|\}}	tt||	|f|||d}
|
| S )NrA         ?matrixdsizeflagsrz   border_value)shaper>   cv2getRotationMatrix2Dr    warp_affine_with_value_extension)rx   rq   ry   rz   r{   rh   image_centerr~   rm   rn   warp_fnrS   rS   rT   r#      s    z#Literal[('largest_box', 'ellipse')])rE   rq   methodrh   rG   c                 C  s(  |   } |dd \}}| dddf | dddf | dddf | dddf f\}}}}	|t| }
|dkrt||||gd }t|||	|	gd }n|dkrL|| d }|	| d }tjdd	tjd
}|ddtjf tt| || d ddtjf  }|ddtjf t	t| || d ddtjf  }nt
d| dt|}t	|| |
 t||  |
 }t| | |
 t	||  }|d }|d }tj|dd| dddf< tj|dd| dddf< tj|dd| dddf< tj|dd| dddf< | S )a-  Rotates bounding boxes by angle degrees.

    Args:
        bboxes: A numpy array of bounding boxes with shape (num_bboxes, 4+).
                Each row represents a bounding box (x_min, y_min, x_max, y_max, ...).
        angle: Angle of rotation in degrees.
        method: Rotation method used. Should be one of: "largest_box", "ellipse".
        image_shape: Image shape (height, width).

    Returns:
        np.ndarray: A numpy array of rotated bounding boxes with the same shape as input.

    Reference:
        https://arxiv.org/abs/2109.13488
    NrA   r   rI   rB   largest_box      ?ellipseh  ZdtypeMethod   is not a valid rotation method.axis)rL   rw   rj   column_stackarangerk   newaxissinradianscosrK   deg2radminmax)rE   rq   r   rh   ru   rv   rO   rP   rQ   rR   r%   rX   rp   wrb   dataZ	angle_radZx_tZy_trS   rS   rT   r2     s0    D
<>
$")rg   rq   rh   rG   c                 C  s   t |}t||d}|  tj}|ddddf }t|ddd|	 }||ddddf< |dddf  t
|7  < |S )a.  Rotate keypoints by a specified angle.

    Args:
        keypoints (np.ndarray): An array of keypoints with shape (N, 4+) in the format (x, y, angle, scale, ...).
        angle (float): The angle by which to rotate the keypoints, in degrees.
        image_shape (tuple[int, int]): The shape of the image the keypoints belong to (height, width).
        **params: Additional parameters.

    Returns:
        np.ndarray: The rotated keypoints with the same shape as the input.

    Note:
        The rotation is performed around the center of the image.
    r|   NrA   rI   )r>   r   r   rL   ri   rj   rk   	transformreshapesqueezer   )rg   rq   rh   r   r~   ro   xyZ
xy_rotatedrS   rS   rT   r3   N  s    )rx   target_shapery   rG   c                 C  s:   || j d d kr| S |\}}ttj||f|d}|| S )NrA   )r   ry   )r   r   r   r$   )rx   r   ry   rm   rn   Z	resize_fnrS   rS   rT   r$   x  s
    )rx   r%   ry   rG   c                 C  s6   | j d d \}}t|| t|| f}t| ||S r]   )r   rD   r$   )rx   r%   ry   rm   rn   Znew_sizerS   rS   rT   r%     s    )rg   scale_xscale_yrG   c                 C  s   | dddf | dddf | dddf | dddf f\}}}}|| }|| }|t || }	t||||	g}
| jd tkrt|
| ddtdf g}
|
S )aN  Scales keypoints by scale_x and scale_y.

    Args:
        keypoints: A numpy array of keypoints with shape (N, 4+) in the format (x, y, angle, scale, ...).
        scale_x: Scale coefficient x-axis.
        scale_y: Scale coefficient y-axis.

    Returns:
        A numpy array of scaled keypoints with the same shape as input.
    Nr   rI   rA   rB   )r   rj   r   r   r   )rg   r   r   rX   rp   rq   r%   Zx_scaledZy_scaledZscale_scaledZscaled_keypointsrS   rS   rT   keypoints_scale  s    Dr   zCallable[..., Any])rx   max_sizery   funcrG   c                   sV   | j d d }|t||   dkrRt fdd|D \}}t| ||f|dS | S )NrA   r|   c                 3  s   | ]}t |  V  qd S rV   )round).0Zdimr%   rS   rT   	<genexpr>  s     z!_func_max_size.<locals>.<genexpr>ry   )r   rw   tupler$   )rx   r   ry   r   rh   
new_height	new_widthrS   r   rT   r&     s    )rx   r   ry   rG   c                 C  s   t | ||tS rV   )r&   r   rx   r   ry   rS   rS   rT   r'     s    c                 C  s   t | ||tS rV   )r&   r   r   rS   rS   rT   r(     s    z float | list[float] | np.ndarraybool)	rx   r~   	max_width
max_height
border_valrz   	keep_sizery   rG   c                 C  sF   | j d d }ttj|||f|||d}	|	| }
|rBt|
||dS |
S )NrA   )Mr   
borderModeborderValuer   r   )r   r   r   warpPerspectiver$   )rx   r~   r   r   r   rz   r   ry   rh   Zperspective_funcwarpedrS   rS   rT   r)     s    )rE   rh   r~   r   r   r   rG   c                 C  s  |dd \}}|   }t| ddddf |}	|	j\}
}}}t|
|g||g||g|
|ggddd}|dd}tj|ddd	}t||||||}|ddddf ddd}td
d |D }|r|n||r|n|f}t	||}||ddddf< |S )a  Applies perspective transformation to bounding boxes.

    This function transforms bounding boxes using the given perspective transformation matrix.
    It handles bounding boxes with additional attributes beyond the standard coordinates.

    Args:
        bboxes (np.ndarray): An array of bounding boxes with shape (num_bboxes, 4+).
                             Each row represents a bounding box (x_min, y_min, x_max, y_max, ...).
                             Additional columns beyond the first 4 are preserved unchanged.
        image_shape (tuple[int, int]): The shape of the image (height, width).
        matrix (np.ndarray): The perspective transformation matrix.
        max_width (int): The maximum width of the output image.
        max_height (int): The maximum height of the output image.
        keep_size (bool): If True, maintains the original image size after transformation.

    Returns:
        np.ndarray: An array of transformed bounding boxes with the same shape as input.
                    The first 4 columns contain the transformed coordinates, and any
                    additional columns are preserved from the input.

    Note:
        - This function modifies only the coordinate columns (first 4) of the input bounding boxes.
        - Any additional attributes (columns beyond the first 4) are kept unchanged.
        - The function handles denormalization and renormalization of coordinates internally.

    Example:
        >>> bboxes = np.array([[0.1, 0.1, 0.3, 0.3, 1], [0.5, 0.5, 0.8, 0.8, 2]])
        >>> image_shape = (100, 100)
        >>> matrix = np.array([[1.5, 0.2, -20], [-0.1, 1.3, -10], [0.002, 0.001, 1]])
        >>> transformed_bboxes = perspective_bboxes(bboxes, image_shape, matrix, 150, 150, False)
    NrA      r   rI   r   )r   r   )r   rA   constant)modec              
   S  s`   g | ]X}t |d d df t |d d df t |d d df t |d d df gqS )Nr   rI   )rj   r   r   )r   ZboxrS   rS   rT   
<listcomp>  s     z&perspective_bboxes.<locals>.<listcomp>)
rL   r   Trj   arrayr0   r   r!   perspective_keypointsr   )rE   rh   r~   r   r   r   rm   rn   transformed_bboxesZdenormalized_coordsrO   rP   rQ   rR   pointsZpoints_reshapedZpoints_paddedtransformed_pointsZ
new_coordsoutput_shapeZnormalized_coordsrS   rS   rT   perspective_bboxes  s     (,
r   )r~   y_uprG   c                 C  s.   |rt | d | d S t | d  | d S )z`Args:
    matrix (np.ndarray): Rotation matrix
    y_up (bool): is Y axis looks up or down

    rI   r   r   )rj   Zarctan2)r~   r   rS   rS   rT   r*   +  s    )rg   rh   r~   r   r   r   rG   c                 C  s  |   tj} |d d \}}| d d df | d d df | d d df | d d df f\}}	}
}t||	ftjddd}t|| }|j	dkr|tj
d d f }|d d df |d d df  }}	|
t|d dd df dd7 }
t|d t|d d |d	 d   }t|d
 t|d d |d
 d   }|t||9 }|r|d d \}}|| }|| }||9 }|	|9 }	|t||9 }t||	|
|g}| jd tkrt|| d d td f gS |S )NrA   r   rI   rB   r   Tr   r   )r   rI   )rI   rI   r   )rL   ri   rj   rk   r   r   r   perspectiveTransformr   ndimr   r*   signsqrtr   r   r   )rg   rh   r~   r   r   r   rm   rn   rX   rp   rq   r%   Zkeypoint_vectorr   r   r   transformed_keypointsrS   rS   rT   r   6  s2    
D 
" ,,r   z%skimage.transform.ProjectiveTransform)r~   rG   c                 C  s   t | jt jdt jdS )NrB   r   )rj   Zallcloserr   Zeyerk   )r~   rS   rS   rT   r+   n  s    zSequence[int]r   )imager~   r   r   rz   r   rG   c                 C  s(   t | }t||}tj| |||||dS )N)r   r   r   )r   extend_valuer   Z
warpAffine)r   r~   r   r   rz   r   num_channelsextended_valuerS   rS   rT   r   r  s    
r   )r   r~   ry   cvalr   r   rG   c           
      C  s^   t |r| S tt|d }tt|d }||f}tt|jd d ||||d}	|	| S )Nr   rI   rA   r}   )r+   rD   rj   r   r   r   rr   )
r   r~   ry   r   r   r   rm   rn   r   r   rS   rS   rT   r,     s    	zdict[str, Any])rg   r~   rh   r%   r   rG   c                 C  s  |   tj} t|r| S |tkrVt||\}}}}t|||||}	t| |	|dd} | ddddf }
t	
|
ddd|jdd  }t|jdd dd}| dddf | | dddf< t|d	 |d
 }| dddf  |9  < || ddddf< | S )u  Apply an affine transformation to keypoints.

    This function transforms keypoints using the given affine transformation matrix.
    It handles reflection padding if necessary, updates coordinates, angles, and scales.

    Args:
        keypoints (np.ndarray): Array of keypoints with shape (N, 4+) where N is the number of keypoints.
                                Each keypoint is represented as [x, y, angle, scale, ...].
        matrix (skimage.transform.ProjectiveTransform): The affine transformation matrix.
        image_shape (tuple[int, int]): Shape of the image (height, width).
        scale (dict[str, Any]): Dictionary containing scale factors for x and y directions.
                                Expected keys are 'x' and 'y'.
        mode (int): Border mode for handling keypoints near image edges.
                    Use cv2.BORDER_REFLECT_101, cv2.BORDER_REFLECT, etc.

    Returns:
        np.ndarray: Transformed keypoints array with the same shape as input.

    Notes:
        - The function applies reflection padding if the mode is in REFLECT_BORDER_MODES.
        - Coordinates (x, y) are transformed using the affine matrix.
        - Angles are adjusted based on the rotation component of the affine transformation.
        - Scales are multiplied by the maximum of x and y scale factors.
        - The @angle_2pi_range decorator ensures angles remain in the [0, 2π] range.

    Example:
        >>> keypoints = np.array([[100, 100, 0, 1]])
        >>> matrix = skimage.transform.ProjectiveTransform(...)
        >>> scale = {'x': 1.5, 'y': 1.2}
        >>> transformed_keypoints = keypoints_affine(keypoints, matrix, (480, 640), scale, cv2.BORDER_REFLECT_101)
    Tcenter_in_originNrA   r   rI   Fr   rX   rp   rB   )rL   ri   rj   rk   r+   r   "calculate_affine_transform_paddingget_pad_grid_dimensionsgenerate_reflected_keypointsr   r   r   rr   r   r*   r   )rg   r~   rh   r%   r   pad_left	pad_rightpad_top
pad_bottomgrid_dimensionsr   Zxy_transformedZangle_adjustmentZ	max_scalerS   rS   rT   keypoints_affine  s    ($ r   ztuple[int, int, int, int])r~   rh   rG   c                 C  s   |dd \}}t | rdS tddg|dg||gd|gg}| |}t||f}|jdd\}}|jdd\}	}
| j}t||g|	|g|	|
g||
gg}||}|jdd\}}|jdd\}	}
tdtd| }tdt|	| }tdtd| }tdt|
| }||||fS )zSCalculate the necessary padding for an affine transformation to avoid empty spaces.NrA   )r   r   r   r   r   r   )	r+   rj   r   vstackr   r   inversemathceil)r~   rh   rm   rn   cornerstransformed_cornersZall_cornersZmin_xZmin_yZmax_xZmax_yZinverse_matrixZbbox_cornersZinverse_cornersr   r   r   r   rS   rS   rT   r     s$    ""r   )rE   r~   rG   c              
   C  s:  | dddf | dddf | dddf | dddf f\}}}}t ||g||g||g||ggddd}tj|dd|j}|ddd}t j|dddddf dd}t j	|dddddf dd}	t j|dddddf dd}
t j	|dddddf dd}t 
||
|	|| ddddf gS )	a  Apply an affine transformation to bounding boxes and return the largest enclosing boxes.

    This function transforms each corner of every bounding box using the given affine transformation
    matrix, then computes the new bounding boxes that fully enclose the transformed corners.

    Args:
        bboxes (np.ndarray): An array of bounding boxes with shape (N, 4+) where N is the number of
                             bounding boxes. Each row should contain [x_min, y_min, x_max, y_max]
                             followed by any additional attributes (e.g., class labels).
        matrix (skimage.transform.ProjectiveTransform): The affine transformation matrix to apply.

    Returns:
        np.ndarray: An array of transformed bounding boxes with the same shape as the input.
                    Each row contains [new_x_min, new_y_min, new_x_max, new_y_max] followed by
                    any additional attributes from the input bounding boxes.

    Note:
        - This function assumes that the input bounding boxes are in the format [x_min, y_min, x_max, y_max].
        - The resulting bounding boxes are the smallest axis-aligned boxes that completely
          enclose the transformed original boxes. They may be larger than the minimal possible
          bounding box if the original box becomes rotated.
        - Any additional attributes beyond the first 4 coordinates are preserved unchanged.
        - This method is called "largest box" because it returns the largest axis-aligned box
          that encloses all corners of the transformed bounding box.

    Example:
        >>> bboxes = np.array([[10, 10, 20, 20, 1], [30, 30, 40, 40, 2]])  # Two boxes with class labels
        >>> matrix = skimage.transform.AffineTransform(scale=(2, 2), translation=(5, 5))
        >>> transformed_bboxes = bboxes_affine_largest_box(bboxes, matrix)
        >>> print(transformed_bboxes)
        [[ 25.  25.  45.  45.   1.]
         [ 65.  65.  85.  85.   2.]]
    Nr   rI   rA   rB   r   r   r   )rj   r   r0   skimager   Zmatrix_transformr   rr   r   r   r   )rE   r~   rO   rP   rQ   rR   r   r   	new_x_min	new_x_max	new_y_min	new_y_maxrS   rS   rT   bboxes_affine_largest_box  s    $D"    r   c              
   C  s^  | dddf | dddf | dddf | dddf f\}}}}|| d }|| d }|| }|| }	t jddt jd}
t t |
}t t |
}|ddt jf | |ddt jf  }|ddt jf | |	ddt jf  }t j||gdd	dd}t j	|t 
|jd dfgdd	}||jj }t |ddddf dkt tj|ddddf |ddddf< ||ddddf  ddddf }|t| dd}t j|dddddf dd	}t j|dddddf dd	}t j|dddddf dd	}t j|dddddf dd	}t ||||| ddd
df gS )a  Apply an affine transformation to bounding boxes using an ellipse approximation method.

    This function transforms bounding boxes by approximating each box with an ellipse,
    transforming points along the ellipse's circumference, and then computing the
    new bounding box that encloses the transformed ellipse.

    Args:
        bboxes (np.ndarray): An array of bounding boxes with shape (N, 4+) where N is the number of
                             bounding boxes. Each row should contain [x_min, y_min, x_max, y_max]
                             followed by any additional attributes (e.g., class labels).
        matrix (skimage.transform.ProjectiveTransform): The affine transformation matrix to apply.

    Returns:
        np.ndarray: An array of transformed bounding boxes with the same shape as the input.
                    Each row contains [new_x_min, new_y_min, new_x_max, new_y_max] followed by
                    any additional attributes from the input bounding boxes.

    Note:
        - This function assumes that the input bounding boxes are in the format [x_min, y_min, x_max, y_max].
        - The ellipse approximation method can provide a tighter bounding box compared to the
          largest box method, especially for rotations.
        - 360 points are used to approximate each ellipse, which provides a good balance between
          accuracy and computational efficiency.
        - Any additional attributes beyond the first 4 coordinates are preserved unchanged.
        - This method may be more suitable for objects that are roughly elliptical in shape.

    Example:
        >>> bboxes = np.array([[10, 10, 30, 20, 1], [40, 40, 60, 60, 2]])  # Two boxes with class labels
        >>> matrix = skimage.transform.AffineTransform(rotation=np.pi/4)  # 45-degree rotation
        >>> transformed_bboxes = bboxes_affine_ellipse(bboxes, matrix)
        >>> print(transformed_bboxes)
        [[ 5.86  5.86 34.14 24.14  1.  ]
         [30.   30.   70.   70.    2.  ]]
    Nr   rI   rA   rB   r   r   r   r   r   )rj   r   rk   r   r   r   r   stackr   ZconcatenateZonesr   rr   r   whereZfinforw   Zepslenr   r   r   )rE   r~   rO   rP   rQ   rR   Z
bbox_widthZbbox_heightZcenter_xZcenter_yanglesZ
cos_anglesZ
sin_anglesrX   rp   r   r   r   r   r   r   rS   rS   rT   bboxes_affine_ellipseM  s2    $D(("
(    r   )rE   r~   rotate_methodrh   rz   r   rG   c                 C  s   t |r| S t| |} |tkrPt||\}}}}	t||	|||}
t| |
|dd} |dkrdt| |}n$|dkrxt| |}ntd| dt	||}t
||S )a  Apply an affine transformation to bounding boxes.

    For reflection border modes (cv2.BORDER_REFLECT_101, cv2.BORDER_REFLECT), this function:
    1. Calculates necessary padding to avoid information loss
    2. Applies padding to the bounding boxes
    3. Adjusts the transformation matrix to account for padding
    4. Applies the affine transformation
    5. Validates the transformed bounding boxes

    For other border modes, it directly applies the affine transformation without padding.

    Args:
        bboxes (np.ndarray): Input bounding boxes
        matrix (skimage.transform.ProjectiveTransform): Affine transformation matrix
        rotate_method (str): Method for rotating bounding boxes ('largest_box' or 'ellipse')
        image_shape (Sequence[int]): Shape of the input image
        border_mode (int): OpenCV border mode
        output_shape (Sequence[int]): Shape of the output image

    Returns:
        np.ndarray: Transformed and normalized bounding boxes
    Tr   r   r   r   r   )r+   r   r   r   r   generate_reflected_bboxesr   r   rK   validate_bboxesr   )rE   r~   r   rh   rz   r   r   r   r   r   r   r   Zvalidated_bboxesrS   rS   rT   bboxes_affine  s    

r   z1skimage.transform.PiecewiseAffineTransform | Nonestr)rx   r~   ry   r   r   rG   c              	   C  s(   |d kr| S t jj| ||||d| jdS )NT)orderr   r   Zpreserve_ranger   )r   r   Zwarpr   )rx   r~   ry   r   r   rS   rS   rT   r-     s    F)rg   rh   invertedrG   c           	      C  s   |dd \}}t | dkr2tj||dftjdS tjd|d|f \}}t| }t|dtjf |dddf  d |dtjf |dddf  d  }|rd|d  tjS |tjS )a(  Generate a ``(H,W,N)`` array of distance maps for ``N`` keypoints.

    The ``n``-th distance map contains at every location ``(y, x)`` the
    euclidean distance to the ``n``-th keypoint.

    This function can be used as a helper when augmenting keypoints with a
    method that only supports the augmentation of images.

    Args:
        keypoints: A numpy array of shape (N, 2+) where N is the number of keypoints.
                   Each row represents a keypoint's (x, y) coordinates.
        image_shape: tuple[int, int] shape of the image (height, width)
        inverted (bool): If ``True``, inverted distance maps are returned where each
            distance value d is replaced by ``d/(d+1)``, i.e. the distance
            maps have values in the range ``(0.0, 1.0]`` with ``1.0`` denoting
            exactly the position of the respective keypoint.

    Returns:
        np.ndarray: A ``float32`` array of shape (H, W, N) containing ``N`` distance maps for ``N``
            keypoints. Each location ``(y, x, n)`` in the array denotes the
            euclidean distance at ``(y, x)`` to the ``n``-th keypoint.
            If `inverted` is ``True``, the distance ``d`` is replaced
            by ``d/(d+1)``. The height and width of the array match the
            height and width in ``image_shape``.
    NrA   r   r   .rI   )	r   rj   zerosrk   Zmgridr   r   r   ri   )	rg   rh   r   rm   rn   yyxxZkeypoints_arrayZ	distancesrS   rS   rT   r.     s    
Bz%Sequence[int] | dict[str, Any] | Noneztuple[bool, float, float])if_not_found_coordsrG   c                 C  sp   | dkrdS t | ttfrDt| tkr2d}t|d| d | d fS t | tr`d| d | d fS d	}t|dS )
z5Validate and process `if_not_found_coords` parameter.N)Tr   r   zIExpected tuple/list 'if_not_found_coords' to contain exactly two entries.Fr   rI   rX   rp   z>Expected if_not_found_coords to be None, tuple, list, or dict.)
isinstancer   listr   PAIRrK   dict)r   msgrS   rS   rT   validate_if_not_found_coords  s    
r  zfloat | Noneztuple[float, float] | None)positiondistance_map	thresholdr   rG   c                 C  sT   | \}}|||f }|s,|dk	r,||kr,dS |rD|dk	rD||krDdS t |t |fS )zADetermine if a valid keypoint can be found at the given position.N)rw   )r  r  r  r   rp   rX   r{   rS   rS   rT   find_keypoint.  s    r  )distance_mapsr   r   r  rG   c                 C  s  | j tkr(d| j  d| j d}t|| j\}}}t|\}}	}
|rbtj| || |dd}ntj| || |dd}t	|||f\}}t
||ft}|dk	r|r| ||t|f |k}n| ||t|f |k}|s|	|
g|| < n|| S |S )a~
  Convert distance maps back to keypoints coordinates.

    This function is the inverse of `to_distance_maps`. It takes distance maps generated for a set of keypoints
    and reconstructs the original keypoint coordinates. The function supports both regular and inverted distance maps,
    and can handle cases where keypoints are not found or fall outside a specified threshold.

    Args:
        distance_maps (np.ndarray): A 3D numpy array of shape (height, width, nb_keypoints) containing
            distance maps for each keypoint. Each channel represents the distance map for one keypoint.
        inverted (bool): If True, treats the distance maps as inverted (where higher values indicate
            closer proximity to keypoints). If False, treats them as regular distance maps (where lower
            values indicate closer proximity).
        if_not_found_coords (Sequence[int] | dict[str, Any] | None, optional): Coordinates to use for
            keypoints that are not found or fall outside the threshold. Can be:
            - None: Drop keypoints that are not found.
            - Sequence of two integers: Use these as (x, y) coordinates for not found keypoints.
            - Dict with 'x' and 'y' keys: Use these values for not found keypoints.
            Defaults to None.
        threshold (float | None, optional): A threshold value to determine valid keypoints. For inverted
            maps, values >= threshold are considered valid. For regular maps, values <= threshold are
            considered valid. If None, all keypoints are considered valid. Defaults to None.

    Returns:
        np.ndarray: A 2D numpy array of shape (nb_keypoints, 2) containing the (x, y) coordinates
        of the reconstructed keypoints. If `drop_if_not_found` is True (derived from if_not_found_coords),
        the output may have fewer rows than input keypoints.

    Raises:
        ValueError: If the input `distance_maps` is not a 3D array.

    Notes:
        - The function uses vectorized operations for improved performance, especially with large numbers of keypoints.
        - When `threshold` is None, all keypoints are considered valid, and `if_not_found_coords` is not used.
        - The function assumes that the input distance maps are properly normalized and scaled according to the
          original image dimensions.

    Example:
        >>> distance_maps = np.random.rand(100, 100, 3)  # 3 keypoints
        >>> inverted = True
        >>> if_not_found_coords = [0, 0]
        >>> threshold = 0.5
        >>> keypoints = from_distance_maps(distance_maps, inverted, if_not_found_coords, threshold)
        >>> print(keypoints.shape)
        (3, 2)
    z&Expected three-dimensional input, got z dimensions and shape .r   r   N)r   r   r   rK   r  rj   Zargmaxr   ZargminZunravel_indexr   ri   rw   r   )r  r   r   r  r   rm   rn   Znb_keypointsZdrop_if_not_foundZif_not_found_xZif_not_found_yZhitidx_flatZhitidx_yZhitidx_xrg   Z
valid_maskrS   rS   rT   r/   >  s$    3
)rg   r~   rh   keypoints_thresholdrG   c           	      C  s   |d kr| S | d d df | d d df  }}t | d d d df |d}t||ddd}t|dddd|}t|||g}| jd tkrt|| d d td f gS |S )	NrA   rB   Tr   r   r   rX   rp   rI   )r.   r-   r/   rj   r   r   r   )	rg   r~   rh   r  as	dist_mapsZtransformed_xyr   rS   rS   rT   keypoints_piecewise_affine  s    "r  z*skimage.transform.PiecewiseAffineTransform)rE   r~   rh   r  rG   c              	   C  s  |d kr| S |d d \}}t | |}t|d d ddgf |d d ddgf |d d ddgf |d d ddgf g}|ddddd}t||d}t||ddd}t|dddd|}	|	dd	d}	|	d d d d df dk|	d d d d df |k @ |	d d d d df dk@ |	d d d d df |k @ }
t| }t	t
| D ]}|	| |
|  }t
|dkr|d d df  ||df< |d d df  ||df< |d d df  ||df< |d d df  ||df< n| | ||< qJt||S )
NrA   r   rI   rB   r   Tr   r	  r   )r   rj   r   r0   r   r.   r-   r/   
zeros_likeranger   r   r   r   )rE   r~   rh   r  rm   rn   Zdenorm_bboxesrg   r  r   mask
new_bboxesiZvalid_pointsrS   rS   rT   bboxes_piecewise_affine  sD    

r  )rx   rU   rG   c              	   C  sP   dd dd dd dd t dd ttd}||kr>|| | S td| d	S )
a  Applies a `D_4` symmetry group transformation to an image array.

    This function manipulates an image using transformations such as rotations and flips,
    corresponding to the `D_4` dihedral group symmetry operations.
    Each transformation is identified by a unique group member code.

    Parameters:
    - img (np.ndarray): The input image array to transform.
    - group_member (D4Type): A string identifier indicating the specific transformation to apply. Valid codes include:
      - 'e': Identity (no transformation).
      - 'r90': Rotate 90 degrees counterclockwise.
      - 'r180': Rotate 180 degrees.
      - 'r270': Rotate 270 degrees counterclockwise.
      - 'v': Vertical flip.
      - 'hvt': Transpose over second diagonal
      - 'h': Horizontal flip.
      - 't': Transpose (reflect over the main diagonal).

    Returns:
    - np.ndarray: The transformed image array.

    Raises:
    - ValueError: If an invalid group member is specified.

    Examples:
    - Rotating an image by 90 degrees:
      `transformed_image = d4(original_image, 'r90')`
    - Applying a horizontal flip to an image:
      `transformed_image = d4(original_image, 'h')`
    c                 S  s   | S rV   rS   rW   rS   rS   rT   rY     rZ   zd4.<locals>.<lambda>c                 S  s
   t | dS r[   rot90rW   rS   rS   rT   rY     rZ   c                 S  s
   t | dS r]   r  rW   rS   rS   rT   rY     rZ   c                 S  s
   t | dS r^   r  rW   rS   rS   rT   rY     rZ   c                 S  s   t t| dS r]   )r0   r  rW   rS   rS   rT   rY     rZ   r_   rd   N)r   r   r0   rK   )rx   rU   rf   rS   rS   rT   r1     s     )rx   coderG   c                 C  s   t | |S rV   )r   Zflip)rx   r  rS   rS   rT   random_flip%  s    r  )rx   rG   c                 C  s(   t t| j}d\|d< |d< | |S )zTransposes the first two dimensions of an array of any dimensionality.
    Retains the order of any additional dimensions.

    Args:
        img (np.ndarray): Input array.

    Returns:
        np.ndarray: Transposed array.
    r   r   rI   )r   r  r   r0   )rx   Znew_axesrS   rS   rT   r0   *  s    )rx   rF   rG   c                 C  s   t | |S rV   )rj   r  )rx   rF   rS   rS   rT   r  <  s    r  )rE   rG   c                 C  sL   |   }d| dddf  |dddf< d| dddf  |dddf< |S )aW  Flip bounding boxes vertically around the x-axis.

    Args:
        bboxes: A numpy array of bounding boxes with shape (num_bboxes, 4+).
                Each row represents a bounding box (x_min, y_min, x_max, y_max, ...).

    Returns:
        np.ndarray: A numpy array of vertically flipped bounding boxes with the same shape as input.
    rI   NrB   rL   rE   flipped_bboxesrS   rS   rT   r:   @  s      c                 C  sL   |   }d| dddf  |dddf< d| dddf  |dddf< |S )a[  Flip bounding boxes horizontally around the y-axis.

    Args:
        bboxes: A numpy array of bounding boxes with shape (num_bboxes, 4+).
                Each row represents a bounding box (x_min, y_min, x_max, y_max, ...).

    Returns:
        np.ndarray: A numpy array of horizontally flipped bounding boxes with the same shape as input.
    rI   NrA   r   r  r  rS   rS   rT   r<   R  s      )rE   drG   c                 C  sL   |dkrt | S |dkr t| S |dkr8t| } t | S td| ddS )a  Flip a bounding box either vertically, horizontally or both depending on the value of `d`.

    Args:
        bboxes: A numpy array of bounding boxes with shape (num_bboxes, 4+).
                Each row represents a bounding box (x_min, y_min, x_max, y_max, ...).
        d: dimension. 0 for vertical flip, 1 for horizontal, -1 for transpose

    Returns:
        A bounding box `(x_min, y_min, x_max, y_max)`.

    Raises:
        ValueError: if value of `d` is not -1, 0 or 1.

    r   rI   r   Invalid d value . Valid values are -1, 0 and 1N)r:   r<   rK   )rE   r  rS   rS   rT   bboxes_flipd  s    r  c                 C  s8   |   }| ddddddgf |ddddddgf< |S )aW  Transpose bounding boxes by swapping x and y coordinates.

    Args:
        bboxes: A numpy array of bounding boxes with shape (num_bboxes, 4+).
                Each row represents a bounding box (x_min, y_min, x_max, y_max, ...).

    Returns:
        np.ndarray: A numpy array of transposed bounding boxes with the same shape as input.
    NrI   r   rB   rA   r  )rE   Ztransposed_bboxesrS   rS   rT   r8     s    ,)rg   ru   rG   c                 C  sV   |   tj}|d | dddf  |dddf< | dddf  |dddf< |S )a,  Flip keypoints vertically around the x-axis.

    Args:
        keypoints: A numpy array of shape (N, 4+) where each row represents a keypoint (x, y, angle, scale, ...).
        rows: Image height.

    Returns:
        np.ndarray: An array of flipped keypoints with the same shape as the input.
    rI   NrA   )rL   ri   rj   rk   )rg   ru   flipped_keypointsrS   rS   rT   r;     s    $)rg   rv   rG   c                 C  sZ   |   tj}|d | dddf  |dddf< tj| dddf  |dddf< |S )a-  Flip keypoints horizontally around the y-axis.

    Args:
        keypoints: A numpy array of shape (N, 4+) where each row represents a keypoint (x, y, angle, scale, ...).
        cols: Image width.

    Returns:
        np.ndarray: An array of flipped keypoints with the same shape as the input.
    rI   Nr   rA   )rL   ri   rj   rk   rl   )rg   rv   r  rS   rS   rT   r=     s    $")rg   r  rh   rG   c                 C  sd   |dd \}}|dkr"t | |S |dkr4t| |S |dkrPt| |} t | |S td| ddS )a  Flip a keypoint either vertically, horizontally or both depending on the value of `d`.

    Args:
        keypoints: A keypoints `(x, y, angle, scale)`.
        d: Number of flip. Must be -1, 0 or 1:
            * 0 - vertical flip,
            * 1 - horizontal flip,
            * -1 - vertical and horizontal flip.
        image_shape: A tuple of image shape `(height, width, channels)`.

    Returns:
        A keypoint `(x, y, angle, scale)`.

    Raises:
        ValueError: if value of `d` is not -1, 0 or 1.

    NrA   r   rI   r   r  r  )r;   r=   rK   )rg   r  rh   ru   rv   rS   rS   rT   keypoints_flip  s    



r   )rg   rG   c                 C  sx   |   }| ddddgf |ddddgf< | dddf }t|tjktjd | dtj d | |dddf< |S )a  Transposes keypoints along the main diagonal.

    Args:
        keypoints: A numpy array of shape (N, 4+) where each row represents a keypoint (x, y, angle, scale, ...).

    Returns:
        np.ndarray: An array of transposed keypoints with the same shape as the input.
    NrI   r   rA   rB   )rL   rj   r   rl   )rg   Ztransposed_keypointsr   rS   rS   rT   r9     s
    $8)rx   
min_height	min_widthrz   r{   rG   c                 C  s   | j d d \}}||k r8t|| d }|| | }nd}d}||k rft|| d }	|| |	 }
nd}	d}
t| |||	|
||} | j d d t||t||fkrtd| j d d  dt||t||f | S )NrA   g       @r   zInvalid result shape. Got: z. Expected: )r   rD   r"   r   RuntimeError)rx   r!  r"  rz   r{   rm   rn   	h_pad_toph_pad_bottom
w_pad_leftw_pad_rightrS   rS   rT   r!     s"    "(zSequence[ScalarType])r{   r   rG   c                 C  s   t | ttfr| g| S | S rV   )r   rD   rw   )r{   r   rS   rS   rT   r     s    r   )rx   topbottomleftrightrz   r{   rG   c           	   	   C  s*   t | }t||}tj| ||||||dS )N)Z
borderTyper{   )r   r   r   ZcopyMakeBorder)	rx   r(  r)  r*  r+  rz   r{   r   r   rS   rS   rT   %copy_make_border_with_value_extension  s    	
r,  )rx   r$  r%  r&  r'  rz   r{   rG   c              	   C  s   t t||||||d}|| S )N)r(  r)  r*  r+  rz   r{   )r   r,  )rx   r$  r%  r&  r'  rz   r{   Zpad_fnrS   rS   rT   r"   3  s    

)rx   map_xmap_yry   rz   r{   rG   c                 C  s   t j| |||||dS )Nr   r   )r   remap)rx   r-  r.  ry   rz   r{   rS   rS   rT   r   J  s    	)rg   r-  r.  rh   rG   c                 C  s   |d d \}}t |ddj|dd}t |ddj|dd}| d d df | d d df  }}	t |d|d }t |	d|d }	|t|	t }
}|||
f ||||
f   }|||
f |	|||
f   }t |d|d }t |d|d }t ||| d d dd f gS )NrA   rI   r   r   r   )rj   r   r   repeatclipri   rD   r   )rg   r-  r.  rh   rm   rn   Zx_invZy_invrX   rp   Zx_idxZy_idxZnew_xZnew_yrS   rS   rT   r   V  s    ")rE   r-  r.  rh   rz   rG   c                 C  st   |   }tt| |d}tj|||tj|dd}|jtkrLtj	|dd}nt|d}t
||d d d df< |S )N)rI   rA   r   r   r/  r   )rA   r   rI   r   )rL   rj   r0   r   r   r0  INTER_NEARESTr   r   Zexpand_dimsr   )rE   r-  r.  rh   rz   resultmaskstransformed_masksrS   rS   rT   r    y  s    
znp.random.RandomStateztuple[np.ndarray, np.ndarray])rh   alphasigma	same_dxdykernel_sizerandom_staterG   c           
      C  s   | dd \}}| ||tjd d }tj||||d ||9 }|rP|}	n6| ||tjd d }	tj|	|||	d |	|9 }	||	fS )z3Generate displacement fields for elastic transform.NrA   rI   )dst)Zrandri   rj   rk   r   ZGaussianBlur)
rh   r7  r8  r9  r:  r;  rm   rn   dxdyrS   rS   rT   generate_displacement_fields  s    	r?  )rE   r   r   r   r   rz   rh   rG   c                 C  s   |t kr$t||||g}t| |S t|||||}t| ||} |d \}	}
|d d \}}|
| | }|	| | }t| | | | g}t| |} || | }|| | }t| ||fS )Noriginal_positionrA   )r   rj   r   shift_bboxesr   r   r   )rE   r   r   r   r   rz   rh   shift_vectorr   original_roworiginal_colZimage_heightZimage_widthZ
left_shiftZ	top_shiftr   r   rS   rS   rT   
pad_bboxes  s    


rE  )rE   rh   rG   c           	      C  s|   |dd \}}| dddf | dddf | dddf | dddf f\}}}}|dk|dk@ ||k @ ||k @ }| | S )aR  Validate bounding boxes and remove invalid ones.

    Args:
        bboxes (np.ndarray): Array of bounding boxes with shape (n, 4) where each row is [x_min, y_min, x_max, y_max].
        image_shape (tuple[int, int]): Shape of the image as (height, width).

    Returns:
        np.ndarray: Array of valid bounding boxes, potentially with fewer boxes than the input.

    Example:
        >>> bboxes = np.array([[10, 20, 30, 40], [-10, -10, 5, 5], [100, 100, 120, 120]])
        >>> valid_bboxes = validate_bboxes(bboxes, (100, 100))
        >>> print(valid_bboxes)
        [[10 20 30 40]]
    NrA   r   rI   rB   rS   )	rE   rh   ru   rv   rO   rP   rQ   rR   valid_indicesrS   rS   rT   r     s    D r   )rE   rB  rG   c                 C  s(   |   }|ddddf  |7  < |S )a  Shift bounding boxes by a given vector.

    Args:
        bboxes (np.ndarray): Array of bounding boxes with shape (n, m) where n is the number of bboxes
                             and m >= 4. The first 4 columns are [x_min, y_min, x_max, y_max].
        shift_vector (np.ndarray): Vector to shift the bounding boxes by, with shape (4,) for
                                   [shift_x, shift_y, shift_x, shift_y].

    Returns:
        np.ndarray: Shifted bounding boxes with the same shape as input.
    Nr   r  )rE   rB  shifted_bboxesrS   rS   rT   rA    s    rA  zdict[str, tuple[int, int]])r   r   r   r   rh   rG   c                 C  s~   |dd \}}dt | |  t ||  }dt ||  t ||  }t | | }	t || }
||f|	|
fdS )a-  Calculate the dimensions of the grid needed for reflection padding and the position of the original image.

    Args:
        pad_top (int): Number of pixels to pad above the image.
        pad_bottom (int): Number of pixels to pad below the image.
        pad_left (int): Number of pixels to pad to the left of the image.
        pad_right (int): Number of pixels to pad to the right of the image.
        image_shape (tuple[int, int]): Shape of the original image as (height, width).

    Returns:
        dict[str, tuple[int, int]]: A dictionary containing:
            - 'grid_shape': A tuple (grid_rows, grid_cols) where:
                - grid_rows (int): Number of times the image needs to be repeated vertically.
                - grid_cols (int): Number of times the image needs to be repeated horizontally.
            - 'original_position': A tuple (original_row, original_col) where:
                - original_row (int): Row index of the original image in the grid.
                - original_col (int): Column index of the original image in the grid.
    NrA   rI   )
grid_shaper@  )r   r   )r   r   r   r   rh   ru   rv   	grid_rows	grid_colsrC  rD  rS   rS   rT   r     s      r   )rE   	grid_dimsrh   r   rG   c              	   C  s|  |dd \}}|d \}}|d \}}	t | d|d}
t | d|d}t | dd|d}t|	| || |	| || g}t| |} t|
|}
t||}t||}g }t|D ]}t|D ]}|| d d	kr||	 d d	kr| }n2|| d d	kr|
}n||	 d d	kr|}n|}t||	 | || | ||	 | || | g}t||}|| qqt|}|rxt|| S |S )
a  Generate reflected bounding boxes for the entire reflection grid.

    Args:
        bboxes (np.ndarray): Original bounding boxes.
        grid_dims (dict[str, tuple[int, int]]): Grid dimensions and original position.
        image_shape (tuple[int, int]): Shape of the original image as (height, width).
        center_in_origin (bool): If True, center the grid at the origin. Default is False.

    Returns:
        np.ndarray: Array of reflected and shifted bounding boxes for the entire grid.
    NrA   rH  r@  Tflip_horizontalrh   flip_verticalrh   rM  rO  rh   r   )flip_bboxesrj   r   rA  r  appendr   )rE   rK  rh   r   ru   rv   rI  rJ  rC  rD  Zbboxes_hflippedZbboxes_vflippedZbboxes_hvflippedrB  r  grid_rowgrid_colZcurrent_bboxes
cell_shiftrG  r4  rS   rS   rT   r     s@    "



 





r   r   )rE   rM  rO  rh   rG   c                 C  st   |dd \}}|   }|rD||ddddgf  |ddddgf< |rp||ddddgf  |ddddgf< |S )a  Flip bounding boxes horizontally and/or vertically.

    Args:
        bboxes (np.ndarray): Array of bounding boxes with shape (n, m) where each row is
            [x_min, y_min, x_max, y_max, ...].
        flip_horizontal (bool): Whether to flip horizontally.
        flip_vertical (bool): Whether to flip vertically.
        image_shape (tuple[int, int]): Shape of the image as (height, width).

    Returns:
        np.ndarray: Flipped bounding boxes.
    NrA   r   rB   rI   r  )rE   rM  rO  rh   ru   rv   r  rS   rS   rT   rQ  `  s    ((rQ  )r   generated_meshry   rG   c                 C  s   t | }|D ]}|dd \}}}}|dd dd}	t j||g||g||g||ggt jd}
t|
|	}tj| || jd | jd f|d}t j	| jdd t j
d}t|t |	d t|||}q|S )	a  Apply perspective distortion to an image based on a generated mesh.

    This function applies a perspective transformation to each cell of the image defined by the
    generated mesh. The distortion is applied using OpenCV's perspective transformation and
    blending techniques.

    Args:
        image (np.ndarray): The input image to be distorted. Can be a 2D grayscale image or a
                            3D color image.
        generated_mesh (np.ndarray): A 2D array where each row represents a quadrilateral cell
                                    as [x1, y1, x2, y2, dst_x1, dst_y1, dst_x2, dst_y2, dst_x3, dst_y3, dst_x4, dst_y4].
                                    The first four values define the source rectangle, and the last eight values
                                    define the destination quadrilateral.
        interpolation (int): Interpolation method to be used in the perspective transformation.
                             Should be one of the OpenCV interpolation flags (e.g., cv2.INTER_LINEAR).

    Returns:
        np.ndarray: The distorted image with the same shape and dtype as the input image.

    Note:
        - The function preserves the channel dimension of the input image.
        - Each cell of the generated mesh is transformed independently and then blended into the output image.
        - The distortion is applied using perspective transformation, which allows for more complex
          distortions compared to affine transformations.

    Example:
        >>> image = np.random.randint(0, 255, (100, 100, 3), dtype=np.uint8)
        >>> mesh = np.array([[0, 0, 50, 50, 5, 5, 45, 5, 45, 45, 5, 45]])
        >>> distorted = distort_image(image, mesh, cv2.INTER_LINEAR)
        >>> distorted.shape
        (100, 100, 3)
    Nr   rA   r   rI   r   )r      )rj   r  r   r   rk   r   getPerspectiveTransformr   r   r   uint8ZfillConvexPolyZint32ZcopyTo)r   rV  ry   Zdistorted_imagemeshx1y1x2y2dst_quadsrc_quadperspective_matr   r  rS   rS   rT   distort_image|  s$    "
"rb  )rE   rV  rh   rG   c           
        s   |   } tjt| f|tjd}t| d d d df tD ]&\}\}}}}d||||||f< q@t fdd|D }	t	|	| d d d df< | S )Nr   r   rI   c                   s   g | ]}t | tjqS rS   )rb  r   r3  )r   r  rV  rS   rT   r     s     z&bbox_distort_image.<locals>.<listcomp>)
rL   rj   r   r   rY  	enumerateri   rD   r   r   )
rE   rV  rh   r5  r  rO   rP   rQ   rR   r6  rS   rc  rT   bbox_distort_image  s    .re  )rg   rV  rh   rG   c                 C  s  |   }|d d \}}|D ]}|d d \}}}	}
|dd  dd}tj||g|	|g|	|
g||
ggtjd}t||}| d d df |k| d d df |	k @ | d d df |k@ | d d df |
k @ }| | }t|dkr|d d d df tjddd}t	||dd}|||d df< qt
|d d df d|d |d d df< t
|d d df d|d |d d df< |S )NrA   r   r   r   rI   r   )rL   r   rj   r   rk   r   rX  r   ri   r   r2  )rg   rV  rh   Zdistorted_keypointsrm   rn   rZ  r[  r\  r]  r^  r_  r`  ra  r  Zcell_keypointsZpoints_float32r   rS   rS   rT   distort_image_keypoints  s.    

P&**rf  )
dimensions	magnituderG   c                 C  s  | j dd \}}|| }tj|dftjd}| ddddddgf |ddddf< | ddddddgf |ddddf< | dddddd	gf |dddd
f< | dddddd	gf |ddd
df< |d |d  }}tj| |d ||dfdtj}td|D ]}	td|D ]}
||	d |
d f \}}||	d | |
d  dd
f  ||g7  < ||	d | |
 d
df  ||g7  < ||	| |
d  ddf  ||g7  < ||	| |
 ddf  ||g7  < q,q|S )a  Generate distorted grid polygons based on input dimensions and magnitude.

    This function creates a grid of polygons and applies random distortions to the internal vertices,
    while keeping the boundary vertices fixed. The distortion is applied consistently across shared
    vertices to avoid gaps or overlaps in the resulting grid.

    Args:
        dimensions (np.ndarray): A 3D array of shape (grid_height, grid_width, 4) where each element
                                 is [x_min, y_min, x_max, y_max] representing the dimensions of a grid cell.
        magnitude (int): Maximum pixel-wise displacement for distortion. The actual displacement
                         will be randomly chosen in the range [-magnitude, magnitude].

    Returns:
        np.ndarray: A 2D array of shape (total_cells, 8) where each row represents a distorted polygon
                    as [x1, y1, x2, y1, x2, y2, x1, y2]. The total_cells is equal to grid_height * grid_width.

    Note:
        - Only internal grid points are distorted; boundary points remain fixed.
        - The function ensures consistent distortion across shared vertices of adjacent cells.
        - The distortion is applied to the following points of each internal cell:
            * Bottom-right of the cell above and to the left
            * Bottom-left of the cell above
            * Top-right of the cell to the left
            * Top-left of the current cell
        - Each square represents a cell, and the X marks indicate the coordinates where displacement occurs.
            +--+--+--+--+
            |  |  |  |  |
            +--X--X--X--+
            |  |  |  |  |
            +--X--X--X--+
            |  |  |  |  |
            +--X--X--X--+
            |  |  |  |  |
            +--+--+--+--+
        - For each X, the coordinates of the left, right, top, and bottom edges
          in the four adjacent cells are displaced.

    Example:
        >>> dimensions = np.array([[[0, 0, 50, 50], [50, 0, 100, 50]],
        ...                        [[0, 50, 50, 100], [50, 50, 100, 100]]])
        >>> distorted = generate_distorted_grid_polygons(dimensions, magnitude=10)
        >>> distorted.shape
        (4, 8)
    NrA      r   r   r   r   rI   rB      )size)	r   rj   r   rk   r   r   randintri   r  )rg  rh  Zgrid_heightZ
grid_widthZtotal_cellsZpolygonsZinternal_points_heightZinternal_points_widthZdisplacementsr  jr=  r>  rS   rS   rT    generate_distorted_grid_polygons  s.    0,,,,,((,rn  )rg   r   r   r   r   rz   rh   rG   c                 C  s   |t jt jhkr(t||g}t| |S t|||||}t| ||} |d d \}	}
|d \}}| d d df  ||
 | 8  < | d d df  ||	 | 8  < || |	 }|| |
 }t| ||fS )NrA   r@  r   rI   )	r   ZBORDER_REFLECT_101ZBORDER_REFLECT101rj   r   shift_keypointsr   r   validate_keypoints)rg   r   r   r   r   rz   rh   rB  r   ru   rv   rC  rD  r   r   rS   rS   rT   pad_keypointsW  s    

  rq  )rg   rh   rG   c                 C  sZ   |dd \}}| dddf | dddf  }}|dk||k @ |dk@ ||k @ }| | S )a  Validate keypoints and remove those that fall outside the image boundaries.

    Args:
        keypoints (np.ndarray): Array of keypoints with shape (N, M) where N is the number of keypoints
                                and M >= 2. The first two columns represent x and y coordinates.
        image_shape (tuple[int, int]): Shape of the image as (height, width).

    Returns:
        np.ndarray: Array of valid keypoints that fall within the image boundaries.

    Note:
        This function only checks the x and y coordinates (first two columns) of the keypoints.
        Any additional columns (e.g., angle, scale) are preserved for valid keypoints.
    NrA   r   rI   rS   )rg   rh   ru   rv   rX   rp   rF  rS   rS   rT   rp  x  s    " rp  )rg   rB  rG   c                 C  s0   |   }|d d d df  |d d 7  < |S r]   r  )rg   rB  shifted_keypointsrS   rS   rT   ro    s    $ro  )rg   rK  rh   r   rG   c                 C  sd  |d \}}|d \}}t | d|d}t | d|d}	t | dd|d}
|dd \}}t|| || d	d	g}t| |} t||}t|	|}	t|
|}
g }t|D ]}t|D ]}|| d d	kr|| d d	kr| }n2|| d d	kr|}n|| d d	kr|	}n|
}t|| | || | d	d	g}t||}|| qqt|}|r`t|| S |S )
a  Generate reflected keypoints for the entire reflection grid.

    This function creates a grid of keypoints by reflecting and shifting the original keypoints.
    It handles both centered and non-centered grids based on the `center_in_origin` parameter.

    Args:
        keypoints (np.ndarray): Original keypoints array of shape (N, 4+), where N is the number of keypoints,
                                and each keypoint is represented by at least 4 values (x, y, angle, scale, ...).
        grid_dims (dict[str, tuple[int, int]]): A dictionary containing grid dimensions and original position.
            It should have the following keys:
            - "grid_shape": tuple[int, int] representing (grid_rows, grid_cols)
            - "original_position": tuple[int, int] representing (original_row, original_col)
        image_shape (tuple[int, int]): Shape of the original image as (height, width).
        center_in_origin (bool, optional): If True, center the grid at the origin. Default is False.

    Returns:
        np.ndarray: Array of reflected and shifted keypoints for the entire grid. The shape is
                    (N * grid_rows * grid_cols, 4+), where N is the number of original keypoints.

    Note:
        - The function handles keypoint flipping and shifting to create a grid of reflected keypoints.
        - It preserves the angle and scale information of the keypoints during transformations.
        - The resulting grid can be either centered at the origin or positioned based on the original grid.
    rH  r@  TrL  rN  rP  NrA   r   )flip_keypointsrj   r   ro  r  rR  r   )rg   rK  rh   r   rI  rJ  rC  rD  Zkeypoints_hflippedZkeypoints_vflippedZkeypoints_hvflippedru   rv   rB  Znew_keypointsrS  rT  Zcurrent_keypointsrU  rr  r4  rS   rS   rT   r     s4    



 "

r   )rg   rM  rO  rh   rG   c                 C  s   |d d \}}|   }|rZ||d d df  |d d df< |d d df  |d d df< |r||d d df  |d d df< |d d df  |d d df< |S )NrA   r   rI   r  )rg   rM  rO  rh   ru   rv   r  rS   rS   rT   rs    s      rs  c                   @  s   e Zd ZU ded< ded< dS )TranslateDictrw   rX   rp   N__name__
__module____qualname____annotations__rS   rS   rS   rT   rt    s   
rt  c                   @  s   e Zd ZU ded< ded< dS )	ShearDictrw   rX   rp   Nru  rS   rS   rS   rT   rz    s   
rz  c                   @  s   e Zd ZU ded< ded< dS )	ScaleDictrw   rX   rp   Nru  rS   rS   rS   rT   r{    s   
r{  ztuple[float, float])	translateshearr%   r#   shiftrG   c                 C  sv   t jj|d |d gd}t jj|d |d ft|t|d t|d f| d | d fd}|j}|| | S )a  Create an affine transformation matrix combining translation, shear, scale, and rotation.

    This function creates a complex affine transformation by combining multiple transformations
    in a specific order. The transformations are applied as follows:
    1. Shift to top-left: Moves the center of transformation to (0, 0)
    2. Apply main transformations: scale, rotation, shear, and translation
    3. Shift back to center: Moves the center of transformation back to its original position

    The order of these transformations is crucial as matrix multiplications are not commutative.

    Args:
        translate (TranslateDict): Translation in x and y directions.
                                   Keys: 'x', 'y'. Values: translation amounts in pixels.
        shear (ShearDict): Shear in x and y directions.
                           Keys: 'x', 'y'. Values: shear angles in degrees.
        scale (ScaleDict): Scale factors for x and y directions.
                           Keys: 'x', 'y'. Values: scale factors (1.0 means no scaling).
        rotate (float): Rotation angle in degrees. Positive values rotate counter-clockwise.
        shift (tuple[float, float]): Shift to apply before and after transformations.
                                     Typically the image center (width/2, height/2).

    Returns:
        skimage.transform.ProjectiveTransform: The resulting affine transformation matrix.

    Note:
        - All angle inputs (rotate, shear) are in degrees and are converted to radians internally.
        - The order of transformations in the AffineTransform is: scale, rotation, shear, translation.
        - The resulting transformation can be applied to coordinates using the __call__ method.
    r   rI   translationrX   rp   )r%   Zrotationr}  r  )r   r   SimilarityTransformZAffineTransformrj   r   r   )r|  r}  r%   r#   r~  Zmatrix_to_topleftZmatrix_transformsZmatrix_to_centerrS   rS   rT   #create_affine_transformation_matrix  s    &	r  c                 C  sr   |dd \}}t ddg|dg||gd|gg}| |}t |jddt}t |jddt}||fS )a  Compute the bounds of an image after applying an affine transformation.

    Args:
        matrix (skimage.transform.ProjectiveTransform): The affine transformation matrix.
        image_shape (tuple[int, int]): The shape of the image as (height, width).

    Returns:
        tuple[np.ndarray, np.ndarray]: A tuple containing:
            - min_coords: An array with the minimum x and y coordinates.
            - max_coords: An array with the maximum x and y coordinates.
    NrA   r   r   )rj   r   floorr   ri   rD   r   r   )r~   rh   rm   rn   r   r   
min_coords
max_coordsrS   rS   rT    compute_transformed_image_bounds=	  s    "r  ztuple[int, ...]z=tuple[skimage.transform.ProjectiveTransform, tuple[int, int]])r~   input_shaperG   c                 C  s   |d d \}}|dks |dkr>| t tttf |d d fS t| ||f\}}|\}}|\}}	|	| d }
|| d }t|tkrt|
||d f}nt|
|f}tdd |	 D }| | f}t
jj|d}| |7 } | t tttf |fS )NrA   r   rI   c                 s  s   | ]}t |V  qd S rV   )rD   )r   ra   rS   rS   rT   r   p	  s     z3compute_affine_warp_output_shape.<locals>.<genexpr>r  )r	   r   rD   r  r   r   rj   r   r   tolistr   r   r  )r~   r  rm   rn   r  r  ZmincZminrZmaxcZmaxrZ
out_heightZ	out_widthr   Zoutput_shape_tupler  Zmatrix_to_fitrS   rS   rT    compute_affine_warp_output_shape[	  s     r  )rh   rG   c                 C  s(   | dd \}}|d d |d d fS )zCalculate the center coordinates if image. Used by images, masks and keypoints.

    Args:
        image_shape (tuple[int, int]): The shape of the image.

    Returns:
        tuple[float, float]: The center coordinates.
    NrA   r   rS   rh   rm   rn   rS   rS   rT   r>   x	  s    	c                 C  s    | dd \}}|d |d fS )zCalculate the center coordinates for of image for bounding boxes.

    Args:
        image_shape (tuple[int, int]): The shape of the image.

    Returns:
        tuple[float, float]: The center coordinates.
    NrA   rS   r  rS   rS   rT   r?   	  s    	zlist[float])rh   steps_xsteps_y	num_stepsrG   c                 C  s  | dd \}}|| }t |t j}d}t|D ]T\}	}
|	| }t|}tt|| |}|||
  }t |||| |||< |}q2|| }t |t j}d}t|D ]T\}	}
|	| }t|}tt|| |}|||
  }t |||| |||< |}qt ||S )aY  Generate a distorted grid for image transformation based on given step sizes.

    This function creates two 2D arrays (map_x and map_y) that represent a distorted version
    of the original image grid. These arrays can be used with OpenCV's remap function to
    apply grid distortion to an image.

    Args:
        image_shape (tuple[int, int]): The shape of the image as (height, width).
        steps_x (list[float]): List of step sizes for the x-axis distortion. The length
            should be num_steps + 1. Each value represents the relative step size for
            a segment of the grid in the x direction.
        steps_y (list[float]): List of step sizes for the y-axis distortion. The length
            should be num_steps + 1. Each value represents the relative step size for
            a segment of the grid in the y direction.
        num_steps (int): The number of steps to divide each axis into. This determines
            the granularity of the distortion grid.

    Returns:
        tuple[np.ndarray, np.ndarray]: A tuple containing two 2D numpy arrays:
            - map_x: A 2D array of float32 values representing the x-coordinates
              of the distorted grid.
            - map_y: A 2D array of float32 values representing the y-coordinates
              of the distorted grid.

    Note:
        - The function generates a grid where each cell can be distorted independently.
        - The distortion is controlled by the steps_x and steps_y parameters, which
          determine how much each grid line is shifted.
        - The resulting map_x and map_y can be used directly with cv2.remap() to
          apply the distortion to an image.
        - The distortion is applied smoothly across each grid cell using linear
          interpolation.

    Example:
        >>> image_shape = (100, 100)
        >>> steps_x = [1.1, 0.9, 1.0, 1.2, 0.95, 1.05]
        >>> steps_y = [0.9, 1.1, 1.0, 1.1, 0.9, 1.0]
        >>> num_steps = 5
        >>> map_x, map_y = generate_grid(image_shape, steps_x, steps_y, num_steps)
        >>> distorted_image = cv2.remap(image, map_x, map_y, cv2.INTER_LINEAR)
    NrA   g        )rj   r   rk   rd  rD   r   ZlinspaceZmeshgrid)rh   r  r  r  rm   rn   x_stepr   previdxsteprX   startendcury_stepr   rp   rS   rS   rT   r@   	  s,    /zdict[str, np.ndarray])rh   r  x_stepsy_stepsrG   c                 C  s   | \}}|| }t ||d | ||  }|d  || 9  < || }t ||d | ||  }	|d  |	| 9  < |t||  }
|t||  }t||
t|  }t||t|  }||dS )NrI   r   )r  r  )r   r   r  rj   r   sum)rh   r  r  r  rm   rn   r  Zlast_x_stepr  Zlast_y_stepZtxtyrS   rS   rT   normalize_grid_distortion_steps	  s    r  )npartsrG   c                   s,   t | |\ t fddt|D S )a  Generates an array of nearly equal integer intervals that sum up to `n`.

    This function divides the number `n` into `parts` nearly equal parts. It ensures that
    the sum of all parts equals `n`, and the difference between any two parts is at most one.
    This is useful for distributing a total amount into nearly equal discrete parts.

    Args:
        n (int): The total value to be split.
        parts (int): The number of parts to split into.

    Returns:
        np.ndarray: An array of integers where each integer represents the size of a part.

    Example:
        >>> almost_equal_intervals(20, 3)
        array([7, 7, 6])  # Splits 20 into three parts: 7, 7, and 6
        >>> almost_equal_intervals(16, 4)
        array([4, 4, 4, 4])  # Splits 16 into four equal parts
    c                   s    g | ]}|k r d  n qS rI   rS   )r   r  Z	part_size	remainderrS   rT   r   
  s     z*almost_equal_intervals.<locals>.<listcomp>)divmodrj   r   r  )r  r  rS   r  rT   almost_equal_intervals	  s    r  znp.random.RandomState | None)rk  	divisionsr;  rG   c                 C  s,   t | |}tj||d}tt|ddS )a  Generate shuffled splits for a given dimension size and number of divisions.

    Args:
        size (int): Total size of the dimension (height or width).
        divisions (int): Number of divisions (rows or columns).
        random_state (Optional[np.random.RandomState]): Seed for the random number generator for reproducibility.

    Returns:
        np.ndarray: Cumulative edges of the shuffled intervals.
    r;  r   )r  r   shufflerj   insertZcumsum)rk  r  r;  Z	intervalsrS   rS   rT   generate_shuffled_splits
  s    
r  )rh   gridr;  rG   c                   sZ   |\}t | d |d | t | d |d | fddt|D }tj|tjdS )a  Splits an image shape into a uniform grid specified by the grid dimensions.

    Args:
        image_shape (tuple[int, int]): The shape of the image as (height, width).
        grid (tuple[int, int]): The grid size as (rows, columns).
        random_state (Optional[np.random.RandomState]): The random state to use for shuffling the splits.
            If None, the splits are not shuffled.

    Returns:
        np.ndarray: An array containing the tiles' coordinates in the format (start_y, start_x, end_y, end_x).

    Note:
        The function uses `generate_shuffled_splits` to generate the splits for the height and width of the image.
        The splits are then used to calculate the coordinates of the tiles.
    r   rI   c              	     s>   g | ]6}t D ](} | |  |d   |d   fqqS r  )r  )r   r  rm  Zheight_splitsZn_colsZwidth_splitsrS   rT   r   <
  s   
 z&split_uniform_grid.<locals>.<listcomp>r   )r  r  rj   r   Zint16)rh   r  r;  Zn_rowsZtilesrS   r  rT   split_uniform_grid"
  s    r  )rh   r%   r;  rG   c                 C  s   | d d \}}t jd|d|d}tt|d}d|d  |d< d|d  |d< d|d  |d< |d d df  |9  < |d d d	f  |9  < |S )
NrA   r   )r   rA   r  g{Gz?r|   r   )rB   rI   rI   )r   normalrj   modabs)rh   r%   r;  rm   rn   r   rS   rS   rT   generate_perspective_pointsE
  s    r  )ptsrG   c                 C  s   t t| dd d} | d d }| dd  }|d d |d d k rP|\}}n|\}}|d d |d d k rz|\}}n|\}}t j||||gt jdS )Nc                 S  s   | d S )Nr   rS   rW   rS   rS   rT   rY   ]
  rZ   zorder_points.<locals>.<lambda>)keyrA   r   rI   r   )rj   r   sortedrk   )r  r*  r+  tlbltrbrrS   rS   rT   order_points\
  s    

r  ztuple[np.ndarray, int, int])r   rG   c           
      C  s   | \}}}}d
ddddddd}t ||||||}t ||||||}t|t| }}tjddg|dg||gd|ggtjd	}t| |}	|	||fS )NrA   rC   rD   rw   )dim1dim2min_sizerG   c                 S  s   t t | | d }||k r|| d }| | |k  |7  < || |k  |8  < | | |k  |8  < || |k  |7  < |}|S r]   )rj   r   r  )r  r  r  rk  Z	step_sizerS   rS   rT   adjust_dimensionq
  s    z4compute_perspective_params.<locals>.adjust_dimensionr   r   )rA   )r   rD   rj   r   rk   r   rX  )
r   Ztop_leftZ	top_rightZbottom_rightZbottom_leftr  r   r   r<  r~   rS   rS   rT   compute_perspective_paramsn
  s    (r  )r~   r   rG   c           	      C  s   |d d \}}t jddg|dg||gd|ggt jd}tt |g| d }||jddd8 }t j|dd}t||}|jdd\}}|t	|t	|fS )NrA   r   r   T)r   Zkeepdims)Zdecimalsr   )
rj   r   rk   r   r   r   ZaroundrX  r   rD   )	r~   r   rm   rn   Zrectr<  Zmatrix_expandedr   r   rS   rS   rT   expand_transform
  s    (r  )N)F)NN)N)F)FFr   )F)FFr   )N)N)N)
__future__r   r   typingr   r   r   r   r   r   r	   r   Znumpyrj   Zskimage.transformr   Zalbucorer
   r   r   r   r   r   Zalbumentationsr   Z"albumentations.augmentations.utilsr   r   Zalbumentations.core.bbox_utilsr   r   r   r   Zalbumentations.core.typesr   r   r   r   r   r   r   __all__r   rM   rN   r6   r4   r7   r5   r#   r2   r3   r$   r%   r   r&   r'   r(   r)   r   r*   r   r+   r   r,   r   r   r   r   r   r-   r.   r  r  r/   r  r  r1   r  r0   r  r:   r<   r  r8   r;   r=   r   r9   r!   r   r,  r"   r   r   r    r?  rE  r   rA  r   r   rQ  rb  re  rf  rn  rq  rp  ro  r   rs  rt  rz  r{  r  r  r  r>   r?   r@   r  r  r  r  r  r  r  r  rS   rS   rS   rT   <module>   s  $ $'*--05(	! N6H(7O72 W =1!"$'A  C*V 
H  >I"&