§
    �WêjFD  ã                   óŒ   — d Z ddlZddlmZmZmZmZ ddlm	Z	m
Z
 ddlmZmZmZ d„ Zdd„Zdd
„Zdd„Zdd„Zd„ Zd„ Zeefd„ZdS )a1  
Edge feature computation module
Maintained by: PDF Engineers (edge_type implementations) + AI Researchers (orchestration)
Purpose: Compute edge attributes for graph neural network input

Edge types:
    - default: Relative bbox geometry (get_edge_transform_bbox)
    - SEO: Default + alignment features (get_edge_transform_bbox_add_alignment)
    - PRS: Presidio spatial features (compute_edge_features)
    - YF: Default + "what's between the two endpoints" features
          (get_edge_transform_yf) -- num_bbox_cross / num_vector_cross,
          how many other text bboxes / vector lines the straight line
          between the edge's two node centers passes through. Unlike SEO's
          alignment flags (redundant with the default 18-dim relation
          features and with DGCNN's own h_j-h_i difference-vector
          inductive bias), this is genuine page-level context that neither
          endpoint bbox alone, nor the two together, can express.

To add a new edge type:
    1. Implement compute function in this file
    2. Register it in get_edge_attr()
é    Né   )Úget_edge_by_directional_nnÚget_edge_by_alignmentÚget_edge_transform_bboxÚ%get_edge_transform_bbox_add_alignment)Úget_rect_member_idÚfind_closed_border_rects)Ú$extract_bbox_features_by_roi_poolingÚDEFAULT_FEATURE_MAP_POOLING_OPSÚ DEFAULT_CLASS_LOGITS_POOLING_OPSc                 óê  — || z
  }||z
  }	t          j        |j        t           j        ¬¦  «        }
t          j        |j        t           j        ¬¦  «        }t          j        |j        t
          ¬¦  «        }| | |z
  f||| z
  f|	 ||z
  f|	||z
  ffD ]X\  }}t          |¦  «        dk     r
||dk    z  }Œ"||z  }|dk     rt          j        |
|¦  «        }
ŒCt          j        ||¦  «        }ŒY||
|k    z  }|S )a0  
    Liang-Barsky segment-vs-many-axis-aligned-rects intersection test,
    vectorized over rects for one fixed segment.

    Segment: (x0, y0) -> (x1, y1), scalars.
    Rects: rx1, ry1, rx2, ry2 -- each an (M,) array (parallel arrays of
        rectangle bounds to test against).

    Returns:
        (M,) bool array: whether the segment intersects each rect (full
        containment of either endpoint inside a rect also counts as an
        intersection -- no special-casing needed, Liang-Barsky handles it
        the same way as a proper crossing).
    ©Údtypegê-�™—q=r   )	ÚnpÚzerosÚshapeÚfloat64ÚonesÚboolÚabsÚmaximumÚminimum)Úx0Úy0Úx1Úy1Úrx1Úry1Úrx2Úry2ÚdxÚdyÚt0Út1ÚvalidÚpÚqÚrs                   ú’/Users/tedpeters/Documents/Projects/Claude Projects/TPC Houses/house-viewer/.venv/lib/python3.11/site-packages/pymupdf/layout/pymupdf_util_edge.pyÚ_segment_rect_intersect_maskr*   "   s  € ð 
