Ë
    yÌj{   ã            
       ó(  — d Z ddlZddlZddlmZ ddlmZ ddlZddlZ	 ej                  e«      Z ej                  «       Zda ej                   d«      Zdedee   fd	„Zdd
ddee   ddfd„Zdddee   fd„Zdd„Z	 	 ddee   dddedee   fd„Zy)u  
qr_detect.py â€” lectura de cÃ³digos de familia en una imagen (cascada de hec-photo-app).

La usan el organizer (fotos que llegan por FTP) y el escaneo de fotogramas de la cÃ¡mara
(`scan_service`), para que los dos lean igual de bien. Cascada, de barata a cara:

  0. Puerta: Â¿hay un patrÃ³n QR visible? Se consultan los DOS detectores de OpenCV porque
     ninguno basta solo (medido sobre 250 tarjetas reales fotografiadas a tres distancias):

                            primer plano   media    lejos
         detect()               96,7 %     93,3 %   15,0 %   â†� falla el QR pequeÃ±o
         detectMulti()          25,0 %     93,3 %   96,7 %   â†� falla el primer plano
         detect | detectMulti   98,3 %     98,3 %   98,3 %

     El 90 % de las fotos son retratos sin QR y salen aquÃ­ sin mÃ¡s coste.
  1. DescodificaciÃ³n mÃºltiple (varias tarjetas en la foto; tolera el marco redondeado de la tarjeta).
  2. Gris tal cual.   3. CLAHE (reflejos / luz desigual).   4. Otsu (desenfoque / ruido).
  5. Enfoque (unsharp).
  6. OCR del cÃ³digo IMPRESO bajo el QR (tarjeta rayada o doblada). Solo con prefijos conocidos.

âš ï¸� El OCR lee el prefijo del colegio, no uno fijo: es el paso mÃ¡s caro (tesseract sobre ~12 Mpx)
y con un prefijo equivocado se pagarÃ­a entero para no encontrar nunca nada. Los prefijos salen
de la configuraciÃ³n del colegio MÃ�S los que ya estÃ©n en disco (la config y las tarjetas ya
repartidas no siempre coinciden). Si tesseract no estÃ¡ instalado, el primer fallo desactiva el
paso para siempre: no se reintenta foto a foto.
é    N)ÚPath)ÚOptionalFz^[A-Z0-9][A-Z0-9-]{1,23}$ÚrawÚreturnc                 ó   — | xs dj                  «       }|sy|j                  «       j                  d«      r`t        j                  d|t        j
                  «      xs% t        j                  d|t        j
                  «      }|sy|j                  d«      }|j                  dd«      j                  «       }t        j                  |«      r|S dS )uR  Contenido de un QR â†’ cÃ³digo de familia, o None si no es un cÃ³digo vÃ¡lido.

    Acepta el cÃ³digo a secas, una URL `â€¦/r/CÃ“DIGO` o `â€¦?code=CÃ“DIGO`. La validaciÃ³n de formato no es
    cosmÃ©tica: el cÃ³digo acaba siendo el nombre de una carpeta, asÃ­ que un QR malicioso con `../../x`
    no puede escribir fuera de photos/.
    Ú N)zhttp://zhttps://z/r/([^/?#\s]+)z[?&]code=([^&#\s]+)é   ú )ÚstripÚlowerÚ
startswithÚreÚsearchÚ
IGNORECASEÚgroupÚreplaceÚupperÚCODE_REÚmatch)r   ÚsÚmÚcodes       ú-/var/www/photos/backend/services/qr_detect.pyÚextract_coder   ,   s¥   € ð 
Š�×ÑÓ€AÙØØ‡w�wƒy×ÑÐ3Ô4Ü�I‰IÐ'¨¬B¯M©MÓ:Òq¼b¿i¹iÐH^Ð`aÔce×cpÑcpÓ>qˆÙØØ�G‰G�A‹JˆØ�9‰9�S˜"Ó×#Ñ#Ó%€DÜ—=‘= Ô&ˆ4Ð0¨DÐ0ó    Ú
configuredzstr | set[str] | NoneÚ
output_dirzset[str]c                 ó¦  — t        «       }t        | t        «      r"| r |j                  | j	                  «       «       n&| r$|| D �ch c]  }|sŒ|j	                  «       ’Œ c}z  }|r×	 |j                  «       D ]À  }|j                  «       r|j                  j                  d«      rŒ/|j                  «       D ]  }|j                  «       r|j                  j                  d«      rŒ/t        j                  d|j                  «      }|sŒR|j                  |j                  d«      j	                  «       «       Œ� ŒÂ 	 |S |S c c}w # t        $ r Y |S w xY w)ud   Prefijos de cÃ³digo en circulaciÃ³n: los configurados + los de las carpetas que ya existen en disco.)ú.Ú_z^([A-Za-z]{2,6})[-0-9]r	   )ÚsetÚ
isinstanceÚstrÚaddr   ÚiterdirÚis_dirÚnamer   r   r   r   ÚOSError)r   r   ÚprefixesÚcÚphotographerÚfolderr   s          r   Úvalid_prefixesr-   ?   s  € ä›€HÜ�*œcÔ"¡zØ�‰�Z×%Ñ%Ó'Õ(Ù	Ø¨
Ö8 1²a�Q—W‘W•YÒ8Ñ8ˆÙð	Ø *× 2Ñ 2Ó 4ò 9�Ø#×*Ñ*Ô,°×0AÑ0A×0LÑ0LÈZÔ0XØØ*×2Ñ2Ó4ò 9�FØ!Ÿ=™=œ?¨f¯k©k×.DÑ.DÀZÔ.PØ ÜŸ™Ð!:¸F¿K¹KÓH�AÚØ Ÿ™ Q§W¡W¨Q£Z×%5Ñ%5Ó%7Õ8ñ9ñ9ð €Oˆ8€Oùò 9øô ò 	ØØ€Oð	ús%   ÁD>ÁD>Á&B E Ä2E Å	EÅEr)   c                 óv  — t         s|sy	 ddl}	 t        | t        t        f«      r-t        j                  t        | «      t        j                  «      n| }|€yt        |j                  «      dk(  r$t        j                  |t        j                  «      n|}|j                  d   dk  rNt        ddt        |j                  d   d«      z  «      }t        j                   |d||t        j"                  ¬«      }t        j$                  |dd	t        j&                  t        j(                  z   «      \  }}|j+                  |d
¬«      }dj-                  t/        t1        t2        j4                  |«      t        d¬«      «      }	t3        j6                  d|	› d�|t2        j8                  «      }
|
r‰|
j;                  d«      j=                  «       |
j;                  d«      j=                  «       |
j;                  d«      j=                  «       }}}||k7  r'||k7  r"|› d|› d|› �}t        j	                  d|«       |S t3        j6                  d|	› d�|t2        j8                  «      }
|
r[|
j;                  d«      j=                  «       › |
j;                  d«      › |
j;                  d«      › �}t        j	                  d|«       |S 	 y# t        $ r da t        j	                  d«       Y yw xY w# t>        $ rQ}|j@                  jB                  dk(  rda t        jE                  d«       nt        jG                  d|«       Y d}~yd}~ww xY w)u`   Lee el cÃ³digo impreso (`PREFIJO-XXX-XXX`) con pytesseract. None si no hay prefijos / tesseract.Nr   TzFOCR de rescate no disponible (falta pytesseract): se omite en adelanteé   r	   i°  )ÚfxÚfyÚinterpolationéÿ   z--psm 11 --oem 3)Úconfigú|)ÚkeyÚreversez\b(z')[-\s]([A-Z0-9]{3})[-\s]([A-Z0-9]{3})\bé   ú-u   OCR detectÃ³ cÃ³digo: %sz)\s*(\d{2})\s*(\d{3})\bu,   OCR detectÃ³ cÃ³digo (formato numÃ©rico): %sÚTesseractNotFoundErroruC   OCR de rescate desactivado: tesseract no estÃ¡ instalado en el PATHzOCR error: %s)$Ú_ocr_unavailableÚpytesseractÚImportErrorÚloggerÚinfor"   r#   r   Úcv2ÚimreadÚIMREAD_COLORÚlenÚshapeÚcvtColorÚCOLOR_BGR2GRAYÚmaxÚresizeÚINTER_CUBICÚ	thresholdÚTHRESH_BINARYÚTHRESH_OTSUÚimage_to_stringÚjoinÚsortedÚmapr   Úescaper   r   r   r   Ú	ExceptionÚ	__class__Ú__name__ÚwarningÚdebug)Úimg_or_pathr)   r<   ÚimgÚgrayÚscaler    ÚbinaryÚtextÚaltr   ÚprefÚseg1Úseg2r   Úexcs                   r   Úocr_coderb   V   s£  € õ ™xØðÛð
/Ü@JÈ;ÔY\Ô^bÐXcÔ@dŒc�j‰jœ˜[Ó)¬3×+;Ñ+;Ô<ÐjuˆØˆ;ØÜ8;¸C¿I¹I»È!Ò8KŒs�|‰|˜C¤×!3Ñ!3Ô4ÐQTˆà�:‰:�a‰=˜4ÒÜ˜˜4¤3 t§z¡z°!¡}°aÓ#8Ñ8Ó9ˆEÜ—:‘:˜d D¨U°uÌCÏOÉOÔ\ˆDÜ—M‘M $¨¨3´×0AÑ0AÄCÇOÁOÑ0SÓT‰	ˆˆ6Ø×*Ñ*¨6Ð:LÐ*ÓMˆà�h‰h”vœc¤"§)¡)¨XÓ6¼CÈÔNÓOˆÜ�I‰I˜˜S˜EÐ!LÐMÈtÔUW×UbÑUbÓcˆÙØ Ÿw™w q›z×/Ñ/Ó1°1·7±7¸1³:×3CÑ3CÓ3EÀqÇwÁwÈqÃz×GWÑGWÓGY˜�$ˆDà�tŠ| ¨¢Ø˜˜q   a¨ vÐ.�Ü—‘Ð6¸Ô=Ø�Ü�I‰I˜˜S˜EÐ!<Ð=¸tÄRÇ]Á]ÓSˆÙØ—g‘g˜a“j×&Ñ&Ó(Ð)¨!¯'©'°!«*¨°a·g±g¸a³j°\ÐBˆDÜ�K‰KÐFÈÔMØˆKð ð øôI ò ØÐÜ�‰Ð\Ô]Ùðûô< ò /Ø�=‰=×!Ñ!Ð%=Ò=Ø#ÐÜ�N‰NÐ`Õaä�L‰L˜¨#Ô.ûØûð/ús8   ‹J8 �AK ÁGK È0BK Ê8 KËKË	L8Ë'AL3Ì3L8c                  ó   — da y)z@Para los tests: vuelve a permitir el OCR tras un fallo simulado.FN)r;   © r   r   Úreset_ocr_statere   „   s
   € ð Ñr   Úpathzset[str] | NoneÚuse_ocrc                 óž  — t        | j                  «      dk(  r$t        j                  | t        j                  «      n| }t
        j                  |«      \  }}|st
        j                  |«      \  }}|syt        j                  d«       	 t
        j                  |«      \  }}}}|r:|D ]5  }	|	sŒ|	j                  «       sŒ|	j                  «       j                  «       c S  t
        j                  |«      \  }}}|r|j                  «       j                  «       S t        j                  dd¬«      }t
        j                  |j!                  |«      «      \  }}}|r|j                  «       j                  «       S t        j"                  |dd	«      }t        j$                  |d	d
