
    ^j                    P   d Z ddlmZ ddlZddlmZ ddlmZ ddlm	Z	 	 ddl
ZddlZddlZddlZddlmZ ddlmZ dd	lmZ dd
lmZ ddlmZ ddlmZ  e       Z G d de      Zh dZ eh d      Z ddZ!ddZ" ed      dd       Z#ddZ$ G d d      Z%y# e$ r dZY w xY w)z7Transforms and data augmentation for both image + bbox.    )annotationsN)Sequence)	lru_cache)Any)Image)Tensor)	Normalize)#filter_keypoint_hflip_augmentations)box_xyxy_to_cxcywh)
get_loggerc                  .    e Zd Z	 	 d	 	 	 	 	 ddZdddZy)r	   c                &    t        ||      | _        y N)_TVNormalize
_normalize)selfmeanstds      e/var/www/ramen.bs-engineer-server.com/venv/lib/python3.12/site-packages/rfdetr/datasets/transforms.py__init__zNormalize.__init__+   s    
 'tS1    Nc                j   | j                  |      }||dfS |j                         }|j                  dd \  }}d|v rA|d   }t        |      }|t	        j
                  ||||gt        j                        z  }||d<   d|v r.|d   j                         }|d   |z  |d<   |d   |z  |d<   ||d<   ||fS )a  Normalize image and convert target coordinates to relative format.

        Applies ImageNet-style channel normalization to the image, then converts
        bounding boxes from absolute xyxy pixel coordinates to normalized cxcywh
        format (divided by ``[w, h, w, h]``) and scales keypoint x/y by image
        width/height respectively.

        Args:
            image: CHW float tensor to normalize.
            target: Optional dict with keys ``"boxes"`` (xyxy pixel coords,
                shape ``[N, 4]``) and/or ``"keypoints"`` (shape ``[N, K, 3]``
                where the third channel is visibility). Mutated copy returned;
                original is not modified.

        Returns:
            Tuple of ``(normalized_image, target)`` where ``target`` has boxes
            in normalized cxcywh format and keypoints scaled to ``[0, 1]``, or
            ``(normalized_image, None)`` when ``target`` is ``None``.

        Examples:
            >>> import torch
            >>> normalize = Normalize()
            >>> img = torch.zeros(3, 64, 64)
            >>> out_img, out_tgt = normalize(img, None)
            >>> out_tgt is None
            True
        Nboxesdtype	keypoints).r   ).   )r   copyshaper   torchtensorfloat32clone)r   imagetargethwr   r   s          r   __call__zNormalize.__call__2   s    8 &>$;{{231f7OE&u-EELL!Q1U]]KKE#F7O& {+113I )& 1A 5If )& 1A 5If"+F;f}r   ))g
ףp=
?gv/?gCl?)gZd;O?gy&1?g?)r   tuple[float, ...]r   r*   returnNoner   )r%   r   r&   dict[str, Any] | Noner+   z$tuple[Tensor, dict[str, Any] | None])__name__
__module____qualname__r   r)    r   r   r	   r	   *   s0     #8!622 2 
	2+r   r	   >&   D4PadCropFlipAffineResizeRotate	Downscale	Transpose
CenterCrop
CropAndPad
RandomCrop
SafeRotatePadIfNeededPerspectiveRandomScaleVerticalFlipGridDistortionHorizontalFlipLongestMaxSizeRandomRotate90SquareSymmetryPiecewiseAffineRandomSizedCropSmallestMaxSizeThinPlateSplineElasticTransformShiftScaleRotateGridElasticDeformOpticalDistortionRandomGridShuffleRandomResizedCropBBoxSafeRandomCropRandomCropNearBBoxRandomCropFromBordersRandomSizedBBoxSafeCropAtLeastOneBBoxRandomCropCropNonEmptyMaskIfExists>   OneOfSomeOf
Sequentialc                    t        |       j                  t        v ryt        | d      rt	        d | j
                  D              S y)a  Return True if transform (or any nested transform) affects spatial coordinates.

    For container transforms such as ``A.OneOf`` or ``A.Sequential``, returns ``True`` when *any* nested transform is
    geometric so that bounding-box handling is enabled for the whole container.

    Args:
        transform: Albumentations transform to inspect.

    Returns:
        ``True`` if the transform modifies spatial layout; ``False`` otherwise.

    Examples:
        >>> from albumentations import GaussianBlur, HorizontalFlip, OneOf
        >>> _is_geometric_transform(HorizontalFlip())
        True
        >>> _is_geometric_transform(GaussianBlur())
        False
        >>> _is_geometric_transform(OneOf([HorizontalFlip(), GaussianBlur()]))
        True
    T
