o
    
DÙi¥e  ã                   @   s$  d Z ddlZddlZddlZddlZddlZddlm  m	Z
 ddlZddlmZ ddlmZ ej dd¡ ddlmZ ddlmZmZ ddlZdZe g d	¢¡Ze g d
¢¡Zd&dd„Zdd„ Zd'dd„Zdd„ Z d(dd„Z!d)dd„Z"		d*dd„Z#dd „ Z$d!d"„ Z%d#d$„ Z&e'd%kre&ƒ  dS dS )+u¦  
Generate a 2x2 grid video comparing ACT vs PARA rollouts + feature PCAs.

Layout:
  â”Œâ”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”¬â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”
  â”‚  ACT Rollout  â”‚  ACT PCA     â”‚
  â”œâ”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”¼â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”¤
  â”‚  PARA Rollout â”‚  PARA PCA    â”‚
  â””â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”´â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”˜

Both policies run on the same OOD condition (shifted object position).
DINO backbone features are extracted at each replan step and visualized via joint PCA.

Usage:
    export PYTHONPATH=/data/cameron/LIBERO:/data/cameron/para_normalized_losses:$PYTHONPATH
    export DINO_REPO_DIR=/data/cameron/keygrip/dinov3
    export DINO_WEIGHTS_PATH=/data/cameron/keygrip/dinov3/weights/dinov3_vits16plus_pretrain_lvd1689m-4057cbaa.pth
    python ood_libero/generate_feature_comparison.py
é    N)ÚPath)ÚPCAz+/data/cameron/para_normalized_losses/libero)ÚOffScreenRenderEnv)Ú	benchmarkÚget_libero_pathéÀ  )g
×£p=
ß?gÉv¾Ÿ/Ý?g–C‹lçûÙ?)gZd;ßOÍ?gyé&1¬Ì?gÍÌÌÌÌÌÌ?c                 C   s^   t  | ¡ ¡ }tj|||ftjd}| t j¡d }|t t	 }t
 |¡ ddd¡ d¡ ¡ S )z)Convert LIBERO obs to model input tensor.©Zinterpolationç     ào@é   r   é   )ÚnpÚflipudÚcopyÚcv2ÚresizeÚINTER_LINEARÚastypeÚfloat32ÚIMAGENET_MEANÚIMAGENET_STDÚtorchÚ
from_numpyÚpermuteÚ	unsqueezeÚfloat)Úrgb_obsÚsizeZimg© r   ú</data/cameron/para/ood_libero/generate_feature_comparison.pyÚpreprocess_obs)   s
   r   c                 C   sÄ   | j }t ¡ Q | |¡\}\}}|jD ]}|jr |j||dnd}|||ƒ}q| |¡}t|dƒr5|jnd}	|dd…|	d d…f }
|
d  	||d¡ 
¡  ¡ }W d  ƒ |S 1 s[w   Y  |S )a0  Extract DINO backbone patch features from a model (works for both PARA and ACT).

    Both models use a DINO backbone accessed via model.dino. We run the backbone
    manually to get spatial patch features regardless of the model type.

    Returns:
        patch_features: (H_p, W_p, C) numpy array
    ©ÚHÚWNÚn_storage_tokensr   r   éÿÿÿÿ)Údinor   Úno_gradÚprepare_tokens_with_masksÚblocksÚ
rope_embedÚnormÚhasattrr#   ÚreshapeÚcpuÚnumpy)ÚmodelÚ
img_tensorÚ
model_typer%   Úx_tokensÚH_pÚW_pÚblkÚrope_sincosZ	n_storageZpatch_tokensÚfeatsr   r   r   Úextract_backbone_features2   s   	



ø	÷	r8   Ú	agentviewc                 C   s2   t  | |› d ¡ ¡ }tj|ttftjd}|S )z0Get clean RGB frame from obs (flipped, resized).Ú_imager   )r   r   r   r   r   Ú
IMAGE_SIZEr   )ÚobsÚcameraZrgbr   r   r   Úget_clean_rgbH   s   r>   c                    sþ   |\}}| d j \}}‰ tj‡ fdd„| D ƒdd}tdd}| |¡}|jdd|jdd}	}
|
|	 }d||dk< ||	 | }|| }g }tt| ƒƒD ]/}||| |d | … }| 	||d¡}t
j|||ft
jd	}| |d
  dd