t        j&                  t        j(                  z   «      \  }}t
        j                  |«      \  }}}|r|j                  «       j                  «       S t+        j,                  g d¢g d¢g d¢gt*        j.                  ¬«      }t
        j                  t        j0                  |d|«      «      \  }}}|r|j                  «       j                  «       S |r|rt3        |�||«      S | |«      S y# t        j                  $ r!}
t        j                  d|
«       Y d}
~
�Œçd}
~
ww xY w)u[   Texto crudo del QR de una imagen BGR (o, como Ãºltimo recurso, el cÃ³digo impreso por OCR).r/   Nu.   PatrÃ³n QR visible â€” intentando descodificaru   detectAndDecodeMulti fallÃ³: %sg      @)é   ri   )Ú	clipLimitÚtileGridSize)é   rl   r   r3   )r   éÿÿÿÿr   )rm   rl   rm   )Údtyperm   )rC   rD   r@   rE   rF   Ú_qr_detectorÚdetectÚdetectMultir>   rV   ÚdetectAndDecodeMultir   r   ÚerrorÚdetectAndDecodeÚcreateCLAHEÚapplyÚGaussianBlurrJ   rK   rL   ÚnpÚarrayÚfloat32Úfilter2Drb   )rX   rf   r)   rg   rY   Úfoundr    ÚokÚdatasÚdra   ÚdataÚclaheÚblurredr[   Úkernels                   r   Ú	detect_qrr„   Š   sB  € ô 58¸¿	¹	³NÀaÒ4GŒ3�<‰<˜œS×/Ñ/Ô0ÈS€Dô ×"Ñ" 4Ó(�H€Eˆ1ÙÜ×+Ñ+¨DÓ1‰ˆˆqÙØÜ
‡L�LÐAÔBð=Ü&×;Ñ;¸DÓA‰ˆˆE�1�aÙØò -�Ú˜Ÿ™�ØŸ7™7›9Ÿ?™?Ó,Ò,ð-ô ×-Ñ-¨dÓ3�J€Dˆ!ˆQÙØ�z‰z‹|×!Ñ!Ó#Ð#ô �O‰O c¸Ô?€EÜ×-Ñ-¨e¯k©k¸$Ó.?Ó@�J€Dˆ!ˆQÙØ�z‰z‹|×!Ñ!Ó#Ð#ô ×Ñ˜t V¨QÓ/€GÜ—‘˜g q¨#¬s×/@Ñ/@Ä3Ç?Á?Ñ/RÓS�I€A€vÜ×-Ñ-¨fÓ5�J€Dˆ!ˆQÙØ�z‰z‹|×!Ñ!Ó#Ð#ô �X‰X’z¢;²
Ð;Ä2Ç:Á:ÔN€FÜ×-Ñ-¬c¯l©l¸4ÀÀVÓ.LÓM�J€Dˆ!ˆQÙØ�z‰z‹|×!Ñ!Ó#Ð#ñ ‘8Ü Ð 0˜¸8ÓDÐD°c¸8ÓDÐDØøô= �9‰9ò =Ü�‰Ð6¸×<Ò<ûð=ús*   Â
#J Â.J Â?J ÃJ ÊKÊ+KËK)N)r   N)NNT)Ú__doc__Úloggingr   Úpathlibr   Útypingr   r@   Únumpyrx   Ú	getLoggerrT   r>   ÚQRCodeDetectorro   r;   Úcompiler   r#   r   r-   rb   re   Úboolr„   rd   r   r   ú<module>rŽ      sã   ðñó4 Û 	Ý Ý ã 
Û à	ˆ×	Ñ	˜8Ó	$€à!ˆs×!Ñ!Ó#€ØÐ ð ˆ"�*‰*Ð1Ó
2€ð1�cð 1˜h s™mó 1ñ&Ð6ð ÀHÈTÁNð Ð^hó ð.+ Jð +°8¸C±=ó +ó\ð OSØ"ñ2˜ $™ð 2Ð:Kð 2Øð2Ø'/°¡}ô2r   