transformsc              3  2   K   | ]  }t        |        y wr   )_is_geometric_transform).0ts     r   	<genexpr>z*_is_geometric_transform.<locals>.<genexpr>   s     L!*1-Ls   F)typer.   GEOMETRIC_TRANSFORMShasattranyr\   )	transforms    r   r^   r^      s>    * I#77y,'Ly7K7KLLLr   c           	     .   t         t        d      | t        v r|j                  dg       }t	        |t
              s$t        d|  dt        |      j                         g }|D ]  }t	        |t              rt        |      dk7  rt        d|      t        t        |j                                     \  }}t	        |t              s$t        d| d	t        |      j                         |j                  t        ||              | d
k(  r;|st        d      |j                         D ci c]  \  }}|dvs|| }	}}d|	d<   n\| dk(  r.|j                         D ci c]  \  }}|dvs|| }	}}d|	d<   n)|j                         D ci c]  \  }}|dk7  s|| }	}}t!        t         | d      }
|
t        d|        |
dd|i|	S t!        t         | d      }|t        d|        |di t#        | ||      S c c}}w c c}}w c c}}w )a  Build a single Albumentations transform from its name and parameter dict.

    Handles container transforms (``OneOf``, ``SomeOf``, ``Sequential``) by recursively building the nested
    ``transforms`` list.  Leaf transforms are instantiated directly from the ``albumentations`` namespace.

    Both ``OneOf`` and ``Sequential`` always fire (``p=1.0`` is forced, ignoring any user-supplied ``p``).  For
    ``OneOf``, which child is applied is determined by the children's own ``p`` values; at least one nested transform is
    required.  ``Sequential`` runs all transforms in order.

    Args:
        name: Transform name (e.g. ``"HorizontalFlip"``, ``"OneOf"``).
        params: Parameter dictionary for the transform.  For container transforms
            the dict must contain a ``"transforms"`` key whose value is a list of single-key dicts ``{name: params}``.

    Returns:
        Instantiated Albumentations transform.

    Raises:
        ImportError: If Albumentations is not installed.
        ValueError: If ``name`` is unknown or ``params`` is malformed.

    Examples:
        >>> from albumentations import HorizontalFlip, OneOf
        >>> t = _build_albu_transform("HorizontalFlip", {"p": 0.5})
        >>> isinstance(t, HorizontalFlip)
        True
        >>> container = _build_albu_transform(
        ...     "OneOf",
        ...     {"transforms": [{"HorizontalFlip": {"p": 1.0}}, {"VerticalFlip": {"p": 1.0}}]},
        ... )
        >>> isinstance(container, OneOf)
        True
    NAlbumentations is required to build RF-DETR dataset transforms. Install the project dependencies with `uv sync --all-groups` or install albumentations.r\   'z!.transforms' must be a list, got r   z;Each nested transform entry must be a single-key dict, got z!Parameters for nested transform 'z' must be a dict, got rX   z''OneOf' requires at least one transform)r\   pg      ?rj   rZ   z"Unknown Albumentations container: z"Unknown Albumentations transform: r1   )albImportErrorALBUMENTATIONS_CONTAINERSget
isinstancelist
ValueErrorrb   r.   dictlennextiteritemsappend_build_albu_transformgetattr_normalize_albu_params)nameparams
raw_nestednested_transformsentrynested_namenested_paramskvother_paramscontainer_clsaug_clss               r   rx   rx      sL   D {f
 	

 ((ZZb1
*d+q&GZHXHaHaGbcdd68 		XEeT*c%jAo #^_d^g!hii)-d5;;=.A)B&KmT2 7} E.778:  $$%:;%VW		X 7?$ !JKK-3\\^\TQqH[?[AqD\L\ #L\!-3\\^\TQqH[?[AqD\L\ #L-3\\^QTQqL?PAqDQLQT40 A$JKKJ(9J\JJc4&G=dXFGGC+D&'BCC! ] ] Rs$   /H<H"H/HHH)maxsizec                \    t        j                  | j                        }d|j                  v S )a  Return whether ``RandomSizedCrop`` expects a ``size`` keyword.

    The Albumentations 2.x API changed ``RandomSizedCrop`` from separate ``height``/``width`` parameters to a single
    ``size=(height, width)`` parameter. This helper caches the signature check per class so repeated transform
    construction during dataset setup does not repeat introspection.

    Args:
        aug_cls: Albumentations transform class to inspect.

    Returns:
        ``True`` when the class accepts a ``size`` keyword argument; otherwise ``False``.
    size)inspect	signaturer   
parameters)r   r   s     r   "_random_sized_crop_uses_size_paramr     s+     !!'"2"23IY))))r   c                (   t        |      }| dk7  r|S t        |      }|rd|v }d|v }d|v }|r&|j                  dd       |j                  dd       |S |r-|r+|j                  d      }|j                  d      }	||	f|d<   |S ||k7  r|r|sdnd}
t        d|
 d      |S |sod|v rk|j	                  d      }t        |t              rJt        |      dk(  r<|j                  d|d	          |j                  d|d
          |j                  dd       |S )a  Normalize transform params across Albumentations API variations.

    Currently this adapts ``RandomSizedCrop`` arguments so a config using ``height``/``width`` works on Albumentations
    2.x and a config using ``size=(height, width)`` still works on Albumentations 1.x.

    Args:
        name: Albumentations transform name.
        params: Raw transform parameter mapping from config.
        aug_cls: Albumentations transform class that will be instantiated.

    Returns:
        A normalized copy of ``params`` suitable for the installed Albumentations version.

    Examples:
        >>> class CropV2:
        ...     def __init__(self, *, size, min_max_height): ...
        >>> _normalize_albu_params(
        ...     "RandomSizedCrop",
        ...     {"min_max_height": [384, 600], "height": 640, "width": 640},
        ...     CropV2,
        ... )
        {'min_max_height': [384, 600], 'size': (640, 640)}
    rI   r   heightwidthNzRandomSizedCrop for the installed Albumentations version expects 'size=(height, width)'. Received only one of 'height'/'width' without 'size' (missing 'z').   r   r   )	rr   r   poprq   rn   ro   r   rs   
