Ë
    ðì�i:  ã                   óF  — d Z ddlZddlZddlmZmZmZmZ ddlmZ	 ddl
mZ ddlmZ ddlmZ dd	lmZ d
dlmZ  G d„ de«      Z G d„ de«      Z G d„ de«      Z G d„ de«      Z G d„ de«      Z G d„ de«      Z G d„ de«      Z G d„ de«      Z G d„ de«      Zdd„Zy) z‘
PassportEye::MRZ: Machine-readable zone extraction and parsing.
Image processing for MRZ extraction.

Author: Konstantin Tretyakov
License: MIT
é    N)Ú	transformÚ
morphologyÚfiltersÚmeasure)Úioé   )Úextract_first_jpeg_in_pdf)ÚPipeline)Ú
RotatedBox)Úocré   )ÚMRZc                   ó.   — e Zd ZdZg ZdgZdd„Zd„ Zd„ Zy)ÚLoaderzLoads `file` to `img`.Úimgc                 ó.   — || _         || _        || _        y ©N)ÚfileÚas_grayÚ	pdf_aware)Úselfr   r   r   s       úz/var/www/ITT-ServiceAPI-Services/ITT-Document-Scan/mrzscanner/mrzenv/lib/python3.12/site-packages/passporteye/mrz/image.pyÚ__init__zLoader.__init__   s   € ØˆŒ	ØˆŒØ"ˆ�ó    c                 óÂ   — t        j                  || j                  d¬«      }|�:t        |j                  «      dk7  r"t        j                  || j                  d¬«      }|S )z+Proxy to skimage.io.imread with some fixes.Úimageio)r   Úpluginr   Ú
matplotlib)Ú
skimage_ioÚimreadr   ÚlenÚshape)r   r   r   s      r   Ú_imreadzLoader._imread   sM   € ô ×Ñ ¨d¯l©lÀ9ÔMˆØˆ?œs 3§9¡9›~°Ò2ä×#Ñ# D°$·,±,À|ÔTˆCØˆ
r   c                 óö  — t        | j                  t        «      rŽ| j                  rg| j                  j	                  «       j                  d«      r>t        | j                  d«      5 }t        |«      }d d d «       €y | j                  |«      S | j                  | j                  «      S t        | j                  t        t        j                  f«      r| j                  | j                  «      S y # 1 sw Y   Œ~xY w)Nz.pdfÚrb)Ú
isinstancer   Ústrr   ÚlowerÚendswithÚopenr	   r#   Úbytesr   ÚIOBase)r   ÚfÚimg_datas      r   Ú__call__zLoader.__call__+   s¶   € Ü�d—i‘i¤Ô%Ø�~Š~ $§)¡)§/¡/Ó"3×"<Ñ"<¸VÔ"DÜ˜$Ÿ)™) TÓ*ð <¨aÜ8¸Ó;�H÷<àÐ#ØØ—|‘| HÓ-Ð-à—|‘| D§I¡IÓ.Ð.Ü˜Ÿ	™	¤E¬2¯9©9Ð#5Ô6Ø—<‘< §	¡	Ó*Ð*Ø÷<ð <ús   Á&C/Ã/C8N)TT)	Ú__name__Ú
__module__Ú__qualname__Ú__doc__Ú__depends__Ú__provides__r   r#   r/   © r   r   r   r      s    „ Ù à€KØ�7€Ló#ò
ór   r   c                   ó,   — e Zd ZdZdgZddgZdd„Zd„ Zy)	ÚScalerzEScales `image` down to `img_scaled` so that its width is at most 250.r   Ú	img_smallÚscale_factorc                 ó   — || _         y r   )Ú	max_width)r   r<   s     r   r   zScaler.__init__@   s	   € Ø"ˆ�r   c                 ó¢   — | j                   t        |j                  d   «      z  }|dk  rt        j                  ||dd d¬«      }||fS d}|}||fS )Nr   ÚconstantT)ÚmodeÚchannel_axisÚanti_aliasingç      ð?)r<   Úfloatr"   r   Úrescale)r   r   r:   r9   s       r   r/   zScaler.__call__C   sc   € Ø—~‘~¬¨c¯i©i¸©lÓ(;Ñ;ˆØ˜1ÒÜ!×)Ñ)¨#¨|À*Ð[_ÐosÔtˆIð ˜,Ð&Ð&ð ˆLØˆIØ˜,Ð&Ð&r   N)éú   ©r0   r1   r2   r3   r4   r5   r   r/   r6   r   r   r8   r8   :   s    „ ÙOà�'€KØ Ð0€Ló#ó'r   r8   c                   ó*   — e Zd ZdZdgZdgZdd„Zd„ Zy)ÚBooneTransformzùProcesses `img_small` according to Hans Boone's method
    (http://www.pyimagesearch.com/2015/11/30/detecting-machine-readable-zones-in-passport-images/)
    Outputs a `img_binary` - a result of threshold_otsu(closing(sobel(black_tophat(img_small)))r9   Ú
img_binaryc                 ó   — || _         y r   )Úsquare_size)r   rK   s     r   r   zBooneTransform.__init__U   s
   € Ø&ˆÕr   c                 ó  — t        j                  | j                  «      }t        j                  ||«      }t	        t        j                  |«      «      }t        j                  ||«      }t        j                  |«      }||kD  S r   )	r   ÚsquarerK   Úblack_tophatÚabsr   Úsobel_vÚclosingÚthreshold_otsu)r   r9   ÚmÚimg_thÚimg_sobÚ
