
    ^j                       U d Z ddlmZ ddlZddlZddlZddlmZ ddlm	Z	m
Z
mZ ddlZddlmZ ddlmZ  e       Zh dZd	ed
<   dZded<    eh d      Zded<   dZded<   d dZd!dZd"dZej8                  d#d       Zej8                  d$d       Zef	 	 	 	 	 	 	 	 	 d%dZd&dZ ef	 	 	 	 	 	 	 	 	 	 	 d'dZ!d(dZ"dddefdd	 	 	 	 	 	 	 	 	 	 	 	 	 	 	 d)dZ#y)*u  ONNX → TFLite conversion using the ``onnx2tf`` library.

``onnx2tf`` (PINTO0309) converts an ONNX graph to TFLite.  **Version 2.4.0 or later is required** — earlier 1.x releases
cannot lower three op patterns in the RF-DETR graph (constant Expand, 1-D TopK, rank-3 Tile). Although the onnx2tf 2.x
default backend is ``flatbuffer_direct``, RF-DETR unconditionally forces ``tflite_backend="tf_converter"`` to avoid a
runtime error in the TFLite TopK_V2 kernel (``flatbuffer_direct`` trips a "k > internal dimension" check at
``AllocateTensors()`` time).  ``Erf`` and ``GeLU`` activations are replaced with TFLite-native pseudo-operators
(``replace_to_pseudo_operators=["Erf", "GeLU"]``) so the produced model does not require the TensorFlow Flex delegate at
inference time.

GridSample rewrite
------------------
RF-DETR's deformable cross-attention uses :func:`torch.nn.functional.grid_sample` once per decoder layer (6 calls
total).  ``onnx2tf``'s built-in GridSample handler lowers the op to ``tf.gather_nd(batch_dims=1)``, which TFLite's
``GatherNd`` kernel does not support — the kernel silently accepts the call during ``AllocateTensors()`` but produces
numerically wrong output at inference time, causing all detection scores to collapse from ~0.6 to ~0.1. The
``replace_to_pseudo_operators=["GridSample"]`` pseudo-op path also produces numerically wrong logit magnitudes in both
FP32 and FP16 (the pseudo-op itself is broken, not a quantization issue).  An earlier ONNX-level rewrite using
``GatherElements(axis=2)`` was lowered to ``tf.gather_nd(batch_dims=2)``, which TFLite does not support and crashes with
index out-of-bounds at inference time.

Before invoking ``onnx2tf``, :func:`_replace_gridsample_for_tflite` rewrites every ``GridSample`` node in the ONNX graph
into an equivalent bilinear sampling subgraph built from ``Gather(axis=0)`` on a transposed and flattened
``(N*(H+2)*(W+2), C)`` image tensor.  ``onnx2tf`` lowers ``Gather(axis=0)`` to TFLite's ``GATHER`` op with no
``batch_dims`` — the only TFLite gather path that is unconditionally supported, neither crashing on
``AllocateTensors()`` nor producing wrong values.

The converter uses the ``onnx2tf`` Python API directly (rather than shelling out to the CLI) so that we can:

* Apply a compatibility shim for older ``onnx2tf`` releases that call
  :func:`numpy.load` on pickled data without ``allow_pickle=True``.
* Redirect ``onnx2tf``'s built-in ``download_test_image_data()`` to use
  locally-prepared calibration data instead of downloading from GitHub (which can fail in many environments).

``onnx2tf`` calls ``download_test_image_data()`` for its ONNX-vs-TF output validation.  ``_patch_validation_download()``
redirects that call to local data, avoiding the network dependency.

INT8 quantization
-----------------
``quantization="int8"`` produces a **dynamic-range** INT8 model (INT8 weights, float activations, roughly 4x smaller
than FP32, no calibration data needed), built from the ``onnx2tf`` SavedModel.

Static (full-integer) INT8 is not supported and raises ``ValueError``: RF-DETR's transformer activations do not survive
8-bit post-training quantization.

Note:
    The resulting ``.tflite`` model expects the same input normalization as the ONNX model: ImageNet mean/std
    (``mean=[0.485, 0.456, 0.406]``, ``std=[0.229, 0.224, 0.225]``).  The caller is responsible for applying this
    normalization at inference time.

Note:
    Segmentation models additionally emit a ``masks`` output.  FP32, FP16, and dynamic-range INT8 all match the PyTorch
    baseline closely (INT8 mask fidelity is marginally lower).  Verified on the non-plus segmentation
    variants: Nano, Small, Medium, Large, and Preview.
    )annotationsN)Path)Any	Generatorcast)NDArray)
get_logger>   Nfp16fp32int8zset[str | None]_VALID_QUANTIZATIONS   int_DEFAULT_CALIB_SAMPLES>   .bmp.jpg.png.jpeg.webpzfrozenset[str]_IMAGE_EXTENSIONSd   _DEFAULT_DIR_CALIB_SAMPLESc          
     z  ]^_`abcdefghijklm ddl eddl}ddl`| j                  d   }| j                  d   }| j                  d   }| j
                  j                  dd      }| j
                  j                  dd      }| j
                  j                  dd      }	|dk7  s