setdefault)r{   r|   r   normalized_params	uses_sizehas_size
has_height	has_widthr   r   missingr   s               r   rz   rz     s^   0 V    27;I ..!22
00	 !!(D1!!'40$$)&**84F%))'2E)/f%$$" ",Ig8G,,39C9  !  #44 $$V,dH%#d)q.((47;(($q': !!&$/r   c                     e Zd ZdZdddZddZedd       Zedd       Ze	 	 	 	 	 	 dd       Z	edd       Z
e	 	 d	 	 	 	 	 	 	 	 	 	 	 dd	       Zedd
       Zedd       Z	 	 	 	 	 	 	 	 ddZ	 	 	 	 	 	 ddZe	 d	 	 	 	 	 dd       Zy)AlbumentationsWrappera  Wrapper to apply Albumentations transforms to (image, target) tuples.

    This wrapper integrates Albumentations transforms with RF-DETR's data pipeline, automatically handling bounding box
    and segmentation mask transformations for geometric augmentations while preserving the (image, target) tuple format.

    The wrapper automatically detects transform types:
    - **Geometric transforms** (flips, rotations, crops): Bounding boxes and instance
      masks are transformed along with the image to maintain correct object localization.
    - **Pixel-level transforms** (blur, color adjustments, noise): Bounding boxes and
      masks remain unchanged as only pixel values are modified.

    Detection checks the transform class name against ``GEOMETRIC_TRANSFORMS`` and recursively inspects nested container
    transforms (for example ``OneOf`` and ``Sequential``). For geometric transforms, bbox_params are automatically
    configured to handle coordinate transformations, clip boxes to image boundaries, and remove invalid boxes.

    Args:
        transform: Albumentations transform to apply (e.g., alb.HorizontalFlip, alb.GaussianBlur).
        keypoint_flip_pairs: Joint index pairs for left/right swapping after a horizontal flip.
            ``None`` (default) means a detection pipeline -- no keypoint handling.
            An empty list ``[]`` marks a keypoint pipeline without semantic flip
            pairs, so horizontal-flip transforms should have been stripped from
            config before this point.

    Examples:
        >>> from albumentations import GaussianBlur, HorizontalFlip
        >>> # Geometric transform - automatically transforms boxes
        >>> wrapper = AlbumentationsWrapper(HorizontalFlip(p=1.0))
        >>> image = Image.new("RGB", (300, 400))
        >>> target = {"boxes": torch.tensor([[10, 20, 100, 200]]), "labels": torch.tensor([1])}
        >>> aug_image, aug_target = wrapper(image, target)

        >>> # Pixel-level transform - automatically preserves boxes
        >>> wrapper = AlbumentationsWrapper(GaussianBlur(p=1.0))
        >>> aug_image, aug_target = wrapper(image, target)

    Note:
        For custom geometric transforms, add the transform class name to the GEOMETRIC_TRANSFORMS set at module level.
    Nc           	        t        |      | _        t        |xs g       | _        | j                  rt	        | j                        }|r%t        t        d      st        j                  d       |r t        t        d      rt        j                  nt        j                  } ||gt        j                  dddgdd      t        j                  d	g d