ˆb‰€BØ	ˆb‰€BÝ	Œ�#”)¥2¤:Ð	.Ñ	.Ô	.€BÝ	Œ�”¥"¤*Ð	-Ñ	-Ô	-€BÝŒG�C”I¥TÐ*Ñ*Ô*€Eà�#�r˜C‘x� 2 s¨R¡x .°B°3¸¸S¹°/ÀBÈÈbÉÀ>ÐRð 
'ð 
'‰ˆˆ1Ýˆq‰6Œ6�EŠ>ˆ>ð �a˜1’fÑˆEˆEà�A‘ˆAØ�1ŠuˆuÝ”Z  AÑ&Ô&��å”Z  AÑ&Ô&��à	ˆb�BŠhÑ€EØ€Ló    c                 ó’  ‡&— t          | |¦  «        }t          |¦  «        }|dk    r6t          j        |t          j        dt          j        ¬¦  «        gd¬¦  «        S t          j        | t          j        ¬¦  «        }|dd…df         |dd…df         z   dz  }|dd…df         |dd…d	f         z   dz  }|dd…df         |dd…df         |dd…df         |dd…d	f         f\  }}	}
}t          |¦  «        }d}dŠ&|�Ú|                     d
¦  «        Š&|                     d¦  «        }|�®|\  }}t          |¦  «        t          |¦  «        z   }|rvt          j
        d„ |D ¦   «         ¦  «        t          j
        d„ |D ¦   «         ¦  «        t          j
        d„ |D ¦   «         ¦  «        t          j
        d„ |D ¦   «         ¦  «        f}‰&€t          ||¦  «        Š&d}‰&r t          j
        ˆ&fd„|D ¦   «         ¦  «        }t          j        |t          j        ¬¦  «        }t          j        |t          j        ¬¦  «        }t          j        |t          j        ¬¦  «        }t          j        |t          ¬¦  «        }t          |¦  «        D ]Ñ\  }\  }}||         ||         }}||         ||         }}|                     ¦   «         }d||<   d||<   t#          ||||||	|
|¦  «        }t          j        ||z  ¦  «        ||<   |�4|\  } }!}"}#t#          ||||| |!|"|#¦  «        }$t          j        |$¦  «        ||<   |�#||         dk    r||         ||         k    rd||<   ŒÒt          j        t          j        |¦  «        t          j        |¦  «        |gd¬¦  «                             t          j        ¦  «        }%t          j        ||%gd¬¦  «        S )ae  
    edge_type='YF': the default 18-dim relation features
    (get_edge_transform_bbox) plus 2 new columns that require knowing every
    OTHER element's position on the page -- genuinely non-local information
    that neither the 18-dim relation features nor DGCNN's own h_j-h_i
    difference-vector message construction can express, unlike SEO's
    alignment flags (see module docstring):

        num_bbox_cross:     how many other text bboxes the straight line
            between the two endpoint centers passes through.
        num_vector_cross:   how many vector lines (from
            data_dict['vector_lines'], the same cache used by
            pymupdf_util_yf.py's vector_margin_* channels) that line
            passes through.
        same_rect_member:   1.0 if both endpoints' centers fall inside the
            SAME fully-closed border rectangle (e.g. the same full-border
            table -- see pymupdf_util_base.find_closed_border_rects), else
            0.0. This is what actually disambiguates "two cells of the same
            table" (should connect) from "two cells of separate, nearby
            tables" (should not) -- num_bbox_cross/num_vector_cross alone
            can't reliably tell these apart, since both situations can show
            small counts of each. Deliberately NOT based on
            page.cluster_drawings()-style proximity clustering -- see
            find_closed_border_rects's docstring for why.

    num_bbox_cross and num_vector_cross are log1p-compressed before being
    appended: raw counts range from 0 (short adjacent edges) to several
    dozen (edges spanning most of the page), and log1p keeps one large
    count from dominating. same_rect_member is already a clean binary flag.

    Assumes no bbox-fully-contains-another-bbox case among text elements
    (true for PDF-native text extraction: one parsed text line/word never
    encloses another). Liang-Barsky handles full containment the same as
    a proper crossing regardless, so this assumption affects interpretation
    ("crossing" vs "containing") but not correctness of the count.

    Args:
        bboxes: (N, 4) array/list of [x1, y1, x2, y2].
        edge_index: list of (i, j) node-index tuples.
        data_dict: optional; if data_dict['vector_lines'] / ['closed_rects']
            are present (see pymupdf_util_base.extract_base_elements),
            they're reused instead of being recomputed. If 'vector_lines'
            is present but 'closed_rects' isn't, it's computed here from
            the cached lines. If neither is present, num_vector_cross is 0
            and same_rect_member is 0 for every edge (shape stays
            consistent; only get_edge_dim's dummy-data probing relies on
            this graceful fallback).

    Returns:
        (E, 21) array: 18 relation features + [log1p(num_bbox_cross),
        log1p(num_vector_cross), same_rect_member].
    r   )r   é   r   r   ©ÚaxisNé   g       @r-   Úclosed_rectsÚvector_linesc                 ó   — g | ]	}|j         ‘Œ