|dk7  s|	dk7  rt        d	|d
|d|	       |j                  t        |j                        dk7  rt        d|j                         |j                  t        |j                        dk7  rt        d|j                         d| idgjej                  dfd`ijfdkej                  dfd`eijfd_g ddd`dfdgt        |j                  j                        at        |j                  j                        }
 _ ej                   d      ej                   d      } _ ej                   d      ej                   d      } _ ej                   d      ej                   d      } _ ej                  d      ej                  d      m _ ej                  d      ej                  d      f _ ej                  d      ej                  d      } _ ej                  d      ej                  d      } _dgej                   d       ] _d!gej                   d"      } _d#gej                   d$      c kd%ej                   dg&      } gd'|g|g        kd(ej                   dg&      } gd'|g|g       d_egkfd)} ||dd*      } ||dd+      } ||dd,      } ||d!d-      } ||dd.      } ||dd/      } kd0ej                   1      } gd2||g|g        kd3ej                   1      h gd2||ghg        kd4ej                   1      } gd5|hg|g        kd6ej                   1      } gd5||g|g        kd7ej                  1      } gd8|g|gd9|
i        kd:ej                  1      } gd8|g|gd9|
i        kd;ej                  1      } gd8hg|gd9|
i        kd<ej                  1      }  gd8|g| gd9|
i        kd=ej                  1      }! gd>||g|!g        kd?ej                  1      }" gd>||g|"g        kd@ej                  1      }# gdA|fg|#g        kdBej                  1      }$ gdA| fg|$g       d]egkfdC}% |%|dD      }& |%|dE      }' |%|dF      }( |%|dG      }) |%|dH      }* kdIej                   dg&      }+ gdJ|*|'g|+gdKdi        kdLej                   dg&      }, gdJ|&|(|)|'g|,gdKdi        _g dMej                   dN      }- kdO      }. gdP||-g|.g        kdQ      }/ gdR|.g|/gdSg dTi        kdU      b gdV|/|+gbg        kdW      }0 gdX| _dgej                   dY       _dgej                   dZ      |g|0g        kd[      }1 gd\|0|g|1g        kd]      }2 gdX| _dgej                   d^       _dgej                   d_      |g|2g        kd`      }3 gd\|2|g|3g        kda      }4 gd2|1fg|4g        kdb      }5 gd5|4|!g|5g        kdc      }6 gdA|5|g|6g        kdd      }7 gd2|3fg|7g        kde      }8 gd5|7|"g|8g        kdf      }9 gdA|8|g|9g        kdg      }: gdh|6g|:g        kdi      }; gdh|9g|;g        kdj      }< gd2|:fg|<g        kdk      }= gd2|;fg|=g        kdl      }> gdA|6|:g|>g        kdm      }? gdA|9|;g|?g        kdn      }@ gdAf|>g|@g        kdo      }A gdAf|?g|Ag       daefgkmfdp}B |B|:|#dq      }C |B|<|#dr      }D |B|;|$ds      }E |B|=|$dt      }F kduej                   1      }G gdv|||g|Gg        kdwej                   1      }H gd5|G|g|Hg        _g dxej                   dy      }I kdzej                   1      ^ gdV|H|Ig^g       d^ceghkfd{}J |J|C|Ed|      }K |J|D|Ed}      }L |J|C|Fd~      }M |J|D|Fd      }Ndbgkfd}O |O|Kd|      }P |O|Ld}      }Q |O|Md~      }R |O|Nd      }S _d#dgej                   d      ldgklfd}T |T|P|@|Ad|      }U |T|Q|>|Ad}      }V |T|R|@|?d~      }W |T|S|>|?d      }X kd      }Y gd2|U|Vg|Yg        kd      }Z gd2|Y|Wg|Zg        kd      }[ gd2|Z|Xg|[g        kd      }\ gdV|[|,g|\g        gdR|\g|gdSg di       | j                  j#                          | j                  j#                          |j$                  j'                  d       y)u  Rewrite one GridSample ONNX node into a TFLite-safe bilinear subgraph.

    Replaces ``GridSample(im, grid)`` with an equivalent bilinear sampling subgraph that performs four
    ``Gather(axis=0)`` lookups on a transposed and flattened ``(N*(H+2)*(W+2), C)`` image tensor.  ``onnx2tf`` lowers
    ``Gather(axis=0)`` to TFLite's ``GATHER`` op with no ``batch_dims`` — the only TFLite gather path that is
    unconditionally supported, neither crashing on ``AllocateTensors()`` nor producing wrong values. Per-sample batch
    offsets are added to the flat index so that a single rank-1 ``Gather`` covers the entire batch.

    The replacement is mathematically identical to PyTorch's ``F.grid_sample`` for ``mode="bilinear"``,
    ``padding_mode="zeros"``, ``align_corners=0``.  Out-of-bounds sample positions are clamped to the zero-padded
    border, which has the same effect as PyTorch's zero padding.

    Shape-dependent values are computed at runtime via ONNX Shape/Gather/ Concat/Cast ops so the subgraph works for any
    static or dynamic input shape.

    Args:
        node: The ``gs.Node`` to replace (must be a ``GridSample`` node).
        graph: The ``gs.Graph`` that owns *node*.
        index: Unique integer suffix used to namespace tensor/constant names.

    Raises:
        NotImplementedError: If the node has unsupported attributes.
        ValueError: If ``im`` or ``grid`` are not rank-4 tensors.
    r   N   modebilinearpadding_modezerosalign_cornerszgGridSample TFLite patch only supports mode='bilinear', padding_mode='zeros', align_corners=0; got mode=z, padding_mode=z, align_corners=   z9GridSample TFLite patch requires rank-4 im; got im.shape=z=GridSample TFLite patch requires rank-4 grid; got grid.shape=_gsreplc                \    dxx   dz  cc<   j                   dd    d|  ||      S )Nr   r   _dtypeshape)Variable)namer%   r&   gspfxuids      j/var/www/ramen.bs-engineer-server.com/venv/lib/python3.12/site-packages/rfdetr/export/_tflite/converter.pyvz%_replace_single_gridsample.<locals>.v   s:    A!{{cU!CF81TF35{NN     c                |    dxx   dz  cc<   j                   dd    d|  j                  | |            S )Nr   r   r#   _c_r%   )Constantarray)valr%   r(   r)   npr*   r+   s      r,   cz%_replace_single_gridsample.<locals>.c   sC    A!{{cU!CF83tf5xrxx57QRRr.   c           	     V    j                  j                  | |||xs i              y )N)opinputsoutputsattrs)appendNode)kindinsoutsr<   r)   nodess       r,   r9   z&_replace_single_gridsample.<locals>.op   s$    RWWS$ekrWRSr.   zero_ione_i   two_ig        zero_fg      ?one_fg       @two_fg      ?half_fax0_1d   ax3_1dneg1_1dim_shaper$   Shape
grid_shapec                     |j                         } d|   j                   |      j                   d| d|       g|gddi       |S )Nr2   Gatherir#   axisr   int64)shape_taxis_idxr(   resultr7   r6   r9   r-   s       r,   
gather_dimz._replace_single_gridsample.<locals>.gather_dim   s[    4rxx(
8gq(!3RXX8*AdV?TUVY_X`ciklbmnr.   NCHWH_outW_outpHr2   AddpWpH_pWMulN_pH_pWW_fCasttoH_fpW_fpH_fW_halfDivH_halfpW_max_fSubpH_max_fc                N     |j                   dg      } d| g|g       |S )z@Promote a 0-D int64 scalar to a 1-D [1] int64 vector for Concat.r   r$   	UnsqueezerW   )scalar_tr(   r[   rK   r6   r9   r-   s      r,   unsq0z)_replace_single_gridsample.<locals>.unsq0   s/    4rxxs3
;6*VH5r.   N_1dC_1dH_out_1dW_out_1d
N_pH_pW_1d
flat_shapeConcatrV   
nhwc_shape)r   r   r   r   r   r   r   r   padsim_padPadim_nhwc	Transposeperm)r   rE   rL   r   im_flatReshapegx_rawSlices0_gxe1_gxgxSqueezegy_raws1_gye2_gygygxp1pxrpxgyp1pyrpyx0_fFloory0_fx1_fy1_fwx1wy1wx0wy0c                     
d|       } 	d| g|g        
d|       } 	d||g|g        
d| j                         } 	d|g|gdi       |S )	Nsh_rd   cl_Clipca_r2   rj   rk   rW   )coord_f	clamp_maxr(   shiftedclippedcast_inti64r6   rH   r9   r-   rG   s         r,   int_shifted_clampedz7_replace_single_gridsample.<locals>.int_shifted_clamped4  sz    c$L/
57E"WI.c$L/
6GVY/';s4&\2
6G9xj4+6r.   x0cx1cy0cy1cbatch_rangeRangebatch_offset_flat)rN   r   r   batch_offset_shapebatch_offsetc                (    d| 	j                         } 
d|g|g        d| 	j                         } 
d|| g|g        d| 	j                         } 
d|g|g        d| 	j                         } 
d|g|g       |S )	Nypw_r2   rg   local_rd   glob_gidx_r   rW   )xcycr(   ypwlocal
global_idxflat1dr   rO   r6   r9   pW_tr-   s          r,   flat_global_indexz5_replace_single_gridsample.<locals>.flat_global_indexQ  s    $tfRXX.
52t*se$F4&/2
53)eW%tf~RXX6

55,'*6U4&>2
9z7+fX6r.   aabaabbbc                >     d|       } d| g|gddi       |S )Nsamp_rT   rV   r    )gidxr(   sampledr   r9   r-   s      r,   gather_flatz/_replace_single_gridsample.<locals>.gather_flatc  s0    eD6N#
8gt_wi&!=r.   wflat_shapec                     d|       } d||g|g        d|       } d|	g|g        d|       } d| |g|g       |S )Nw2d_rg   wflat_r   c_r   )
gatheredwxwyr(   w_2dw_flatout_contribr9   r-   r   s
          r,   contribz+_replace_single_gridsample.<locals>.contribq  sp    4v
52r(TF#VD6?#
9t[)F84"TFn
58V${m4r.   s1s2total
total_nhwc)r   rL   r   rE   )r(   strr%   r   r&   zlist[int] | Nonereturnr   )r5   r   r%   r   r(   r   r   r   N)
r?   r   r@   	list[Any]rA   r   r<   zdict[str, Any] | Noner   None)rY   r   rZ   r   r(   r   r   r   )rw   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   r   r   )
r   r   r   r   r   r   r(   r   r   r   )numpyonnxonnx_graphsurgeonr:   r;   r<   getNotImplementedErrorr&   len
ValueErrorfloat32r   TensorProtoINT64FLOATrX   clearrB   extend)nnodegraphindexr   imgridoutr   r   r   f32rC   rD   rF   rI   rJ   rM   
im_shape_tgrid_shape_tr\   N_tC_tH_tW_tH_out_tW_out_tpH_tpH_pW_t	N_pH_pW_tri   rl   rm   rn   ro   rq   rr   rt   rx   ry   rz   r{   r|   r}   flat_shape_tnhwc_shape_tr   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   r   r   r   r   gidx_aagidx_bagidx_abgidx_bbr   saasbasabsbbr   caacbacabcbbr   r   r   r   rK   r   r7   r)   r   r   rO   rB   r6   rH   r9   r   r*   r+   r-   r   rG   sn                                                                                                @@@@@@@@@@@@@@@@@r,   _replace_single_gridsampler  Z   s
   2 "	QB;;q>D
,,q/C::>>&*-D::>>.':LJJNN?A6Mz\W48J!x|.>>N}o_
 	
 
xx3rxx=A-TUWU]U]T^_``zzS_1XY]YcYcXdeffE7
CSC"$** O O "$ S S ET d$$
%C
d$$
%C xrxx{BHHh/Fhbhhqk288W-Ehbhhqk288W-Ezrzz#

H5Fjbjjorzz73Ejbjjorzz73Ezrzz#

H5FsBHHh'FsBHHh'Fbhh	*G
 :RXXaS9Jwzl#\!=Lw'  ZC
(C
ZC
(C
ZC
(C
ZC
(Cq'2Gq'2G T"DusElTF#T"DusElTF#rxx(GutTlWI& )288,IusGnyk* E
$CvusedC[)
E
$CvusedC[)V2::&Dvvvc{+V2::&Dvvvc{+xrzz*FusElVH%xrzz*FusElVH%2::.HutUmhZ(2::.HutUmhZ(  fDfDWj)HWj)Hy,/J \!=Lx*d#l^fa[A \!=Lx$(D1L>FA;O %rxx8Dx[Fur4j6(#	lG{VHwi&,)?@	lGy7L)G95 x[Fwq!bhh0!QC72KVTW]V^_	
4By66"RD)x[Fwq!bhh0!QC72KVTW]V^_	
4By66"RD) V9Dur5kD6"
E(CutVnse$	
4BusFmbT"V9Dur5kD6"
E(CutVnse$	
4BusFmbT" V9DwtfV9DwtfV9DutUmdV$V9DutUmdV$
E(Cur4j3% 
E(Cur4j3% 
E(CuuclSE"
E(CuuclSE"  dHe
4C
dHe
4C
dHe
4C
dHe
4C
 M2Kwe${m4-RXX>u{G$'8&9::rxx1EF^2884Ly$&89L>J	 	  S$/GS$/GS$/GS$/G
 gt
$C
gt
$C
gt
$C
gt
$C RGRXX}5K #sC
&C
#sC
&C
#sC
&C
#sC
&C	
4BusCj2$	
4Bur3i"gJEur3i%! <Jy5,'*6{ZL3%&,)?@ 	KKLL	KKur.   c                   	 ddl }ddl}ddl}|j	                  t        |             }|j                  j                  |      }|j                  |      }|j                  D cg c]  }|j                  dk(  s| }}|st        j                  d       | S t        j                  dt        |             t        |      D ]  \  }	}
t!        |
||	        |j#                         j%                          	 |j                  j                  |j'                  |            }|j(                  j+                  |       || j0                  d	z   z  }|j3                  |t        |             t        j                  d
|       |S # t        $ r}t        d      |d}~ww xY wc c}w # t,        $ r}t/        d|       |d}~ww xY w)u  Rewrite every GridSample node in *onnx_path* to use TFLite-safe ops.

    ``onnx2tf``'s built-in GridSample handler lowers to ``tf.gather_nd(batch_dims=1)``, which TFLite's ``GatherNd``
    kernel does not support — the kernel silently accepts the model during ``AllocateTensors()`` but produces
    numerically wrong output at inference time.  The ``replace_to_pseudo_operators=["GridSample"]`` pseudo-op path also
    produces wrong logit magnitudes in both FP32 and FP16 (the pseudo-op itself is broken, independent of quantization).
    This function rewrites the ONNX graph *before* calling ``onnx2tf.convert()``, replacing each ``GridSample`` node
    with an equivalent bilinear subgraph that performs four ``Gather(axis=0)`` lookups on a transposed and flattened
    ``(N*(H+2)*(W+2), C)`` image tensor.  ``onnx2tf`` lowers ``Gather(axis=0)`` to TFLite's ``GATHER`` op with no
    ``batch_dims`` — the only TFLite gather path that is unconditionally supported.

    Only ``mode="bilinear"``, ``padding_mode="zeros"``, ``align_corners=0`` nodes are patched — the only variant emitted
    by RF-DETR's exporter.

    Args:
        onnx_path: Path to the source ``.onnx`` file.
        output_dir: Directory where the patched ``.onnx`` is written.  Must
            already exist.

    Returns:
        Path to the patched ``.onnx`` file if any ``GridSample`` nodes were found; *onnx_path* unchanged if the graph
        contains no such nodes.

    Raises:
        ImportError: If ``onnx`` or ``onnx_graphsurgeon`` are not available.
        NotImplementedError: If a ``GridSample`` node has unsupported attrs.
        RuntimeError: If the patched graph fails ONNX shape-inference or
            model validation.
    r   Nzwonnx and onnx_graphsurgeon are required for the GridSample TFLite patch.  Install with: pip install rfdetr[onnx,tflite]
GridSamplez6No GridSample nodes found; skipping TFLite-safe patch.uG   Patching %d GridSample node(s) → TFLite-safe Gather(axis=0) subgraph.)r   z1GridSample ONNX patch produced an invalid graph: z_gs_patched.onnxz$GridSample-patched ONNX saved to: %s)r   onnx.shape_inferencer   ImportErrorloadr   shape_inferenceinfer_shapesimport_onnxrB   r9   loggerdebuginfor   	enumerater  cleanuptoposortexport_onnxcheckercheck_model	ExceptionRuntimeErrorstemsave)	onnx_path
output_dirr   r)   excmodelr   ngs_nodesrU   r   patchedout_paths                r,   _replace_gridsample_for_tfliter.    s   <#& IIc)n%E  --e4ENN5!E ;;?a!$$,*>?H?MN
KKQH X& 94"4a89 
MMO_&&33BNN54IJ  ) Y^^.@@AHIIgs8}%
LL7BOC  D
 	 @"  _NseTU[^^_s<   E? "F7F5AF! ?	FFF!	F>*F99F>c                    	 ddl } ddlm} ddlm} ddl }ddlm} 	  |d      }|J  ||       |d	      k  rt        d
| d      y# t        $ r}t        d      |d}~ww xY w# |$ r t        |dd      }|Y yY Zw xY w)uI  Verify that a compatible ``onnx2tf`` package is importable.

    onnx2tf 2.4.0 or later is required — earlier 1.x releases cannot lower the constant ``Expand``, 1-D ``TopK``, and
    rank-3 ``Tile`` ops present in RF-DETR's ONNX graph.

    Raises:
        ImportError: If ``onnx2tf`` cannot be imported or is below 2.4.0.
    r   Nzonnx2tf is not installed. TFLite export requires both ONNX and TFLite export dependencies. Install them with: pip install rfdetr[onnx,tflite])PackageNotFoundError)version)Versiononnx2tf__version__z2.4.0zonnx2tf z is installed but RF-DETR requires >= 2.4.0. Earlier 1.x releases cannot lower the Expand, TopK, and Tile ops in RF-DETR's ONNX graph. Upgrade with: pip install 'onnx2tf>=2.4.0')r3  r  importlib.metadatar0  r1  packaging.versionr2  getattr)r3  r(  _PkgNotFound_pkg_version_onnx2tf_mod_Version	installeds          r,   _check_onnx2tf_availabler=    s     H:"5 ,Y 7	    	Xg..yk "R R
 	
 /1  .
 		   L->	 s(   A
 A' 
	A$AA$'B ?B c               #     K   t         j                  dfd} | t         _        	 d t         _        y# t         _        w xY ww)u  Temporarily patch :func:`numpy.load` to set ``allow_pickle=True``.

    ``onnx2tf`` 1.x calls ``np.load()`` on its bundled calibration data without passing ``allow_pickle=True``.  NumPy ≥
    1.16.3 defaults that flag to ``False`` and raises :class:`ValueError` for pickled files.

    This context manager monkey-patches ``np.load`` for the duration of the ``onnx2tf`` conversion and restores the
    original afterwards.
    c                 8    |j                  dd        | i |S )Nallow_pickleT)
setdefault)argskwargs_original_loads     r,   _patched_loadz*_numpy_allow_pickle.<locals>._patched_load  s#    .$/t.v..r.   N)rB  r   rC  r   r   r   )r6   r  )rE  rD  s    @r,   _numpy_allow_picklerF    s6      WWN/ BG! .s   "A6 AAAc              #     K   d fd}i }ddg}|D ]M  }t         j                  j                  |      }|s%t        |d      s2t	        |d      ||<   t        |d|       O 	 d |j                         D ]4  \  }}t         j                  j                  |      }|s(t        |d|       6 y# |j                         D ]4  \  }}t         j                  j                  |      }|s(t        |d|       6 w xY ww)ur  Redirect ``download_test_image_data()`` to use local calibration data.

    ``onnx2tf`` calls ``download_test_image_data()`` during conversion to fetch test images from GitHub.  The function
    is called in two places:

    1. **Validation** — compares ONNX-vs-TF outputs (all conversions).
    2. **INT8 calibration** — builds a representative dataset when
       ``output_integer_quantized_tflite=True``.

    This download can fail in many environments (firewalls, CI, air-gapped systems, or when the upstream file is
    unavailable).  This context manager monkey-patches the function in all known module locations to return the data
    from the calibration ``.npy`` file we already prepared.

    We intentionally do **not** use ``custom_input_op_name_np_data_path`` because that code path triggers a ``tf.tile``
    rank mismatch in onnx2tf
    1.x when processing models with DINOv2-style embeddings and N > 1
    calibration samples.  Patching the download function achieves the same goal without that issue.

    Args:
        npy_path: Path to the ``.npy`` file containing calibration data in
            NHWC format.
    c                 \    t        t        t           t        j                   d            S )NF)r@  )r   r   r   r6   r  )npy_paths   r,   _replacementz0_patch_validation_download.<locals>._replacement2  s!    
 GCL"''("GHHr.   zonnx2tf.utils.common_functionszonnx2tf.onnx2tfdownload_test_image_dataN)r   zNDArray[Any])sysmodulesr   hasattrr7  setattritems)rI  rJ  	originalsrM  mod_namemodoriginals   `      r,   _patch_validation_downloadrU    s     2I !#I(G  Ckkooh'73 :;")#/I"JIhC3\B	CC"+//"3 	CHh++//(+C7B	C)//"3 	CHh++//(+C7B	Cs4   3C;C;C;#B/ '7C;C;/8C8(C88C;c                X   ddl m} | j                         st        d|        t	        d | j                         D              }|st        d|  dt	        t                     |d| }t        j                  dt        |       d	|  d
| d| d	       g }|D ]  }	 |j                  |      j                  d      j                  ||f      }t        j                  |t        j                        }	|	t        j                  d      z  }	|j!                  |	        |st        d|        t        j                  dt        |       d       t        j&                  |      j)                  t        j                  d      S # t"        $ r t        j%                  d|        Y w xY w)a  Load images from a directory and prepare them for calibration.

    Images are loaded, resized to ``(height, width)``, converted to ``float32`` in ``[0, 1]``, and stacked into an NHWC
    array.

    Args:
        image_dir: Directory containing image files (JPEG, PNG, etc.).
        height: Target image height matching the model input.
        width: Target image width matching the model input.
        max_images: Maximum number of images to load.  Files are sorted
            alphabetically and the first *max_images* are used.

    Returns:
        A ``float32`` NumPy array of shape ``(N, height, width, 3)`` with pixel values in ``[0, 1]``.

    Raises:
        FileNotFoundError: If *image_dir* does not exist or contains no
            supported image files.
    r   )Imagez'Calibration image directory not found: c              3  b   K   | ]'  }|j                   j                         t        v s$| ) y wr   )suffixlowerr   ).0ps     r,   	<genexpr>z+_load_calibration_images.<locals>.<genexpr>k  s#     aq188>>;KO`;`as   %//z"No supported image files found in z. Supported extensions: NzLoading z calibration images from z (resizing to x)RGBr2   g     o@zSkipping unreadable image: zNo readable images found in zLoaded z calibration imagesF)copy)PILrW  is_dirFileNotFoundErrorsortediterdirr   r  r  r   openconvertresizer6   asarrayr   r=   r"  r  stackastype)
	image_dirheightwidth