d            | _        y t        j                  |g      | _        y )NReplayComposezalbumentations.ReplayCompose not available; horizontal-flip keypoint slot swapping is disabled. Upgrade albumentations to >=1.3.
pascal_voccategory_idsidxsg        T)formatlabel_fieldsmin_visibilityclipxy)keypoint_instance_idskeypoint_point_idskeypoint_visibilityF)r   r   remove_invisible)bbox_paramskeypoint_params)r^   _is_geometricrp   _keypoint_flip_pairsboolrd   rk   loggerwarningr   Compose
BboxParamsKeypointParamsrf   )r   rf   keypoint_flip_pairsneeds_replaycompose_clss        r   r   zAlbumentationsWrapper.__init__  s    4Y?$()<)B$C!   9 9:LGC$AR 1=oA^#++ehepepK(NN'"0&!9#&	 !$ 2 2!g%*!DN" ![[)5DNr   c                   d}t        | j                  t        j                        r:| j                  j                  D ]   }t        |t        j
                        s|} n2 n0t        | j                  t        j
                        r| j                  }|t        j                  |       S | j                  rdnd}| j                  j                   d| d| dS )zReturn a readable string representation of the wrapper.

        Returns:
            Representation including the wrapped transform and type.
        N	geometriczpixel-levelz(transform=z, type=))ro   rf   rk   r   r\   BasicTransformobject__repr__r   	__class__r.   )r   rf   	candidatetransform_types       r   r   zAlbumentationsWrapper.__repr__  s     	dnnckk2!^^66 	i););< )I (:(:;I??4(((,(:(:..))*+i[GWWXYYr   c                   t        j                  |       r| j                         j                         nt	        j
                  |       }t        |j                        dk7  s|j                  d   dk7  rt        d|j                         |S )zConvert boxes to numpy array and validate shape.

        >>> import torch
        >>> boxes = torch.tensor([[10.0, 20.0, 30.0, 40.0]])
        >>> AlbumentationsWrapper._boxes_to_numpy(boxes).shape
        (1, 4)
        r   r      z"boxes must have shape (N, 4), got 	r!   	is_tensorcpunumpynparrayrs   r    rq   )r   boxes_nps     r   _boxes_to_numpyz%AlbumentationsWrapper._boxes_to_numpy  sk     +0//%*@599;$$&bhhuox~~!#x~~a'8A'=A(..AQRSSr   c                z   t        j                  |       r| j                         j                         nt	        j
                  |       }t        |j                        dk7  s|j                  d   dk7  rt        d|j                         |j                  d   |k7  rt        d| d|j                  d          |S )zConvert keypoints to numpy array and validate shape.

        >>> import torch
        >>> keypoints = torch.tensor([[[10.0, 20.0, 2.0]]])
        >>> AlbumentationsWrapper._keypoints_to_numpy(keypoints, 1).shape
        (1, 1, 3)
           r   z)keypoints must have shape (N, K, 3), got r   z6keypoints first dimension must match number of boxes (z), got r   )r   	num_boxeskeypoints_nps      r   _keypoints_to_numpyz)AlbumentationsWrapper._keypoints_to_numpy  s     38//)2Ly}},,.RTRZRZ[dRe|!!"a'<+=+=a+@A+EHI[I[H\]^^a I-HSZ[g[m[mno[pZqr  r   c                h   g }g }g }g }|D ]  }t        | |         D ]  \  }}|j                         \  }	}
}|j                  t        |	      t        |
      f       |j                  t        |             |j                  t        |             |j                  t        |               ||||dS )a<  Flatten per-instance keypoints into Albumentations keypoint fields.

        >>> keypoints = np.array([[[10.0, 20.0, 2.0], [0.0, 0.0, 0.0]]], dtype=np.float32)
        >>> fields = AlbumentationsWrapper._build_albu_keypoints(keypoints, [0])
        >>> fields["keypoints"]
        [(10.0, 20.0), (0.0, 0.0)]
        r   r   r   r   )	enumeratetolistrw   float)r   r   albu_keypointsinstance_ids	point_ids
visibilityoriginal_idx	point_idxpointxyvisibles               r   _build_albu_keypointsz+AlbumentationsWrapper._build_albu_keypoints  s     57$&!#	"$
  	2L$-l<.H$I 2 	5 %1g%%uQxq&:;##E,$78  y!12!!%.12	2 (%1"+#-	
 	
r   c                   t        | t              sy| j                  d      }t        |t              rt	        d |D              S | j                  dd      syt        | j                  dd            j                  dd      d	   }|d
k(  ry|dk(  r3| j                  d      xs i }t        |j                  dd	            dk(  S |dv r2| j                  d      xs i }t        |j                  d            dk(  S y)a  Return whether Albumentations replay metadata applied a horizontal flip.

        Args:
            replay: ``ReplayCompose`` metadata from an Albumentations call.

        Returns:
            ``True`` only when a horizontal mirror transform was actually applied.
        Fr\   c              3  F   K   | ]  }t         j                  |        y wr   )r    _replay_contains_horizontal_flip)r_   rf   s     r   ra   zIAlbumentationsWrapper._replay_contains_horizontal_flip.<locals>.<genexpr>  s     u]f,MMiXus   !applied__class_fullname__ .r   rD   Tr5   r|   axis>   r2   rG   group_elementr'   )ro   rr   rn   rp   re   strrsplitint)replayr\   transform_namer|   s       r   r   z6AlbumentationsWrapper._replay_contains_horizontal_flip  s     &$'ZZ-
