§
    �Øj•1  ã                   óx   — d Z ddlZddlmZmZmZmZ g d¢ZdZ	dZ
d„ eD ¦   «         Zd	Zd
ZdZ G d„ d¦  «        ZdS )a’  
ImageFeatureExtractorV1
Purpose: ONNX-based image feature extraction and segmentation-based bbox detection.

CCL execution strategy (lazy):
    predict()               -> ONNX inference only. CCL is NOT run.
    is_image_page(page_img) -> runs predict() + non-picture CCL only.
    get_picture_detections()-> runs picture CCL on first call after predict().

This minimises redundant work across three usage patterns:

  Pattern A  is_image_page() -> False (common case)
             ONNX: 1,  CCL: 1 (non-picture only)

  Pattern B  is_image_page() -> True -> predict() -> get_picture_detections()
             ONNX: 1,  CCL: 2 (non-picture + picture, each once)

  Pattern C  predict() -> get_feature_map() only
             ONNX: 1,  CCL: 0

Cache protocol (single-use):
    mark_cached() / consume_cache() -> let BoxRFDGNN.is_image_page() signal
    that ONNX inference has already run, so the next
    image_feature_extraction_task() call skips predict().

ONNX output contract (matches ImageFeatureExtractorV2):
    'combined' -- (1, 5*F, H, W) concatenated decoder feature maps
    'logits'   -- (1, C,   H, W) raw per-class segmentation logits (no
                  softmax baked into the graph; CCL uses argmax, which does
                  not need it, and softmax is computed in numpy elsewhere
                  only where an actual probability is required).
Both outputs are read by name via out_map, never by output position, so the
extraction code here is robust to output ordering the same way V2 is.

get_feature_map() / get_class_logits() contract:
    These two outputs are kept SEPARATE (not concatenated) because they have
    different statistical character: 'combined' is a continuous embedding
    space best summarized by mean/max pooling, while 'logits' is a per-class
    score that should be turned into a probability (softmax) before pooling
    with ops like mean/min/max/entropy/margin. Callers that need both
    (e.g. GNN node/edge image features) pool each separately with
    roi_pooling.extract_bbox_features_by_roi_pooling and concatenate the
    results themselves; see roi_pooling.DEFAULT_FEATURE_MAP_POOLING_OPS and
    DEFAULT_CLASS_LOGITS_POOLING_OPS.
é    Né   )Úresize_imageÚto_grayÚ extract_bboxes_from_segmentationÚ&extract_bboxes_from_segmentation_numpy)Ú
backgroundÚtextÚtitleÚpictureÚtablez	list-itemzpage-headerzpage-footerzsection-headerÚfootnoteÚcaptionÚformular   r   c                 ó2   — g | ]}|t           t          fv¯|‘ŒS © )Ú_BACKGROUND_CLASSÚ_PICTURE_CLASS)Ú.0Úcs     ú /Users/tedpeters/Documents/Projects/Claude Projects/Misc Tools/notename-sheets/.venv/lib/python3.11/site-packages/pymupdf/layout/onnx/ImageFeatureExtractorV1.pyú
<listcomp>r   B   s5   € ð Ið Ið I˜aØÕ%6½Ð$GÐGÐGð ØGÐGÐGó    g      à?é
   é   c                   óP   — e Zd ZdZd„ Zd„ Zd„ Zdd„Zd„ Zd„ Z	d	„ Z