max_imagesrW  image_pathsarraysimg_pathimgimage_arrays
             r,   _load_calibration_imagesrv  M  s   2 "I) UVVaI$5$5$7aaK0;STZ[lTmSno
 	
 kz*K
KK(3{+,,Ei[P^_e^ffghmgnnopq(*F 	**X&..u5<<eV_MC**S

;K2::e,,KMM+& ">yk JKK
KK'#f+&9:;88F""2::E"::  	LL6xjAB	s   A?F!F)(F)c                X   	 ddl }|j                  t        |             }|j                  j
                  d   }|j                  }|j                  j                  j                  j                  D cg c]  }|j                   }}||fS # t        $ r}t        d      |d}~ww xY wc c}w )zRead the first input tensor's name and shape from an ONNX model.

    Args:
        onnx_path: Path to the ``.onnx`` file.

    Returns:
        A ``(name, dims)`` tuple where *dims* is the NCHW shape list, e.g. ``("input", [1, 3, 560, 560])``.
    r   Nzonnx is not installed. TFLite export requires both ONNX and TFLite export dependencies. Install them with: pip install rfdetr[onnx,tflite])r   r  r  r   r   inputr(   typetensor_typer&   dim	dim_value)r&  r   r(  r)  inpr(   ddimss           r,   _get_onnx_input_infor    s     IIc)n%E