S © )r   ©Ú.0r(   s     r)   ú
<listcomp>z)get_edge_transform_yf.<locals>.<listcomp>’   ó   € Ð4Ð4Ð4 q˜aœdÐ4Ð4Ð4r+   c                 ó   — g | ]	}|j         ‘Œ
S r4   )r   r5   s     r)   r7   z)get_edge_transform_yf.<locals>.<listcomp>“   r8   r+   c                 ó   — g | ]	}|j         ‘Œ
S r4   )r   r5   s     r)   r7   z)get_edge_transform_yf.<locals>.<listcomp>”   r8   r+   c                 ó   — g | ]	}|j         ‘Œ
S r4   )r   r5   s     r)   r7   z)get_edge_transform_yf.<locals>.<listcomp>•   r8   r+   c                 ó0   •— g | ]}t          |‰¦  «        ‘ŒS r4   )r   )r6   Úbr1   s     €r)   r7   z)get_edge_transform_yf.<locals>.<listcomp>�   s$   ø€ ÐUÐUÐUÀQÕ/°°<Ñ@Ô@ÐUÐUÐUr+   Féÿÿÿÿg      ð?)r   Úlenr   ÚconcatenateÚemptyÚfloat32Úasarrayr   ÚgetÚlistÚarrayr	   r   r   r   Ú	enumerateÚcopyr*   Úcount_nonzeroÚstackÚlog1pÚastype)'ÚbboxesÚ
edge_indexÚ	data_dictÚbaseÚEÚ
bboxes_arrÚ	centers_xÚ	centers_yÚall_x1Úall_y1Úall_x2Úall_y2ÚNÚ	vec_rectsr2   Úh_linesÚv_linesÚall_vecÚrect_idsÚnum_bbox_crossÚnum_vector_crossÚsame_rect_memberÚself_exclude_baseÚe_idxÚiÚjr   r   r   r   ÚexcludeÚhitÚvx1Úvy1Úvx2Úvy2ÚvhitÚextrar1   s'                                         @r)   Úget_edge_transform_yfrn   G   s
  ø€ õj # 6¨:Ñ6Ô6€DÝˆJ‰Œ€AØˆA‚v€vÝŒ~˜t¥R¤X¨f½B¼JÐ%GÑ%GÔ%GÐHÈqÐQÑQÔQÐQå”˜F­"¬*Ð5Ñ5Ô5€JØ˜A˜A˜A˜q˜DÔ! J¨q¨q¨q°!¨tÔ$4Ñ4¸Ñ;€IØ˜A˜A˜A˜q˜DÔ! J¨q¨q¨q°!¨tÔ$4Ñ4¸Ñ;€IØ&0°°°°A°Ô&6¸
À1À1À1ÀaÀ4Ô8HØ&0°°°°A°Ô&6¸
À1À1À1ÀaÀ4Ô8Hð&JÑ"€FˆF�F˜FåˆJ‰Œ€Aà€IØ€LØÐØ —}’} ^Ñ4Ô4ˆØ —}’} ^Ñ4Ô4ˆØÐ#Ø+ÑˆG�WÝ˜7‘m”m¥d¨7¡m¤mÑ3ˆGØð å”HÐ4Ð4¨GÐ4Ñ4Ô4Ñ5Ô5Ý”HÐ4Ð4¨GÐ4Ñ4Ô4Ñ5Ô5Ý”HÐ4Ð4¨GÐ4Ñ4Ô4Ñ5Ô5Ý”HÐ4Ð4¨GÐ4Ñ4Ô4Ñ5Ô5ð	�	ð Ð#Ý7¸ÀÑIÔI�ð €HØð WÝ”8ÐUÐUÐUÐUÈ*ÐUÑUÔUÑVÔVˆå”X˜a¥r¤zÐ2Ñ2Ô2€NÝ”x ­¬Ð4Ñ4Ô4ÐÝ”x ­¬Ð4Ñ4Ô4ÐÝœ ­Ð.Ñ.Ô.Ðå" :Ñ.Ô.ð *ð *‰ˆ‰v��1Ø˜1”˜y¨œ|ˆBˆØ˜1”˜y¨œ|ˆBˆà#×(Ò(Ñ*Ô*ˆØˆ�‰
Øˆ�‰
å*¨2¨r°2°r¸6À6È6ÐSYÑZÔZˆÝ "Ô 0°°w±Ñ ?Ô ?ˆ�uÑàÐ Ø!*ÑˆC��c˜3Ý/°°B¸¸BÀÀSÈ#ÈsÑSÔSˆDÝ&(Ô&6°tÑ&<Ô&<Ð˜UÑ#àÐ H¨Q¤K°2Ò$5Ð$5¸(À1¼+ÈÐRSÌÒ:TÐ:TØ&)Ð˜UÑ#øåŒHÝ	Œ�.Ñ	!Ô	!¥2¤8Ð,<Ñ#=Ô#=Ð?OÐPØðñ ô ÷ ‚f�RŒZÑÔð 
õ Œ>˜4 ˜-¨aÐ0Ñ0Ô0Ð0r+   Ú c                 óü   — |dk    rt          | |¦  «        S |dk    rt          | ||¬¦  «        S |dk    r9ddlm} |j        \  }}}t          j        |¦  «        j        }	 || |	||¬¦  «        S t          | |¦  «        S )uJ  
    Compute edge attributes based on edge type.

    Args:
        bboxes: np.ndarray of shape (N, 4), float32
        edge_index: list of (i, j) tuples
        edge_type: str â€” '', 'SEO', 'PRS', 'YF', etc.
        page_img: page image array (required for 'PRS')

    Returns:
        edge_attr: np.ndarray of shape (E, D)
    ÚSEOÚYF)rO   ÚPRSr   )Úcompute_edge_features)Únode_bboxesrN   Ú