j$'ujtuuuzz)U+VZZ(<bABII#qQRTU--V#ZZ)/RFvzz&"-.!3355ZZ)/RFvzz/23s::r   c           
     P   |j                   d   }t        j                  t        |      |dft        j                        }t        |      D ci c]  \  }}t        |      | }	}}| d   j                   dd \  }
}| j                  dg       }|r| j                  dg       }| j                  d	g       }| j                  d
g       }t        j                  |D cg c]  }t        |d         t        |d         f  c}t        j                        }t        j                  |D cg c]  }|	j                  t        |      d       c}t        j                        }t        j                  |D cg c]  }t        |       c}t        j                        }t        j                  |D cg c]  }t        |       c}t        j                        }|dk\  |dk\  z  ||k  z  |dkD  z  |dddf   dk\  z  |dddf   |k  z  |dddf   dk\  z  |dddf   |
k  z  }t        j                  |      d   }t        |      dkD  r(t        j                  ||   ||   g      |||   ||   f<   t        j                  |t        j                        }|rs|rq|j                   d   }t        j                   |      }t#        dt        |      dz
  d      D ]%  }||   ||dz      }}||k  s||k  s|||<   |||<   ' |dd|ddf   }|S c c}}w c c}w c c}w c c}w c c}w )a  Rebuild transformed keypoints and keep them synchronized with kept boxes.

        Args:
            augmented: Augmented output dict from Albumentations.
            kept_idxs: Original instance indices of surviving boxes.
            keypoints_np: Original keypoint array, shape (N_orig, K, 3).
            flip_pairs: Flat list of paired joint indices ``[a0, b0, a1, b1, ...]``
                to swap when a horizontal flip is detected.  Each consecutive pair
                ``(flip_pairs[i], flip_pairs[i+1])`` names two joints that are
                left/right mirrors of each other (e.g., left_eye, right_eye).
            did_flip: Whether a horizontal flip was applied this step.

        Returns:
            Keypoint tensor of shape ``(len(kept_idxs), K, 3)``.
        r   r   r   r%   Nr   r   r   r   r   r   r   )r    r   zerosrs   r#   r   r   rn   asarrayr   r   intpwherecolumn_stackr!   	as_tensorarangerange)	augmented	kept_idxsr   
flip_pairsdid_flipnum_keypointskeypoints_outpositionr   kept_position_by_idxr   r   albu_kpsinst_idspt_idsr   rj   r   iiinstptidr   visvalid	valid_idxresultnum_kptspermiaibis                                  r   _rebuild_keypoints_from_albuz2AlbumentationsWrapper._rebuild_keypoints_from_albu&  s   . %**1-#i.-!C2::VZcdmZno@V,L 18 ;oo!'*00!4==b1 }}%<bAH]]#7<Fmm$92>GJAeAaDk51;7JRTR\R\]B88XVr155c"grBV^`^e^efD88V4SV4BGGDD**81eAh8

KC19-') 7 ad8q=	"
 ad8e#% ad8q=" ad8f$&  *I9~!BD//SUV_S`befobpRqBrd9otI>?emmD
||AH<<)D1c*o115 "#A
1q5(9B=R(]!DH!DH	"
 AtQJ'FO  p KV48s   L#L("L4L0L#c                   h d}i }| j                         D ]  \  }}||v rt        j                  |      rI|j                  dk\  s0|j                  d   |k(  sC|j                  dg|j                  dd       ||<   it        |t              szt        |t        t        f      rt        |      |k(  sg ||<    |S )a;  Clear all per-instance fields when no boxes remain.

        >>> import torch
        >>> target = {"area": torch.tensor([100, 200]), "iscrowd": torch.tensor([0, 1])}
        >>> cleared = AlbumentationsWrapper._clear_per_instance_fields(target, 2)
        >>> cleared["area"].shape
        torch.Size([0])
        >   r   r   labelsimage_id	orig_sizer   r   N)rv   r!   r   ndimr    	new_emptyro   r   r   bytesrs   )r&   r   global_fieldsr  keyvalues         r   _clear_per_instance_fieldsz0AlbumentationsWrapper._clear_per_instance_fieldsh  s     M ,,. 	%JCm#u%::?u{{1~'B"'//12Gu{{122G"HF3KE8,ZU|5Tu:*"$F3K	% r   c                   h d}i }t        j                  |t         j                        }| j                         D ]  \  }}||v rt        j                  |      r,|j
                  dk\  s0|j                  d   |k(  sC||   ||<   Lt        |t              s]t        |t        t        f      rtt        |      |k(  s|D cg c]  }||   	 c}||<    |S c c}w )aN  Filter per-instance fields to match kept box indices.

        >>> import torch
        >>> target = {"area": torch.tensor([100, 200, 300]), "iscrowd": torch.tensor([0, 0, 1])}
        >>> filtered = AlbumentationsWrapper._filter_per_instance_fields(target, 3, [0, 2])
        >>> filtered["area"].tolist()
        [100, 300]
        >   r   r   r  r  r  r   r   r   )r!   r   longrv   r   r  r    ro   r   r   r  rs   )	r&   r   r   r  r  kept_idxs_tensorr  r  r  s	            r   _filter_per_instance_fieldsz1AlbumentationsWrapper._filter_per_instance_fields  s     M ??9EJJG ,,. 	@JCm#u%::?u{{1~'B"'(8"9F3KE8,ZU|5Tu:*5>"?58"?F3K	@  #@s   Cc                	   | j                  |d         }|j                  d   }t        t        |            }d}d|v r|d   }t	        j
                  |      r|j                         j                         nt        j                  |      }	|	j                  dk7  rt        d|	j                         |	j                  t        j                  d      }	|	D 