img_closedÚ	thresholds          r   r/   zBooneTransform.__call__X   sj   € Ü×Ñ˜d×.Ñ.Ó/ˆÜ×(Ñ(¨°AÓ6ˆÜ”g—o‘o fÓ-Ó.ˆÜ×'Ñ'¨°Ó3ˆ
Ü×*Ñ*¨:Ó6ˆ	Ø˜IÑ%Ð%r   N)é   rF   r6   r   r   rH   rH   M   s"   „ ñcð �-€KØ �>€Ló'ó&r   rH   c                   óL   — e Zd ZdZdgZdgZ	 	 dd„Zd„ Zd„ Zd„ Z	d„ Z
d	„ Zd
„ Zy)ÚMRZBoxLocatorzPExtracts putative MRZs as RotatedBox instances from the contours of `img_binary`rI   Úboxesc                 óf   — || _         || _        || _        || _        || _        || _        || _        y r   )Ú	max_boxesÚmin_points_in_contourÚmin_areaÚmin_box_aspectÚ	angle_tolÚlineskip_tolÚbox_type)r   r]   r^   r_   r`   ra   rb   rc   s           r   r   zMRZBoxLocator.__init__g   s6   € à"ˆŒØ%:ˆÔ"Ø ˆŒØ,ˆÔØ"ˆŒØ(ˆÔØ ˆ�r   c                 ó"  — t        j                  |d«      }g }|D ]³  }t        j                  |d«      t        j                  |d«      }}||z
  }|d   |d   z  | j
                  k  rŒMt        j                  || j                  «      }|j                  dk(  s&|j                  |j                  z  | j                  k  rŒ£|j                  |«       Œµ |j                  d„ ¬«       | j                  | j                  |d| j                    «      «      S )Nç      à?r   r   c                 ó   — | j                    S r   )Úarea©Úxs    r   ú<lambda>z(MRZBoxLocator.__call__.<locals>.<lambda>†   s   €  A§F¡F 7€ r   ©Úkey)r   Úfind_contoursÚnpÚminÚmaxr_   r   Úfrom_pointsrc   ÚheightÚwidthr`   ÚappendÚsortÚ_fixup_boxesÚ_merge_boxesr]   )	r   rI   ÚcsÚresultsÚcÚllÚurÚwhr%   s	            r   r/   zMRZBoxLocator.__call__q   sñ   € Ü×"Ñ" :¨sÓ3ˆð ˆØò 	ˆAä—V‘V˜A˜q“\¤2§6¡6¨!¨Q£<�ˆBØ�b‘ˆBØ�!‰u�r˜!‘u‰}˜tŸ}™}Ò,Øô ×'Ñ'¨¨4¯=©=Ó9ˆBØ�y‰y˜AŠ~ §¡¨B¯I©IÑ!5¸×8KÑ8KÒ!KØð �N‰N˜2Õð	ð  	�‰Ñ*ˆÔ+Ø× Ñ  ×!2Ñ!2°7¸1¸T¿^¹^Ð3LÓ!MÓNÐNr   c                 ó¦   — t        ||z
  «      | j                  k  xs5 t        t        j                  t        ||z
  «      z
  «      | j                  k  S )z/Are two boxes aligned according to their angle?)rO   ra   rn   Úpi)r   Úb1Úb2s      r   Ú_are_aligned_anglesz!MRZBoxLocator._are_aligned_angles‰   s=   € ä�2˜‘7‹|˜tŸ~™~Ñ-Ò\´´R·U±U¼SÀÀbÁ»\Ñ5IÓ1JÈdÏnÉnÑ1\Ð\r   c           	      ó2  — | j                  |j                  |j                  «      syt        |j                  |j                  «      }t        t	        j
                  |j                  |j                  z
  t	        j                  |«       t	        j                  |«      g«      «      | j                  |j                  |j                  z   z  k  xrI |j                  dkD  xr8 |j                  dkD  xr' d|j                  |j                  z  cxk  xr dk  S c S )z5Are two boxes nearby, parallel, and similar in width?Fr   re   g       @)r‚   Úanglero   rO   rn   ÚdotÚcenterÚsinÚcosrb   rr   rs   )r   r€   r�   r„   s       r   Ú_are_nearby_parallel_boxesz(MRZBoxLocator._are_nearby_parallel_boxes�   sä   € à×'Ñ'¨¯©°"·(±(Ô;Øä�B—H‘H˜bŸh™hÓ'ˆÜ”2—6‘6˜"Ÿ)™) b§i¡iÑ/´2·6±6¸%³=°.Ä"Ç&Á&ÈÃ-Ð1PÓQÓRÐUY×UfÑUfØ�I‰I˜Ÿ	™	Ñ!ñV#ñ #ò oØ(*¯©°1©òoØ;=¿8¹8Àa¹<òoØNQÐTV×T\ÑT\Ð_a×_gÑ_gÑTgÖNmÐjmÑNmð	oÙNmð	or   c                 óö  — t        |«      }t        |«      D ]à  }t        |dz   |«      D ]Ì  }| j                  ||   ||   «      sŒ||   ||   }}t        j                  |j
                  |j
                  g«      }t        j                  || j                  «      }|j                  |j                  z  | j                  k\  sŒ˜|j                  |«       |j                  |«       |j                  |«         y Œâ y)zcGiven a list of boxes, finds two nearby parallel ones and merges them. Returns false if none found.r   TF)r!   Úranger‰   rn   ÚvstackÚpointsr   rq   rc   rs   rr   r`   Úremovert   )	r   Úbox_listÚnÚiÚjÚaÚbÚmerged_pointsÚ
merged_boxs	            r   Ú_merge_any_two_boxesz"MRZBoxLocator._merge_any_two_boxes–   sÜ   € ä�‹MˆÜ�q“ò 	$ˆAÜ˜1˜q™5 !“_ò 
$�Ø×2Ñ2°8¸A±;ÀÈÁÕLà# A™;¨°©�q�AÜ$&§I¡I¨q¯x©x¸¿¹Ð.BÓ$C�MÜ!+×!7Ñ!7¸ÀtÇ}Á}Ó!U�JØ!×'Ñ'¨*×*;Ñ*;Ñ;¸t×?RÑ?RÓRØ Ÿ™¨Ô*Ø Ÿ™¨Ô*Ø Ÿ™¨
Ô3Ú#ñ
$ð	$ð r   c                 óN   — | j                  |«      r	 | j                  |«      rŒ|S )z0Mergest nearby parallel boxes in the given list.)r—   )r   r�   s     r   rw   zMRZBoxLocator._merge_boxes§   s(   € à×'Ñ'¨Ô1Øð ×'Ñ'¨Õ1àˆr   c                 óî   — |D ]o  }t        t        |j                  «      t        j                  dz  z
  «      dk  rt        j                  dz  |_        t        |j                  «      dk  sŒid|_        Œq |S )a5  If the box's angle is np.pi/2 +- 0.01, we shall round it to np.pi/2.

        This way image extraction is fast and introduces no distortions.
        and this may be more important than being perfectly straight
        similar for 0 angle.
        The box_list is fixed in-place (but also returned).
        r   ç{®Gáz„?g        )rO   r„   rn   r   )r   r�   Úboxs      r   rv   zMRZBoxLocator._fixup_boxes­   sb   € ð ò 	 ˆCÜ”3�s—y‘y“>¤B§E¡E¨A¡IÑ-Ó.°$Ò6ÜŸE™E A™I�”	Ü�3—9‘9‹~ Ó%Ø�•	ð		 ð
 ˆr   N)é   é2   iô  rX   gš™™™™™¹?g      ø?Úbb)r0   r1   r2   r3   r4   r5   r   r/   r‚   r‰   r—   rw   rv   r6   r   r   rZ   rZ   a   s?   „ ÙZà�.€KØ�9€LàhkØ,0ó!òOò0]òoòò"ór   rZ   c                   ó$   — e Zd ZdZdgZg d¢Zd„ Zy)ÚExtractAllBoxeszBExtract all the images from the boxes, for external OCR processingÚrois)r[   r   r:   c                 óR   — |D �cg c]  }|j                  |d|z  «      ‘Œ c}S c c}w )NrB   )Úextract_from_image)r   r[   r   r:   r›   s        r   r/   zExtractAllBoxes.__call__Â   s(   € ØKPÖQÀC�×&Ñ& s¨C°,Ñ,>Õ?ÒQÐQùÒQs   …$N)r0   r1   r2   r3   r5   r4   r/   r6   r   r   r    r    ¼   s   „ ÙLà�8€LÚ2€KóRr   r    c                   ó.   — e Zd ZdZg d¢Zg d¢Zdd„Zd„ Zy)ÚFindFirstValidMRZzzIterates over boxes found by MRZBoxLocator, passes them to BoxToMRZ, finds the first valid MRZ
    or the best-scoring MRZ)Úbox_idxÚroiÚtextÚmrz)r[   r   r9   r:   Ú__data__c                 ó(   — t        ||¬«      | _        y )N©Úextra_cmdline_params)ÚBoxToMRZÚ
box_to_mrz©r   Úuse_original_imager­   s      r   r   zFindFirstValidMRZ.__init__Í   s   € Ü"Ð#5ÐL`Ôaˆ�r   c                 ó<  — g }g |d<   t        |«      D ]m  \  }}| j                  ||||«      \  }	}
}|d   j                  |	|
|f«       |j                  r||	|
|fc S |j                  dkD  sŒY|j                  ||	|
|f«       Œo |sy|j                  d„ ¬«       |d   S )NÚ__debug__mrzr   )NNNNc                 ó    — | d   j                   S )Né   )Úvalid_scorerh   s    r   rj   z,FindFirstValidMRZ.__call__.<locals>.<lambda>Ý   s   €  A a¡D×$4Ñ$4€ r   rk   éÿÿÿÿ)Ú	enumerater¯   rt   Úvalidr¶   ru   )r   r[   r   r9   r:   ÚdataÚmrzsr‘   r”   r§   r¨   r©   s               r   r/   zFindFirstValidMRZ.__call__Ð   s´   € ØˆØ!ˆˆ^ÑÜ˜eÓ$ò 	1‰DˆAˆqØ!Ÿ_™_¨Q°°YÀÓM‰NˆC��sØ�Ñ ×'Ñ'¨¨d°CÐ(8Ô9Ø�yŠyØ˜#˜t SÐ(Ò(Ø—‘ 1Ó$Ø—‘˜Q  T¨3Ð/Õ0ð	1ñ Ø)à�I‰IÑ4ˆIÔ5Ø˜‘8ˆOr   N©TÚ ©r0   r1   r2   r3   r5   r4   r   r/   r6   r   r   r¥   r¥   Æ   s   „ ñò 5€LÚK€Kóbór   r¥   c                   ó<   — e Zd ZdZg d¢Zg d¢Zd	d„Zd„ Zd
d„Zd„ Z	y)r®   ztExtracts ROI from the image, corresponding to a box found by MRZBoxLocator, does OCR and MRZ parsing on this region.)r§   r¨   r©   )r›   r   r9   r:   c                 ó    — || _         || _        y)zo
        :param use_original_image: when True, the ROI is extracted from img, otherwise from img_small
        N)r±   r­   r°   s      r   r   zBoxToMRZ.__init__ç   s   € ð #5ˆÔØ$8ˆÕ!r   c                 óR  — | j                   r|n|}| j                   rd|z  nd}|j                  ||«      }t        || j                  ¬«      }d|v sd|v r(d|vr$|d d d…d d d…f   }t        || j                  ¬«      }d|vr||t	        j
                  |«      fS t	        j
                  |«      }d|j                  d<   |j                  s| j                  |||«      \  }}|j                  s| j                  |||d	«      \  }}|j                  s| j                  |||«      \  }}|||fS )