¡ tj¡¡ qM|S )zçCompute PCA on all features jointly, return per-frame PCA images.

    Args:
        all_features: list of (H_p, W_p, C) arrays
        target_size: (H, W) to upsample to
    Returns:
        list of (H, W, 3) uint8 PCA images
    r   c                    s   g | ]}|  d ˆ ¡‘qS )r$   )r,   )Ú.0Úf©ÚCr   r   Ú
<listcomp>[   ó    z%compute_joint_pca.<locals>.<listcomp>)Zaxisé   )Zn_componentsç      ð?r   r   éÿ   )Úshaper   Zconcatenater   Zfit_transformÚminÚmaxÚrangeÚlenr,   r   r   r   ÚappendÚclipr   Úuint8)Zall_featuresZtarget_sizer!   r"   r3   r4   ZstackedZpcaZtransformedZvminZvmaxÚrngZ	n_patchesZ
pca_imagesÚiZ	pca_frameZpca_rgbZpca_upr   rA   r   Úcompute_joint_pcaO   s"   	

 rR   Útopçš™™™™™á?r
   c              
   C   sÆ   |   ¡ } | jdd… \}}tj}t ||||¡\\}}	}
|dkr,|| d |	d }}n|| d |d }}t | |d ||	 d f|| d ||
 d fdd¡ t | |||f||d	|tj¡ | S )
zAdd text label.Nr
   rS   é
   é   é   ©r   r   r   r$   ©rG   rG   rG   ©r   rH   r   ÚFONT_HERSHEY_SIMPLEXÚgetTextSizeÚ	rectangleÚputTextÚLINE_AA)ÚframeÚtextZpositionZ
font_scaleZ	thicknessÚhÚwÚfontÚtwÚthÚblÚxÚyr   r   r   Ú	add_labelp   s   6rj   Tc              
   C   sº   |   ¡ } | jdd… \}}tj}d\}}t ||||¡\\}}	}
|r$dnd}|| d |	d }}t | |d ||	 d f|| d ||
 d f|d	¡ t | |||f||d