cg c]  }
|
 }}
d}d	|v r| j                  |d	   |      }|dkD  r|ddd
f   |dddf   kD  |dddf   |dddf   kD  z  }|j                         sOt        j                   |      d   j#                         }||   }|D cg c]  }||   	 }}|D cg c]  }||   	 }}||||d}|t%        |      dkD  r||d<   |"|j'                  | j)                  ||             n|j'                  g g g g d        | j*                  di |}|j-                         }|d   }|j/                  d|      D cg c]  }t1        |       }}t%        |      dk(  rt	        j2                  dt        j4                        |d<   t	        j2                  dt        j6                        |d<   |j'                  | j9                  ||             d|v rM|d   j                  dd
 \  }}t	        j2                  d||ft        j:                        |d<   nt	        j<                  |t        j4                        j?                  dd      |d<   t	        j@                  |d   t        j6                        |d<   |j'                  | jC                  |||             d|v r/|d   }|ddd
f   |dddf   z
  |dddf   |dddf   z
  z  |d<   |Q| jD                  r | jG                  |j/                  d            nd}| jI                  |||| jD                  |      |d	<   tK        jL                  |d         }|d|v r|d   j                  dd
 \  }}|d   }|D cg c]  }|t1        |          }}t%        |      dk(  r/t	        j2                  d||ft        j:                        |d<   ||fS t	        j<                  t        jN                  |      t        j:                        |d<   ||fS c c}
