OLzBcPrkv?YPHtN
z_hP1jLG$o2Lz?tjYT92ZVblIJ_|lrzvwNlNEUy>gPtfnD>@^H1BdbZWza_0KvmU><
zcs7=@kn=P6^3c2HGFCs}X}P{Wa6-X3`i_NN@ih`wcH~6%@P3+&gUFAJ5K8B?*%Itr
z;_`LUuV@&q*`CBsOIE2ts2PU87C5%F{2|Yp_BCY<5$|vFwV3dob9IYT`h7zB{Cp}h
zO|}NhY|7iNW-l$XEj_UnGetYd+C3Pg{&=Kf!$g^h+32OX?{;b*uX?xdHJZIct_iOq
z?q;9YliDUXLI9k70M2r|`F5pbH3N~$rlQ3uhLCBXxfp*G=jnDdPaleMDX3IPm(4XM
zoLEs!aKCxO%sB5#jI=5tta6+C86apZP=6vjM@d_pu_vqU%Z~gR7ep+0|=@zjv}^bf%(_r
zPT#GJFhxC;9e#RYY_kMGZm+_kgE|Ia43B?*VJ%7fj{OlaW79r&s~AxQkT*QYN8){Z
zOa5zAuQuXkqg;{-k%a6_YW{FtFjxz>jf)L3Eq~&rF}jF(@A(tz_qRC>UwDTWssa;O
zvT8EmHL)|}Syg+T$+6yMdk|bKN*aMe2woHS%=T3mG*+q~cu~-ZF?U5szTV>|MB<&A
z{724H0c`j#igwhbZgoTL)1rJATrp*2vY-DK_nZ=hk-yDA-u7_Lg!fwsH|IQJsS2YE
z5X>Lp#mO|kt1y*^2<`K6%>sPNi%xmx;H;?}qUqqDt}?W$V~-6`pRT20#4*
zE^$M#q2CJWq(XqwTq?8IH(0RCp
zN+V7RJ2>&(FJq`^t{*Il<=46ilm=8d$l28~!msKQcOJh72`**o`prbiUyd_V`Wgh;
zu217*SJ-k2M+@^%QkWV3R>CICr0OA6j%(Fp8uGCIKXbP}{~o?_QuP8M{r9iRQuUvD
zR0&_#qwUv{7yvl44Qx$SYA3iNLi?@E0N8(kdr@Pos#qLcG%1WR44V{p=kXY)Jux|F
zl}3jALgVwaKvUFH!p?Z9s64B-XB6!{I*sNV7tAiHDdq~64}C&(EKNjvBt#!M-enQd
znep6y+clX2>6Hj6bI)Y8x#l&(hqf1zipIVrAP2puzuP`JdNpM^dq+n>9~?FCMQ>T5
z>6|=O`Yu!s_R2Co`))!!2+rs~R#Q~gl_QKio!R|yMWM~pbm^EE$?DlB0+XiOhb0o9
zxM6r;Jb-=@*^F^2W`A=q4{CGzoH+nCDZrm7Wl3+UwH&97g9SAt;45j%j6Kg`nfD(o
zzKk+RctopSH$x+|+Fr~cZKhG`^D%%m+FcyN-o+Zm+)N+c3d82hs))Sh$du|}fJV{w
zp-#@mOH#hr%0mcC;u}h%Ea(iVnApnmI8P{NocY=SjnGne}QU0=hyBk<2N|+77E0rzay{p|o*JQXNOfPs}
zn&^a5PRY}0d<7eiNsvD2Q~rfEo7~6}JA*>xJH@X5c&5E}W_KLhbOI~I-llob?SI*O
zWyCuH=YmZJT=N6mNDz
zcv=%F=XE@_-ot|#h>iZj%iBha-Yc8CY!iMwb&lgn1^ZDw628tV16?Kl*T43@B@h_ii@Z
zZ|^H$(=vL|XXXsclC0$^&HDcUh4fAT0RGoWBWj8%+od-}Px(dTNi0Rf|1~`L$z!pe
zJalHq@7Hl75CnpQ_<^*S>@^($Q$lWuHPiF=yeLs$_>gFO0
zT;(@_(c|#Lvgjt_s3*Ty|BLk)m4W5z0oQcLcmDvD(WxcG{MRE6$Pr@fpYc)dR4N|W
z@@u|lkVcQRTE}fM0K3|>kr!+BQmZ)&&rzi&*-C5W5-q0Sqxi{^CtWGb
zoxI};H)IT57r#c9!Mq9MxhVkVKbp2~8M-k~p|Czy*&v@5qsIcS81Q|)Ibu5TJ8{)h
z>pd1%mepXL=a0J>EqHuH9E(%2nn(|`<5`2O)@cUxobX3%CcB=mehw#>2DP^sKj{n0
z*mSZ2h^mm&C0kkQ`x70CzUot^bseu(GZF;|;BATbT3bu5h{}Phl>Z4N3C{uGi#Lsp
z&FDqtUq+xUoZb9mx6CM+2&BI@*Nc{2h5Sc;S>0w#;q?U6Ih|qPWxAefwfm(|@M*%5
zpy3Qki2K~eFDzqz!pP*C$WDFEvfA8Z?(ixGv64(w;q8p5?*lY@EMes(K4JMKrupmF
zeh5-^-2?K=s`;-wIMimjhdz0D<3sw7v?NZ+Oh3~|5bo%OBDVu|#kq`t4UEf#Anbqn
zYkIfFOH5^>_OBF2L^$94*kYPmne{^DG(<{5
zvZ}%}AL+#ho{2*Wt(b*b#xX?7TN(}fm7tT>N~wP;;=G%)2*UNYn5UO0qdh!YoSsl5ZLK3g@vg)tBXU6ODcYE8WW#ppKq+cdN;jVjg
zgFa`_=-Y+ZY}YBh?Y=h79aHfjy&3wTLOR6k{lJfwy`6PgnOkh1d7~;dHYzGnOnrq}
zD!Q(xww*Kfj~%#FJixB@c4xh<@sDF3v;Q=dPdj4BzmiBM9uua&F7|a^&htLy`R~$|
zQOajX31e4Pxna_|4Q-7Q%)Y+)C%5O--QYH_TDJqw2NT%nzg@(4ZGZblsK(XI9=%
z3YORVgYk{fjdsl2g>6t7v#!Q?Z=W*7hA5>Urmef$YB^_iYLQxNMBrAd$xLY@p=IK=
zzA%HDoODNY>&3&Bp!aFsMZVwDey*Q=%en}ObC^x#
z9Y#oH=isI?~jJ*|HlIPBP3{m9%86z*@m0W#XT-!Pr==;3FF!#&$Ps+BjmvvCJ`K
z*1-Pi3}G(2h+_ugP*yAyB1CyqEp62|#VV=KnEwFc%&d!1{h9FCJ@eB&ech2S<$!6`
zvkG%c<-Ia+su^smzHG7jQ(!Sa(_>FN!sIIqG)q0Uq5iNmE%2Pmz5AMl=Ld}khO?bR
z1$vLf;HM#jrQ=Q?K9+m#@KwxYthd<=s*I<%>V?jnijV&HQU8?tskq#IR{PrdIOgOz
zb&ujb$UWOKCrBbtdJR?aUVRnu#0(R{LEP=Vzh`X!AuBBFQEkZ5)XuVmYIokBd(Q(l
zQDlw3nM)crh+Ryi>H|?YKTSpNozxq2@P1HX)$DC*pF>N9*gP9(YB{1`>3awU;GDh`
zogNeuK9S9#h_w@4Hqe=_F8@m}=Q2)z9~$BO(dI5P@
ztI(4#9?7vmtro6ZA3Zpm_*pGzq|66wv6_|ERJAXy=QGV7%Kd%b^g}?gKkP|r@hLrd
z(yiUx=y8%}Wa+Kdh91DJ7lMh%v*BAus_SvcosXM#fTmUV0{9?8aZI*y{
zbkZ=qz_7#Q9A%hQ8{fX`lx}KR82;-e9JIaurXcL9mRP=rRyc{PqJLNr8G~BnX~LKp
zDAZg5ffbLuIKBml26+`(_BhM$BL99&HY%Id2@VJ6yBP%z85{I*23>^NK;O*tU!D4~
z^l2S!Qo(+o8RoZGbl-C
zRd2v_pN@<#mbI8DY@vsgvw1yE`$Bb6EF2{&$RVB&53W}OCC?`hZ9K3pjLkD8SH>VpjKfF1v?uz^Qw=$bQ?5OGCmQ3u5
zIi>UKFF~-Y(CTQ*y353UleeULp=SDQf6F6u)LZE?GrJE|{z@2>Hiz(DOp08NC=dSj
z^={-M7bLZw30h&*#$TLHWmFuajw}!U2QYZ~_7Cej`KnHMn8I5gK8v@-7jf&i_3>WY
zD8M{0UCmE2vNw&bGCfpYce;$WyVjT8GF)Dj?uwfBzX<#wmyp%e4q#cpruuJ4EUvA`
ze;U@%;?e?>Jc`IMssb@(guZAn1#?>_
zn8;6W1>^g)^QOI6f65n-SPc6bfAE7tE{jM2n2;-Veh*+~b3?qJo<^O~IiO4cL$gGM
zIU@XM;6S7xN5(SL54yXHR|j|UAVCH7147ZUDGzj`pU+
z;CHP#Zpl)zB#AU@HJ0U4NFM6F%RxO47`L
z09mpl9)q=4SYAb35=)Xe>rYi_B*0zHdjXDW{h%e#M7zu?K?A#1k&Vt)?o4i=F+nz`
zY^`w~LP)(>3}b(|s#nhg%aIvJVp*7+lbW?~VYWdeg(aOE0aKA5*0J74c}%h#xj8xQ
zRXJ&4Sr;L8L|Foo(43rf+D&LjZeAC;uyimpZg9t@H+r=8v?GRWE}fd
z*rl~bMls10#YtIUIabH{=xB>eaIEvE`(;@lQ<-xd2W%=vL(ALn9e*nH4I5ALY`=Jb2&PGq-)!0dUYo=-U8inKj>Htd)rQG%v$tTS3{aExN
z9!T=9>6P`ZX5Yh_V3#59Xa4|G5%u*x)x+BfZYO)YmonT)#JivK(x~e9Q{VWO?XFz6
zmn%gW?5M4@e=66uQm6(x40`6Xj+Eo*iOZ6e2sPP$MxC$+80RAZ`t_^vz#jl$1JG7|
zvjU)=Jx5B@frj7@*8{ah&rZN>1FHmWxa5K|Gf|rbW+B*P&<=*RVo-TF
zqLO-()7Fs=9Z5Xo9yk?dMH>hJ1L`_eaCrk9jxuR(TF~?l3(2?;dN%_Vm#kYa+GPcP
zaKwY#6=%Xykoeus2Pcv#-Xvcx=$_{|HMjh`Oy-jP;l|wCpbQ5Gp$4(;?UDH-b_Ow?
z2(E_dcY;q`R$Rzb5_@n3W}S_BZjN&LEs&gb$4aN95BR8TKkMc{{R~#FszO4?IL{p`
zFGnN$e#6vUL=J!66{~lpFGhJelC>!>C;1e?*wDUlwSmF
zqhy6V|814w~QIZE^fO*AcYA$j8Jt?fgNhIJC-juch^!4qp%f4Ub@PQ8NNF@0|Clu_8O|01l^)d)AJbr9Hzel8=<|
z0q;?1Rz~h`F^9?sIQ2Ex#R9^h1?M@eYgOgZ6;7L$tj9t&WSnGvG^O&So@uB7Om@aT
zhMlttz;o|dv#}Vuo;J2R;Gd;&GLUXb$9!|mcG{pDSoS;`;wR6X+2DiQit5AlIq^Fp
z_mP%cJ~z_ndeuQY1DuY
z(jO|nMeX?4QiNN%&(6}aG2}}dbplDu_eUX?1BD%UC)?JWHMEfd6ws);mkg?S^4I!Q
zb`J~&X__t0xboeO26^r8MbwuPsWUcYkmZL~BeMM~t8{SRsS~=Yn5{5K2Ykn=`c;i2
zJNfpaGEzhb%u~v@P;*gR#E8xqj0N2*4sfF!el=#&RYf3e25I*ay$Be^X%(%^r1UfH
z(8nNRiUjGBdVoP7{-Ub^wMjjD4{F!dm1C0j)F&`ZCr0Q(Ha8EZaBCbA!yO6rtfs8T
zaWkCYah&3%P1sjW?spPY4k{%9PDmtkky^^*$ri#G+F(Y;kCJoS>shj(lX|kfgkkTq
zw3Gz=!?*bQS437Bo^3N!Qxvnhv=WSxGr$e(I{r0K#B>Rw$lr4jR!F5nq%p=uJNjmq
z#ag^Njn%uyX+5pPLkyB&;Cf_MP4hL&w{xm7P>bf@v4yBuPp02XX>qj48Np>aQawd!
z=ob4UpHN|vxr!W*;yq8`Kb>Y=Jlcip6->xS3ZsB4vC^y}xSG>XSr^KIeqvjvcRz`u
zqkOhx(SmbwTO+IR?v>!Uf|P~Mwe~^AjjSM*O~#q$3fWAgl_J~R&9H|P21m>#CPW(^7A3*r`|QY
zXv)gC;Nz(8SlW_>@eT3E{I_GrZsGg@C!FMCBe<-^at1ns
zjEa>XILZ7e14QoQ2fsZ>TF$y~2O~V=rCN9SNoEHf0qI$na|Ko;j1Nj;BY933V~(KI
z2-uU7Ks~e4k~T7ofOcU0O(BiZfg_JxaZC=c!`D7d!pP0FLHWLwui}!j!)tXLsg228
zk4%4vu4_kr8ZZgtHPmZXNonCA+xL>fQhg3b0<(om&ZMB@?puE|#g%+x%$+$?Udq}^
z`nUaQYkA*W9DL-Ga&woyBLFegIbX>N(
zV1McAGyedO3Qbmh^Oep>Ii=9Y{7)=7{{WYWx_`baqDs_d=+750mEk`}`4w(>5tTht
zJu!;!Y~^V*oi0E!e7%H|!TC@4R|Po?R~%y_n(VX*JnaS`046C$KU&sX8&mk1$X%g_
zP?A1@Sda&AN?Rz5_pwG4^1zU4&c6qdt4hJL8C2&z>VFN}8E(wjz)(hg>A6}rGf}+x
zPNF!$c+?KK>Frv00dbM(RV|YfIX!^ndews55!4?_;d-8e&}qj}oKp@s$4^1pf_T8=
zJ*hL1z`z}I)|vsN;PH>?O(2tll27AKVoy%E#W3Uo2OwhrRU%lza4<$l=iF0KJo(l&
z>Q7PWNat=(LVdcL*wAde$qbG->q=7PR}xotHuT#uZEglJ$E8~%EJtE6ed*-_r~sS}
zDbYk-fIYuj^Q8Hm&cwM<&mkLdIXnt@AOqXi9V!*+jP=LR)X9u<=xcUajjCX&91+xe
z*B2;(F4)bz$T`@YerNnEwbcj8an}d%t{UiK@rO_wy>(%_oYxRT23k`2pdK+GsnGHzlK%QMdGTe#^8W>B-cznElzn~kj}M;$ig>3#K;05
z@9j*~o#MEcc?H47K?Dz_MI5D}1d}EA$B8-fAjs60<*48}sO)O0X
zh=ed{E`WAH)kpBD{&dJBcGKZ0v`Gx)fO!X{MsA*WYnDH~1ce~g?o$tq5+
zMhb&~->wa0>o(0Et0aVFWo^Wc22Wq}?@FTT2%zW7WFv6tPTIDyHL`)B&%mE#iJWm3i825GUTlqHabp5(I}j&=^8pIXw??DWkp
z(CRlU<|`Pe5FJs0GQv6GScaaQBbG1z)lG$iAUA719H#7Myhr#R1QlhZ}T0P-80_ot4SC!FJv
z%~lLOeTXB9oktw>&T&!}9&k@yc>HTR@-pza13VvNioRutKEmHO93CkgDagk@;b%gU2}gCghHfQ>(D@LVWZ(q{8K|Sbzj#Txuw)qn0kTDFJ-lqdfrGoPXj*83
z?#@;@8$sG>EqW09+{n`P0UYR1wh0+rWvi*x?_{3EEkj`r3xn<2pQTzu4UX|{qCjw4
zZcR|tTV#w6BpP-}Y1Ge|w{FHHtQ2x{$4bW3?gS7?At0W^u1#xPliSldu0vh94{{(@
z$c$yX^H)wcb|SR-BXcR4q;_(0NhF%n(33jlk8E7W{{W#1%mnAJ2DNlCjbhN}k#i9L
z0LR62SNQr^&x~~Sx9@_Q;oQ$vswQ|qY=d$DL(ziSrFimNp
zCkrYOgOi%MYZIzEO%V8rFxK*sk1=w`sLwU6;k#1?oxQ;ZLQZ<|R5iF6mMBjrVd>5=
z;MLz4Y092OB0T?G26~MQ-IovI$#ca(+(65*S1A-
zqhKI`>z;Gon8biEI3C@oCy|W$ck-=5>GsH=}jjj<}G>8ON;{&(hT$QqKyo_}qlbrRg-qlJ%tDILpCR$k5it0`g
znTMg^)yU!syc~C?K@r*s{7sgqe(N2P)Hf}snl_gUYyiugKm0MfMdQ-
zTG^VJ8lV9Vae?{Q2X+9vU_DP2-s*T*$pi7OCGDQ`Q!t@XjF)r3LF#L(4Y|*W*q*`^
zWH%Ep`aE4oJhAKr8nn-GW9LXf+`u9Zh_pz;;%KdH!7#>fgQi=%AFVfDMYXlJggAFn
zOA*sPpM`bD;?U)rY7C`i6iB8?uvRcwAwBzMslCI067@+{3+=$f+-?(
z-{yi#cL%jr3-`ewV1AX--ipTnU~{-SE=o73_VpEZCznfWWtv8HbtM`$$P3ty@T$LXw_=v3Bv4
z4YT>Nt`xZBzXLTQXw$4IE!1+DTa;kEe!ibdy`oM&vv=O#Rkhu7*Wk-I)y-JMDt%bl%112ag1NtQXKM2-}U41~gj
zALp%eI@Y9hjYctT8S+&5cIY~joNoMjR?mzzqdn7UkiN@kfb)!e20fOs=Ku;-
zaK7LinquX_7sDRj-od#s#rZ-PrO418lI!8TW4BoS#~p$Q`l=(DgX15iX?(!$TaP
zAH0oH$r_;#ZOcRrV?s%?)OTV#((czNe
z5sBj?BR=)O`8vFyh~N%0gN7!pT4|cwvjc3voE!tjE1fBBj%g^p8Q)mOixlwvp(JhF
zu^Gr2@0#ZPMY`7M5U~0FZ1k-0CcC5EiI!N95r&3EEF0@o?#;#iqSu#mnH#ViDu1#E
zrF6^K`vk4;-|#$W!!cQuq~yM@%cARl>i$-?mn-TwV&2wUhB(6TJ?n>dXJ%(PEJ-B#
z*3XD21*2j_0
zLEP11H(e^6VCSVa$^4B*-a*wQ1QA+vIyRCz>&OFY$ru?`l2Cak8N&+HL5z060~E)A
z8~J27$#iUEEZXJX0Tlf1NxYy>Xr~Opx$%-<~Of
zE|?2n4D*53ySrG+C>?;VCqqEjgyRPQdYbfGNVY}~py}ycn2l#+YEIC&;U3ikrh3p9
zEzf$9j(E;Tw`yoZxON!nS=cUd(Pft{j1K+kKn?~D38-K{xm@wk`&60Z=rLB#gl1|H
zDTYkQp}^r!0E*)E8}vncD?|w!He`R|bMNduE3nl;DoJjG)1`U+@IPr`=t#h>%sWp*
zmlJfZYiY_X?3(sW24GSU+~oTGD>qV*4Ux!jz-Mu-8~GKQ*p6UG6aWS=am`^|JKS8B
zlkV}5I)Po0TAWp7XxMe+0@y&OK4V51v)G!36kU@!ZU765K%lRO0oqt_K5
zKV^4;45Vw7hJexsqS;;$4#dfcRpCuW69MLPW6
zUU-ys`F;JVscCf)m`H+1&em);LXpqlK1x;1A*-#gS1hF5g2)>eB9Q*}yIAJj7x4c@uIl`=tK>jtyRd?p-UxHy8GqE7(I?V
zR|RyU-9yl$E@&!ixzELA29siSZAhe&1`QNX#`fnwm2q0Gr#7vq$$38ouoVYHQP5S-
z5$m$+cf+-$1IxESasv9FQfnZB8yyd&I#6j|Tb%XkCi!Eqr#pR*r&>_Q7(U$6FaR8M
z=}C?U9N^Z-$EhNdf;x~79Wzdlf{Hs1pIVF)z|IKoQuR_f$DpKA*wocq{wH_D;QszV
z2mB1vtR*8EeouO@s=nQF{{TG&)V;sjH3TkE+;zauPSwu{-jsb1U}tJn{TJkP(asN`
zILg*DA@KPQs%5qz~ej$oUnB(*FC*OQ$R3r
z)2Q{S^535a0ac2p>vl|4mbm?AhCuLaTX2Vm`5cLyM`JSgOJ6~XEkhW6^>T>OIwfcjR{stCj{{V@um&QQ2Oypyd;<3(E
zaCpec0=BfpKic|Eai20H{{WAQ!oTq4=*X}93HmcMjkANCWq{A8H5nUjhJU}o_RUPg
zbf=z4Bz%>)T^}Hz{K((1fJamK^ZK7
zj;E8F;ga{6-+M(0#O*x}IG_%FcqriAI*#Y2DWpxsamEi%N{F0{k6M(S
z-Esl#P_P`2$aghJmd2B`_T!8Ljw(4DgTWaXCq3y)^T6Xc;-Qt!)6+d^+fj39Y#*21Pe}G2@Q4ni;~x#2_6#>OjPF?V64=@(lGIY2b_idQ~%|%hbX0nDhd;8~$#a
zo-#)j-s+8nCye^nCuyNv>sM3U2U}e}+-BnPU%43c9;|%@bz!=k*sp~oQ+Zh}13pYZ
z6~=MN=~OLtMoTeZFiGTg%|_>(4uFh(Dv^Umwv-v-yx0cw5LQ#%
zZU&q7$A;!eB#U|^m?Jo49k6K793}0-kW6wwLC0gp4O!Ifqgj&eT={dM0Ar9xQJUF&
zDWY$!i)6!cp@4Uhm~LDE-&}f+ZnVg463+TTA^p^mFu)lo-@<h5)hqo`
zWYMG1#2!)!g~ys3hE2G^QQtn5wK++&7D$lKYatBi2PYXL-y@}Gn=Ln7w300(JaM(Na7G^txdq1EU2y7f7dLo*S;CxClW0rLzFPx7fQZKt!iduy1QDU|%m!vs?h
z24FpUQfSF)#e66SuQ|mz)T0GGK+Z)h;3!ZJ1QYE|Z~#HyXzS9FDUCVZpI(0&dK`@4
z4{p@K_jAySP;huSCy_uVXzM5bC%TCL0H>Kd_4$ofRw7kA{J@I6aeb!U+{<$I7hC0a
zk%HYW0Vh0u?xSgRzV=sugP-{OU;Z~-)a4}w7ZX2XvS6{{Wd^&3kw0
z@)M9T*0gOeLtx}&oF3GO+EnrG7MFv+{Cutl7GSFJExR!
z$?Km{NYdN50fUdDbNs13$!~Br2`52}aKoR{g(v*4@c#fYI`jVkpK|{I$$$AHo!0JJ
zI|)R8eSyjJ6~uBORT#lJ_pepG@QgQhZjs9?vvY=&CVw4=AC+;su7KJ=FL$GA1`*PD
zuGQ1?5%KufO&X8+e=qYq{7q_&r|jkUf0@mJ>COiosz(H`Z=2Jav?kJs&)BaBJm>9-
zfA*a`&!q(bSzVkAf5+PY0P(ut+DrLg;r?ebtv}>{`7imDj#+^?=mlEtRemPC!R6rO+z64&OJ^?sphv^ov&lk
z)Q_@TgWBD4eR!%VbrRqclYx>)0;JSrFxrv8V3Ec#*E>aXrQ`}EEI>F6JrDl?Ub$^{
z+0^t&9SN0NfWxgI$6kaVsH{Z0Zta3{aZ@g!%Eyv(kEJw;X0|vyV3K-rDmOh313Vh2
znu45z$Q_RYr1Q~_2N@rgK2mNu@yW=>I`pd2xa5vN=}&eCoPq$z6;@M>p2MwLL`j{W
zfNjZnX2ty<4A-4nSCcy79T4%1^VCy}
ziU$Xps#t(=-=ICJr1v310@yh9%|^zSxy?jCMHw0VX~3Lg)a3e9aVaP<+pncO4st#E
z){NY(djtyEzIIslleRuty}hf8*LAzSP2;f%_I*AgOjeQbx%F>T-n!jP{HLin!97W=
z8{IT$bJ|aMKhh_aB=Xo`h?fj=^*mQr9?yM_TuOG+bDF(?S!wFCGmF{ZbHsS^&~(qa
z^r}|w5?r@N1QX~gO-kxZ-A?A}eEE||%yGw4Rff>Rc6JlaI^w&ddmc>Ry{AIUhK#;f
z3ycHTBA*mOTWJUJp$wxPja++cdy7Se4N4K^oyxZZV{_Zmk~kK^2~p;bdx=wOtWA|s
z>P=cft;&}764=OQkDdS^^dr)@O!2j<%^G?3cLOrUKJuS$fByhoVM@&;3k+&lhRFeP
z#lOO*NhZ=)YE81@KntK5P&;IL){&D$*s*18V)v2D9sXFq%92HcR`olDc1uf5rrs7I
znTN{F+*A`@ExfBFu*7dj3?mDKR>0(sQ|VOgXJjubjo^}HkFyWxN2sb!+7oW3jkK(&
zL3jHx01yt=jQ0I|)Y?^}UTe`j_g+Q7lL;Ug^Ef%qISr4_izP<7hIphKR25)GLm})(
zr}C>3+eqlb%??IdA<=^`@cnVn(Is_qle4i};(JYGy_Mad$H+OX#`SE7
zADg#18LbByDjf|vor4D{x$#S=*!n|Uk9H7(fE
zD*F$zWi7P&=bp9E+b~eONy!7L#w(Y!Y)D8vfC%HQbe8H0p!MiYV^r(IGVmdf4V-n)
zN|iS4A;|<|jPp=}6APTV=%8ei{(IGk<0yAI%Ms2w;<=kUR;0*4Jd7Vof0>p9dx1!C
zoDSm@C@e;Ma0N}vu=v9gJ-sVp%`gD`Nhh^Xi3ePBT2}A1fGPm=#TOx?S~3-R$6mEC
zT(1WJjAOB?Nhx3f0~};@s|f5^5(ga)F`7Ay`Aj`m!1^|=xs
zBZHCBuDfOkhoh1B99Mnf{{R32vTB+XIFUhV?GSLoyCWUDI2-atGBe1oNbGh`;xi?-A21`6k&k+)_RKSm
zgMcexkTUe-)GVDr9XfNxT4Xa3qm^&Z8
z&j3}XLn}dZEU$$*JdBS@E@zSj1du`C6IQIE5y;Jf$>;|Z>$@X&a*>6|W&jG8Q7KrZ
zPVv7}()=wuzKa6^fKUt(TCu4lu>=9YCz`LL+O4LW5(VHW!98(TPs+ImfDhwdY_*eT
zQqfqiETI7;;{(4OS1+$?miEyX-njsszNWA1H~vMmnK&eIUOj(vJ^k|n%tr18c&>^R
zx!$`GQlf(MJxe4x=Nr
zV)(ySwx7fkNq1@HTdlc(0&vmDtJwMj&2C@ht8cc(GY*NzaBpJ4;Bp*!kQmG9f8v)}v6%i~>OJ#{9o|&hH*a-s;M_)l*=Z|sB
z>SPiH2M6w+^&|*xWMlv;g(rc)s>3JT)|QnX^Kmdz1W}Sm=~W$Gp?j3fv&O$8x*7ibc!Y$surCa2W5-Xo6Kj7De3|IU~39ICOy42AO5{)wGE3lg;Mef
zA#6t`NgdY}r1GD8b2d-Teo!
zt3qNppToM7>b7Vim0}J_
z{6li%Pq#84F*zt$9O1ov#V3Sgn(pcwa^#CSL!*vD2R}F9YZ)(jq-dUqPRO2hy$+|&UJFj3Cf^Ur>@mGrpM)uOkiG0jQ_V#iWQ
z;-e>V2dL{$8CbG9vB5OhkVDJ%+q{Q7l0hDoiy9Un4*jd6wjXAN5CM`p
z1CnbR&qJ#UwYxDMArlRt@ZU)DRPZ*fr5`-|pJz0m$^4FHGj5LQ65@2j8Vg1_96KT&|92}2aREdn9dFz^xNVOXUt6VH^J;Wj+6PR{7TD}__{7VBLyArdUaz+%@U1v>~
zNY(CaWdJlzRQhAL;72u~;U&(iG^YeCVMslB{{Zz?({_tzG!y4Y=@GXUJ$jE^Q=6Y(
zr=xvoN<6MGuo>-1NhPI(_u&01tVN5B-h1af!-rfXMBvodPX
z0)zl^L91E@g(ct8}bvF7`G2AHwX#{bKT4^WlLmaej-H)q;ZAeV4mwnD1Lg6IM1eDQJ>bm1IM@kcu)x^0{~>!
z+(@<>WU#ZZoZ(tUAHsRSthlA}8D8C(O@a#^i&9&-`zxV2-1%lao&{2N8bxGsyMV?R
zV1wyO!)hqcDhVQ+x8}(iQOP5W(z_RJ`~q{3J5+)I8I+JX0~sByznQ#^M`Rl1SNnn2sME10V49ty51@Sp7!XB$nRRXbai2CN+551~=+5
zIURe|Z7SU98a?IjpCk`-f+&r|dqBZF@zsmpgZk1MoMvqoGQ2be$v^dOGRDtoUH
zTVBSoYI=N^NKhC6n+74%A6)mWcKi
zcNi3;4A%CZ0>Smi1GQi<^EGKtB|ton
z#=2{0{J2m7jEv%)Uxgr`uTo65qtJkRgVvpbI&{Yr)Xs8xQ_0Ri9AI>=W3dhA9CbbV
z4Ai6F>6(C&AC5a>r3ajxkw^*GgV(k_s>&XxzIeq{P6^LL+uo;)_8*-xS#=waV1G)r
z5dd&-F~O|#U>FX5wP^^*!N>-D8{Odvo;AHd7WkCCY^gh0|p%dT&4tT{}
zOk`g}>Cde^ADipjwrV6N0QSW{oMWbW99F5BG9}&I^5VJA
z3Js}oAOx0>E=REc0PEMPc;`(1*6@A&VRIx3E+Zc6$^A24Vc~#3vobl{<*|;v1ze?i
zb~21@RWo;mh#rTLpGEW5_Us^
zyFE)piaT94Fb4rg=Tic*Oi1ca80}T;=8fzC4g4Su^(Bzp#P-RrJDTk2wXVr7Tq2Ti
z525C~^TjvjP0=C9JPPc!tC0*WPhV>D3)#%Gvl2+j!0(#6YnoRYRz^Ir5OLJ?B=OBl
zZ5Z2}V;-F;vbNF)=O+Ljlm}FgJqNdX?$*aXmpua5lM?mDJN2naJG0)aTRHN$=b_KF
zPBFmjdK%(;ouv&!n2tsdIj%RwHtM%A73(PlSKA$Hw!TuxLGBN>D~Z=-XBL3xF>VJw
zovW)0@mS`?uiff%E^)_FGf!BaKuAmh)sFyEWFIk8!5v0B(z_FY%t#%H$Q9@wKT%(5
z0;u^M5s6E&U=MoQ)2+w@
zBb?`+IjrX{RBuKOa@5qdO{xeS5z?ogQ4|t6Jrw4egfCoVcEx6EcX*9$ko?b#fGd$j
z%2quoNkT1=X>qm0<@%RB{RMSVs3aQ7(XE18`GjC}A4=8-1Y~yYMOj`$!^4&yq=7)`
z!93=iNdp}_Qh>cP$F&6Hk>4Hb1Eh?OLED_r3C22%bv^0qNE~(@1vF)PoD7LZdW|R-K
za(Z<4tvF=J2O|V_tXQ0e1b)2Mj4}d74>-;}>XDR@trKM9@v8%c^ggvx2jFL^?^c8H
ziq#n~DjiBlxkt*dJqR`AiC{D~yRv=_yv(c5Y~+5G>zO2z-|1dy@iGZ*^&3l=&KBu#
z#BuX4AIlY1Z$cYOQx+{vT@(??4rk<^NnFI_sGM;!L8
zISdqzzlY=z_;H$HQdfdO^*jpZ(^X~Qll0A6O<-pP@Gvvf6HAegnHs@2Fdn?|-ls^2
z2kW0#A
zee+&_rrF5%(WIb1Amkiky$)AfTM{#j0!ST&a!$)sZ+7%KuM^!Ya5CqTbIp0g@?&v4
z^U}AxOR0$^Y@UZ5bDHC(xoMoLw?37=8k=h5)tZik#Ft`v0D2BhMKhNn7mzWG9+ZyZ
zA_%9SYIU@GSIv`w$_7VT-|AtFwA*@wC)1kq`Mf83U3i5hGv>0!aXKO=0sBjAI!dw7+#u;BrVOjwu~WCz-cs&N0@3
zu~D0FBZdbmed+N}Br+L5+*sm3dJ$2g8C*s>pS#X6QjN?WGBD#LuT10r0If}T1ESI{
zBb>BJGJ?^xY_l#^hh45cyD;fhuI!+0h
zc>~mX*7E9eEsBfFSl-S@3LZa|xzA5bdRD4eT8i4;8Q1aL{gv)EG%CbK;xmMBG1Lx+
z*w$KtK_ilJz~?ooe=m^}EHc47cMJQ;lwqq9dp!FiYo5Nv1JK)6|ulM=DHhX5w1YSdv!RfHn0UL#s)Am(-msw
z{tGqv-!3@VIY0iYBUG4836J@r>hZ8`%!_rk%Q~nn3-(|-R)DSZUt0B&m7~hsnDL8
z>F6jVzXJ`!oMZ9DUV-vBBL|@CS!p*3NU|Igk=wO%9zWB6v^*y?fio?m3DAzX=-;Ju%5uHP>}ihz
zTte3JfMbzLFg*x46`>DZ4+Duqf;r$0I@4u`jN>4Zaqm*x!1fx2#iW4mf>w{8V^1x;
zMh9L$8q>_@@@7<#0mvMBjwWDas`)KO7ZN1o*5?#XSV
pF4;T|xg#|s^AETVKpio~B(5TiBA$JE99302i9e+@gPPhO|Je^3l92!a
literal 0
HcmV?d00001
diff --git a/doc/js_tutorials/js_setup/js_nodejs/js_nodejs.markdown b/doc/js_tutorials/js_setup/js_nodejs/js_nodejs.markdown
index 46cab5aa33..f2d7a16a79 100644
--- a/doc/js_tutorials/js_setup/js_nodejs/js_nodejs.markdown
+++ b/doc/js_tutorials/js_setup/js_nodejs/js_nodejs.markdown
@@ -227,52 +227,128 @@ In the browser, this filesystem is emulated in memory while in Node.js there's a
### The example ###
-The following is an adaptation of @ref tutorial_js_face_detection.
-
@code{.js}
const { Canvas, createCanvas, Image, ImageData, loadImage } = require('canvas');
const { JSDOM } = require('jsdom');
const { writeFileSync, existsSync, mkdirSync } = require('fs');
+const https = require('https');
(async () => {
- await loadOpenCV();
+const createFileFromUrl = function (path, url, maxRedirects = 10) {
+ console.log('Downloading ' + url + '...');
+ return new Promise((resolve, reject) => {
+ const download = (url, redirectCount) => {
+ if (redirectCount > maxRedirects) {
+ reject(new Error('Too many redirects'));
+ } else {
+ let connection = https.get(url, (response) => {
+ if (response.statusCode === 200) {
+ let data = [];
+ response.on('data', (chunk) => {
+ data.push(chunk);
+ });
- const image = await loadImage('lena.jpg');
- const src = cv.imread(image);
- let gray = new cv.Mat();
- cv.cvtColor(src, gray, cv.COLOR_RGBA2GRAY, 0);
- let faces = new cv.RectVector();
- let eyes = new cv.RectVector();
- let faceCascade = new cv.CascadeClassifier();
- let eyeCascade = new cv.CascadeClassifier();
+ response.on('end', () => {
+ try {
+ writeFileSync(path, Buffer.concat(data));
+ resolve();
+ } catch (err) {
+ reject(new Error('Failed to write file ' + path));
+ }
+ });
+ } else if (response.statusCode === 302 || response.statusCode === 301) {
+ connection.abort();
+ download(response.headers.location, redirectCount + 1);
+ } else {
+ reject(new Error('Failed to load ' + url + ' status: ' + response.statusCode));
+ }
+ }).on('error', (err) => {
+ reject(new Error('Network Error: ' + err.message));
+ });
+ }
+ };
+ download(url, 0);
+ });
+};
- // Load pre-trained classifier files. Notice how we reference local files using relative paths just
- // like we normally would do
- faceCascade.load('./haarcascade_frontalface_default.xml');
- eyeCascade.load('./haarcascade_eye.xml');
+if (!existsSync('./face_detection_yunet_2023mar.onnx')) {
+ await createFileFromUrl('./face_detection_yunet_2023mar.onnx', 'https://media.githubusercontent.com/media/opencv/opencv_zoo/main/models/face_detection_yunet/face_detection_yunet_2023mar.onnx')
+}
- let mSize = new cv.Size(0, 0);
- faceCascade.detectMultiScale(gray, faces, 1.1, 3, 0, mSize, mSize);
- for (let i = 0; i < faces.size(); ++i) {
- let roiGray = gray.roi(faces.get(i));
- let roiSrc = src.roi(faces.get(i));
- let point1 = new cv.Point(faces.get(i).x, faces.get(i).y);
- let point2 = new cv.Point(faces.get(i).x + faces.get(i).width, faces.get(i).y + faces.get(i).height);
- cv.rectangle(src, point1, point2, [255, 0, 0, 255]);
- eyeCascade.detectMultiScale(roiGray, eyes);
- for (let j = 0; j < eyes.size(); ++j) {
- let point1 = new cv.Point(eyes.get(j).x, eyes.get(j).y);
- let point2 = new cv.Point(eyes.get(j).x + eyes.get(j).width, eyes.get(j).y + eyes.get(j).height);
- cv.rectangle(roiSrc, point1, point2, [0, 0, 255, 255]);
- }
- roiGray.delete();
- roiSrc.delete();
+if (!existsSync('./opencv.js')) {
+ await createFileFromUrl('./opencv.js', 'https://docs.opencv.org/5.x/opencv.js')
+}
+
+if (!existsSync('./lena.jpg')) {
+ await createFileFromUrl('./lena.jpg', 'https://docs.opencv.org/5.x/lena.jpg')
+}
+
+await loadOpenCV();
+
+const image = await loadImage('./lena.jpg');
+const src = cv.imread(image);
+let srcBGR = new cv.Mat();
+cv.cvtColor(src, srcBGR, cv.COLOR_RGBA2BGR);
+
+// Load the deep learning model file. Notice how we reference local files using relative paths just
+// like we normally would do
+let netDet = new cv.FaceDetectorYN("./face_detection_yunet_2023mar.onnx", "", new cv.Size(320, 320), 0.9, 0.3, 5000);
+netDet.setInputSize(new cv.Size(src.cols, src.rows));
+let out = new cv.Mat();
+netDet.detect(srcBGR, out);
+
+let faces = [];
+for (let i = 0, n = out.data32F.length; i < n; i += 15) {
+ let left = out.data32F[i];
+ let top = out.data32F[i + 1];
+ let right = (out.data32F[i] + out.data32F[i + 2]);
+ let bottom = (out.data32F[i + 1] + out.data32F[i + 3]);
+ left = Math.min(Math.max(0, left), src.cols - 1);
+ top = Math.min(Math.max(0, top), src.rows - 1);
+ right = Math.min(Math.max(0, right), src.cols - 1);
+ bottom = Math.min(Math.max(0, bottom), src.rows - 1);
+
+ if (left < right && top < bottom) {
+ faces.push({
+ x: left,
+ y: top,
+ width: right - left,
+ height: bottom - top,
+ x1: out.data32F[i + 4] < 0 || out.data32F[i + 4] > src.cols - 1 ? -1 : out.data32F[i + 4],
+ y1: out.data32F[i + 5] < 0 || out.data32F[i + 5] > src.rows - 1 ? -1 : out.data32F[i + 5],
+ x2: out.data32F[i + 6] < 0 || out.data32F[i + 6] > src.cols - 1 ? -1 : out.data32F[i + 6],
+ y2: out.data32F[i + 7] < 0 || out.data32F[i + 7] > src.rows - 1 ? -1 : out.data32F[i + 7],
+ x3: out.data32F[i + 8] < 0 || out.data32F[i + 8] > src.cols - 1 ? -1 : out.data32F[i + 8],
+ y3: out.data32F[i + 9] < 0 || out.data32F[i + 9] > src.rows - 1 ? -1 : out.data32F[i + 9],
+ x4: out.data32F[i + 10] < 0 || out.data32F[i + 10] > src.cols - 1 ? -1 : out.data32F[i + 10],
+ y4: out.data32F[i + 11] < 0 || out.data32F[i + 11] > src.rows - 1 ? -1 : out.data32F[i + 11],
+ x5: out.data32F[i + 12] < 0 || out.data32F[i + 12] > src.cols - 1 ? -1 : out.data32F[i + 12],
+ y5: out.data32F[i + 13] < 0 || out.data32F[i + 13] > src.rows - 1 ? -1 : out.data32F[i + 13],
+ confidence: out.data32F[i + 14]
+ })
}
+}
+out.delete();
- const canvas = createCanvas(image.width, image.height);
- cv.imshow(canvas, src);
- writeFileSync('output3.jpg', canvas.toBuffer('image/jpeg'));
- src.delete(); gray.delete(); faceCascade.delete(); eyeCascade.delete(); faces.delete(); eyes.delete()
+faces.forEach(function(rect) {
+ cv.rectangle(src, {x: rect.x, y: rect.y}, {x: rect.x + rect.width, y: rect.y + rect.height}, [0, 255, 0, 255]);
+ if(rect.x1>0 && rect.y1>0)
+ cv.circle(src, {x: rect.x1, y: rect.y1}, 2, [255, 0, 0, 255], 2)
+ if(rect.x2>0 && rect.y2>0)
+ cv.circle(src, {x: rect.x2, y: rect.y2}, 2, [0, 0, 255, 255], 2)
+ if(rect.x3>0 && rect.y3>0)
+ cv.circle(src, {x: rect.x3, y: rect.y3}, 2, [0, 255, 0, 255], 2)
+ if(rect.x4>0 && rect.y4>0)
+ cv.circle(src, {x: rect.x4, y: rect.y4}, 2, [255, 0, 255, 255], 2)
+ if(rect.x5>0 && rect.y5>0)
+ cv.circle(src, {x: rect.x5, y: rect.y5}, 2, [0, 255, 255, 255], 2)
+});
+
+const canvas = createCanvas(image.width, image.height);
+cv.imshow(canvas, src);
+writeFileSync('output3.jpg', canvas.toBuffer('image/jpeg'));
+console.log('The result is saved.')
+src.delete(); srcBGR.delete();
})();
/**
@@ -287,7 +363,7 @@ const { writeFileSync, existsSync, mkdirSync } = require('fs');
*/
function loadOpenCV(rootDir = '/work', localRootDir = process.cwd()) {
if(global.Module && global.Module.onRuntimeInitialized && global.cv && global.cv.imread) {
- return Promise.resolve()
+ Promise.resolve()
}
return new Promise(resolve => {
installDOM()
@@ -333,13 +409,12 @@ function installDOM(){
### Execute it ###
- Save the file as `exampleNodeCanvasData.js`.
-- Make sure the files `aarcascade_frontalface_default.xml` and `haarcascade_eye.xml` are present in project's directory. They can be obtained from [OpenCV sources](https://github.com/opencv/opencv/tree/5.x/data/haarcascades).
-- Make sure a sample image file `lena.jpg` exists in project's directory. It should display people's faces for this example to make sense. The following image is known to work:
+- The files `face_detection_yunet_2023mar.onnx`, `lena.jpg` and `opencv.js` will be downloaded if they not present in project's directory.
-
-
-The following command should generate the file `output3.jpg`:
+The following command should generate the file `output3.jpg` look the image below:
@code{.bash}
node exampleNodeCanvasData.js
@endcode
+
+
diff --git a/modules/js/CMakeLists.txt b/modules/js/CMakeLists.txt
index 19f0b19790..d2a08fc61e 100644
--- a/modules/js/CMakeLists.txt
+++ b/modules/js/CMakeLists.txt
@@ -112,17 +112,6 @@ foreach(f ${test_files})
list(APPEND opencv_test_js_file_deps "${test_dir}/${f}" "${opencv_test_js_bin_dir}/${f}")
endforeach()
-# copy test data
-set(test_data "haarcascade_frontalface_default.xml")
-set(test_data_path "${PROJECT_SOURCE_DIR}/../../data/haarcascades/${test_data}")
-
-add_custom_command(OUTPUT "${opencv_test_js_bin_dir}/${test_data}"
- COMMAND ${CMAKE_COMMAND} -E copy_if_different "${test_data_path}" "${opencv_test_js_bin_dir}/${test_data}"
- DEPENDS "${test_data_path}"
- COMMENT "Copying ${test_data}"
- )
-list(APPEND opencv_test_js_file_deps "${test_data_path}" "${opencv_test_js_bin_dir}/${test_data}")
-
add_custom_target(${PROJECT_NAME}_test
DEPENDS ${OCV_JS_PATH} ${opencv_test_js_file_deps})
diff --git a/modules/js/test/tests.html b/modules/js/test/tests.html
index b20013ec63..deec57a706 100644
--- a/modules/js/test/tests.html
+++ b/modules/js/test/tests.html
@@ -43,9 +43,7 @@
// Helper for opencv.js (see below)
var Module = {
- preRun: [function() {
- Module.FS_createPreloadedFile('/', 'haarcascade_frontalface_default.xml', 'haarcascade_frontalface_default.xml', true, false);
- }],
+ preRun: [],
postRun: [] ,
onRuntimeInitialized: function() {
console.log("Emscripten runtime is ready, launching QUnit tests...");