++

A
C88D!$!5!5!;!;!?!?@AAKK@D@:  .
 		 As   B
 1B'
	B$BB$c                   ||dk(  rt         j                  d       t        |       \  }}|\  }}}}	t        j                  j                  t        ||	|      j                  t        j                        }
|dz  }t        j                  t        |      |
       t         j                  d|
j                   d|        |S t        |t        j                        rH|dz  }t        j                  t        |      |       t         j                  d|j                          |S t!        |      }|j#                         rot        |       \  }}|\  }}}}	t%        |||	|      }
|dz  }t        j                  t        |      |
       t         j                  d|
j                   d|        |S |j'                         r|}t         j                  d	|        |S t)        d
|       )uz  Prepare calibration data as a ``.npy`` file for ``onnx2tf``.

    The returned path points to a ``.npy`` file containing an NHWC float32 array with pixel values in ``[0, 1]``.  This
    file is loaded by the ``_patch_validation_download()`` context manager, which replaces ``onnx2tf``'s built-in
    ``download_test_image_data()`` call.  ``onnx2tf`` uses this data for both ONNX-vs-TF output validation and (when
    INT8 is requested) as a representative calibration dataset.

    Args:
        onnx_path: Path to the source ``.onnx`` file (used to read the
            input tensor NCHW shape for random data generation and for determining the target resolution when loading
            images from a directory).
        calibration_data: One of:

            * ``None`` — generate random calibration data.  Sufficient for
              fp32/fp16 but emits a warning for int8.
            * A **directory path** containing JPEG/PNG images — images are
              loaded, resized to the model input resolution, and converted to the correct format automatically.
            * A path to a ``.npy`` file containing an array of shape
              ``(N, H, W, 3)``, dtype float32, values in ``[0, 1]``.
            * A :class:`numpy.ndarray` with the same constraints.
        output_dir: Directory where a temporary ``.npy`` file may be
            written when *calibration_data* is ``None``, a directory, or an ndarray.
        quantization: The requested quantization mode (used only to decide
            whether to emit a warning).
        max_images: Maximum number of images to load when
            *calibration_data* is a directory path.  Ignored for other calibration data formats.

    Returns:
        Path to the ``.npy`` calibration data file.

    Raises:
        FileNotFoundError: If *calibration_data* is a path that does not
            exist, or a directory with no supported images.
    r   u   No calibration_data provided for INT8 quantization. Using random data — this will produce poor quantization accuracy. For best results, pass calibration_data with representative images from your dataset.z_rfdetr_calib_data.npyz)Generated random calibration data: shape=z, saved to z(Using provided calibration array: shape=)rn  ro  rp  z6Prepared calibration data from image directory: shape=zUsing calibration data from: z!Calibration data path not found: )r  warningr  r6   randomrandr   rl  r   r%  r   r  r&   