NrB   r¬   z>>ú>ú<r·   ÚdirectÚmethodr   )
r±   r£   r   r­   r   Úfrom_ocrÚauxr¹   Ú_try_larger_imageÚ_try_black_tophat)	r   r›   r   r9   r:   Úscaler§   r¨   r©   s	            r   r/   zBoxToMRZ.__call__î   s+  € Ø×,Ò,‰c°)ˆØ&*×&=Ò&=��lÒ"À3ˆØ×$Ñ$ S¨%Ó0ˆÜ�3¨T×-FÑ-FÔGˆà�4‰<˜C 4™K¨C°t©Oà‘d˜�d™D˜b˜D�j‘/ˆCÜ�s°×1JÑ1JÔKˆDà�d‰?à˜œcŸl™l¨4Ó0Ð0Ð0ä�l‰l˜4Ó ˆØ$ˆ�‰�Ñð �yŠyØ×.Ñ.¨s°D¸#Ó>‰IˆD�#ð �yŠyØ×.Ñ.¨s°D¸#¸qÓA‰IˆD�#à�yŠyØ×.Ñ.¨s°D¸#Ó>‰IˆD�#à�D˜#ˆ~Ðr   c                 óV  — |j                   d   dk  r”t        d|j                   d   z  dz   «      }t        j                  |||ddd¬«      }t	        || j
                  ¬	«      }t        j                  |«      }d