d
„ Zd„ Zd„ ZdS )ÚImageFeatureExtractorV1ap  
    Wraps an ONNX inference session to produce:
      - a feature map
      - picture-class bbox detections      (lazy, via get_picture_detections())
      - non-picture-class detections       (lazy, via is_image_page())

    All CCL work is deferred until the result is actually needed, and cached
    so repeated calls within the same predict() cycle are free.
    c                 ó    — || _         d| _        d| _        d| _        d| _        d| _        d| _        d| _        d| _        d| _	        d| _
        dS )z­
        Args:
            onnx_session: onnxruntime.InferenceSession (or any object with
                          get_inputs() / run() matching the ORT interface)
        NFr   )Ú_sessionÚ	_combinedÚ_logitsÚ_raw_outputsÚ_cachedÚ_detections_pictureÚ_detections_non_pictureÚ_page_hÚ_page_wÚ	_target_hÚ	_target_w)ÚselfÚonnx_sessions     r   Ú__init__z ImageFeatureExtractorV1.__init__U   s\   € ð *ˆŒØ!ˆŒØ!ˆŒØ!ˆÔØ"ˆŒð (,ˆÔ Ø'+ˆÔ$ð ˆŒØˆŒØˆŒØˆŒˆˆr   c                 ó   — d| _         dS )z4Signal that predict() results should be reused once.TN©r"   ©r)   s    r   Úmark_cachedz#ImageFeatureExtractorV1.mark_cachedo   s   € àˆŒˆˆr   c                 ó&   — | j         r	d| _         dS dS )z’
        Return True (and clear the flag) if a cached result exists.
        Called by image_feature_extraction_task() to skip predict().
        FTr-   r.   s    r   Úconsume_cachez%ImageFeatureExtractorV1.consume_caches   s   € ð
 Œ<ð 	Ø ˆDŒLØ�4Øˆur   Nc                 óö  — |j         dd…         \  | _        | _        | j                             ¦   «         d         j         }|d         |d         c| _        | _        t          || j        | j        f¦  «        }t          |¦  «         	                    t          j        ¦  «        }|                     ¦   «         |                     ¦   «         }}||k    r||z
  ||z
  z  }n t          j        |t          j        ¬¦  «        }t          j        |d¬¦  «        }|�t          j        ||gd¬¦  «        }t          j        |d¬¦  «        }| j                             ¦   «         d         j        }	d„ | j                             ¦   «         D ¦   «         }
| j                             |
|	|i¦  «        }t)          t+          |
|¦  «        ¦  «        }|d         | _        |d	         | _        | j        | _        d| _        d| _        dS )
aþ  
        Run ONNX inference and store the outputs.
        CCL is NOT performed here -- results are computed lazily on demand.

        Calling predict() invalidates all previously cached CCL results so
        that get_picture_detections() and is_image_page() always reflect the
        current page.

        Args:
            page_img:   np.ndarray (H, W, C), uint8
            aug_fetmap: optional extra channel map concatenated before inference

        Side effects:
            self._combined / self._logits    <- set from ONNX outputs by name
            self._raw_outputs                <- alias for self._logits (CCL input)
            self._detections_picture         <- reset to None
            self._detections_non_picture     <- reset to None
        Nr   r   r   ©Údtype)Úaxisc                 ó   — g | ]	}|j         ‘Œ
S r   )Úname)r   Úos     r   r   z3ImageFeatureExtractorV1.predict.<locals>.<listcomp>¬   s   € ÐDÐDÐD 1˜œÐDÐDÐDr   ÚcombinedÚlogits)Úshaper%   r&   r   Ú
get_inputsr'   r(   r   r   ÚastypeÚnpÚfloat32ÚminÚmaxÚ
zeros_likeÚexpand_dimsÚconcatenater7   Úget_outputsÚrunÚdictÚzipr   r    r!   r#   r$   )r)   Úpage_imgÚ
aug_fetmapÚinput_shapeÚimg_resizedÚimg_grayÚmin_valÚmax_valÚnn_inputÚ
input_nameÚoutput_namesÚort_outputsÚout_maps                r   ÚpredictzImageFeatureExtractorV1.predict�   sÉ  € ð& &.¤^°B°Q°BÔ%7Ñ"ˆŒ�d”là”m×.Ò.Ñ0Ô0°Ô3Ô9ˆØ)4°Q¬¸ÀQ¼Ð&ˆŒ˜œõ # 8¨d¬n¸d¼nÐ-MÑNÔNˆÝ˜kÑ*Ô*×1Ò1µ"´*Ñ=Ô=ˆà#Ÿ<š<™>œ>¨8¯<ª<©>¬>�ˆØ�WÒÐØ  7Ñ*¨w¸Ñ/@ÑAˆHˆHå”} XµR´ZÐ@Ñ@Ô@ˆHå”> (°Ð3Ñ3Ô3ˆØÐ!Ý”~ x°Ð&<À1ÐEÑEÔEˆHÝ”> (°Ð3Ñ3Ô3ˆð
 ”}×/Ò/Ñ1Ô1°!Ô4Ô9ˆ