|tj¡ | S )z$Add success/failure badge top-right.Nr
   )gÍÌÌÌÌÌÜ?r   )r   é    r   )r   r   é´   rV   rU   rW   r$   rY   rZ   )r`   ra   Úsuccessrb   rc   rd   Zfsrf   re   Zthtrg   Zcolorrh   ri   r   r   r   Ú	add_badge   s   6rn   é,  c           I   
      sš  ddl }ddlm}m}m}m}m} |  ¡  | ¡ }dg}dg}|| D ]}||  |7  < ||d   |7  < q$|  	|¡}| j
j}dD ]}z|j  |¡}t g d¢¡|j j|< W qD tyb   Y qDw g d	¢}tƒ }|D ]}z| |j  |¡¡ W ql ty‚   Y qlw t|j jƒD ]}|j j| |v r›d
|j j| d< q‰| ¡  |dks¨|dkr°|| j|
||ƒ tdƒD ]}|  tjdtjd¡\}}}}q´|| j|
tƒ\}}}d} |dkrèd}!tj |!¡rètj |!|d !d¡} g }"g }#d}$d}%d}&d}'|$|	k rÈ|%sÈtj|d tj"d}(tj|d tj"d})||
› d }*t#||
ƒ}+||(|tƒ $|¡},t%|*tƒ $|¡}-t&||-|ƒ}.|" '|+¡ |# '|.¡ |dkrìtj|j(tj"d}/tj|j)tj"d}0tj*|(|/ |0|/ d  tj|d +dd¡ !d¡}1t,| -dddg¡d ƒ}2tj*|2|j. |j/|j. d  gtj|d +dd¡ !d¡}3i }4| durŸ| |4d< t 0¡  ||-|,f|1|3dœ|4¤Ž\}5}6}7W d  ƒ n	1 s¾w   Y  ||5|6|7|(|)ƒ}8|5d  1¡  2¡  3tj"¡|0|/  |/ ‰ ‡ fdd„tˆ j4d ƒD ƒ}9n,t 0¡  ||-|,ƒ\}:}}};W d  ƒ n	1 sw   Y  ||:||;|||(|)td\}8}9t5|8ƒD ]¤\}<}=|9|<  3tj"¡}>|=d }?d}@d }Ad!}B|@|Ak rŽtj|d tj"d}C|>|C }Dtj6 7|D¡}E|E|Bk rQn=t 8|Dd" dd#¡}Ftjdtjd}G|F|Gdd…< |'|Gd< |  |G¡\}}}%}|$d7 }$|@d7 }@|%rƒd$}&n|$|	kr‰n|@|Ak s7|%s±|$|	k r±tjdtjd}H|?|Hd< |  |H¡\}}}%}|$d7 }$|?}'|%r¸d$}& n	|$|	kr¿ nq|$|	k rÈ|%rü|"|#|&fS )%aÃ  Run a single episode using teleport+zero_rotation servo execution.

    For each replan step:
      - Predict a window of actions and 3D target positions
      - For each target in the window, servo to the 3D position (max 25 steps, 5mm threshold)
        with zero rotation, then apply gripper in a separate step

    Returns:
        rgb_frames: list of (H, W, 3) uint8
        features: list of (H_p, W_p, C) numpy arrays
        success: bool
    r   N)Úget_camera_paramsÚeef_to_start_kpÚdecode_window_actionsÚdecode_act_actionsÚ_reposition_camerarU   é&   r   )Zwooden_cabinet_1_mainZflat_stove_1_main)r   r   g      À)Zakita_black_bowl_2_mainZcookies_1_mainZ#glazed_rim_porcelain_ramekin_1_maing        rE   é   é   ©ÚdtypeÚactz8/data/libero/parsed_libero/libero_spatial/task_0_clip.pt©Zmap_locationFg      ð¿Zrobot0_eef_posZrobot0_eef_quatr:   ç:Œ0âŽyE>)ry   ÚdeviceZrobot0_gripper_qposÚclip_embedding)Úcurrent_eef_posZcurrent_gripperc                    s   g | ]}ˆ | ‘qS r   r   ©r?   Út©Z
pos_denormr   r   rC   	  s    z&run_policy_rollout.<locals>.<listcomp>)Z
image_sizeé   é   g{®Gázt?gš™™™™™©?rF   T)9r/   Úevalrp   rq   rr   rs   rt   Úresetr   Zset_init_stateÚenvÚsimZbody_name2idr   ÚarrayZbody_posÚ	ExceptionÚsetÚaddrK   ZngeomZgeom_bodyidZ	geom_rgbaZforwardÚstepÚzerosr   r;   ÚosÚpathÚexistsr   Úloadr   Zfloat64r>   Útor   r8   rM   ÚMIN_POSÚMAX_POSZtensorZclampr   ÚgetÚMIN_GRIPPERÚMAX_GRIPPERr&   r-   r.   r   rH   Ú	enumerateZlinalgr*   rN   )Ir‡   r/   r1   Ú
init_stater}   Úshift_dxÚshift_dyZ	cam_thetaZcam_phiÚ	max_stepsr=   Úmodel_modulerp   rq   rr   rs   rt   ÚstateZpick_obj_indicesZplace_obj_indicesÚsir<   rˆ   ÚnameZbidZdistractor_namesZdistractor_bidsZgeom_idÚ_Zworld_to_cameraZcamera_poseZcam_Kr~   Z	clip_pathZ
rgb_framesZ	feat_mapsZstep_idxZdonerm   Zcurrent_gripper_cmdr   Zcurrent_eef_quatr   Z	rgb_cleanZstart_kpr0   r7   Zmin_posZmax_posZeef_normZ
grip_stateZ	grip_normZextraZpos_predZrot_predZgripper_predZwindow_actionsZpred_3d_targetsZvolume_logitsZmodel_featsr   ÚactionZ
target_posZnew_gripperZservo_stepsZ	max_servoZ	thresholdZcur_posZdeltaZdistZdelta_clippedZservo_actionZgrip_actionr   r‚   r   Úrun_policy_rollout   s  
ÿÿ€ 



þýþý

ÿýüÿþ$
ÿ
ý



î
ÿž
er¤   c                    sÔ  i ‰ ‡ fdd„}| j d }|j |¡}t ¡ ( |  |¡\}\}}| j D ]}| jr0| j||dnd}	|||	ƒ}q$W d  ƒ n1 sBw   Y  | ¡  ˆ d }
|j}|
j\}}}|j	}|| }t ¡ Z |
}| 
|¡}| ||d||¡}t |d¡\}}}d	d
„ ||fD ƒ\}}| jdur| j||d}	| |||	¡\}}|d }t || | dd¡¡}tj| ¡ dd}W d  ƒ n1 sÀw   Y  dt| dƒrÎ| jnd }|ddd…d|d…f }| |||¡ ¡  ¡ }|S )u  Extract CLS-to-patch self-attention weights from the last block of a DINO backbone.

    Since DINOv3 uses scaled_dot_product_attention (which doesn't store weights),
    we manually compute attention weights by hooking into the last block's attention
    module and intercepting the QKV projection output.

    Args:
        dino: DINO backbone (DinoVisionTransformer)
        img_tensor: (1, 3, H, W) preprocessed image tensor

    Returns:
        attn_map: (num_heads, H_p, W_p) numpy array â€” CLS token attention to each patch
    c                    s   |d }|ˆ d< d S )Nr   rh   r   )ÚmoduleÚinputÚoutputrh   ©Zcapturedr   r   Úhook_fnQ  s   z*extract_cls_attention_map.<locals>.hook_fnr$   r    Nrh   rE   r
   c                 S   s   g | ]}|  d d¡‘qS )r   r
   )Ú	transposer€   r   r   r   rC   t  rD   z-extract_cls_attention_map.<locals>.<listcomp>g      à¿éþÿÿÿ)Zdimr   r#   r   )r(   ZattnZregister_forward_hookr   r&   r'   r)   ÚremoverH   Ú	num_headsÚqkvr,   ZunbindZ
apply_ropeÚmatmulrª   Zsoftmaxr   r+   r#   r-   r.   )r%   r0   r©   Z
last_blockZhandler2   r3   r4   r5   r6   rh   Zattn_moduleÚBÚNrB   r­   Zhead_dimZnormed_xr®   ÚqÚkÚvZscaleZattn_weightsZn_prefixZcls_attnr   r¨   r   Úextract_cls_attention_mapA  sD   


þþ


ìrµ   c                 C   s¼  |  tj¡d }|t t }t |¡ ddd¡ d¡ 	¡  
|¡}t| j|ƒ}t|j|ƒ}|jd }	tt}
}d|	 |	 }|| }|
}tj||dftjd}t |tj¡}t|ddƒ}||d	d	…d	|…f< d
d„ }t|	ƒD ](}d| }||| |ƒ}t|d|d › dƒ}||d	d	…|| |d | …f< qlt|	ƒD ]*}d|	 | }||| |ƒ}t|d|d › dƒ}||d	d	…|| |d | …f< q™tjtj |¡dd t ||¡ td|› ƒ d	S )a~  Generate a grid PNG comparing self-attention maps from ACT and PARA DINO backbones.

    Layout: RGB | ACT head 1..6 | PARA head 1..6

    Args:
        act_model: loaded ACT model
        para_model: loaded PARA model
        first_frame_rgb: (H, W, 3) uint8 RGB image (already resized to IMAGE_SIZE)
        device: torch device
        output_path: path to save the grid PNG
    r	   r
   r   r   rE   rx   ZRGBrS   Nc                 S   s\   |   ¡ }|| ¡  | ¡ | ¡  d  }tj|||ftjd}t |d  tj	¡tj
¡}|S )zPConvert a single attention head (H_p, W_p) to a colored heatmap (size, size, 3).r|   r   rG   )r   rI   rJ   r   r   r   ZapplyColorMapr   r   rO   ZCOLORMAP_INFERNO)Z	attn_headr   ÚaZa_upÚheatmapr   r   r   Úattn_to_heatmap­  s
    z4generate_attention_map_grid.<locals>.attn_to_heatmapz	ACT head z
PARA head T©Úexist_okzSaved attention map grid: )r   r   r   r   r   r   r   r   r   r   r“   rµ   r%   rH   r;   rŽ   rO   r   ÚcvtColorÚCOLOR_RGB2BGRrj   rK   r   Úmakedirsr   ÚdirnameZimwriteÚprint)Ú	act_modelÚ
para_modelÚfirst_frame_rgbr}   Úoutput_pathZimg_fr0   Zact_attnZ	para_attnr­   Zcell_hZcell_wZn_colsÚgrid_wÚgrid_hÚgridZrgb_bgrr¸   rb   Úcolr·   r   r   r   Úgenerate_attention_map_gridˆ  s8   $

""rÈ   c            9      C   s¾  t  ¡ } | jdtdd | jdtddd | jdtd	d
d | jdtdd | jdtddd |  ¡ }t tj	 
¡ r<dnd¡}td|› ƒ d}tj||d}|d d jd d }td|› dƒ dd l}ddlm} ||d}| |d ¡ | |¡ ¡ }dd l}dD ]}	|	 ¡ |v r—t||	||	 ¡  ƒ q…d}
tj|
|d}|d d jd d  }td!|› dƒ ||_dd"lm} ||d}| |d ¡ | |¡ ¡ }dD ]}	|	 ¡ |v rät||	||	 ¡  ƒ qÒtd#ƒ t ¡ d$ ƒ }| d¡}tj td%ƒ|j|j ¡}t!|t"t"d&gd'}| #d¡ | $¡  tj td(ƒ| %d¡¡}t& '|d)¡}t( )|d* |j* ¡}W d   ƒ n	1 s;w   Y  td+|j+› dƒ dD ]}	|	 ¡ |v r^t||	||	 ¡  ƒ qKt,||d,||d-|j+|j-d.\}}}td/t.|ƒ› d0|r}d1nd2› ƒ td3|j+› dƒ dD ]}	|	 ¡ |v r t||	||	 ¡  ƒ qt,||d4||d-|j+|j-d.\}}}td5t.|ƒ› d0|r¿d1nd2› ƒ td6ƒ |rÏ|d n|d }d7}t/|||||ƒ | 0¡  td8ƒ || }t1|t"t"fƒ}|d t.|ƒ… } |t.|ƒd … }!t2t.|ƒt.|ƒƒ}"t.|ƒ|"k r%| 3|d9 ¡ |  3| d9 ¡ t.|ƒ|"k st.|ƒ|"k rA| 3|d9 ¡ |! 3|!d9 ¡ t.|ƒ|"k s,td:|"› d;ƒ t"}#|#d< }$|#d< }%tj4tj 5|j6¡d=d> t7j8d?Ž }&t7 9|j6|&|j:|$|%f¡}'t;|"ƒD ]¬}(t7 <||( t7j=¡})t7 <| |( t7j=¡}*t7 <||( t7j=¡}+t7 <|!|( t7j=¡},t>|)d@dAƒ})t?|)d2dBdC})t>|*dDdAƒ}*t>|+dEdAƒ}+t?|+d1d=dC}+t>|,dFdAƒ},t( @|)|*g¡}-t( @|+|,g¡}.t( A|-|.g¡}/t7jB}0dG}1t7 C|1|0dHd<¡\\}2}3}4|$|2 d< }5|%dI }6t7 D|/|5dJ |6|3 dJ f|5|2 dJ |6|4 dJ fdKd9¡ t7 E|/|1|5|6f|0dHdLd<t7jF¡ |' G|/¡ qt|' H¡  |j6 IdMdN¡}7t JdO|j6› dP|7› dQ¡}8|8dkrEt I|7|j6¡ tdR|j6› ƒ tdS|"|j: dT›dU|j:› dVƒ d S )WNz--output_pathzP/data/cameron/para/.agents/reports/project_site/media/feature_pca_comparison.mp4)ÚtypeÚdefaultz
--shift_dyg
×£p=
Ç?zObject position shift (OOD))rÉ   rÊ   Úhelpz--fpsrv   z*Output FPS (each frame is one replan step)z--max_stepsro   z--episode_idxr   z4Which init state to use (1 gave PARA success before)Úcudar-   zDevice: zP/data/cameron/para_normalized_losses/libero/checkpoints/act_v2_exp4_n64/best.pthr{   Zmodel_state_dictzpos_mlp.5.weightr   rE   zLoading ACT model (N_WINDOW=z)...)ÚACTPredictor)Zn_window)	r”   r•   Z
MIN_HEIGHTZ
MAX_HEIGHTr—   r˜   ZMIN_ROTZMAX_ROTZREF_ROTzQ/data/cameron/para_normalized_losses/libero/checkpoints/para_v2_exp4_n64/best.pthzvolume_head.weighté    zLoading PARA model (N_WINDOW=)ÚTrajectoryHeatmapPredictorz Setting up LIBERO environment...Zlibero_spatialZ
bddl_filesr9   )Zbddl_file_nameZcamera_heightsZcamera_widthsZcamera_namesZdatasetsÚrzdata/demo_0/statesz
Running ACT rollout (shift_dy=rz   g{®Gáz´¿)r›   rœ   r   z  ACT: z replan steps, ÚSUCCESSÚFAILUREzRunning PARA rollout (shift_dy=Zparaz  PARA: z Generating attention map grid...zL/data/cameron/para/.agents/reports/project_site/media/attention_map_grid.pngzComputing joint PCA...r$   z
Composing z-frame 2x2 grid video...r
   Tr¹   Zmp4vzACT RolloutrS   F)rm   zACT Features (DINO PCA)zPARA RolloutzPARA Features (DINO PCA)z4Same backbone. Same features. Different action head.g333333ã?é   rƒ   rX   rY   z.mp4z	_h264.mp4zffmpeg -y -i "z?" -c:v libx264 -preset ultrafast -crf 23 -movflags +faststart "z" 2>/dev/nullzSaved: z
Duration: z.1fzs at z fps)KÚargparseÚArgumentParserÚadd_argumentÚstrr   ÚintÚ
parse_argsr   r}   rÌ   Zis_availabler¿   r’   rH   r/   Z	model_actrÍ   Zload_state_dictr“   r…   ÚlowerÚsetattrZN_WINDOWrÏ   Úbm_libZget_benchmark_dictZget_taskr   r   Újoinr   Zproblem_folderÚ	bddl_filer   r;   Zseedr†   Zget_task_demonstrationÚh5pyZFiler   r‰   Zepisode_idxrœ   r¤   r   rL   rÈ   ÚcloserR   rJ   rM   r½   r¾   rÃ   r   ZVideoWriter_fourccZVideoWriterZfpsrK   r»   r¼   rj   rn   ZhstackZvstackr[   r\   r]   r^   r_   ÚwriteÚreleaseÚreplaceÚsystem)9ÚparserÚargsr}   Zact_ckptZact_sdZact_n_windowrž   rÍ   rÀ   ÚkeyZ	para_ckptZpara_sdZpara_n_windowrÏ   rÁ   r   ZtaskrÞ   r‡   Z	demo_pathr@   rš   Zact_rgbsZ	act_featsZact_successZ	para_rgbsZ
para_featsZpara_successrÂ   Zattn_grid_pathZ	all_featsZall_pca_imagesZact_pca_imagesZpara_pca_imagesZ
max_framesZ	cell_sizerÄ   rÅ   ZfourccÚwriterrQ   Zact_rgb_bgrZact_pca_bgrZpara_rgb_bgrZpara_pca_bgrZtop_rowZ
bottom_rowrÆ   rd   ra   re   rf   rg   rh   ri   Z	h264_pathÚretr   r   r   ÚmainË  s  ÿ
ÿ
ÿ
ÿ
€
€
ÿü
ÿÿ€

þ"€

þ"þþ
6
ÿÿ
$rê   Ú__main__)r   )r9   )rS   rT   r
   )T)r   r   r   r   ro   r9   )(Ú__doc__r   ÚsysrÔ   r.   r   r   Ztorch.nn.functionalÚnnZ
functionalÚFr   Úpathlibr   Zsklearn.decompositionr   r   ÚinsertZlibero.libero.envsr   Zlibero.liberor   rÜ   r   rß   r;   r‰   r   r   r   r8   r>   rR   rj   rn   r¤   rµ   rÈ   rê   Ú__name__r   r   r   r   Ú<module>   s@    
	

!

þ 5GC 4
ÿ