page_widthÚpage_height)	r   rn   Úpresidio.graphrt   r   r   rF   ÚTr   )
rM   rN   Ú	edge_typerO   Úpage_imgrt   Úpage_hÚpage_wÚ_Útemp_edge_idxs
             r)   Úget_edge_attrr€   ¾   sµ   € ð �EÒÐÝ4°V¸ZÑHÔHÐHà	�dÒ	Ð	Ý$ V¨ZÀ9ÐMÑMÔMÐMà	�eÒ	Ð	Ø9Ð9Ð9Ð9Ð9Ð9Ø$œNÑˆ�˜Ýœ Ñ,Ô,Ô.ˆØ$Ð$°ÀMØ06ÀFðLñ Lô Lð 	Lõ ' v¨zÑ:Ô:Ð:r+   c                 ó–   — t          j        g d¢g d¢gt           j        ¬¦  «        }ddg}t          ||| |¬¦  «        }|j        d         S )zò
    Get edge attribute dimension by computing on dummy data.
    Used for single-node edge case.

    Args:
        edge_type: str
        page_img: page image array (required for 'PRS')

    Returns:
        int: edge feature dimension
    )ç        r‚   ç      $@rƒ   )ç      4@r„   ç      >@r…   r   )r   r   )r   r   )r{   r   )r   rF   rB   r€   r   )rz   r{   Údummy_bboxesÚdummy_edge_indexÚ
dummy_attrs        r)   Úget_edge_dimr‰   Ü   sk   € õ ”8ØÐÐØ Ð Ð ðõ ŒZðñ ô €Lð  Ð'ÐÝ˜|Ð-=¸yÐS[Ð\Ñ\Ô\€JØÔ˜AÔÐr+   Ú4Dc                 óÈ   — |dk    rFt          | dd¬¦  «        \  }}t          | d¬¦  «        }t          t          ||z   ¦  «        ¦  «        }nt          | dd¬¦  «        \  }}|S )zê
    Build edge index from bboxes.

    Args:
        bboxes: np.ndarray of shape (N, 4)
        edge_sampling: '4D' (directional NN) or '4D+AL' (directional NN + alignment)

    Returns:
        edge_index: list of (i, j) tuples
    z4D+ALiPÃ  g333333Ó?)Úvertical_gapr   )Údist_threshold)r   r   ÚsortedÚset)rM   Úedge_samplingÚedge_index1r~   Úedge_index2rN   s         r)   Úbuild_edge_indexr“   ñ   st   € ð ˜ÒÐÝ3°F¸EÐPSÐTÑTÔT‰ˆ�QÝ+¨FÀ1ÐEÑEÔEˆÝ�C ¨kÑ 9Ñ:Ô:Ñ;Ô;ˆ