w c c}w c c}w c c}w c c}w )a  Apply geometric transform to image with boxes and optionally masks.

        Converts data to Albumentations format, applies the transform, and converts back to RF-DETR format. Handles box
        removal and per-instance field filtering.

        Args:
            image_np: Numpy array of image in HWC format.
            target: Target dictionary with 'boxes' and optionally 'masks'.
            labels: List of category labels.

        Returns:
            Tuple of (transformed PIL Image, transformed target dict).

        >>> import torch
        >>> from albumentations import HorizontalFlip
        >>> wrapper = AlbumentationsWrapper(HorizontalFlip(p=1.0))
        >>> img = np.ones((100, 100, 3), dtype=np.uint8)
        >>> tgt = {"boxes": torch.tensor([[10, 20, 30, 40]]), "labels": torch.tensor([1])}
        >>> img_out, tgt_out = wrapper._apply_geometric_transform(img, tgt, [1])
        >>> tgt_out["boxes"].shape
        torch.Size([1, 4])
        r   r   Nmasksr   z%masks must have shape (N, H, W), got F)r   r   r   r   )r%   bboxesr   r   r   r   r   )r   r   r   )r   r  r%   r   r   r   arear   )r   r   r1   )(r   r    rp   r   r!   r   r   r   r   r   r  rq   astypeuint8r   allr   r   rs   updater   rf   r   rn   r   r   r#   r  r  r   r   reshaper"   r  r   r   r  r   	fromarraystack)r   image_npr&   r  r   r   r   
masks_listr  masks_npmaskr   
valid_maskvalid_positionsr  transform_kwargsr   
target_out
bboxes_augidxr   
aug_height	aug_widthr   r   	image_outr   r   	masks_augs                                r   _apply_geometric_transformz0AlbumentationsWrapper._apply_geometric_transform  s   2 ''w8NN1%	E)$%
f7OE.3ooe.Duyy{((*"((SX/H}}! #HHX!YZZrxxe<H+344$4J4& 33F;4GSL
 q="1a4.8AqD>9hq!tnxXY[\X\~>]^J>>#"$((:"6q"9"@"@"B#J/-<=&)== *99AQ99%-SYcgh!c*o&9(2W%###D$>$>|T$RS##!#-/*,+-	 #DNN6%56	%+[[]
x(
)2vt)DE#SXE	Ez?a"'++fEMM"JJw#(;;t5::#FJx d==fiPQ& (1'(:(@(@!(D%
I&+kk1j)2LTYT^T^&_
7#"'//*EMM"R"Z"Z[]_`"aJw#(<<	.0IQVQ[Q[#\Jx d>>vyR[\] #"7+&+AqDkE!Q$K&?E!Q$KRWXY[\X\R]D]%^
6"' 00 99)--:QR 
 +/*K*K #88% +L +
;' OOIg$67	!g&:%g.44Ra8MFE!'*I4=>q3q6*>I>9~"&+kk1fe2DEJJ&W
7# *$$ ',oobhhy6IQVQ[Q[&\
7#*$$Y 5 > :& FH ?s   	S(S:SS:S!c           
        |gt        j                  |      }| j                  r| j                  |g g g g g g g       }n| j                  |      }t	        j
                  |d         dfS t        |t              st        dt        |             d|vrt        d      t        j                  |      }t        j                  |d         r!|d   j                         j                         nt        |d         }| j                  rd|v rd	|vrt         j#                  d
       | j                  rd	|v r| j%                  |||      \  }}n:| j                  |      }t	        j
                  |d         }|j'                         }d|v r9|j(                  \  }}	t        j*                  |	|gt        j,                        |d<   ||fS )a	  Apply the Albumentations transform to image and target.

        This method handles the data format conversion between RF-DETR and Albumentations:
        1. Converts PIL Image to numpy array (required by Albumentations)
        2. Converts PyTorch tensors to numpy/lists (required by Albumentations)
        3. Applies the transform
        4. Converts results back to PIL Image and PyTorch tensors

        For geometric transforms with bounding boxes, this method also:
        - Validates box shapes and coordinates
        - Handles boxes that may be removed by the transform (e.g., cropped out)
        - Ensures labels stay synchronized with their corresponding boxes
        - Transforms masks when present to stay aligned with the image

        Args:
            image: Input PIL Image in RGB format.
            target: Target dictionary containing:
                - 'labels': PyTorch tensor of shape (N,) with class labels
                - 'boxes' (optional): PyTorch tensor of shape (N, 4) in (x1, y1, x2, y2) format
                - 'masks' (optional): PyTorch tensor of shape (N, H, W) with instance segmentation masks.
                  For geometric transforms, masks are transformed alongside boxes to maintain alignment. Requires
                  'boxes' to be present; a warning is logged if masks exist without boxes.
                Pass ``None`` for inference scenarios where no ground-truth annotations are available.

        Returns:
            Tuple of (transformed_image, transformed_target):
                - transformed_image: PIL Image after augmentation
                - transformed_target: Dictionary with augmented boxes and labels, or ``None`` if
                  ``target`` was ``None``.

        Raises:
            TypeError: If target is not a dictionary (and not None).
            KeyError: If target doesn't contain 'labels' key.
            ValueError: If boxes don't have shape (N, 4).

        Examples:
            >>> from albumentations import HorizontalFlip
            >>> wrapper = AlbumentationsWrapper(HorizontalFlip(p=1.0))
            >>> image = Image.new('RGB', (100, 100))
            >>> target = {"boxes": torch.tensor([[10, 20, 90, 80]]), "labels": torch.tensor([1])}
            >>> aug_image, aug_target = wrapper(image, target)
        N)r%   r   r   r   r   r   r   r   )r%   r%   z!target must be a dictionary, got r  z target must contain 'labels' keyr  r   zAlbumentationsWrapper: geometric transform requested with 'masks' but without 'boxes'. Masks will not be geometrically transformed because bounding boxes are missing.r   r   )r   r   r   rf   r   r'  ro   rr   	TypeErrorrb   KeyErrorr!   r   r   r   rp   r   r   r7  r   r   r   int64)
r   r%   r&   r)  r   r  r5  r0  r   r   s
             r   r)   zAlbumentationsWrapper.__call__  s   \ >xxH!! NN"!# *,')(* + 		 !NNN:	??9W#56<< &$'?V~NOO6!=>> 88E? 5:OOF8DT4U!%%'..0[_`fgo`p[q 'V"3v8MNNb 'V"3$($C$CHfV\$]!Iz X6I	'(:;IJ Z%NNME6!&&%!TJv*$$r   c                    t        | t        t        f      xr t        |       dk(  }t	        | |duxr | t
        j                        } t        | t              r| }nLt        | t              r%| j                         D cg c]	  \  }}||i }}}nt        dt        |              |s|sg S t
        j                  d       g S t        t        d      g }|D ]  }t        |t              rt        |      dk7  rt
        j                  d|       8t        t        |j                                     \  }}	t        |	t              r|t        v rd	|	i}	t        |	t              s+t
        j                  d
|t        |	      j                         	 t!        ||	      }
|j#                  t%        |
|              t
        j)                  dt        |             |S c c}}w # t&        $ r#}t
        j                  d||	|       Y d}~-d}~ww xY w)ul  Build a list of :class:`AlbumentationsWrapper` instances from a config.

        Supports both a flat dictionary format (backward-compatible) and a list format that allows duplicate transform
        names and explicit ordering. Container transforms (``OneOf``, ``SomeOf``, ``Sequential``) may be nested
        arbitrarily deep.

        **Dict format** (existing, backward-compatible)::

            config = {
                "HorizontalFlip": {"p": 0.5},
                "Rotate": {"limit": 45, "p": 0.3},
                "OneOf": {
                    "transforms": [
                        {"HorizontalFlip": {"p": 1.0}},
                        {"VerticalFlip": {"p": 1.0}},
                    ],
                },
            }

        **List format** (new; useful when you need two entries with the same name or when explicit order matters)::

            config = [
                {"HorizontalFlip": {"p": 0.5}},
                {"OneOf": {
                    "transforms": [
                        {"Rotate": {"limit": 45, "p": 1.0}},
                        {"ShiftScaleRotate": {"p": 1.0}},
                    ],
                }},
            ]

        **Shorthand for container ``transforms`` list** -- when a container key's value is a *list* rather than a dict,
        it is interpreted as the ``transforms`` parameter::

            {"OneOf": [{"HorizontalFlip": {"p": 1.0}}, {"VerticalFlip": {"p": 1.0}}]}

        Args:
            config_dict: Augmentation configuration -- either a ``dict`` mapping
                transform names to parameter dicts, or a ``list`` of single-key dicts ``{name: params}``.
            keypoint_flip_pairs: Joint index pairs for swapping left/right keypoints after a horizontal
                flip (e.g. ``[0, 1, 2, 3]`` swaps joint 0↔1 and 2↔3). Pass ``None`` (default) for
                detection pipelines where horizontal flips are always permitted. Pass an empty list
                ``[]`` to mark a keypoint pipeline without any defined flip pairs -- horizontal-flip
                augmentations are then disabled until flip-pair swapping is implemented.

        Returns:
            List of :class:`AlbumentationsWrapper` instances in config order.

        Raises:
            ImportError: If Albumentations is not installed.
            TypeError: If *config_dict* is neither a ``dict`` nor a ``list``.

        Examples:
            >>> config = {
            ...     "HorizontalFlip": {"p": 0.5},
            ...     "Rotate": {"limit": 45, "p": 0.3},
            ...     "GaussianBlur": {"p": 0.2}
            ... }
            >>> transforms = AlbumentationsWrapper.from_config(config)
            >>> [t.transform.transforms[0].__class__.__name__ for t in transforms]
            ['HorizontalFlip', 'Rotate', 'GaussianBlur']

        Note:
            Invalid transforms or invalid parameters are logged and skipped gracefully.
        r   N)include_keypointswarnz.config_dict must be a dictionary or list, got zAEmpty augmentation config provided, no transforms will be appliedrh   r   z=Skipping invalid config entry (must be a single-key dict): %rr\   z4Skipping %s: parameters must be a dictionary, got %s)r   z5Failed to initialize %s with params %r: %s. Skipping.z.Built %d Albumentations transforms from config)ro   rr   rp   rs   r
   r   r   rv   r9  rb   rk   rl   rt   ru   rm   r.   rx   rw   r   	Exceptioninfo)config_dictr   original_config_emptyentriesr   r   r\   r   aug_namer|   rf   es               r   from_configz!AlbumentationsWrapper.from_configp  s   L !+;t E _#kJZ^_J_91=YFYBY

 k4(!GT**5*;*;*=>$!Q1v>G>LTR]M^L_`aa(	NN^_I;j 
 
 	EeT*c%jAoS #D$78Hf &$'H8Q,Q&/fd+JL))
 
1(FC	!!"7	Wj"kl/	B 	Dc*oVg ?R  K	 s    G(G!!	H*HHr   )rf   alb.BasicTransformr   list[int] | Noner+   r,   )r+   r   )r   Tensor | np.ndarrayr+   
np.ndarray)r   rI  r   r   r+   rJ  )r   rJ  r   	list[int]r+   dict[str, Any])r   r   r+   r   )NF)r   rL  r   rK  r   rJ  r   rH  r   r   r+   r   )r&   rL  r   r   r+   rL  )r&   rL  r   r   r   rK  r+   rL  )r)  rJ  r&   rL  r  rK  r+   z"tuple[Image.Image, dict[str, Any]])r%   zPIL.Image.Imager&   r-   r+   z-tuple[PIL.Image.Image, dict[str, Any] | None])rA  z%dict[str, Any] | list[dict[str, Any]]r   rH  r+   zlist['AlbumentationsWrapper'])r.   r/   r0   __doc__r   r   staticmethodr   r   r   r   r  r  r  r7  r)   rF  r1   r   r   r   r   j  s   %N 6DZ*    " 
 

 

 
:  < 
 (,?!?? !? %	?
 ? 
? ?B  0  2p%"p%,:p%DMp%	+p%da%$a%.Ca%	6a%F  15A:A-A 
'A Ar   r   )rf   rG  r+   r   )r{   r   r|   rL  r+   rG  )r   rb   r+   r   )r{   r   r|   rL  r   rb   r+   rL  )&rM  
__future__r   r   collections.abcr   	functoolsr   typingr   albumentationsrk   rl   r   r   PILr!   r   r   torchvision.transformsr	   r   rfdetr.datasets._aug_utilsr
   rfdetr.utilities.box_opsr   rfdetr.utilities.loggerr   r   r   rc   	frozensetrm   r^   rx   r   rz   r   r1   r   r   <module>rZ     s    > "  $     
    < J 7 .	3 3v- ` &&GH :KD\ 4* *"RjH
 H
a
  
Cs   B B%$B%