ØDÐD¨¬×(AÒ(AÑ(CÔ(CÐDÑDÔDˆØ”}×(Ò(¨¸
ÀHÐ7MÑNÔNˆÝ�C ¨kÑ:Ô:Ñ;Ô;ˆà  Ô,ˆŒØ  Ô*ˆŒØ œLˆÔð (,ˆÔ Ø'+ˆÔ$Ð$Ð$r   c                 ór   — | j         �dS t          | j        t          t          gt
          ¬¦  «        | _         dS )zDRun picture CCL if not yet computed for the current predict() cycle.N©Ú
seg_logitsÚclass_namesÚtarget_classÚmin_component_area)r#   r   r    Ú_CLASS_NAMESr   Ú_MIN_COMPONENT_AREAr.   s    r   Ú_ensure_picture_detectionsz2ImageFeatureExtractorV1._ensure_picture_detections¼   s>   € àÔ#Ð/ØˆFÝ#IØ”|Ý$Ý(Ð)Ý2ð	$
ñ $
ô $
ˆÔ Ð Ð r   c                 óp   — | j         �dS t          | j        t          t          t
          ¬¦  «        | _         dS )zHRun non-picture CCL if not yet computed for the current predict() cycle.NrW   )r$   r   r    r\   Ú_NON_PICTURE_CLASSESr]   r.   s    r   Ú_ensure_non_picture_detectionsz6ImageFeatureExtractorV1._ensure_non_picture_detectionsÇ   s;   € àÔ'Ð3ØˆFÝ'MØ”|Ý$Ý-Ý2ð	(
ñ (
ô (
ˆÔ$Ð$Ð$r   c                 ó   — | j         S )aY  
        Return the decoder feature map ('combined') from the last predict()
        call. This is the continuous embedding output only -- it no longer
        includes the class logits (see get_class_logits() for those).

        Returns:
            np.ndarray of shape (1, 5*F, H, W), or None if predict() not
            yet called.
        )r   r.   s    r   Úget_feature_mapz'ImageFeatureExtractorV1.get_feature_mapÖ   s   € ð Œ~Ðr   c                 ó   — | j         S )až  
        Return the per-class segmentation logits ('logits') from the last
        predict() call. Raw (pre-softmax) scores; apply softmax before
        pooling ops that expect a probability distribution (e.g. entropy,
        margin -- see roi_pooling.extract_bbox_features_by_roi_pooling).

        Returns:
            np.ndarray of shape (1, C, H, W), or None if predict() not yet
            called.
        )r    r.   s    r   Úget_class_logitsz(ImageFeatureExtractorV1.get_class_logitsâ   s   € ð Œ|Ðr   c                 óô  — |                      ¦   «         sdS |                     d¦  «                             ¦   «         rdS |                     ¦   «         }t	          j        |j        t          j        ¬¦  «        }|                     |j	        |j
        |j        ¦  «        }|                      |¦  «         |                      ¦   «          |                      ¦   «          t          d„ | j        D ¦   «         ¦  «        S )a¸  
        Determine whether the page is an image-only PDF page that requires OCR.

        Returns True only when ALL three conditions hold:
          1. The page contains at least one raster image (pymupdf).
          2. The page contains no embedded selectable text (pymupdf).
          3. The segmentation model detects at least one non-picture region
             (text, table, header, etc.) inside the page image.

        Condition 1 + 2 confirm the PDF structure is image-only.
        Condition 3 confirms there is recoverable content inside the image.
        All three must hold for OCR to be both necessary and worthwhile.

        Calls predict() internally and marks the result as cached so the
        immediately following BoxRFDGNN.predict() skips re-running inference.
        picture CCL is deferred to get_picture_detections() if needed.

        Args:
            page: PyMuPDF page object

        Returns:
            bool
        Fr	   r3   c              3   ó8   K  — | ]}|d          t           k    V — ŒdS )ÚscoreN)Ú_DETECTION_SCORE_THRESHOLD)r   Úds     r   ú	<genexpr>z8ImageFeatureExtractorV1.is_image_page.<locals>.<genexpr>  s@   è è € ð 