|z  |j                  d<   |j                  |j                  kD  r|}|}||fS )z…Attempts to improve the OCR result by scaling the image. If the new mrz is better, returns it, otherwise returns
        the old mrz.r   i¼  g     h�@re   r>   NT)Úorderr?   r@   rA   r¬   zrescaled(%d)rÅ   )
r"   Úintr   rD   r   r­   r   rÆ   rÇ   r¶   )	r   r§   Úcur_textÚcur_mrzÚfilter_orderÚscale_byÚroi_lgÚnew_textÚnew_mrzs	            r   rÈ   zBoxToMRZ._try_larger_image  sª   € ð �9‰9�Q‰<˜3ÒÜ˜6 C§I¡I¨a¡LÑ0°3Ñ6Ó7ˆHÜ×&Ñ& s¨H¸LÈzÐhlØ59ô;ˆFä˜6¸×8QÑ8QÔRˆHÜ—l‘l 8Ó,ˆGØ$2°\Ñ$AˆG�K‰K˜Ñ!Ø×"Ñ" W×%8Ñ%8Ò8Ø!�Ø#�Ø˜Ð Ð r   c                 ó�  — t        j                  |t        j                  d«      «      }t        || j                  ¬«      }t        j                  |«      }|j                  |j                  kD  rd|j                  d<   ||}}| j                  |||«      \  }}|j                  |j                  kD  rd|j                  d<   ||}}||fS )NrX   r¬   rN   rÅ   zblack_tophat(rescaled(3)))
r   rN   Údiskr   r­   r   rÆ   r¶   rÇ   rÈ   )r   r§   rÎ   rÏ   Úroi_brÓ   rÔ   s          r   rÉ   zBoxToMRZ._try_black_tophat  s·   € Ü×'Ñ'¨¬Z¯_©_¸QÓ-?Ó@ˆä�u°4×3LÑ3LÔMˆÜ—,‘,˜xÓ(ˆØ×Ñ ×!4Ñ!4Ò4Ø$2ˆG�K‰K˜Ñ!Ø (¨'�gˆHà ×2Ñ2°5¸(ÀGÓLÑˆ�'Ø×Ñ ×!4Ñ!4Ò4Ø$?ˆG�K‰K˜Ñ!Ø (¨'�gˆHà˜Ð Ð r   Nr¼   )rµ   )
r0   r1   r2   r3   r5   r4   r   r/   rÈ   rÉ   r6   r   r   r®   r®   á   s#   „ Ù~â)€LÚ=€Kó9òó>!ó!r   r®   c                   ó,   — e Zd ZdZdgZddgZdd„Zd„ Zy)	ÚTryOtherMaxWidthz—
    If mrz was not found so far in the current pipeline,
    changes the max_width parameter of the scaler to 1000 and reruns the pipeline again.
    Ú	mrz_finalr©   Ú__pipeline__c                 ó   — || _         y r   )Úother_max_width)r   rÝ   s     r   r   zTryOtherMaxWidth.__init__6  s
   € Ø.ˆÕr   c                 ó  — |€†|d   j                  «       dk  s|d   j                  «       dkD  rZ|j                  dt        | j                  «      «       |d   }|�,|j                  d   d| j                  z  z   |j                  d<   |}|S )	NrI   rš   r   gffffffî?Úscalerr©   rÅ   z|max_width(%d))ÚmeanÚreplace_componentr8   rÝ   rÇ   )r   r©   rÛ   rÔ   s       r   r/   zTryOtherMaxWidth.__call__9  s‘   € àˆ;˜L¨Ñ6×;Ñ;Ó=ÀÒDÈÐUZÑH[×H`ÑH`ÓHbÐeiÒHiØ×*Ñ*¨8´V¸D×<PÑ<PÓ5QÔRØ" 5Ñ)ˆGØÐ"Ø(/¯©°HÑ(=Ð@PÐSW×SgÑSgÑ@gÑ(g�—‘˜HÑ%ØˆCØˆ
r   N)iè  r¾   r6   r   r   rÙ   rÙ   -  s$   „ ñð
  �=€LØ˜.Ð)€Kó/ór   rÙ   c                   ó4   ‡ — e Zd ZdZdˆ fd„	Zed„ «       Zˆ xZS )ÚMRZPipelinezYThis is the "currently best-performing" pipeline for parsing MRZ from a given image file.c                 ó¸  •— t         t        | �  «        d| _        || _        | j                  dt        |«      «       | j                  dt        «       «       | j                  dt        «       «       | j                  dt        «       «       | j                  dt        |¬«      «       | j                  dt        «       «       | j                  d	t        «       «       y )