isinstancendarrayr  r   rc  rv  is_filerd  )r&  calibration_datar'  quantizationrp  r#   
input_dimsr7   hwcalibrI  	data_path_cs                 r,   _prepare_calibration_datar    s   R 6!NN, -Y7:
1a		5q!Q?FFrzzR 88
Hu%@[YaXbcd* O) 
$bjj	1 88
H/0>?O?U?U>VWX" O )*	0;MAz$KAr1a,YqV`aE!$<<HGGCM5)KKPQVQ\Q\P]]hiqhrst O   HKK7zBC O $&G	{$STTr.   c                   ddl }|j                  j                  j                  t	        |             }|j                  j
                  j                  g|_        | | dz  }|j                  |j                                |S )a  Build a dynamic-range INT8 TFLite model from the onnx2tf SavedModel.

    Dynamic-range quantization stores weights as INT8 and keeps activations in float, so it needs no calibration data.

    Args:
        saved_model_dir: Directory holding the SavedModel ``onnx2tf`` wrote.
        model_stem: Stem of the source ONNX file, used to name the output.

    Returns:
        Path to the written ``{model_stem}_dynamic_range_quant.tflite`` file.
    r   Nz_dynamic_range_quant.tflite)

tensorflowliteTFLiteConverterfrom_saved_modelr   OptimizeDEFAULToptimizationswrite_bytesrh  )saved_model_dir
model_stemtf	converterr-  s        r,   _quantize_dynamic_ranger    so     ''88_9MNI!ww//778IJ</J!KKH**,-Or.   errorF)verbosec               V   t        |       } t        |      }| j                         st        d|        |t        vr&t	        d|dt        d t        D               d      t                ddlm} ddl	m
c m} ~~|j                  dd	       	 t        | |      } t#        | ||||      }
t        j%                  d|d|d|         	 t'               5  t)        t+        |
            5  ddlm} t+        |       t+        |      d| |ddgd}	  |d"i |ddi ddd       ddd       | j6                  }|dk(  r&t9        ||      }t        j%                  d|        |S || dz  }|j                         sht        |j;                  | d            }|r8|d   }t        j!                  d|| dz   d| d| d|j<                   d	       nt5        d | d!|       t        j%                  d|        |S # t        $ r!}	t        j!                  d