ð 
àð ˆgŒJÕ3Ò3ð
ð 
ð 
ð 
ð 
ð 
r   )Úget_image_infoÚget_textÚstripÚ
get_pixmapr>   Ú
frombufferÚsamplesÚuint8ÚreshapeÚheightÚwidthÚnrU   r/   ra   Úanyr$   )r)   ÚpageÚpixÚ
bytes_datarI   s        r   Úis_image_pagez%ImageFeatureExtractorV1.is_image_pageï   só   € ð2 ×"Ò"Ñ$Ô$ð 	Ø�5ð �=Š=˜Ñ Ô ×&Ò&Ñ(Ô(ð 	Ø�5ð —_’_Ñ&Ô&ˆÝ”] 3¤;µb´hÐ?Ñ?Ô?ˆ
Ø×'Ò'¨¬
°C´I¸s¼uÑEÔEˆà�Š�XÑÔÐØ×ÒÑÔÐð 	×+Ò+Ñ-Ô-Ð-åð 
ð 
àÔ1ð
ñ 
ô 
ñ 
ô 
ð 	
r   c                 óZ  — | j         €t          d¦  «        ‚|                      ¦   «          | j        | j        z  }| j        | j        z  }g }| j        D ]X}|d         t          k    rE|d         }| 	                    |d         |z  |d         |z  |d         |z  |d         |z  g¦  «         ŒY|S )	aµ  
        Return picture-class detections from the last predict() call,
        rescaled from model resolution to page pixel coordinates.

        Runs picture CCL on the first call after each predict() (lazy).
        Subsequent calls within the same predict() cycle are free.

        Returns:
            list of [x1, y1, x2, y2] in page pixel space.

        Raises:
            RuntimeError if predict() has not been called.
        Nz8predict() must be called before get_picture_detections()rh   Úbboxr   é   r   r   )
r   ÚRuntimeErrorr^   r&   r(   r%   r'   r#   ri   Úappend)r)   Úresize_xÚresize_yÚbboxesÚdetÚbs         r   Úget_picture_detectionsz.ImageFeatureExtractorV1.get_picture_detections  sÈ   € ð Œ>Ð!ÝÐYÑZÔZÐZà×'Ò'Ñ)Ô)Ð)à”< $¤.Ñ0ˆØ”< $¤.Ñ0ˆàˆØÔ+ð 	ð 	ˆCØ�7Œ|Õ8Ò8Ð8Ø˜”K�Ø—’Ø�a”D˜8‘OØ�a”D˜8‘OØ�a”D˜8‘OØ�a”D˜8‘Oð	ñ ô ð øð ˆr   )N)Ú__name__Ú
__module__Ú__qualname__Ú__doc__r+   r/   r1   rU   r^   ra   rc   re   r{   r†   r   r   r   r   r   J   s»   € € € € € ðð ðð ð ð4ð ð ðð ð ð5,ð 5,ð 5,ð 5,ðv	
ð 	
ð 	
ð	
ð 	
ð 	
ð
ð 
ð 
ðð ð ð.
ð .
ð .
ð` ð  ð  ð  ð  r   r   )rŠ   Únumpyr>   Úcommon_utilr   r   r   r   r\   r   r   r`   ri   r]   Ú_MORPHOLOGY_KERNEL_SIZEr   r   r   r   ú<module>rŽ      së   ðð,ð ,ð\ Ð Ð Ð ðð ð ð ð ð ð ð ð ð ð ð ðð ð €ð $Ð Ø €ðIð I <ð Iñ Iô IÐ ð !Ð ØÐ ØÐ ðuð uð uð uð uñ uô uð uð uð ur   