Nz1.0Úloaderrß   ÚbooneÚbox_locatorr©   r¬   rÝ   Ú	extractor)Úsuperrã   r   Úversionr   Úadd_componentr   r8   rH   rZ   r¥   rÙ   r    )r   r   r­   Ú	__class__s      €r   r   zMRZPipeline.__init__G  sª   ø€ ÜŒk˜4Ñ)Ô+ØˆŒØˆŒ	Ø×Ñ˜8¤V¨D£\Ô2Ø×Ñ˜8¤V£XÔ.Ø×Ñ˜7¤NÓ$4Ô5Ø×Ñ˜=¬-«/Ô:Ø×Ñ˜5Ô"3ÐI]Ô"^Ô_Ø×ÑÐ,Ô.>Ó.@ÔAð 	×Ñ˜;¬Ó(9Õ:r   c                 ó   — | d   S )NrÚ   r6   )r   s    r   ÚresultzMRZPipeline.resultU  s   € à�KÑ Ð r   )r½   )r0   r1   r2   r3   r   Úpropertyrî   Ú__classcell__)rì   s   @r   rã   rã   D  s   ø„ Ùcõ;ð ñ!ó ô!r   rã   c                 ób   — t        | |«      }|j                  }|�|r|d   |j                  d<   |S )a¶  The main interface function to this module, encapsulating the recognition pipeline.
       Given an image filename, runs MRZPipeline on it, returning the parsed MRZ object.

    :param file: A filename or a stream to read the file data from.
    :param save_roi: when this is True, the .aux['roi'] field will contain the Region of Interest where the MRZ was parsed from.
    :param extra_cmdline_params:extra parameters to the ocr.py
    r§   )rã   rî   rÇ   )r   Úsave_roir­   Úpr©   s        r   Úread_mrzrô   Z  s7   € ô 	�DÐ.Ó/€AØ
�(‰(€CØ
€™8Ø˜5™ˆ�‰�‰Ø€Jr   )Fr½   )r3   r   Únumpyrn   Úskimager   r   r   r   r   Úutil.pdfr	   Úutil.pipeliner
   Úutil.geometryr   Úutil.ocrr   r¨   r   Úobjectr   r8   rH   rZ   r    r¥   r®   rÙ   rã   rô   r6   r   r   ú<module>rü      s¤   ðñó 
Û ß ;Ó ;Ý $Ý 0Ý $Ý &Ý Ý ô$ˆVô $ôN'ˆVô 'ô&&�Vô &ô(Y�Fô YôvR�fô Rô˜ô ô6I!ˆvô I!ôX�vô ô.!�(ô !ô,r   