|	       Y d}	~	d}	~	ww xY w# t.        $ r! t        j!                  d        |d"i | Y 7w xY w# 1 sw Y   =xY w# 1 sw Y   BxY w# t0        $ r,}	t        j3                  d|	        t5        d|	       |	d}	~	ww xY w)#u.  Convert an ONNX model to TFLite via ``onnx2tf``.

    Requires ``onnx2tf >= 2.4.0``.  Uses the Python API with a NumPy compatibility shim.

    Args:
        onnx_path: Path to the source ``.onnx`` file.
        output_dir: Directory where TFLite artifacts will be written.
            ``onnx2tf`` creates ``{stem}_float32.tflite`` and ``{stem}_float16.tflite``.  When ``quantization="int8"`` a
            ``{stem}_dynamic_range_quant.tflite`` is additionally written.
        quantization: Quantization mode.

            * ``None`` / ``"fp32"`` / ``"fp16"`` — FP32 + FP16 output
              (``onnx2tf`` always emits both).
            * ``"int8"`` — additionally produce a dynamic-range INT8 model
              (INT8 weights, float activations, ~4x smaller than FP32). Static / full-integer INT8 is not supported.
        calibration_data: Representative data used by ``onnx2tf`` for its
            ONNX-vs-TF output validation.  Accepts:

            * ``None`` — auto-generate random data.
            * A **directory path** containing JPEG/PNG images — images
              are loaded, resized, and converted automatically.
            * A path to a ``.npy`` file — shape ``(N, H, W, 3)``,
              dtype float32, pixel values in ``[0, 1]``.
            * A :class:`numpy.ndarray` with the same format.

            Dynamic-range INT8 needs no calibration data, so this argument does not affect the quantized weights — it
            only feeds onnx2tf's internal validation pass.
        verbosity: Log verbosity passed to ``onnx2tf``.  One of
            ``"debug"``, ``"info"``, ``"warn"``, ``"error"`` (default).
        max_images: Maximum number of images to load when
            *calibration_data* is a directory path.  Defaults to 100. Ignored for other calibration data formats.
        verbose: When ``True``, stream ``onnx2tf`` per-node progress —
            useful for monitoring long conversions (5–15 min on transformer-based models).  Defaults to ``False``
            (silent).

    Returns:
        Path to the primary artifact.  ``onnx2tf`` always writes both ``{stem}_float32.tflite`` and
        ``{stem}_float16.tflite`` to *output_dir*; ``quantization="int8"`` adds ``{stem}_dynamic_range_quant.tflite``.
        The returned path is the dynamic-range file for ``int8``, otherwise the float32 file.

    Raises:
        FileNotFoundError: If *onnx_path* does not exist or
            *calibration_data* points to a missing file.
        ImportError: If ``onnx2tf`` is not installed.
        ValueError: If *quantization* is not a recognized mode.
        RuntimeError: If the conversion fails.

    Note:
        This function is **not thread-safe**.  It globally monkey-patches :func:`numpy.load` (via
        :func:`_numpy_allow_pickle`) and ``onnx2tf.download_test_image_data`` (via :func:`_patch_validation_download`)
        for the duration of the conversion.  Concurrent calls from multiple threads will interfere with each other.  Run
        conversion in a subprocess if isolation is required.

        ``tf_converter`` backend is forced unconditionally (overriding onnx2tf's 2.x ``flatbuffer_direct`` default) to
        avoid a runtime error in the TFLite TopK_V2 kernel.  ``Erf`` and ``GeLU`` ops are substituted with TFLite-native
        pseudo-operators to avoid a missing TensorFlow Flex delegate at inference time.

        Segmentation models additionally emit a ``masks`` output, decoded by
        :func:`rfdetr.export._tflite.inference._run_inference`.  Verified on the non-plus segmentation variants (Nano,
        Small, Medium, Large, Preview).
    zONNX model not found: zUnsupported quantization mode z. Choose from: c              3  &   K   | ]	  }||  y wr   r   )r[  qs     r,   r]  z export_tflite.<locals>.<genexpr>U  s     "Tam1"Ts   zG. Static / full-integer INT8 is not supported; 'int8' is dynamic-range.r   NT)parentsexist_oku   GridSample TFLite patch skipped — onnx/onnx_graphsurgeon not available (%s). TFLite inference may produce incorrect scores if the model contains GridSample nodes. Install with: pip install rfdetr[onnx,tflite])rp  u)   Converting ONNX → TFLite (quantization=z, verbosity=z): )rh  ErfGeLU)input_onnx_file_pathoutput_folder_pathoutput_signaturedefsnon_verbose	verbosityreplace_to_pseudo_operatorstflite_backendtf_converteru   onnx2tf does not support tflite_backend= — proceeding with default backend. If TFLite inference produces wrong results, upgrade to onnx2tf>=2.4.0.zonnx2tf conversion failed: r   zTFLite model exported to: z_float32.tflitez	_*.tflitezExpected TFLite output z not found; searched for 'z_*.tflite' in z and using z\ instead. The returned model may have a different dtype (e.g. int8) than the caller expects.z0onnx2tf completed but no .tflite file matching 'z_*.tflite' was found in r   )r   r  rd  r   r   re  r=  onnx2tf.onnx2tfr3  onnx2tf.utils.common_functionsutilscommon_functionsmkdirr.  r  r  r  r  r  rF  rU  r   rh  	TypeErrorr"  r  r#  r$  r  globr(   )r&  r'  r  r  r  rp  r  r:  _onnx2tf_commonr(  calib_npy_pathrh  convert_kwargsr  primarytflite_filess                   r,   export_tfliter    s   N YIj!J"8 DEE//,\,< =""T.B"TTU VTT
 	
  +<<oTD1