ˆ
å2°6¸5ÈsÐSÑSÔS‰ˆ
�AØÐr+   c                 ór  — g }| \  }}t          ||¦  «        D ]ž\  }}||         }||         }t          |d         |d         ¦  «        }	t          |d         |d         ¦  «        }
t          |d         |d         ¦  «        }t          |d         |d         ¦  «        }|                     |	|
||g¦  «         ŒŸ|S )a¢  
    Compute the union bbox of each edge's two endpoint node bboxes.

    NOTE: for image-edge features specifically, prefer
    compute_edge_gap_bboxes() instead -- see its docstring for why a plain
    union tends to just re-pool each node's own visual content rather than
    capturing what's actually between the two nodes. This function is kept
    as a general "bounding box of two boxes" utility.

    Args:
        edge_index: np.ndarray of shape (2, E) or any (row, col) pair of
            equal-length sequences of node indices.
        original_bboxes: list of [x1, y1, x2, y2], indexed by node.

    Returns:
        list of [x1, y1, x2, y2], one per edge.
    r   r   r0   r-   )ÚzipÚminÚmaxÚappend)rN   Úoriginal_bboxesÚedge_bboxesÚrowÚcolÚ
node_idx_1Ú
node_idx_2Úbbox1Úbbox2r   r   Úx2Úy2s                r)   Úcompute_edge_union_bboxesr£     sÄ   € ð$ €KØ�H€CˆÝ"% c¨3¡-¤-ð -ð -Ñˆ
�JØ 
Ô+ˆØ 
Ô+ˆÝ��q”˜5 œ8Ñ$Ô$ˆÝ��q”˜5 œ8Ñ$Ô$ˆÝ��q”˜5 œ8Ñ$Ô$ˆÝ��q”˜5 œ8Ñ$Ô$ˆØ×Ò˜B  B¨Ð+Ñ,Ô,Ð,Ð,ØÐr+   c           
      ó¢  — g }| \  }}t          ||¦  «        D �]5\  }}||         \  }}}	}
||         \  }}}}|	|k    r|	|}}n||k    r||}}nd\  }}|
|k    r|
|}}n||k    r||}}nd\  }}|�|�|                     ||||g¦  «         Œt|�6|                     |t          ||¦  «        |t          |
|¦  «        g¦  «         Œ¬|�6|                     t          ||¦  «        |t          |	|¦  «        |g¦  «         Œä|                     t          ||¦  «        t          ||¦  «        t          |	|¦  «        t          |
|¦  «        g¦  «         �Œ7|S )a=  
    Compute the "corridor" rectangle between each edge's two endpoint node
    bboxes -- the space between them, excluding both nodes' own areas --
    rather than the union (which mostly just re-covers each node's own
    content for adjacent neighbors, diluting whatever visual signal
    actually sits in the gap: a ruling line, whitespace, another element).

    For two boxes A, B:
      - On the axis where they're separated (no overlap), the gap uses
        the actual space BETWEEN them on that axis (excludes both boxes).
      - On the perpendicular axis, the gap uses the INTERSECTION of A and
        B's extents on that axis (only the band both boxes actually
        occupy), not the union (which would include the part of one box
        that has no counterpart in the other).
      - If A and B are separated on BOTH axes (diagonal neighbors), the
        gap is the small rectangle between their facing corners.
      - If A and B overlap (no separating axis on either side -- rare for
        PDF-parser-derived text bboxes, but possible e.g. when a chart or
        image bbox contains a text bbox), there's no meaningful "between"
        region, so this falls back to the union -- an intersection here
        would lose the very context (the smaller box) that an edge to it
        is meant to capture.

    Args:
        edge_index: np.ndarray of shape (2, E) or any (row, col) pair of
            equal-length sequences of node indices.
        original_bboxes: list of [x1, y1, x2, y2], indexed by node.

    Returns:
        list of [x1, y1, x2, y2], one per edge.
    )NN)r•   r˜   r—   r–   )rN   r™   rš   r›   rœ   r�   rž   Úax1Úay1Úax2Úay2Úbx1Úby1Úbx2Úby2Úgx0Úgx1Úgy0Úgy1s                      r)   Úcompute_edge_gap_bboxesr±   $  s˜  € ð@ €KØ�H€CˆÝ"% c¨3¡-¤-ð !]ñ !]Ñˆ
