From 36bf40245bdc9c1c2d7e8c715b7e3d43118eacf0 Mon Sep 17 00:00:00 2001 From: Sebastian Rhode <3833249+sebi06@users.noreply.github.com> Date: Thu, 12 Jan 2023 10:52:02 +0100 Subject: [PATCH 1/9] updates --- data/nuc_small.czi | Bin 0 -> 628768 bytes src/czitools/pylibczirw_tools.py | 1 - 2 files changed, 1 deletion(-) create mode 100644 data/nuc_small.czi diff --git a/data/nuc_small.czi b/data/nuc_small.czi new file mode 100644 index 0000000000000000000000000000000000000000..67d0d7eab599f73a058b6e598acc3860528e8aa9 GIT binary patch literal 628768 zcmeFa&yO6*mmjv40mEK+Pxirp0HaJpwuEID>-P_DPg(tgO}5lMq}V+}a(0DrvMam0 zcB(6Dv#Ln;fChB23>)@E(7}K&IvDWD@E)v_1RWNF{SO%a6Lj*y@YROlPef)!#*Y^n zkwwl7YdVSVce*)&>V0zYv6I6i&#^yu=% ztG9p9$Qk0_%YT~u@oSIl_OUbK+b6=?^mc9b8_5g#24B2mf_0BM)6H4%&iJ`1xP_Tk?;8`Pcr_!*Bn$fA{2n z__zOq|MTcy{MmnW^uyEd&rtgGeY{wo%~oq!^YeaD+yC4DuxS6E|Mf@z`M>{P{rR*1 zZt#!(`v3k<{-^)%=!eVGb1=l^>@L1oM|aEB=d~-K)%O3ve_6Eucm9{?AO1W4?LYgw z|NZ~?fBdgc{^NiAmw)HwGY)cjySaO}h-PzvV8?%E@^Sw3^5FR3@&JSRHk0K4`#k@7 zMoIRL54uA4_fPMloA@}n+T4NhCx7&ZjmG|YypE>PI^uuf@24M^aSFa%tQ+6Q>1viN zzIoJcwjbr?sPSsEI8UbWErLC`3z{|gT?e< z8ZFmUrTr%Y73&H6x1Uve9;MR{Q5s*w>-B7LGs(U-(6z>&9v{3secU)W>5WG0M}NS7 z47%ONjl*8>)qIA9gghnXaJGfrQso5O=xK(Q;I@wr3+QWHr_0#EM z8h@O$_Med7CDiHaBEDhs5l<)A(R>x}KT$ua_14j13exe}S*M$=E=U}r=E~58w<8*a!wVu7lj8DIL#Jl;Z zk@3O>W^f*Je5fc2B|Bl#FV;zl?wlm^X`D`u|C2xaZUxT!v!CPHYV~I?XIJY@8n5=B z=+#6Q0RL@t7f-&8KkPr@zbPf?73<*UU5;B2MgQssc={3Sv54jq5>S?E2#)5-DxSVr z93^+ldAx=I71KtPDX==FP9ROudIDXIp=&hEOBBeXWbr;u*O$rYaJClB=^vHa2h-`B zySWtnoT-ejYW?MMet$e$E$0zu5OiiG8abfK6j;U_FCdpQQ1O_lnT(RwX9Wbae{>ry z7V(@-r)bcgaBYHObseuEeHj%=rvd4?m?Nw3I^VHvpgS?`B?!|`e)Wsis%8E00J$v zhL}w`bNk%zJpCA7Z5Z`lq)g$qVI`S8GCoT@XGyX=j<08n8G~6(_MezvWYvOZ9OFth zjOjkO`isqM#c}XLoyC}1c+}tpuVUDwcyR?=G3X9jo$;X4YW4c~&#3aO4g?cr z$n;|U-I947!V7_Oy^gg{w$H)wz(h!)f!DEFs$I`oif`EHc_GntWu&Y+#(M$3ejY#1 zjGCZOR>sR;Kx5CN<)qcb1c*O~dKXtw=5L+HFz<27<~9F9)P9*{B#x)A@(zp2C$n_5 zetUTvFW&4waaSX7Ud9*+2zhZEE#sAJUG~ROPGCA+K_*x5NYnJ7g@Ch=IK)|Ta1oV0GKE6!Ol1LKpFkXL%<3+~%Vw!!`2+S8txF{D_@giQGE~NEV zrJ$q+fv@2CBlJ0i&E{_W!F+bJh^K4}SJAwvqzZ`!K%v&;9iID~qkfsjQ)UL@=>=AO zKuA{$Lij9=u!3wgJADdIRvLup^=!JnopjsdUbEjFP{^_xLCBuRYymmxjEBSKU_7L- zpTfkK70eo^h6!;CgwVMt8HRz$9gtH)IqNXr9fJchUTmDamC5 z4Gsea3ynn|emRbB(m0-U_MZgQB5-s5@}c%uA~2uAzlBn~*reCdRs3SJp3ma+6_%&( z6`~4K1XLxOAfQabKxYKh3d4PQi{-~HJb{bZ&0RF2e&0=rR9<-BkQ1v;1aQ5wvARhBQ|oW*v-Y{4L`ZjR)1%KA-wwV~l1 z>4H_(@BM@MJox|>1-It_;S1eW%K{VhaGA|WHtFU4GM>DGRgYNMf-Mq@&vxH1vdjs| z{D}N_vK~{KxwpZ7v+H0%ruUtyQ)qPNh^v1Q2(z`MS_y^_LNt%nZvlV@X&T*|E^+3| z?wbgC-3D8{e)H&{{itz@fDrTJyPaOE(`vUyy>74DDdLHROU$`0BjKA#uQO;jJL4W^ zfO~mw2x=$*ZzrQJ0D3LhfDGWR3m`KHgYIbDYz?9RTn2(;&-Hy#+i3uJ_0!eMG+Bdt z;JOGEReZ52odZFZrdZS~OC$+R6gkjn_89Wj-h&P^KRRG@E^jdI|K`!t`Fyrq#gD*Y zd4Y?T=H$~n!bfRV%~|9n#UhN{P8Q+VR0eUe2Ct|xhKnV5#UCcacB|R#gCl-$442nC zT@h0v=9(y?DGtS9iK3%byCO|xyB-%s5^K5Cb}XrFciY|Gpyid?cDFO`HU|SKg|4~D z)h3&rVW-*afmLgU2ukrCwRr$-rfr@xo6Am0QPq1ms_aZ{-{}4PuJ8aV#^{W?b_V(fF3uuSYqs? zxdn@iyFRfk7{tXIQfdWb)Kbex!v2+8+rBNeLs;99b4sMO?X|k&R&zM2u(<8D`ptf? zwzh@XnvrA3GQHH=|EdJIPO+=+6XVh{VCbwf-ZaXc=t=~@`LIRQ7Gekf%*iY__n}bnpZ;SOUl-^FtEqDa^ zQy#fhIK(Trf-`El4U^k;ixAu{Hn#oSx1)Z)-)RndRRU83M6x=y!7T*0jl`zil-w>P z*SY7w?dY3{!XjSD6|7N9ZlL7axiFp10Fvup;*NX$QNP(LR;;ecQC#I=8;OXu$yFqG zM-!$PlZ;2cHLk)UF6Q7e&U|IT8nxsGO0JzU)#+f7+wm`Q+btw64x8OkmFUz6ImF}I z=qjQMV(g~T#WHu~Tjr`f;+0*&8@22P$?hRxblI-CJ0nKMm3y5&mbn$Saoa8RXD@N6Wlo0SyLiw>X0zGT`Y6E{$(z5P@yCT zh41T{9O*1$dXUss0J!Gp0m<#I8eH(mc;s8x%YEPZ`cqZ-~=*GVj-OEzekr-gL>-Qs!(sTA z5T*yIZG-w~kYaOpG+7x;g86rD_;V@4N!)@({E{nJqn6w-n`>t)cj43`rPyn8d*fca z(`*-q23(V)+FT^J4eQfEisbHSx-yug*D|k56C9WQgtx4)h>JP6jB}eSSfiF)LC11$ z+0IwSHARqIrz0e7F6#8yDZMJ|T$bK8EM(m^Ne{?d-qD0*c9&JF=iKq<^7v+=u!vuB z1#7sNg5#{cW$7*38OvS775eUIzm#0$qYekiSQh&`uDMZ7F7n%kBe^hk&CvrA+#StW z29H2+2QI%X7uF6kisK=*%iD|%WfvQUfH!1sJdOqW59uWq@)ge z%~mTs^$}qWq;8DrlR`>}ipA@8G4~N%0@?MYP$@j(mtDadwd@AUuAM}M^cdXM>eQ&x z?y{WJ)}V>qp(=(Kw?mHW(?Y87?r0uW7cO(leIqD$`QA)aCh<$JV2)aPgQWL}Fub^+ z23IY+Vh~bzQ4Pyob`5#p0bMMOkpMXuNC?*)J(xIEcULo)yFJ9I2G0G%TyhHz@yM;h z87`jSDC_vLh%X-zMz`04-1_sVke7<($)G1s?YSl=m-M$2b~LQll=WRx^nk>6XALf* zR0HScp)j}wgSc3O*Hjq8#S*pDYSHB*J8u~|jgVSr;80pyY#z0+%mw2GTU(alhWM9& zaLv&J65Ab3TLzCnY<-K|f=i(6Sdsxa%*QTcl+$jt$&G&l+-R(xWz$R*W@VH zmgPPUYjZ0i%iX;lOJt*!?NHIzu@Ma;o^x}ZgsgO@|6cz;RamyI2_=;61<)sekvEs80;L4>k~pU z_oBPA`N{*VaNP$$bHPp~KJt|-gqHir!ocYg0pXgZhh%qmHG67+#jg7xDCaz$t}J-OE4z|6TwK9% z)^=Ce-DAS+4zbe{aPMOecd6rWZn($Ht_nob<5(__U32tcB)7M-*~&vKcD>g=798S{ zTZJ=RJWS~v-Gx6al@-?k{*!Y?yAKd zVx{Z7^-*CF7jtlVd^=HC!^IS}njeQr{C@kV)jzV$;Yt)jRs^+Nlk|XW?ye>+XN&-ZEj(6cQk7mOajUE9?C@wGiMRMK`*UnqUkxNLf|70$*QagB%ufn?|klaBVS60<0g$kRy zqlwGxIDNbA42I;AtFVY)as_MDk{f1o?abvKyE16tKbwoa_W~+9V z3&Gvd%w_Nhp0@BUa}^Hp3vSLCwcLiut)02t8zHXT^83n&EsloGPEXoT*W9R)Wn4Yg zs*eiE6sq2?W-fzCa0=e%D=RGGVh(Pb`JRwqjaqWUB$vos?v2I}UB9!8%;omDIVf@& zU9+Q#F7hynT_o2eJ)l+Yt|l&nNg%pDXIWtp7jqP%o3nqvJR>KHDG;s*OLJn0Q+wEY%G%`KM$7ngI_d9f)-sr+*B<#cg%lR?ORivzT5>bF^+~RswTw%2A-Vo#E>cq4 zSmh4o=B#USRGW(@Oj@1V<`y=0N3)i}B#>O+Dpz3<7jtmi@^%8HE?A?M+%U9?B5y{T=kw7EN)wG1YKu!L8Uwa!t|$3Q+B6)-sp`lIz>fRanHu9EHsltl?q` zUbEiM75lkQ?W|>_S9I~{k9Rv4J3{StvsYZg>Y5x?a*^TIu1^Zd9IF1_j%F=`NqYF7 zi%GbYy_qO1;$n_Mas_MDlFMkq{*_0l?5yQJ9&m!c?3$EZ4*6}my)I74%XpJ(a#YDh zid(xrDWpg)JeFNd!~l~(a-B;m&UYG%xR`_2;dD_6Qi<}&U;hwS>7x=2gy;kl72F)Eh*h&iuCS4Nk2 z^}=N^2}IYo)Gb)VFS&v>YRP3fiv9CgT|14c-^Eh5=U?iwtkhw*+3km)(8b0Ow%ofi z)!8*m56SB8XdV@~1fuI(>#98B7v7vVTwKAqxwg8(>Ymt{%YEE_JM8<{x@_ifL^s^% z_Pa=$!%gW@)LnD*U?g|2tC`E-5lp{tsatS}M{E_&aPdSfw_4`%31xDz(Czydx=2gK zrC$Rng|4}gAu7xcZdS*e7PYgSJIjMzOJt3a3DFv#T&fh+lj&%wcIkA zuz#h#@(Gc*j912oeg7gCNvT6Da>tbf7b$M-`lyhMEf01!Zy6WJb=tl~uEHXI$rY?o zOKv8&KFKBWmU~#__Wg@oB&D{n#w~IgU6Z4lT%@?Q>$5_N~al_%jT5_f2 z?rPq0kKNeo+s##2#Kjzi%@wRsOKzCt5_!vgtaAs>C5*JWNJ?$Bn!Qn_buLoeIz_&u zYmy#NV0l;bmT}AvS+brSDuqS-k}Ful#S~n|>m4DvBShpa_q&i>|2h|U>2^EKQD6GL zuE|ktE=zGMGB;e4^nfIHSM!!xFxY(*luH@UTNW(hVvfS*3f6EjMJ>7do)D3@+{Ze1 z;9uv$E)S5(SmA9F>;NM9vD=lh?wX_rB)Pkqx7^1%*L@T;mt2KKypk(f!^IS}yAJ>RA`-KU0&5mMpS@L7IJ}jgJmv=UA8Ln`r>s#q6JmTUGX>}!U z)UwNH!v2--=u&yh11xoi{-rLGQagCyYbbLWU9+RgE)v|Z8CVCYWRPlS6PE{A>vnx> zU4=(n+`)O2?F_NRSk4=@>~g{N%Px^dHNaxmeYc;qyDTep&}nvi(gSu)k1D*#yu!6Z zweS|gi!k3#9`+kxx!Vm%T^3B@Vvj<41#{HW8zjBYh}>mFpoSyA;l(*kc5+W{^|)@z%a+uY&KCNE=2)#-UnZowjc$rY?oOKzCOC9;=? zxQ%M$k1!)OwU4FlDBN3ynJ6scmt4Ubwd96L zE|I-FLiY05pGnnicg9%f_QT)egybU8ty3QuQY3d*vzJF$`1F18Wraojk}FuFmfSGO zC9;=CSmnAe_mkc-Qd2v<=CHyWC6HVsy1|30y=Cbw?`rn)2+Q2QcbQwTh+lFAYt)h( zCb>lR@(Al(_vLpuKQv?DYeMh7>%2~VWl-LlH9t*nO@fYRP3fiv26ietbq{F5~Izk^5FZX>^g5IvOd2K9&vF8ugR^h;0+g7@S3&1 zEd6C7b9vBeL3aJyxyVXwkDB8Ox5&ZlBEJnM32TR0hNyNma~WI$+4Ze;6&~@+uHcPY zcA1u9|2%frPNEv%0ql|cazANzS>|%Tt)AU;O^<4L@!EZVP_K5yJGw}J+|kTsunC?s z_bqo-CULO`=TdHY1#{HW%SG2Gz0d8;%QJk$}R5HMQ8!KSGd2f z$&oe}(}N^8Tqh?WT$A*G1b1gmF4nq3-&$8;5f^jh7FS^n7gN-dt3{Zf+nLJ)xHzM> zf2oVK)E<_)#altH$x$U2Np3)1OK!f@9qnr7GMEIC>s#q6EaGC0LUILb)RN0+!v2-6 zbf4Rq%Y$~eJ91y|Cv7g$Qb&-(!h*UcN0nS8x%Jw$%@yn1(cX?`E`v$1JMUZODlFn+ z4$h%(C$PDKHEPM_V(XP$J9Bx^#wyo+y`Pj^WG=V6&0#xyYY5YWL^njGYnv-=?v7?I zgGnH{&Vwmj9^Xt97V%52V2xUG!z9}|6(mU8Ht9M$F`(XCgX z7LxfGqrDx?T?Ugta((Mu_E>(fGt%8JOx48<7cqLb|MlHEvl51x#4{$r!$bGq=lw72y zwp-0nH~fSyBp1=>etlj@k=z~4UIvr&I%D5DS78yqhHM?rQQfm;|EhTj&-n;+I^(8ZM^bGG1q5 z$V?0>cNsC`QP;nri@em!90)==yea z6&`VMM`3jZZ@9RkmR&s;qfMky;U$q#&w0;~w7M)UwTG;zPWVY(xJ83jb1<&&#whl5 zcQ$vqi~F`)zQwM}B!1}?%u!2k<}>d0ojW=STztH~ezVOF8|@4heq zjc=p7_?t)PoB4Y7A)e10t~+k_4;ELqN%}IGE!LC0@faHc<96@yUS~AGZzDvo)CzWs zNESIvHjC-%V)k=9X(KhK)p|VY_gk&~CrSYccoePUnTs?PS0% zO=%596WK>M(DQh9bGtU7@~>{N8t)3XUjiGGx8j51DngL z=T)-iLOy9h5Fp*+PqNX|ySq4DK_z5*WzfY8JZ9;|=)F-!!XGBa`Q+I= zTCJG!)V`KLUf#~GepP-p8klY5Z~0#-^pfVwRC;P8X{|Yiq)d!NB}sxTh8O>$sA&67hi1N9nbFK1!MY) zc)eNDz6ku46)$d_t`692gT<%cJYqJReouBk2r*y1ahKr|O!QNw7C7wb>hko}$pQ1J zF7Dy}q{#x#%QV+nu2P$Vncua4dKbZ{+#JVC7*8(q%HK{X-glia*U@|xKf-E0x3;B>WO3Bse>Xt9Xrl$&ZKeYQ1+dgZj`adb;y0y1R?u z6yowp^&fkMC@b>7pCuodOWA56;N2Z}TgVd`_An{zo;z0N0e_B_7Kg`^N@JWx?BOcC zalhRf!bjz09(Xhnr)=aH_ym2{{`SCrw_Fym7z5^B`{V;(#SqfvJi7lrnr~vUa)$=d zKYP#|&elf=H>_VpiyMR+n1iH!@qlyw;a=mjG`h#CxcK0Kga-q*Ian^|_fHpG4@q~y zD)69^^|=zxrvq^ofuDc&;5d%f5r**YU7Y%CX(kiC-2rOhZ?%h9_PPvmB&5!TlKEO4+ z%a-Rp3h%?+O4r;3aC657DL!~%Pv;1>AzH*tBi9+K6@l|+7JCt*^XJjWr+09`fpRkF zL2Vn~LUDY&n&lA+Y@bOi#n%u#r_;=)Ns(cWRQVqxMvSSEyg8I`A=S%g_fH8zv) zq7|z?7_^4%VP~*+^={hR>s|HQd+)l#_FgL*#Btn<;_+36T7fC=CX?x{CVpR?DfH4m zl#E5@@yDpaVH;Rr>_6dO%lim26f9Mp#}w}ii!JppZ?hKvse|5zJ-II%t=8x9`Zk%a zCQwS*GW3iEcy;V%U2ks%028ED5k=8I2Vh|_n2PQvrf7N}|2T89dtT~o{fasIJU+va zh4H%%Q{DuYB8s2t<6G$6YynZi-Lj5*kpTyoMNync_IWX3m++~GQ@CR~x%*LR`HvZ^ z6nFS)!xXrn>z4{ssHp2OMb2erKqA{7G{3r~aK8>yWRxuayPsmUV>Rn$R>wbRID?7nYBBc%SQgA^=gR0k;vy-5FtrD`3d zP~sULiDN{>`bm$0Jm??=ztXi>QMz4~jQ{q(_$m;~4lPL0!aCUa=IvBozX(V4S%lj9 z8iEIb1tLh%L$VOip)dr85^xAxv;nmOQ{KdH8KmIMQawmfmB-YuL>;6kX9wmA7kgyk z?kC`;&L9P2_q^2Gx*l){<98jVyontSQq;*5bTUP6^ovC)RMd5pBDdLQJVLHAv=j@s z7}imWjF8HIMf~B*L@6>wQ5vS`jK(U4o>WAw@l#l_T}00 zXrA1}i!)r^0}lUMFQFI@$GGlQBu-=uzI9N7-9zzJ2PIe<=`Nqs|H8-gu}fDU(-&q) z{N~RucssKXa~;#;ojJ@(O@#Lo~y6l95?23cw_foma3oxEm>3 z_yll-Jg8m5H?od9$#I**-1QEmP{bZb|K+ozU&hN<5!Wt$?c(SBxT`MysEsSldM)AN zgS%`0UZ@z@Jma#Y-}WA^+P`NrV>#gUu8d^Z;+HS7D+6utkevIMx40aufe65KhP&0P z`}bAJwL{K5ADZqWyB%A>g>uP|IrSQjs)>(0b`+Y?@-;g1#BJG-t&qF989Dc#7}8YezHkG>KD$Qp0=MIn*NHpQCHGHP{QAo5nt6Fyr~1uY z_W|TAS==0~UM9Gq4|ffV^8(g)0`hQ^;_5W^CWYs_3q^=tb_mA$caB(`j zpEdu@f_~xz#}H1C156FU_--NPlzEQ_2+R_&3ZH;wv>?h*%5QXK_g-ed*zl;6-_rlx zVz$Nw&bYpDvSQmX{OW$?qYcYA3$sr(4jQuyN~u0OmU5w&h3}KgFvNzao5RgM@8gE; zG-Cy`2!uF~78?*5(wF^WR)MfM!GSqhIDO9FyHY;%)aq*%TbcD8}r#!x45F#U1+t0-Cces z{~_+B#A7&+X^BW?p}Q(r$c|&&InMPq2RTpP$BeJ2&lc>-3^v`EAMQHgIxzlt6;USb z;a=zIUJDnLS2Bv5yHKm>>;f+6AiKSP2FHNtFv3j!!Te@pAv?YAwEN7{2pCTLx0)Jf z$A3*rq-Yp}ANKQ?HI#>8DyTxf*yY#rGYM1vvIOgN^7;~PsnpHs!8$<+nZ zldgpdRvMWH%gs)vm$gs!wB@fFy^5D{v}R5}TLXwYgq5!rBvab(if`g$2@?;O@%l>KsN*9N@k;)%>MTv^fmNw5&UW(u`x)RLCC~d#N z0QHgG)tzOw;gW3mphNi64#i|hEPCIWcIBCKNoukgH;Y+p`^!eOp^|VNM1~pa;u>Lw z26$n;S$*>ergb)bl>0AsJ%hNci;Tn!AA7Sa#!HNFocKDt1f^M(#ws3)U|zn7EbQ>Z zK7X6Tgzb(8kJelG$O{nv&AOaU>zdg2)l@`W5$&x z)=cOe%s5ISyHcM#Md%qj_@mlZk>G zNxbvL3u$1kd(b{s*y;V@<{IxYb2DqA!jTkLRT^=A>#iY{zGs2*%X+y!eTBCv&2D6q zo8gv~934>J3Nq2;9O`{eh+k$T>#)p@-oL@SPVyW z`9;ViSWa0ihc?oMUxgynAF!U5-|-L?b#zI5l$*j^GZw%i`*eZjyB?pgo$vx2Ur^R1h*~+_ImwNeQF;?wx*76g)S%IPo6Hbunav5smQR3E!;9kb z7EkQm;>lQ(MtI#9JD){cvLcOAG6mNGq=LW^IHDGfj%ja~+RTe+!0#5T+bCV0M=ML$ z<^$UY9#CRy`UW+h)xy65e07I>fvgtpbe%Z!%v0)E&l9JCjQZ1)@C#qn&M`w=*Jz`#@L%rfiGp1-@wYccBL30iuHj>>dL*^A+fTI@ws zycM~|1CIHt#TEf`HYtH1nm|;9C%;)mEauH#KBvM8h&ZqpcUeqD{H6ybGX$A*;|P$FvpaW2)yOI5>V6X zsuOqS_q>^O@RlwJ>8)24^V>koCPgymT+Ri_iih(!y1Hd=frsz$WiS9>@1U>vDUn68 zZcJlLKRe|@l-on*?Ft+$TM9Cz1M-hLpr|7HYX~oHQCviKv#Ts|hmt9l{kFWFU0qy7 z^Z1)bZ9H(Fzf0X24={hdKK_GuBZ;6U3)Rgcm;y4|cpgXWjakNOVo(?m^;I7cj{-st z*<-{xpfM;OD3otn@o=vDt#0V0a$MjE*Ej&HlF#S{TPYyxi49QAj-rTcKtk`}xgPG= zT+DusFOwWz?6S~>CYjhBK5$BJV2P6yPs752T?q_Skp(YWWo{DB?lQW`-vA~?XjbDp zx0ot(N1T~)f^FDV!kQ~c!57@wz)%*`D222*Dp>u`+QqGk>-gqgCp+CV=zft`=S?EP ziK*voI~$&hT4uHeSh=Rj-(15AQhmQYdm2qy`V`d&WXJ?#@7%M^wHJ$n`5cFLpL%%K^qs(4iJO0YI!idVUwi-y)k5!D_6C`TNA=GnHIzk zCd3a^8>g%9mQx6(7-Kxmb~7n5^X$9(iSW9KmE*nAMUO^%?-9B8n$Z(@;d zEm1~dQJZ5KQQF@mgo2p?sDIk^^F&#mCN28vpe76rH^-i82GNBe9U!OcJ!5&-JgEcw zz0*95l1QLNN_RDKOa7|qo%!SVZx-J25pw@IygU}!WYLdnW-4FsoK$KR!FfDaX7Tj! zUW7X(XrXILYfDjp~oD_Qh|;c zUo+$-<}O==bJS{es|Olho`mrCu$P2rirke+{+`$ z@e*1-nKC~q|BYZj+0XN2!(z2JGN;;6QiemgG>WMgvSk#NVZq*VwmrvPr>7rrHbVr z6s$4}&VKES)b5LQyj&qd!A?6_-|UtV3|_8pUtDv^m~@*x)bg&2qY)q^(1|Ub2ckPv=D!hBX48I-K7MoQ$!SI5hBS-cD?nDf?F|O z=357^qQy-t7w^Vr0`x5T5T{u(WShlXmA{GFFVQjNG9m9D8_~rdbj>`3DarBO96^$! zBuZD1X8@b!1QsMlrOO32F7nu4e%}fk7My0fkU;YaCk5cyVnGt6SGO67+TB4DgP@8C z3<_kfnlg;&MS+uuvGOHwRz(=B$4RN0WFKDmW@#&@YP^Z;%^?YH?b%i0wFd@ z(>TuO?RCtqG~}_`iz`HYSeL(f^gN1Eagv$F2%Ik?w)2aXEA4FYk;*zuhZKLc;pO41RFDfaXcR%cnOKlA>H<-T0Jdg_^7h6Zs!aF;Rjc4t>vetnjp zthLW;5lC}4nc3R>SE7>cfadP0{*i#fK?8Prc8Ze{%*ig&rKNp!l@rJ=ewrk>D-S;SNRsaIr;Xag|Ehc3>N z3PU?xaR{Es_AJKnSROW$!a)`4Jz`cmKiR<_?8>A78>vV}W`03-=%XmIpz~+G*dt$M zSJ%OAbDVrN0tgQE8@k?`Zh=5xN%R?5-Y4YWjRKt(_D|XS>FXcsbZ@e?#i8VTpfd}Fvon{ zY?*SCVzE9|mKlOy!FOc2)h())I>yrSD%kiaORy2cJV4$zwv+k3^aLq1+TT{&{CW~3 z0ARxc=b7Lt>L0B7I8?~Pe9>ISZ-nxsiY#~wS|yviFc=RfmnbAgoNb75t4lemg0I=}C$8iX#n-UWRV;-0%)$1iS(*WxAN!*V4wpD&Oi)0{PdZF4 z&|K#laWiPCj@;$}b~OtSX;n>h-(z#- zGP#R8iYr0jWV2@bX66CH3F9{;-dtn{@zFg@Q{keJ?h?E^td)@Oaii!V!6bHxPAw?2 z)77`kT@@~2A%ps73oc_HG+Xcz`@^dDES*g!FC%swX951@N>y5i{)amqZ+ZPhMspv`^MZ*n$PIki#QEfZ~Hy7g<^h1W%E{ zour3H$QglU$0RW0NSEjs;Fzle(nbsaj6SBUvE(5;TO{Gs3?W`p!~o=$2}sAY;$9|+ zdU=sqF5@9Ahoynks>YvYcLL{^y6VDI1Tu;nWkhTqDZlL>+j@P@_ql%7S5cXuN}o^Y zaFuS#O1WrF(m;=y6cv>2biS_cbic0d^uDg{w14b;UA*&Eb*J|gQAc}I!3us$et^xWKMX|G$D@6s`p$~cDQLPT=Y`fuf z^^0idS{>$l0;KJ#)nOTRYIXRCIiJ6pI?VEyGkth)(i@E)Hx5qv1N^9+#acU3rv8`p z>}%~vD{K&@*yj@U7 z9*sjJzW4?LCo-pqTuj#bvP>h?`m&5VwZ8my^`#PdtkoquHt_4OE^BpJrVwg%Sw@{& zUH-c2GLKCDDbtme9mm5sy2FJh>`mm9r8VIqRck*>9ORn*&B^piBsX0odDk2ly5;8;mq?0@h|Jm`(>4#($1ZD%~4}6 zit60^G1lx2YE{X5+er~LNwJX+vk8%K5;R++$=9{lMmvJ z393W^JV1Kk*R|OThwL{|sNVW2Sgt5k9vRJV zeq|`8Q~u&JyXrW*4z>WmZSWDEijCwK*s5a2@oq!*w1(QM{8)6!z@pa95t}zG%J#~( ztj}jxX|lRXmNBd}2&tXR%O2X8#tSedza|(HW!PxJmi$Q$y{2rRnfTSwCdHNaYer8u zilUAVd>sA!^E`f;ChP1XX(tA=qK@W_y(^TDlijMFq)~eB#6{LN5GK*|b&~#clx%Ri zv4l8hMnxUX_jt-LW1EvSj@hN2{@#_q=*Z6^%-%(G9j{Aa ziEy4>(}(E-igK~$SD5NPq}13p&H~25eg$M{U{9^Oer!d{B^0pm7EpQ%FW&t*I#@A~yzuR0 zuiI~Sha=vA{HQ||jT;m)`r;zw7a@>!#z-a5Ubn^G#1j)~^ed|=$+6cNH``rp#P*Yt zY%G!-?Pk9-_*y80H{rlom04)i#Oo%Z1Zp?Ct)V(#4}tc&?dG_vL74Aw7L6B_<+5o^ z)|X%+Cy6{v)M;if#Dxf2x7Y0Tv+9zwbg|Lc?(L-0?DxJJ3gM}}ZI~#Wg}TG$V64fE zkA>P0;Xx+EQt)(w{+cZ0B#?)NhRtk_Ls+QSYqoT~;-OK$IUH-c{nrt)Oq{-qDeHx+ zX*_e<%M4$bfbDg<&0#xZ(O;E=D$=K%gT~EpV~>|$2fE037zj#h^z~54sisSvA($GL z6_WdGhV{^NF5IEc5Cg8nMD1R)Kh7HX3Yf@AqH-qcz<>wSs6S|SJDO$ka8a+-Y`4A| zJ>=BXrChW(sGz9F%yw(0$wQ%GA0DIT9Q>A=+DV~uChCtXxQt`w09Ns-`<-TI{2P!c zBgU68X8mAAG;Vf=mAs)L__MFE+^@qy70FZ1L6uZ>x6>TAG=}g98J4=k{%@d$Xc9R! zb*UQax0)GS1o(@+W~Wt+hkDKS_&4x}DiX-cL*b^ZJ8JgYRcO>6HpgR4ZT*&FmJzP% z(!N*4Wo);bBYh3wk+QvhyE)M1@~b3~hl{$+Oj88N*Mq2$}heAER zi2pLC3_E{eE_GHhY(=Kk{VHiv$~C`lnrvsV_F5HI7Fen2K8#0>TFur#Tde(-JmaKK zInRXK^F4%>2UXTx2$1Q5QpZ5Ave zWdDf;qXdBVznk%t5huRvWn#0I#K3!<^@KR+&o(B^Lh;`13-*fqe6GCOt#0SZgFEK< z_>PHv%^5Ny#IwDQO>p}%-2FV4LfnpKG}ZWSFUUb;T<|3X4k%28ys!&00es4>LzrnA`{_+&aJog`|os}P1f@+F9vgX0Z8L}tt z`mMp8MjbGFvf89EOU5#0Z_;L`n$!w%x*%)(*?I|av026`Hr8fS%=CEhnEk&!9<&~} zx$(%}xG(lq_D}DS;d6MujxT0Eo9!Z}?iEtRUM%MK;vLw2zuy{m&>Ia-?G7){`;>Ku zz(#bi09TQoAuVEioD$kZsSQGs(Aix%c!p4eB@usKhiO7wEFz@DZP=yhMt}1R8Kr{R zsZuyLq`ZO(cQF0)&1!8n#ht*cNUhA61$l%ok=S5DK;tN-Do`dg$1~goAKmjC+jXT! zuuU#P4ag7_8I*DwDex-tgXMC5pVdI7*fp|?%#LNbN_M?+EX~XNrHP8@=po*JXEl)w zXmy06@U(IqUq_p{Nlc<4*C?7f2CE&;T`$rZ5~R4wE+LCq)r)I8;+W0Q^s#DfLn0Rv zbz~XS9L^#$cZi@q+k-3W(^vwD!psCRE;FVna9YGrih?Nst!;zZy>)X1F?5MjfhtA0 zJsp%59OO`IoXuA2({+3oFnz600KA#OILluiw?CcCXG{K{#`7ed{hUBh=JWfbvj+lHp>V zBAUWFrlWPRN7km8*{XdNaB82`wQOLYRo!oaJ8=ik8C%n8Xo(rq%FkyDKP6gCW<5+e zXK$fq{5S`ArHZ{%rDKggIOIfp1}{rlFSL&IX=sX3u9K=uz=3xw$}a zB2JA~avW+KeEl5A9j|b7BID-*%ElE$-`3;S;|?+l9(Ot|Yz=4l0BNJEGH%vEfs7a{ zUy_J0OyHO`gJ>gA`>U4*3kA50gtSl>MECPpF;yIQ0qVfI&7>)qjhW*miXUwBcOr(Y zRI&P{lx;m#CXUiZ>cxz~2Kw;<>?4gqy6O&E-q^(mZ2G zO2<(}?Hna{?~=phBhipc3u((G|7fh$A18~3#2SrO8Yn`l{kNOoNIdO?&%d(rBbsut z0w0xJQTZvStow>qBs@JBSsX~UV%0}9Q9?9kOZ_7detLnM!O=ZuC4rr`4I3w|#8A>e z_#EdFQD7#fRc;&vcDyicI;7fRYwsWsw?-5iNqI9>NMqx7P>0WaVZ!|^qm#%0CkLP@ z3x5NaK9Xb65c!)5oEKFu(M@qg0quoPkfNU)O_og;r-M`^eu^r3N^ec#a1Tya38ZGB zuN+Nzh*?c<>1~+0%PtKZ1TCtCrU_^$oELD1lGu$bT5wE1-`PBztpkGMZA2p(VhLi- zu`Jk@C^`Z+`e0IEv8MHy3OvY0{cO)DAgin0lOMMkxeiRDtuo>QA2H{=kaGoAL1CfE zJcesQT0K?KZcPXaNqIBDgyHd9YYY@au$vHOiw!OXI=K3a&1}W5Y++YpQ1=gH+pMJ^ z>HtbB2GXq9LiS?|Z#$Tf0VA$V)n=sy%x!%26JNlTkDpl0lmig+ORUxSg+d6DmT^hb zyvCxuHl5o=v>ifwA_Xe@bbw-9>{t{Z^d-@ zSukw$0$(L6RGH|^Gmf3Fc@*0lCejDOjypQ_Ct%Tl-`C6aEkLgxO>N zhf(T&?6UtOBd_-9CLg#dI=SoW>2-Y8&_NHJ}vamaOlbdJJY{7J)#*|sVnF_Hhm7~~(JMA_sCMBv= zDryJZu%D3uHAt9-gDL7)gyGEHxfwB^d%0-|7O^v-GGYX7^kCYb?9v{oRT~?v*wk(YGlx#J66W9|ti&L4VW)PCP+n?rpvmRI z=`{Y7gEm;_3<{anS8at=Hw2haU9h=0&Y+`ZkS@;Jj>$P)2}V~=t_AXJp*l=hwa02% zFqeo$OmW^cTx`aUA2suwm5{c9&I%p2@O6##fVk@($`KO550i?F#HUaA^ZUXA$;rX(v(| ztnLH29C^s01Q%{SZQg2Io-Ht@k>I=hXo4F>=^(0zs5qZsHRYDlV!E~zRfeF!8Jd_O z%T!rC+e&ncPCMkcsIa9FTL{!S;<+D3?EN8Q@+Vk87lkxPpxkN(tH8;j9f*U4RqG{X zB9!(jaz%bsLIXQzcpB$#oc1)bHvrAsvLZ7cR#heSb=rN_@CZ{K;s}&6@e-u z%t}8AVNyzi87_+!+K_6uvtxwVZWLEc1agmkOwPnn-byeliBotgdP48pvhifMXDS<>0}Ry%Pd&Xq+*~770dFT+$H^wJU9PHX-(7VA=UmemrMQ9` z4Q7Fo(NVg^*|X{#;X|d4GrRL8kd&&^qyQ55f~f?Zn`0uxBQFVZhTu(LPp5z&Iu&&8 z47Z93B)Y4Uf34==6kU>VM8~R3vG8T`?hr~hFR7?-btt=&{7Aqn+=gM=_Do-A;IdK) zd?2qCp&$b_hcCk)%zI?dDcLl8P|^5j;0?CoZ*U4V7##(lNt)SCfZ1*pJ}EMs99YUo zSj9Nl<1p=k*@3M%vmjFrUe^EI?7m2GEZN{!VjGTwvC7b&w+ooWJ6Pl!d-@60s1P}Gee@-wpHPXJ_u_2QK}siP!z)IGb6f4%tu}%G}v*BFa=(8 z4Y?Hxo{i^^(erOMsVPugaVTc7h#^8rv}-jih88!Bd(G znb80-i^m&0TFvjZJ;F`S@3Pn69U6+PtM-kAKqXhVKxGO_$sRKEHygZoG?2c1QF%x~zo&mys1Y8+iLRA5Q zSl>-B+m+h`etsZ&EuqW${&|Afz|iecS+24(jOM&(E-TFr1p9+o z$jSj;)_+x~{T$F?kM)|Zs3_E?_->$jA6>=KxZu?`zD;JU_+&OG9rES!*U`=CpmC`gS>MGn_EQkpV4w_uvrt#`FnOnXnUK-nfqTrDgeTV|7th#wr zGi3yqeG%02GqiI!0)=;2LHZuPB{>))VUDg7mNe&TLD&9d3sQ;u>4o>Y9Sun&UW!%W zU7?xDeiP1v1pf37p?-y6c%`9L@l+$L~)=DYH@HE z8edbv%8woiC4W>`sxVWw1}nkh8&jYogD1$=4LlNrCmU9{G6ipMh&xmGd^Y%mlJjQ= z*d=Vi3RM}q1ch(FLCOspfoj6gZDf@Y)dqXxfc5oWswBS(aEw7>@HJJ<4J5~t7>VyV zFGOJFS?PI^+9`JWhgIK3#5GtFsXPwhr)2BXw@G@3OIxxZIAeT(Y&^USCw}5g{PW{i z?EiR{hCO%qeina_zdRoCg*gAu<96#)e&>bwiIiqBP}wK4$HD4{ozD;`b0897bwwl5 z5_U?K1*#Nz9s^w!hcQI2t)-AePVf)0%+CS{+D4d$K_$m`i`8wEF3+Qtm1>I!gd5tpYK(eZt$R_$S z>hkslnfz+N?z@0>jG?MkQ~EAIRm@yqvMU(*de!Lh#CV#)tX_sHkkCrrG0?~6rd zvvn4~H9Ablt!D^k5cndwUVn&EaR&iYpGF(pw%D~)S`bJA$)P!wE;PQTq|P^bPEqGm zE5DV6-^&6!#BXJR_2GB2K_6@yg`GcQC#l%*l6RZ=^}aX>dFFM-3}9WXuI zi_N=Lbhn)Ihn1=E+BS;lW5v}J)efo(IMHC0J zeqqU6IDG8{vs<3JW)GRtzEp22ahyDRUV_Otv?@Ld(kFWrK!GEWa$zV>IlD&Q`~}>b8~7}95k#Efj z;4Fz9cWJBRx7W;iaTSivA_OG7N+{(BeResnF$e6}p;f08K_Im?BWkr^5#6p)$$(zm zFRt>dKiDzC`%mTv9QP9&*76r(<27Kh4#0psdvPyivz9MdrByM=jCm?%ibHO-cS zC6IB|7?lZ4jF>ej)UM%)5i+LrQ-Ha!ZppW-M|wLhzU6)Y9KKZ%xJI!h-*VJ$zU9U2 zKYp5v95r<^86?r+mso)XvWeym=cpyP3v~q#7#H_87{2@m3n68HXFuTmwzI#pACFs~ z*vT~hA36EFKeA0%r?$|#9I;#L^5QCdRd~B9CEW@!8(3JlNbFwZm^sl53Yjja+KFKH z&7vO!s7m*j$%i=Q(d?s5il;2hC@7&avRfr_sfh_%iZ_spRb^*2z;Tqf63jb9|0(rn z_o1Q^JW7%*5a_^EKAb8%D{Z*l62Z83rC`R&?YR?!IwZDt_IkFyy?8Bd*ksJMS;-T> zIWntWDUv{DDngAGdW)6p8o{jO8Un>nb~C|b&*a&OpqyY~mMMRg$!^0mPj-zeoLcM& zI`Nc`CQt?$_QnTrU*qC+Bj>+tgIi-sdkJ|to5$Hr1jO_1Y-32TpmtU-31n2wb`uy% z8j#l1?{~C5y|}(+|A(zUn=Rrf&40j_AL6+a{zd#^j*$*q8GQ{F8H$uWOBb1rZu#{6hEMlRs zi)wH*sOQ@!QLg2qwkLBB;Y6WrUM4mas)o&6Wjyq$06>ls^z7r&KI zrD!>5W-Y3l0z)7x7n;>XZmCzRLNGJrfwY_gvWJ9ew%Vwo0QCu#%@D4t zTV|`b=f$PYlDl#kW2yI1Q!lwQEEaQ}Jw0X`Yk_=p>y-)Im97@6eO12)a+9VQEqEvL zU`8@}ODPhq)he?o);bgdXU{8|PYpL=Keh5eKjR!PM>YVcPo(WuXt+a{$R?1J5J5Zs z=JWx`jS!q{XhElv_a@vf-IHnCnYeV zIYQ@B&3%UWh?YE3B&r1pQ(oPcCk>BpvbK>f(SQ+xqNuiwtV#54k&{)nmOK@MN~7!s zxKrG>x@L*v0asHWe4Hg$(Othaxcl*ox7Yo#f2E8Gz3 zXi5-Q77`z>&~}4`#XnR^QI~ZyjC+(toDZ2g=Hjt>u_b8eHNrp6hiHSUi1XWl*^WRs z=1kKNpekmbmtGa82BczR$2j;sBw39hH^?}pY@<*cxV(*%G=}+z=9kGjnx98E3pgxS zuqZf`$D;g29u!vEZjQU{7Rw2+@TslkVse4O zLJxemm=T$X0VDjYe|oq7#Kxtz^eUMh!S#z%WWucC^_#6I2|}89O4@k)h1z(lw!!In zz^=O_(=q~x)neC|X=o&Vq(?^VxF^VTl+5Qb-WX4c=PdV$Lik4CJUYKT%sizZS?ncU zMDr%|o$>AI+1(?Fz0uf*%}Q42IB^tYGHQ0Z!{P#3Rz(HPGFJ?ePFhWXWJPLaE}ZPP z*KAAGgz91tQgS7e}Y?#q!LRsa`qZ5NQ8m^3$D;A9=RI->=y|}g` zj@b-NAFEcd%b<|Rg+v`$#x#es$aJ#^>O(Gy>NJu-nrKnj0;fd_r6`yHS}z4_*}Zi$ zMcK#|s8W>M(?My$K@PP>r5p(W-prtXKZ#$ECvh^LE%|>M&y#faGgc|le14B)iC6La zI9W=EUOyi4dO3_;0X4d64FTL9wQ$Fk4t73E(&74V`iX~p94Auke3E_pU{YWR#hyXhJrPuV;r9OM%XuuOi}@7 z1IWN!ea}iimq-d0PeZ{(B1hpN(dGTpbi4?$dzMD`Soi7n~E zap$l$`pgIQ3zi^8R^NpukPLRrJVVRDow$R?ulJQ=2z7xF*k@JkTi|M+wZh;&vz&Gc z(up~2dx%|itf4h_hV-(KyyTAdR0mt+nU}O_I4k5Mx0+qtqf0|O$xtQYd6wLW?MAH? zBG$zvZ+JoQ<^jstZ0y%QV>KFghOC85V)D9kdKqG;)EEKh) zDGwd>1zGCB-5atZa zf^BJ{BXFY+ri9sJN&sY|K?IPs)!xaE+YDR>rqNazaDk7QabC!IA`db92hHQg7M#^j z73tPwu#l8D6HFF*3=}tH@mVObSVg+ZTiO?T;NnoV7g`Kkkg6LFG)CfC>0*M|a%D+~M2P<8Ya74$d zOtA<6=iSLc38YnXm#2TeDKZ~zeB9|Kp0A$d$(31uWUUeefy}1ljq|Qa)RwFoO$+Jr ztI<4<#X#?5#G(g*fhC!x&0AEq;?q9;xIn8* zfr8W)0ktq#`3lh`fSujmk_FW!DE?E+tFi|H>*_wAUUSx--YwVn;9><2uUCpBaQ=!Y zgN43y5_t#0HJNJ&0z>&ws?2F|>Ou%X)>Y4>W|cl;W;F!raPs7Pb5h%nA=7yoQ9W?_ zieNyF%9=FsJy__}j#1Mi5LTZQ1CkiEn6l$|882`UF(fAq4pENSh7M{Xkknet!r7vW z{&wF4Z4JgCA`N9D6j;JW8t50~qicvxH;VZvA+~*d%T1*!%p;FYdeMauA$g1AwAS8xxoYHvd}v^VoTE)#ldtX zyG@c;8<@zxGN?cpVi2;>A4V^|L=Hb-NsogN!sAUe&mx{jI3D;8Hxb(>AccV;>#B1~ zR;WC+ZY=(`s!lZB7GXW;ij@RQzG7L5%R9!?sljEFY`sEaJ)L^-Rc0!(A%R70PPi|K zlSkHlRM22rxf3vuf7HOOhS<^#dTB7nm>s6Hf{wZhcVu%&!n-LG6z$DkU(zuf*@1lF zNd!I#{nc%@My<|p)b9>jtr6Cr5?1Y|{J6#B>991`Dw9^=BgDEW40XTqr$Vhjr-6y? zcNLD1YnB2tVIYdaur&$z0NZNHv{{$P6&TgOYh^*U6x?LNDNJ8xI+()g#8=(U6{>-*=)QUe39M`bNt z%&Dp(TVjXFfun3}TZ);O^hAG*%qMrlHTdWTX`KPycXyh^OX zWf+C4Kr)jE&J|Hh?J!XsD1%yv%!S6+RImurBcbGv>dH3ERD#Dhsz6ufWr83k?u@}X z{uO%(L$=u9Gm3lbHVb&&@^dlI_Lc`#sUqK%pzsYiNWDQPP)z{Vi$3JEBrFbzURP~Y zl?s~8F$RgjI}*5Ze*G!dTU2MwJ)eu)XbR?$RSoRI!7;0;9W~1W6+q$YkO)d!2$uWY z>9JbK5tKGkMd}Op$7+f&AZDG5WRqT*6FUL3&=t#iouLYx0!;_gZCS36qgtiSi)ZuX z9qzKpeqamZVi{kttMKG6?8TpWK8MqPl_1+SPK`fZl%Y>ZI=URpM9ztz?1!VVO3Wm; zL&}l)`QZE6YW5CqLE2aIXR99?e*smcg+QKji+#qrMdN|2rvL=cXxQk(r&sAim5I&* zu)awU8$FdKBP?W~UKVN?0&6yWmP7cnh6N$~dzVrsoYb5)xELf}+_0BlwQDjJ6)9>E zL=r`T45}^;jpkh(DO(6;z(S>%uw?@X@{{=L$?Uk{C0WHG9ulH51YB8vrC(#r8^IGe z)kn}oA}DL6io}Vfo;eV=k(TolCQ-uoG8=G6kHlWl**4MpMJMz5{S#rXD)N>9n2Dgx z1Xh$Z2l;Ll6PFW;*M>56lh+}pH!lekTFa`a;3e3D1y-{d0(lNyg1-uHT0W#`#?73f z##X$k0+$S7JI)ly-Ha(}Y`NMMdHGlQo{jQ znH?3Bmi17}f*CP4o&>C`%iHmw+5o|TN-?v^*5YOViCM$ZJq{g6R_GwCRap*|jlPV! zSsTG5CXf@7u8c3-^~ux-0jg4m?|oFosR60j*sd`T{bV(QBE80SL^cYw0X1iQga^SBz!eAwqEJ@D^;H?8DJU%-Q_HG|S?2f?&}yfl$kKz?hlFltxr$1u z11NB$S`a|NY7JH`tF+wDa=jWa7W4a0%I|EEkF&XH^v4&;_4-4U#^&GJDCxDm+Nodx z*U3jM^Hj8f2I|x6+ZpuWVKiq~t2WrQ8U1I^^Sb7$3ilM39jV=`#nX=&@ zUcWn>Z!ERXeE7Ub9jW*h$dcL;EJtUtqr@3J?MmmUIhP0ti6it4&$KUwYvr^^bfNsi z==u8aB)VEBse;#buLVV_@Qlptk=-KrMV3`m<0hOr^{--ki#Hn~N{Gr^vI&rpT*AIr zWfBF3>R@FSVfYvjg)EdofJ5gGhLmIvRyV}m>T9~2hWX>5p((p1p;u#J1e5DwPC9KDl*V6Y}3;yiS% zAeEpF)D>k&!A!^yjFdxw@)ETZ`KK*JM}?#?RuLM3D~F-bRT3A0&onvWdqqJ3SS}?R z0;-$a$cI3h!%}ENB}1Sx#6o@!F)95fE-F(XsQyTX4wMCfPQp=0D@uXDNr=F+G>#Vz zA&_Ni7v#q+1VM+Tu~rd=0v{m`MPVg@(81_FRul@3G8c;iJx4<0-$pbF#Ds7Zg`wi1 zLD0PnG8Jy1*tNV=C5MWw(?2n$=TJ7h%^shtqtwvZGJl*Cy@P9!!$ zOk`OlDbb+F{#BF_hA~!|Wop7AYF6$khz6!6~!<#6UuK zy9#3$o5uxeO81M&5%CzmhRwFD3arGGi-Vs0)t8E_M8T@$P|NcJO>AGr-*0uj>OkUY zCzx%_M|$#p5(@SJ@8KmkjF?QrvRxEoAZ%;LuB3LHdwAqixM!e-ML%G(I$4A&4S zanJUjPjO}28u{ne@lGN{RlI-w<3+EHTM5R)b{|hwo0!zWm}P(yL^4gt>^*7uVOTc>Tualn`9eaVx4>AE^LAEsQrpRUY`4m1bvquD%qH#cc1ay}JAvZ@2s%M`C#TA2)X zhq8J)l9zEhOQy3c+3Q!E#R4K>uu#z}7l4LVOw8*f{Rzl;o}-EJJKRWmJzK<6_AEd? zk!pPv;cH}are}CQe14LovbKq_2N#j9sp5i>pBA+au7TN!5eUnwt7uPGFW&t*oNQ!EyK{bdD5bW2l%z0Pi*?Ejmu`ZH71-q2JX)<9 zm$!V6-dJc!JBJ5xHgU@oiuMp&+eRHZ6j(gHsBMBnml-uw;DhCIe(%=%qF4pVm9^jq zv%Nj+nz!5IQMWg04acJ~tiJ)FB7+G=4`%kHN=D6!C5$qfDn5fccinpKa%m>!Zrnv; z(^ocOy{#v#(dquxUmhVdX zLPhplNDbF`nTy`mJZ_xOn-MprHrVRwn@4}+(FEJ9!_lzS8x04oF-x1v0n`y?Ie^Sz z+^ocFaV+-CzsrhWK=_qmbgB)b-5hnWxP?mVbO$3W>VMO5a3!|YbaeX7UZ*qcbq2kD zdxTigZ#oVx+u1lO&PR9D>A52YfmnqomVEwMEZhE3S9Vg248Pzjl(il0cyA{uZnCUSe8H0j(@b(U zpDi}YhV8)Roc#q@_awR6tl*`wOMHGtXb#JAb}`b$RkTODn3%(>%% z5VEF?>cx_8Yn`(IGN#l-#ltaR0^Y1qS4E}~Mfhdej*f|v$Gl4Fd(oN*2cv!y-Z_a< zBHP{yWjluX7Rryz0Ht6MZAnzRiDz~fH|A`!_2<}Hnz(n3pfJ3fhLfT5HQSKlpRcEfr?R8?`Q z#b!^&qhYhh!jNhu6__!N@?H|fVCNBIhWWjy#@0~;n8dohE`?y+#0>SDs^W_H}3u zP5Sk}j6|!1kb3d+#9Br91!Q|TM4+z+M%zxbsotovX@=B5f3?^oJL0C@PN&)JBXFwL zQ@NjzRTIr`AmVSUiukAt5%0Ae5#j(5A9WF5#Nlgku$EgaOX}eXFUqr}T-R6+FxoV?&cT{l;=?+m}MW_}_O66u{*y;2; z%|RO(1Zp)E{Hqf3%QLb3xL7C43WAPpM%W|Th?CwZ>gx7td*|cw>^VC{6E9exP3yBO ztjl&bWtln|B2;J2qYPV#3MLTG!?YF%8ACIetXx8U&R|dH^NmQ%$*xw>#w^?@a|p-- z5k;~auuzuYWm^Ml3BJkD3&ztpIn zzicRs`lUzk3wJ)B5=O8kBiN5GHVE>mllTRuB1n+xE=eiH+U8 z7r~8o%*03}stV6G#9fM%x_K?NG^G0Foec#<7Ad$vfCfM{tD43%Iqb3|)yIKuA}S%Bf9lwtD?$Kf2QlJZativk~5| zG@bC*|9PfNZ!D!P^kY$S{%@-pm=4Dlcwfb{q7i$7GrJd;o4mb*hxfnH~e6yAFahX~Z$Z`Np_u?-97+;yLrpehnCSJUV&BZW~l1 zqxag$peqcHWAK{#E_?d`;S!%*oSi?OU?;MQ32>NclX*$DeU5gYq1jXlSEL@Y1$ND0 zIDYF0d^jB*$FsfZbY`O}6ezJs&&9JMvzCli+R!ruGliI^HLYl@fX|QrbTMDh*49_E z(eZj@Fx#r9rN@I&JU%8wS!F8$?gw~xBb?*xcANbUPI9MAU#FQqcqqGH#Z5|wv^zLW z+s2xq)oiw-5*XxrmN|F`*XSIFiQWZI${`(1!C@mdm#)#4Ywh~62bY}r+Mh4GG@GPL5&+a>H}~3 z%7u`s!=z*{u1`JDCtt0c6@xZZNe+@W^s z$nG@eE+r;gVcBfORS?5+H5}kol9SW>$ESF!MOqEZT3pLsJRZKvRRiv%rFoX(;*5?k z%iUr+!s05ZbGaJI=_sj%buw2?o{o~@jE=~Tj;NfDTn*)Pl+?oL$W@c4qp0})Bs-7= zkyrGwK@pW29;XWOnXM=dct(k`h@)!YRG;aLG&{B;Cc5mH(4p$@ak%=)7-oYVSU#2h zRoO3E`oBW3^|R!*Ae|7)?W0hjeLC`-%cxev4!@cDGT73xsAUmtb#!)drln`e34%^* zGidi3L6m&y>whVJcfK068OAPQD%nXagpJC@-rGLFb+X~)^kO`mC2Y%=F;|N?3xX3l z`DkH#Ane?}gFS7W&+Uh|ai|gg_1isM-o*ApzK?2U*3IS={3^|Aroh<2oIZ9t{mpi_ zb-RnZshiz)2h?`jt=?w8*}9GUgZ$ePsx=KZHs8t$O+t0M+rg#9eoyea(`&VHJPoct zb9E}a`3~sUn5pR1PKQ5_7Dq4QdA7lCraT3cCX4Bj%`aQ@jxSJl<$f-FG+WD#)>XM|PvTL7@K6 z7jcq0WV^W-vB~iUdmq*K?#2cMToNI1?rsp`rbqW?)1TtW-3?~L(+4nQ^C=!vaqJ8y zYbSJt|B6xX{v2Lw^7<=<_Yh%--eM#kDFl)Iqz++a%U=1-7Gqkv$bli74UcHeHpkV1 z#vkHWh(t8O+rV+IbYq)I;)FaQN+#=ixaY>EmZY8X?j|EN&$i76?jSzlV<%3V=gCy zJt|bGLH)!|wW8`_6mkBgN)084O`NKb^GKD_CC4kOR3SJ2b8&FoYmvbWu37~yNjg=L ziwomk%hhPf#XE88h!R-=7yGYTg)38?Bvgu*@u)&2k}{__9Llb&FY18-C-&(3@=jGG z7mLrHBNE>%U=b9muq0DFVgUi1D{w9@!f(-4euvgYae9F%@u6BJB4kFDhDEfBie*@! zh>B&dHY5;F@Z!(s_k84mrf?UJjW)hkx6j8+rj^i;=# z%$bxbok(jbJsaXyUFM8Nl}hAsg5tvxPF0njOsG;@x;tNOsIt4?PF7#p9~ zeP&O$3a_-CN_~g4N;RoeVmp%fHi>c1-MI#~p;@JLULFnZ~xu-qI@0^v+& z*@l-^hj2qVheN&rs%EgH5tc{LneVq~NUr0HTd{4aQft7Y<*ij|ae1pbkB7P8s*PKA zz=FwClla&St;j3>8blOBo%6RWXM?QRLe4~9^{+*TDzZh??!q2f)~FGRLusjuiW=3w zSLqqmW9FFz#P)-?%FY#cuo%g?^^mpTC~hjH#XZFoCb6c`)8*dzRT5B21WB;r;!lpF~l6g_2m}AYbip32J>)SV|@LDk|&aWx!GN*yZm!s9Zv+IIDoE zDo46p(fi~9>z^mHQ#2-pvdu}a;vexN_G*V!;lK$M9_X}eeWw<@>i5BxYS)&mo37s2 znkZWvRcjfmwNT@o67N)arNC?Nz4hE%uf6oxOK(@+)0Ovfr9E6}?^f2cmGx>RJz7a` zuH2I=_u@)DxKiuREjzdB)S^>sm0POZN~IPm)oxw0b*)x4TGh6+rlqwQYcQ5h6Egr; zt`GP|4Nh#+J9OjYhj@4AqTZ{?<9&6VVp)l{QhNp!1c} z#4M&~{Axp+{;K4vBV)?%bJRq;g%pb0p{o`Z(Wnj^ys2^892$JRb1S%gL z3&t9RqD;RsZ&+W>4Er3a*?99==#>Jm0^qIZ-g@n&$6k87@}91|mn-ezN_)4mo~^7` zE9ucndUNHTT)7ul>cN#-cW&9aRi_r6TC3brEML4|TWr2#~igT1~tz<*}l@-<(Vdc3>o56*s zqW5?f0acY4xaiJ|YdP!jAM8X<;j2j{hOeVbY8KToB zNj#u%QfxD5wpSc*`J3s4#c8e@Yx&XebTT>_9q};B&CO=3+ojj4YmZei8a$oiJrd#_ ztrVPJf1u^%?Abp?L?HP!&9HnB@_~sH{-+6zvm|73hW9MAb(&2Cu32rEYgewbP)vxv zo%Blnj2ADa$MfxPCqGT5KjW>+GJ(@5eiIU0)bkk8oyh{P-s%KV&Bn<= zRIQKVRaB-tORGB^;oZUu(l{1OFYR!7?WINXjUAzG1X*9wdqXaZDoeYgP>^e(v{0r& zG8^<7mEGwD-W)tR%`izy?UH<%TED|_@FZRo%3x8d zRs)5z!|Mz{_GGagc7ci12U^yzhw~)y;Sg`C29vfq=L-_Fg0Pg&|3r%S5@7{rGiDP^ zl46%TZDRT4o&+RIW_0mOeL;nIMq{+HS5s7&vn^=Izs4Y;M}4zF5j3GGGV=n2)nbz? zy@KP>Xd>QHLJ&{=nGP{P2VM{)^Y!t5JNC*T`6Qc_W#_oL&*i|ytnUiX>))N+a5LC_Z4fB%o&^nEPsiI(^9;v7>6kOC#mDfeG# zfucRs3W&-N@VXO$pE0SG^9-bCw|aU65_C#oE$3 zkP@2f-Eu+Cyl*2Ba`u!%6ZRU8B0h32{<^8p&e}T=v~L zEalnRbg|;)oNU{YG*w7Qh=d$&-wEhlSe6gt~7w=;~9!>Dl#Jv&4 z+{U7dUcFxQSnB~~Lo5Tta#nqm&-*hV`uFUeg(+fwI8~lzi+pR+_HW zvOnju&C7Jec20+*$sDhuAHEzdUM2ipo^M&TPp5cOAb&F)d`RWhX7Z)gS|R{=XG&i5 zl|X~N_yj-=@AWK41~m3b>S_^B#W(9MCz-umOuR8r0uOo*<0)Lq(9kUCNm5=bS?QQ! zzGEHQxy%FZBw;kqLAuL3^jb8aD|&C@(|a=G4?4v^@Lbo1Q~@tid>6G7JFS2|l{vhG zg6L!cE2Hh!9!zm|!Gwut$mcqAo}=SBr{EQb-{CrzPj8 zR)H9&B!j^C!+bdfUhymnmu1@}h4ply6okqPQu0kJAXg(U({n8?UyDM3mJ_dIwg-@G zFl49IdQz&eh$+vJ>sJwAw7`)dZF)mVxj)Ad|2{?u!tDFIl7rsB+6Tg?0g6a1@^3I0cYg8#9e;3woRttWU= z$>dW{@QO!B*F{3x(&JN4@X8cgGDoZalX`+rJc9KEueg$O3RX|>!VZgxUt8H*dNgQa z$7=qY;aQ9&zVMnf#KIrAQu;Nn+bUn*!I3M%k`=zT2`e*~tnMkZ{mOojTfN(?>VeAA zjo9)P{Xi_V1;jV3=5btA+jL#Jl9!biu7#IR@H7N=t9Rlk%c-#5%Px*_wXW~tC~L^# zmAdUDK5$#1>&0AF=UNTwbWvqxPJ<|yjC}IVtZnm?HZd|&M&V{AMK#^*0A{-1C4|~y zHLi72qczi8Elq1%?X;Gw(keV9L9HXD#Cipeit1e)>|9B!rL9{B`}7`7Y>Zx;U7QM2 zD%Q{0DOD`B)hYG5t*h%P6&`S13Z-z8f?GJp>nSza53i?G?O;b{lB%avZDy8;p`KF3 zs?Nju_D!5ppUy_7r}0e8r}C#djq+<_n==zJ)Wuk)EoZt%qFRi9J=#AO234eb1se4rTy$3SU>)vmuw6lYa5dQOMemFoUq${eDqyc! z5Q$oo#Kdnpph6Cx2;Pc6D2EUEZ%sFli`@cKa`E3WE z&gdj6E#$|eV`Zv-*a$C`IZe&k5Lt_+@rP~8Wd7F8=1W@b%=xR!KadG7K>e_<_9=^Oei1Sg}WRmoCO(KW=seMRzxXjRr}| z-HrXpHb z)0$<3I^@e0Prd_tXL34@#fz{Lc14}0s(6&Q@#NxsG;Xlm4Vk?_`wA<5Mm4?#3J5Y* ztb)zI|3{Gwe*L7;)Gj+M*DRg!1=>Y-_US%44LDtW(|)f&$%0Z+p#7CJry41+o~tG$ zEF}fnZ9_^Vj6gL~VBl9xN>oY;`e;K+%Z-$1^`x{)NkJcNNI^IlJ_}VF6)X~0Ju2-| zQuL5DrE1@CBL%2>)uePvNkJcNMy2aU3RcgnCZ$_S3i@b6O3#fH#2#2RDZNrs&_^3m z`fjB3R!>U5loa&QhLiwPsf*s@p8cwaB`BZosh3vNNGx5=iUmHVe8Ze?Iq3%yFIObT z!?Yii&->I(tI=_s`2!DAe^5U62bPn6;5hpS9;W}GeEz3yT8)k)0th@nfS?=$1ePEm za6|!t2N)2P;{fWW)#x}PfxrV42+FZQUG(4fCE7}9-wYojgBKC2s}W7pd1qf zmY^VX1Od#qJq!^DhSI_0d>=6bRtJo5PCobQ8_B0 zZd#G!hzcSPs30mw1(78xh#XNtZTPr zj;NsJ0Tr~$Q9;WR6|@{tLCXUwXqBS^>ZaA`IHH1<2UO51M+GfQRM2un1uYM#pjD0v zsGC-!t>ypl(`?jw32)c|ZlNa#YZ=Ll;fVycjI&DW((DHx^+U2N#x@kp@BPwWnKn3k`RM56W1#L%E(Dr}|+U2N#x@k2! zj;NsR0Tr~%Q9;`h6|@~uLE8f=XqTe`>ZaA`IHH2K2UO55M+I$5RM2)r1#J(gpk0m% zsGC-!rBLB|6s=#-;^jwLGSIHCf2RmsXnryLbf zH?2m;5fyYipn^_0D(G0Ef{r69pj+uHADwbkK;5(&9Y<8q@qh|C<*1-zi3&Q7sG#Ek z6?Do`0d>=AbR1DZ#{(+pl%s-JH~K;5(&9Y<8q^?(Yx<*1-*i3+-osG#cs z6?Dr{0d>=AbR1DZ*8?i(mZO5MB`WASqJpjmRM0C&1=LNO(djv&f}RId&?`p;)J-dL z98p2f11jj1qk^6#D(E?)f}RId&?`p;)J?0=aYO|@52&D5jtY8~sG#SF3VI$;L9ZMY zP&chc#}O6uJfMPJIV$K`qJo|yD(HDY1-)`qK;5(&9Y<8q^MDF^<*1-%i3)m-sG#Qo z74*tc0d>=6bo!2{pyvS<^vh8Jb<>I*M^w=FfC~EMsGx6&3i^(ypzi?{^vh8Jb<=8e z98p2v11jj3qk_IAD(E|+g1!e-&@V>?)J?0=aYO}u52&DDjtcsgsG#qN3i=*ULBAXo zP&chc#}O6uJ)nYqIV$K|qJq97D(HJa1^seVK;5(&9Y<8q_kare<*1-X$c&HrXzAd3qD9H#}2HgR-@z^0ue2EAjuR3 z(LM~KD|+w~2+@EZ0$cRp9t+V2dN4&pv<`sC5h}M0Kk^w!~hHDHc5Te^C`9Kebh>fhLRz}=4W+Phg zF-nG0leS?S0}6=fHcCFwgCTMw>#5x+xuORj!5afZ^hTuF8gWEsg)-}E( zTJSMS2H%5ih{u3}C%TQ258s0!$|LKk-6*-D2OohR14E=oq}k3(uIRx>u*bjif=)n*PlJ(SX zlw8q+k6@62AsQsoY>l`pdhihtGB8AhM4By0uIRx>P{_a#6%uK-B)OsoAAunQLu5## zS(4-$8xk#euy}^(kb!M@$iOu|BwFx6k^w!~hKLL(_@a{$_You#y(tGowqYUz*Eo@A z-Nz^yd=IvvA_EG-=r&3|(1RgbBC0F#|BY0$Bh#rYFTasMSgO31`fgyq<(kw}GjUkB^JeUVV z6v@CgjATFo8lB98k3f>>P5D%78%i>8jU|cJeT%41Ekp4yF)BYHp! zK1eE$_Ym5~nhafIO`-)KB$dZ|U_G@OCD&M!Xu$(XhFFuKZLGU>j>Pbd5C$Uicu% zfF5jPO@^+qCcz6IBpJ|yZLG=AHP$3};e#Xtda#W(8M?-r1TTD$WIzwLu_i;;Sd-v| z50VV%!8X=p=o)JhyzoGhA=V`8sTCk{jWvlDe2`>754N!;L)Tc7p^sRT=uNqY$Trqw z=o)Jht@{`y1A4HHH5t0bnglO=kYqp))KfcMa@@H9FMN<>Ko7RDCPUX)lVRusJs4t5 zvYy(FlH<+=TJSMS2J~PXYciw=j!sKJAF(FU8-vBO+_?x{V@;xUAERVI54N!;L)Tc7 z;Dra046!CzPpx#xHP$3r@IjIRJ=n&Y3|(VQhCX6VqBrHbWE*QTbd5EM)_shU0XF zhf!)7(1UHP$754N!;L)Tc7;Dry8488~IsTCk{ zjWr2g_#nyPd$5f)8M?-r3|l^Y4~AHitfzLPKs~iG;;ykK!3!TG8PJ1mtjW+d)@0cBfgTL8CRtDIM#4jijWrp%#+nQ}K70>`Sd*-$cBABq9(=@_ z3=Od+k!CA+uIRx>tjW+2YZ7U;B)OsoAF(DwL##=pS(4-$YZ5JZFb{@Ulc8;_$ zKs~iG;;ykK!3!TG8PJ1mtjW+d)@0cCfgTL8CRtDIM#-O+)NYipqIV<7 z1A2&><>&!vw&ua{#wJ?uF-qmWhlrwL+KrMUdO!<4NGgvO71_p`j9g<)q6Hr$mB)Kv zJ+&JpNA!Ree2`Qg?;)~{H5s|annVjeNGiXv$$DxvO0Kac(Siq(46!C7+gOv4YphAM z;DaOsda#W(8M(%q1TTD$WIzwLu_hzeSd-v|50VV%!8X=p`H5s|annde9M#+F4Y-3GEuCXS;3m+sI(1UHP$%rC2I=NGP#F|8J41#CtdvJ|4 ziPn9Lk^wzXPwgz86=_h5)M8QI2~j3|Pm8%aLUgCW)= z>#3c^b43q6VogSdSd&PzHR7&27d~Q5Muu3ENV6r$6+QTfH5o+)^k5rnGIEVIiPk+B zaYL+0)>A88a*Z{K7JQIoKo7RDCL`BalaY^Dlju$Pykr||GIEVIiPn9Lk^w!~#+r;= zV@-k=K1ed42isVak!!3;@WKa42H%5itjWkV)+Bi0gCv9RfqH6Z@m$e^k64qDA=V_) zY)NuO4?bc|Muu3ENV6r$6+QTfH5nOVO(M;bB-dDzXu*Rn8DdRFwy`E7*I1Ki!3RkO z^k5rnGIEVI310Xh$$%bgV@*b`u_nO_A0!#jgKezI$Tij^c;SO21A4HHH5s|anglO= zkYqp))KjaemLfPh`5t`4nv4vwCXr@Kk}G=f5o1_0u__#rt8Z1{sX8U$B`twE1 zGw%-P$hs-(+MhhWSR^^0g_md3g~q_0m&4I`_f zlyj7Ccq#K?l2w0d`4U14*iWM{(32DPGLD`T-y72laEC@?qfx?BmhTx@s&Vnr^kTAz zQI7LaboK6re5X1ahQq~h5pQT5VBOlEu#|1a@xhk(sZxf{XJji=%~IM%6D!j8kDCc2*>4BIDi&HVr1?S-5GrV=Hw?R8gQoqSkGYBEM(#N0iTMj(DEX5N$>s zTOufLMST&inW&&@uTDs7vu{fWvf7a$FO=Dkfm*LlMQ5`c+LA$e?dZS`Cr8>gbWpWd zC#1jGv!w%B?Z}Y(uQp_$)~izy1e?~wL3!=ykP9ChI;h$e5E5;+TDAicP-Z()pUPXJSoBPbtH6(nu2 z^XXzSJ!>ppjE;Vq#Pc~V6E5#=z>Gu87I!!P{x_@~-~xDXF&-a~8|Ay<_yPkLu(O@R z%k1-z-8UUg4&tNf8Qd+$@o`bHl4MqTFFqMwj2EII1nyQ@7-U$*7VDC*P^WSKY{ZGAk6iLCdCkGcsH>@*)$2 zW@QxRN@Ru?*Yz>DCT1ouQ)V1DnkKf6{Rc z1SG*w;DCW-y)?a!$$7j#f6V|$b)*A9t3OqZ(){WeQiC55(-d)=IsLb>zzJGm5aLyY z?N+DL#HAea-m6)(X5eZW9Sz4?1vSJfV#3FCbOPjsyFx{Dq$zxa)8AT7RvBX`SVk4+nX6ih?KWI$9lY>3Xm>(HBEaj1 zNSjkIrjX;N=2)E>)I4-nbKRl!_N#e-lgq7Ev)Sx7yCFNYQ&G*^LA%v$wOj38r_;wv zb8&sIlA1R=L9gEndN^B2FHER)oFOoV!LGTEw>I0Ie$eSe82(PHMF#%rDjvD}Gq867 ztCZa-mlz>e^Yxj|UyDDZc3;pw<^Epnz9{v~l({di;Jw&@10{t4i-|Por)bI2HAe276@3#s%jXF`M)o=H@cvX2f=+Tvz8lb*005xoF zM!kN&+wBG2sM`+c9dqjJa3f2bPT@>9lI^)VMZ9=U7g_3zLnL~C7VeS1y2F4t$D zPMNf`V@r^c2Hg%7UN;tBpN%aG7VAI?j3>UQdq6oQtpz#Hf#n$uFE9Dmbj!j4qLTg%yDfQjqnM+PyRTQUN^8?QYn_)>N-W*Xk=S z2-;y2<$&d}xz&r{w63-yKxu@r=>ShQ1GYO>uLzR$08PAx9kXk(E!FF8_7OL~-@~?4 ztJ`eSjouZl2)H&@+m^D|@^@`ps@V!Uy-w6`x4Ug9TlIB+tBW`RQ7`D=G)A`rm9M7e z*prI--FApg>>l33LLu7fg@2;x*I&)MeW-bt&gS3{LlCwpfMg2SdX4t#~KOy%SBtU5Sl}%Ys!PyOVW*RqbAH|Co)8qN} zz1isW#p1yz9v|Os96VHh;-oPhzE<+)5cq?5NLLi++ke10q`{W_najA3Q?9c)&TP-O zcSgvm{LbZlG(4S*PH^gm-L0j_=lYXefp0EQIi1oG$Juep2K(*#$GMW1cqFh!5`vbdiuPPc0^Wg#|gk{XHyX(cv6 z1+l>d4977}E6cvEO>DlI;z;%6)Q{va0IiCWJ{I74UP6e)PLhMe%hy$MxWSjEYg|}>r0m07sUp+j-Qr-PV%H$8#HLk2Y_K?ZT^6T; z*nE$W2bHEO3UyU^s$w@n9pt9>dhE@L?sn}X=)~+SKE|osSfH#72t|y8U3hf?>11QdyindIUUHdIUOWkvOCibeAHd@+4FoMrSgQWuF&FP_Kac!EocbUATRxEU<+ z2(^pehJ_-$C(a8+&en&-HojC$A|MGyH4tdzvPklZ^V!IoyuqA|X#vvLXE3-I^anaL zp@36NIEaeek|s?mAuzBckms`@P%4xyf$VNT`6}Ux1U5I@RwKys*$|jQZqD7MBZ#V9 z$MA}iJ?~(Vam<(!In_(a=Vqj#mbIJkMGb06F>eXOQ_uHmq_;#h1>-p9?I!)@Wo#r` z+9azvaP>2PhKeG|DKf%V#cb%8Cd-Qc*MtG?#ly4t`IyDX%V=*~1p;cm#`iDccwC4D z)NDd6+iwFNV7AuHag57nM{u`}_a`R^9sqabEK$vaf4g_sXf@l7e{Epn2mfN{2>*5{ zhHidsuCjKs)x#-KtjrOJz`>84?@_^Q^LTa~&#-`f9?!Nj&0+FX5_RMN_nUMV-#dis zIYyv`IYRBpr@8$1#?zypz8}6Mq)S2b;bY$l^V~myr+b9aeg&s>A6{$aLB0Td(;E2& z?OOL89?NBYZ`14Du4UhQOZkp_x!1GrxOBXX@7)f}$lCahYq-|BF?c!~PUdH$Iql`> zvY3Vc?1+}0r9fPvCZs>i3u;!Rs9!-C*QiG=I%#3#I9 z>p1+dtm`=7us*J1_s?20aNr_0)n`;=kk28&z*7PP?Iyv1sE2=E0bOqej2CNka&`Cpc-`jMQ@H+P0!V1)LsXWBUW@I*uEqb+< z?SQUvLTdHlj$0y&^yb%V*!6aADXgmmNw3QFHBU&&>ePBZ3M()H;!AEnK`{qxaV zzAo)t)48;skF?3AUfT^6cMkkJS2gMtTvP4d(y8<)J=O!T&U8R_N7wrF&ntGW&yM>HepdS#?dR*AcwyyCV4x<+%(4yK#Ovyzt)>px{` z^1RcqVmITJ`@ZV6QL=-Xyt^xL)O|B;s9SMMC!)!8f_IkLY#7T|r+h<2umh&IuelPl ziK>@=`E6gk6peZMAB@I0ubJ8R?W+5}B4>kV{ke4KS7c;+!_T^J!=5kfy6YLRJbp-| z=ilxT~=K`Z&v54S?v`|URSTP%=q-tQ|I%m*Uf0d~4cl<%L7=a)8|d2_b- z=Kfjbjo$*BWxLyi+jGk%M{KuS&I7^yJCt{A)|A~0UosQ)ZQXn8?mX_5FXKE8H(XQB z!?Crj^Ei`nO*`N1F714uE-+YOpOuKs)lY6*hn?$H9|2jTC22R8x(M1~1y9>LyWWGI znT;Sqv|CE^dL1(ziqvQBIsyZ%uj{J@!+FS%K$@onX*%7lutj^zhw2biM`y z)`xZ2rn&9H0s)+sNi^OlU?~*qotE%6lx1BLb-ZR_S=Zm<8C5z7!qyf$7S`$Y zs?4agEna0tUBNE=n?9q~z5(GmU+IDL_EO&fTY=XzDw~s*T?KfDme;mEmo?9*7R&3_ z^;}-toMR!VW2K5K;QCtEsBQALhghR_nr%L#cBAH2r|^(<(Grz*_p3~(D>zK>rcbD~ zzuL!y+F9;wXU7Drw0Pa?>w1amXNmfjJAJRIzq(7e5tliA*N6pHJ)!C+5YiJ6Vt!1H z1z31_{TzdUZ|rq+u_FSVS)9~(O8&oQ&gaxS6hMJO`%w$zr+Z}S6%gBT!Lic!Ph%DNOv)g8&MAmLg1QDJ&&H= zqkKeo5q(G#rUjvwVP95nS_(;|pe{>|3nmE5*V0i=C3>}Z1-FQUq?jV+NkU%j{pPJ+ zuZuTPwDE3q#E1{+{p4DfR5@Ak+*{}Hj`M;la@jJ~q(SIW9&a|oOU;62r;U^R9k#8K zre_KYvM`|Lm!yL&nRlWuc}+SBh{_KrWRiM#l1N87drq>nxVusCs&pEOhKP)L zm^mJ< zh3KhBt&w|+j#b#GH3ChDj7!wICu_{PlbBlTFRdfBj`C`a7oz0VI(SZ8nKo@Py}Rmc4Q;5P)^wuIcD8QQwY7BeR_9>g31xO} zr_*||T1Oh$q}HvxT6btkPb^VEI9t;$o2@;q?`shL5|P(2|7oL#p|91NG+8W|{HS$@ zBA8gu*2uj@@946btvdnWy^UITT1#eY{mq;M^3Vz2TOiTv6i*a~ zB!6&#SeR2hrt-{*!qofj2J`Ax_onD{+fp4F#YWPt3r@SkT$sgz19x5p(ZSC9>Q{EF z5gkxzEjn2?GG#h^aY3HwWOBXUqNBR0?!efozg3-ql&O9ymR0dvWwPoUI28Fc`$R1O zLrD7#3ELEHw00;9C#Nuc8wu}I^Onz{;u%vu<(ltnU8N9UgEuq}*=oP(M8W{!UQ>jK z;d{*~TI}@HvZ&$-(a$+Vi>t_G%T!ZCHvf?Ajqsa3EAX5asdnuFj31uII z&L|=GRtwkCmB)I|lLB;lp+y(+Y_>;Oh%KJ6c-IrksX2q#DWxLSZWpO`Xr*X{0Ot+cerVIQemwbF8aC?MB#MklMZOKvwRN^7{=s3~pf z%|r z+VXpgR!Yn6Cgzk@IfAoPY5l~Had(o|N?R*!Nx%aY9-Ge9WR-Tw%|u0M4fhgNr7gde zxLj!qE*x_G)x8Ia6Q)Rguf5Y#9jEe}h?QAIo)+N?(3zFET5Oqht#03`%qqWoXU!~e z(<^1wTN^Q}K8;o%G?jDZJAa1!jW~^_yDhQI6s~i1#aW+3t1om)y?*DfhP(+U(Q;}n zB4%Ch-D&mM8Mdd@pX%NNKTs?jDBF~?K82R-UKqDCz&OP>T7CfFHJ(DtNv;TaqE1Nl z3AB1bswbpzJUK-ay*J_nny%LRagOBRl{Px*ruGO~CZ!+HWE7cK zP8Y-R&UrjrT+CwfaO)4Wyqw(qV+w1_c!{rR2HOEp_$}BRUW$;=@v&fdG9+1s&k;F# zleaDgExbINE>_I?Bto^@YrfQFByoQ<5x`wxS7vV^EZ=>_xxQPa5`3u5kW;1y-KJC9?-ybBu| zd1Oqn4mmoC=kw_dqM~V>Xo&8&oxFRMpV1=jVF)Drj!vc65cyR&B~}3_YRTt%4aT4m=^3?zK;V zp*I4tkESosp=$^sjk+e zT91lekoLCdk<^?Am=~X1oSi?OJU%&*i!8p})z)FMM1zVns7&MWWDBJlNihUwQk`78 zL$)}R<%(K*_O3717~2nj9xaYu94>$jl4V<#cjw^tGqAd7{Z^h=F=C}l7`bN4_GAO2 z%eo|9+=OL*HcxgXmQ6QOgQW$<_%RC&CmdOAXpzgYY(ErQ*qU`2DOX!cNr^v*&!#Vj zLg?*Aw4xAkSenyk8%<#Y{*L|92Bx8k7gi8>aI2~9X#AQ{?o;LL7v^O zW~1YTQDW_+p}jO`tz>nbRIu0R3H=-oG|WsY3RZ9}b8Oe<=+Mmi9CRegLgipR%?i|P3fLJ1EZ zDqCMH8H=d%#y|h?FC}`HR`Ki_Z+m%gus1iOAnf&nAc%VXFzU2hol5@p!ty!Kp(^=` z|Gl{xgzcc+!AY-nyVGxV0tEA}J@U-u4#$y z*Ts7|AZ5kj;`93oup#;K6>LZp;TT)g>a{v#LFf(VwGFwpHpE65pJ4C&XgGN|JUdsk zF?rhHj|-Nj2M?cVB^8Ks^@VBi*%f|vV}HIEFXAH{qLE%c&1ibwBo8a@k+b}^`c?0d zC#r-$<{A0x;XDeP%|D2hxBRWi0-iFzs*;QU)^d@IV}#;uoPr%}$zN%neDguEORsf9;jwAanCW-b z72|8(JCG+vb{~f%_LEqiKE;#9;n{FJZhWT;dwLq=NxVCqY^p_-A@enrFn=AK79Qr3 zkQR<5F&5~+P72iUCi-gUt#|?3$pj0&w;7xzd|t_^ki#L)4)e3d-%KZPbt(_4OarM@ zL!KWDp~uPY#pCb}`>6t{G_zCzmgmhDe>fFvrN651ul}$hI}4fu4YnFj$D0=E`dh77 z{=Y&z^v7Be`$aoHiJI99K%+pgOscsDszAUgPjWblv%4Tq#iLQnAr3#WZoYJ|xpO(1 z9u3dh%?@6N!(~5t)W9=+3Q+PsJ8XCR(oL!)kcnR793WO0-;L&@=i_*P^3}Kd$(eEN zzmw%Yik0yi@F(KEKM9A#d2GfEJiGr;@&YTSm!tS+o-xl2p845fD`bc%_I9#?QkJZl^h_o9 zCsf0+SX7pzW@?aK$d%5el$Crk8=Z|Nr_vs#9gtPzvXs@eJBu0KNh=c!k#*&>lvQ?s z?dP9y2TL+H*$&^I%gSJv@*$w2Q zQJ{d5$t4i^Q>Bt)L78M&eDa*i^3$+yK-_zEj{BEmEr0G=rYGrE0Q!;s$m9~8g#^9E z*UaMPQki@x5RVw4WaHkAW+FgJ3YR6^4}AO*A~zmuNvf(OhYE_at#raHcA-l#Hu}&> zQj{B?BvZ=I%xJ2sy?%ewu>Q*lpxOUXD1)p>1u-o5fuMR#5dZ!kyM~n@a%UEO$ZuW= zDh(8F;38e!!bN@gBWB2Bu^OaYLw;FCjzGbZ5ZSg^i<*4XQ^AOWe(GwH$iG%Wfr7jl zFZfi2+Fr(M3#&v}s4O~}*P|+CGMSr2Q0;nhhDxWCZ^r26EFh#Z_bgK;osW?@{gKHf zOu==UoVnFeU%v^Xl^2~la5HIU663iR9fte4=tRpVED<@mwmP-d5lcocI+=i=s?`~e zN6-1L(P2D6fRK7wS(_h#X_JWPc8G^jSiGvmC!MW0yiR{-;7JbTymi1w7Ybcqc1E+W z;kS)vwIfTx#_znyWW}OHXmGlqo}%h03R9F67>I-}EW5+m^XX)7G`$=h$GVl#&T^6G z9Nx}!LqI*irTRP59P*S)Qf-;5U2MWv^t8Vgho|YV7x&7gGue{kUbLjmx!nD^JHJR> zH*Be&nP)T3&M#K#>il8_j?VJqU*yoN?B+xv<;<7i5E#{7q$CD4O6@L`gr5T|QXh8s zYl-TxJa551LHQvu@K;-cnq$%-cY)F@J19njy@=1hA06YJFr|kikxO%iEjuhmwd&`> zERKsQjTAWF=`_5yNlKcO75Ck?l`QMZ7#nlWCMsEP*VT@5Od+c0N|z5iF{-!jYB4g2k-CAm?C9BJ zAPPjZNlu<7zcg^1Ji9hvc(6A*#nHv_KCaR}kH@(f=Xx@310vJijjwQYayB|@B&iKV z3!zihT2;FCWfJvvL?;lE#n=mxUe*nyHv*+6BEQxZjCImck!-3iTy1sHvg#xkMdvb{ z+QCZ_rl)eW;1tmxs0-o&Pb;noY|xXlT;Qn=3A2I z-zqwnDjYvw(qA~u4xWAumuKS!&OTBA{=2{X^Tl-W`(U%lzck_rg4f~vZIW=YIJpyac-sH?-Ra`@zx(f( zd-TuF@2RSt*=+dg@yWpgwc~E+$>=n>oYz1bllhmI^W&tV7mLODms?vu|NQgj&(Y>| zcDjW-;lb7qj~*T#y@=0-N%NxE$gHCx8_>%JT-128}UThkj43FY_Q;42gDEqvq zG|j?P2IKK8U|kbWigF*s!|`HthTBs~{{%g`grd^-MvJt2$qzjr>wpDcDPLl(m>r~Q z1J0}L<9q{&6_f>&a{OkVe3~x@kI`Zu=bEW8W^7n*)u-iUWVO68QAj?{mpq8))A0qN zPDWgdHKspQ3-RQfT2a|-Hox-CJ8CQ6T;!xUsa+*)Agb7%yOqdy$e2`7z6Ikmf?w3r zTE2zBdW+?|$ArjMzPZS)NxmZ&`R4T0_B(4Sx$(&f^4o5|yIf*z<(rG#n&g|C;%fFg zp{JJbH$lF+X)RkK5Ct1})uGR232N6M{k2+_WII83+7MgsfFkFTqyhsPqhW(+5j~P zj|-sgy$cJ*Yh!t;t;W`dR!MkV&}#2pSdCc;56dLI#YIyb?sHA)ZOtB?)VzV~3kVDP zw>nH1cDTCOPU~yKgouJNqk%_27A7T?7KDPT*D$&7Pia4g1}HvQ>^~+F=mEWBgVB+u ztIowlTP%i}a-w3U6q?MgQYNa+)@y@$LMA9$j*C#dwn;l3^fy*0g0LF?UmJuILa{%o zE)s&MYh))9GE#|gF%ygva$YSl78?zIMlAL?wM2vY$1%9jk;Vc#+p&C7w*j!`W!@ zipEFXjZUAc=7Epe{`}iH1>n2)N_6<})qD}3>2;WLtOP&|0RhA*uyquIwr1dPTOus7E-tbv=ZEkF-vJM*|yhyFGwaUr^;POQ0 zT*JTV>SbR~c#Bk)`xlK^b2aB&rONUaD*@xz<^-*QaSe>Go%^ZH#$=r;%Ui6Zvb;z= z%ht1OJC!p#>i|jC?y2C1th`t4TvwS1d_MCCB_9IY9i`A62J_A)N zZLPF2@Z8!DK?M$nppr5*Jg(vK+X;^=&a^!HI@h$k#cDGxFHOjX#9P&rwmu70D{ZZ`Qfb$I7%Ff$43(6r;c*R*Yj_NJ zy!PWzNsHBH+WI_HJ=4}Rt(pl^+KXD3kobizJ^m$ zE)kKFe%5MRt8J~eP}|pVO3EcBa?;OQZELly)fQ^|8csR?==3z6?M^2rqtlDoa50)res{X~{l@lS zYcGB|I*PY%-E1VE0e{liKfb#m1o!aOd=Z~*G-&tp?#9FUS)?Rm8yk)N`OeGXXgqvAMw!LzBBn?8X49YI$=!{U;drh*KtuB>9&a=rO&8PI=r62~ zcQ=w>@iCRXe>Pey;+ax-FJ8n)$S)o(qvz4ndk;r11Dt(0+t~sqwG5)(Rh7*jvppzao+b zJ`~t0s8_3YuvN~4oM}L2)qo@w4Ys}>&W?W`&f>#(u^3HG#q-?@Xw=!xVlf*%zgWa6 ze+P#b&*$;t5DCcr!#@W>t9d7Awfq0_*B}2m>b385!q&h1b@!ixfB9=!;vo_TTUtGW z^uf->@dy?VmS>x+VEVK1WHvpD=X11rxQNf^cQ>L9vVv5Sa_-LJ;Ueah{vnRf(ZP$O z#l0{u+wToZuV&4MZ>)5$1mgA>|z{0x)?7;G$fOF{A4(TwS~Qthf448 z(`t5^IX{Rlt4ht8(#m#+lZ1`?lk@TLC?-OfF=tawVRBkp9Wg41alF(17gjQxloq0~ zcr=|4M@LUb<9Mz&Sdf^OL{HC$rx?_O=}0e@O-aiL?n_xAMW(_$3m>neruu|G6DNaq z9<)39Ss_u9=aa*kIWny0D(yzX{Un_;JrNjVm$&pjQ^BLaL`AD-MMdTWrFSZnKHCsa zc_6753nF#*%YT0wpPm2vh#veqY|`RqAc218+0WDAVzGJjml3N{<|kr!BW0#RFpzRt z+K&*%Be)X;PlN84_^;L74Ent`{%5uyVHk9KL7&@?u(ltkY(Qy}pvj&AZLQRAR-*m&CbuJJ?T z$HpI7jRo1$x}@3JBQrH$91j=6XMc=G^ZCE;?saqK>e(UY&MVt2ka!wD8C}L>LY6wx z(JWPuKukXjn%!Q+}hnKYNovhpkRYJ;+YbOyckGX|}O{Hav|R+_YmtWtGd4#5UQSBHw(v=Bg-{3G{HV zm97)S0%ogZ1vA*6oJ?nDZ0#jiQ-eqFU|_94OG)_tdqI0L&z0+*bvZ5V+88kPWQKuD!(Z4oBsNP^JQ$k2azP6xpZGqBb^jT{utXx}Y zs4c2oTV$xMRk^m7QrlpwU~u49C>Y4WxAS<$R{U7sZOJ+YJLl))(UC3*U?6Gb_8;$m z)A;A-82Ltw1u13+tXNq>I&y>W;u*~w+tFrsv)|op(r2(Go{EeR&VqIZd()$fthh1d zK#hA?^bm8DwQ95bW!T(ocbicZwzvhyik^V2$S>Lh1w=3F?4#iD;OoJb{4MgpJCJRj z%Z+3XYB?PJCH`SM3PBV9DoQ75KW>MuB<;s+xkK{tP^rUg$~}>CFPrj6q&!kn9>e=N znheLg)3fs#t^cQ!NAcpt^mzN*$x-qwJ|1k9=To{ZGTlefV|Wt&4bH65;?;IQ16P)) zl;1h}^Tmiev1p8TCd^Nu$#IMHzuDVtwYpI&X#EkxUtCD3mKe1wsNYqoC#t6tSfzWT z#V$FbwtM8r$v#x-l5^BHo3{pA=>sKK3VM{*Fs{i|mb!b$K&8)_5+!-Qgffwk-Q8eo z<_)bPR_Ola(P+U0^!wq6{jecbd&sAHvTPi^^bBhtFZxQMS`*c7tx%xpVaVxOJy>)C%rA zkGjE~=CBjTacda&kJ_v%rA)5M-RXEb+y3tl@CkY(Wc)!c--Gc5^6iY{%VC3kZ@>*P z*y4|Kx$j?|PieZM*=IXwGs8obYtM6~_Q&IkGpG_-4CaWue?B^)cH{X`JUNOnNo8~7 z>VOY%F@v)`Q9;%}Sv-@NE1XKAK{S)G&&&7{!B5I5%0{lA?eDQ8@L&{=VIi_fx&Eo- zD>9eO$IRd4bR0j4hd*t%JCNr?*plRTLGA-W>GSP29HlVegRSIuHmA;iLkFMuW?SGmtub2D1rQZ~xpO(19u3bL?Pf=v zS!EBfKgG9rO1l6@{ppLv;q+n#=Wb~(bU?{x$cBvj@YZ8J$E`Xh<;E9atbCRy4Mm7u1R8-mUs761ftIVBAA<*@F?`FY<{&;{J&+HI z+aK(VX0PVhcqnZdJ!A?42t|66CwuT{csd!K2*C8s@Ec^+9u<^9MRwpp>< z)8!?9EG((w4#8^LXi?hyv9L`%UE1c4g>B+#LCNn%vjwoklhNeGbR5%s&ih;NJX;!u z$vifgG`K56Ygj(j)tab3`bmF=p=D2*ps;UWh6U4g(h4LhTbx)cpn(uL1qI~AU3RXZ zGPdD$^izhIf^H9shgG@s6FC^`pkc>>804ri|(j^^qDP|6fG z!iGmL2(L3-O?^NkXXkT_o}Q$)h)$DLEv}g?O;LZqA&v6EAGM#T^M@b*`1KIcOO7W% z!~nv_@yTdHC|FO>`!e_=%=LV4G~;uJS}c>2DaY6;y1I=COn;!}9&G7-9UNx6eh93Q zSxz(YV$U0Fdj!6C&ES^rdN@uc8rhYfc9RQ zeZx8tT$_Ow@uVR4F2*~aNy=McC)f=8t=qi_Us}x$#9Yr&R0j|fW;X`74#0U}fia*K zVI}dnpcFg&!9;Bb^K$yDAnW0aAzXh65+}bMwyLp1QN%ZJ4*2up4BOAg@g*%d)Svmf ziPO`cWoG%kFlUApt+3n1>>Gettrol?fZdrKg|#r#Ndvjr?1s0+7rvIvh*C172`7`i z`1|AW2>Z8Xb1-98t{##CNM+`E@>I7Kc&KCp{+Hfk_3`89eh5m zTk;)*eJpLVDJqZPq&gm5oNZ&eAU_BDuBGozXNhhzBt9LF;~5k7ug1gqyz%tKXhI`i zl4)sG_ofRR2|z7JKQXg|#Zhr}*^EnTCYKdJ=w5t|1tMD{<@>1R(Q1^;7E_aaki|k+ z;~NHMaCjac(Qqmw$utJr`=`Xg9O?N4tz)}Wks&**!DgHBt}MNzn#U(6^LVk{ZZpjl zg_EZxWgfv#F&hoX+e`>(z(hHjl9q&Y=XrpmJ4dtW{AhX}3l67Gax8v&@jQMA0G_e& z1Myr`A%5p7*b*sP4Hz6OT!-U>c+3{C8I9sg%PA(N(-=^K<3DX2nJt4iB7Oh^0i!?W z`A}QDe@1@5(SkkA6q3bAE)S+uK|Tjn+4w;mV{7~vj6Qs0V}2kke#Nqx~u$7`}^?z-W^z5oCAXIraL*K=_%J|rDU$ZRDd_d zj!Ke29APPzTTN}szSD?n=iFqQbODbNEUflCQ)vGT<3;-! zd2}G)U}=`JsyH~5#gy^lvSN~@M2^b_qFA5|Lam@Jr60;uRYW8{&}MxIjz9xUZp4pN zY{aQupbl&>%p2GvyUdA zrEEy{MkMj0BY}&=Zz3`4Sdlv-yh6QAR-4!w;Tc4o{8v$p=&G>@riE-LmKBNeMba&s zqK*S332&yxoKhy{oL2+EG(oWV4L?_|{~3a-GO?AfaT!N&Y`7{D3*6~0OjKB_(wT5p zeI#X85!eE0Wx$m#a*%0c`xx4gF&W&wSnN->+f7=eu&=6ii5!nFu#O3_b%HGPUC)ys z-SUP6VOqI_ER}-1s8pb!hXuKrf{uR*qMXIBrvRq~mQm1cdmn?=6)3nKQj`ZF_G8e# z7E;u?!WgVwiiC2j_DPy}^&yOwu=Gsn#g78RmF|@46)=00CT_RLQK|ga?GhSP(%J+m zmEWekop#HRS54CUM;=BF5Oh-~p6Ybx&0X3(z1}yH?vxoRF|@gxb3F`_!_dI-%G5ic zZJRMRN}#>^9+{q!I3UtHTJYOXXLMeu!Af!;JNufIG+9yEy)@B|PWdha}9d0-gQvsUX}n+uGPfhlTk7vDb)winWZwiH z;onQM;*#owF1#LZ(_KGEmi%2@1j~pbnhF_&F-7K-ox6|2WD8aJyB>#)!!vNF@tq|L zu#Bm|0f>S%`Rq!VCH+Mt891Pf^dS~((^;Tss2H?VVshETYT2Wb?7&6h6kU3T4o?Mx=*TZS`Wo|U~!sxFD#3Xn}p42s72T&hZHn#e4f zQcxN(mXL_&0~{Eg@32!x*h(S;s+z&f#3O5{Vg@sHkPMWW!Tx5{g~Pnnu`z>Ts|VG; zni*86R~VIw>wt4+dyh8aN7!T_Zecg#`Dl!@AawSO# zKG2Cq=sWKn!j}4@!cx3OxsT0?Wu6t56USrwID03)7iRono9~VH0CI{>)?g%m+)j7R zi;mDBi`=-cxT`OV(p<9r1cS|MVn6f!1-wjUIX5H~beeJgkeyawU+<+~AF;0mtA77V zpJ}qg}mDh^@?8yHxGk>_~wJI#Gqo`5GT)}0Tom%h=QmepaW9>@;Xzin97rM>MWSw(nNh;%{&)VF(D0kKr*pL zbW92QD}phA`8aPqJjU5{1xFxM!eW}uNJ>rnpvg6u{I+Pi5wk5fVK!S478U(F{btm} z33&iTnWUJ zswYDL7DS&}0Gnd3Zu{?l!~rW+1;jrxKo$Qt0$yn!Qf>MyUQo34h){+PTZs6`MEkr(kw|r_zlwdUbQ0FgZmRy zdlc{DIM%}v_La7=a>_kZx{;)DNJNKHpTxMKGNJQjxN#-R7N(18Fyj~WbMW0L{yCnp zvt(?&-)xX;Ci9Tj!R)4DDiVqY4~MUCbU@O_eyLCK_kK?1`JHw-Bc&9ZC+IMh^FuHU6yxVSty-v5)Kf!>=fPBiRB$bik@}pb8 zb{q?;z>(iCS56~u9r82H^Z#{pfy1C;j0Um?k`@PnYp2Yojyzm;05irh!4P0lB5>_ zh7yOw)C8zm-cBjq!*r7kbC{UHBvbN>vk{YDVj++UBzgujFR%OmLi_x2|L|bv`@?VV z-Fx_W_Yc4L#s6&nlBKuA-}jR2xB2tE@96jc^Yo+SvHIsj{P-bx@IQI>U(nwllV|^* z@^?4+o@Dz4{XHyrcCX;sqk?D2qm};|Ji7mMXK&}}&NlX*F#EvGD+2(O?9lZ$JH1XT z2qXRGKg%r-qrzQ?Gu#x(gs&0VZ)VhFXfR4-5Sr(}G<<^ic@*u0Oa>2vLclwhOoAl~ zv%v?}BIH7|b-sL24pD`ApPWj{tLCivY^o^rfFd7K_Nd>vP5%%2o#ySJ74UbX$Yhbf zs0T@OJrdna??ow2ce0Wq^iM4+OLo@9!c4brDrt=KRFoF5$z5eOICRjtF;5|>R8oRWD#9tBz>GR%u zjCcOX0RKMt=;M!X-u&d&uYdcSU;i2m{Os1JU?(x~gZKS4yaFy6svv>;C1;&q641)~ zH$M9K<|m(g`Z+Q0)-CYwvri#^B!KV#%FxGapY+RL_|jTQIt|l%AKrih-~9A5#=cwZ zLkj%yN8q2|(UbjnW51A?e*gpcG5GiSuOWJ$-@=#AK7mG)0@wUoGP1i?rjDcnR1F!LU?^o{u7I0K$sQKMU0{b9;pWM2I`Im_vSomw$ zKzu&AsmmYbE^arlC)?R>!b$L$?s*)1qBSw46GvHpesie?5# z=?64%2nI5-yTP2+Op)JV{{89$TKd8O($WJQWLA*z57PHBpMAObCAxZpEFeQL&}8y( z(TAqsw^P8DP{rn7Fz~a_fAiaaz}x`_a?6M18~6wB3w?g|KD3uBFy>t;h}^|b{38Rn zq@}krnHF!*L-6m{;NNe5Ly8M2gzt*jcLUyMuD*aRZ2llFGWf#(($|E4?<#nw5(d88;?@i31S4ev;-s!9O1jJV$9^%}m+bgTO)dKQM^U2x)mDKZ*QN zVKR4gQ~_U^_A<7vS^Us3^|KBkCk`?8L1Jjpg^#i^iQcf&Hq4;Gurb9I%wrPD<_}U= zV)9MWUgiZP?m?tKSmWxKILCIdFv9qcK60GBagsEYvyYj7#y@DWT(Yv^Om>F&hK9mD z(s1G*vw*}wW*j*K!9Mr_*J%9G-9PNS(C!O;ymJuCS7z-=dcZs|fpI}OTEIA{{G$&g zmtHP%++jfGkNxJ{)7Y(-UFk2dj~E6|F75xZ9W2^E@H}~ed^T4m)}Oq0T#C}IHKJ0o zFGn&nkWyvI3^Kb17J_|>q0ALr^#g_=IOo7Ya)Hwi4Nk1ONv3{~K4$soNE%x*a0aFt zyrfn18xl+cM~q{W2a~`Q<1DFQHOYc~V*fPRe}Ms!rf4+{skM;sh>4SC53FoIrPF<| zW6IvHTB6{4qQML^)9gW?51H4+zx7VLlWbw}jTr6^r;Sv<0to(nz)lCTX#Y8bgvgVj=sGM+gW248^2kD+*Od7_IW-&HRWi*0}7DDdV3q z123x<45Te2W(29z>z#O6?>PTx7m#z0SVY)Hidv_ezp|m4)B?6l0yLLcrNizM}SKP2zB8Qf<#=t z3{F`uGW_G>#|mJ#7tJ#p`#hq`Da+82rB*kRrp8K z7<0q%nS{7!HG>fQNZ*-<#LD#mjYCt3{CxuZ#}4-4U%DalQ6>B5k26*^llqeN!#~W8 zNs}1!Ul0$8eHN==hD9)qVrPPu(aSv#4E!?vz^VbqR)3G!9PqP z3F;#yrVkTB8~LLN@CI1$*{zQugCAgfXBqxw;)jWX>>u3nuzI5$GZ7PyyG*FmGxFx2>vA}IO!N&@?py%p}@<82~)f(a^A8q~vo4v0x&DHKHu6Uh{DY4$3EaBL z94Q|G^B5I@%paFO;vs~OK4b~W14HYNwc0;$k3@(m8=o(Yo!3Qy;4>BG#*dl256Jd0 z{l!DO`2&@}l@7{3B31l&nfH>wGpR50AJ8cw&OWBgkiPZuk7SP~(B#7erUC<5ak76* z{#Yf9d3^e?^a0WkvML~jI3>Ux59OVpoipYzg#y#!m#zllSQUN1)V1;t_73vHsCQ!^ zE6=2treB&eP#J+he8mSgp_~s=l$rTOH$KFDq0c_M`5{V~E|`LUbZ(DqAE_@+m$H=y zv5$H2?fqA>2d-Z*@ph0kPHcs4`D~D4kodbfpqebW{_mxgFFXV0F(KH{nOQ# z@s2GDSIXk+Dwe-z#UAH&KI{l+9k%8EG` zxR1^Vu?d8xaGZ`5ofEuc$c!D{5GgD93(^_SBnR0Pz?83SdRb6 z?-T4H1*F^IOsGpc0cL}L#A`Bu4EWI&I@4YyHc7%hHo6#5)LB|%p04FjnDJv^gI4}b zZx|KG{E~a~ebL_8KfO6GGVNuLI9B+A25&>A;ddUUhT>YRYDpDf#AsAWD{~-TK0g~O-p^oK4=2j zUdBEgJ#ye*qPz(knYLKhLV^MTq*xEPfd820nF@1u^C^Uw2;D{RHR&U-D8X{Jtp=H3 z*vx>6mhmf-F0_~Qmc|kMBL)(3BU@qWyj~OaGL=8cL^3wkm5{Vy8|=o%;2+^<;-B71 z<`;TT>qKTQ(R-qRfp*y_9|780CH^IfMK*9r#hG@>e_{ktHRBy)A4~`?<~Z!*5zzJ!C%LhQFP&WmFNtCxD`$*NO2EHSX<;ODdBbIaPP17z37+GzFebgiN zVapLc3l7VUU*QYJh9D@jekA(@1lTb;bf$7EjDg@BX)*tx>y^66y7S?OAH#!( z)rXKGBYh~sB!D{4m`8)2G)fi)oq@WN{<3uyagPwf%J@gx!aqy|lf4)kOf*u>N-#yC z(H88hS^??rhs+88+h4;6P5fKVe$r7kJ22T}nkm>vSe#}Ku#8ws;s?cI3|tle*mTF| ze6S2GWI&2d!PN&TWgusvl>n*Ig$izb_S=8_Pyh5!|HrRy!b;ijkCdL~S0+{**>V0c zzS2et@r-FU*(t`rRoOo@!5o$ZooE7PK$K7vA*ky7V-o-%FUcT%q#~(2GJS{n`}jBi z^q>FBfBkR&mbR`;cmhY^LrkOJgGho`GYWX@sB{ zexXzTF8m`7qE`N?-bHBQr~mk$|LgzR_`m+!fBN(WO$5tG`2X8G4}hx5{*R+LaexZu zP9+slD{{9OI(TK3b&%DkOOjL+nqc2=*@+iBg^$#{qz6* z{+@F#cf*A%T5*(n5BHw&JfG+L8_#o|b5P&&q=rS`g=Woc573y{NPwZE2S<3s^frxt zG;8n7p5y|IqJ}ew^-2<&QGPg&UK4AlwEr2*p-}PCN!5f~W)y|X4&OEl^F1oA_~73tAENa|u3 ze5CvwDt@|7nbt-*T=RJpFIB2!F*nRUa47r;Jr#u&LCTM*iWYkXBEqoXV+M%oIdlSx zqEmj?K)K!AixzXw&m`Cpbx?YkKPvXg8+-A)X1mP!u#g`X z2v9O;0hqZU3JBC6L10PEQ`O&!( z9Px2Qtt&%@2?JrK1SsaK&L|LN_>u90{BT-F247NZlG!zO0>y_pav)R%X^Pf&g8g9Z z9^&9flJhYX@jp4j)@XCuwSmldDB&jqR!W0zoHfD#tOfvKzm6D?Bh|IHE&Ui4dz)B% zrvjmRNFvIQ*&Dp{349W5NsaixPn?wucBv4YDGOtxsUt#!^m-yayombX03QoKz;O&7 zci>GRC_bMK0Uyq}ljveFR%i=XM0U!)@vHdv%v}hf{^(EHC`?*}9|H(limujq{2T^i zp)k82FjnN1Tl6{cR8+wJ6FVN7_Y-`YtHK*`LV&=RS^E`<10eW8f4Blj>*et|&f^sQ z(HO;_JvxY#ABMc-35lG#hHCK9&Kf^bq(-zj$v2v_Hm4~QrK0_UpVlnErub>?;}m{G zCmnkZCODXp*p-~|m-K#|a3l|vX zXUcpPKSfKakU>Pl2Z?9$k2|1VGLi~*(g~1vjiIr8GP_Ram%m`)qD3)#2+x*Ok0@c@ zLUqv5q3bIZqlbc(NlL@oF?wW5e~iC$4f3Kp2=b%QnIUE3=!l7u!3s5663j^axwHE< z5_|D%aoo4XsrUNaXYylqLHP+jI(W(h^`j;w{#unE9kwiKHLUV~D#%lXR5%tzk7seL z`)+nR=@BS;$`SDtT3~W!W+~Ro2dweI_U<0ViWTuFL?c4;=av(qK0=N!NG=pV;A4Jk zE)a549$OoTFsqdDuhgU7S6 zxyX;tSHlDT@Ps-(rLKqv|2hgTKXE35u09$rIr6?F!h=6HFN45iuTNcKhke`k*EqFY zJC$l7`;+$JS@<9LGG)J%A9F~0Z#jYXA~NUTwvzxte(-*HydUkC2%*6ug{cEfKH`-z zO?-+Tswkcj@T}m2@|cmRMO|jH=Oi_L+yU1uDQ#rX(nvf@Ci7?TLnDJAC18vY`o^zJ zE}f!4)$_p9{X9L32|ssOGH$=jmk$#MX~3z*OL#OaX3hw0W(`Oh0uTGBGO`mvnVN5D zumlOfTJXSWI0=yfO5B91qG3~qS9E?_f@DOHg+r4$D?8`o*$cV@pC?ThGUQ_t`3xZp zA#iw!3&=_M;EBluc4^V`(7YK7suCSlWFcsVT8DvMAU@L87C8^9hxg2f5Bd?A97Vz5 z94h`&5ELK)+9;J?y7ax{76$a25H?`B@O=2eD<6!F$up=P#iT7t_7XS5ubCX5aYH#X zY)xMVBC?;IK#i#n!cU|p+bfI$l<_MQ6%=cLAExahu(L^-wo(tuh(Ity08vlW)lheF zf4py+&pp7+msn#4!|oe~0C{sJt&nFH(Z-SDuN#u~?xN$P{arg;)02%0CgNaGt)WQeSKB|vh5S0M(-Wg3% zIzM3Gn5ksVsSTjD1W`yPr$$lOg%*S~6fm^Tf@jcOgjP>uepZ{mP za5IVA%Y?#nZ(FIbP}dwFY+gH&)YsN7X<*a8#*tNWDZZNwslR;Shpv_g5GQk3fLg$l zp>ZW4)&RMK%p9lfVgkOhUdWH?AU>*H7Majsjh=xYtb>mw=c54Fl?F%>_D*$;OikbxFzG)7iO7I{* zv?0|;3#1U$btU6s8rDUBFcvlh|Fjw3#D--+h0?V!c~woBzDaJ>pZIr$Pd(Y(aD0bX zB!5=zA!!jaOS)Gy6YwdX4z>|}os^L5AqY{Dm=L@w?_c0^59nPb^xrh>1u4ErB!d>&d_67+Z$B|Zhv zAp=%LWp2Sm0puTmgerNrFb}Qjim$|V3qLLP5+mwmGvsT{pmaxmG+qGYs!Hap8nDx6g5T&{{ z@(uI{eiDG$j`{QBmLyvOlw_OsOK1dGkow~qheT~SKyg&eVW<=VNbxa4w{aS)tgL-V zWwY>X6jls{Po>9m@dHO5kRdm#ih(K5q@KPGdO}bo>-2IgyMhEwX&<8$I5{{ltI@tS zD>VHmotPM`29U9Y9PRPL*|kvwlB4vPA7m3Xa);*h@T5@_9X@0 z8cY$&{WXLkNEj=4MV!L|TH8wrWP}NgMMc5tkvmKJY#|TrUSg7yB7&oMKjF#g7&a9} zHz9#uT93(gPS5rW)>D>$dk1W<_Z+*G>Ow?UCCTE{VRYHQUjsn)BQ zA()_CT!;CZ-XUrKB&5M~92^Sq&jA#1$DfZ;01XIDOOANW_5%_XNI556ITUODUD`)A zwkAQ6!-CJ~(JY7?6&L4~=Ht6_;Njpnq{y0OP|c_WNVDJ!L*^Ow3T0G2S=IS5HRGQ{ zQF*d9h-|IJF&CYG4F!T#x3$9Lbz{)~c7V}2C_n~l_AvSMBK{M%L<1C|J>`)})ggYGKBTjT$K;@^A=3;bTo_?6TIJ9n1xs`1!~_NcltCJk3N+dj ztE#yfL*O591wbMk*kM67XVd$c9>lr75ul79utk}5pbnB1FePA6bEH(tL5Vuh*+pNM zA`#27aTM&pwgYf%3(eA(u>tvtb6X%00fIO^g!C{$P~qdY)&%ZcT2_vxX@)MJ5tEju z7}7OM24`g_TOZLK(7JFKZI}SnW2UbM5%V3XKbm17K9)oAlNpX6$b`;ZfE>=_y)X=L z3Y;9D>74c9EA#U?|91$GE*us^<-wBp#uedb`FsMqB~S+=zrr3IWOk?i7=&0oYw}S1 zxIO>uk(L!xeORp7BrI5sH!$Erd31l0&)JO~cFUZ}FX>Y_0)i%m`GO+Bi2)%5+wp$N z85?HOYo(Bbaj*r~dW+&h8v%-v7#ytt1P*SEh2vY=X*r^TRWW??eUt@D7R)#;_|gi! znIx3P?#csFfOOsLcd_xsiQ7(|K~Lp!%%qI!i0a{b00hWCBdA^Gz|nxhkO!I+2fQ)1 z&u~SCvLXSDrGNqFv55XSFYJX2<1n3rB#VwD2_^+_!Y|@eq%C1 zRcFFDBS!TQjvy(d&m6DEL+X^XQUfj)#g!g21kVHz_}~MDB3{(>Sqm^m0ErOVliq~I zfwS%d3Q$cFVJe{(-~&Hf!Pj6!)Bz#NfV-5KWI4wf*E{5j2W%2FnblcO&?rD&3!@PO zAO7kk(-_1sUY^6T%`zCA1A5mkyYlQfstwngwE(3pC6^#M{Lp-Cqy#il@j$R1F2azt zpdpJi^YZ3u@Pq#Yi2TqXc0?Q00>IqvgdjTlj9RU>svcjhVMXe0WhZq$N>-REGd$3dm$q zXrNh`(1Ede^;C0kVgUK^hX%l7+v!rUnQ$V$m>=(P{I2RuccO^UDsAGEC(`M8KerT! zV#b;TrB9?N>cfrv_ck`xh(4FLk6(I^^pt>S_SA08Efp8v2s4w2bErw~+Q z!OTnaaA2_$xNP$08tD>akG z<7Ia=fJ%s?t^K%lR`DezzYy1!{8CRvlCFmGF z8DjOGnkWih5C9cIlD5fDvSOZEEk-9)$00MuHkEeL!eyR<(RP2ARk(Wb_2dP96roa{ z5~7TTF!XQ{-H5NG_jBW=4XX(;>yAc~eatzigHUzi8TleAhXySMx6Jo_0)JP5fx%MT z8eJgpiH!m!1xWi+H6uY7o=oq8HW6yXwjHV!a~mZ+!Ka5R3FX#ZsY8d)@{M(V=(9@n zp!x)jmgeDCR8&)$CPC!`S#`?+<+Lim^RY$HVA!88T$G!zm7u0|%*Xk?WNONYgdcq# zl*Jya0HpxQiB43RB!`=TSgLF>9-n4aa6XmQkQs$8M`9)ebW zy8bfg#Bml0#q8*6!zOfT@Pd>ND{Lzzw^>W|d!1h7JZ|P*0{C9 zC+5{QoJs-|{`46x(4o~RL;_527mk)oda?i>A@WC8A^Tenmn?%jNIoI)7sBIzW)-rg z;S+ww=9|G2soiiov=#Z`S)sUt4nwPQjYNblK|URar|}x$gAp505@7~iOFB+$Z4)Ix z5>$(djr35GSxEMh8a=EX!J*IObmYhR9k}m7E|4&kFiEmH6k&0AgfA?*_zdd?1@aS^ zvBiFm<^z-lCWVDbEf$?0-?1=Rv)M<>K#3?o=IUxX0|?4xb}fRWtJ10X{;I z*vN{r4xl^{gq>R-mJ2?2IgEM+3 zOi^H#@zXcjo{y6{nv1Hh!q{Gm;{Q711xE z3(P73;sOX;lky`z>6hYFrGD(Ar2)jjuVvD+q|Y#1IzZ-tJQ+LLX`V}YKNJ{676T?b zSqM{xw6mU{%$Wy5@?#3a8KhibQjYbi(uGnim`NY!#BoNUq@8kokV(0Hga-44(8OrL z4h2W!D`%)sX086TE4(TUi~dB2l*A*zhqK9y8V>J3frQ7F0s!T<>}S6Q{TcX);lTZn zpHgC0+b>jus751Z&;UEWaP5)cF2uoc0rD2o2b4;rxLLDN~n z3~gr9ZD;6cc6ZVm3eP*?Y)kq-Nt{x{Kk`AVBhsk=MQaj=b^U>=lHX(EXSc@A{!$2H zcCd=VlEpBl0HeIg>^Lh8s`C?i%8>P3Ml)-u`rp7$t^{aL0#I3?Fl07}cq{?gYL89> zsF}a!M>U#a*8diKk_pPqauR+G>7ek#wLo>Rr={%ar=c;m=tIvEG$=sj{vL<-vn=T& z{)9adk($b*X~@zW!3sJj98K%Wfd@(Mw1_fhruljYe`OH)dMQe2~1oJ z`V$MrZ}`kg*fGZcPgxiJsc_4*Pn5`654ip;f6^6^#JT|!0K&KM1He>dJ&@h>(GU_waCg8>0ut4fW4lr?fzfGQt%#yz}BNrcs-iqxe5@bj(~tet46|?ucMH zZTR0RKbhqRe7gKNzG7~`jK8YmAz7bTuA0-q9eMr31osoDH1nqoIje|_y^O2Ej}nx{ zY$mJZf82!rkNRWeW#XqE#0K+q_S7$DRZlyPbgtxzuagg~JrY3J;Oe9@UMdhbvm2cX z!v_Sc5oGA#q(Cg#QM{$`#zy-+8LMc1bgp>0ln9+#CO9^k-$F74`1BIuU_db8hXUZ* zCAR@XB|J45Ro%_t zKV`zy9~H=ZpPuyCi^A*k)i_@Vfzf#Yacs^JCq0hE7X&}tD$KOS0C~?Mx7A`W z&6jh(Qc*v7Yw++K7}5MXh2cDa6qf3hL4Gtz#sx`WO!#s5RE1xfF!LDRip1PmIlm{F zAFLOXmdH;f7y#(BTnoBl&zG{|b0{@_B0y77F@K@%Ku_D6zmcDLrt`$<57tY3T4Di+ zEU}X$2!7hQ3!(>SIMb!^Vz@aK=rpeYW1=nSh!Tq`%o7JKwLqHnDt;_6t;B25nif&`sn!y-S$ zK?Vlo78wr3gP&>t4VQ3TwVD1X1V&56m+-}GyC)Bz1Yf<9Qgmw28Fwo%DK|t9s0zWC z@q<)M!!M!82(&0p?nf7X-0X=~pO*VEcF^C*sJXZy<)AWYIAXql#pAMJn3dp1w1x0% zdUO@3Sq}VYk=NG5(i->i~QI=TKwDyaZ3?)0 z2$r+(!INlb^=L7a4Rz=STdC0gCXaZoy!=J(@i4j|bnX~2v=>ZX0 zE%`B;asC;n0I{l$)WpuVIFdH02=9W>&c+UY1(TtTiSp>As1IItz(@%#+9FL$|KZ{M zaNtya)Sj+BUevJa8>Dv5t)c;*VPY1JpZ;p3v86Wj!jyqdo&%ONUv{%7MfkuBDv*JR z{3L$xkecCA-#C|L{7s{v8T}ft4B+$?CjwOVD+&~VQcZG%uOdR3wUd$NQrU>=Rzt~b z3~dn^W)(kbS;xk|TqO%X%xHU`ix90?>4)^!&zHD(b_LIijKf z@?GI$XiCpcS+B4p2s>Ldx+C2WGvgrAenuIrBg#Y-tSPg~}K_Y4UT@?*yr0Fk_K!LbMsC*i`tWG(#K zWs!j|P4OAjCs8;9hqJPaK6tA5iEZ%(Y9TQ9}z7v1$hlhsO zlSN|3qqkmNed>-0v0edbFl1-huL_mo{Gbsf6C}BO9u=?Fs(bgCZrB%``M1gef*-6{ z=)r0kjqyR242zn|&kDax49uC9B| zU;>LT{6*cCF7%wpPY)r)CO;C3rOywKDCV1yi~|6vKwJ+}05$d5Q;qY!)9lwAAb1j~ z)3O&;VCC#-wy6uF2uDz1&a^z^$&Tr)B~u7q9ncocXviw#MF9=`4Ei%f5U1l~2%)Jl zxz!}doKKQnxWMTxbL zr)wiC-mUP{Nuo?nF9A(WGhyT};`As#EaaD|;Sw_B;`B1xJ)pFkPR#iI5fuf8+Lz1^F%{tOi%5-l6K^V(UUw@Q_ zfD9kbT&r<+fj8(IhWNc{*;DGx5wp8TdIn`svH$=tvKAoTG_@d1N& zPCd-y*DUt?PHhS^6Xu`Cb?Wi09*oTN#p(usqYqgRh>p%rRq1b3_9vvMx)kvKgp??| z7}*4}31kz!Yy#N?vI%4p$R?0YAe%rofouZV1hNTa6UZizO(2^YLI3fEhF*`;!eF#nFP;9LJ;y#@?Hg-x!Da{g zoBYc96UBplg>yFg;dT@AVw>|DtpN+y-`!33Fy=Su)&(Jq}4Cvmszv*HAHorYQ@$e@XkG3t_wB#gPmpAaw<>6_U7A|=(X+Ad| z*T9<{^W<~$;7z=4o`u~DdOYP>qQq04#fz6JTj}XirM=4(FJ8WSdGE?qs`^$f`E-rv zYxq20$)~Ch50V4Z?cMUZ6?1bd=2NP8DIbTIhhMuC$>;KQ4j0!P6CZg$HQ$d zPov3mn@;?cTe^6!m|FpCknG`-GY4)8%7q){a^=F-qwu{;t|GaMR;bx5PqBdRc`HVG z`i+^sIG=a(b>Dae9=i2RtsaBM=65UpREd(MDpjuHQ`NV2ow_g7tKXo-Oa3iewQkck zC^)2JXs6f0!h81W{hvO4BL)u{I&ApcBSwzEoa{0>DpMU-B_MN-; z?*H$>@2nT=sV_=FX7wt9df|2k*Iam1OD^2rHJrD270H#mLd`ryn+4?U9$BoS-xtq_DxfT39U(KQYe482!=^qHYK*WRh;wf=gJJBd+^SB|*Q_}c#M`(D^HEq?dPdM)OEQPS(kYtxx-tLDVv)au z{+WDw`mxV@h3(&UtItodM=!pywf`6XejDc%Ol)7x_qQU&3Jzc478O?H^?X;K9WXw^ zXGwCsZ}m#ZyYIf(?h~sdSH7OurTpb*gG0Z$Q#;&ijd!`32_EHdReLUC>ZkDy2ZSFA zJn;0aA9pVI4_h&+ef=s=_4W+#ubJC3psD=S4_^0m+_BGQ1b*aRolUrZOd0|D9`Mpn`&C_+t@oS;4 zZ6Ca+M#EZ1mUk>R{!rzn0SjWLuiU+PX5k|nhxht2_-gQ$V|U_{S2y~zTh(@hUTW*J zWMiYBmPGlu=s&CPz1u3-_w3Slmjwr(_;2AGwfx&3I#78+v&r{YPW;KO<)C2KE@#%< z?dsppFZtGkmLre)E=Xy5W6Z%|ucNtpj__Kxyzark!vVoP-#l@$bBF#fxAyulIcV#; z-@H3sjy>G>=dRQCO}iF0Z}E@`O&Tt2(Pe7j_?Nrf3vWNaf0Kq&=5>y}Td7^-M(?-Y zYuBP{o3rI!nq6(}D!+b9H#GBn z*YH1)3u5X_`SRZP9iw0B6uCXT!PYI_pSyoB^r>1kmycO`ZC{JdK~=&#-K@L0+}+^I zdwWe>e0{;VK1-qwrntQuSYp4wU(^ew-fXz!=QHuww);2wW5>~}Ay@Y--;y)JvnYH_ zY|Mm19eVcqxsczQ-EkXXPh-kf8DD#GeA1ZM#53Vu&qY=LbK=&O7k^yoH)^$e`y(rU zI639>@PR3-4>UYbVMK{pjiTEPdZ*OXjSWYZJAQoap|4jJ`S8p8!~gi`s?U-w2RwbZ zCw$-Z?TaBJqf6|K&h5EuM%e83fsi&-R@lVHYt2ek^%;GW6=hzE5Dm&d+qoxAUP(;=)UpPb~@D zoZO(T?{2TendAS8nKpT7;@CdlJUu@A#~hp9c;ncSUI)+Z4BU`XEx_OF;E2Wa4;`kw zyea40j(5)eaXvDrO0U6f11`^-_-U?Y;W^``w60Qbd-=6G`StqOURU%|ORoX{8+oSIfB_F(mixzF-9GT@jBDGQt@v;09w8s*J$Ni=cJhjw zW#1mzFYm-|XZs9$)??$|7Vn2`J63o5=Y4nW^nAZx<^3n`?(6Y-x6%{y#`T*uK7ZA& z3-*W4ITX{d)v$goLnjRIs~2A?q+6R+vo3u$BxT`X*E#tQl#krrIqHkK2TDa>+!~O3 z-@%}r9TIxA{o{1K?G0LA?AE1J)aMm)do4b9zS{n!NfQ$m)v8-7>hPp?%^S@4C2Cw7 z@4@3bulerM=~HVH0s~^EU-{|znX6hXX!*>Rflm)z7hL(HZ71p+Yu#e#_V9=+bzSHA z92+#O`;@Q0jV<$2&ydx{xBWM4b^D;Ci=n|WN2gVc-Tch5A+5jIeSTTPfxa;br?yPK zHOe=1)09Ckhq_MsV%pO2ed?X9Id4+#kM5seyXx%S1F=oJja?Pm)o0Vrplv&jg+z2~ z2sauQJBE@7Yta9HmBKc}2glU+sx{4TT|%kEnMbA{ozU|1_G*3?N}ON2_^Z?R$E;|# z@}kG2vG1g8`ts9i3x3`k|J&KgM|-=M_rH?dd}i$3Wr-(t#&o)ccI=(<`?M(~PrLa= zbWDnQe?n5yu3=R+uTCkF^zqrotMebTD-@M7{8%e&<1cGB}x zm+2!)mc7=XQ`n*VRrfq}@ePb#wRvPr`8g>ILBIJ`OjcEHF5pLw4Q55E~3U!q>^!pVb!nyxPx;(MpZgO~%eR=fY_ zg>pMXdR$%m{N<@JwWGS+*_Ak8|BXevett1|Q5}ER!<$w#ZyVa`VE5Z@fpIg>P8uFL zt@^kzUCyjt-}3bqp^0^hUdZQuA*8~Xlt%9j{i4LC=ia^@ncV%OdBHI|zNs*^ssCG@ z&$>T%$#ucqIa}UqRpa-do=MS__N-eH;niS5+2h|;zOgvI`krym9IJD7=Yqe!UEI6I z{i%JE2QFJ0Kh?XW@AtEN!jK{&C;3> zz?vaDVyh(73oGhrSQoajA60kMAz~^pyv- zuiu<>Xs_Etm$q|zUx@mCMIP_Pbv}uk`$L&mrZ4~MkE3nR-Tr#z#*OiDKTNKEvt8Sp z5#RWGhQyxJ>%{kt2{%gh+8_D9khhNy9@91V*7vKNdo3h>Pg28yZx0RYc5g@Rd(+&j zOurVgI=)ZL=(2-fd+J{0CUct<3247%*5uP(v$yn{SGr_U;Nj3_DJq!?oZ2U6$|w1v zn|El|d3E7ikvn`R%=dWd!IpIioo;)0mkjFSm2xKO#Tm~nTH0=~=ZaT)mfMs#^vhp= zKi+Qm?Zu7UQaS{6?!I91Py4*Te{xhtWNgc?rnDRSu7_8LIrDb)=ydVut4rT}Z9?}c zfn7c>8s(F?VF$ebXg8nbu8nv zElnQWqxkH`UDo&eWY(pdvA+k_3+?i8MBV<=$4rg*@0>Da?)=iFLyIfkeY=+VY^h6B z)6kpAK?(a_&GW%uF&}oDQnh}opi7ZSF%1%0y|E;!TF}S7KQ8o$Zr+}k8 zjq2%IF>e1;Ua<>X#LX-k8Xi${_NLXHTQ*4uo%v_dlDoc3?hh?5rDI;iAK!i>P8HvE zab$4qz`k{Q2lqVxOR-_4FAmt+W88=m1Cq!4hhMKa{QW*hlIM>6{rbzlE&BavDW8OJ zFRz}FM_2on)*%#_eX&=(#hZm1t3z?G=9X+pO<-vo{W^)ZTXsp|GuIGJt((YKm{{wW- BnPUI| literal 0 HcmV?d00001 diff --git a/src/czitools/pylibczirw_tools.py b/src/czitools/pylibczirw_tools.py index ccda7bd..5883b5b 100644 --- a/src/czitools/pylibczirw_tools.py +++ b/src/czitools/pylibczirw_tools.py @@ -16,7 +16,6 @@ from czitools import misc import numpy as np from pathlib import Path, PurePath, PureWindowsPath, PurePosixPath -from typing import List, Dict, Tuple, Optional, Type, Any, Union, Mapping from tqdm.contrib.itertools import product import dask import dask.array as da From 71edfd07234f07fa190e62b6186e9887d0245c94 Mon Sep 17 00:00:00 2001 From: Sebastian Rhode <3833249+sebi06@users.noreply.github.com> Date: Fri, 13 Jan 2023 16:50:51 +0100 Subject: [PATCH 2/9] updates --- data/10x10.czi | Bin 0 -> 2400 bytes 1 file changed, 0 insertions(+), 0 deletions(-) create mode 100644 data/10x10.czi diff --git a/data/10x10.czi b/data/10x10.czi new file mode 100644 index 0000000000000000000000000000000000000000..4c2bad88e0c3c438f5fe976abe34b034e7ed9940 GIT binary patch literal 2400 zcmeHITT|0O6b?5*yx;Fy>s=|EP@o9QN}CjnP{1aXHeTwc-ImNWX{QYX^|$yR9G`u0 z#z+5z&p!CzFL0CH^uly-93LDU`|_Rd+wW}8o;}+ds+Nv#E~+a@fqnYBv5(UL8sd+w z_g|W;ALibD8~VOe-phU82y=%9gRK8}P&;7w^Pz1d5-V%+vLJlr1M!(-o*%5pQOS5g zD3~IyG8m?i<;rLW<~m|8^wv^b3+B&5qlYH<(DWXf)lY(`^d34)FU2-qH=wW-k8KQK6S zXn5rC=#itxj-NPr>hzhj=gwcacd0NvqGMm|)@#5vH*ZDWtEbM!JcK<;=g79g8 z**^x@^QQOR@}F9Ss*TG;sTQ_v;MHZ#R#fSsam(OYgHXeJQsqR^_FCjBtpO< zU&ytD9CH{*Vg`e8Ip8wmdcbwYwSa4krvjcrP_bxT4O#;oO?eAD4ym-GkXoD%b9Gh> zhgPvos33(AT(@>fMQ_#!TB_sb3CD}**-R}%BG zq2^2Vs-4%>Mfx246)0D2zq_?&#WJa*z7r}Fht#o4inIp4N=s;6L%de2SOwmE?^vjv z3xO}d45@pKLDBJvaoX^JLeP~Ay6#acVDE9$3w4Q2Ppw!D7dr*wcQQ-+mcf;6dKMB9 zV{}WOc&%ZsJ^tKp`i)=v0j~?R3I*+n1l_ahzeD!E6*Zld^|f?PSl~anV;qCu-@o-} I5BmSfPqlbUlmGw# literal 0 HcmV?d00001 From e354e9e94a801aefa5505e3f83fe3015c6da161d Mon Sep 17 00:00:00 2001 From: sebi06 <3833249+sebi06@users.noreply.github.com> Date: Thu, 26 Jan 2023 20:16:13 +0100 Subject: [PATCH 3/9] updates --- demo/notebooks/save_with_ZSTD_compression.ipynb | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/demo/notebooks/save_with_ZSTD_compression.ipynb b/demo/notebooks/save_with_ZSTD_compression.ipynb index f086eb4..3934e6a 100644 --- a/demo/notebooks/save_with_ZSTD_compression.ipynb +++ b/demo/notebooks/save_with_ZSTD_compression.ipynb @@ -144,9 +144,9 @@ ], "metadata": { "kernelspec": { - "display_name": "Python [conda env:ia39]", + "display_name": "Python 3 (ipykernel)", "language": "python", - "name": "conda-env-ia39-py" + "name": "python3" }, "language_info": { "codemirror_mode": { @@ -158,7 +158,7 @@ "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", - "version": "3.9.13" + "version": "3.10.8" }, "vscode": { "interpreter": { From 57990af78797d05c37ef18730795a40d38923677 Mon Sep 17 00:00:00 2001 From: sebi06 <3833249+sebi06@users.noreply.github.com> Date: Fri, 27 Jan 2023 17:15:21 +0100 Subject: [PATCH 4/9] updates --- src/czitools/pylibczirw_metadata.py | 98 +++++++++++++++++++++++------ 1 file changed, 78 insertions(+), 20 deletions(-) diff --git a/src/czitools/pylibczirw_metadata.py b/src/czitools/pylibczirw_metadata.py index f2f7cc4..2077136 100644 --- a/src/czitools/pylibczirw_metadata.py +++ b/src/czitools/pylibczirw_metadata.py @@ -20,6 +20,26 @@ from dataclasses import dataclass, field, fields, Field from pathlib import Path from box import Box, BoxList +import logging +import time + + +def setup_log(name, create_logfile=False): + + logger = logging.getLogger(name) # > set up a new name for a new logger + logger.setLevel(logging.INFO) # here is the missing line + log_format = logging.Formatter( + "%(asctime)s - %(levelname)s - %(message)s", datefmt="%d-%b-%y %H:%M:%S") + + if create_logfile: + + filename = f"./test_{name}.log" + log_handler = logging.FileHandler(filename) + log_handler.setLevel(logging.DEBUG) + log_handler.setFormatter(log_format) + logger.addHandler(log_handler) + + return logger @dataclass @@ -450,6 +470,8 @@ class CziScaling: def __post_init__(self): + logger = setup_log("CziScaling") + if isinstance(self.czisource, Box): czi_box = self.czisource else: @@ -470,11 +492,13 @@ def __post_init__(self): } elif not czi_box.has_scale: - print("No scaling information found.") + # print("No scaling information found.") + logger.info("No scaling information found.") @ staticmethod def safe_get_scale(dist: BoxList, idx: int) -> Optional[float]: + logger = setup_log("CziScaling") scales = ['X', 'Y', 'Z'] try: @@ -484,13 +508,16 @@ def safe_get_scale(dist: BoxList, idx: int) -> Optional[float]: # check for the value = 0.0 if sc == 0.0: sc = 1.0 - print("Detected Scaling = 0.0 for " + - scales[idx] + " Using default = 1.0 [micron].") + # print("Detected Scaling = 0.0 for " + + # scales[idx] + " Using default = 1.0 [micron].") + logger.info("Detected Scaling = 0.0 for " + + scales[idx] + " Using default = 1.0 [micron].") return sc except (IndexError, TypeError, AttributeError): - print("No " + scales[idx] + "-Scaling found. Using default = 1.0 [micron].") + # print("No " + scales[idx] + "-Scaling found. Using default = 1.0 [micron].") + logger.info("No " + scales[idx] + "-Scaling found. Using default = 1.0 [micron].") return 1.0 @@ -508,6 +535,8 @@ class CziObjectives: def __post_init__(self): + logger = setup_log("CziObjectives") + if isinstance(self.czisource, Box): czi_box = self.czisource else: @@ -529,7 +558,8 @@ def __post_init__(self): self.name = objective.Manufacturer.Model elif not czi_box.has_objectives: - print("No Objective Information found.") + # print("No Objective Information found.") + logger.info("No Objective Information found.") # check if tubelens metadata exist if czi_box.has_tubelenses: @@ -540,7 +570,8 @@ def __post_init__(self): self.tubelensmag = float(tubelens.Magnification) elif not czi_box.has_tubelens: - print("No Tublens Information found.") + # print("No Tublens Information found.") + logger.info("No Tublens Information found.") # some additional checks to clac the total magnification if self.objmag is not None and self.tubelensmag is not None: @@ -560,6 +591,8 @@ class CziDetector: def __post_init__(self): + logger = setup_log("CziDetector") + if isinstance(self.czisource, Box): czi_box = self.czisource else: @@ -589,7 +622,8 @@ def __post_init__(self): elif czi_box.ImageDocument.Metadata.Information.Instrument is None: - print("No Detetctor(s) information found.") + # print("No Detetctor(s) information found.") + logger.info("No Detetctor(s) information found.") self.model = [None] self.name = [None] self.ID = [None] @@ -604,6 +638,8 @@ class CziMicroscope: def __post_init__(self): + logger = setup_log("CziMicroscope") + if isinstance(self.czisource, Box): czi_box = self.czisource else: @@ -612,7 +648,9 @@ def __post_init__(self): if czi_box.ImageDocument.Metadata.Information.Instrument is None: self.ID = None self.Name = None - print("No Microscope information found.") + # print("No Microscope information found.") + logger.info("No Microscope information found.") + else: self.ID = czi_box.ImageDocument.Metadata.Information.Instrument.Microscopes.Microscope.Id self.Name = czi_box.ImageDocument.Metadata.Information.Instrument.Microscopes.Microscope.Name @@ -634,6 +672,8 @@ class CziSampleInfo: def __post_init__(self): + self.logger = setup_log("CziSampleInfo") + if isinstance(self.czisource, Box): czi_box = self.czisource else: @@ -655,10 +695,14 @@ def __post_init__(self): self.get_well_info(allscenes[well]) except AttributeError: - print("CZI contains no scene metadata.") + # print("CZI contains no scene metadata.") + self.logger.info("CZI contains no scene metadata.") elif size_s is None: - print("No Scene or Well information found. Try to read XY Stage Coordinates from subblocks.") + # print("No Scene or Well information found. Try to read XY Stage Coordinates from subblocks.") + self.logger.info( + "No Scene or Well information found. Try to read XY Stage Coordinates from subblocks.") + try: # read the data from CSV file planetable, csvfile = misc.get_planetable(czi_box.filepath, @@ -682,20 +726,23 @@ def get_well_info(self, well: Box): if well.Index is not None: self.well_indices.append(int(well.Index)) elif well.Index is None: - print("Well Index not found.") + # print("Well Index not found.") + self.logger.info("Well Index not found.") self.well_indices.append(1) if well.Name is not None: self.well_position_names.append(well.Name) elif well.Name is None: - print("Well Position Names not found.") + # print("Well Position Names not found.") + self.logger("Well Position Names not found.") self.well_position_names.append("P1") if well.Shape is not None: self.well_colID.append(int(well.Shape.ColumnIndex)) self.well_rowID.append(int(well.Shape.RowIndex)) elif well.Shape is None: - print("Well Column or Row IDs not found.") + # print("Well Column or Row IDs not found.") + self.logger("Well Column or Row IDs not found.") self.well_colID.append(0) self.well_rowID.append(0) @@ -706,7 +753,8 @@ def get_well_info(self, well: Box): self.scene_stageX.append(np.double(sx)) self.scene_stageY.append(np.double(sy)) if well.CenterPosition is None: - print("Stage Positions XY not found.") + # print("Stage Positions XY not found.") + self.logger("Stage Positions XY not found.") self.scene_stageX.append(0.0) self.scene_stageY.append(0.0) @@ -722,6 +770,8 @@ class CziAddMetaData: def __post_init__(self): + logger = setup_log("CziAddMetadata") + if isinstance(self.czisource, Box): czi_box = self.czisource else: @@ -730,27 +780,32 @@ def __post_init__(self): if czi_box.has_experiment: self.experiment = czi_box.ImageDocument.Metadata.Experiment else: - print("No Experiment information found.") + # print("No Experiment information found.") + logger.info("No Experiment information found.") if czi_box.has_hardware: self.hardwaresetting = czi_box.ImageDocument.Metadata.HardwareSetting else: - print("No HardwareSetting information found.") + # print("No HardwareSetting information found.") + logger.info("No HardwareSetting information found.") if czi_box.has_customattr: self.customattributes = czi_box.ImageDocument.Metadata.CustomAttributes else: - print("No CustomAttributes information found.") + # print("No CustomAttributes information found.") + logger.info("No CustomAttributes information found.") if czi_box.has_disp: self.displaysetting = czi_box.ImageDocument.Metadata.DisplaySetting else: - print("No DisplaySetting information found.") + # print("No DisplaySetting information found.") + logger.info("No DisplaySetting information found.") if czi_box.has_layers: self.layers = czi_box.ImageDocument.Metadata.Layers else: - print("No Layers information found.") + # print("No Layers information found.") + logger.info("No Layers information found.") @ dataclass @@ -765,6 +820,8 @@ class CziScene: def __post_init__(self): + logger = setup_log("CziScene") + if isinstance(self.filepath, Path): # convert to string self.filepath = str(self.filepath) @@ -780,7 +837,8 @@ def __post_init__(self): self.height = self.bbox.h except KeyError: # in case an invalid index was used - print("No Scenes detected.") + # print("No Scenes detected.") + logger.info("No Scenes detected.") class CziMetadataComplete: From 1ded268a6879cb7e58f32e1f5ec2277de4f6de84 Mon Sep 17 00:00:00 2001 From: sebi06 <3833249+sebi06@users.noreply.github.com> Date: Fri, 27 Jan 2023 18:12:20 +0100 Subject: [PATCH 5/9] updates --- README.md | 2 +- demo/scripts/use_pylibczirw_metadata_class.py | 2 +- setup.cfg | 2 +- src/czitools/__init__.py | 2 +- src/czitools/_tests/test_md_general.py | 13 +-- src/czitools/pylibczirw_metadata.py | 79 ++++++++++++------- 6 files changed, 61 insertions(+), 39 deletions(-) diff --git a/README.md b/README.md index c1177d1..3c85c22 100644 --- a/README.md +++ b/README.md @@ -32,7 +32,7 @@ xmlfile = czimd.writexml(filepath) czi_channels = czimd.CziChannelInfo(filepath) # get the complete metadata from the CZI as one big object -czimd_complete = czimd.CziMetadataComplete(filepath) +czimd_complete = czimd.get_metadata_as_object(filepath) # get an object containing only the dimension information czi_dimensions = czimd.CziDimensions(filepath) diff --git a/demo/scripts/use_pylibczirw_metadata_class.py b/demo/scripts/use_pylibczirw_metadata_class.py index 6ae3b39..4e11ec0 100644 --- a/demo/scripts/use_pylibczirw_metadata_class.py +++ b/demo/scripts/use_pylibczirw_metadata_class.py @@ -43,7 +43,7 @@ czi_channels = czimd.CziChannelInfo(filepath) # get the complete metadata from the CZI as one big object -czimd_complete = czimd.CziMetadataComplete(filepath) +czimd_complete = czimd.get_metadata_as_object(filepath) # get an object containing only the dimension information czi_dimensions = czimd.CziDimensions(filepath) diff --git a/setup.cfg b/setup.cfg index 9aa5bec..1ce1653 100644 --- a/setup.cfg +++ b/setup.cfg @@ -1,6 +1,6 @@ [metadata] name = czitools -version = 0.2.0 +version = 0.2.1 author = Sebastian Rhode author_email = sebrhode@gmail.com url = https://github.com/sebi06/czitools diff --git a/src/czitools/__init__.py b/src/czitools/__init__.py index 4d730de..309890b 100644 --- a/src/czitools/__init__.py +++ b/src/czitools/__init__.py @@ -1,3 +1,3 @@ # __init__.py # version of the czitools package -__version__ = "0.2.0" +__version__ = "0.2.1" diff --git a/src/czitools/_tests/test_md_general.py b/src/czitools/_tests/test_md_general.py index e8421d6..634baba 100644 --- a/src/czitools/_tests/test_md_general.py +++ b/src/czitools/_tests/test_md_general.py @@ -63,10 +63,10 @@ def test_dimorder(): filepath = basedir / r"data/S=2_3x3_CH=2.czi" md = czimd.CziMetadata(filepath) - assert(md.aics_dim_order == {'R': -1, 'I': -1, 'M': 5, 'H': 0, 'V': -1, - 'B': -1, 'S': 1, 'T': 2, 'C': 3, 'Z': 4, 'Y': 6, 'X': 7, 'A': -1}) - assert(md.aics_dim_index == [-1, -1, 5, 0, -1, -1, 1, 2, 3, 4, 6, 7, -1]) - assert(md.aics_dim_valid == 8) + assert (md.aics_dim_order == {'R': -1, 'I': -1, 'M': 5, 'H': 0, 'V': -1, + 'B': -1, 'S': 1, 'T': 2, 'C': 3, 'Z': 4, 'Y': 6, 'X': 7, 'A': -1}) + assert (md.aics_dim_index == [-1, -1, 5, 0, -1, -1, 1, 2, 3, 4, 6, 7, -1]) + assert (md.aics_dim_valid == 8) def test_scene_shape(): @@ -85,7 +85,7 @@ def test_scene_shape(): # get the complete metadata at once as one big class md = czimd.CziMetadata(filepath) - assert(md.scene_shape_is_consistent == sc) + assert (md.scene_shape_is_consistent == sc) def test_reading_czi_fresh(): @@ -105,3 +105,6 @@ def test_reading_czi_fresh(): assert (mdata.sample.scene_stageY == []) assert (mdata.sample.image_stageX is None) assert (mdata.sample.image_stageY is None) + + +test_scene_shape() diff --git a/src/czitools/pylibczirw_metadata.py b/src/czitools/pylibczirw_metadata.py index 2077136..6b7711d 100644 --- a/src/czitools/pylibczirw_metadata.py +++ b/src/czitools/pylibczirw_metadata.py @@ -26,8 +26,11 @@ def setup_log(name, create_logfile=False): - logger = logging.getLogger(name) # > set up a new name for a new logger - logger.setLevel(logging.INFO) # here is the missing line + # set up a new name for a new logger + logger = logging.getLogger(name) + logger.setLevel(logging.INFO) + + # define the logging format log_format = logging.Formatter( "%(asctime)s - %(levelname)s - %(message)s", datefmt="%d-%b-%y %H:%M:%S") @@ -75,11 +78,13 @@ class CziMetadata: sample: Optional[CziSampleInfo] = field(init=False, default=None) add_metadata: Optional[CziAddMetaData] = field(init=False, default=None) """ - Create a CziMetadata object from the filename + Create a CziMetadata object from the filename of the CZI image file. """ def __post_init__(self): + logger = setup_log("CziMetaData") + if isinstance(self.filepath, Path): # convert to string self.filepath = str(self.filepath) @@ -88,7 +93,7 @@ def __post_init__(self): self.dirname = str(Path(self.filepath).parent) self.filename = str(Path(self.filepath).name) - # testing + # get the metadata as box self.czi_box = get_czimd_box(self.filepath) # get acquisition data and SW version @@ -99,6 +104,10 @@ def __post_init__(self): if self.czi_box.ImageDocument.Metadata.Information.Image is not None: self.acquisition_date = self.czi_box.ImageDocument.Metadata.Information.Image.AcquisitionDateAndTime + if self.czi_box.ImageDocument.Metadata.Information.Document is not None: + self.creation_date = self.czi_box.ImageDocument.Metadata.Information.Document.CreationDate + self.user_name = self.czi_box.ImageDocument.Metadata.Information.Document.UserName + # get the dimensions and order self.image = CziDimensions(self.czi_box) @@ -132,7 +141,8 @@ def __post_init__(self): self.aics_posC = self.aics_dim_order["C"] except ImportError as e: - print("Package aicspylibczi not found. Use Fallback values.") + # print("Package aicspylibczi not found. Use Fallback values.") + logger.info("Package aicspylibczi not found. Use Fallback values.") for ch, px in self.pixeltypes.items(): npdtype, maxvalue = self.get_dtype_fromstring(px) @@ -320,7 +330,7 @@ def __post_init__(self): self.set_dimensions() def set_dimensions(self): - """set_dimensions: Populate the image dimensions with the detected values from the metadata + """Populate the image dimensions with the detected values from the metadata """ # get the Box and extract the relevant dimension metadata @@ -354,6 +364,8 @@ class CziBoundingBox: def __post_init__(self): + logger = setup_log("CziBoundingBox") + if isinstance(self.czisource, Path): # convert to string self.czisource = str(self.czisource) @@ -367,19 +379,22 @@ def __post_init__(self): self.scenes_bounding_rect = czidoc.scenes_bounding_rectangle except Exception as e: self.scenes_bounding_rect = None - print("Scenes Bounding rectangle not found.") + # print("Scenes Bounding rectangle not found.") + logger.info("Scenes Bounding rectangle not found.") try: self.total_rect = czidoc.total_bounding_rectangle except Exception as e: self.total_rect = None - print("Total Bounding rectangle not found.") + # print("Total Bounding rectangle not found.") + logger.info("Total Bounding rectangle not found.") try: self.total_bounding_box = czidoc.total_bounding_box except Exception as e: self.total_bounding_box = None - print("Total Bounding Box not found.") + # print("Total Bounding Box not found.") + logger.info("Total Bounding Box not found.") @ dataclass @@ -393,6 +408,8 @@ class CziChannelInfo: def __post_init__(self): + logger = setup_log("CziChannelInfo") + if isinstance(self.czisource, Box): czi_box = self.czisource else: @@ -416,7 +433,8 @@ def __post_init__(self): except AttributeError: channels = None elif not czi_box.has_channels: - print("Channel(s) information not found.") + # print("Channel(s) information not found.") + logger.info("Channel(s) information not found.") if czi_box.has_disp: @@ -432,7 +450,8 @@ def __post_init__(self): disp = None elif not czi_box.has_disp: - print("DisplaySetting(s) not found.") + # print("DisplaySetting(s) not found.") + logger.info("DisplaySetting(s) not found.") def get_channel_info(self, display: Box): @@ -672,7 +691,7 @@ class CziSampleInfo: def __post_init__(self): - self.logger = setup_log("CziSampleInfo") + logger = setup_log("CziSampleInfo") if isinstance(self.czisource, Box): czi_box = self.czisource @@ -696,11 +715,11 @@ def __post_init__(self): except AttributeError: # print("CZI contains no scene metadata.") - self.logger.info("CZI contains no scene metadata.") + logger.info("CZI contains no scene metadata.") elif size_s is None: # print("No Scene or Well information found. Try to read XY Stage Coordinates from subblocks.") - self.logger.info( + logger.info( "No Scene or Well information found. Try to read XY Stage Coordinates from subblocks.") try: @@ -717,6 +736,8 @@ def __post_init__(self): def get_well_info(self, well: Box): + logger = setup_log("CziSampleInfo") + # check the ArrayName if well.ArrayName is not None: self.well_array_names.append(well.ArrayName) @@ -727,14 +748,14 @@ def get_well_info(self, well: Box): self.well_indices.append(int(well.Index)) elif well.Index is None: # print("Well Index not found.") - self.logger.info("Well Index not found.") + logger.info("Well Index not found.") self.well_indices.append(1) if well.Name is not None: self.well_position_names.append(well.Name) elif well.Name is None: # print("Well Position Names not found.") - self.logger("Well Position Names not found.") + logger.info("Well Position Names not found.") self.well_position_names.append("P1") if well.Shape is not None: @@ -742,7 +763,7 @@ def get_well_info(self, well: Box): self.well_rowID.append(int(well.Shape.RowIndex)) elif well.Shape is None: # print("Well Column or Row IDs not found.") - self.logger("Well Column or Row IDs not found.") + logger.info("Well Column or Row IDs not found.") self.well_colID.append(0) self.well_rowID.append(0) @@ -754,7 +775,7 @@ def get_well_info(self, well: Box): self.scene_stageY.append(np.double(sy)) if well.CenterPosition is None: # print("Stage Positions XY not found.") - self.logger("Stage Positions XY not found.") + logger.info("Stage Positions XY not found.") self.scene_stageX.append(0.0) self.scene_stageY.append(0.0) @@ -841,25 +862,21 @@ def __post_init__(self): logger.info("No Scenes detected.") -class CziMetadataComplete: +def get_metadata_as_object(filepath: Union[str, os.PathLike[str]]) -> DictObj: """ Get the complete CZI metadata as an object created based on the dictionary created from the XML data. """ - def __init__(self, filepath: Union[str, os.PathLike[str]]) -> None: - - if isinstance(filepath, Path): - # convert to string - filepath = str(filepath) - - # get metadata dictionary using pylibCZIrw - with pyczi.open_czi(filepath) as czidoc: - md_dict = czidoc.metadata + if isinstance(filepath, Path): + # convert to string + filepath = str(filepath) - self.md = DictObj(md_dict) + # get metadata dictionary using pylibCZIrw + with pyczi.open_czi(filepath) as czidoc: + md_dict = czidoc.metadata - # TODO convert to dataclass and check if needed at all. Replace by Box? + return DictObj(md_dict) class DictObj: @@ -971,6 +988,8 @@ def create_mdict_red(metadata: CziMetadata, sort: bool = True) -> Dict: md_dict = {'Directory': metadata.dirname, 'Filename': metadata.filename, 'AcqDate': metadata.acquisition_date, + 'CreationDate': metadata.creation_date, + 'UserName': metadata.user_name, 'SW-App': metadata.software_version, 'SW-Version': metadata.software_name, 'SizeX': metadata.image.SizeX, From 45113c30c47970951fafc8b1a97487b3ef4e7387 Mon Sep 17 00:00:00 2001 From: sebi06 Date: Sat, 28 Jan 2023 18:27:30 +0100 Subject: [PATCH 6/9] Updates --- src/czitools/misc.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/czitools/misc.py b/src/czitools/misc.py index 4fd78ba..37b1b0e 100644 --- a/src/czitools/misc.py +++ b/src/czitools/misc.py @@ -235,7 +235,7 @@ def check_dimsize(mdata_entry: Union[Any, None], set2value: Any = 1) -> Union[An Args: mdata_entry: entry to be checked - set2value: value to replave None + set2value: value to replace None Returns: A list of dask arrays From 11d279f0099593f598d2d2cf69aa75bd1c65e454 Mon Sep 17 00:00:00 2001 From: sebi06 <3833249+sebi06@users.noreply.github.com> Date: Wed, 8 Feb 2023 16:21:42 +0100 Subject: [PATCH 7/9] updated test_czimd_box.py --- src/czitools/_tests/test_czimd_box.py | 106 ++++++++++++++------------ 1 file changed, 56 insertions(+), 50 deletions(-) diff --git a/src/czitools/_tests/test_czimd_box.py b/src/czitools/_tests/test_czimd_box.py index ca02ecc..a23c156 100644 --- a/src/czitools/_tests/test_czimd_box.py +++ b/src/czitools/_tests/test_czimd_box.py @@ -1,62 +1,68 @@ from czitools import pylibczirw_metadata as czimd from pathlib import Path, PurePath +import pytest +from typing import List, Dict, Tuple, Optional, Type, Any, Union, Mapping # adapt to your needs defaultdir = Path(__file__).resolve().parents[3] / "data" -def test_general_attr(): + +@pytest.mark.parametrize( + "attribute", + [ + "has_customattr", + "has_exp", + "has_disp", + "has_hardware", + "has_scale", + "has_instrument", + "has_microscopes", + "has_detectors", + "has_objectives", + "has_tubelenses", + "has_disp", + "has_channels", + "has_info", + "has_app", + "has_doc", + "has_image", + "has_scenes", + "has_dims" + ] +) +def test_general_attr(attribute: str) -> None: filepath = defaultdir / "CellDivision_T=3_Z=5_CH=2_X=240_Y=170.czi" czibox = czimd.get_czimd_box(filepath) - assert(hasattr(czibox, "has_customattr")) - assert(hasattr(czibox, "has_exp = False")) - assert(hasattr(czibox, "has_disp")) - assert(hasattr(czibox, "has_hardware")) - assert(hasattr(czibox, "has_scale")) - assert(hasattr(czibox, "has_instrument")) - assert(hasattr(czibox, "has_microscopes")) - assert(hasattr(czibox, "has_detectors")) - assert(hasattr(czibox, "has_objectives")) - assert(hasattr(czibox, "has_tubelenses")) - assert(hasattr(czibox, "has_disp")) - assert(hasattr(czibox, "has_channels")) - assert(hasattr(czibox, "has_info")) - assert(hasattr(czibox, "has_app")) - assert(hasattr(czibox, "has_doc")) - assert(hasattr(czibox, "has_image")) - assert(hasattr(czibox, "has_scenes")) - assert(hasattr(czibox, "has_dims")) - - -def test_box(): - - czifiles = ["CellDivision_T=3_Z=5_CH=2_X=240_Y=170.czi", - "Al2O3_SE_020_sp.czi", - "w96_A1+A2.czi", - "Airyscan.czi", - "newCZI_zloc.czi", - "does_not_exist.czi", - "FOV7_HV110_P0500510000.czi", - "Tumor_HE_RGB.czi", - "WP96_4Pos_B4-10_DAPI.czi", - "S=3_1Pos_2Mosaic_T=2=Z=3_CH=2_sm.czi" - ] - - results = [True, True, True, True, True, False, True, True, True, True] - - for czifile, result in zip(czifiles, results): - - filepath = defaultdir / czifile - - try: - czibox = czimd.get_czimd_box(filepath) - ok = True - except Exception: - ok = False - - assert(ok == result) - -test_box() \ No newline at end of file + assert (hasattr(czibox, attribute)) + + +@pytest.mark.parametrize( + "czifile, expected", + [ + ("CellDivision_T=3_Z=5_CH=2_X=240_Y=170.czi", True), + ("Al2O3_SE_020_sp.czi", True), + ("w96_A1+A2.czi", True), + ("Airyscan.czi", True), + ("newCZI_zloc.czi", True), + ("does_not_exist.czi", False), + ("FOV7_HV110_P0500510000.czi", True), + ("Tumor_HE_RGB.czi", True), + ("WP96_4Pos_B4-10_DAPI.czi", True), + ("S=3_1Pos_2Mosaic_T=2=Z=3_CH=2_sm.czi", True) + ] +) +def test_box(czifile: List[str], expected: bool) -> None: + + filepath = defaultdir / czifile + + try: + czibox = czimd.get_czimd_box(filepath) + ok = True + except Exception: + ok = False + + assert (ok == expected) From 7310093c25efa719422f1d165ed137860b9b45ee Mon Sep 17 00:00:00 2001 From: sebi06 <3833249+sebi06@users.noreply.github.com> Date: Wed, 8 Feb 2023 18:58:25 +0100 Subject: [PATCH 8/9] updated tests --- src/czitools/_tests/test_info.py | 71 ++++--- src/czitools/_tests/test_instrument.py | 117 ++++++----- src/czitools/_tests/test_md_general.py | 93 ++++----- src/czitools/_tests/test_read_mdarray.py | 239 +++++++++-------------- 4 files changed, 232 insertions(+), 288 deletions(-) diff --git a/src/czitools/_tests/test_info.py b/src/czitools/_tests/test_info.py index be97e26..369c9bf 100644 --- a/src/czitools/_tests/test_info.py +++ b/src/czitools/_tests/test_info.py @@ -1,43 +1,40 @@ from czitools import pylibczirw_metadata as czimd from pathlib import Path +import pytest +from typing import List, Dict, Tuple, Optional, Type, Any, Union, Mapping basedir = Path(__file__).resolve().parents[3] -def test_information(): - - to_test = {0: r"data/CellDivision_T=3_Z=5_CH=2_X=240_Y=170.czi", - 1: r"data/Airyscan.czi", - 2: r"data/newCZI_zloc.czi"} - - results = {0: ['ZEN 3.2 (blue edition)', - '3.2.0.00001', - '2016-02-12T09:41:02.4915604Z', - True, - 'CellDivision_T=3_Z=5_CH=2_X=240_Y=170.czi'], - 1: ['ZEN (blue edition)', - '3.5.093.00000', - None, - True, - 'Airyscan.czi'], - 2: ['pylibCZIrw', - '3.2.1', - None, - True, - 'newCZI_zloc.czi'] - } - - for t in range(len(to_test)): - - # get the filepath - filepath = basedir / to_test[t] - md = czimd.CziMetadata(filepath) - - assert (md.software_name == results[t][0]) - assert (md.software_version == results[t][1]) - assert (md.acquisition_date == results[t][2]) - assert (Path.exists(Path(md.dirname)) == results[t][3]) - assert (md.filename == results[t][4]) - - -test_information() +@pytest.mark.parametrize( + "czifile, result", + [ + (r"data/CellDivision_T=3_Z=5_CH=2_X=240_Y=170.czi", ['ZEN 3.2 (blue edition)', + '3.2.0.00001', + '2016-02-12T09:41:02.4915604Z', + True, + 'CellDivision_T=3_Z=5_CH=2_X=240_Y=170.czi']), + + (r"data/Airyscan.czi", ['ZEN (blue edition)', + '3.5.093.00000', + None, + True, + 'Airyscan.czi']), + + (r"data/newCZI_zloc.czi", ['pylibCZIrw', + '3.2.1', + None, + True, + 'newCZI_zloc.czi']) + ] +) +def test_information(czifile: str, result: List) -> None: + + filepath = basedir / czifile + md = czimd.CziMetadata(filepath) + + assert (md.software_name == result[0]) + assert (md.software_version == result[1]) + assert (md.acquisition_date == result[2]) + assert (Path.exists(Path(md.dirname)) == result[3]) + assert (md.filename == result[4]) diff --git a/src/czitools/_tests/test_instrument.py b/src/czitools/_tests/test_instrument.py index 16e4863..2bf36be 100644 --- a/src/czitools/_tests/test_instrument.py +++ b/src/czitools/_tests/test_instrument.py @@ -1,70 +1,89 @@ from czitools import pylibczirw_metadata as czimd from pathlib import Path +import pytest +from typing import List, Dict, Tuple, Optional, Type, Any, Union, Mapping basedir = Path(__file__).resolve().parents[3] -def test_instrument(): +@pytest.mark.parametrize( + "czifile, result_mic, result_det", + [ + (r"data/CellDivision_T=3_Z=5_CH=2_X=240_Y=170.czi", + ['Microscope:1', 'Castor.Stand'], + [['Detector:Axiocam 506'], [None], [None], ['Axiocam 506']]), - to_test = {0: r"data/CellDivision_T=3_Z=5_CH=2_X=240_Y=170.czi", - 1: r"data/Airyscan.czi", - 2: r"data/newCZI_zloc.czi"} + (r"data/Airyscan.czi", + ['Microscope:0', None], + [['Detector:0:0', 'Detector:1:0'], [None, None], ['Airyscan', 'Airyscan'], [None, None]]), - results_mic = {0: ['Microscope:1', 'Castor.Stand'], - 1: ['Microscope:0', None], - 2: [None, None] - } + (r"data/newCZI_zloc.czi", + [None, None], + [[None], [None], [None], [None]]), - results_det = {0: [['Detector:Axiocam 506'], [None], [None], ['Axiocam 506']], - 1: [['Detector:0:0', 'Detector:1:0'], [None, None], ['Airyscan', 'Airyscan'], [None, None]], - 2: [[None], [None], [None], [None]] - } + ] +) +def test_instrument(czifile: str, result_mic: Dict, result_det: Dict) -> None: - for t in range(len(to_test)): + # get the filepath and the metadata + filepath = basedir / czifile + mic = czimd.CziMicroscope(filepath) + det = czimd.CziDetector(filepath) - # get the filepath and the metadata - filepath = basedir / to_test[t] - mic = czimd.CziMicroscope(filepath) - det = czimd.CziDetector(filepath) + # check the microscope data + assert (mic.ID == result_mic[0]) + assert (mic.Name == result_mic[1]) - # check the microscope data - assert (mic.ID == results_mic[t][0]) - assert (mic.Name == results_mic[t][1]) + # check the detector(s) data + assert (det.ID == result_det[0]) + assert (det.model == result_det[1]) + assert (det.modeltype == result_det[2]) + assert (det.name == result_det[3]) - # check the detector(s) data - assert (det.ID == results_det[t][0]) - assert (det.model == results_det[t][1]) - assert (det.modeltype == results_det[t][2]) - assert (det.name == results_det[t][3]) +@pytest.mark.parametrize( + "czifile, result", + [ + (r"data/CellDivision_T=3_Z=5_CH=2_X=240_Y=170.czi", {'name': 'Plan-Apochromat 50x/1.2', + 'immersion': 'Water', + 'NA': 1.2, + 'ID': 'Objective:1', + 'objmag': 50.0, + 'tubelensmag': 1.0, + 'totalmag': 50.0}), -def test_objectives(): + (r"data/Al2O3_SE_020_sp.czi", {}), - to_test = {0: r"data/CellDivision_T=3_Z=5_CH=2_X=240_Y=170.czi", - 1: r"data/Al2O3_SE_020_sp.czi", - 2: r"data/w96_A1+A2.czi", - 3: r"data/Airyscan.czi", - 4: r"data/newCZI_zloc.czi", - 5: r"data/FOV7_HV110_P0500510000.czi", - 6: r"data/Tumor_HE_RGB.czi" - } + (r"data/w96_A1+A2.czi", {'name': 'Plan-Apochromat 20x/0.95', + 'immersion': 'Air', + 'NA': 0.95, + 'ID': 'Objective:1', + 'objmag': 20.0, + 'tubelensmag': 0.5, + 'totalmag': 10.0}), - results = {0: {'name': 'Plan-Apochromat 50x/1.2', 'immersion': 'Water', 'NA': 1.2, 'ID': 'Objective:1', 'objmag': 50.0, 'tubelensmag': 1.0, 'totalmag': 50.0}, - 1: {}, - 2: {'name': 'Plan-Apochromat 20x/0.95', 'immersion': 'Air', 'NA': 0.95, 'ID': 'Objective:1', 'objmag': 20.0, 'tubelensmag': 0.5, 'totalmag': 10.0}, - 3: {'name': 'Plan-Apochromat 63x/1.4 Oil DIC M27', 'immersion': 'Oil', 'NA': 1.4000000000000001, 'ID': 'Objective:0', 'objmag': 63.0, 'totalmag': 63.0}, - 4: {}, - 5: {}, - 6: {}, - } + (r"data/Airyscan.czi", {'name': 'Plan-Apochromat 63x/1.4 Oil DIC M27', + 'immersion': 'Oil', + 'NA': 1.4000000000000001, + 'ID': 'Objective:0', + 'objmag': 63.0, + 'totalmag': 63.0}), - for t in range(len(to_test)): + (r"data/newCZI_zloc.czi", {}), - # get the filepath and the metadata - filepath = basedir / to_test[t] - obj = czimd.CziObjectives(filepath) + (r"data/FOV7_HV110_P0500510000.czi", {}), - out = obj.__dict__ - del out['czisource'] + (r"data/Tumor_HE_RGB.czi", {}) - assert(out == results[t]) + ] +) +def test_objectives(czifile: str, result: Dict) -> None: + + # get the filepath and the metadata + filepath = basedir / czifile + obj = czimd.CziObjectives(filepath) + + out = obj.__dict__ + del out['czisource'] + + assert (out == result) diff --git a/src/czitools/_tests/test_md_general.py b/src/czitools/_tests/test_md_general.py index 634baba..02166ca 100644 --- a/src/czitools/_tests/test_md_general.py +++ b/src/czitools/_tests/test_md_general.py @@ -3,11 +3,14 @@ from pylibCZIrw import czi as pyczi from pathlib import Path import numpy as np +import pytest +from typing import List, Dict, Tuple, Optional, Type, Any, Union, Mapping + basedir = Path(__file__).resolve().parents[3] -def test_pixeltypes(): +def test_pixeltypes_1() -> None: # get the CZI filepath filepath = basedir / r"data/Tumor_HE_RGB.czi" @@ -20,41 +23,29 @@ def test_pixeltypes(): assert (md.pixeltypes == {0: 'Bgr24'}) assert (md.isRGB is True) - # check the function to get npdtypes and maxvalues - - pts = ["gray16", - "Gray16", - "gray8", - "Gray8", - "bgr48", - "Bgr48", - "bgr24", - "Bgr24", - "bgr96float", - "Bgr96Float", - "abc", - None] - - dts = [np.dtype(np.uint16), - np.dtype(np.uint16), - np.dtype(np.uint8), - np.dtype(np.uint8), - np.dtype(np.uint16), - np.dtype(np.uint16), - np.dtype(np.uint8), - np.dtype(np.uint8), - np.dtype(np.uint16), - np.dtype(np.uint16), - None, - None] - - mvs = [65535, 65535, 255, 255, 65535, 65535, 255, 255, 65535, 65535, None, None] - - for pt, dt, mv in zip(pts, dts, mvs): - - out = czimd.CziMetadata.get_dtype_fromstring(pt) - assert (out[0] == dt) - assert (out[1] == mv) + +@pytest.mark.parametrize( + "pts, dts, mvs", + [ + ("gray16", np.dtype(np.uint16), 65535), + ("Gray16", np.dtype(np.uint16), 65535), + ("gray8", np.dtype(np.uint8), 255), + ("Gray8", np.dtype(np.uint8), 255), + ("bgr48", np.dtype(np.uint16), 65535), + ("Bgr48", np.dtype(np.uint16), 65535), + ("bgr24", np.dtype(np.uint8), 255), + ("Bgr24", np.dtype(np.uint8), 255), + ("bgr96float", np.dtype(np.uint16), 65535), + ("Bgr96Float", np.dtype(np.uint16), 65535), + ("abc", None, None), + (None, None, None) + ] +) +def test_pixeltypes_2(pts: str, dts: np.dtype, mvs: int) -> None: + + out = czimd.CziMetadata.get_dtype_fromstring(pts) + assert (out[0] == dts) + assert (out[1] == mvs) def test_dimorder(): @@ -69,23 +60,20 @@ def test_dimorder(): assert (md.aics_dim_valid == 8) -def test_scene_shape(): - - files = [r"data/S=3_1Pos_2Mosaic_T=2=Z=3_CH=2_sm.czi", - r"data/CellDivision_T=3_Z=5_CH=2_X=240_Y=170.czi", - r"data/WP96_4Pos_B4-10_DAPI.czi"] - - shapes = [False, True, True] - - for file, sc in zip(files, shapes): +@pytest.mark.parametrize( + "czifile, shape", + [ + (r"data/S=3_1Pos_2Mosaic_T=2=Z=3_CH=2_sm.czi", False), + (r"data/CellDivision_T=3_Z=5_CH=2_X=240_Y=170.czi", True), + (r"data/WP96_4Pos_B4-10_DAPI.czi", True) + ] +) +def test_scene_shape(czifile: str, shape: bool) -> None: - # get the CZI filepath - filepath = basedir / file - - # get the complete metadata at once as one big class - md = czimd.CziMetadata(filepath) + # get the complete metadata at once as one big class + md = czimd.CziMetadata(czifile) - assert (md.scene_shape_is_consistent == sc) + assert (md.scene_shape_is_consistent == shape) def test_reading_czi_fresh(): @@ -105,6 +93,3 @@ def test_reading_czi_fresh(): assert (mdata.sample.scene_stageY == []) assert (mdata.sample.image_stageX is None) assert (mdata.sample.image_stageY is None) - - -test_scene_shape() diff --git a/src/czitools/_tests/test_read_mdarray.py b/src/czitools/_tests/test_read_mdarray.py index b988329..391c8de 100644 --- a/src/czitools/_tests/test_read_mdarray.py +++ b/src/czitools/_tests/test_read_mdarray.py @@ -2,176 +2,119 @@ from pathlib import Path import dask.array as da import numpy as np +import pytest +from typing import List, Dict, Tuple, Optional, Type, Any, Union, Mapping -basedir = Path(__file__).resolve().parents[3] - - -def test_read_mdarray_1(): - - # get the CZI filepath - filepath = basedir / "data" / "w96_A1+A2.czi" - - mdarray, mdata, dimstring = pylibczirw_tools.read_6darray(filepath, - output_order="STCZYX", - output_dask=False, - chunks_auto=False, - remove_adim=False) - - assert (dimstring == "STCZYXA") - assert (mdarray.shape == (2, 1, 2, 1, 1416, 1960, 1)) - - mdarray, mdata, dimstring = pylibczirw_tools.read_6darray(filepath, - output_order="STZCYX", - output_dask=False, - chunks_auto=False, - remove_adim=True) - - assert (dimstring == "STZCYX") - assert (mdarray.shape == (2, 1, 1, 2, 1416, 1960)) - - # get the CZI filepath - filepath = basedir / "data" / "S=3_1Pos_2Mosaic_T=2=Z=3_CH=2_sm.czi" - - mdarray, mdata, dimstring = pylibczirw_tools.read_6darray(filepath, - output_order="STCZYX", - output_dask=False, - chunks_auto=False, - remove_adim=False) - - assert (mdarray is None) - assert (mdata == mdata) - assert (dimstring == "") - - -def test_read_mdarray_2(): - - # get the CZI filepath - filepath = basedir / r"data/S=2_3x3_CH=2.czi" - mdarray, mdata, dimstring = pylibczirw_tools.read_6darray(filepath, - output_dask=False, - chunks_auto=False, - output_order="STZCYX", - remove_adim=False) - - assert (dimstring == "STZCYXA") - assert (mdarray.shape == (2, 1, 1, 2, 1792, 1792, 1)) +basedir = Path(__file__).resolve().parents[3] - mdarray, mdata, dimstring = pylibczirw_tools.read_6darray(filepath, - output_dask=False, - chunks_auto=False, - output_order="STCZYX", - remove_adim=True) - assert (dimstring == "STCZYX") - assert (mdarray.shape == (2, 1, 2, 1, 1792, 1792)) +@pytest.mark.parametrize( + "czifile, output_dask, remove_adim, dimorder, dimstring, shape, art", + [ + ("w96_A1+A2.czi", False, False, "STCZYX", "STCZYXA", (2, 1, 2, 1, 1416, 1960, 1), np.ndarray), + ("w96_A1+A2.czi", False, True, "STZCYX", "STZCYX", (2, 1, 1, 2, 1416, 1960), np.ndarray), -def test_read_mdarray_3(): + ("S=3_1Pos_2Mosaic_T=2=Z=3_CH=2_sm.czi", False, False, "STCZYX", "", AttributeError, None), - # get the CZI filepath - filepath = basedir / r"data/FOV7_HV110_P0500510000.czi" + ("S=2_3x3_CH=2.czi", False, False, "STZCYX", "STZCYXA", (2, 1, 1, 2, 1792, 1792, 1), np.ndarray), - mdarray, mdata, dimstring = pylibczirw_tools.read_6darray(filepath, - output_dask=False, - chunks_auto=False, - output_order="STZCYX", - remove_adim=False) + ("S=2_3x3_CH=2.czi", False, True, "STCZYX", "STCZYX", (2, 1, 2, 1, 1792, 1792), np.ndarray), - assert (dimstring == "STZCYXA") - assert (mdarray.shape == (1, 1, 1, 1, 512, 512, 1)) + ("FOV7_HV110_P0500510000.czi", False, False, "STZCYX", + "STZCYXA", (1, 1, 1, 1, 512, 512, 1), np.ndarray), - -def test_read_mdarray_4(): + ("newCZI_compressed.czi", True, True, "STZCYX", "STZCYX", (1, 1, 1, 1, 512, 512), da.Array) + ] +) +def test_read_mdarray_1(czifile: str, + output_dask: bool, + remove_adim: bool, + dimorder: str, + dimstring: str, + shape: Optional[Tuple[int]], + art: Optional[Union[np.ndarray, da.array]]) -> None: # get the CZI filepath - filepath = basedir / r"data/newCZI_compressed.czi" - - mdarray, mdata, dimstring = pylibczirw_tools.read_6darray(filepath, - output_dask=True, - chunks_auto=False, - output_order="STZCYX", - remove_adim=True) - - assert (type(mdarray == da.array)) - assert (dimstring == "STZCYX") - assert (mdarray.shape == (1, 1, 1, 1, 512, 512)) - - -def test_read_mdarray_lazy_1(): + filepath = basedir / "data" / czifile + + mdarray, mdata, ds = pylibczirw_tools.read_6darray(filepath, + output_order=dimorder, + output_dask=output_dask, + chunks_auto=False, + remove_adim=remove_adim) + + assert (ds == dimstring) + + if type(shape) == type and issubclass(shape, Exception): + with pytest.raises(shape): + mdarray.shape + else: + assert (mdarray.shape == shape) + assert (type(mdarray) == art) + + +@pytest.mark.parametrize( + "czifile, dimstring, remove_adim, shape, ndim, chunksize", + [ + ("w96_A1+A2.czi", "STZCYXA", False, (2, 1, 1, 2, 1416, 1960, 1), 7, (1, 1, 1, 2, 1416, 1960, 1)), + + ("w96_A1+A2.czi", "STZCYX", True, (2, 1, 1, 2, 1416, 1960), 6, (1, 1, 1, 2, 1416, 1960)) + ] +) +def test_read_mdarray_lazy_1(czifile: str, + dimstring: str, + remove_adim: bool, + shape: Tuple[int], + ndim: int, + chunksize: Tuple[int]) -> None: # get the CZI filepath - filepath = basedir / r"data/w96_A1+A2.czi" + filepath = basedir / "data" / czifile - mdarray, mdata, dimstring = pylibczirw_tools.read_mdarray_lazy(filepath, remove_adim=False) + mdarray, mdata, ds = pylibczirw_tools.read_mdarray_lazy(filepath, remove_adim=remove_adim) - assert (dimstring == "STZCYXA") - assert (mdarray.shape == (2, 1, 1, 2, 1416, 1960, 1)) - assert (mdarray.ndim == 7) - assert (mdarray.chunksize == (1, 1, 1, 2, 1416, 1960, 1)) + assert (ds == dimstring) + assert (mdarray.shape == shape) + assert (mdarray.ndim == ndim) + assert (mdarray.chunksize == chunksize) - mdarray, mdata, dimstring = pylibczirw_tools.read_mdarray_lazy(filepath, remove_adim=True) - assert (dimstring == "STZCYX") - assert (mdarray.shape == (2, 1, 1, 2, 1416, 1960)) - assert (mdarray.ndim == 6) - assert (mdarray.chunksize == (1, 1, 1, 2, 1416, 1960)) +@pytest.mark.parametrize( + "czifile, dimorder, output_dask, dimstring, shape, size_s, plane", + [ + ("w96_A1+A2.czi", "STCZYX", False, "STCZYXA", (1, 1, 2, 1, 1416, 1960, 1), 1, {"S": 0}), + ("CellDivision_T=3_Z=5_CH=2_X=240_Y=170.czi", "STZCYX", True, + "STZCYXA", (1, 1, 1, 2, 170, 240, 1), None, {"S": 0, "T": 0, "Z": 0}), -def test_read_mdarray_substack(): + ("CellDivision_T=3_Z=5_CH=2_X=240_Y=170.czi", "STZCYX", + True, "STZCYXA", (1, 1, 5, 2, 170, 240, 1), None, {"T": 0}), - # get the CZI filepath - filepath = basedir / r"data/w96_A1+A2.czi" - - # read only a specific scene from the CZI - mdarray, mdata, dimstring = pylibczirw_tools.read_6darray(filepath, - output_order="STCZYX", - output_dask=False, - chunks_auto=False, - remove_adim=False, - S=0) - - assert (dimstring == "STCZYXA") - assert (mdarray.shape == (1, 1, 2, 1, 1416, 1960, 1)) - assert (mdata.image.SizeS == 1) + ("CellDivision_T=3_Z=5_CH=2_X=240_Y=170.czi", "STZCYX", + True, "STZCYXA", (1, 3, 5, 1, 170, 240, 1), None, {"C": 0}) + ] +) +def test_read_mdarray_substack(czifile: str, + dimorder: str, + output_dask: bool, + dimstring: str, + shape: Tuple[int], + size_s: Optional[int], + plane: Dict[str, int]) -> None: # get the CZI filepath - filepath = basedir / r"data/CellDivision_T=3_Z=5_CH=2_X=240_Y=170.czi" - - # read only a specific scene from the CZI - mdarray, mdata, dimstring = pylibczirw_tools.read_6darray(filepath, - output_order="STZCYX", - output_dask=True, - chunks_auto=False, - remove_adim=False, - S=0, - T=0, - Z=0) - - assert (dimstring == "STZCYXA") - assert (mdarray.shape == (1, 1, 1, 2, 170, 240, 1)) - assert (mdata.image.SizeS is None) - - # read only a specific scene from the CZI - mdarray, mdata, dimstring = pylibczirw_tools.read_6darray(filepath, - output_order="STZCYX", - output_dask=False, - chunks_auto=False, - remove_adim=True, - T=0) - - assert (dimstring == "STZCYX") - assert (mdarray.shape == (1, 1, 5, 2, 170, 240)) - assert (mdata.image.SizeS is None) + filepath = basedir / "data" / czifile # read only a specific scene from the CZI - mdarray, mdata, dimstring = pylibczirw_tools.read_6darray(filepath, - output_order="STZCYX", - output_dask=False, - chunks_auto=False, - remove_adim=True, - C=0) - - assert (dimstring == "STZCYX") - assert (mdarray.shape == (1, 3, 5, 1, 170, 240)) - assert (mdata.image.SizeS is None) + mdarray, mdata, ds = pylibczirw_tools.read_6darray(filepath, + output_order=dimorder, + output_dask=output_dask, + chunks_auto=False, + remove_adim=False, + **plane) + + assert (ds == dimstring) + assert (mdarray.shape == shape) + assert (mdata.image.SizeS == size_s) From 86b8a48954a57fc5cd25b92f8c90ce56e8c68079 Mon Sep 17 00:00:00 2001 From: sebi06 Date: Thu, 9 Feb 2023 08:53:19 +0100 Subject: [PATCH 9/9] Updated some tests with pytest.mark.parametrize --- src/czitools/_tests/test_channelinfo.py | 69 +++++++++++++------------ src/czitools/_tests/test_dimensions.py | 26 ++++++---- src/czitools/_tests/test_info.py | 8 +-- src/czitools/_tests/test_instrument.py | 24 ++++----- src/czitools/_tests/test_md_general.py | 12 +++-- src/czitools/_tests/test_misc.py | 6 ++- src/czitools/_tests/test_scaling.py | 4 +- src/czitools/_tests/test_writing.py | 7 +-- 8 files changed, 87 insertions(+), 69 deletions(-) diff --git a/src/czitools/_tests/test_channelinfo.py b/src/czitools/_tests/test_channelinfo.py index 0de0078..1da2e4c 100644 --- a/src/czitools/_tests/test_channelinfo.py +++ b/src/czitools/_tests/test_channelinfo.py @@ -1,38 +1,43 @@ from pathlib import Path from czitools import pylibczirw_metadata as czimd - -basedir = Path(__file__).resolve().parents[3] - - -def test_channelinfo(): - - # get the CZI filepath - filepath = basedir / r"data/CellDivision_T=3_Z=5_CH=2_X=240_Y=170.czi" - czi_channels = czimd.CziChannelInfo(filepath) - - assert (czi_channels.clims == [[0.0, 0.05983062485694667], [0.0, 0.24975967040512703]]) - assert (czi_channels.colors == ["#FFFF7E00", "#FF00FF33"]) - assert (czi_channels.gamma == [0.7999999999999998, 0.7999999999999998]) - assert (czi_channels.names == ["LED555", "LED470"]) - assert (czi_channels.dyes == ["AF555", "AF488"]) - - # get the CZI filepath - filepath = basedir / r"data/Al2O3_SE_020_sp.czi" - czi_channels = czimd.CziChannelInfo(filepath) - - assert (czi_channels.clims == [[0.0, 0.5]]) - assert (czi_channels.colors == ["#80808000"]) - assert (czi_channels.gamma == [0.85]) - assert (czi_channels.names == ["CH1"]) - assert (czi_channels.dyes == ["Dye-CH1"]) +import pytest +from typing import List, Dict, Tuple, Optional, Type, Any, Union, Mapping + +basedir = Path(__file__).resolve().parents[3] / "data" + +@pytest.mark.parametrize( + "czifile, clims, colors, gamma, names, dyes", + [ + ("CellDivision_T=3_Z=5_CH=2_X=240_Y=170.czi", + [[0.0, 0.05983062485694667],[0.0, 0.24975967040512703]], + ["#FFFF7E00", "#FF00FF33"], + [0.7999999999999998, 0.7999999999999998], + ["LED555", "LED470"], + ["AF555", "AF488"]), + + ("Al2O3_SE_020_sp.czi", + [[0.0, 0.5]], + ["#80808000"], + [0.85], + ["CH1"], + ["Dye-CH1"]), + + ("w96_A1+A2.czi", + [[0.000871455799315693, 0.044245974575704575], [0.000881881329185286, 0.05011349562051524]], + ['#FFFF1800', '#FF00FF33'], + [0.7999999999999998, 0.7999999999999998], + ['AF568', 'AF488'], + ['AF568', 'AF488']) + ] +) +def test_channelinfo(czifile: str, clims: List, colors: List, gamma: List, names: List, dyes: List) -> None: # get the CZI filepath - filepath = basedir / r"data/w96_A1+A2.czi" + filepath = basedir / czifile czi_channels = czimd.CziChannelInfo(filepath) - assert (czi_channels.clims == [[0.000871455799315693, 0.044245974575704575], [ - 0.000881881329185286, 0.05011349562051524]]) - assert (czi_channels.colors == ['#FFFF1800', '#FF00FF33']) - assert (czi_channels.gamma == [0.7999999999999998, 0.7999999999999998]) - assert (czi_channels.names == ['AF568', 'AF488']) - assert (czi_channels.dyes == ['AF568', 'AF488']) + assert (czi_channels.clims == clims) + assert (czi_channels.colors == colors) + assert (czi_channels.gamma == gamma) + assert (czi_channels.names == names) + assert (czi_channels.dyes == dyes) diff --git a/src/czitools/_tests/test_dimensions.py b/src/czitools/_tests/test_dimensions.py index 90e210e..94b1e1d 100644 --- a/src/czitools/_tests/test_dimensions.py +++ b/src/czitools/_tests/test_dimensions.py @@ -1,14 +1,20 @@ from czitools import pylibczirw_metadata as czimd from pathlib import Path +import pytest +from typing import List, Dict, Tuple, Optional, Type, Any, Union, Mapping basedir = Path(__file__).resolve().parents[3] -# get the CZI filepath -filepath = basedir / r"data/CellDivision_T=3_Z=5_CH=2_X=240_Y=170.czi" +@pytest.mark.parametrize( + "czifile, dimension", + [ + ("CellDivision_T=3_Z=5_CH=2_X=240_Y=170.czi", [None, 3, 5, 2, 170, 240]) + ] +) +def test_dimensions(czifile: str, dimension: List[Any]) -> None: - -def test_dimensions(): + filepath = basedir / "data" / czifile czi_dimensions = czimd.CziDimensions(filepath) print("SizeS: ", czi_dimensions.SizeS) @@ -18,9 +24,9 @@ def test_dimensions(): print("SizeY: ", czi_dimensions.SizeY) print("SizeX: ", czi_dimensions.SizeX) - assert (czi_dimensions.SizeS is None) - assert (czi_dimensions.SizeT == 3) - assert (czi_dimensions.SizeZ == 5) - assert (czi_dimensions.SizeC == 2) - assert (czi_dimensions.SizeY == 170) - assert (czi_dimensions.SizeX == 240) + assert (czi_dimensions.SizeS == dimension[0]) + assert (czi_dimensions.SizeT == dimension[1]) + assert (czi_dimensions.SizeZ == dimension[2]) + assert (czi_dimensions.SizeC == dimension[3]) + assert (czi_dimensions.SizeY == dimension[4]) + assert (czi_dimensions.SizeX == dimension[5]) diff --git a/src/czitools/_tests/test_info.py b/src/czitools/_tests/test_info.py index 369c9bf..9adf2a6 100644 --- a/src/czitools/_tests/test_info.py +++ b/src/czitools/_tests/test_info.py @@ -9,19 +9,19 @@ @pytest.mark.parametrize( "czifile, result", [ - (r"data/CellDivision_T=3_Z=5_CH=2_X=240_Y=170.czi", ['ZEN 3.2 (blue edition)', + ("CellDivision_T=3_Z=5_CH=2_X=240_Y=170.czi", ['ZEN 3.2 (blue edition)', '3.2.0.00001', '2016-02-12T09:41:02.4915604Z', True, 'CellDivision_T=3_Z=5_CH=2_X=240_Y=170.czi']), - (r"data/Airyscan.czi", ['ZEN (blue edition)', + ("Airyscan.czi", ['ZEN (blue edition)', '3.5.093.00000', None, True, 'Airyscan.czi']), - (r"data/newCZI_zloc.czi", ['pylibCZIrw', + ("newCZI_zloc.czi", ['pylibCZIrw', '3.2.1', None, True, @@ -30,7 +30,7 @@ ) def test_information(czifile: str, result: List) -> None: - filepath = basedir / czifile + filepath = basedir / "data" / czifile md = czimd.CziMetadata(filepath) assert (md.software_name == result[0]) diff --git a/src/czitools/_tests/test_instrument.py b/src/czitools/_tests/test_instrument.py index 2bf36be..c96333d 100644 --- a/src/czitools/_tests/test_instrument.py +++ b/src/czitools/_tests/test_instrument.py @@ -9,15 +9,15 @@ @pytest.mark.parametrize( "czifile, result_mic, result_det", [ - (r"data/CellDivision_T=3_Z=5_CH=2_X=240_Y=170.czi", + ("CellDivision_T=3_Z=5_CH=2_X=240_Y=170.czi", ['Microscope:1', 'Castor.Stand'], [['Detector:Axiocam 506'], [None], [None], ['Axiocam 506']]), - (r"data/Airyscan.czi", + ("Airyscan.czi", ['Microscope:0', None], [['Detector:0:0', 'Detector:1:0'], [None, None], ['Airyscan', 'Airyscan'], [None, None]]), - (r"data/newCZI_zloc.czi", + ("newCZI_zloc.czi", [None, None], [[None], [None], [None], [None]]), @@ -26,7 +26,7 @@ def test_instrument(czifile: str, result_mic: Dict, result_det: Dict) -> None: # get the filepath and the metadata - filepath = basedir / czifile + filepath = basedir / "data" / czifile mic = czimd.CziMicroscope(filepath) det = czimd.CziDetector(filepath) @@ -44,7 +44,7 @@ def test_instrument(czifile: str, result_mic: Dict, result_det: Dict) -> None: @pytest.mark.parametrize( "czifile, result", [ - (r"data/CellDivision_T=3_Z=5_CH=2_X=240_Y=170.czi", {'name': 'Plan-Apochromat 50x/1.2', + ("CellDivision_T=3_Z=5_CH=2_X=240_Y=170.czi", {'name': 'Plan-Apochromat 50x/1.2', 'immersion': 'Water', 'NA': 1.2, 'ID': 'Objective:1', @@ -52,9 +52,9 @@ def test_instrument(czifile: str, result_mic: Dict, result_det: Dict) -> None: 'tubelensmag': 1.0, 'totalmag': 50.0}), - (r"data/Al2O3_SE_020_sp.czi", {}), + ("Al2O3_SE_020_sp.czi", {}), - (r"data/w96_A1+A2.czi", {'name': 'Plan-Apochromat 20x/0.95', + ("w96_A1+A2.czi", {'name': 'Plan-Apochromat 20x/0.95', 'immersion': 'Air', 'NA': 0.95, 'ID': 'Objective:1', @@ -62,25 +62,25 @@ def test_instrument(czifile: str, result_mic: Dict, result_det: Dict) -> None: 'tubelensmag': 0.5, 'totalmag': 10.0}), - (r"data/Airyscan.czi", {'name': 'Plan-Apochromat 63x/1.4 Oil DIC M27', + ("Airyscan.czi", {'name': 'Plan-Apochromat 63x/1.4 Oil DIC M27', 'immersion': 'Oil', 'NA': 1.4000000000000001, 'ID': 'Objective:0', 'objmag': 63.0, 'totalmag': 63.0}), - (r"data/newCZI_zloc.czi", {}), + ("newCZI_zloc.czi", {}), - (r"data/FOV7_HV110_P0500510000.czi", {}), + ("FOV7_HV110_P0500510000.czi", {}), - (r"data/Tumor_HE_RGB.czi", {}) + ("Tumor_HE_RGB.czi", {}) ] ) def test_objectives(czifile: str, result: Dict) -> None: # get the filepath and the metadata - filepath = basedir / czifile + filepath = basedir / "data" / czifile obj = czimd.CziObjectives(filepath) out = obj.__dict__ diff --git a/src/czitools/_tests/test_md_general.py b/src/czitools/_tests/test_md_general.py index 02166ca..6f8a4e1 100644 --- a/src/czitools/_tests/test_md_general.py +++ b/src/czitools/_tests/test_md_general.py @@ -63,15 +63,19 @@ def test_dimorder(): @pytest.mark.parametrize( "czifile, shape", [ - (r"data/S=3_1Pos_2Mosaic_T=2=Z=3_CH=2_sm.czi", False), - (r"data/CellDivision_T=3_Z=5_CH=2_X=240_Y=170.czi", True), - (r"data/WP96_4Pos_B4-10_DAPI.czi", True) + ("S=3_1Pos_2Mosaic_T=2=Z=3_CH=2_sm.czi", False), + ("CellDivision_T=3_Z=5_CH=2_X=240_Y=170.czi", True), + ("WP96_4Pos_B4-10_DAPI.czi", True) ] ) def test_scene_shape(czifile: str, shape: bool) -> None: + filepath = basedir / "data" / czifile + + assert (Path.exists(filepath) is True) + # get the complete metadata at once as one big class - md = czimd.CziMetadata(czifile) + md = czimd.CziMetadata(filepath) assert (md.scene_shape_is_consistent == shape) diff --git a/src/czitools/_tests/test_misc.py b/src/czitools/_tests/test_misc.py index c4fbe03..32913c0 100644 --- a/src/czitools/_tests/test_misc.py +++ b/src/czitools/_tests/test_misc.py @@ -2,6 +2,8 @@ from pathlib import Path import dask.array as da import pandas as pd +import pytest +from typing import List, Dict, Tuple, Optional, Type, Any, Union, Mapping basedir = Path(__file__).resolve().parents[3] @@ -9,7 +11,7 @@ def test_slicedim(): # get the CZI filepath - filepath = basedir / r"data/CellDivision_T=3_Z=5_CH=2_X=240_Y=170.czi" + filepath = basedir / "data" / "CellDivision_T=3_Z=5_CH=2_X=240_Y=170.czi" mdarray, mdata, dimstring = pylibczirw_tools.read_6darray(filepath, output_dask=False, @@ -51,7 +53,7 @@ def test_slicedim(): def test_get_planetable(): # get the CZI filepath - filepath = (basedir / r"data/WP96_4Pos_B4-10_DAPI.czi").as_posix() + filepath = (basedir / "data" / "WP96_4Pos_B4-10_DAPI.czi").as_posix() isczi = False iscsv = False diff --git a/src/czitools/_tests/test_scaling.py b/src/czitools/_tests/test_scaling.py index 32e2250..426875f 100644 --- a/src/czitools/_tests/test_scaling.py +++ b/src/czitools/_tests/test_scaling.py @@ -6,7 +6,7 @@ basedir = Path(__file__).resolve().parents[3] # get the CZI filepath -filepath = basedir / r"data/CellDivision_T=3_Z=5_CH=2_X=240_Y=170.czi" +filepath = basedir / "data" / "CellDivision_T=3_Z=5_CH=2_X=240_Y=170.czi" def test_scaling1(): @@ -79,7 +79,7 @@ def test_safe_get_scale(): def test_scaling2(): # get the CZI filepath - filepath = basedir / r"data/DAPI_GFP.czi" + filepath = basedir / "data" / "DAPI_GFP.czi" md = czimd.CziMetadata(filepath) assert(md.scale.X == 1.0) diff --git a/src/czitools/_tests/test_writing.py b/src/czitools/_tests/test_writing.py index 39746c3..ec94a1e 100644 --- a/src/czitools/_tests/test_writing.py +++ b/src/czitools/_tests/test_writing.py @@ -11,7 +11,8 @@ basedir = Path(__file__).resolve().parents[3] # get some data to write -filepath = basedir / r"data/z=16_ch=3.czi" +filepath = basedir / "data" / "z=16_ch=3.czi" + mdarray, mdata, dimstring = pylibczirw_tools.read_6darray(filepath, output_dask=False, chunks_auto=False, @@ -24,8 +25,8 @@ def test_write_1(): # get the CZI filepath - filepath1 = basedir / r"data/CH=1_16bit.tif" - filepath2 = basedir / r"data/Fluorescence_RGB.tif" + filepath1 = basedir / "data" / "CH=1_16bit.tif" + filepath2 = basedir / r"data" / "Fluorescence_RGB.tif" files = [filepath1, filepath2] sps = [1, 3]