29jI	 /#Z*N KK;L;K<XaWddghqgrst;I$  !&	*&s>':;&	* ( ),I&)*o(,#*{& 16v.N0*H.HH?&	* &	*^ Jv)*jA0	:;j\99G?? jooI.FGH"1oGNN)**_7U*U)V W!!+N:,kRYR^R^Q_ `ee B:,Nfgqfrs  KK,WI67NU  
< 		
 	

~  *1
 ).)*A&	* &	* &	* &	*P  I23%898>?SHIs~   G? 
I3 I&,%IH,I&&I3 ?	H)H$$H),&IIIII#	I&&I0+I3 3	J(<'J##J()r   r   r   r   r   r   r   r   )r&  r   r'  r   r   r   )r   r   )r   Generator[None, None, None])rI  r   r   r  )
rm  r   rn  r   ro  r   rp  r   r   zNDArray[np.float32])r&  r   r   ztuple[str, list[int]])r&  r   r  *str | os.PathLike[str] | np.ndarray | Noner'  r   r  
str | Nonerp  r   r   r   )r  r   r  r   r   r   )r&  str | os.PathLike[str]r'  r  r  r  r  r  r  r   rp  r   r  boolr   r   )$__doc__
__future__r   
contextlibosrL  pathlibr   typingr   r   r   r   r6   numpy.typingr   rfdetr.utilities.loggerr	   r  r   __annotations__r   	frozensetr   r   r  r.  r=  contextmanagerrF  rU  rv  r  r  r  r  r   r.   r,   <module>r     s  6p #  	 
  ' '    .	 )G o F !    %..X$Y > Y #& C %tn	CL(
V ! !, 0C 0Cn 1	7;7;7; 7; 	7;
 7;t< 1MM@M M 	M
 M 
M`0  $CG0S S%S&S S A	S
 S S S 
Sr.   