�JØ,¨ZÔ8ÑˆˆS�#�sØ,¨ZÔ8ÑˆˆS�#�sà�#Š:ˆ:Ø˜C�ˆCˆCØ�CŠZˆZØ˜C�ˆCˆCà!‰HˆC�à�#Š:ˆ:Ø˜C�ˆCˆCØ�CŠZˆZØ˜C�ˆCˆCà!‰HˆC�àˆ?˜s˜à×Ò  S¨#¨sÐ3Ñ4Ô4Ð4Ð4Øˆ_ð ×Ò ¥S¨¨c¡]¤]°C½¸SÀ#¹¼ÐGÑHÔHÐHÐHØˆ_à×Ò¥ C¨¡¤¨sµC¸¸S±M´MÀ3ÐGÑHÔHÐHÐHð
 ×Ò¥ C¨¡¤­s°3¸©}¬}½cÀ#Às¹m¼mÍSÐQTÐVYÉ]Ì]Ð[Ñ\Ô\Ð\Ñ\àÐr+   c	                 óÔ   — t          ||¦  «        }	t          ||	|||¬¦  «        }
t          ||	|||d¬¦  «        }t          j        |
|gd¬¦  «        }t          j        | |gd¬¦  «        S )a  
    Append ROI-pooled image features to edge attributes.

    Pools feature_map (decoder embedding) and class_logits (per-class scores)
    SEPARATELY, using the same scheme as node-level image features
    (see BoxRFDGNN.get_nn_input_from_datadict), then concatenates both onto
    edge_attr. class_logits is always softmaxed before pooling since its
    entropy/margin ops are only meaningful over a probability distribution.

    This is a standalone, single-shot entry point: it builds its own
    softmax/SAT tables via extract_bbox_features_by_roi_pooling() every call.
    If you are ALSO pooling the same feature_map/class_logits for node
    bboxes in the same request (as BoxRFDGNN does), prefer building a
    RoiPoolingSession per tensor and calling .query() for both node and
    edge bboxes instead -- that shares the bbox-independent softmax/SAT
    setup between the two queries rather than rebuilding it here. See
    roi_pooling.RoiPoolingSession and BoxRFDGNN.get_nn_input_from_datadict.

    Args:
        edge_attr: existing edge attributes, shape (E, D)
        edge_index: np.ndarray of shape (2, E)
        original_bboxes: list of [x1, y1, x2, y2]
        feature_map: decoder embedding output, shape (1, 5*F, H, W)
        class_logits: per-class segmentation logits, shape (1, C, H, W)
        page_w, page_h: page dimensions
        feature_map_pooling_ops: pooling_ops applied to feature_map
        class_logits_pooling_ops: pooling_ops applied to class_logits

    Returns:
        edge_attr: np.ndarray of shape (E, D + D_img)
    )Úpooling_opsT)r³   Úapply_softmaxr   r.   )r±   r
   r   r@   )Ú	edge_attrrN   r™   Úfeature_mapÚclass_logitsr}   r|   Úfeature_map_pooling_opsÚclass_logits_pooling_opsrš   Úfeat_pooledÚlogit_pooledÚedge_image_featuress                r)   Úappend_image_edge_featuresr½   l  s�   € õF *¨*°oÑFÔF€Kå6Ø�[ &¨&Ø+ðñ ô €Kõ 8Ø�k 6¨6Ø,Øðñ ô €Lõ
 œ.¨+°|Ð)DÈ1ÐMÑMÔMÐÝŒ>˜9Ð&9Ð:ÀÐCÑCÔCÐCr+   )N)ro   NN)ro   N)rŠ   )Ú__doc__Únumpyr   Úcommon_utilr   r   r   r   Úpymupdf_util_baser   r	   Úroi_poolingr
   r   r   r*   rn   r€   r‰   r“   r£   r±   r½   r4   r+   r)   ú<module>rÃ      sc  ððð ð. Ð Ð Ð ðZð Zð Zð Zð Zð Zð Zð Zð Zð Zð Zð Zà KÐ KÐ KÐ KÐ KÐ KÐ KÐ Kð<ð <ð <ð <ð <ð <ð <ð <ð <ð <ð
"ð "ð "ðJt1ð t1ð t1ð t1ðn;ð ;ð ;ð ;ð<ð ð ð ð*ð ð ð ð(ð ð ð>Eð Eð EðT 9XØ9Yð/Dð /Dð /Dð /Dð /Dð /Dr+   