pycorrfit-1.1.7/0000775000372000037200000000000013554643106014412 5ustar travistravis00000000000000pycorrfit-1.1.7/MANIFEST.in0000664000372000037200000000052113554642611016146 0ustar travistravis00000000000000include CHANGELOG include LICENSE include README.rst recursive-include examples *.txt recursive-include doc *.tex *.bib *.pdf *.md *.png *.svg recursive-include docs *.py *.md *.txt *.rst *.bib *.gif *.jpg *.png recursive-include tests *.py *.md recursive-include pycorrfit LICENCE README prune docs/_build exclude docs/_version_save.py pycorrfit-1.1.7/examples/0000775000372000037200000000000013554643106016230 5ustar travistravis00000000000000pycorrfit-1.1.7/examples/external_model_functions/0000775000372000037200000000000013554643106023322 5ustar travistravis00000000000000pycorrfit-1.1.7/examples/external_model_functions/ExampleFunc_Exp_correlated_noise.txt0000775000372000037200000000050413554642611032511 0ustar travistravis00000000000000# Exponentially correlated noise # Model function for PyCorrFit. # http://fcstools.dyndns.org/pycorrfit/ # This is a test function used to check decay times of exponentially # correlated noise. # Fraction Purity = 0.5 # Exp time tauexp [ms] = 2.0 gTrip = Purity/(1-Purity)*exp(-tau/tauexp) G = gTrip pycorrfit-1.1.7/examples/external_model_functions/Model_AC_3D+T_confocal.txt0000775000372000037200000000052313554642611030062 0ustar travistravis00000000000000# 3D+T (Gauss) # Autocorrelation function for 3D diffusion + Triplet ## Parameters # Particle number n = 10.0 # Triplet fraction T = 0.2 # Triplet time tautrip [ms] = 0.02 # Diffusion time taudiff [ms] = 0.4 # Structural parameter SP = 5 G = 1/( n*(1+tau/taudiff) * sqrt(1 + tau/(SP**2*taudiff)) ) * ( 1+T/(1.-T)*exp(-tau/tautrip) ) pycorrfit-1.1.7/examples/external_model_functions/ExampleFunc_TIRF_zOnly.txt0000775000372000037200000000336413554642611030322 0ustar travistravis00000000000000# Axial diffusion (TIRF) # This model function describes fictional one-dimensional diffusion # in TIR-FCS setups. It demonstrates the mathematical functions available # in PyCorrFit. # Visit http://fcstools.dyndns.org/pycorrfit/ for more information. # The first line of this file will be treated as the name of the model # inside PyCorrFit. PyCorrFit will enumerate user imported models with IDs # starting at 7001. You can save a session and the user defined models # like this one will be saved as well. Lines starting with a hash "#" # are treated as comments. Empty lines and lines with only white space # characters are ignored. ## Definition of parameters: # First, define the parameters and their starting values for you model # function. If the parameter has a unit of measurement, then it may be # added separated by a white space before the "=" sign. The starting # value should be a floating point number. You may use abbreviations # like "1e-3" instead of "0.001". # Note that PyCorrFit has it's own unit system: # unit of time : 1 ms # unit of inverse time: 1000 /s # unit of distance : 100 nm # unit of diff.coeff : 10 µm²/s # unit of inverse area: 100 /µm² # unit of inv. volume : 1000 /µm³ D [10 µm²/s] = 5e-5 d [100 nm] = 1.0 # The final line with the correlation function should start with a "G" # before the "=" sign. You may use all common mathematical functions, # such as "sqrt()" or "exp()". For convenience, "pi" and "e" may also # be used. If you need to use the faddeeva function you can do so by # typing "wofz()". A common used version with an imaginary argument is # also available: wofz(i*x) = wixi(x) G = (sqrt(D*tau/pi) - (2*D*tau/d**2 - 1)/(2/d) * wixi(sqrt(D*tau)/d))/d**2 pycorrfit-1.1.7/examples/external_model_functions/ExampleFunc_CS_3D+S+T.txt0000775000372000037200000000542413554642611027612 0ustar travistravis00000000000000# CS-FCS 3D+S+T (Confocal) # Circular scanning FCS (3D diffusion with triplet). # Further reading: # Precise Measurement of Diffusion Coefficients using Scanning # Fluorescence Correlation Spectroscopy # Petrasek and Schwille, BiophysJ 2008, 1437-1448 # http://dx.doi.org/10.1529/biophysj.107.108811 # Visit http://fcstools.dyndns.org/pycorrfit/ for more information. # The first line of this file will be treated as the name of the model # inside PyCorrFit. PyCorrFit will enumerate user imported models with IDs # starting at 7001. You can save a session and the user defined models # like this one will be saved as well. Lines starting with a hash "#" # are treated as comments. Empty lines and lines with only white space # characters are ignored. # Note that if your code does not work, it might be that some variables # have other meaning. This includes using "n" instead of "N". # If you get some Syntax Error it might be that your starting variables # are not set to a reasonable starting function. PyCorrFit is testing the # function with sympy (for safety) and calculates the function for # different values of tau. ## Definition of parameters: # First, define the parameters and their starting values for you model # function. If the parameter has a unit of measurement, then it may be # added separated by a white space before the "=" sign. The starting # value should be a floating point number. You may use abbreviations # like "1e-3" instead of "0.001". # Note that PyCorrFit has it's own unit system: # unit of time : 1 ms # unit of inverse time: 1000 /s # unit of distance : 100 nm # unit of diff.coeff : 10 µm²/s # unit of inverse area: 100 /µm² # unit of inv. volume : 1000 /µm³ # Diffusion coefficient D [10 µm²/s] = 200.0 # Structural parameter w = 5.0 # Half waist of the lateral detection area (w0 = 2*a) a [100 nm] = 1.0 # Particle number n = 5.0 # Scan radius R [100 nm] = 5.0 # Frequency f [kHz] = 20.0 # Triplet fraction T = 0.1 # Triplet time tautrip [ms] = 0.001 # You may choose to substitute certain parts of the correlation function # with other values for easy reading. This can be done by using the # prefix "g". You may use all common mathematical functions, # such as "sqrt()" or "exp()". For convenience, "pi" and "e" may also # be used. If you are paranoid, you always use float numbers with a dot "." # to be sure the program doesn't accidently do integer division. gTrip = 1. + T/(1-T)*exp(-tau/tautrip) gScan = exp(-(R*sin(pi*f*tau))**2/(a**2+D*tau)) gTwoD = 1./(1.+D*tau/a**2) gOneD = 1./sqrt(1.+D*tau/(w*a)**2) gThrD = gTwoD * gOneD # The final line with the correlation function should start with a "G" # before the "=" sign. G = 1./n * gThrD * gScan * gTrip pycorrfit-1.1.7/examples/external_model_functions/ExampleFunc_SFCS_1C_2D_Cross-correlation.txt0000775000372000037200000000161313554642611033454 0ustar travistravis00000000000000# 2D SFCS CC # 2D on component correlation function for perpendiculat SFCS. # Model function for PyCorrFit. # http://fcstools.dyndns.org/pycorrfit/ # http://fcstools.dyndns.org/pyscanfcs/ # The detection profile is eliptical, as the focus passes the membrane # perpendicular to its axis of symmetry. # The axis ratio / strucure parameter is defined as: # SP = semi-major-axis / semi-minor-axis (wz/w0) # This model describes the cross-correlation for two-focus FCS ## Parameters # Number of particles Nob = 40.0 # Diffusion time taudiff [ms] = 1.0 # axis ratio / structural parameter SP = 5 # Beam waist radius w0 [100nm] = 2.3 # Distance between the foci d [100nm] = 5.0 gFirst = 1/sqrt(1+tau/taudiff) gSecond = 1/sqrt(1+tau/(taudiff*SP**2)) gac = 1/Nob * gFirst * gSecond # Diffusion coefficient: gD = w0**2/(4*taudiff) gcc = exp(-d**2/(w0**2+4*gD*tau)) G = gac*gcc pycorrfit-1.1.7/examples/external_model_functions/Model_Flow_AC_3D_confocal.txt0000775000372000037200000000217413554642611030656 0ustar travistravis00000000000000# AC flow 3D (gauss) # Autocorrelation function including flow for confocal setups with # a free 3D diffusing species. # This file was gladly provided by Thomas Kuckert, Schwille Lab, Biotec, # Tatzberg 47-51, 1307 Dresden, Germany. # For more information about this model function, see: # Staroske, Wolfgang: # In Vitro and In Vivo Applications of Fluorescence # Cross-Correlation Spectroscopy, TU Dresden, Diss., June 2010# # # Brinkmeier, M. ; Dörre, K. ; Stephan, J. ; Eigen, M.: Two-beam cross- # correlation: A method to characterize transport phenomena in micrometer- # sized structures. In: Anal Chem 71 (1999), Feb, Nr. 3, 609?616. http://dx. # doi.org/10.1021/ac980820i. ? DOI 10.1021/ac980820i ## Parameters # Diffusion coefficient D [10 µm²/s] = 10.0 # Structural parameter w = 6.5 # Waist of the lateral detection area a [100 nm] = 3.25 # Particle number n = 10.0 #Flow velocity v [100 µm/s] = 0.5 ## Calculation fo correlation function gFlow = exp(-((v**2) * (tau**2))/(a**2+4*D*tau)) gTwoD = 1./(1.+4*D*tau/a**2) gOneD = 1./sqrt(1.+4*D*tau/(w*a)**2) gThrD = gTwoD * gOneD G = 1./n * gThrD * gFlow pycorrfit-1.1.7/examples/external_model_functions/Model_Flow_CC_Forward_3D_confocal.txt0000775000372000037200000000244313554642611032343 0ustar travistravis00000000000000# CC fw flow 3D (gauss) # Forward cross-correlation function including flow for confocal setups with # a free 3D diffusing species. # This file was gladly provided by Thomas Kuckert, Schwille Lab, Biotec, # Tatzberg 47-51, 1307 Dresden, Germany. # For more information about this model function, see: # Staroske, Wolfgang: # In Vitro and In Vivo Applications of Fluorescence # Cross-Correlation Spectroscopy, TU Dresden, Diss., June 2010# # # Brinkmeier, M. ; Dörre, K. ; Stephan, J. ; Eigen, M.: Two-beam cross- # correlation: A method to characterize transport phenomena in micrometer- # sized structures. In: Anal Chem 71 (1999), Feb, Nr. 3, 609?616. http://dx. # doi.org/10.1021/ac980820i. ? DOI 10.1021/ac980820i ## Parameters # Diffusion coefficient D [10 µm²/s] = 10.0 # Structural parameter w = 6.5 # Waist of the lateral detection area a [100 nm] = 3.25 # Particle number n = 10.0 # Focal distance R [100 nm] = 5.0 # Flow velocity v [100 µm/s] = 0.5 # angular difference to Flow for Foci Vector alpha = 0.000001 ## Calculation fo correlation function gFlowT = (v**2)*(tau**2)+R**2 gAng = 2*R*v*tau*cos(alpha) gC1Flow = exp(-(gFlowT-gAng)/(a**2+4*D*tau)) gTwoD = 1./(1.+4*D*tau/a**2) gOneD = 1./sqrt(1.+4*D*tau/(w*a)**2) gThrD = gTwoD * gOneD G = 1./n * gThrD * gC1Flow pycorrfit-1.1.7/examples/external_model_functions/Model_Flow_CC_Backward_3D_confocal.txt0000775000372000037200000000244113554642611032453 0ustar travistravis00000000000000# CC bw flow 3D (gauss) # Backward cross-correlation function including flow for confocal setups with # a free 3D diffusing species. # This file was gladly provided by Thomas Kuckert, Schwille Lab, Biotec, # Tatzberg 47-51, 1307 Dresden, Germany. # For more information about this model function, see: # Staroske, Wolfgang: # In Vitro and In Vivo Applications of Fluorescence # Cross-Correlation Spectroscopy, TU Dresden, Diss., June 2010# # # Brinkmeier, M. ; Dörre, K. ; Stephan, J. ; Eigen, M.: Two-beam cross- # correlation: A method to characterize transport phenomena in micrometer- # sized structures. In: Anal Chem 71 (1999), Feb, Nr. 3, 609?616. http://dx. # doi.org/10.1021/ac980820i. ? DOI 10.1021/ac980820i ## Parameters # Diffusion coefficient D [10 µm²/s] = 10.0 # Structural parameter w = 6.5 # Waist of the lateral detection area a [100 nm] = 3.25 # Particle number n = 10.0 # Focal distance R [100 nm] = 5.0 # Flow velocity v [100 µm/s] = 0.5 #angular difference to Flow for Foci Vector alpha = 0.0000001 ## Calculation fo correlation function gFlowT = (v**2)*(tau**2)+R**2 gAng = 2*R*v*tau*cos(alpha) gC2Flow = exp(-(gFlowT+gAng)/(a**2+4*D*tau)) gTwoD = 1./(1.+D*tau/a**2) gOneD = 1./sqrt(1.+D*tau/(w*a)**2) gThrD = gTwoD * gOneD G = 1./n * gThrD * gC2Flow pycorrfit-1.1.7/examples/external_model_functions/ExampleFunc_CS_2D+2D+S+T.txt0000775000372000037200000000573713554642611030061 0ustar travistravis00000000000000# CS-FCS 2D+2D+S+T (Confocal) # Circular Scanninf FCS model function for two 2D-diffusing species # including triplet component. # Further reading: # Precise Measurement of Diffusion Coefficients using Scanning # Fluorescence Correlation Spectroscopy # Petrasek and Schwille, BiophysJ 2008, 1437-1448 # http://dx.doi.org/10.1529/biophysj.107.108811 # Visit http://fcstools.dyndns.org/pyscanfcs/ for more information. # The first line of this file will be treated as the name of the model # inside PyCorrFit. PyCorrFit will enumerate user imported models with IDs # starting at 7001. You can save a session and the user defined models # like this one will be saved as well. Lines starting with a hash "#" # are treated as comments. Empty lines and lines with only white space # characters are ignored. # Note that if your code does not work, it might be that some variables # have other meaning. This includes using "n" instead of "N". # If you get some Syntax Error it might be that your starting variables # are not set to a reasonable starting function. PyCorrFit is testing the # function with sympy (for safety) and calculates the function for # different values of tau. ## Definition of parameters: # First, define the parameters and their starting values for you model # function. If the parameter has a unit of measurement, then it may be # added separated by a white space before the "=" sign. The starting # value should be a floating point number. You may use abbreviations # like "1e-3" instead of "0.001". # Note that PyCorrFit has it's own unit system: # unit of time : 1 ms # unit of inverse time: 1000 /s # unit of distance : 100 nm # unit of diff.coeff : 10 µm²/s # unit of inverse area: 100 /µm² # unit of inv. volume : 1000 /µm³ # Diffusion coefficient of first component D1 [10 µm²/s] = 200.0 # Diffusion coefficient of second component D2 [10 µm²/s] = 20.0 # Fraction of species One F1 = 1.0 # Half waist of the lateral detection area (w0 = 2*a) a [100 nm] = 1.0 # Particle number n = 5.0 # Scan radius R [100 nm] = 3.850 # Frequency f [kHz] = .2 # Triplet fraction T = 0.1 # Triplet time tautrip [ms] = 0.001 # You may choose to substitute certain parts of the correlation function # with other values for easy reading. This can be done by using the # prefix "g". You may use all common mathematical functions, # such as "sqrt()" or "exp()". For convenience, "pi" and "e" may also # be used. If you are paranoid, you always use float numbers with a dot "." # to be sure the program doesn't accidently do integer division. gTriplet = 1. + T/(1-T)*exp(-tau/tautrip) gScan1 = exp(-(R*sin(pi*f*tau))**2/(a**2+D1*tau)) gScan2 = exp(-(R*sin(pi*f*tau))**2/(a**2+D2*tau)) gTwoD1 = F1/(1.+D1*tau/a**2) gTwoD2 = (1-F1)/(1.+D2*tau/a**2) # The final line with the correlation function should start with a "G" # before the "=" sign. G = 1./n * (gTwoD1 * gScan1 + gTwoD2 * gScan2) * gTriplet pycorrfit-1.1.7/examples/external_model_functions/ExampleFunc_SFCS_1C_2D_Autocorrelation.txt0000775000372000037200000000125713554642611033222 0ustar travistravis00000000000000# 2D SFCS AC # 2D one component correlation function for for perpendiculat SFCS. # Model function for PyCorrFit. # http://fcstools.dyndns.org/pycorrfit/ # http://fcstools.dyndns.org/pyscanfcs/ # The detection profile is eliptical, as the focus passes the membrane # perpendicular to its axis of symmetry. # The axis ratio / strucure parameter is defined as: # SP = semi-major-axis / semi-minor-axis (wz/w0) ## Parameters # Number of particles Nob = 40.0 # Diffusion time taudiff [ms] = 1.0 # Axis ratio / structural parameter SP = 5 gFirst = 1/sqrt(1+tau/taudiff) gSecond = 1/sqrt(1+tau/(taudiff*SP**2)) # Correlation function G = 1/Nob * gFirst * gSecond pycorrfit-1.1.7/docs/0000775000372000037200000000000013554643106015342 5ustar travistravis00000000000000pycorrfit-1.1.7/docs/sec_about.rst0000664000372000037200000000410513554642611020040 0ustar travistravis00000000000000=============== About PyCorrFit =============== In current biomedical research, fluorescence correlation spectroscopy (FCS) is applied to characterize molecular dynamic processes in vitro and in living cells. Commercial FCS setups only permit data analysis that is limited to a specific instrument by the use of in-house file formats or a finite number of implemented correlation model functions. PyCorrFit is a general-purpose FCS evaluation software that, amongst other formats, supports the established Zeiss ConfoCor3 ~.fcs file format. PyCorrFit comes with several built-in model functions, covering a wide range of applications in standard confocal FCS. In addition, it contains equations dealing with different excitation geometries like total internal reflection (TIR). Supported operating systems --------------------------- - Windows 7 and higher - Linux (Debian-based) - Any other operating system with a Python 3.6 installation (via pip) Supported filetypes ------------------- - ALV correlators (~.ASC) - Correlator.com (Flex) correlators (~.SIN) - Zeiss ConfoCor3 (~.fcs) - PicoQuant (~.pt3) as implemented by Dominic Waithe (https://github.com/dwaithe/FCS_point_correlator) - PyCorrFit (~.csv) - PyCorrFit session (~.pcfs) Fitting ------- - Pre-defined model functions (confocal FCS, TIR-FCS, triplet blinking, multiple components) - Import of user-defined model functions - Global fitting across multiple model functions or data sets - Least squares fit using Levenberg-Marquard, Simplex, and more - Weighted fitting with standard deviation Tools and Features ------------------ - Averaging of curves - Background correction - Batch processing - Overlay tool to identify outliers - Fast simulation of model parameter behavior - Session management - High quality plot export using LaTeX (bitmap or vector graphics) How to cite ----------- Müller, P., Schwille, P., and Weidemann, T. *PyCorrFit - generic data evaluation for fluorescence correlation spectroscopy.* Bioinformatics 30(17):2532-2533 (2014). doi:`10.1093/bioinformatics/btu328 `_ pycorrfit-1.1.7/docs/gallery/0000775000372000037200000000000013554643106017001 5ustar travistravis00000000000000pycorrfit-1.1.7/docs/gallery/Screenshot_Desktop.png0000664000372000037200000061261713554642611023332 0ustar travistravis00000000000000PNG  IHDR XsBITOtEXtSoftwaregnome-screenshot> IDATxgE෪{lλ䰒 Y1bg' b (*D%6<өѳlGtgv߮nj888S )ǻqqW UkS)I23RT+cǻqqWǒT? R8/xׁ88*Ķl={t_/kOSce?p8y3IcێeU) /"P[988$! 7ђts˯K&'ŵ5/>ѣ~6/.y5RJ)b\0yK]fpwVtqϑVӻcf dYIxW88;:@05MlZ-tsuWk|ʤ65?B(!>65()PU$1-5alSJJ+$IL-++y]_\+:-&$9@\l9c֪k)Iͼ} ;xI0YxGj XSt>=[@t"BνniW$\Ҝ={eg8!!s4S-ƂRZ/ҊׅI(=9HhWUXA!;t88"+jEE50dȠ=uXjj޽g止$IԪMN駟}~M7 V\V; i-ipݺ̟Y**+Sݎ%Xvnj뮹pm{tSZ-VDJL$Ƙ3t@'OQlzkқR7oԹbO >4*qC%)!2e}i=:QmԜ/IҴ)-[TtHgꕳeێf=>MkJ#3/9jr]o}bS5i_;K-يKX |ڞ?B#oy[Y0{DO@Ɏgg~XH{g_u-c{'Ԫ}D˔ɱONŽN ;; ]~KL *{?g?{z?88;E0ƪkܢd;o;cp=e`Z3R{tzil/ᾷ^ST\Vm6M7\oeŮo=Z-h};w|ſο}Ղ~@"ȈaC^xM1VY5c ?@UVfnv<1iͷ[ wϮ6YJ͚8G ~[PuRRS=S9s;ӛMo^U?; ~J {^80Ww\ꅛ!ǹo~?[K{nH\7'}G ұCoFd#1Є{u Q&:j$Geb=o5qi5 2ZlZ7 `VęYumcEؖ33lp1(#5~Ou%UR$eB~yꪂ쒚ݡG^;:C -*>r o{pcPxvT;Mٽ.>5qwNbB϶> tӌoX,u''gj]fi32(Kޫnk/ۍ88r7^?^,_jG!CC11yYcnj:s?|mh|jEť~()1aЀ76!⭹;.]URRH)5[_~Q#ʧʊ w?<顾}rɹ}gy BFV&%%ʋ11mjv_|'&&Ķlyeͽwݙӻ`(ecFwpMu~n7 hͥA8Ni jf6된跣Cs }Կ'0XP9ٮƥvTd}"@PɵW"!ݦvcg/fٷ"BYyE c"9)IӴ<943 Xh8>Ry3:ξ}ol}%Wsh5ك_c9Ȉny.~WunwiXg`%^rFXTX_q5xUW %ņ>7yoDb4]5o߱k>[.[FC/vӖM-)(, pםvP#Qg=gvOx-[ _ͺܵ_>i9i{3(&[6<%IJ闽e֦ c5u1B[`|dRrC;:E߰ʖ o:}6%^@ۦ>VHVcQ 񰴪H0a/ }Wb6l ťm;YS3f>Ĵ҆wߞf><9IBV*#;o^*{ ΃@hC櫧6]k@ ._-u?u^S)9w>#f»ke%} ˺%T]TjP_X2S<ښ˭cO E'pG1R,ݢ]3 3=֫gv?33f==mʣ7ol1)p'/3h-[lܸy;;xс'$ԴK w=;wlĀd; G,:`=.Ϳ|5/2 k.,|XP-o״>Ysm޼bccenLі2zWmZ,A k*"&#FY1`Qқj@Sh5RynSm(;duUI9tHs$5F/M(#eN]m s`zʷ-KP'5+>ZW`xd(((@kRSsZsCOs-7 d_ѯK}|Cj1zI_VT bf5Ǵ6Y۴fcO{'TEv` PA ixQdu3|*(&CSL+B::*)șYRF\ʐ'5rV?88Nq0h[nUѧ֭[UU1ĄÇ]~٥O>ںu196>vᄑwNOKyjjjZJiHVl6dX"릶 )yiSw {{ff(]i޾ !(1 c1Nc̘3I$XHlZō)ssG0~Wh2`u#{eZ@HbM7%^4cjD2:ZҭTޡCg]nn֒{C`LUiݣz23iǜ;gLi/ Щ=mޟۅ"IR# ՝K\~8cۊ_~ڇt(޳:H#n68aɛ_NHMrjU#""4k2PG$sR+T߆fRB_5u-rZt,'f\qUbu;kF3˪mTQ#++xIq&]xn YwH~_vuox+.mVuҖT @ؓD5v_''N8 yCs{}F?dEWÛq`u7S{!0z+Ji`= )ɍn.~SWN ;Zyݙv0IYnOTyw#ժ޵oj|~83ܿw^Ոu.}pͻ9q$̦GE!)CEtB0SBnCSWtmu;9ThiC`pa/3e),"ڃFՃqq~j$'iii[/8W^}o3[BW͍0!TZV}Nٝkȃ'$0)n'=Ͼć};l5V#>F=EE&3sɟ"O-tY7g[eGbڥp~M0%C3VDsfըm ĎxlX{h]ܓq*tvgskUtJ*# I>q Q[߻W[n)3Lh1X0$˲~7r6ܰyt e=pՈ\XJV%'dw gFOמ=;z}@˾]xq\ʚLW$[n9mQ=+}m.E\rU Oq=whfzq/}4cMKК %yX4~J<88; -V رcllXjPbb.YY555V޲ie֬))-[tE зO٣222"9#GrAYY)el6?=ŹȃM~|zcɓ+1![@ `{n`kV,\zK#z`r5^ Z;|! U ›T!1]TTm##U;qx_[uf %.)cN,#ul[n>3#g~nہп]wܚikl6 !M/02FRb9L6Zwh9c !o rs@.ZrT7.3ROuȳr23 jG̫Yϸl:Y6%~?E) :FM4U蜠}7]t^W&j0sjzeGs^R,^ ۳Y}z]_#wo\qY#;'ݳˏW*jcy"XS`8SoR f墟ͷl?kŸؘ~^͕FK(J3N~w^y-IF>Fv~IE1ꤤW_ tsĩiZ_YU|'.!4_xhՈ]l_:s홋HCq9{YNUUM}NwF)0@ʈ,A~K_]B0O-w?@i*KVAQlb륋Hӈ`7t[hsȲsM߼Ŏ14^ #r @@ `m /iSUN$F`!ˈK/e˯-[Z3f.i |ïK2뎸ؘ6P];( ~cʫ@ "IҔG'effL>};qck,Ci$=mI޻Z-O>\~}F9ܘ{]+Wٺ}oM{VBbݏL T鿃r988;?۽_W{g~96Ͽ\6T@0؆X-슢x}~72Ln$ 0/0[776nu kl2IfDt}[& JL=98㸿5鰓Oy_Ѧ^nw/K.jjivMEcvkaO>Ll(?%>?y:b:LEH L&ɘjV`J~BIڑ-/{0qqǝ1dT۞nq>aIl6+yiR-_if'[ t+cL8B#h# rqqַi7xNܮ>lwR IDAT]R8pqq/&`h35 Yؚ82a0qq܉D D@!`55vfjSf8G( xS0qq܉D F 0o0Ƣ & !fB0&Hca1PJ)SF/)fM1VP$i&j?Ipe7yXԆY0 ǥ/% 2110qq E `qxd2$I2I&d2DQ0^H$J$I(J$ i**YUUeYEY6]n筷Nt58ODHKp<88;H+.B IRb|}N;UVɲqF#T%!!AeUQd%1B(ix+E {e`n];;v`^NFbVp3:wΎ~yH~T)oS 5&%05 ٙ3wx}TE ):"q>Al=$ڼ v`;=騝88TE.cd0c1fLt[`0hX>P0 Aү_3N?}Ĉ _?s9999:wVUh4&@e(TUaQ2ENG.4##nEwZ]^OzKQZԖjku3cf:B'SZɈ|áNUUt;!A! 4V1;S]7bcEBD1VT4˘B.%`BH"e88fܥPb 2Zi6{hUUO:=jb1SLޜ;7##Rx~Rj6'Lexnn:CVc1":weyD("~cbCx`,G*l2ŌZt)UUZYJQ L}U^H:6Nbz hN<13USd2eeeegwի+dED1ϳ1cƤ$SJuBAI2ffWVVZ:$+fI"hF"$j4oceMH&͘.`?ݯ{桋}?q#5(& k"f4 eMga17Ԁ/ j=kF-kHK/s8 N6]&ӻE1))b%%%N4) f~f޼y'OXY%I !B0"FDb|~ Rl#;ƘP:UI0{¥;4UJty5H9` \{֯앝U< "n9;jc`ʨj&Q-LU]ކyR$gܷtg inL;1`,'}1qƏ>s#դ?d|ݽם#IR}bL10_ю%_~NN̘|ϕ9sh>*Yx qǝp;ׯ12` )9^y˖iS$;ǏA#!))j\.Aq(&}_h\Ҳ BH-XyyeҲfKAyyERYY !TTT!-ob(o/{ Q_t@I$,&"DoE:}#GD$@Sʌ3 jY6֙2\sH RUQF51-ТQ-MSEQdr@R٣Ga @R(#Faj9FHG9 9ǟYVL4&HA$89LR?cWdP%U`bXѐ?T42@D1X& )K^<"& u\`5kfLw<0{o-8]j~_%OO ֔}=ezbet[F qDՆz'PxhQxjUU5V31<) NA$\. !Q@^@Q$j4B c1V?^UтdܰT0*++71[J/LII6M K^RR*2Ժ&XYة,bg, VMD u;_;YE5Ef 4M Ֆ2N"I7{A*Aյt#]uEHj5=#\"ꩩv-e+~;FUUTyyQj̟7G',F3 }GD|ۗ&Bw6;G&@?vS~fN𣏕D3^Ewp1X[--lDAݲl Xd_S\q> g(xM)׼5?ᄅ+T)S97̿=C`/>ɂ=eL .wŎ=Jjf<8`49Ƅ|"Zpq)"P76 WFY\\\倫VUU~a۶mEEEUK,1bDI$UTVb,0FcpMӋK{Ld2urDaGӴbt:-Ѽ <HBE1b7eBq;:q'ctOCLh~T( /m*xZx~W-jV@{F%Y1tAd {#I0PJJKDBA]׍cǎ.lJUUhfFzIqI[}`:3&Y;Z{OG~ptÒK/m" Vm*,@]o6}L1˨[5R$;TY-偤NǪ@ PY\S%g@ToXv@\5iF@]~ɽƭ<9$V-&10>8+`p[h-, ǎ[PP`6]|Ń _~+b6[Z#BUs iύ> G%>^_3nS<o3nS\.ndsǵzQQ %Nd?,2;# y(3m!Fuͦ(2@5~tk6Qm6("`0RHu t]Z6.)|;Qoӳd!S2@5E" P5qØx7fҝznu}1z~1*60V7,pO>>\a̚UEiqZt:g[KSg"}YWu:TAf[s7fYK݆5q88xDT ZMSk-[ƌYYY3g>S?&''SJ7|S'iL ;vvWVV QFeR!D5QWc~q\{j+F1+SPCjhVo1o<j2koxӍ&31BHUU](R`2ښQ?5o|Up7kJJ%G-;%wpӻbu+t='z1]O wBɚoCpZ2^_ɈL^^ 62Z M}#j!$]}(b\x4[G↝?=*i4XH,,ldqWqS:PHMcW50a!$''K.1^NMM} c?`pƍv}…2jL=qSӫ4Tr 8d^.И!7(tXx ۍ{􊑌t/B!B@1£Fe ="c e! @B*0سތ ydYF tIWYp`ʅ?_|۝]2[C(^ V]P 9_-#Tt,b86R{Z\?.WpJmb`b6#_7/F1F;Cz˽)Ht ݯaGSe,1"vIPW?0sЮ?9[}@sx8;!@FCp\wuRz˗_p1~JKK};w";_EB~wUAAQ#G9'q?dh@ cr!*j/{~ ="`$dL`kj\1j qxoBbB86 '\KR1ŒSGl2Kf =oݚ-'&j8zMܩ|UI+brhg>b>!/r]0+h5TЬAe`%lO=N_ MJG6+!.m+C{G3 [+0pN^JZQ;3L凙 ҲJM 6Eu(Ɣz8\$BR#`ݺ i*(%b+@Ɂf GZ15_[}aO]Ms1w֖m}=?[zѱ}~d7d56r#a~ IDATN֥cM 1!>_#Cs*6~:}ܤqgݞ5{iI̙{zגd`V쏽`bmqa3iR͟rq2H_o fݻ7&&rV^O>DeJEV^P(D@ (hYi`r⌄83Dnh:%q\T3n!Pl]]t$JM>GvAIHL4I"aDtB8ө(W׸\55VPRGR(`#*,6+iKWeĤᗢQWd?<13, gM, F f4X{n(gjn~Yv*$&k t%ϪWȺڱ-V+Amk-M-sgQ]z/ޝ9D͛7sNbXd̀nfqK*PHD&e88OR30c1DF[B }s:11h6p}&''GvWYYu5k>|8 ifDΊ.cHt?`ٮǏo(8;y]xy^l6 !Fw{Ahꝕw2X ,$N.uL6QR*Ni.FoG(ڌ0:;HAM#esN4R (:t>m<}r6_# ^nY:y(DrP*˪Q+ l+˥NBB  ]:`Ol(( 4AHk:vg^BHB$Ϗy7ٝ7nn޼cMeKZIQ39ת0qE-SlKƈ%_) XUȔ2Չ")L\ ɷ2QâFk1ܼ3ŖG !p8l6dAQE J aJ|@T҈iӦ&xyy6n877WeQ]\\!陙@`iٙRƘ%f0۷o_zZ,!2(yyyn)ù3TՖ&@hd٪Y1e7Z ՍpЪ>dZ-$SʬV+QH`KukuB2J h(@!BȵkRI!wǯH`;V|+p8{"X0F Wbp:u, )kݺuWLL(fYBjC3RJ#QB($FWQ_)]ңP)z _tؿYn`C*XQƣYP0F $bJoYI+ MSJϻA .FM{bŚc2FcQ\ .PYQԯ?>xy-aHԊ:#Ŀ9{,0 ϋOHp0͔RʨNRRRrBB"cͤi)yyYifPJU00F,boKQ)BgO nTA u.9y6!"^/(LUv5CXL؍]+-܋RAd/%d\KHiE^p/0H-RS:=&0Jj+K(j8ԷjeR <7.y H7l6(Q|d6A{תpTVCcHբF_ D+LVnq`#0Ƹ0Z5Y[)h(n)TNp8TXf 1`f.`AeFYAؾ$S)cp.BH@B%(BTHi2p p8é|F S̀BS -9ٷp"H@ `a\\A(z]XhsO%q5ЬVk2 ηXA4C4￟ ٷ <T^p8ù/Y!` B5X b\t7jkծY( K8Ul O:ywhEo;09yϪU'vȝ1V0S9|Wh0hZIA#p8éXv|PÐ!PEKjPLZ=.i4:>WN;wׁv[^~6WNױ!;Ju\|| b>c%T(](7(p80@b_?)jDEAHbhuI0˕KLs o#kWNRP-a׀wmIA=svSeƀ@s8p8EUJ 6bEHzƬ8fs4<yA}Jc[_MxoH\\B@ޢǰ=ja0/ub0 _yus}}\[ߘ?ѩ(GV~?ݾ9L4Ew2ح?=*JtlvYuR&ytC[h& CIX7GlZGD/dX0ם.[^67wyMA)R Plp8d̰hiIWشڠ!>򶅿~PiL|g|;٧N 4Śn4csx΄/Z:TL=eEPAy 4p8yX]p_Ŝ!GdJRclLŋ=%ؚ\r;M>$QH 6h7~͒#51lg},s{/wUT5B(h=BCi)V ,uA"{,M͕Qh޿H @0ҫ˗\ל҆֠Y@aW$k3W-VqH5L3,۞:t =ETŸOe"BOp k׭Cp8ùt^ @U%Bd)j5Ži] uk:dmjTIR!MPƵOXEvgy4uõCgm=jޘj7^cV`M<~Yqk}vKyuf(6lY+Ƹk3aؒY:ob4`M>ܛĔ20#`c@s8ʦo߾zp8w˗!GEֻ:c!1F 1# ji%F0?J e6y׎>bMwӒuiAW N!EzÆ+tkniy7,0 G_d`:>3 U bf@s8TM\iWzY3Ћ10e(f3)BľHSg"D՗*!H!L c1Jb,*ySj =t&G b5LFh3Yd@Vn쥫9\Yyδ ~MGhblI'մjWw?bT@rl2!]%J)ӃCz1wO }FD믋74m E7 vL-U'T˗/( ޝw'p!˗/S"# ac$ղ(_U['ڮ Y*zUEef7x8 êE@P15HYoXtB?LYݪvlpͽ9R"U"P !tO.?p8U*ۇ`LT`H3_? t/jW}dJj4c(P0Փ2I_1g?+.Sw*Xa|!Ԃ0Vp8é(e}0 L2`@V.%\"XZ1&HBe AH] Ā!c =`;#Ԭt1čtV{p8ùB8oJD)6@`La 2B!Z F#oQyXAW]|J1L=cLe(BD0 0)j,,Ex`0+~A=r(<WlŽ+/}6Ozm>5.sC*4{E>u7g6 9?e\+{4m[ιPgOf=qޕ3齲۽yWei}/6N9db o޶&77DyW.= MI\9 uoۯ,Kzi` g*Ҩg %6oSlwC_>T:u33)`h=oތ/l[t߇Ϥ1{p|꼾?x K:kW-VKe(Pl61L bf!,˲I]Ü^;|+(%3w n]p _vi;]XɥOgy`Y~k,@ _?q[opqrw,]n$}קZ@͉vl8xP o͂'Ǿt 0@U%0[QPRSFeMT "bTMX,9ad6g  1 >i;jqUVbS0ct~E3xw|Rpr~6j^ Oi\|!f3g3}tw38;O]Nc_|Bxt1:`+/|jݥ8nrl]|f>j#'<פN ;M2(EmkѶ+( [wkO}5N{䄍Ӿ4qh$h~v>vM=vP_>Y}>K1=ɡݪ=EIZڣt+{[Ths?<ꋬoۨ>M1.No+GO8qt_N4yʐO;u!9B7mY^]?Ks_z{ 8`Yr! o]8 Ճ?S s/i%\8qʵ5|˵[{P,ݷ!%5~F@)RD_<߷mcv|+ ĔOϻmC9z֜EI4aCw&p/G 7lT#Pg3{'bM7?Ojz3I$ hTnqKVoլdw4o,/Rb?1Թ1;r^gjܮG4d ,ܯnqKH^Z>,i9z/7?*{NV)ьKH977ӽj*{$vK+eeinh}l^(%6֫_F/b`::axtz\Ìt rMݻƄ8ŷ%ܰhP7tqenթQDmfm}kкw? IDATfj_vIS7<>4m6j+#n# |~XZRj`(~ᤨ~/jecZ/h^m #A`(ȈРNf(jU 3@$ 4p8$=4^ԄL!n5Yg,pk}EpoڠI}tKR>FEx )9GsNWpdXE]NԹDcIcjn֦wlqh@ɼ`ǑK 학[yvW{ 5GvlŮ߽ &b{ǡ*潼K{] <ۿ#Qzڭӭ ul54͚bU= e;jOȺ\% uzU[u黗hR$4hw#,G ÿЅp[< o?geea th&JE!xo$-BkAa`a#eDa6bߗl6Q,zVfI:+vyˮn^O(%͍7FiIƥ$ ʮ53-[І qqp8`9 l7^;xKLn|ã}ãktRw+UcT\e*9iW{/ai!&vU:M[SY10 )Os,=wR){tES:8e֫{|^o?Z4csxvg'@6$8z}PyeXsX#/f>rc4ipb:xaяogSh8%6E9w0N<}GzZAI%1ߎjf>ls^[n֪jocwer3[ovnޱ{TTаTa#Ϭa;veOƽ=gmrZ)<<U; oνBJ{)! ٗ.fap^Δ{ޡ]OPwϪ=u9 z-_;Jؽ9uc<:LM/s@ YS\O4 KyyIV55먁=zF5S׮zfpKIGikYzW):|h,@F<bNϲB͗>nܾ?^?LF3OɖpdJ@2)9X ?  mnX?2Y0 ԙZ(!Uu0u..m\o矋^~Oc,D=5gwy)S'w?`IPwjuWm'3yo;׫s(o=M_B@߭[q'ƥj= K2VDv Sv,ah¹׎l\t2ş{N*xO |Nx4w-7psDvعrMF!Co ?-=9 ܳk~ᑗ'??)c$)9|3ѡfLH?ybP'ZdoZ&$Z ssiqhM,غyYuTf=K}Q5u_N+WeV 5.k})zda]D ̎҄0_>n40vjvl,Ȇo**6k'x~ M۸ǝӤrW+{P+=j͸oͪ%6W&C3LslޠJx^[q湉ˬsϱU pk{s9:[`xQҖ| z 5߫ pH݂ŀn}?]D hk=npE5a-{' {|WLǩ4EaO8~s3:FA mD8ugp橚.j?&lբ[KOdLɒz{ws֮_/bN'_%voٲe1K)UW ^6o˨N`ocGյ[ [ Z~"OO;obD`oqo8759u 1eoqFlh=w \"9\h3t%3;{a[_83x7:ѿ4}Ӌ?&.dJ1W_^mWo' fIiÿptë́wy{8[X1ChӶM̬￟j2 V+I҆{9ecܟ ޝw'p ˗/:9)!f] $C໅.#7n(l<*5zieM@rmpz1Mv/ت̣:gwxKnOi$`Sш\s8bT+ۇNfρF W0*.? I7©,7"˲Ka,`wN(FkNQ_a}bTl[Kh˾^=.r]C/hO ,4) ԭ.:@elwin餘VPrSǨXeDIr^"o$ߞZszcf͵тeop*p8NZ}0`(ɲ8^oooU:ǝbMu;>}y+'fϞطi9\jnc8<{׮7{{%Ew5jɷ,㧯޹Y;q7͒.}^.u +2]p/ֳ{oY[fkgv2kcof~h~3^JY~3^ΐù"p8 S8oJDSv*y晢k УGRý tcŅ\knsMj7|zt|Li; |6泏y~p"ڽ٫M\Ik;͐}֧g/,L>_פ<6 tiݸZeAS߻>yÿgzM &jw-~?V}lgk:zz΋/oe[aӰл2P#y'p {w~G_`VkRcju]} 10hVm۶%ulzzz͚5/^hZj#""8Vr3g΄8O( ՊgvZj_58*0Ϝ q8..V * 0*|p>Xڵk<Jٳg1)qL[6LPw¹p8Ug˗WH8Su-3~P).=ݴ R1ZmRRŋc`t:WWWʙ0@Ln|6I3wư՞y/Op8UAJ7x{2<`^uǻX].r'9Z+/3`777쯌22U^|uSYps8T%#Ћ* 7uPtR 0GE9@t.vW۽pJMUj>`u5L (_AWšVr9t^]+Ov}]j|e?<4T0,E>S`mYg 15z.Ր9" !Q-Ҁbj7jȞ>_hIgєt @ު eqڏ__ 9⃗bR`kV,t蚙YG4@I?q]Z!^}[鑃)iǏ\7Q=vGǼ%̞/]I}[ujwH=uafZ2r ۡ_v. 9zs7coOt }<&ڌ|G8 A{H;MsDdϰ萛d79qv5jH!Ccb?F)'/beA-D;%p8BL:~Μ%4n5G,"$(<40 0X4^Xp8 ApyEEnlG3yFz|DG7=8u_Mx&>+#nC JĿ8ɗkTIf9zO<;MKVYZb `(*,6lafv֜Рށo͚9cO:s)C׬ADM `ȳEK(foTu=̠d=[@-)vJD#GuozDϨ拗i/p8*qL!wc@c?  p8An 3W3֊  2$3iJQ՞7߀ߖRZգ#BtBF{rӞc54!YG?\窵Vsrst"%'>Ehm ̧6L}!EHlݳFu1kH~r#rP GxY' >Ў7ͭ-w 98k)1vSM<}5g39p8B[j,j4:p8- `G)й96fXPV_ƕ]Hɲ :8c|-5a:F_GjݺE_L!${ᤱ ol)dJ+d Fɟ %|"1Սo*zD7Ȋ 7ؙOKQZ'ڴkhϛRv9E{rblڸ C!cǤ]wEo>Y7F^٢0rAI9 S5Q(~:e[op]r/%| !ǜpBy 81rK)w֭ ز/u b~,WkݍR(V~O\H5w:V`̗1VBwe@ zQIsMRۀh/ uAaAa 8A'oTt.̹ii,ܜ4p8΃C\gp$q@`0e#[wX}p]Tҥy 3l<P{8ٲ(H[M_043 IDAT^oJ_ &dNͲ1 }xZm7^gϤ)a"(i^(3>{&] ](@g43 80 !PG}y1М0|*٧_z]evugu!:fN:c9ܭG-'o"(c#+3/0(ʽp*-`bkE1B!10VPF`RF) 2J eBH5uQ į 8(ķNZ :W=:ۭ T驑jqK,GL5y'Atְ- Wl{e zS>/r]aP5  ~'D:6=ovo4 j7ڵPu*iûf϶޿vgu ӔO| W+X2wwQ;ո{EV툭ٯ >}Zs3g輪RV"+MPҏN5@4EQ$I0<a!㞍[ $F%Q(aQĢ$`APEA(yȄ(TQ"PEf"+21%"`dzfꃊi#Wp8]c;pt5V$jy4}W1 ygT}ǹ"0Sg7ש`HRB|DTt>&>mpkr7VoDAEQ XTR;yLUf (A°!X`X X  @aRPB(QQE&BeRdY&DhM9ewG^-z٘{ v_#o[/p*Ϳ \L!`1b f3@ 2 Q{AN%` QF0 fp8]Dp]A.{"e@܀Q^m7S54iĉ(8NWT)ֶ@P #@0V0{3L6 UdirSKBV/Yi8Bpϩ~= V)c`Ō(0 9^z@@`,#jMݼEZlģ( 0 P 轿?p8(b)Ќ1j6fbq8w(n&ShHzp8N%Sf@ %@D@( E)Rd4di1ҋN9eVJ _cۧ6[#$Q@! ``Q TD9΃-S QrsN=*mlΝuĩ-[`V=}I&j0PNr& Q` Q`-_trl`kѕ"IPfJ BZRֳ t9>C/ѥ4S 3Fe@ R_+~~1uQ\S;-G V|rgU$t\x@B?=dU|GOIo3QC+ks `\Vdҕj Qq8w h4O8Y^=.9S}TB1FLLB%eeyݡo~>>#>=;%&ԁ钒jSҬJUI*EI˵fFg<٨)UvHLBl7'{47zGpպsmaKӏ܏JPpDa?_fnNspr}e/,{A iB( =^2! AKffv/wis$MnY^qLu:!+k^zu_ue 4!d2*Q i4zhEQԓy\`,D0"`{eҪ2LV3Jי9X'&UXDH6 k4-oo5iw{fZ2 !(K$IZoOyjh֫n1u_)G,6RwJ=;X-Fƭ:tzww*n:t'K`iExp9ÈngL,PNYdTٯ]R./kOc~9_N3ly0Y{ _XuʸZο7(HP0{ 4_'D4[yqq7c",EQ%F *)^6% ,#!ْdAUQp #9JXB A&(Xmb۝i!:Ӝ=lmtuAZ dGgLem{C[9TĤcKGVλrA_ hNѷӚ=j&lw%[~Ls?@L7zwDֽlnFcLǴ~#cKw'1gv jS1qoz2Uݻ-c[0vۢ=0jyd |}^8Ģuw#4 Qu Mwolʃ R)(w~gY6uS&VuRmGwٺxە V}ȂJqB]e*?W-|x3I}9"nz`<1\yugHj~ѿZ'GjcݵoǕ]!xx_mut|>0_;t(n޵dvB~VRA!Ƶf5z) =((?, (HMl`6/_ XJ+lXXch Fdm&YEf䥇n^)lר~bID=;ߑvQ2zj2 ժV` vh|֞V\j^TkyfÅ`qSbK1…9`sr ?RYsrփ WF9j-:}F7zsq|\?<(qoPn3AZƯr^i9&o[Y:n'5 iU)?[|ڱ9i)Z/uZ3rwKTZg]=7hЬ<ߍ[xS҂D#e<@:y}>e=ބ_TbugB)v㤞ԫW^/eѵ9)()=V%cIH0" b` 6K.67P%a"D8Ăm6`mڨo,%OScZOh49V ,g^w$6-w{i`8(  VɁhyß[X伽j2VA7UVT-;^Rd0@ԁgl%'Nrg6gl̡5 }Tyϛ]v=k=}|_Btq|g?Z?U:kVLn@m&\Tm^3wrÉͪz0 ((=‚SHW1Ͽ6qO{Oǥ4~j>lb֭ ӖHl8odޣ\îÆw5vɸzOR eˋZIJ=9HKeݫ3cs~XZyTh}O ۰}/B/464a~?/iS#EQ:) B D!ȦZd!<10Ba~nyE?j(>Eh|Ub#;Wno~`F6z pHc8ī)6^Z6r*ҾN=~Tv1Z/0YuǬ `x:/-0q5s4(w؅}MipȅHjqM!fF r# ~真?`ƻ<:P#y!CbPۈ9-ď]lgq|-ݓ-R.Kh:Q/t3eƮ550emfn@I:ybڰW .IG=kaWIcyĮpOԺ -g8nugRʾfNpΕsoزЏ"YL썇9t%}oٗ6/\;T~wԣ)(ֈaVdgg3A(/#%# 0b%U>WnM0fB$Yh% xH&Włeǃ; (8$Zzx`n鸜~E*^3~+ %$oaSb?#,} 'y{R7?^_#lf ` ׮=gf_B~J)=>]|Xn]TH4#輼uYtJrzފхEX^Hk \n0q:N5q Q, y|uq-擿!Q_^+:y==_LLOx7#suiԲEg¦3]yf6_! E]-կgRm*jo]o@N>e'o=ȴ }X>0Z} /gyEƟNJ?.N|w|8\j]Y*k/{BT̓!Ymm({~f?{diDbgQU=\sgc:GDioM7[ 8=$9Sh 2dHx(?ǁ.ݚ5dF=Ö緍ʅ@뻹':Nso3199wd41c\iM3#ZR\W*:ÔiwS]B|XXwqPE*|4C^|Y ,ٻnuDE2 2!!6-@5M`0fggg$FbFdsDߣ(hg$>_߅TmH_[koĜacgN\xVH<}1EbߵhŸqɘq ݃p¡36A=i^z:]5vγq7ٻzƒNnnz[Snw>ǽ_ލDv=gܪf=XHcg_T9N9diF݈O]!;\W>L\9ao٠!g<{6ÕN9u_y4'ӧN#+bMk )[Yw}u-7n%%Fͻk=ywӥ 2~XB9"Ư0똮ݔK̛ԧe%t_/[jժ_U߼ELZIOfa箤vS6bN.l}f௶Wd'\['uyuN1{wc` +!GmȦLpt^]_jժUk%F.Qӗg͘2BN 8*NxՠX/L \"FԋW3Ne3bjqɵǠ+^9`% |Kv{m_"%-Z=#ln~:߸ק6|}׮+oݷBM!9F*+V*,<,$ĊM{LszZ㓧xzNimj3rmP'm8|qy|6|޾TRچM:Yq{֖O("l:>I|XݦVturA l8gu IDAT7`]ㄾǝC{?ޫ`:C] |n>c{>x\pe/_'*w__mr@UK*'tyo\YY}T U؀v0y* 4,$^}=TqTZB qX㷌OzO߆Mwqp<8vzޯ\6#O9ܒfR%ʿ&h׻VI {M ̛u`L3i%Ioji}#5{QE9m8oN}? ! $$0f#huUQ a! BňdI"#z*b[j&4e!\S`(nx/kgJs!ޚ"rLeֺwnXfjߘ =]?~6aɟEz{[O:0r <^;\Ģ +)bMVr%ZgMjOSE9P{N+'Of}=d%d2 7O풹;T RGxO-w,"k36 k5)$K(NHѲJ 8ٔ'Úpu;hF>QU(ywn6g_O,,֔~skqcVk`k>P'֎(SB3TW 2~JB q/57F5.b*d#1T9ޯjEՓV{5VP9Ix6$$wG7mx*k6r5jԨQjrin-Yؒo2t& MtA4 1:/]QJIdW׿MF=riˁ^ (TܷAI ˡPBlAq0ae"jQcLl#oOwI@2"[b3 91Q ~)mK%[j~5z1J.!#\:w.+𥳐]~PV* #&v#|bۆ{|. :۶kƊ#HJWF~᧕;ݛG3)*T>qK?D] )19qZe܊`=LG't,n3oz{pw9ct?Bh:w ݻb5m!kJB?aQVgtdNm>i*Z[Ҵ]xF { [iB;޳֬3c/h"*ziY˖ DezCQUqLeei!$ a΀ ,MX% D,IȐ؂.*=H∀$Hb*Xk"!M#p6kI2m:PR_ж6id*Xb.[m묃92] (*HJ~x9i"`ep?&[)kjکWҿ[=s6Qu@gY̱kD5RP- D(8fG%yl@o 8 ]۬ś*jB(»}u 7{}? y:Wyq+ q҈Op3lV[~O6*l-GZu(5@S7̛֥ X}ӷe !~Ew؇lJh5[fk`>^a{*{XoX-٣qmk|SEQ da0PoUssLY TB %<0, $ X$nɪ`ȖbR DY@d2M>(*1l\w&Z:VT߻iɓ'N|9)!d0dt]CFr,q1,2,Š*B{,0B"BBW O'jchB t+l9tѥj"qDk/3๯.ۚŬl@̸6# 5}^]X(/r'$u"5(OS/*}_. _*jeuo~598~K28) *e8D 0F0 Vbs@@Kox$$BV>Q! I~k_[$, Rdpy fx5ﭿmZ'Y[V$Р/=5~ǂ. @QU=)/*b=Ɗ=m[wLQ׃1s]N' 0ite%^JȐؙi:EQ/<6׷'%lR[ })*E4 .ž6}fa,!zť,X9WG0:/'Π +XL;nv*eD B Is]39R ZcW,3.~?x<`Θj:m'? sgbWMK~g86mת~2vVzfݟN٥sga<bӔoѳK(Y:EQ/z֯/-YQEQT1?0 `SH"B0X,b 8dƚ DH6OKYB,lwU(~mIl㱣ۗ23,ͺ|H?1HL:[xd+ŀc3 (4-rt QJٝ-ٚРoxIR-=ܻ{ѹ]ov\ٵNw];.d\kYl>:K(b8$Y)qss9<>N(*ug:"`˹TBfZ6+6hD dIEDH1]A#ya90ɺ{/aٻfյ~ ֭_<9f3v`OY~AOrw#K L>ۘ ?1m;Ү|H[Q$xrF_ozYYw-7iјg2d)UxާrymxCo/EOA!cccEn&JHfիaa2_u](( oPXh z .fee$qqrXtND"X Qd5:!,x5o'~ ^V[Q  [Yvn9BXwv䜍%1m iGC.Zs$-,kR˝G/>feJ3:}g̑.K&Jo*~ PoB1 BQƘFEQTɒ?΋zB!c`U+IG]@ X8880XH9+ ZXa ` BfUKjf u11e^Fl}_zjs DQ R2rSE_ẘz Q!D%^(,L(^#yIH1ǀFՇ݊'$+G,bȄ,0d@vIw 䦌F`1 +X."@a)}'Hbve92>LFB;f)=>=8p*b- JLqu֛E1ugTKo/E) u..t (rԟ@$Y`읯Z*x][=4A0MbmDmm 3W)YY0 "@ B07VX\YpV]`V^wĦcgWZ?)1l\7?JRU|ݔ.nJ6>dz`D~x3.N  r bΣw+K@2VW6ۃ*{bf2.;O W`KY;"$܈h}BJYn!ESVTj_-QqvV&&Ó(^}#B@1r`XuQԘĞȾpkCB0եeLXL` mٵ~ m4h.y57oؠwÞ_[yi ͺ{u(!t8;ʍ޲ |׽&^`'Qw,h`<2="괝G&9[8BYWÔ/Z)sFXJ"򛮹-niܧ ڕJ_U(sلTQk4HhLQEJ`   &ag]{Yy˕;! R,F,˸e{ne@#[ _>Qߜ-:*W>uX)F_哹;>y9@o\0=s~xI,rԢ M/l2<,Y:EQ1 hzW'ׄ,I6Uׂ(z=#H1 o vmOQ<0!!SL.!"] EQ B( (PEQE F{b3Q*ԩQU1,Kzj$EQEQEQU8`$119L2k\>!e9+PE8w4;}c3/~No-(($!A WRPFV+ RE0rƥ#G=6޹|vPb6!Wc%EQEo4(z5,W1k;o~$ )kxA[SEQ+q=(dqMիW^NKv EO؜|~GOQX2,ד(({2~[8w_Gz YhWAkDSkځ$kv^Hy)$Р֏7=~B~b/˖@WޣV!^$("j1G %]қ 3Lpצ[T#ޡ79Oesߢwd_m:4+[1܎wYR z2.ټT ܬcj8{O1[ mS?\H)V,5.K="vE7\ݵ e 겝F}Z[8gM?0(0:+q7Μ()<+4ԵiyFw~3j'@xϯUR;YJTNnٰ#~=뼠O(J,0Z ˰,2 ò}V0˲ӟ 2X΅1dY$VSnn,IQ2]3i-K\j]̄~ЏO%Vlsrm?.(P"bsPm_~Y@:`SמSW[6=zkg=tfwö%(wTlIkR*!?4v~Ϧjwp7٧a?cS=[tVc$ݸ&!JsџlKٺ*󞘝epZa^^u<@*wӜU‰WmޢH7jӏ8qɓ'N+BQ3 VJRT(xBT*x BRp<#cw8Xs@J(>ʅgHwSr{M)#!EsMD6h?+h޺!URڟ{;mcU ;)(99DQ$Dxj h%ST7jZ/ݭ^~7ұIP/˲:Zyy&O w(y:!,1Ƙ#Y| orjE<,óceXq bBd>, ,|*IƢEK("0.0z&)ʝkj >z;ʕza5km?5Z313FV_>;>sH߈8}o(UeWĚZ^Yer3wVq󂳏Ěoʉ^˄%ZpΟ/>ōong V[us |Uea=i`H>jE')bNcؾ3b,ViS4S>ri6xPҡe P/dLOEQ^{ LQE+c1!˲ _UY" @Hf!A ܛ47RŢ`AA$ b01$b@ !ctxrl<KF 0H[lvmN IDAT2m@H_>{ג~z@ݭt 7NV ,g^w$6-wԵ{i`8(  VɁhYONIY:N~Vu-tc1s~.b^ϲZKQ`? &88Xs6ǷU0«xL6@Ǟy#] @JmB=8<|Sׯ!e8`otAx{8f#eL0nRnB.-#J_=C+̪y ӛt5RTAZշ|7{M>R Կ!흚$ j^tҤI@;{)J,c,$NNjҢ튰k0lp?֪6yEaKx(c {o0eEI&хU4XϪ%B `# s v/3/ɤe vUO kSw />/t( 1aݸy_F{ө BDPnګ&W~Rv\`]q*)<< fY^#`S瘹> V`,Pk#[{zuxp=^T"@ȷy>x9~nYOޚb}"RymEdZYƃe$j V3*Wd]MU=}i>t=7:"̾ Rz|}L'2r:.h>5Rz5sX뇺bJ\H _x}?޵t+ذRa (WN(畘_DyĦSKRmǡ!-"j6ߩK^FSmaVz`ŦV/گb>+ eHu;~9e! zVS3 uM``U߫\;Xzh]ێW wV2j6~Άz8"1gf:tzU׀s-vp?9 s_lk,D  Ix^A`(rK$" @QaFŽOS(nՃlN(0x$YeY%QƘে)kTP^]W6U1 Ϊ_QZju#yl@Z}3%4˼}vcG6u*>{knJ7WFf>8HxbJ?9~עMVHgԧĒSY~;NPu(jL, bΣwB请MU3f8ɨ(}yzGrїmƎqQzEl;ljۂ]"~HTz]rϟS̼C-h SZ]4&! NW-1(z X$ID "˲U9ebr6LPh0U;x[Z]7(15.2&d_ZƘ`t7"RZpovІ64}xM|{ҷZPyWlPߏaϯ:WRa qE`yHuWSxSQd"K.M(2:c2Eϑ-/oΖ~wz:l<0mjYMjbm8|qy|6|޾U?>ō量9Hg\+4lzoY1;kգ(ѕ )J=vAmjZbCT0NVh5C뇗xĠ7mF1.K2,˒,=9(굢T*òUc}Z~݄P8񜽱dD)U%|*oĉl86(%(0fl"s Zl0,dje'z\nM4$eY VQEsA{Fkqӹ,w g9y^s)*s)xAɓN)߯EQ?Wp4hZY2& $)_[xZv/bǥ?n.WwJ$,˒$I7A(ꅐe);;ʕkᡡabh>wb Bkq3+3%8$4UrڇKQU\LDIe"aeXM`d_빊jI"Goxa kbY $Id e(z-xvBdddҥ_u]-z> @]|Zj|\W/e{xJR+&9JEQ#BzYEQUy-8IXz~&:OgPxvy2{.1 nnn:OOO!o,'%=B&B$V?Ir+Fof(J J W1[ xnM[q ߾/vUޘM*,'m˶f1+9;όk3 ^ӧ[뭟'[t"Qm9['TUxwUO|7jzMI[tdz <0ƶt9H,a*Mz.퇨ݼ]X: ;Et9hQ{L D|['JmDj>̘MWWL @2,_t ŭ1DN=h 6Ԛv8?ݲx9pc,~S_tki`_;hoK)_g]0kOG2$g=~0//c'=JΧ&b] f3ɪleȐv]^UJ3EQyfWQe2gmкh;W8zAXņ%l^][젉5@4kcW0X Rm;;b4:afߙٽwfxVyC'܊9>sle͖@ GI !)x(,YrYu74gݾ/l|nr:سտѶGhiˤejɲ\8$7ԪE}u#ƪظ.1+S ]Frk3{8 R =k,! uz}tgu]0BĤJ[×'d~9nֻ_Pa CBI/@  E! hDQPqAsٷzxTwmȾWosE  @m]z$ Z5t0V90 =wu:y=[ ^3鲩1S hs)!VA+zR޺Dg i;Fwٖcz h;\] Gvkо58`l bԾ ~O q8HSx0PڸZo;}&,տoNf܅"G:{KlZnȒY>Y?gȤn 9l;f3M3mf lƠ QN +(w?/Br|- I/8>9B?oL@x[0P(9 Jm< glDe!mͲj/( & h Odsx񅜤dUC7SDfdʥH 0%u-'rlS󇠽#ߨ9#|{sf*r Vm]ɧFznmmB a;rCp i"Ud@Xb2XpxVF')4[q"߈iKw} ȬC#_=k\a[O> fv7$tTGW7>+qmjb_;˾+)@f>-QzܭLvߐ M^_D6^,d irbcꭥU8À .桳zmzma*g,ݬ@(+cD&J+mjg y/is;5xłau,Mb CKJ`\庄DVh(bFF(-ws4j{"8Y![Pe0NU-h=uq& .#!C{a}D@("*5o1&b`Z [eTew Ɉ@`ˑ2su'T.zh yaޭ[HT龘 χzX:!zW]~Ϭ #jhKP6z^5 ٤C Wn4ks'ys؃AIP DUm~Fn 0zB P>[6i?gCWYK~'5u*$u4)9j#$.9T9'࠘u+UOWo-,[A`195/^VsoUe1`(Ƿ1%׵j&>{򱓉=GT+#[c^y@ K cZ/&*$|f+*C!Jgs%o*jInGoVRҝ?"#6ʲA;bRYI:v 7 l~aɂ'%'^9:k=sG.&V>Lؤ\ƕ="O/=?~X`r,OѰJ2սOzǡҠlFsj_{pұ-+~WV\7\ #@`{ؖsnx Q.씕O\v.)=OJRʼP_\̃'o8׭Ia?ﯡ,o%V`;FPT]4Ӭ ?L3  Ont`t__iZ?/}B~Jbֶ[sWUWqZ^w >-:~yK{I9%1Cyϡ<05-s`$щE99ϟ??,l7gLoa( BI[Nxap%2`zt(A h5nd3l {4 ]r?.X lvM᥌q3#8nޜjLsW H{QV{{o2v`\z/]W^;a@&'e|Pv [ԿZ3d=?,=[,b`˗DʻdU亩MCZI'je?k·,[ݿXuw}6EF֕#;~7 dQ= oՉdޛ縮sM}K+atneW *:}Ct/;X"eovk )3Мϻlx2nCvR κϕv›5mr{:Z"=̢l@_a맕X+&J>@XZju6_^5uv˖'ΈJP2ɽWJJ3"e:c.A_!4m@}4VEac1eW>mDNzqړ;Wͽicjhv/Z8mJk{')gY,V'>zXTq4|˂+I<q8ݽ_[c BBS݆.*Sh/ZA輫A |RWɗ6m_l7HuE |HuYm+]Z]6˚ZR{kFYy[>XL3$6Ul%F~KM|x۴{q\yZ;Di[afvʂAk2iFQGX[g\GoYCcUA +Z?6 pzGj Hpwnx̊69&iʡ5k*ٖ5Џ wն? Yig1C۵&UxĚ)DֵO-A|#m?piG@j)RFmB"ԩA{5 b7 a Q)EKrq*Aa`]^&H>!gF\̶׋{U% {вek6N@5:L[5*VŁ gcNB !z| cbi1a"*&~Wb~凉Tr>SW/%Zbcc up߽gEvvv>} ]f5׺' ڞA xx{}5Nnnn|>PuYWU;9Hg gI+;Lz |+zCܡ B^I\:F@S:C-Zn -Or;-NI  !_@5%U;-3QAcDvF.k6Xx$E&KN.`xDǬj>XF Ԝ?`<:N8Z }^1{{zw&#K(A"!A@ ʦ Bi-.oF 0y>#>MG8.|r%ŦȺew] ѣRiau䦦rӏaB`b헄Bb#@BX kc 4@ ;(j%@ ]c @bj(}y>1gt#oe7)D b*\BPh( Rd쵁@(&CD! JmI.w l:0"QFfmָ]@wUOPE?\R]00B"$+++PFjgg_@ (iXE! Q@hRMx`/. Bb} UrLʨI4GCeᄄ/r͟w$~!Ν;,˾ﹼC ŭ[a,{̌c;{GM!Qᝢo@ AIycYs7gsiw6e7p=~]#^ sM jW%R/Coi B1'Ocv`e {+3kX=(ݻo`{:nVI0/\vv͛<<zǧjժ{. sssgggSS έV$ Bh(M &CexS$FlEY7dU ݛklӟ?)6!5B vd0% {n!2qcgLsWN,A}q3=h_&T#o)D/%%ݏ}&s6Isss---496669e:4ᕚ&1DzZ]ʋcYR9Aa;?W)@2 j}cjEYV.Թ(a9w qloogölĊ)7:챭yeT*oدTu`O@e#6l\ղ GPخNY+F/ 6¹L5̜h UAUw/sYwR^dOoU_s'*F\{p,L*fM4MSEBEQ4MkӶ" :Ax8s KDj8FP%mitPPvS(-L1_o] 6Em ޱn6 :Mjvs at>fO)4=Hnv<'E ^ V4_l^,73yzRn?fK ڏ9BoZM LQKgggT&H$XHD̈1#Kb/1#bD dz,a5˱Vj4F֨5FV135yB($q?μ̯ |rܺ}>tR@(? R d;h8>Fp) ,Au]NŐT>rk/́QOKs 4G_H$'j05f4"a#bD"#fD"CDb#b&ayAxc9 q8e9հjB/K*5sLdd!+fAܤ߂ml?e@ "T*### "22$$)X`1σFS(`_2rVuG/IDݭ@(MX @ T 0 AX@8@xp7,{9E-s jhϼlu »ՀIյϼMcnzBM {F.ϟGGGh"""ׁÀJ,3|{ XĈ|< GSI@Јl<|lԒĐl!.ً@xCC+ ^aR@ *1y r8PaDHxQ4P:?{$"XU< {6+'vo$9}z\O$u vh4bZ#mFإ7*i?oFsg\$k%<{0||j>}O]N43╂UV "WU19Qwb@V\;{Ԯ-[j_˃coJ%=z- )`G 7BC_KVH9&I mзbyc5hyY^4 `ffffffjjjVXŹM!ͯBfJկ8Rsj5p#@A ⍄JCqi'o-Mܿ䦚+)@f>-QzܭLH^+SK$wSXd;HְV!U?>C^&4-N Bşߴ3# "/{xWشLOgq)r|Ӳ5E*6rs0FQYol´#?1&Y'Bzn(NEɫ\%w#*>'5uHbh WSr8p*&._SrD3ڻ4/Ҕup=#X@hUR[rP.մtj9!'Ŵ]9xgȸ̪( z/37w`IS1,!'Wj?7VnmZ4, D&;41» 4q, `yǬC"1ZטV,&f tƌCnǛjx8ΡINuYUv2Q8]U)BHbi Bhg4߳ @Y5> @-Z7*צ$=}w#˜- X_~^uIyG>l;3SOILvks]̯iLh`\klXfTf^br { ey)c}hmޥV/z8@X D ܡ /`4j5+yE"Ϫh$޸;1Y* 0cOVkfSV&q"SKk)ϲJ ́Iig׬eҢO7g@>g^\g؀=պz;uҴvaZlje.?WکtuSeMūO%J@5Ov?<(xcPkl(V,tA66l3t^5i!'~fr&N :=M|,N\ە1^ΦXT\bK[UoL6_3AW 8]~N(&Q_Lh\2!<# t?o[_=&륹ki~|ƽ ^Kw%%[t.MNsTBNaFѳjcV}ރдQX9 _M |XL  cZQkxEӜFI#y!cZxo^-w noS /< bh m>׳.=EyG.&~xy!=8vCWhG_Ê)Hqȣ{nKҭ_KsgI_dŋ4clf)utkr>  KQ>p6q|)p {&7L IDATyihԄn 8 c0u2  t,FgTK_MӀ"{ ja-\-&ǻVc \ѣZ+.H$<ݵ%)Hn㐚hnܞZN&rh SW7 NPLٳcc@j_<X]wA`{ }8::>]ջYX_(<+h{%8y &>L0~@%rcNhhI*$Z. 0fh?ӯ[_ybVq֞E6#?w*|IW0?1{ =˧ WO>$<jk/h`'eS /)M}k]3PFk0v#LE#FʃV]fƏwڄnXXq~Reo")oȾUkϟH͖-elƖ.\Rs"SNd'w\N9o7dXVѵݫ;ooo_ԷH*c"7dvdE9H$RRjsb33(-@ W!ByeUJҪr \.뻤6tXE7߶Z֩=]~YuvSn!{Okc&J^N9yBVV6FEEEEEm kgp+d^-} )oڛRwP30zc"V5^r٩ց( {@:K^Kٽ Ʃ Ȥj-뜛q/5ڿ~%!VY$)q³Wb ش{,.޺QY'*ɕֽ떣X ڗѣm [u䦦ήn.n]$R`U<߫R*BZmU7];?:+;lDTrp:m®EhE?#O}ЊBapgqKlG!zʉSf5|#soCj\]{'iW4D|NV6|jj*+ BFՑ֓ϋ?S!Qq0qcgLjWN+7[KĔHP`R{ "~wZyE}mxOM]FO[hK=2zٺBBSK'vo2.c?v&K f)PEHyQCX,.i׭@ BE1 5 yAoDϛMūW(n4j_z^Ens7=X ֎Vv|[y6l;ƨANq j;j_i*rYئF!D>-*ήF_2HvMu}{l}޽7(cτ-qAa O\$rtP.=Z[> 0.ƫW0 2 θ7oE,N_y+_mjk@K] l#$zS;v;*B2HJL)%IUyKq&B)!DQ4 ټ LYjP-!ƣةXПJU<إ㜩#B:3_/ oع MstukoJݱܟ8zw(oËɢ5{nZrʢNy̓r7[~∡n/SF9\t"׵|`%%bPSv]7@m"O181!Hqceeq 0ÈRK:B6@ kOMQ0)m#A\QsRv/ArPx♖0viUzvhJu9wǓ@ |8 Dy{{ݹsq Q8BPܺuN_33OMI; ‡G@xǔGF寖D ,]*kU{ \A?&/oY6 @87Fs^2"A$GX,qvviԩSX$5hPZڦ1>AE[JсK o$X(€u頋ݫ,ToQ@kR_9 ;?(DM ڹ}+2@AѴJ*.{.mQנQ)/PHv`R @ }c 0 (@7/vs< ^@k RGcFQk++B #iFQHh-Z84}σ@ >. @SBzI ]}ɹ_?>c =IK.OU#7§XQ=},//OTU>"ԌE7֓aĶvvfbH»AȹoMދI M& t7½uKw'US$!#fQ3&չ+7H`@ `u)wh1ݳX%@*y)e4X58ڲm^1y_;z  粳o޼)`&///;;ո-Z 753U$RQf$O--)+fU{Ӳ%Nkx/9nFټ5g"6z; wޙ;ƀp 2)c!!\\[TZ9AaU, DrGVZE$XzdI0C@vxp`M'[~xTm?>tf:uv9ΐ~EVx>lje.?Wډ B1LLLeffFS* {2 % }z+oJ[n˜W >y!MBԌG0Smm޵ǏM\hS;S{&./4G|JR 44,4t~h.!VD"7H$( PRnZ:9L0'±|x J@(ZθQjɱm eiw7]@Ǿ[C;WRd||ꦫƭ{ߦ?Ϣ}jhIˍ_Wjct6G<5Vy|,) w;Ġ a8z7Vll^ۏUI;0e>w~Ի~}-n7V܈v[_hAD&(S jԙKۋfa5OW?tQʭ3ǾT/N5YBpͲd**>0lڴB}/, V%^,ߩυa;@^ ?>BCC^c `CeNʔ6v埉mܸ1""bs Qna 0 BeGrk/ziY2Ů&FmΤBX$rtt 1u4(LE^B@}GQg]FB 4E)R4(E ib@@"EHS齇NBIOl$%I0;7w}wfq7"l@d,M2NyEbި-=X&(dfr`!Ƚ۸7@Ϟ=aB!]gӽjZRS%}@wi ^μ~Z+P:ksfA4.ݬsĢ;=`;XRyuF]26G=Y,ٳg@})9a%( Z aːzy"F_acbJV~_x߃z\B _iに:Z%ۺ(ϲ\t5͚i{~sr'~Z?صp[M"q)Թţ]EQT0bS8@n HTB֦#wCxOf!`>2sĬL~1Y0=r{wWcp#k.Ɍkεg|!y[>O8E=;/0c$S`.C>V?.sn՗2>^]{/c/y_hAw%]+Ͻd/{s.'خKz f;B^;=X`BgvBϯ(Jeg 4A?X#%eNg@}6Y"Ӭ1$X[SN{IJnʰd1i^ƕ>0dޱޫr0뾳}\hLQMdȥ9yeCGIZ8}ټP,?voQO/O95ƳƏçY_Ç.j9+IǖM5h~ٷm}X!GҤۨ|H^??!C.- x)Wk* |kgqi3f~ʹE%\|鋕ՙV3N8᫡@҄şV)}nY;`.͆EQ e? R??~a)w 0!0λFfTkGjN._KgpjMFaMBҾ#(殺\v ۆmܢ\4FgM2.j/?Y,Z Wrk޲kux 'Eo<'7 }.6}+]!L18GWBmOi12'PUgbKWo30ҍMf4fıq_~slދ~hX 0<*VP_7V#YoK=|/ vۮ vuG._,oOQ W KuC;99mfm TovǮ#Oh5| Xw^+00UW@^ZP ]@@!hXoO6$l׌R) T1۹H~04aDW+ve$xTS ٰKO@=0d2@  &Kotÿ%b[;Wg!mHwkg]qLC^7ypsrʻ-\txiD#_~}}ަD]z.䷚N|QL?gԳ_tښ{ R3/]Za聓N89uX.,й¾I"|^{J&e"aE&@m=G;镝t(-EQ<# ]ٰ䙻Rǀ5a<|^|UWꅚs] ƺ4(*YF(wWm|'Mgt + TR _CZ/~mE}[=h0Ŝ-y^Sf/ZLjW;]64ޖ\I3꽦ګ@ѿ1nqǸ"mhrn>{Gs{?(ַ?;aާG(GQ哒 Fi<| p0)SB)b?tx%}ť^ߪqxY/>I;+j 5X\f;ڎhYI{d?خa!ƨBxm6'Q'N@/rrDݤF;XO!g>o'^_v T1cJ`Bb U||<{2](zf 1& my IDATF00ћzIB0Q?Pd P^EQ#w k9@{[X~EW7t+Th9h#߭x5Jui,m&:򞱕z'E@r%{?,f/TURxp+uG'HfR{#Nǃ5!:S8-3(nַF^՛4aح_l7JcpGXљj$L3BSE9R8F RVVıl~ȋ~   NZbEQk6AVrfnFl)9`-TXGZpfnVX]mfm",Sy6ǩѼoEKKV3KceDQ+l6x{ymuzdz[4Fzi^sx?W^E0Ipݸ _ƯQ%r\_Ye;OVW#\ i^ 1 vJwN^'W=0RDx)Iw(iV ̜G74BȍF# 0 +S3ʸDk*Z ٧:=[GeYc9} ~٬|\(/3>ay_?wMu^)Q90Z7o\q~e@][4Hᄡ3/?hSR2 [dW2"߸J;ϞkG l`"p~ҮGKVM _u;H\|?O; [ 3|֑_e`#e^1u^85)}i\Fq']ig nvLz0n pB%EQT.08|~AV\\\B,r0t(]N![&_ ϫkV!\3c>X_yqu>/tt,W[W}7eJ{Qqn> 9p B8n9ιq1 X_7B풆:-ץVOꅳ`|6oVGq@tRc .M}[Mxwx?:]mo;kX ^v%W^kʕ;uɟ]$6:;Gtϼ1ʮA1!aӛL%u)t㎵^}<O)"大ɼY( %3j˲*4yސ \SEQ%[-PE=8FLkI2ly9SWN@HUgޓ.fڨ!v )cR;菾D{@˫cK-{K// :+IzAoxqk6niGPh_e8$PQϮ\ 7EQED`>zԪ1S:τ -ʑK&MQE= INZaYeeaXUP \1VraeEQd74 !c,y)1 l' 4RAX, (*'g/ (00~~~NZ'FhF^A#JxN9YV$I%QdIDI%Q6QEf}nݺ<\Lql6_\[3~Ƶvh(IV+2VXİ,.]'''Eqxc}zfsMֺlYIQEeYVxLdSEyW Ųdɒ]:99٭²~p6DI "DoS^7t9yyXo ؿk|}:Ț ,BW~Fh6p4/吃TXzDQ*L$YV"ceXjAQ`Wzc\#6[dK ˲:L0!tJEQT-ٳ a>}\0PðJf>DZnؘUEj/~,_k9ԩt N8zK޳]*aoꙦV'B\EQTϠNTmV0E@][!D+GՏ&6ĿjN( &X?ذːϺr#.9 Wlk{wro]NxK=b΢;4ֻ==)+}:tgE WY7J9W fqbkɬILWx];V$͛˗/OJJR  "B Lp쿠*Mב| 1=^O\-Xzܔ!o^藿Τkpk++z̷B"[Ec;oǯ7J AWR|[R܉)kAvFX.m[<=(RE k{rrw+\@6k{7u }|n"WWg("c`m|hioMaF#EQxqБKs3i 24ᓴqnys{RrK4. =H ,T{{{S-z!bipϟ ZtJ7{('Зs&񃦞ռZ?MKl}-M$n$2dHO"/E48rtjMAxtGWp9J[Aif}޹Z™|$آqֽNF[}h^*|Jh8iPpm͋V-9$ mځ W#4ҵ8րh7B_a#K'BSEdZx\:|b7T+Kv%j2gg紴4R{!-; C7XBtI0EQgbKWo30RStcӤ}:qli̬{dgp\׵å=>qLDr{Ptөa+ 87&n+EиK]GS$dp{ПNNNn۬xp՛ݱZ7j򁿭& k'ډ `Ly!<|6('e9QvF"@`@϶g8#N`!w[|Yۆ mjô4]Uf L SE˰1A )p.Qda~\HD!! @ya_[]e&"}+5}kF x[ޤ l]x};VDDtU}Xj{[nܵ?n[}t_`>qc_7eR~=D׎ nӾb3(rn4fovo)D^^:rȏܿryswWoܵA+mת\rCDvv3;I_m(gXm'i@Ě>3nk4E7Dܾv\5 =qR~WS)Dn%/ׯg)0ᄡ3/?hSR< EFq},!  Pvb!,W !zhqߜU <6ѣLKx7zW/Q4 3~`(ⓜR+EQT9e? 4&*>e²)fN⴮H96oo6_H&Kį)OC;ӤyܯfqsO$wq o&aM'v)wf@CbUeovk5`;;3WGXƶ%r#\Vc 4K#*H彫;+}ȇb*n9|R}Axnm%[_pD{:<)I[fKqk gS#""bG~ۼYFӥKLH Ů&Hcb 7 5uŻ.^y"VK.U~\5pޯMZZm9PWkҥaOoz(>A`|o`bgX!?YF%L)~t˞J⦹n]BYSp>eL18GWBmOi1*Z5o:Mx2k 9Gtmsotm(GL>gQа7# d*XP0Mܹr;6Wd g9( IDAT9eW2_fg_!f$ ~hFNTߙqziovj+>NO{Urzm%"sصkWn71^׊+322:wso}MssDc|-N?T쐳nܱ-F'1$"RNzz ƒ.[2,Bc!t'$ʫb0!(7JP`AKVv2x6gZ9@[F@!]VlHu:[Sm9P4e^ s@p j7J]?K1 TFФ?jQrf_7g wEQq^>Be|rrB']&,fܢb1n ~۠l{Rer i=xFz^?z*^KxyyuqevUpapX7xG|vɋqh>,ԇ $-ꌜ=F M- *hKQ`!İ!$X5h"v@'Bb(<@4s>wm(yf5\~-WWZi/ckگ :o?|pkh,${W3<<Zm~>IzAoxqk6niGؠc~{f'#3Oju3ժ9/5>w#nZw?CSzoqX_!)eɰȷgOfxU뀝)(OKBQEݣg<& >H /U.)wO)O ڷhXgjIV֪vݣ]?sBZj:nޒ19߼OSzPE=l`C1zڹC*tSưR` 0(4 "~fm, /4  9[Nzi%-_{zLa f|_xrT v6jy?D{u*{cwx*u({AB!&쩎??ͺuMêW|o:r"@~'q,^JH)0 o'{zzMQ/IzKYȚ^]'HՃwlb,$P;QE 4;PZ#ZkE`fغ](ydժUFD+IIO/" )9)YD矿!k4@l?xҵńwWENQU0 u$o3ӠA =9SU Ϗe۷?EGtss?wnA!tzk_uʺgEHSUdݙEQ7;S @AqL!`h1rO/!DT#v 0&tz|f%N6< eOL׏渱o>]7uҫRE=o_|y8wC$rK,,-R?m0Mrw9?l}*);nѰ_X:]JUB,_됒'x qj%Kt{6t|Yn5oZAݶȏ%ϕ{k56.کι*Dц~!XLG8^FQU+m|[YzyWzW1/UjxA;.%>A>`Ӯm S,Ol^h٣Αy*ptc6ߟ}vߌ/~G(+Ͻd/{s.'خKz f'{sU> G@ Dw ΂()54Bo:Z䜇|'3j0|Pv}wO׹n;j7#Z<ƖE]{.T-S!3j侮YIs;"gRl0@ *^޴w߭_]~nŘ/ _o ӗ2tn;ҭSpXώ/z3ٱceNݒeoI ڲ|PT(rFFƉBBBZm..6..w:4)*W1& F00Ή|U@qn$EVB@a[@ɾnMJ}nڀZ7,䲭.n]O9w@ᔲexׇl0@Q9xyo\x-\7*hծr:OѨu8f ;)a_v;xذW$dee+YYGu+9;+! ׮C2`BXxu0MQTag LyT AV\\\B,rWd'Νѻ3ߝgog+Pb-蹽?{!hĢF#e֓O=)y-FQ{Of3z[-K}Ge\(_f>c|^p(:ͽqFQ۷~ܙ<)S+ (<2L.zB4AՔy)*DzWz@JF}~T)g߹ OCK, oܡK[ BzY:$ A}L{0# ZbQ+m{EBZ xl0 eA\R7O'hgaCQT[,%Ktڵmc|7w,պuetzv2%Kw. D6~N5] y87`-=9(4.( }燐Ƅ?mQj/{n{}槾ǡg3TKܺ|-|9U"ۗΝ* ^oˏӖ܈;ka_΀wp7fxq ѳת2CC6'ծ7}|.N7=_^*[CQTyFe0{v>} YggFb1۝$j1z4̛Q`Ŝ퇼_UosͲ ƧeEQEQ &33EqmL+˅?qlߍ%.oY9lgk6*p]w&|yw̌aٯr=N?e6<5}.kk'lZCqkC:%}P*׳}`ŒUlN [-W)/ߵkkn q˓Ԃc"PMJ>|^46ݫ}V !0R3:D4ϼW,HQ(P$V|<]LQTՔTQ}%¸s6..^*~LL#|w'ΝՖFɠk4 FŜ;e׮=M41n,yۆZzh܃=4{XӼOWN[?pO3;sOuyWzlܸuVrkͲֈm9MoDž:M"r/ݤ |8u ]π+\wvlC~|oX)_AYMΏ? BJfVI)+3Hɶ7 9˱,qǰ,˰H 9':i^`XeYaebaYVC-/p XɅ1Ee`h41MXBQ̬{d+S H×:f`ɧ^KeZ=ӢW~?s>MlѴޗK$Qlrv굓ۗ~U|-L/A`ʃ00~~~NZ'FhF^A#JxN9YV$I%QdIDI%Q6QEf#to4(W˲cae3 #`,b( b` EQdYVdYR"cY2&X&!AHA!YVO\p' 9779L9rxQ0Im/_|˔-.K5jTXW1<@?TD\-;ӽ0Ea0 1!EaYVV]{,VUduXQQ#g@c\p3^ޯ6嫯jyY3Mt+Jbi1_4mד ~̨Ѕ7 / (*" eQ!1DzD! ,2,bʕ##""4{Tl6YO;k2B !{ٯȲ(K$)ya1pIƎ~mb+vkOjXJڞ0v)-m/W҆zy ~*E=;os?=s̜mǮfȠZa}ZHP }=:`NW 6՝aJ#Hvz֚7nw|xGgbtͧvpԎsb8v)U |QLrۖl pa.[=-glgg|utUb:lY4~-gQа7# ]իWZrsFFFN|||F wށQkfvKϥF &HQE$ `HHE$R&!=\ݝylr$=~3$ExdǎLjVS$\\`Ԟ.f00hT$D$N ڹsӦM%I9.EZل{ A?oYLs7Ww|9R*$J)x0(<-L~QQ۟`Iߺ9k'ۆ 4qO#6oM6b֟<|enːo]>6RЈJ;D8<l{oqi}gXZNw+$ldC^<"W-Lɲ*UAQQrM`f]?jRR31\O/rl_zu^Jv%\u:]A ..m;t4zEDX `0b)b6[-fl&_n]h4Fk&&&JFFFxx@n^lo֬YNzqrD_ܰ' 'o"cegA׼:b^X p ԷW R֩K%Hɕ߷wo]ޑ1UI_:!Punr}t#!\ն~1o;v|_+;T3NU pHy4fNakTVb !rE0ZgTmZ$aNDQy!?W-ZpttRJZV5׮]͕T*Օ ~{I)uppȼ֩S-[HJ$|AJrA吻}ʐҸ%{h 5D}]l3/ock܊]s 0 + en=/8XgpJ\G1rvON~^@t.qx.Oz,: ]bdן=$`0:n5WeWA|"0 ^, `4`*@Sj1-f!DQ٣{ll#!DkZϞ=Rh4(Z}|u[!n4@r$Y '|:/ 5֐tKoOO^t1cW1E~zI">]0{%=qՕ:뿬Oqr#G3oZ'.4s'pjoeaaL6oċ\xrU:0z{S`05 IDATFA`Z0!+IZ}`4l*[jL&<8qg2JJJ4RWSD<fZ)]7/"Bգ;^^F5#ov\/T=NO_u`07An;TʶE[,:xҐ@qD;qw`C+F k^r8/ ^rweEZĵ!~ \cRR2B`02c1F#@*!/ oM{{nܸaX\\\䏄ٹ}@ZΐoLͳxjt|Ǐ~ggA7o\}QJ%QE8PJy/rV&`<`l\{:9ijR)Fd4*XV=.`41yAfS<ׯG~ js@=9egIIIIxoJEZvn:aq<<8sHV9J`00"8f!t#;?"^bb_b1L@)FЃ`0b1˗z|<ϻk4Zϟ=k (<<=5ZB*[`0 RE 01Fb--csڹzKQ^YdaR悵O^NnRY/?1Ɣ0`$ԩP\CΝ;#ZƷ׳ ?F1(8jI "~ /QE 0 @X6!\j\dsuxpN:$|!<#@ ՚vqƁZF9yd\\\5X.Synn CVR@˃`0U%1@Q% R,m9w!3UQ(Dӵq+Iln)(7wg0{-..Th={ƐB/{ !J!1 sf0 Q**R@`j-**j BE^4>g:$OpA$Y"Zwj8:ڦr^)PxF0 FoZbʄHǎdeeꋋޱswΟ'e|])SCڶ/>+z N_}BK_V|" 4^=[mԔOJQ`0 VR`PJjJ?Jzv~~&Uo,gqҋ KXmt׈@;j9Ziii'O޶cc}{k:1s']xԵ<;&ۿ15FH=ɰܾ8U`ؽh'NR&ǿ=ouxiɩ-׉yRˇ5C~^4wYf%^F3֍ |9?mW\u{ 5<m)ټ r\Ֆ)%U'꫰`ڷ콅nw\qt.c^r&t/o'`L6|͈#*moՐ !ׯ^70Q[iuqQKڏ90~DS( /?i:a/rqG]Ǫ }eՀH`0Ԕ"yyEY|?3MԩzPs&:ٲ^hZZ]\\L)t"O>ƨ,o&`ΣØKkܪ~?̘{φWh4;6lϖfb>?m^ \v5sy3c)uRƎ)nS0oxZ;S]B?*ؓr_ \4F)KY?'!ᝯGgNO|oT]K; h4~Oǎ[^q5=iFC'bnJJsWm!g)Сi`8𽖂`0$U%Bs6>wL;r3 *q{mcOOOJ/[Zje63CڋjnszIZp{?}ގiך# =mӒG1>yl/ilө.ؙ_7j$LM֭@'tXճA[Uc#@ g7~0gTp"q;`Ki^W<6\ykڰ^;Ǐ쯖e<wm#H"Ĕ#CcU*j9-Ŭ?dyx % ܖ!n8|lŽ<-&-hQz<fd]JBF&<3!!r”,klI_㸹Ro{!f|΂3Oz閮n(kׯ^:##CnXhѸ[u Flӊۏ\3/'GhM(ܝnf7Yrx̚eSPx|z+]u<XsoXzL8هښO~k}`Z.UC{'`ar2,P#{h;x<]k"%ҥB,(|<ݿc^7T*5V5 999) LFµݰW5V/VsIF*yF7gW{־ TےC'/ҭ8  0}r pb~~^w,ըwx- WOyN \-D Ud,7R/?y4RmbƉ\{ưo0ַlmg:#pK;%V/«)ԥύd4Xy/mŷr\P.=`tyV#eK%}ݜM/~Eք ,i߼>meI3_iO~ ?6}vwҤIf^k*tvͬv'<^; /jԴ{i@@hU@yGw qn$?KV xgQor+>q7x\]=9@,#'990f0 ;eBat&u~K~"BZ(Z25. c%E`0dffZܽYtͻA]SX\00p?*ݪM+ۚ2=_P7]Coug0xw~q.ѻYQjB*ZExbݡq:!w! qK@jvu#qYԯN .W~|o$Vm{A%Q{Km  |[k$p9ra;| ݹ3\0XgpJ V>өb6U rG_)M^sMkL_ju_׍۰O{c 1|SZ9'yK4z(ul!($tc~-lB]@'~Sl' 3 3 F2H GPi"ݩPeX>}⚵xíU^XvyV+V 3g:='m92[/7Q!f/==a xuҙN#/4WoaS`7~o- ~9˓{\0{%=1noic:vXդc6~=v:O^nͱ}bf%>Ƴ/xD;m8v&ک{2VzC=~GP'}(,=<31TX `jc%*s)_rӒʭsp׿ʕ+{p={ΝO  S*Ǟ_^x#Leպ4w$8^D,щ$ u H3n^ЮE5y}=Pct0uy_P98׆QzzyiWb܎]_Z"85szW/M ??ZdTS5ve Gzt+;@j!]疝`H_[932@Tz@Jo Q$M^no@ pQ{saڼuo'aJ)8Ŀ4}Hm')]2;):E]5Ms|*h9^gK?f|n!11+3/h`J a/|i,H8s./ǭ?w\u끃m{/a@ADHDΫrDp폈S`*0b[Uʈ7R)9#҅O? %@,VX^(5#&þg )o- +ƉJܛim? >;33[_k=HykBTMz-o oڱEsUOn=l j^mMj|Smi<"|1*0-jgQ '~{ع.U;YMOR܆앍`0U@Sa@lNF1 73a. w^(k4z^E(';wwl-"S H «G/5uwɈ+y{KyPOx=]SHZ+H7txd+\x(4Tye]?$@SrlǑ"׎w?4"゙•W ]ze턧WH#\9:үx3F.88}nXq]nYɄe%)w冎_v)]>_]⫖m1aY^7DŽ]8S`JZue[Eh?_mm .w- ȻrNpZ/'(,*'G͹%s^n|f*B= 0&%3ah ^}_j }x rWYO_)W5X9?Νuo)w<]+aZˆ?9R;ك/|!}|r6k5Qs `PuT̀T DK¥iw9sB!<sQ~| )!T"\k{Ӟ ;k=K}f/Tsw d|@Э˱-n͟Hӳz*oo?cΫ.Z9ࡷZ(M_YBsDo(  vYF]0Y:n=zrň@{(#y͢-bI2a曏:q£G\!MLWw~B5dqn?T-]2kܖ\-^7N*|ܷL/*3(qqU~2r9x}oaeï/yJȹÜ{+Su; {|sdn3tn891 a#>c=N}̟&57oKv:gg!{{{\+<A#&5i.d^f0  0!DFʛ}]?!$QTuTio {_m`3x]uՆH Ӌ+yPNKrժ't`]\\vTiV)i] 9z@ fv}@Udf0 0Q;kxKrDT*A%7MFmX1"ё"'pC3~5=`L IDAT0{pB3M]{)5t/Rl-2XBTm1u_M*)? Lj&~;>ܚ : &&2/hSzi%}+jcc}3(*B&x`0 ^{ Eeøo}jOl2C˿:Kю kg/׺8 %%+Vjʾvޠt T;`0U$*MRu)y@JvttqqAq<Ƭ*`Toy1/FQP(̨JiIIsssfFt^^^Je58S,䵭&-J kgR[(W朒Os|ι fe0 @#@P_J;R T*T*eAM=^1ܿtK_>-kM#j:.yVP\۱շۡkL=LB|o"̍/ 4uaz `0cB<ϋxRZ,72==u,Q $9sfÆ NX,J8"C=ԯ_?ooaS̐K*6N\m+i?NJ"9&) aSNI@ŭ[Ze#~Y{wPS{5>Yjmlsޏ/'^:SA{ѬO6̥nM{B,iɩ-׉yRˇ%x7:v))HOf.yrJ揌 %_3m.$я Z&t J;uL1]A"9V3)ߖ̂5R98ę#~P%}4+l.@?ؼ5mFvGMsjW*(Hu˛ЈJc"S~sTm_~u#BY)WHȄB0x&$DZe}܇#CI ZF*O2/DQ{3uUv 1CBkY.RE\jBQ#]v(M!776mr5XX=$qHR+1#0#8#J3]!!D(!B$J/518Q 0 zI+๤g#[jn$g7W @3/[KQw>9\}ū{z5d^gm罼dc%I[__٧sW6hnlzN7TkHbHYyduP){|yʕY;2*h$<4˸-LAYp5yǵĠ W!%9Šr>eiܒ=4]"G]`޻uk Xr*0e~ur_p{#Qj^?8ᓓ_~<P55?Kd\=m>_\uq FpɹB;[jh 3go)..={9sH x U!@V)A8%SK<؊J"$"D,U%hE%J"ZExHF9ܴ,W{ZƼJD JS`5[)8?ݧ.Yz?_̩$u^EeaaL6oċp=f~Sᷯ|*b᳑:yrϚkLZ+N,Ű3 !ҵkW`PWWG~~^ƵCF*/ZCl6FRrԩ{+`0nU)(!( J9 RLA> aD0@ !%$IX" ?)pb $J05"X M|٘pՠal^Y;҈/W+DYW yVjqZ?Ҕvܸ͂no4 |Ҷyt#ph>V|6t\G[jn2jyx]˶>-Z~R0tEux qkgLX1E 50X?ߪDZs]b0$Zʛ6F?(PB:G S=E*h-Jlh -Z܏QGX_Ѱ%D"@@ R*a0`Q@`(% 0%YHA {Ӟ ;kvݧ'%7\gΝ`9K|xBƮ^>ձ eeӗgwFF 0AC缝3sgQfd5EgC맿F\ _ٶh%sXO(o[yٸ+&tsx7x3%w˄o>ٗG>r[z~1}-gR.wYB`ˬq[Jvxݔ*zK`8P @)2}60Xyv%G9TVlh -AyA3뇱\#@DJW*988aa Fc#!@~RYI!$*2%$f,bq^7 vPr~Ufg>ҙ v^ Nqw[QJ Pbsv:SJ+Fқ[V@6djTοRoLX`v+\B#ԈN3^Rx(FW>A,CJLLc) }̿|S*.*вs xy1qC&=*lR+RB PRQKXcaS+4jA1v%"Y%s\lDJT,0R+E">H Lr6}ԫk>Tm&McMqw*<yxc+1;|mjgS*YE;ii4*;Lk׮˂5lS4hHT|!@Db%"O 9@ܳv:N`0[!6KMr359tTrG8X1[zH)BXV_Y4jL9u]86P}nDiwtt|cbb\]]yG)333={iӆ)w["hx@  P A#1=^ ny kT4J]QkjHHj"ncrqӲM-R1Ji%U􋲃;pkҘ'G-6UJ{)K9YޒFgZ1(\vyjVntLi̹f˶:jXļy8qŋ;:::995` BWoqtP+h;ct{7//;8|R0)ضU_a꾺a9?yT:Hљ7gwlSVUZ{8`wt̰v^rZrzř݋t;K}Ml)x>:͑IљCG3}6.ڙh Ω##<?[%:#Rr?' w0]sW[$BJWN{6?q$uđ^[:EO,_QB)ܣ;%Dw|I}}KCq3nHJhH*X/o|-W^s}rH>֞a TD 8bTjO2eW\  /^|OEc0 =l":Ř(VVlJ2R祋ѺHBUPH0@*R~~ն_@։O9rX}`s%b=7YA~)϶vWH/]F'/FJa0(!&2&TӋ:nH`/ŗRptY[R&-aYyp7WVJ@*gjbwxwrt[yofR,?o@@ d5|;h MmKϵ>h+OQb0DB*ެ+ju3K3W][{20 Sɱyt͔vs΋-ҥKmA$;C$%''&&,€ ƃEz!BHVS`sTr*7dY@o֟wTwB@Be@RDAeD$Bbddd@@@5""cǶ8o߅Gmn}!?V8Jn|J޻ukeiܒ=4]"S4D1RMnse.6uEv1>ѩim-5 IDATNTuGh)޸ѷC n#j,UT{^Cd߮7..zhzS-7S[@|Hb @ʶ_h洍KwZ#y;+HտEX9?|1Խ`̾Vߩkw^A4ХUG[6Rʮ xy?mP2ؾgGJk\xǏW[! n2 ЈR”P흒"+7RRRt:ݽ?x M`?d-LWV X$żAHYg(3{%w{@ J@zT)b)*"$TD@UwɕݝylBrC^7ٝ#{|C9"4dqa 2 8OLL gFms >Zɿ~x@/ wcZ/D2Gkl[Yй{A`YekTVR"40M?ſg]gTotɖ_hN(2&5ZuM]/a_msmDG Us_zaS.,T! bG>hQi;Ynkh> 952p}k94q)Gw(.2OVΙnk}]AUf.IoBQC>~w;MkڮC5SUV/Gi;^hѢ]Vva']9EJ 0Bɔp侻 J)}p8!LBy0B<9::?֭[uֵ:X!86ڸC_ށ ;R7=ehM;A^ڷz#CQws5@}1s zW1 < EGz 1Ε3qx~ *G (}dؾ*\( >_LS}B.\-0QАn4MPPxޭ3ͯb,oou+4;/RAɢ h1CĤU|E *0yW=:bĈȐp±cǪW^89P0UY9$ 1Pb! ҚD09A eBAkoҍ(y'N9 ɲsxJ@gg]vEDDԮ] #;7oiο1#eLuK)wAWa}p#74 1 #4o:+t% {c%Pl`a:3T 'ϵ^j /z|=GՅXV:o$HY("=hEr5 c\8 x()`BZ+a0ƚS@X5$c soBQD1:RM@B(ƜVХJT"TIEPWˡZ-tew i@zC(^l99 ">M61ޥجu@pKʼ&3P%¦.)0 Lp{&QCB*8M(Y'ZAykϱF7Tab2aN_ͼW!jS#(QtT V4QR{ ZNnJ˲!䭈|ae o6o4VvuP~1/(5ZYi4y :m0U`Hx"SkeCaǂV$Q-onZ*Z@ 0,;;jkV^+RFc'G7.>X10Oew4N}J/3(.>>.6eya$ JJȔX(H@$dJE B$JEB;ːx]ޡoC5ǩYQNOJEQ y{D%T 4 v%*ۯvwTO-s' tڻ[>MwԮzNɑ+<0 q׊xeդ'3DCw.O )5^N=k-D\4 7B֍ $npz7=d.fP/Lp_vgQ6 a'L6ƸR RJ}MG{_8 ~괳WI3{]?x;=ߩ{=GHmp:p6 OQVp7u"xF!ReK)wAw PZya I?4s’s^]>Yi>Gae+0C~h񵝻=Xo37kgֹPrvJ6]\Vlψzk7}K㫶'=BajU%G;g+ tA͇OmxXN799;>~P.rش og<;TN[/n-3~B㞖e@x/:YԱ˟MS-0LYQŝ_y%}#R\H+l:yN'/Fr>Bt}ngd:7 Koxsާ)Op=@ȀëX7zGp;7a/x^L9?.E RͿL#[4@{߅Y٠_ T{N%AYis@2OmXȅmlimffM[`)"hٽG ;DMIZ忤 sWQu3 %?t]C %mVM(KHP'JFQ!qq-~YYub)QUzߺY{`g2m|sk;k-: 0UQaB;/b!Y]$7toֳZp?ͣ~p.M ծ'@5|ĩmN|qp)DT^^ny!4+al{p p`y[gn({3W_3׬^$lڝ< >fhvwۏܓ&&o3߬;QTt :n5|?rRfT4hժ'@P:zS !iA_b2O&έGqiS\aj &\i%Y3tX6{# z(* 0 aVTqcʬ`㔿)D&y!,˒dj5"IOQj)a2ʴ-GuposP=֦Gp~%^1+[F\RBWD_/J'~RjTSJt[3ǿF.B!/tM\3F{lwߜp1U/ì[5VUʮ\*yГMRIӦs74{|8>g3{٪=jɺR辶,I>766 X6a*.`l(zÀ1jjZVTZVUJP *Z╯T $ɢ(ZD$JEh-fbM搐0A z,`sssTaizUX`*4i̦{Ew_TC[9"Q!NŁllBMhDZ@AS;} >DڱPH9A `FjNl3vرmZ?(4Q钛v(]GEŐR("hV==Df3*K̙ / x8W/(] ˄YD"$Ih%IEI-ْVi&(KDvl0/E}jlVoԏdY4{RڹK9G#7M9s-'b*RO'&;s:E ,9wpCƼs;w.1U 7,w K+x ={! rʰRkDzrw`5AjM{VoXդi{v_mWSz[ʭdѢTlSfJ?qD8Aë8^x?<<ELPCr-B$˲,I,ILdH2%%@)E!!$IfO)yv8:P {ݡQ5{eu#ʬXWfߺգ~$ڸ9tk4~H-By w7Lсu"]}NsC?MIR:!?&Ky@Zm99{.n|`NMl^hdնÌM?#9p {z57qaӜ%BFސt`k `֏U4} JWH/'x34EQJ2<}d6fIceP9D,cJ$(%qBfl$"r_YRe(P RՀa8Vk6h=N _9爆^}<4ȲΞ98y>o+9811){o P"%dYX%aTJr%"K/Yٙ=z`/N0aaq,eVt4THQE/a1Fppe B"@TSѨ\JI~',ILD"/2e(a/0 0S%666.+0 c9$hXx8ۀ1Fs!m5qCeׂa@^.xbbH x S=Z3td}:~vr~_Ѥ7jh!S1o٣ wƄ6:s0 0̓xhEd2YLJgF~e3@b1[,lX,bHd~V`h4sss&d2o2M&I$Y FD9 M[:v0 0OpApİ@A8A1b}/I"\`b2FP[eL,I(* xOa 9 c'pGm}|z;_7۠ooUS`}s<|99~ ^4S^:__:Ry;>wɶc&lheЀv:dkxioVEAZ'6wkG+jUE1好c&]i7qrī?xG/U<'TVzjvP^ǫoO9*EMXv@Zu{F(ovbݼE_ͦ}h2~bÂ%Is]|[\M5j0ENJ;z-]OT>^t0mWW:O%/@W~l[{ek,xf&3Ϥx`+3 $roU8.#^@aĸڵd2PɔkE)}҂`8/f2 Ôr}˔m?G sEX HyU! @I[NoWUÑܤi=Fv ֚ocɒ \۾?Huicf-fꩭ?;udl3IKp:Z MH:"ES**Nܹ|Q]Q͑Ϥ}dn#T'FQS/'mzWZxxsWCmG/skkÛw '2 2 vCR_6o2s͜SFQ 2!$axA$l 2 &9arwǽZpęμh 3PeS3gkn)G?5KdKGߕQsHܫ;!7ϋz5: zjiQPC>$dFtHd%LyO7/^=8q[؜a/J,*}hֱ[^?[ .!d:y4n{u>@Du/k?飆%ֽxk8zokɋ:PAM0R@(u3 D)#1 Fy}1F揝FV5SeluAl!D 6a@ڟ?J];t5~>X 4͊h%?Kv>[*88\+yĔӈ>?ȧUu/8Ͽк]VK 괬s*YƱ Oǚ6Ӄ=>oќq_V||4aHwf % N&94p+ާZ-o'\Eyf\ v+xD԰@Akw%Sdž]Kmdy̕[:huv...D&))wX/0La֣Y~=W8ie3wF vخR `ʶD`kq9 5Y,a'G6}~ޘ/nyX•,vd_C7[L?xCJ ` H !owo@ ax)bO[ƻ3gJ%W_po?u2 o pm3)aA{ro?q%,NfrAqqR1 󤰖 ѣJg[QJ)WcǬ ow T%{{Vc=06Gp-W7ޙ׹[.nv +ծCл b뾝&Sg6m)/E#Zӓ"\ujɪY7${<4NW$Ԓ);vj*X uM@{|Vn^YEV~3x9ϼeNQ_oJn P{k,bkߩk@Gui&_ʲ{׳Go#=[vW垽i*aYU4V>8L7}XK "1(~5JPQ@KLr~=f?o`a*i_ؘ?iUOJJ)G&ɟ> p9FâS3pc?pbo۰XND>A/.r_$wnhވ39 Wo<R;=glxeTY}]=Kz=CyK* jFk6޲@o}2puKi\< l92҄hū v>Q>rvάeO? :!%LF>~ђͿ}N |uj5ÔAlll\eׁa8탕cH832Ŧ|3E922_ҥ}[&g^ڿcT)%{jҩMGeZ^*h]q)ẩMl>X;0ϸU';߲u[^SՂ ܜ"Sӆ+|4:}7kL ~̉s>&}Sú+x>NwCdHq=(4oA >!&`[ccc*XYaG1_23lٶyџr<<8t>b}ғKaB ܷQ!J|E{)JPs1_EԹү'K%Xas$AWUd6LrUդ;eCnٻ6ѯ\=z-/yQB_i < "P3P<#Q}Y ߷Q0`@a0HcWs| TSyyxCfz @3v {?/X>ϊ!gwa>&6@L=k?a" th /!n_fٝ9ѯYn>5jy )k|uaїl=FoLO^[~`lcWś[Ots o+M.Gc'Sc3&Dm< riΘ哚9Z?;w/ono>~|n_q> ѯƎZ0#RIM;'g[9 {gf [./-@Y) on)n 폺U݀_ϕF;=J0 0b- B+!%ݗ!(y _R^Mo2SJu_WNMFW!&qV=>cd#-: &h&/|kQCqQ+ ĈZ0 ,"a0BX !1a+E8 @!m@Ә+f_b_ݵa@ـA,x6ZwdtRh,>yKT_ v}_7D=W iT+N;fb<oڿC 2%SB 9bP5|›WxЍ{)u~s:gdzӢs-;4ĺQ. YkrvOF}TՠwHT^]BY 0 T%,Mm0 X !.ȃ0gkR@c`cFRDvr;֝gY>C:ڕTO5U {6;[9'$\1=0{3eFD*FS ETvXrKI֩ߧxW|/Ntu=Wq?0x|-=j;Sp {SgBNINNɖl,0 0.666>>R/aiQD""jc 7ebUbޢC"EhZ1'A>4?;D8@*W^{h+/؏vzqvsݏ;a.V+~un\s-GAd?|ۭ f;A6(_ܤS+ٲ*̫n8z|qׁ̕*i)k7ͽ0MiǗ孧Xo[=sfszZ~[t7 X|$K0NEDK/Z!Ax+CF HLy`4\wcR &cayBXMcw\ AacNyu@E|]:}f}^Sޣq6W7W 6F}EoC{]8GYk7 ؞=;g6z/ݰ.rش og<;TN[w)a$TZ g/xmOND^^N9Yd~<.*o=Kl+PRɜZ ?(ebG)X9997n&LʮQyG7p\ѫrW^6y''@qD[%A7U*Mua<} R9#@*b6@!e #@Skl)!2!O<1.,,,thBB Uj(ZA@~W^E X> f×eAN^NEu^440v`iʼnηl`oj z1f_=$uZwQRK~,L쌌+W"ϥn( "HI1G(%VS?; LFM Ĝvy>]? ,%e#gܲiOs]wDk5ktzԩu##(^p)>}('N*6IoO8u<]'gYIYfrA^0Rbfa\2RJy(PJ)}}k;k÷Ϧ2ӌ^cREkHD)凌L8ה/[:?UJiNNչpqq9{;4++?}pqqIL<[vB._XVMvvvOrSf8`-'`T`!1 SH`9ڄ(\: y#*ר4Zh2歜0 3˦oJCyb6~Ď, 4hpp=nggZt Pj>agG!effdgWǡ tMg0JIj*( 0UƽFou ZڪTÉc ^z #Ӊwu,?6 ,ׯVZ)G2OryZ;W<6P㕝Yx yF4OaGZMu3 SYLJ%Y? I%Ih4:;;GGGGGGNgut~חK"^w{>9yՠ2zo T}Rxب n\NDh{kTt9)2h>cFfV3rF>+AcRu~;cgc+:Ff)y˄ӎwI_}_-[4Aų1 T"kY JZhOnǥ(eY)iɠK"f,][?rs:gOdz?zlVX./Əq IDATg*Ó7W 9B|ï}DOsN._J.75^492L~I>E#ٿy>}3h}t~kͽU{>>\0{ܮ㢴6܏z>nh B''M}Gqagk. ^ި X``__6ri ߖVFJ;yw6m__@Nw}%;5ިW8\HN/FNx{/]_|!+i=w*vp[e*j#E7,YrMeӇ Z&c6 xoȼgz+Lzl3#fԈ`a[8erZ_e\یSǘ~=Mn_d4Uo; Ͽ*{ˆoN{c씺ȕ$}(fyp׶MQC8}1 ~܏z>/H;47w*e%LOΝش'oxҋ[~Ǐ p u~k;n„/WY2xn͹]+?ϋ.Ym?p\Ξ;Y̙s-g-{K@ѣv|[R~p}ä1nߤ=s=dE.3E+uzh [eNUF=R<sI,jϿnzȾ-wndI疮s{chn9u\,7RMfRTz1iG<_._2T3Jɻ.Er˖E8ljRBޚL}Ttǎ;vؾnjOAM'-W|VRʓC+]ٗ0f'Yv-Zv;f!G6押!ifD똘v}>x"KnU΋a2?ݹ}>m}ws~}KzU[it3CY'zhiWHrwu$eN͚|yP좣@6)/@:^pM 뗵 ozo⭣4bP0rT\2 Fex>Oe%%@CkK.Wlbz1 0J0FXը0qƘ0Bcq򗲽 Ȅ XIeIwpjRJ2/ZzkY d]4xF3wG7@Y֕q/ }۶/ؗ-aKᘯ8b볚^J7[~.g~ʢevص6F}[Cgw߁Q9un6=!=!W"WłX+$"HQ"by0B*sfqI4}nfggϜWk={DQ9WW(h^8!;B,[vFUDU߫9PTY۵GenWS5˙}Tpi~)X,|#4@ATpy:10.}Sw.1J a0mQŒ,˲,K(K,K$J$ɒ$$A$QQy4**W*(^7..AEŊ1u #u_`+Fb@lTȟpvF/wB;401ΪooNztޭqG?N %Itr/髆hGW^Lӟ5M?ْq ~19Gy5_91]jO?le ^;1tTtC?%DYOz! ;~HǼk>중awd0gf\b~0 {vl3%t SG6~?1"PO)D=kl3$aZQX` b +^+*( $ C1!)5MTE4UU5UUTvdxU Ms`G3'wӓS~;wU,:E_dT/8Ny_/u6v q }q;gW/?>xŜ Xcߜp@Z^)d>)R~;븟 wfi嫸|o/iȆG-:͇VZ=ŶsJ(0X4i*&X#&D(C#aC-)=jZe4JDޗb+_g~%2b[<") Ozy_9GbO|c@fCzw.aπn kEо n`.%m( i)|GRNhმ=Yd iuQh=u߽OOsA }}ck}#a)99RS!9Y 3 %RB00!"ERJŘyMx}pkr/!` @sm (Jrn'>-+N4B[{Jm"Q!/`3&-'6\ߵmb'^^-3,)=z}@eyGAё~rUWmXIMvqOvǦN+@_dtTKE]#ɟ4mdrqW}_IAWL+dȨveqSIwɷdrů_u[j;~/rp@\':qm>1z_?k^%Mҧ^c_d-]^&_u&*ofݻ|^W/ey„1mj@p_k2Ǝșo巩30aE 1&w) 4bJ2 q_<Jpy, c 5PB(&3;)v`7hR`kWbQ*y3~JS akyAiТ$qڐjH޴%Z ܽZ61RNNJys֭ob)ع]]_◲vlzj5o{hoYygKC"E)> S/i<=B5, Κe%p 5V@VYG⋞y }xG><Evz畗I M|9ϳ ޜbp/o@S=Xsd͋ҳg࠵q7R95Mm5sx`5jvi!@f9>.N󪼾W^/bys<#9JbB(&r(XMT}e!4XDY,))y/i=v[6zÅƔϵkE.`fM[8_L0L[>jYfYEQXm8hen]fiӦ+Yx|g圜UW#)`6aV _Jj|eY BH9@p^3Bj*OQy,򂆉 TqHtk>|kDUkƘ`}6q)8󃷮o-)HƩ7׻ 0LCʎq[5AALb0*Kt'崅DL cˀiIu jj*B) @0񼦕O|9B'cjX4MØPJ{[/}B,nLs0 41s9? \HPVlap1h׫ /ְ&h<~!k|r=iaQ#DM09=q}S;QraѨ}ʼnUo7Ģ`%V,NaYWb4MȐǎX5]Ze󠩦ic?۴I4/ְ&ufiaq hҼ{Y P\\Gi(Jyy9Amj"4sAIMMmC`Rk 4׫x#ka1TݵH_+#^C_Ƹ"##E$AO-IaPtT  bo-n{޽;u=!.!!}w*@(%Vt_lJi`P9iY sV(SRX 0L = z<yD1l6 PTTo`/B4**ra y>(((000&:FqbẄ"9LS8gÝ6_lB lj['pӇKF^Jurxp_IL9uëSY6iaΆ$ɡ!$}u Tݻ94U$aaa亥ۯJ[8sx?{+KHf{ s-ٲ&=}aan?5>?:Ґv;0 !},͈R; ZZR2^}S~\uc2[0LWnjD)"633d2;QyWU!p8LW,%+'E @q/maFM=)Qk4{Zorp#}1Ov,-g>"c~Ds$?5R ÜJ)څ !0̅Nt!559999>,a. uLvnSJ (ӧOǕOl }=o(Ӭ<\{iRkf?u\sYRRҥn)IIOpJȤ)"tUfQIIInMYTϸߛwuÓ֜\GR'_=8))鲫'~i7nrwx] y}corDيj$ڱc/;#d˭caaaEյJP$ b`@b#>>!w޴SNY`Q/'L&(??_oTrr۳.l5ofc pNZ/>}]*Ģk?XPߣoz715۷o[r-۷o߾}(Mlq.k@CF!p.`L!=AwWq+,F&}]i{-C+M-rF99bOޓg3 3 PH~Aaq}Fߌ +A{rKJՂ~*p">ghK7nbȐ_w{q &0 0͎ ô:!rC(>.ߟrĉN:Fy5804(((;;pF(dp0q% 'N%1Kp1]{U%%{7_''Ez<%)<e9"_9- $V&ɗwՕ?ˤk:Z3}23v'V:m_Iy_vßϟ7KS/dka͐X6haZ@][ !@BNv=;;;++_~oyE$I$yPUU y ;rHPP,(8U4ƧF*"1NjH4j,a)e?堮U֮2w+~]?o[m?&w}ys“ =~znONXvkw]?ecY,iK4U7doiq(BN`UU 81BH%rAԫ0 \rqGJ3Lvׯ_ 4MELO|Ԙmَ;f4AE+ A#*کçʧ#$ZjE9%N玽1v@KQiXnsY8kD|:-ttr@N&DKNC1a-/gpײ[M#4d)X-BIqTrG `0ô REbM(ab`$FJ$UWU*SU]nq,VI y |V _x IDAT~x'Hcxsa{qxcqK]MMx~ aq{_RH.&eG,HzZ9(:O$?~4K(Ê`sC+@kgLZ'._5%|b9>E-kБ/m9t> hT)8X Gdž#"X4\* 0–DTPPj@Qn5EQN.j݇3pdAddB(2:WM贪&=nai!DG<veYc5M8N$YKkq\N!dXE95~00lNꡔ&^!!Bw;e亀cF$[k/z#| ,~_sVy˦O6 WţoL}MF2 fښ;X?yϿ1u| c;֦cIoY"={c޴%Z ܽZ61GZW QD,>Я/ߨR/?y\)s^ҩ-8jj5UuyBCCfss :UX#VUՎU@^^~HH|ce9((4'Diii;>!y!%%dJ 00 sq8`4ح`-74p(o;ĕCu&{5M7FԩS/h6 AW?vعcUO_G]z=1 WO8-OpkE-o>#(_j0$̅.%%3 4`!(C P1 Z,ٌg>ck  EQEӐp/'v~VB,nLs0-E+L}{%qKqpx4Q{=*A58K(?]__NtޞSe$.Loca.uzǎPIIIII$I&IeY- BHgׇEx<.V1 ZEň=a.\ZAF{31_CuHoPz2 oUU?z% ſL@R7f 4# !p8$I\ͫWx<*[0f q(**f\a l!Aat)ʺuCevesN_߿_ 0̅m @65**k򙮱v80D >ea B#.lAhUqwuILS~ނ(茌M+:MUފ})-.)믿KJJ;п TRaL[6f=ܳgl8.;;o t:~~~vbL&$I z.MBc=V)78e{ԌNlxsF? a[yWltxp_ @}.ƗmߎYU99uëSYiid5pP\""'Ҍ];qS"'>8 [9td^w[Uʛ'gwRjX \F6&k9X)%?KaI!@(ӅgErLjc^G*R;/tըfsa-?xhzLݶ~5wbNI\s5X;M+Mi!-~U!5|Uo}.b 7YSI1CUk ASbK=AN [e)M 9zDWd?`}hr׏3GLlLc0MZ;c7>} ~X8ͯ*zj40L$''>G0L;Vh!kPXX(I_ɓf3vZ*4!qqy>4u> .wNL'Y1-QbeqgY+|6O 03_ߊU9羍K ~ɧg;b+ʼN#n%UW9!_'(G׿)juf@ rnM1VyDw>D+B_:l R˂i;w}VRー Mݖ{=Zǽ;}{ߎ9w%F^LKY}_=W}ėK1"@w^7Jki-G&L(qw<{jX;(U{aSi@8p }_LIY3 » 48<7Gd !>uOEK&z4dN(hնU[q o"`-B J>aڣS@(*!HSB p$vaB=劸 n7?qW7SgQ?W<2xl.CƧS23S8|~%w$Wyw I0C{}Б;v\YBK (,OIa=Ok bȘEkTE5ܸȼxs_7==?N?%3߸m/Uo|'/S;&'Z}\[_gzg/> ~ gk?H,pc;R·9 }}٬YnA9 ;JrrGy=|4W^1 4oeix5 jL*o.B~,`a.5B)P8(W~clw%? B )B@Bx=)ٻa?~^~geC>kϤr`. O/: gqW]aLzq9\Z}<L"(nӯ^rBgWG'z=}S`[zgbbΜDNڣ+M߿P-{o55BBoz֤:OǗmUO}rBƓsnC2*7fb{7MdKƍwGemxvaaxV3.Ty.^Ӈ_ګ)^#[VHѩRg<Aj 7Qy+^k.9PԔT)e 40MSKJJVWD~~T0$˂A2 za F$vY8Rޙ70.91)癄Bӗ}rů?m^DŽdRT{~5%׽C޹b)"˗zYu/gب&nZwJV}Ҹzˏ^1'_ 5Ʃ%gJ1yrbj {zs]-wfi9?rs93] cmᐐ^ʪ7e/=<ܜ1>Pv#*y-ٳSQP)99^8Ҝ}Aۧba) 0̅V4Pqq6(*pRz"jD8F(vj>w7 j^ZJʟ3$H杻=Dm{s}OV` 4&.:ᒫy~_DŽ=~ NZ눰>(ǐ2ILToXf~Y>*-/eyds'9תY; Zx抷 7ZukWP{O|LMwSǼ̵]\\%s;b7ٳ_78ct 7!Qu8$J0AQ=uΗSdoXVVG}Y̴9)fH 55U!ap#FMNnJ@o`0M&Áҷ[-˞ѡ!OPט0b8\'~~s_[:4ȜopAHkgLZ'._5%Ʒ=;.5ڳbzm}Ϭ_ƈ^A>竝]aas\|졷~uԗc2lòWNOrdnZ댾N.|+o HY*-;VfQҟX/QN^ti#DCD$;#B9Ks\'\vӷw@9Ƅהk3!bY:Ùo1|%oڒm-s^QSV-)Y~߲hr4ՇE6p+LC^V]?em=7ô9}#GlƪF%_q\\ pP 0 :PpX>!qzghWhBƍptԵǏowP߸yz/Pfkǃ΋\Rof3 Sᣵ6|fͲ,hܮ>4(s K3编*JBBqntRڴ-/===..c^nVdtռ9Y'b;ѿ|TBl936a@ _Jj|eY  |o?~:t)$ BX4MUTMSUUSUE*nK ^WѰfaii!̢lZ[X!v !,fk V[O5Bh;:UBM;@ ,buԼͯ~H>Uw7ir))c XraY!)As19BI)/4$ 0 ô.oE KK`QG=+)}sxy#Z0+%9RA`a]0Ƅ Ph*ŔJ1&Be,8!N!=x&$X#` kPL()a6OWqq @^]<;L ]FԱw²,e{ߘ;/coKb;3 4j^)h/Uի 4ūx^xʼSʹSx=zKzXÄRPB@kaGkDԓ?ej/=8wi#xM18!t=][.@?~#t6(צ<2<g b)8**1|( a&U#8;:!A>PSa7!v["N0ԏeVj:5ij*B) @0񼦕O|F#!~'Ъ{+1ƪ5M4 Tdd״ IDATQ.tA&w7awa־>^cpw}>W=Um)Bٺ{|n:U&ib6 a 7F#xb kB1p!4MS5,j` _.0 )т>ҫg:=n0ytT^O|0kgDsy)>c٢)V%5quG 5|Y? nw=;; ~YhoGrU/qo_NJ595wN-{+~)@ :|q}k|z}uR,=\%eȓ NiZ{T6%gafIWb4MȐǎX5]Ze󠩦ic?۴I4/ְ&1eqIU/z xŒIkV7 #|`h Pegu;tf\kfx͚yEΟ)=+Fh'ٙF0st4O<~ӻ־~]^+Q]O9i' MX0W)Mxk1fn}mѷ $Xu,fa Z)Д*^U0"p=; s0̹= z<yD1l6 PTTo`/B4**ra y>(((000&:FqbẄ"aIL/wbsw @y5㚣Zm1^c(?ZȨ` RTP0 mAH/.#j/W|$* vvJ],t祐 ׃ܳhޓS;g&iu)dQ00g1Ոs|pMxW$22RO~4HeBHYYzlLAX,$iV?Ja Y"~Avysp@$H<+`㟼3s@W PSt7.ߝ}гh4`aurS#JcjTTfff<_\\$l6K$ ˲\ ` 8qw7Pb0c"kGA(w~_5B"L"xJӯڟ q׌3>**&.RG/}.Nݓq 5Ro; ôMYls`aZ j]tBUU-jx$I$IO\y???}3[TB=c9Oq& N'B;<(e+o7]E~fCNU"101\-WDSE~I e9FV߭-)8>omܩ\w]rlFX|X1 W#ӼaJIN>aoueNAB)ѣBdddy, Z9!d21pT{#6!a!hM#vՊzNNx Vo\L}keI V}07.hi`|v1L*|{{/l Y:Q^bޙΛSj{LG#X%n0 fQ>a^/!@l2*@c!˕i2F#]c$]fffPPq;wȌ'Ѱf6 % Ts?~| a&>jYfYEQXmenWkô1_foi*!ļeV#[ _Jj|eY  ,99C(P|\?!ĉEEE:uE*2`չ RRp8Fh 2B8@8vbaa Ür"@ꔘh۳:w<$IZ~~~Njʲ DŽy uNh2# @R@3 0 0gJOD3 sJ8 T$dڵkW~ݪ55ME2V%/// j 6رcaaaFI8՝OX>i7}dt9m f,)ai.b fӶ7Fa.l@a3U3.,,!([rzzz||>>??ciq$ɲܷoi\.DY,E 8s˯h& q #M=ܻT0 4=⢢{:vOd0[t m{;Odt]!!a0 0uBAltoDD H#!14** 8 tV5 7m>;v8$bztf#:?q(۱9ppN$)7xk#l;Fzi!ӻMyvr&t(?7UP\b9sf9ܵGȨk3ZQo zZ9:+8~6m{ vG?ôeɩ)0:R[,}d2 6ME=Ee/$$D$???}$ͦiK7'ZbUo[}%۲}ۗ|yЂSP3n֪/jv. =تӗ];ߘV{ű_wUKa la!ql"jT8mgN*=fi`!(C P1 Z,٬j/ EQEy8ӗ#rnɁ" ^̷@9IbD hޓjcɮ~/wǐ3$Lggi#aa-JWGPo:V]Q} ssA=9#uzǎPIIIII$I&IeY-~2ߕ(ry~`c¬V,aDŽqͿ50;$wMmNG'ٌ6Q)P  %l(lJKq M_fMBY-B ӱ-C ; ǥtwzز~z{!jGkKO =\Xc,3zPXGP(1B9SxKfrk`gGA4Be󵳳DކpRT*9yo,˺0 cggPaU.6Dm<0Q{BP(CxHҴܷcCK`ӆ) Od!~P(SP$@L-Ç f0)":2GnPvwPK=&31)h, BÖ3O 6D(w2t_P "@M ͠[]~Ғa/_9sJ*JRHIJ,018Ny yRd)Oc2ThㄏUİ"!c~}at \ kgf.n~ ȗSBP(`f}`ս5zs箻 ARL R/0yP(X }Z,VU.h8 BP iiiVBA4K}>[XhhhuX9|TDsthݪըc;֫F /p΁Cog;=CVnGJ1(BPp!09Yܛ-;(o! 1Iۂ_i4b \N}m$ B1G!BV"a---ZB80WRj#Vј1NOO5$D$ juk3Nֻw-ek^op֋*=!;El[En>dnjex2@`Q(Rɑ}EM*8c lpÿ&% h4b( 4ABiiS{O`OQzuؙ7U8ZcjXS0B)}__װ$^SԞs`#9}ujQ亿=@6Ϳ4˚6 p g/Zsc[e{[RbЫF\FN]]u5MIwTS(a CS(B)h0 i5cuaURb\~.Q8'@Ѩ[9D R0gp3ۅAg](n=e79kL<-*jҘ7n8[I~Cg~_";[Jq},%y'-KĆ>{} ?w­۷7oޜ=<<[o΀ %S(/ @RaϮ.%!k++1c*'uqk+k3( Et܈a#~Alr6 {]o_!@úo[Ϣv`I 14~ȱ1o8\۟qdNI"e~!'UAu։yZG[rwwOB0`4B1Q-LP %cB b@/0?IbB( %HZ}Tsq⦛,hX׎RwS3d&ej/oy}cFDF!Q!>4& FSJ#$B$X bp R*C e ͋ó"fe*ZOu]x6H֭?ʍ')Z;7*(ܫW'^ z]FMނc^Z9k!˘c[{(c_:wͱ;s.ʃz,}:~{Zz sg:qYn;<qB[)cjgƮɐn#};W[B 1%$T*D~qj>1jZ\r)MJZ݀IFv%ؼgVy댽_j{˴GQy7/)cַc8P?8e^3MXMZnݺu֯$ 96nҨUKҜ&֛B [#k.HZcĊ|l>a[ \|XXd`|A-6aD7^(-a@0B&4`-6L 1kŷ1t*cP*]GPP]vڄC{-K 6h͖a"Flk@q7T-%c2xb&n^ܫ;q8`P 8QeNJ;nأ~ƁB緈G߯?٭|>3HmǞY n_M HdSjU~M#kV}[I^)M7eBq.KuWUu7zd& =LٵYFɭِ͆)X5-!d.!!1A!!4g$Rdh:h b 'p`Uo}VOdh[n"3Ô!"vٹϭg|-RnԮf|3&aXKV"Ծ4jcڄ;udm pV܅8v|'&P()0W>^sDDŽ@cЉf*)")t _c4b'lMIFS #>}FJ2 mɋܿmXXY=LmfJK'S:}:$d&fi.uk${Gwr +E5uk1d+7ǽM'|&־LY6EOP(SlKtb`>Y'~ye:%Strx BG&@LFŁ) Ř cU!Q/Z}ڷzgŤg#ÑxPp?ܸqUۧ=Z:X igq?a7rk‮]Ԫ٠ v+`S뙛7Y2ϵEx,y) L ZWźFL',ɯ ] z)"z'0 %N!'[SC4rӡehV緝ҴX6m- @q-Y:YCVRMd_? VN.dĪS҉{]Fc1b`wG|@xxB~pOo/ؔ=wP(1|[p!k_EEu˄ DIi)+ :B`|Nj? .h22ي?XF !L5fk( ƽ,F$E$}JͶס@l P߅`~rP51,j43p`}20 'Bz/5^[ _/._s~7KhQ`M?{6=ϝYmy/9r 2R2YYBPFo\XUwpظTEl G_?yp?۞JvQiR_Wndb޸|U"O? صe:1Wl{Vr2ӿ ܖ%'6,OZ>kV{Smd{!h_Z_~.R峿w,%j baכyP(fA sF 6-4܁y/ZӨ/_<ϩU~؏mm)',;҆ eb*_vg=-3nykLE{V{LrYX.˨P>n*ś=[ЪlQ_3j绯ϰ#MŠ-A`!;T rêI@I3MUliXRw IDAT&* \jwn娦 lO&ʣ07m3/ousV,ȘzS zq*clѳn%IIaMP(Aj5ZSqq@PffqVrzZo{VVե,j/[n۷[Ӓ;2 _P(*zӽA ob)! bMPʌ4+kk] QiWR[g2'L|TзeSqk+[( mPf+Db0#RB`R`5W&60Hh33JP6D{hwUϞ_B/BTT*P(;JH^Q>6,RWwOєFR:ܑ+ϔ!A훻^K+; ֆ(c3]?W( N`,k(JB0B #J66+R(ZFRYkZ|SN7l~~D@{'Mw1Ն{ܩkT?G~+@S(9TS6a>l|vJC85J)#pn5%Ͳ'UA&TVqu51ن=_Ͳuy|J9?T# =&8g= L׋BgET82Bթ.5*fe,K@JRBP>=gͶAa JrN@h\)EUJ_ 6OSQ6o0qYC)[BP(ix &0S6a×K_y2b[!"q7T5{4\ޡMMk:d \-n}OˑOdǶJ%[~1&cV]LFM&44eKO,@օm:%#>kڼ=q<.ry7Zu4N ﳉ?q$Quq]~244]oS >J9{ǩݏ@TIo}q-_cܾ)WP(Yx'0|P>fʿDt\ǎC7U]O'24-y7y@avFG\QXw5Vjű3/x5ueܠk][IIȬ=Ǐoa{T$ @7ʣz߲kAL[$0hSϮ>noSw`:.tr;Rgn5g+|ϔoV?6|;}T:]ko7摫ƳtNN"#98YáO/ls(g;t-@Œ\*\L >.+C,<0Qa ۇ؁$Zg'nA iKkV0lмM|T/F.t?UeGRq.OR8phcustP*.[M4i aH~+qTx7O>JyɶB'LZ)ٺ;>|k4y[W VIURtK˽&:KSW%tܧ P5{{-a,ܪ$AA^h޲=h18X[7rĨBӮ[qRUgJ}{lFϛE0NQCR+ qZOt>Nl9n|OMĿ  T\S 8ۗyRؿO<;g`q7KI}(R&3#]R*77+ÿ]s 5ۿ86p.5kXp55OC֦fGOT5ݘM-<\^3Pף)6E4)Ҳu+<߬yOT$Ŀxc+GB)=P'0QQ0{hf39r;3U=L88#ӧLiVwtdmݮ!G > 4wڡv/6 Xbճ6>ijq1N?a98 5شkc^=/%KUF\0[oFߐ5{XkƻoPJ9!*S[ED!N߹{mj 28cGr={|KHl,賭B}Z[1S>ry$G@Ezӽ V7wD\s]/ϒ58_]J}ސXXEp@6zhБMwѧ[5^EǪ#zȆ *# طӨ=ؼJ nh#24aجǷ"Ј/:Z*ƘqOݟXy_OzIDF(i5RQ(eK"Grda0!@%4OK0ٵ]jfݻNP(e¾;<|ԪM;k+KKX, mA@ /V>EЗS(oMB *XTi!R)!Y.GFʀ@$TpJg͉cGN^ ,˲,#%g*ͷB@S( ]^_ܿ6Ϩk/LVGۆ9INu}6*.u~8u]Jxo_0-橦X[ԜXo)Q)i9afK"N,:RZBH_HP>Xʽ7). 1873x`0Jo%rP]kT̤˚~y*O'"aC=[wl;nwҨ! 県&'Fv[W9fо~E^=|lBjUVfqYb%Ǣ{*W V8PhSR($N` 僤ߔ 8oR5`U<;'pj6|jn໢xuVun{_owzL^:#Pw4-t}'G~<XM] $}]atdš'LN)hwoTXDRJq(w4~q޼IyBQ(B!L|{:h4̯{G>{#p pp g.Xx%\G  7K}F͕s(oۻtܲӯm8aSMmrEڇM;#cr8:& }[}Y_]ȡCú-{-Ct8wZ5]7hw.O]϶ 1U>3N<4׶*CV"hj[7y3̩>ls&TZURC~̕%(_ck::2N~GjM;؜0]EG9WXe޿&hI{ ZߨIIȬ=Ǐoa{T㩤Py^0yۆ۞YZ>ofym`@5)Ǵ mwE>3[;sտ[MƴYזM5Q;v-&鋘Dv?za!$}|idC9ӵGj®-=y]*ra6 E x~5ǯk  ƽaO$ tWmˮ Mjl twuNd 8EVDR!>M7S/Ʈq@׈~f/G<ׁf51"cD9+NҾ,+Uu.>M64 .רy}Ѥ^:M۵o%1 %,p>R c`+!`l}3@Vc 0Q$JbHZB@(ΕUws_9Pl.B H;qON_T0_Ʈ^z;&pDĖ3i%uSlW珗?|{1_xKFbU֞~<'UuԷB,*qIŘI)iqq^^Uw2a7V_?7n{zTO%cdԖo6+A],ZbѼk~xA_p׊Ebƒsn_h&Y>̓s@bMk/|c"]̒}wlE7Ǘ/IP2Êen(0s 1e/W ~s1,cC{*)¨0 *^ SBA`)m])8; s,=x}(P?3h._na FˡŷEΙȷW]|_KÙ9|T>fޘ/;KÚxMϚ =TV4g, Rz~X<@ B36vK>}q}E4&4U8oa\>_Ͽ ;,m@M]T*ct}g|GvZf7 E ~-G/bPF$~s~w0c[9|AUrՅS4s-H);8+vJ ro}RwG`ftm4nX[>0+kw> oo:AĎM/i*}&t8<~ž  BH8ؙc(t5bȩBC)2ĠA7?g"I4= IDAT"B( WOaA cP;0T ήJ`3{]{7AWڳmI{ ,zQ:8h-zz"$88~p 7bsdQ7mmlm,-%P(V|يKkpɩ] ޾GZnaf{zTi.f}er- npّ\}Ć2xW~d/˙}mcDU%.vS}uVR&q6yRaAdX&uDK /ESRL ῱D +`Jy/Jy^O;:vV `Ye@/9KTiJoHֽcgTiRS3jQq JrF h8+_FKjZwL?Ϯ1xP]U>24 ΍*S]N>%9eQczֶ&5֢WE7JdISipvU/=W0[8GP\|綟LXF!p Tծk%θqƬ[G`M?E9 ־;=1w>-WԞۊPo<0 ĭإ:{ kZZC206UwMr!JY>DP(埏Jκh*{45VN%5:w^j %UFRemKBGth\-#FhqVY(٧)N\>r>jKQʿf|޵+G?X(y>p㫷Evܹ+VMw;iYKōOZ\i~w$ѕ o&m,`Ӥζ"K;}A?vp Ⱦjz1Ic0Rv1wv'w͒Y7cYׁ3W~BRtnu,7ܸ ( !shu͛?~qKC\~M(F2P'0R0?ńq%>AFnvK|w v?nV(em RQTǩ*DYkB~ zuOybY3Y6S;7i 14lҤ Wzkfc+Oa]CQ5t~<#PuJ^kS4ihy, [q&[8`Bz}3up3|so5 ',Y<|֚ 0}ރ~Wаw_골|t!W`ݤ~,)G'6-1}+~AZ ^=n:11O@>|uVn\YaX h, b >l P(EuZ8G. R4j""#>.a r"o y]`y?J׵ \!؀)227j4)*JƠ;E8DR^#f3wIuO(ܴap@ Ig,n׎on"aWc4CtZѰ _u8ׁVQܪѽ֣A+Fd)B].F ƤK.V{Ӵi3CKV3ev0 %7h>+"e2A!ϷB!T8JyN yrq1@y_6$zO5+j}/Vi?%l )`]n1 #`)i4e0n߻QCrHb"p-O_:a6=8q$Sx79yߞE -k]s5^>m>ǀb! tKGvm6kؖ]W-$EX@d!,~XB0Ε/XeVϐի΀WHP W\/y+Ϸ@Pʖ^ks͖}JXqa~iq!>W,+) S'-\˲* FIwYD|so^^H\9 ro[p>9ۑI" ɫ/u旝=~ޏCM}f<]I}_*.P -ŠL]Rz79mD޳Wz)P a?"sRsptssK={=J,9# % R0VfpȈ fd.TS(eN[wl;nwҨݏ^b&^̲AWV/Bòɓ',YBR! QʻSrԘs{TIg7om[ӕ<9f}cI m /R3S`y)?0Cu)^ƦulY4{P>44nFja!H4-Zɛ*~KJy%U& Y.66 sJR\@O UFB@ F(vhxv|%NB *يw6)8+\|`f`swQQۘ[ȤGazqhЇ@R(R(eYsѱ˲,FxYJ(78@Bk;>U}rMwGaݹǢttdp9H ig3s{g7Ut˾i*z˽PVȟWh=:{4"@Yk eq#- f/*j Ǖi?lݿ/V藟~ӄ[/#NL` X{Y ! Ơ"1Utȶ5rqԲ靄v<[TPr_YGZd2\nJPJ lpw iں󧍼-L+wzU6|?tԩߧĽݞUr}ֱ|޳*imE\@k(n--D$DBSJ2+KaH$VV"P `Z qYJ "Z V)a<ϡ|h4D)S@#G'f\&.o}h}h>k[{_D+ih^eêH< 8?t{='ז&8JATχFd ˉlܞBo*mĿ=6.6:~.7$?~]<$³[|xv7ŦFׂr8; <k e=<ܜbBH,Y[[9;;yxlƾ]/quuqssD#,--]]]\YE<23Wv뛔5VUܺ^%!.w1vG/gd{̗˟DҀN#7.u3jnq1! !^). "Ka@7?}\`TEV"W6"PLKDFHB7]o [JѨ7Br˂Vm:lV!Ǘ3}D HQcBx.t"=}^naQpD&Qm:w,E,+ Ś QB) 9W t>` 1AC!u͇Bt $7)q= YDdX9[AfRm"PCt0##e)i\(rZ\t L2c`=HaM*F `]')YMrLrgĄL z9ߺꝟ`,;grqq1&E'@L0@ RpZ Z:KU 3p٪,eB9mF) J?ORLjuf2S>u5<`"KG(jYJte5/E<32 0X0@a Ý@ 5o_2; Rxzϑ2\.e_&F0*`P\u+{Y+yK9,8/Dž4w1H0vS.që[Τh\6j#6mPHq@/X5cuձxVwP@L:s]>VjڊoŲDBDVV$ƽLTj!^[U'˧c`]BZ4rF$ˉ,+u"Mf34`FΣW#dSbKu7Oo^~u"2w_e N4*ܰu{Ľԍt!M:WBƃ3|\5JLr|C)-ܰM{P=|^&H:Z8q9Y:5XO/͊+hJ9x>  R'J  b7MAW\p9?QX:Z#ER@ L4.})_tŐ"o͙ED|a"2lpm_*p%[m QУAeMs ŃcQgsti(;KÚv^=/%KUF\0?wf$* rT*գO*[[[eff=|W"х++ EL/_㷱όzYgff! $bKU*`bRK<*91aA"DZHZ:xn뱒a6'0bd[95Jk6Wauҋ ;[›,,vu@f|R6ɉ&j^PWT)/3Jlo+ƙ[GKdd^Y9ZM|ɌB)BD"9yx`Q"CU m^"C} owȕgʐ kEh# KK&rHCnGEif2 }c3٘J9B`V#ib+c]{}ssh4|پr7hgkƺ^3rttV [~f/:H$18ll>pkkko"D{z<^8w㍖a yP,J\}RN(y5;yV,$#[yX^w|SemMRb)*@TdPE@P6ʐR SPTT@eLYe4M36YMڤI~39y#99j6E3w@)mv+zXdS슂<Juh^ @QðA!aζ?*& aIEqz_z@ѱbV~3ʰ3un*ի3gΘZ٩+çϘQPΘ^X&3A/3fhlu ɕ1UU7bn,,:=L=ӥ(.ڠ.E 3jtg xF'z@%Ze^Yj]:*e^c;3tR*aFb ۦk\)+s}h(PATN v2 !R^"BرwhT*Bdiy8MXyXh*(eZ ӝƌ̦M Pum Qp6UkTRdZy○i^ %24;4¤P~ȱ{. Q+IРȹ}O[,ʼSȱD2KWޏ^f58aO 9Duߊ1>%YE4âF q,XM xT\Ǡo乧[~pxȦlovwd/VJGug)g-Ԭ^W<<"4VI/#pN5)7kbq-<uytԳy6 E!:>phJB" (RRR,q"P$zILLyyN~-s4S^ЯUZZUY YZ)*.ʆ0  SWVR/(eK5;tYTtOӠ)h_8qR)%ebAjn. Bye )Xh2#"Ey2!Vay.|C3Du|: @ۼS|"v,^|O燊5`{ IDAT+(oPPؑ(ol_BX߱o%?Mhly@<ҹ_!8״F|\o-և+#ʶ! "!@#15[eU!8B-FA fƤGaBH^^{!kA^^QPK}Q}>@gVħ)/5@yΥ.r+ܸpfuѭ<x7+9sy4PzghsMQ4Vk5B<rﯟ9Mt ]f'} T=ȺzX2ޟCP߿|w{tr40HǕAҠqY0ҠZ}cmJ4M7=Bf0T23b8hw2ii\VURuHh\\q^>J233+ _(h499jʊ`h旮1L5I2u$-n)NJyN)RſW=㾑JT7Px޶ |߸F$_Յn˙LaS ;so,i:-mLj,4u⠭WZȱܞ'[n_+c<[C:G;UTuZ3`A`V K;Rڵs$+Cw痎]m)< |MQ# @CWSMP("6LIuS(LBE(Vk͕H=<ahP0VJT* SZ^())))11R===$RHXqPTk> A1~XaKEFGˮoۍ\ܻvgڨ'V+F*1w)DJTn~V^-5-uB?(oDqmCnY8b܁Y CLTqotC1 :Ag.?>zR0ol?{z37Uv$.%G^6wڴ`}%o'UjpQQsW1@d̢#5wc&灓w|n@#b\9 `@^왛i"r# @m pv=IE34@0!"(S@ RkR rEEEEk+yss#Se*!u_!%Kj.:2h\:9Z埛v;ʄ+򤁡&CqhXf|3ӎ}Gxnן-Gx1ԄmT$k53%~"إ(k+(<%JU-S3 [6AAR!кtdSE7Yd4v,E'7uS;scWs ^Uwڣmk?ꖩquQ{u h+u|Uiy{oZԵ W5kwꢕc__SAJ:ٺK KO?)q$oV`&i`Q.8qWepaCM{ NܰSs-mڡQ;k^݆Pvv]iwϒ>q݇;Sj+yLroMͪօi^] ۢS_/Ρ^ FNҀLE - g9]?R~Ჟ.C o wq}bj P ٌ6^@tUGaJ~xIN?.&zyQz]-cm3:M54jT ޶7^g"+*ح4՛fyS!Թ]Mӿy\JO[tA`(4s snm&7(g<7[X@ubǪ~k>h~g/IG0EQ +8iJl±Őj~܊Wr]<)Јݫ8F/"Bi7?w8WAMlqX12I a\JNwJ 0Lg־oT u0#{2،G[ῤ5qK$Sc6dH# |f}KUiV )'~w6- Ml9G.S!uCP[C|lĆ=WS&&ʹbĜ`\ogAךU>+v:ct]BA=ͷڋ3/v8vSGپ؀H_?_y{L[ /}><_읳s]GΤ?qmPqzMOpVQEy~1?wr&wR]XS'\hWn@߼uY`[D=g+%?mp=ϓ88C{E ˀ_Vҭ[ظEk=F#ݪFv{6ݚ/D)d\C=52Lir/?8aeYJ+9Q `uOO>aܮs_7 m @i̸#Ӗj7~b$8<Fi|48w.*%۸D+_Jľ#p!Ҧ|㍁1%NYoCꐱk۱MMީ~+ֶDq?>hkXo  lp a&Xr K7];V86Q;7~.DzF1xp(7 (oPP W-)u-Kg?]ק[;/CŎff  k,CQ3֦0,ۿ{@S[kOL AAGvfҥM4\&77פ'"2ҊI*|X{QbәvBο B g>wnF̛هQ۳~d-8C{%xlgn+Q0^1]zgY$>WDKKoP6FtkխS%Z2Y۹K+?nǷZc2M?Qk˺mW^L)T]9,}%7Q)ɺԩ[' ;~GJhzŎ37s<^0Xi(MJo/y&#D @9tXhwؘ_ݱR|6>|?^" H#ϊ+,xWMM1 'ai_MVz|݆G>L Aׄ?>zR0ßr;y>71Ry0DqmCnY8b܁Y׊B9J(oMׂ: xsB;Ӷ\2"FF4j?ăP[c&cʞ4DvaݜGOPn0Ѳ_v#ݙ6ko ]D"BI,!ƍVܻ9N҇g_+ 7G3Qӻ aK$ƨ|iWMO sK<AAng{=ĉml{˖`cM90})rݿ#m@:Y4AڷA𤁡W[XBJg,)9k^`^ nXqk![5Cc=㭅ţ \*Pī|5+-%E- z+u|~8,}@d$T CCuFZ(C:9 118]/VzDUk!urB3>?7w Tݗ[R\iB)uOO-kRbZW#S yu)7]?} -HܹF1hԩS L÷uʴU)vAWGwڳ]ֱ]WV&,.QRHZI9g"M:;0Guݗo%Y0˫Usg&;7>v5PAyJE<$B۶twj*oDU=H/hRNݻ?ס,1&c?|+,k*v>+VS6^f(Ο}"cӘ A\ Mw&LhӦuaZl2f% tD T5CC,JVR,XeoKfl_6"SG%K(Xs< ;/\3eȸ=ϗ0ӆ%vKWs0?/ڞ?,^ͅAF(im @(nݺUPPpՉ'z{{qnr]4x1!K`.^)aCxVՇȯlY)͸YL_!jaFDX f\< 7WܽGIFsKP 9ؠ(^'J}XB&/'0WAO+VpR5!+mɲgfdu Od/:}g3C[K:O1^W~>BU;e r.8t/_r-ԥqgʡK˨V@g[7l{e˼g0l~],]m/# s_NYz#[nYy2b*nl[ ^{k2;g|~/d{A@f͚7L2eذa]vuE4((ati|}>+RPelVn?Mw9 1.\uOчj$ C 2aPW{6,8tgC|֥gnfJV敞n7;SP22&9ݿl,T𶯏eFQ!h|w]81|Ѭ!-"N|;516f n!x^ }xz:&CW>l8S{t:?뭻m@9-ڴK٩wҼbگ{bT/l4 AGRr"mη=&/:P*T_Z`Ò%w!+NQOv-+a %~g IDATP@ITF_ݗ/pF~|`- }.F~x~xO[||U{N\̊h%ne7wo<ѶmLdWT_q3x`kB6Z?`M#<矃$%Rqfހ*lޱ𑯴p8BRYQ&eg) ]mytL3DcX%MUA@<5v+F,x61͜S,^ݽjop{1no5w/nds{O?Xqĝqo@&όx9i5e5gE쟭 唪qSz:@9T@P0M4MS4[Dngf:!!!F#9z\{… 2SN3fLppH||fg1 $#>r@u'HD@7S{d.PEbpyıSO>ɂE|+Jc8PU{R^^B@Ҧc5+,|p7 WT`RrfGDFˬ"z8 uo{=mt_tsFcPelzOˎyH㥴_~7q*2 ˲,K3 C3Vr:Uijg<@FEEEFFZ_]7fi(k>^l\[)/KJoDZ-`=E6}9( P8x͍S7iX$Z}f\|bXxlsj|%H\PÃj AR﯇'F~\3?jX;T@Ѫzz?`ؖuYK8t5P+boP??@waH'WAH&ԁ5@B }ĶIBdRO/oXa&i[7}8NlsiT?W]Y{u;\V򣆯;.MQd@pj)GE\"C b잶@?:BBH=T*qnAA_>AE1HP(*mtFŷ,~KwJI+OYsa[χA2i3{ɠfl++,(BAAoMf׮]fk"w˷@K;[07 Je 1O ?P^?L uՔ@M6"Ȱ$  ?&LeM~h z8䥯XSUڿ&`uf^Royտ1NWXW3fƿ AAy'}2A _6Z&cXC5̓3Ws]9Zwؗꚙ}~}|k`AAk@(ߵkW#fÇc*ALCCmOL1Lڤ7B%\v$cb#à{ܧ  VQZe.]hߪ+V?~0Rk\pid!дGվn:vdAzD] cFk-DAbڴisxO>R\d˗?3gxpVAlϗ52Z,=7DmƵtoh   րQ$%%M!}rFj @6Sg(.Rc AA(\ F{<~ -XKoAы f!䩥,L+6_~$=_^c$*&,|a.էa\yQ}.%t6oWG_'$o߱ã8_&M>E ,2Z `VN0lL5n޼O)XSfE7x %/Y }nqm O]%+1D0  V7dA1bjmibәvBMᕳy8?g{8 ck(N`k<&N;M2FX \U0  5>guMg!P|2tX.}}u܄am}tlmR@kh7[2SC vMMq65ZLQ4˲j.2V `w/ .AA>Z$nhIp|q uf.[6q@MlkGkapf֖c‚&?Sºctqt-doGŸY\Lk%uF9`%|A`p0?ˏzUkJzxxF44ީ@\Mӿy\JO[tA`ϣ͖a1 )Pdرj4ߚIbcFR@`A;>%h4UKڷMQ&A[)m\8T+ۺ,֏ڮ !vvSMA!avi58iJj8Zycyn <-;؈ {LL[Sً5,}W ${PڬkBAlEYxuqKJJV^ { jӦMYe |埽_[GMtTpz*gMXnNך&m} !ZW6! "8k>,Lx&ˏy9<~')93Tqg󎽇|U~Aځ>op{۷wƍ[lO\wL c^ZkeB2vl֥5A[߲`GA[=m/(ٞyH6A+_JUȌ; dueg1 $#>JL0{ md#|>_S1u&=s% v\k[uu]֋guFdkW[ξ뱈q@ZXeDqQa#cbIE~uL`%H._<_\\C GB djF/w_w2??@waH'W)AH&ԁ5@o_1 ln486 4ɥu&xu_``ŋuo !_@$ߋ#ԑef% }ϭq [o;D~~~hfկX# ~:ٯa6̿wϙ"%nq:S+gQq~<pj?늬D(E }0[K.9r̙3}Vvn9e6sȨ~ ԼO 9,wm vj ԛ=r&ۅZ4Xw?W Z% ۾H:nB6\>:V }ު۝O}\ŵHcC|ݡ=_eoN)1ib|S?qE=k.|pk!>&Cg0H>>nnn[nXn]ppٳ-ٲ v+M~+?XOMGK;kR(ۤt q襼;Wlj6XkB*fUF>;$hojSZ 'U5WDg?QlyZO ;ęɳr/e[=GZy܂AiP ew-] yb/A{>0KJ%!B.]gQUob#xW9r`+ O)uka&+J`] vi&.bM脽0JTE ?7T΃vr\4k׀N*ਰՄoZ.1aAL^a^fiAs< v}'OP(iL{J !tj ٳ?.PGKst"Lm [Hl9k7(B;&w ׾ PF68TЎ][`B/aR R3xŰy8j$nnH%c/u*&5UZ#Uv1@/T]ŲdH-a׸ZB[];&+ZS][EUB qSmU\[3?Z:^N+i)NMk?B  ]@lW?p߶m[kZ#{oSP>&ZNZX/5NNsBk>$գ| 6h֎#[b5ѪZ|TABzp$k@l};iiiIIIÆ ߿u늋׮]m{6}ueerv45 63!;T֮n=)(鯴Jőj~?і;8 ݉b!ZFi@{0^{mɒ%,^v~ӧ*ћ7onݺիWǎ YYYL=(}eeP N_>a777&HMM3fL߾}a@]~ o~+%urձ1IAtuʌWPMs(|!}5k,HM8B8Rio%EQc@Q#Я_&Mٳ?jԨN:Y2~ŋ_|7 ӧOo߾,FA\8 ˲Z ˲999,՛IalRn&®tb ]?B I*}Ca 4=jgk®9(pFăRh;w_EAU+!RE( )Ъp F(SN;vT(,|Kgi4>2ƠAĚ(6&AQw5-Z|2D]pj]SFhi=p%Җ<%g}}wH@V.Bax3]?m2*RrTn4:@hE=15i@jE``ŋ}Y[Bȁ-R(YYY2L_L&%%qmuCNEznd('ϝ;w~___e˖mEueVM3f V~;\8ϥ.0ĺN 8h4rr&A"R IDAT$0@5h!.[fCU`Dzkҥp#G9s棏>P֭[fR(2L"T*ݼys=m = s6ŏ[_&6we#_ cyn <-;؈ {LLb5[h@3q#tVBEm3GQ]Ob9m3ESY (8**| ?!O<񄛛֭[=<<֭[<{D UҒ ֿu]6::lFA\"%0FV !g5&+6_~$=_Oƫ7Z9:^T[) h%4o# Wg`aMշ sҥĄRIK.řr &˲cƌ۷o=n-JYo5Ú̈́A1BJox(~)W=`AV 3QDg}m{2P( "H,"@(Da?BX$rk4<Po3  "eN?RVD4kӅ'Ovಲ2As,M5DzlNNEQQ 9hh9e"㇏:nCz)<>FqvEys{k[-ԿmpBA,gצcw23s\^sGа:?l+WAVRPP`b-._c-]W^wHH };b$6Mcٵ X `(2~sfLH#25@#˗{CbqL1-Z:i?p߶m[ njT*`ԨQiiiׯ4i-;`PL6H(䑢 q(b$FsU +(2ΝYspS⦉Og* 4W扣0D Ai(T*KZG׮]`͚5Zh7_BCC/<<|ԩR}E=sLsUd25!$??͛wNjFAljB.!Ru\# {H).zp_^&,="A"8 0R WZu䀀+L:UF9tPzzzݍpY/ H]@ljB.u}5~hP-D<0Q+*vi_2eJǎk0]tꫯƍgmuwFѱBM޾#6\71Ql}#޿{S\YNvZzX$#xfA:z BJP(VD&MԥBHFFBa 6H\YNFNYŝ"Q߾\׶|uLO\}rڟ #z>PRBo3 +A1FѾ0`]ƌ# XxdP(222zmK 痽;2zw߁QT[ϝ-N: BJD#D)""M> ("H"U) "k-@B$-3s?&Yl&nnfg);;g{?mWK=y45_T!Ӈv {fBB ,Z 6 %BGI/SN>0w\sˆ_B TN .0HtS@DtߜFSuY0!$\mj}Bd L?\hQppA!*-[_jժD~e &R{ZfB8^T*U@@@!*8c!1{l37 07xcƍ=zɞyb7|ڑE[_%YK!ϧjA!*/iBhҤG Nw)oop߿bl!`l/ǎ{Ν;|B tj߱qs+=ޛ2RzG!:uꔟ_XXܻw/--Aϟ_|I5j$ͩj…oƠA~t:_u YS`;j `{yK%E``݂רQ_! ѣǀr9h_UVK,'zvZdhL־} )_0F=W0RUT<GxM֭[lR\.g|!Ge۶mdɒѣGX&QB0m۶mݺNLbpNaF4Zj)-(?R=`jP\RV-fHfUUۖQ"!s\6K)gGn޼`Wo0HяH}yozĩD)&R ZNLLѣLYK=y45_T!Ӈv +rB "ԩShh2 0=]}թ~1B'LEƕB TNK%2t;(l]G J,ئaLV1FU$08DjPDFs_wu`֣" L4g\?yB0Eljˆ5^n EIޢ `CI9sf޽UV֭Bጣ,p$ qFZ \ʪWOEK3B3L 2Lٻ{Xe98eY%R XlG;v}6TVsUVutP!P&㏬ŋۃ5jdno&...000%%/pBZZڈ#1pk CCFka8?~|W^zѣ;Bm֭-jڴ׬Yca{EEEM6q=V;3-DѸnt>bs|ҥK{կ_?uk׮]tifB!Tmڴiܸq͚5DGG/[̚eO:մiSB4&r-JdK,4)}]0Bk%%%}g8k߾B!TTVp\."jZz={\ҼysHJJrww/Z û \xYPxk\Y5O il.G*\k̮c[( k\!ŔHl.f}Ŗ\ T5!Lܹs?lg!FPPЅ RJaac&''_'O6mڴkھ w'% '<SUlnj XHԩƯe)P&`1) l`]?M-k.!yir]@?]KA w% ޒmpY 3\P_Tr&곡(.Zzf6qn4AI G*t)&{iKoῡHlTӋ8H2 1WZY1S>&8gy`A( H}lrQ ! w_\X'm3Q[ <5A_/ߠPqrjR\0<ҵy 6+>>~}/˔4Kaŗk q YHQƋY-sn)> xͭ 5ez\E=,~[ Գp.RIn .UVӦMk֬P}4G6;s僎 AGWov>9Nb"TR0 t׮]m۶-[8K?}޽ݻw6Ț;w;[>Șe[&B&!H>쳠www@6mJNMNՏ路g+>[5+.qՈ7Y-ϣw_c %|T*es`BU$˖-ST%jg!"3?4Gd( c"+!'W@D!swww ܺq':NsT50y2BQ6ϛhR[1Zm*O;i'.Rƣj.)1дEOX;%7wnjR*Acfbس`)U u_cV?2lA4]L +ƃhX[ٽgBB0="2ۋQ}?:ǹOϫm/枋RkԢ2^(.m};ۻ}Ň5^>õ U )փhX)RD3ezkA4]]ڭ_i{uљ޳B!BMQ1ֶig;uT6N|7q^ePț ٵ;>ў}wzaS$>Wkޗ{knX`̐9_T>5xm9E&deAYic\_ŻGW_w(ps:q)=+೭{Zfµa='MKWD#lLTh2c'}еT u-^}=FhăxH!PgrrWDU5&H]uJ!GumӨ:q7[߼BPŌi^~y. v\o[+Bc#i5Х~L=)){OtTz{"f1/ cV`B!T ьʺvuTo7ogXhjIϭZzz 1;ls+LlX77I˱q{Ul",ǜw˞]t3lA_rcB!P%}x)Irqdud;΋}ԿjAKm7ߙNGݍ4fg`.fBl㴉A1;[]ꎘ3/'ݓۖ-baэ=#Ӟ9+qw7WHY~~ח {:{o45_y!Yˬv>|_P @Y+ԉ5=NYJD1; 1gu'yi9<@I7?7o.`gf9f'9{3q PVwo9?QK̀ 6F8ƒF-OAFڴM*;|xdn_c&f-8!yƍ=ܦ,\1sAml{ފCדOoik[%A{զtچ%kw>pj±˾wP͉rʎ0Ҩą;vx1 ܽG)JEJEA:i|ϸ~C/upD9v.8 4aڌiu})b@{ks7ϳn4`A{}ɰOo ۷xΪ2{5zk!2]]M+-x{x* LƲo SsttCΑ oMO0vqJХ_8w)~y?s塙?g `~04%fK;UbօMso<|jwArFhc٫vkK̍\ǯo栁Vw'K#;+Ȕw?X8cXeX"4%& ocGm7N }O0UOB ޳wyʹC| 9g_4Q\k:|u,|m} Rg`o 1C?Jz-ͥ10F0F!'W`lʼn\ ޹AX%brnYҌ7 d?d ޠM8>B!B6{jl *^j¿&K#fy+齡5mŗ[5r^ B!0FV)lIAa Jt (͈o~'O_Æ#B!Tzb1 K|)`c tgmM@!B&U/MV %/A.]w+G'UB!B6[jZiKƼ;_a>\h&}?% eu3קd nniE!p/K=ݷֵe#'Qcq T-5B"7'/{Swt^qqNgq<ij-σB!;۹`ǯ9/~j.Cg=\͋NN`i"ˆXCU @IF͘V׬?"4ɸٿ?w<?q@@_}6%"Zu S#ޚP$qf2빕k{YuZrFoM9DV{!!B!+QzC[Zu|Յ¢Ξ>qs@nө|ĥƟ/jZx |U|@i L9lۛ3{mߏRМ?OsnX2|Ln4V"hn~=> `<"݊k;VڹG^p*dķ79l* LB0 0 _@KHeP!pҠ=997f:R8`-iѮ  [GYXw}m=7! :wCE ndã8M;W UQȗFMb\hُ215X ^C)`\n!de!W  [-'4AR$^B&&W’9ZARiVj¿&K#$uő=,(G_<앎v}͚6j˭) U<!X',˲90!TЬc_ Pg:Q)lLa *u,2SAQ-nF|s=yW6t2T`l+B[6qt,!&d2 *1]Ay!vb2̅d͋Q Ц\^Ef"/z!?3_ieG1bPVy-<}xlq&ZІmD!v|w(RJo( BQJH)Q(aC6²,')rR)8c`8@f^j;lm 5V=رh_q AVǦp\*_tQ GNkCˍm;v!Z@owY|hom?ٸqT7zxS TYk+BBRBhÁzPP* Pʪ\\ݥXd2L.sW 0!T0F0c{qݽ#E鍗}2{c::[:ZiKƼ;_ī'n1f}8~Mٔu lj?wԬlWydvGOX3%Rllѕ+9\̓ܯNb**R1ia<ޓ5ix]PRR>)znPJEQ?۪m;asq{Bp.gw0:a_&1WwY{Yd;Gr> ˲ K |g]a  *BQJA$D 2*J7WW]&XJGڈBLܫ{>e\1Sj!M|2Gձ1^yh⻳x [ UBkzP 2C(%( ^Fʊj^la@Ҥ礑6|2)xuPlQaҏas4A!dĭЙMRLnzB@?_GB!b!B!!BNʸsQBXB!B蹀 0B!B&!B! #B!z.`B!B蹀 0B!B&!B! 80B׎;B!ݽV?`Ν:9:B!MB!B=0F!B!\!B!s`BYv^w@rĬ{m/Ց[6㇝:`Y5NR۱f˙'riY/B  0B!̄=#:*~x{yyB*Bȼk׳!ws׼  Ofw{…jii+aDb7SsqmJ* >.{£;71ҁ2yᙯrܠQK/^8(f]?q#eQ9BfgjK?>jntg}!/fioo=NYT^êFZy+JlTttۦXת=qf28Kf'Jo=Bjžhe1xD9qf:©[Tv/9G~եn½,>>irK<YHlUʍB%Bryk>hU|ms:9\Cb;ܭ?NYӻq?&Vy#jT\y8nBl֟O8<.֗ҏ2өYZָ!k={Ol\ZUC?ڟ.ŗx-“ ;Wu"1KV+!]ډͿ{qh}kfK^7~:cc.$((sϵr2twTMG. ѭ{Dg'} J}_Rp'o2$!r5i}z/n|Z zw?@P3z˿Nk7task1ፙ{W,r$1ܯcWߕ/zp):rJ|̜&6Sr^"]t@tjq_ʀXo2 cjzNK0 _Nx~Mt:(Ϟ:p#NJMTY}6xJ=?M'|Ŝl?ؽLy/r*]Fz ^^Irli@ ܜQ3;L!1mW{W~r10 sGOʴު*Oo35?hơG.iBsb:R_M# 6D6{~(ކ 69l!!ʍ,ːL]XQoa2sopsp횙S5_}ӯ"o}V w]F?xiخ>4e-&DKJ{t.'];ke 7'K߹7M=uURl֢0uMlaGGV,c.:]Lj}n˒U3~óm!:? lHb2/C1/)ړcT\۵|Ӫkzpe 6[W [:=>qOh=g "4tH=|s{Qb> |w>erW=sü>B:sCcy-;\wo+O=9Gxe!3])DjQ5w6L=dMD(THEoHs돩9F7L! YP]No/'@\_x֍q%w\"%8̟fOݽ&s+}瑰>"v۰^^r_WN]t9O]pXբȖa\6)Lͮy̥Kkr'^yf? 4$ZHWrK_"Vc]#Vanj =k~N>{)651s^8aՔn\=v)q7z0{/"*ը@ 0B{hˣ3W]s+Ox3@n(׌Bd^ܞs?iQ\4{[`*apIoG&ҽ*^lrW?gmRi:jM7.}pJ6ye7i[_Yg7Y/M 8nw1RwPXXg1AVebTUbˡ#hb=%@ /QuMz|a~W ޠQE1iv1#(,,._4uwϦG1k1pmX7V5R-¾IBPP]ZLFsiiu: kvoDd³fp 9fU`LN]hk-}{)AwBs8W+2em~6}J??Γ?Tev9j/R^1o$w}am_ ٵsONsV[o exJ?s"M'*E5\d~/ni#/V# JP?DU]@g|Zxy1)zm_|K|~ }XH.6R'P'UH Sǡtչ 2|5kUeEX:n.^I? @|=GTE`BxmKok%PRdCw﷙s`=4 q1˲,*(` cT<\|0ѠQx[IӐJ\=P~q4,fʔ^ )+ZVO:Ǣ1k͡A<ˤݿW:^#,WS4m&7PQK6{6dpL0;Gd w&[<{g޹Lھ *-߈ڴiM%uG4gƧOU>54a 7UgiuB>mx{f_2b>OрENjүo9^ .JVPP_/hZfG󮦨;0ԴHjwl6e*pk11'B \Clؒ۩wc_~'vzهhI2NlܲM:aS+F壂Kj`D"LS T|+JݯhGtQ* R+JB!SrB!r\!W2\.9jh*ZLQf-c\Cky2g: lWSX Ҏp>X1:ܧhֺut Ѹnry@w8b>6mݻoՑ@\ۗ=jg7cZ1g69t!:G1=׺DZյG-tJ=s'&>6O rL)v|o>V",Y8c5~>c+k5 ue,lkVuFe*y1p-_Kf!ro&|jᕶϞew 4}t,erFyвoٴbk \ ʸsjMCa0T[54 (2L.8'9\.+2'd2'ɤaAN:^V+贼VINi ᝻w7B(_Q5;;;wOWfasB ut,BpQ@~^ w̽wdrDz'5!;Igk)0 #RJED" a0:J@T 0r"/< >2e8[ (Ax^O*BpIq q5=B!gb峉&ЄN^RNfDQt+98e S:VQ &?_rС=!ѣG+~YyN;CUV;!_qt! (3B/Є߂VqND 0/x-RRDk׮]dd7 ʄ΂(N'RD /PJ :??%ANvNdfZ^ky99ʃ)̀<騠B!ʊ !Dzܕ}kh`*Z(3|FFF^~~dBAj $E8T:穎ױ +YJ%0>&"QY8Nyyyy( "x< *W=@ PJ ~$,a!L7?}>? {u_0 taRA^q2 Ćª }L)<*ReW3iUDUDJZ0~F!PeC_Ba)N`DQy^E(ãT+X^^T9* ,BT*Փ'Ů!* Ê`A!BQ줷<&`wg  h+LzX (Ri23-=͚!P| M_BUF]4j 2,P!Ȳ"1X"yS(0 殺u1,}?Z!T_ Z BU:Ι3 0˲ a)#(|@(J]QHL J`"JL\LGaY&^B!thW4%AaP\0D=]qhP]p!B!ʏN0 CHUzؗa(C)Gh)% 0@Ye I@F3`B!Bh, Z`P"Htl>`y G9:^02tÁ1B!B!2&D~A?V%*m1LX3)g F  SXo!B!ʋg(p$B䲅KC)02τa0> /0E!B!TL4~Z=[8%CQ= :EaB!Ч5R;jJ2B!BٙNaX@(H@"q(v%=/,%M !T+=0L&j4E@B!B%@E*J mta+R}heaHAhB2ENV#B!*GfA"XJ ˲0Xe *BXR+U.Rӌ;"@4B!Ba`0@DJ)!PBIa/圌aeDD:VbaEB2@p$B!B\/JM#4S֯W/jRT(HF Z* i'0,VkA :!B!P2zv&R_W6jWQ ZFiuZVhJ L)BhӶO? A$!B!P910DHASJ+ 5:i4:V (,R+Q* \T ,f(a)h}A#B!w=MDQDJcY!AP1A$ƪ#̼ⵀd6EL:y^  \&_ {qpp~y>ߙLloMӝh)"ŗrs^rI09j\Z"̪U!%{z谔̞滻8 IDATxy|C8$AFCJ*^?OTi"Z(ZŻ"C D \96Mf1}^J;3<3y3Jii^[[lɷ"B!h졇gblOٱc^SSCee%zEAi>o!?h[o|3i(6FOX?:ى hQT9'/I/D (M>f43aCVe(}>@nZx>GMRу`ZA?Fzz9כQ$,G_uCnȲFT T0?[i4<2~t!wՌll f*n>w7}^]]-7@s)B!D;|?0,uwuJtB!HG復Kt^B!HJ(8Eo*?t]G5t;_IsҘ(9: ~ΨҰttEEEQTU B!8z+Q Grxx@bQZ,ج֠ߞu6:ry<4MCuԺWX,t5XQ-,7Z!B5躎q]8kӣKХ Y쫬fǑ se[jmu#nZtBII1m۶u>eٳ;vacY#nHbzNARYYɑ#Gٶmw&n u%Mő ܂˅fk2BrXT8c8:GSVvO?ZLf@_Jup]\1ב#wRVocܛ;ȵY(S]A.(X,V\7%%dffpTC !2֯ʡ( .lK9o?vCǎɩK>cnwc5\.WC:ǪPyX7:~ց,޻[о} q֕73Ğt)T^v}߈.V'[` YV gBə OOaOãeR{p 2/}qL뱠ZרqE|sS?iY`׫<}/c|mzf<{(z()\ Bjc,]UN$Em#<33۸YſUV~!凎}VQ\ #КB EQ/OilFM.`v\ QluD8cz2rȶTg׷shoy (@:xּ\ڨ:ˉ?(9Os JhmPLPX{v6; 3_Cb >aLagu=OLpWCzoPnƦb;OB!UN!}JvriYFnEVU靾zөv8utEQ9rDüz󩫫ttÛu:5(z,ihi555uǨ֢m{`bū+TӉ's}6wW?Q|*6+PZSv}֣7E#FrΑX@r62|ƌ.`Ygoዯ}V>TbB!H |訪-NY44wTxk6Wp˹'?ޣOzrB UnUU9|yy9躆8T؊ ٚ🢨TTTԥѣu訪 x?iv{Q,o>:yKcrʽo3 S.T{>]͡Px)>^J]ΐ># h>[52B!HW46_!u5Sׁt+O%cn;۲QQZ '' ǃhŢ6 hgZٿ?65`:޵aUvizC:V-)M n4:EܮW?a#37R_]'gs$Ն&M>]2`,¥ٰ|,|]Kq{L{p쬮BnwjsaڶkG5V,K]@]?Aǣy5G8r0ٹ4M4n<(dfY?&P> CSSCej 7SEle!B!DSQCo&B!Fj!B!Z@j!B!Z@j!B!Z@j!B!Z@j!B!Z@j!B!Z@j!B!Z@j!B!Z]:\e4;a^w-Aq,`K|rom,Yt XnFsmQd !B,9ܡ n|)I) oz5YBNzK<<='KI{8b.Nu2(Z08B!B.Pg[z :[ پ]m0S69%mɹKp:rΟ3{ 2'-fw{PvæY!HR: ="t]OtFBF}iR֬XC!pvN4{+6-dֽfi݇!|Acݾr("<0|.Ŕ sXR%,v|0WN[ o(~7M[uZrUBϦ$P,BeY%hP !BM [#M[8θzmgуOuy ݹegAxߗv<sarACqx8Z׹9ڭeu\8#L?a՞]{HfN7[Aq=>^ˌ/[fhZiK⠬}EO%*!].AB!։ &?Ɵ&$;A%|5YϺLdwap >dÛf6=t!BDy}FBS~)yQ}x U߰o0p/OlgK>{>}mןByLj9znۓ )+m -ڹ%/&O/:˅?r |c]}sQ\{#^42mc>@͠]N]l_2vgZYkZ |n1[:>-B!ѩX /i]7'µ4 o,;y'0F`S-lsuWEMYȗqNObEo4Ng/o7Nb|,+%VAͣdHns#ڨGeq 8 Ǟٴ/:s,\08s>wO"Kɼ,!BB-3ơ#?#oPoZM0? ȧ( ݱw/e~GQA{ڦ mm9tqF8}'GVOQШth4,#qs0d3IJ;rߗ|ƅ޽B!.1}#X(ځ7S~A!R8hs#so;dPХ3mwMw #_fVW<{M+;͗?œ;)\4ayrBќt:^xq'1ㆾu5uB! w=ەӇ=sLu.m|<ؿVprĻxJ[,a:n?~M{OTfXzv> [L= !"lt^ZR NdQg-<*}M]4G)kV`! ivtjV\h>k9u)Ba{Y#} S> adMa7^& ώ}.z}d7{?]_ox;Ԑs>' YP\jaQՈ`\9m5.L$gG2oՀ1\ݿ+݋rp]Ųgg4X8:%A!/fϟNŚgxa(fN(յ4 o,;y'0F`S-lsuWEMYȗqNObEo4Ng/o7NBe׵?6cg=9 7ļT˞eуOR€ g(@>Enhx)k(C?ڍJ 6]hkȡm5|;9~FnCU*xL>q!8a՞]=mNtzFtTާ AB!D 0Z(ځ7S~A!;/8hs#so{BAδ}671|śW]Q !V>1ecگaj wkvˏz>qxB!E!o$Ưnj{!/;Ui't P!vAvIff6ٙVuᗷ] A>Qi/L/هk>a ?VF97EvpV.{m]OϙmTRvVQs)3mf)]A)t}l9&M{Hlj|Ųg`],KG:mC|DNNk?jWfofϟHNSY.(nJ#?~*V%ܕZH"r-o.\Bi;$4-)2L_2Bݘ'̭2B]ER: N0B!JJeߍA1=ҠE2Jd5Se^/.Ba=#grr8{gӶ ϙÇpdҦ=튖A-(>CQ$ %d!tjjAyྻ&:/q4D=T"2Vvʹ&HVfdDzZ,+D|)ђߛs: pן.°<+_q?p_ |n.(i1˾݃j-[~׀D70!DlHێEfɐGPXZsfJVt֖>st K> i`Sz6_t M"ː9&9g&G!?LIČP{9~+yCIVBϦ$gn%\,dՀ1\ݿ+݋rp]Ųgg4X8:דգ+EtOS#IgXZl^V#zٳ,zIʐSQ|8ڧ/\?e9kz"ctG)"19Lc¤\6OjO.=x$Wy3'U|ț@iK⠬}lcO%*!].FrirJݡ7633H{q,+$C]t;^9&qH+]\b׻pֽ9ogӳ_tjvˏz>E :6iBDĉ%x")bӹ%/&O/:˅?r |c]}sQ9A.\hn Բ}l9&M{Hlj|Ųg9(L"Y4Y77U(:X=t!H2'?,+%VAͣdHns#ڨ3giM{ukPk8|Ϧm}5u,eN}5 t@'m}W<-盫w$ګ6l$H?럷[(2v›K^AMt&Mt4HK?GpdVƢѮSo" 1RPԦ#uzyoTLc3C]0CZJn#벓>/$#)X OKXw<$BH@-Rt̍(  -R"&B4+*+V+AEQ8#ΐ0X} 'N?2v_bKxN|gDW uCKf ͛Gǎ '?J@~]yI澕}$h} n 吊}^E0OL%̢PH_:bلZ&eSUל$j:SJTv@ݺukO9o[/_ia8M[8θzmgуOuy+E8׬NKY)Hh>.­җ4cx9Bg٘0a{#ׯ; %=ѫ{&<›ЁmSUoz*9P r愷}K k˦t(d9XXMǷGuY`Pl7FVPO?]v,w"۾ޅmM|? _/ST^nҦR<4X׷Tb<"5B/6Y{rc87EvpV.{o,Co.5~1+Xbt͍}Z/S;Gpio- Dz\+QeJ kFO*w鷒? j=KYcs5ʵp nP(8[\ň6s,\08s>wOnMi Ǟٴ/:% qžm^!)B#e"AYd'u?"3Ma3RWNhCJek/뗌ٍ.F.ovT3my;'Pnl:'oZ|\.PO49gGM'`dAʧ}Rw{vk鱄p汇]\Yɿ}OsjԭPePmρ<5 u}=n#_Gr=F{ t}5w#?M@Ao\m8+ yoM-3)u3{:zs+FgΜSr7RRRŒ."yT"e##V΍JiF3'P(C4N ۼn lZϞ=ѣG3$DFu 'h57 LyzRR {j qFBr8|;T/>fh`i8%?W&N#nv͖"u<s~1zt *ҽcI50,nolR焉hel-Sg8"!AJpέDXlN,foLeH|#  4ybi¥KF'WɄd?o"2ѾXR=;a*RnNN<;4g(;baV朊HyU"|RI+.-;Xb]?ҷȵ Çezc1K?X*t5.\.ehZxEEɰ1\we mm Ma{Ԙ=bH\ !0E%h]ld(=Y8"fn'BP$={1jԨ&{<yЉ7Ut=,!b={%}üR}>͹2Ȭ̟;9VČ Ĥgb/ՂDΗ4˨vޅD@2 Kҏ%꧞z Yg'`ʕꬁ:w]>tmbOxttfŖlFΞ}27Xg0YKn1z_ thx̉de|}2S|[c6%￟3gb>|8(e@Xǫ/ny0 Gjжy漻~J7AV:v*P[h5` WJ\{W: νNaaE@RHJaM|Xˇg nzkcӗ~ww\5=OIilf`bwT8'#J']-s|$ۓbnϗ4ssUF2wA1e%կ&2d(2}ϟoxǺsoϼk2s%tˬo =0ٟ8t6j]/W@Qyr7u l W ӡH#ϓ/GHo~!`KYJ/ݹeqLu9'jV۴'`Woe/t5G=^1&(JIGєhOcNtΩ{f=FhM3^.9'i%d9nܸѵ=rl{N#s6 uDCo;3>/=aTG+P%yj|6]3?x ] ˞'{ NBRz\~ rfjG~S,SD 7l@~ 'qF}ݱ8,~h-)O,@q jn~Ln5=߳i[_ju@$jKES{;zq \{154IVءYPhd6ӌ3~eۿ2 / Rm5hךk> XjAݕ;@&ys+Ɩo뼤4VG^<ij.[2աd:_-ɫ9GȀ/+ݻG' 3"v5_!np"pH핪7),d@=iҤxCDS<a;xҥR$F*h9I!-\"c%P9 B4/ 7f"Az2S]4A*D,H@-DqQ x-E2/-=f]F2h5bpcx-"9D*A-&u"ūK`G)}qiFH'V}HEUDhE4cQͥe-1&. 0L|InXz6i|~˗S\\̉':wXV ]ͧ)1KZxEEɰ1\we mm r/]7Vam\}DF|R0NFb*hN4}E?׬BbTV': k⩧Of޽{W%Tk9̺{ 7fp34 ~| h`֌p`\?>–p iɭ%#F E<$Sz b̊ 6_Hjs7ao'OK/mzٶmDr8mP_o> ۶OxTsp5+d3򦉜8%7]Ne,V4=w).]G0^D2awp/O Vn왹H{f>οr.buH%z̖a*aԕtڵvq&N4{+3@wJEYfaCEw`m:Yuwe\ - dJ+ZsRfK%J2&m5囲WШ$)m2|c.oksGb9 A m{oUzk ThPkUгi3ܒKa.TU6>k0x?(K{A8^( r~S̒=8@]X_J?~<`Ŋ|駬^x ,= 3g|_ރ+^˾G @IpZlQM_< (]EIބ0=3P{;3PqꫯҪU+{9JJJ1ckǺsoϼk2s%tˬoL =0ٟ8t6j]/W/5-yJ7}SɁJmKuAJbD[8XwN6 rxDSioI!PlM IEara6l` 4Zt]n:6l_~ѝ;X>^_YsBNJvdQtNdwap >dÛf6=6 MOZsIC/tJ|QhrO4׿MkjHuiK@E\5{@Q222DUU ɵ=rlN#s6nH)UXŷ勿=΃ Բ}L" Y la5l04 J+u:IԷ" LN+mӮgF+uKX}}1^= ׻NIR1Vh >V6'C-+"w#aQ~ɸ+o`ΝMV4пz衘e4EiH>9`>Az/J7;!n%E>{-)oi8 amg Kp櫙i4JH:`umXF+t|$ H]Մvn4I6u^|4i4GL~Ý._^| k:^R{O]jȋ~#2q׻ IDAT >6{bp:@ߛɇ77blzԨQ,ZzҡC"IBR*Ƴ>9l1cPRRI' Tc)PJ OM u硹!T/Te)uJ Wۛz*'tNݻwSYY!CD5sBr1)+"A ԙ$D~ӧt:$// ycɄJFYo$͑>]ZQISjuՑ&sӒ22JD[X-X!Ch"{9-ZiرccF`NZ)z>Qa]R,⪹/(eLy W2]-[0vXrrrZtЁɓ'3&ŤABx%.PڠHaaMZhsii)wV#GK]ƪ7Ul,BWrELR>(\;_> bG58~s3GaoMa{^1w$3T~ [FDX.(ǖL2MI KEZ mՕ:V@} 'qF?xN=T͛ǘ1c/ԩD\{*w czS=OGRb9MNw1m [}?~y]sY@YVČ <ץb)Ih#Ö0sAɕ/|e&Xwfg)ybd j 'M0B} 7`y:t=c,:w>tmbOxZJ~z<+P+xol6쫁5Xr֣?lR !"ɽF4R)ۗEpeJ*t24d91PnΝ˼yhժSNraZQհc7NډfoE~`+bؐ?Ъ]~L8>ճ"~NV)bI2ED}W)ywDž\ӣ?sN:Cfs2?_DQ n}Y`yhO<$MKak֬ڵk2cҝ;x{}\33.[kx z# @n+owso` vFtTާ IGfMҕKReV$JnQRG#GFesg >rNB3KT`'j****,2mڴЉXb~Uj>]N7\6o㣨?67IGr R<*VT B"K-*ܤmA6(SDE9,)9CNvwz?;;g3;s;werޑ H>,fY%ˆo [o>)1XB0)+mXU/ѝqu ĺ.J͛_E]T-bӴ1>m;_cҔH^|6ПSXCWFL}^3?3g1E.3Oߘpq_s&L$qD֬bahׯ̐H5L%7o7k0['"|;C}ƶS}I} Hz4% Hk#P>.?7s&CoKx?gl蚗Ց9d?}ˮɓϤ"'xǜ K傽a>4l(:c$q@x"].DZm'0JޞEUɦpZuwC%syHy׷;ۄ[0:ם;W_ot2z5<uk5S3Կoy뭷HMM%<< `lf­FbH1!3/=PHsL + rhbJh, b!x.@Œ>75XGW5PFm.Q vCU_rWw?S W_}yWr-b֬Y6nrt2 툶F^̫?^R/2ܴ$=7[M-mf/bv-0z<ǯ&c*;ݶaÞɻ&(?iS2Hjdo୵ă~E,͛/ǰbn3!1Զq<8 \BlB,Q֬Ԅڌ_np;b}u3/u*ue=U'=VLȥ1e"Y\Da kÆAQa1囚-9 yػ$ƣѴi=>ϗ탹8LhUPV|R)fJy3ô,ٖ&vB"Ih҄qu/_~togx[GWZ6wT;گT~uT889oz8WUwWߎU_:G〣vY]:ڇ+8ݮ3m1|8.LijZwٲ;[NG}̸1:JeTٸ 68wV,]l>ԕ n`ݺu(ۿ@k;ߩbA3` _[=z 튵'`jxj?PPAff&v X必B;uf֠ޢ=y"qQYXX& .~'*f$55mW|rT_u)@oHL%M4oFڵ:t(ڵsk7x@^oe(MIgM鋖7RWW^_/-|Q_`}azޮs[毉?%5\Hu#s333yhѢzy.OYRG. N|Ūf]v u+^<+(㲧Lq7tRVRxuOӢpI"&g IS"yT"[qOs*,a ~^&mzKgvhaqIxPb5Vr;%fԆkVvx%fTw @a7P_ :uyf5jt;ԩJj02/$"|;C}ƶS}I"Y+J.>yuP:թg6eTr{Qշ@@?]I]`?@z`_du|h=xJ 9sضm;w`ƍdddԸEgD!)#֞U6Hog0w!%auiuM*=Зܚ+#U=Q+f}__ C)_)Td*ۨ[.+VoQF?kF0r6dVHCLz!2VY:F^Rs(u:ae-hC|U sf"KB K:o@>):/u(7J7KJJꪫk((( ""JJJ 5qþ3`0OnGtd,vU^Ns9h8?[Ś^ oaLVc+MnG2ef/EE [|5/W&$$( a͚5˦6l3yw2~غON4c&-lҁFlL)A_N1{pTҫ:pWS_d*i۶m۴iڵk^adIϾIriK=#}AN[hGb {g~+鱻-WRN%`⋼9U'|u/"ja*'66kn;ƒ|6;2xD#@H$ MP7lčMWw cva/N)4oMYl=\#X_H8(,m <)8̽#2Kd>L%III|u]og~)N'MODws9 hL R4z yw}O; $ӛY>m Hkv4 7w婎VviPP˝lo,1ǓT1P'--}_ 88?q9[뙹j !Me|UnFǟsp@`c?&Y{ܕ&KL=pb]ldg!~SL%\s 'Ofɒ%ĤI+ΩX+zI]b*$-g3gٗ #- 3n=)QG |勥C26m Cx=ӧM19[xgkLɋϦي{ҞWai ~^&m+Ya1>GƄs3yfb$&ʝ}X)yuP:թe囉9׷% h'@JBm3Y5qҳoe\ZRe{_V%VI[ts:%ϽV|r;FӦ}= diț3Z'f>BPV03w׎T`b}B]xƳ`g #{ؽ{7#{/ 54`K'1 a𻎜^=}xtv]t }kvoKھ ZrVU1ѝrϗ/KK9qf׎Y'=ydCH?Nc;MÀ~ZIO]K;{q6trsf?0'dA~w:/<̶]_63% tjöEF]KdGVlS:w4msSʾVzܝ8VU/egnk$Fe^ͮUZ~TUWwM8;gg?kL>W];pfuQ飕vf gbv}>W]Lʹg}s⨜Jp|< fՕ&y@㸳pũ:9̗K{yV3 C | }yo T?vRxM+O2/jwR{ o RYV(S]{%R)u쌇f]&E_h >J%d?+I0HA H%z^v}vw{rK0@F >J%h2i\i:V`q)2ꬁlH0p8}, EY,ٖu#$]{3tĝt_HD32X~w-Fz3{hU~vθ/x5tǦPaq`6ՠiTT3ߥ؜huhO niZN6Il1iJ$/>JR^Ì9qսL|H.6Ͷf["_A.%l6_$xUKuRMCmc5Y_gept_w]aMKYlu;ZΓcWM3Ҙ[_l_Pz+9wMWUɛmQ6T'⯂rbY˶|ufOߠZďh 8þC%x(~ZD|/ZqD SZ=Ցxj9A }=8G}Y`_l:G:N:/}},|i k;M=d%lY>9˾@^oaؘtK:HP__zyxuOӢpI"&g IS"yT fωe̴dCr9ijU:^̱g2ѿ1LI!zE "(%:xYPJa)e^HEvLmT? }Q{;U(,LLϙ -Q@QCtV~ߟ=Cb R"*o#DO\C]tNID"L>t'Y<~w{ʢoNPTgL6eӪŔ^ Z/k1 -`a95 f:""^O,Yz=C #8rصdEߦ0Qޚ2e'1I-3KX=K"""""ǫ ah0 nG 0J0.#ynٞ;~kͰŲz#qjÞɻ&(?Oeezԏ."ZxZ%>]a9$&ڶ<疹 8cK("""""㕄ڰgjgʤ4gM,b/(<?5 ۢ)=~x46nH>.<ȇiY \Mԑ=޽{B_<wxXsz܌ ػth1 ճק8_HgE7Yw f~jG1?6'MODws9 hFܕ#|^x}5JLꓣY8`'iGJ1,~aOɃtj?{i\ 7v9,a+s_#C-J;n]WvfvM_VП`?o`RVQdΊ`/S@CxBid ֝PHQ'"D P@ P@ m^q/Kgyzey q'vθ/Xt餏M! ./"""">'ԅX4-z O$brט4%M%) F%3uDs $~Etk3w>SʼL N%)쫡iٺ5N"RsJEě4'%SQS``BJT{/թoˋJ`: ^^O,Yz=cBӡ.kA g+d3M*"R%"""5tӰed4Ӄm I-](qXً#ʭ"""""k" {&NT>ՏQ*lҁFlZEDDDDj+ adIϾIriL4@q؈#1ι uˋԔnwƳ`g[#{}$Id/B|փ,(`׍N,_9͆FNq P,|'Fn>ِl"}&ϿӘaEQDYosn@D\;ni8@ yw}O; EYo.2rX¦#tz+.mF!""""'@ P $-g3gٗ #- 3nze~Y:3ֳ-4FHͻf;\?2JN錇x<.]{u:ge%0v[4:f6 "%c xg]&J Q =nD`"yo.a/=Ojb8(Nb .3ARsg^<3q1] RxuOӢpcD$ MJn^H #ԖF>ΟK0]ڹ{c-&E?O?ϧ #*/SQ}(uI8{baY2U_#D6l dM^8X"?30mKWc!_˙EԾ.DlfݑDXguY40<괿cܹysH_3ZxaRvnǵy v,8^1'"I""QB-"Rh|E{!>qM<;h%²Ed|<̳c8u*64D>KH#M/vC|!?d,S܉_}J]|}$=W<clnq@PI0WGZ4koѩ=D }:Kg@fdo"0N~͒Oqc4lԒGrG>38(BСSHlّriDDRvED"m)`htÍkG j:f_$ɔJEDK>DD !%E%n]kE$2l).Rh,wYⲓ*8A #\ʱαSՆԿA74~Gö/Y\V/pj{%l}'˯9POz:("b%""Őr.1C朿mltz3Ʋ, 4!.AC[Ed0,r "73}hhm~pp)łƳ] 3_G73gqKa )dK@z6{5 ϩE!(mmu6c%KeYIG:F2Fh#vZ.se ~U]OO|կo-O}穪3A(P @(X+ h#P @(P 0.(P @(XEf4 @(P @(P @(P` qH(P @(P @(P @E(^iF#P @(Pk @(0:?u;P @(}@(OA(P` }b<鏾GW緦' P//Z P ,YpKA/Y"ʂP txɝ @(ٵM5Q(@?}+ޯ#j_UfK~?f׏W]>__?}Gb_*=j5f?9?|O^wjכO>f_Sfg~??YHC?+> b?[?}j?byG~AϿ5~Wyj7 rxW K~|wr5/_+jBV\SWC{K^M5_"*u_*-_;[?m3~ܯso~~~>دyCz?ߩ_z5ךxuXo~__(_=7ܨkrI~~˛?poojCz_g-geߪܡ5Tw}]S]bf?/sn=jO^}f{g]o?|\;= NU?/Kwv\́+5Ƿsj?xO7>q7>yٿk~7?}|Fܯgzo?9l$Sf_-z?y~mڿpڟ~DZ_;~׿ُ ;"/{~Å~;}ڏO~o~\K٥_/.soA/yʼnb~?#GJUrk?W5z<5ssXOܿ_o톧~ڟjIϊf?G췜4{7o#[` & P/`p``0Alcw~_+K.D___4@V(S6u, 0<HpDZb2\/<[y{yxۿ^+R/yKWo^2XC(*CW??x_wF4Bm-chh̚*95`eUֽEuRx~F>(W /zٻw{D'lԥW~+7UnR#]?r_Zw`  1`9c &Jx nw_EMB_X7~!ЍN2C(01~wH~ G~Y ?~oMu LYE/|[ՇI>L؞Wn%#I_~g_/=OԯI|gnILc.Mw?&˯&w{~S$#׼M=!w|S`yj L{diqﭱI2 n$X`LVL*fVOTvA:_gi"` YU.A:u+A]Wo9- 3Łot>gT)gO?W$;˕.šog }$Ivub p^s)N> ߡ\ >?̗;?ɣO(*z,@u_9B+'7 c)p Y@Us>¿qk`V!Њ~r૮}럸]W:@| q2RZpo >?\8#aw›ά;w#?u?aS ՋV7Rdok飗,wܫof0< R0FnbEt62C(0>B/g`Epb{Ǐ\E;z|RdJ?@S0| g}?w]&1פevr $3O_sx f>EAzKj 20?/)Vr98y`LEۋ/{,>Xd..oXq# 9̭9r?++{r9o۶`__W!_xr\;f,rgchY_x!i~fqVqN4<9u++?az|zXK?GIb}GkFC8 k7ǥ+OwRh "V~A19[`}k|n-}@oՄv!w ϿK$({kx_+_z$c X8M"zυYN/AR^}䁆fz;d"c p 0<c pя~!Q(ˬ&A(Ƥ@o}ߏ;?Cܿ?hE c5+-L n `r>XOM>(_QNo08fL|oV?rk!b ~~׿%l g`3~ ] ?L8_Ht{nzG!K 9ߔ}Rf0/fS@c$Ar6rGyC0fk7lkp/[ [ ?y ~}+~ /a *2HIR=?Q57ۊI5YYܿ,YGd,ˣ~ Z1"Y,`3 XeK=X_k` kb1$k,z Xbl63XON9zL;7O|o=4 7)L~<;1 &$Xb+ pu._C+[n @poOeB7O{^_ٙgw2HG3`5YI xAt?=bg,^X?g?z Xv?O~3?(@Pc_ -, s>U$w 3:]-f&)`Y`(8>XMOe<## E.z X}CAԿۿ}: @(PG:i+_B+Bu <l"h|bT?)UnUl%$2 !bb_D/dpr?y`}.7?t?߾zro9[jj_G~1%:`f+wm3?qGI+O{z>}co̱b㏏s~ψ<&3[\#$3YIf /?ׄ7+F At eA(нa]Whz hkhp+,xR_!v11Xx!Ђi;n'*T0?٢_g%B_uUWQP5't  ƅU?{׽0QG7{ngQ~wӯfѿj$X 9_+l%bpkr@/ rgWjggh9,b"h7$w]/!]rÛr?K|M~ؿ.2?Qoc[}]/x`}f@{r(Ƣ' @(V@%6?^w5k5Q P~x(P I_/| ynGBMpSP @I+]kC޺ݨ @1(SC(I ,9@}/Ϟ~v>Eb( J"%P @(0u]vً>"÷;7l}G_]\pAm\xF @ T{v9uG`5ٺA Iڵ[+ٶӮ]KԮ( @( TvZ!'PLE/~ogbZկW)ܙ?~ý&͗%[TQuz=C(P@]>}+z[V]pR=Vt3ʡKֽv*U\Jҭn^EڵR9i]}-MKt @(zQk" ot'q&71⟛\.;D`W⨸2SKHMO.VV ^Gl]M~9X?wU-FD=ܖ( @(0b*FM;0[ۜ{m*pq]6ɷ۹d;/jb}lpM+dKL|8m3.ɷKMm+.Ϸ+mlWUvu&~Sx;zݐo7Mv3nɷ[|lG|3"|;ɷ{v_˷8ȷN=HD=o|{,Ϸ'o+ily `[% dV̢䌗f)ٱNZtP8NeG%Y9ŷ&pkRS0xAUY]vڅw@nX*?Ъmc\SB|Sq/K5(PT;%_-!J;h+'=N_/<(&@eרQ _?`ykZkiE[v8N*Hs-KQ E:G C#rN3NB#9"e#2~-Iu]Yknc\?*rGqqevzBRr}C?-̗E4BUt!M?ucjzI\}pח>}k<) }dW 1uY)Q%2V0Gy=C㧬[fJZ\N9YNl!%Q`` ,z^O19.8+lw#{~W j p*К8_\G $PE2{ ^SFFܞIOO;]mIQ˃ 5Er!‰ B]8z/"%StC$_-^]41st.^h]-=9Sik~~n#,<}w_x=|z8g$XKB> VX/uOnn/I_%#?utb#I%d)\9gm^)[[jQǁIsLT"#]ɞ=WD X`wyNqJo]By,%]`!\J @ @(:S`֥5aA{E!Bh~Ց} ñ Ibٔx-n;{Ad+Bt) @(Ƥ<d_f`_Ԝd!g> ߽O6m>tsgc'.R{CXy/{OL>le>_~Oigr -'JGoΖAEiZ=<(|V1Ҟ8ru~C|xSO~9y;RHw;vwe~{(L͘K-$XL[^\SEb3M 3OHp^cFD:P LSEz ,p}_:a*`ᨧl|!LҞ;ՇC|8݇|C/\zDsG/_/pɻ/{t2[=#<~|di'n$|ò4 W%o[.|$Ǯ2ٻu9h|;%|#>`x{cp>pv,&6 R w 4Z P t?3?sgfu{]•!]L _;XJ?mdQ?(#ϽDoznɿ1_/ߔ9{3.BW:{O{y)ZmZ ~csph?#M `M8[ [<8m _5Y P @y)~E/slG/|rUYJ9T{lh5+Xl0`*d uu4cr1$X04:xsS3J=/gK&2|w=%{Hr&9 Td#'!У`C 4nmy=}ҠP @)* 7~Skw^"]I+8:[IWo0:rp)Zڊ1b.l@M GW[ο Я|x,XZIld!n̷fݒomv;َvgEvwݓo|;oq=ojOU,ɋI*E(P` cQp+<GX+j{9ı+/XQ8t'|I/^.lӟޒh;P @(*F-'j(dž^X6ɾ^ tSZBFP @ ktXEA#ZAO(Z87xk< @(NXP qo[र $P @(<!K' Pk' @]>y4uA(P-BݘʮQ1-9âmQ T`P @)!1άIwТ1(Y P @(@ gF3l{SZ]ɶ-M {vI**d*FI#q~#):(AQ 41=V!1άI6%QoP<1ʮ[d6dhb\xy`WdY>\-ZWH).XkV|?~ fBä QGuxM3fF#hVVV>Z98K"$v>BElѨ˫ ED^pjIZT_HoH%+@\px'l_r2'gFV9a`j)3fGܭsਛgEE0cOG_g[rNږ.VTA6 W3ַij-i2-2hﷺꥆ0cu`svf(YtM4x=m\-`~v-A 7WK}"ؿEeרEimh,VnZvYO}fl%.os>4c8w*O( 'gR9娩A<~UN±Mo(Ee#y-kUSWr[KGnے sHoQ<NI W'|+KVMzE llBe1|m7[#`}KA(ݙ '^NJȂjG3xyz ZJW"`qAJU*=u;ɵUm'1E }iKX^`;inWTBJC7KK&prcĢ3jrm :jiQ'ҷ޶g8ė[ +5DMqD׫.zUIr׊PȬTBq$\'* \-IWe“-5/6Wqx:AES=>3L})!pS薸vV(*&cՕPL' C&ң_4I&k>iF Ce0?@h>)]U4RKHe9 ֶ76/ ̃pŔl)* xcC2Sd?iɒ}!H! &74Y}-Yb'v8g%ZDNBxj uR(%Yk*[XeVR8<^g~B /4ѡ$ <穘A{P@[ܷo_UFڧ5ԝBֽ1C復2"2ˍ1a=!Y k,3},XI,jdv,S۾Z80I/,6&aoLpqV#)8{MAP5审Sd!NS|f;2)|RknL,b j' P?"\-t΅rHhS?X r$: u}42|.'~94v ([d^98D[ \?`G],>+lP`@f֪M'1g7@{2x!,QPS(iҝd s–dI~] f_vvү"T JvS/  @@Ǣ8f+7KeJAz W %{@<&\.m.OUm}!z,;B xgw ĦԾQ #? Ѧ|ib=0 @(ij cW5VzӃ?87a #T@;eB c?W zR8KJnl(P cP @(06h_Eau0*iOhpXh2H4ʆ={eeP#kVb[:V{v9^[U ?.(SB@r3H֏ SXɍm!b 9VćkCfɪ ۞G2zMXT4SƮ⺴7 . ϝ@(O b78]+`Ce}pWVs^+>W ʢF_]k{ʭXT sZC:]Y1՚[H6Ĭ-a^t^9ƘۇeqgfoZtTpLV9mZVmejkR_e4\EOT5S.Pg T+AIS׽囇ONȅP @obIuZBQS )X&͸K3MgREUF\&.5A5jQ2eN2|1'7k)^uv@F)Vf:NE]V)9Ē[[@oi&tS`d(B WnS M ;{ k&@Nz AzП6!SU?jTT?njjQ-M`kᓂ@4E8%IPjܰZKxCRiV̬5 ؒ?E99տ֚.V2w٪¯9TCfdV"YS2Si?^d FpNu^ Ks_z5n+'C(06N`pO6]Xam[JBض ט`~k@aB fl;xsq?9S tZW3-=&}fŽU7`5CEQlj\M8\K(G42z.r9Ed LBu=p[}kϏТd"`o? V2Ϟ=W­tb pБXΩg5I'$ %-/tƒv}.e΁UMhxS d֕c*f(fg%_-?N.;F K?r/M\3˲uAJ> HR K0?8@RBA*^ @ T@k;L9{V3+&:Ҵl8XS}$/-ccōԕG ?gr٭-si˳\Lj*('∎( .nEbb-M\:: Vq54f|t}$8Oihjې @ϏТd,:EB7i4r3ҚPȴD{xz*N=7!=qɰ SH+'*Ox0cܒi %LIͽʅüudy!BpbH@Z@RdJxQh@)kٳ`9ޓSW qIOP` a @R SX"Ɔà=k ^kԔg@%P` GshQI=NLh?Y({<Ʃ43ǿ muېf t Zjj S>(e*˳)}K5reyZ./P8a~ z$dj_g~LD&̼H::Ȃ_ȩSq@f=#vuB;%<4^+8|Bb( GshQGR,+Y;![>Sֆ%>Zp󂌙z ~fO:i&sVQ);s9]̎ʬN'8I.$P8a~ 8_ճnl/B/6koe~jͲNˋbW׳,&o.)]ض8R6R0lE|k-r\V؟%Ppzi(%d 66xTl.ӳ2Am+)[lKfGCeJ$[hFbveIBb( GshQGW *|9v5;OIb, {%U%e&G GtΜ aőCFŊ c3 .N/v[9oc>3kCGTPw 2K/=xed)٫+3'3MN:$ @dXB)鑨mRǣ<0tƒ]Oi V)le|&fT_p&AvYS* h=n@lg\ir$9)b2i:\מYrlfLڿ3;*JN:$ @Z@Gy)]8t"XJq ]V ح(t-/FCU1:rLĽq(!3qMѰP9Q&-C'o@JHI!1qɰ SH"L5lBKjW `&̼ǘKS}!^Ua.>#oIմKCG @@zxLq2Ҽy>^\' tIr!1L9(Y#13W選n039F̪t9%=Xfұ͑|Eߢ˘ -CJA݊shppUH3QSBdKJ=$)N$C(T K0?z$MD"o XZh\5E0ىdPI TRhg[R;N  T ^TO"ݻk"gXI=ʾt jь]zeV#?=?n}G#r-P͌9fֵMUa n`2h5Zp2,!w}g1!c26(TJ|iF>_t*;f鷵]UP tx~4%+ l_6v7 @+N%d`\x8K㝕86Z=HPv|fhV[J\N yiE-I]2 tgE]?bM9f1EogP+0/^%Ϧn.}:z1"{vq' v P`YB+N%dAvᎢHbӫqJϫC>`IlBRk+ s:;68kZ7bQPz@VqVVpK!v Ƌ6\LP` GshQh_qLβIw7PbSM뛯,r[VzLa҄m_td Lz x Ļ=?w@ެr[VWɽő -Sе.e}Ks`WQd9(Y-ou [GfEr1"l0O-׍5r pwJ fx~4%+`+< |Uagj0/:~ƆM"EaK Q"9cv9Pjm~+ da@ڰڋHܧ!'ΩC?jG4jJ(1őt\jF}[HoQlFi6+Af?Hjuk52gXkN\H;I/Μ%6`<Ҝ'yh(j)N%dj?dnòmOD>`>K=>[9W!b xl+K@i5̜M5%)SO0I6#y ,-dc𘞫 LCh-JV`ݣ3翚ƍOY \pܡIlMU [:8e L]VAQU2yxXpQSz\V(ZU K0?QX13睲[6 *ur9z9"Յ, $ZCo˵ga9/n<  @ GshQdLSQ_Le@\Y[3E[b\*a.EEbUN]zRg'*բ|yJbwRH<3x P S K0?,N#rPؙ topup(PLNcOS̥:|/Uo4 Z 0Fc #Nuf׿eUHֲ.W@_̩]ûn֣.Lg0_a >8:( ,Gp2,!@h+:P 1(0&`k zXzz{B\Z Ƭx~4%+~P&MKN숕Us< `XdXB)C".o PC%fj0,&P`#ʡő傗GKЃ&1ZY+-b XkX{͕LHI̓x3&T כ7ke]'Uc VM:%?1%3 @ި!XΪ`p]{<9(YXI⍤ĭQ+Π |dLƯINPfטO)l{7u 2bv(0^ɰ SK}oekU:O9K?уɊޤNbWv<6ˁfB4gFoJ%FqV ".R;3è\bOk.J־+k%[]89ٺ%׀7uHٺ<C-mΉdk]imOܶ " .>hvru(ig֢@vګ_"flyhݜtzr3. %9^3k_Tip 9G=E%08kg<xp1ih-hTˠY<#\ N 1XdXB)PlcWҎ:x_aZ DVXU ӡb]Ui̤] .  bѶe((g.I{RdɟŜ_9>XwY5Ͳ&s/K9uPK/w],NLq:`j3BVzr"ݮg%Rw.l%/eC(0"LOJ@IZsE``pN(\劌&GJIY|f|^/gą`Zl,Z[{ed-[]6UgBf8֙my¼oW#I믊q5`6lij0 蜝@ )'7ƪp?TjwKdp'A) qMJU߿~hwU~h$2j:GIeb44{ J #ru͉l8"s;Kbn׭r`ظuwRM9*,#0fj 6du j}19<\&}(ԾpD߯:ŽF>J14G7GE¡9J/Kdᑪ“f|iyqZougi34 8~ `3,`}MNIE5 "|'Rb)9V9nR\n^f) m}qcɵΎ5%N_YOe,ShqAZl|zI)PQ{1^ ]2%R8Gd$i^JObFrmEVɚrݳ<\%`LФT"qsQy:A[Q<׭ۺ~JsC;k֎]L&ZYJd6˞,B[']3Op3m.a,9Lh3T<,#` .5Kݡ)GYeU@eWV@W^6|R\62 4CHfx8&*ie%xZm7zVNUL{vEDSIJ{U:vpD@A~qъI8=J/-CE" kC ,Sp*+! X.9ç"PdŜq|-w>m&'jЪM54]0 "VPj)^2I= Jc y Քt9c2T sR" 'h5Z qMJU d\c,sg?xWԭBaVA]pȞOQ;yi|^~VaP?t-Ks+Eqb{ɢ2e*r!iYbmZEQc,Ê%5lNMbZvzEQʅe?m:etɴCƝw"΢1u-({9̘6Yp9x3`/ ntүwL-'`J,Ed8ϲ;[mY""5Ʈ4s4Zhs|^ZQd/9Z-l3@5V@"P# 4Xq18 r?{̹f=8`p P [8[f f{cM zVcPeEʩk֟ 5h3Ux8SY<orFgM⩢,d^8ty,iʿ ǯ1* 0`ipR!%Վ,1k]'s ,V qMJULy],:`B,Ĺ:.)?R{^;W }ր#+Q ϸƦ50N[;;W{#*H Wp*+! &$sU)|?k@{fvB4GNȵҟbrAPh-s.i;/U,k qp94` MФT;a&@p VxKi-wiHg34VqΡN#!1TVB*7f 3Pj[. 56 a8',O2V`2g zXX &{Vܲ;TX` ~H <ФTu`Q" xx9~8`㬵NnK Yxk!GqTVB*p`Ά7bi#4;eEd(#C˗/<2c;s%(0w~VŤٶε|ul \4n.o4 Byo9vf3`gY֧(&%-*) T\)iAзލnw6|<$*˔?\\ܒ#mE/鞋6{Rj QJH?C`0X ImƴDR= ~ۊTwTѲ%UɏhttryҐzyk.7/QC1yL[h"Zd"ARs܍.p kܸ9ǒoGOz}hg.(cbYHO8s:vKv lf$r~C][}q:! 4 tx8&*P—iKV upKzwV2H6iB8J+ *hJ,`U]*KFƱjnJ ={]6K~ 9p\2ESϲ);+FVk67EΦ>׃<8p ř#.Scr.0eBq*Ne%wX{=#]lͤ\v+`O煎@8W[IǛjBlЗ0\qf9 az|2W M;&"8->V,v{bڊ&>[Mqva gshR%ΡQ|<*§ Pj]2 xR"zѬ'BE9$Z@`.qșyP+ k HY]whfs !YJORqM+3DnB+O*7[p~`pjTJH?C( gfi RrE/&قd:V<:+K3.M@wה7Jq;b&2Yٹ|hb+,XVwֶ@\=~tT) YMQf+sɕ?h_rKX\6@=( =j0VdP`f gshR@DP5׿())ad"n u؊.q1IT5*,Uv)D z#b:l=D0xfԁ@(Ne%};LPfGm \C[٣ (PneH Ưx8&*P9Qofݛs]2NJu9V_B& SY g  : nV^!`ڤ \`]"^n 4Qh[ϜKXZq"vZ9CM/ϸW8KƐ^r>/A3 an(X z xZAS0G%FxX<"+lP @p*+! ;v oZ>at$T3G[JLez PK܈W N?7ZP H$\emuQwzϒ$o]6[P8 vz6{wI6C(06394)UK?Zh:=$aY̷9OnCnR`xjVO4K-ܛ3ugCލ"ެL`ssS'D劌:q>\|C!& : jU;` >ax0 VJH?CN$X%l¡m"y ǯ97"㩓ph_M: y <0hFlmԷO?¼0:cϜ{e`kK~6uxe\- _ qMJU$3KeƣO4q{B+V˓`\Y$cL<- got '7 ;8!K\<`Zlh7#`؏e"7Z]C1`0#>aX0M?B(P[p*+! LŘE5㭴'vLxG(.b\c 0\0^a Ǡub ӸndP` gshR `2M VpiglbIWlU8{ =~R %ʗ]Y Σ`$0/ZNڤ1۶a=;lPTVB*NUC T{ү8hբB++sk{| =TXq:U5mH.c)Qژ1ޱVr%?P4yG P`l gshR<!@,*5^Ma+wR.F.$N-֙[%AgUŹ 6$°|6(뢵EWf0c:h iPpe\) d$ [ g/ko Xp RE.{C][`P` gshR#R"$ Y^Fw`0><-b]oh4x~[h SY gT@lbh] (<2OϪp⪄fx \ ŠNͿV`!!Yl\ pO qMJU;S YT a$'u`'q1 ,Uݹ,m;!,5)48^P`p3T ↪eA(g|S0TA˜&(oRvB ]<.*C '[aUaV^YE~`%^,QP`B ơ(HU SKo$D/\p#@X0-W!HF4箅4P` S;lH( cxjvgCIG k ٽ's8}kU-MuyY%k8s`Wz ZR?/ -_}u]u+r0R6M!V^+d(Бvq( @(RG{p Xs|u` m鏈Eyzn΀:Pm6P @vX<]o* v ٛ ,1Zl]VDtElo {ܵ(W!q( 8A@ )( tnJP @TLV(2nxx{|/?9pl"E گ+\{uV \YVw[ǫ>HÞMFx 7ӽc+{7O:.jrL OCQ^VX]P_"m{*<|]:7f))g *0(Еvq( @(RhzֲC` JfynO˒ől}DfkӻGd Mܒ QmloMv)+ϰWi4xl.̗$ ~H5f] pWr@7 S;lH( OnޯtLRnZCrY+0*?ee.Q (|2K; 6,Zᬾ2)[) Zl p7R@ ơ(HU['>9훛t&PYiq]>^HL|NՑ4SW0VФ[owC:W@ix!^zԱ^l@dlyp#&@kE[ ƪ8ÆP @(Ў0ujKd袓<V UP{HHZ{" F-.ݳZ p,Zըv9 !zݱb $SZFX1vAvq( @(RhSB]FUBЀ3j_rmV!Еe{ڱpKR+ o煷/D//ۗ+vQx8nvix,mˣm;]ޮ oWGk۵px.]no7vkt-ގ;۝v4 oF۱]I 3H`^Oz=pH5)ڪl3l9pIzP#nF,%ea`.tai›-+͜(hý~ar_`  u:;sw) T洩F 14p*7R'5\4y- ^bcV\9<O\w;Oʯ|1W{ߤc ;cݱ`x>/, #'KWH554u){+)@Aw0_u"[^^%J0Ļycϟ:v}X8=(s8I.;!'%ܱ4 oxT*[s&Ou}Oud޳Gɱ7͏ӓ-uB[̜`.@+}CÅw$/\zD[>Gn X _.z7؞m†n= Ϗܤ`EOfY?z:^p h@QMf^X<]7& NZ_uz{h}wmD}=Cn%c #7hY+P1%Cws΋3-GAXy]|KJHH}^|N|uwC$K2-EA#G.L\>xKW7hM'dGn<"ZLU/_qx$;\VGhX{xhsp*7N\r7>nISѷArC2HrHXi{jmne7/`p5 C.b~zzPc'R-poaXy3VuP}ıKtM#;o^}~VKX,W<pp2k= _!]TǏo7{x@k~pNoUgַ 0Fܿ~xkr  ] _ɣŇW}U{Cm>j1N9V_kkfKB'cM.3s0֮,F{Dzw\ ^ M(OOܠCO=qRL.-WdXp!dVh ~9*X=+W`M{k\vuX{}</`Ƃ{xG8~KQF?2H -K!os?>OBXǽۡ"c0o6Q$XjlOcXI[N 1W4 L)Xjԃ7}`BWrН)h%!zt$vsX} @4k?zlie˺qV L~6`f[*ME()`ߛw{$bH9*}`?}%d9NgbC͠_5,dOup6W?ik.;`I<Ő" eÀf >K'^5ӕ*?gCݚ.#r$pyAMRЫ_5,7lۿ??y'|V`eÀMJ6Bw`pk!qrmPgA:)8>މ( )B>X. ߯H9{ĉ^3XI}"!5} +_ ]^)˿I ljţb1$,]{$w:t C*#o鯫ѿz+kIF d_#:Xet3yf= ɮ *UاBiG5u'^\oGXw)I>8u@Z ,]} ;V d18_ }}bԮZ/Cʗp^k倭e,VN_#_} \:Xuw6`3ϕ0)IH]K˙Z 8SOl!V6HqT͟;A(Т`7 " '-Uo~'Ν®W>ٮ o׆7{07CrZ!@H3EBu\hp܂@+ c!`  ۜ^@wpBSdNm^+hhky9&e+|bZ$qP6S4gӈPƍ#ߞ]M 6SNW~4{Q#hS0}x:x\.\ٽܶ؍a\)'{3؀ pev.>2B(Rfg*Ev㠕J.e W ^=/N`/+zy 9i9P4R`~!-xǮn++\)Qآ\QuS6֣y\I,K&(iPU߷qB)GbړZB9ېct׏;33lI[7-VA+/WH@9w.ܶ;wɚP`l /O@zD} +ie@둽b[d,wfg9N==.+^1 RCC{KQ=%[$t.+gLo)KqUL%Rmɋ`%.8Z{`U n޼b pgxtNw[S;GwV33N%JC+$P!Е`c `Du,2VRx\+$x+RYvf0 GzyjJk4|6%@ٜJ4p\LEW$XB2-˙+>$\}AYz)@"a3 q5-/q9P^\EM夦VQcL(O X&E-_4=r= 4Xq ,n,jBod[2v!=(GȲ:I_·`ZE©v&4o$ SH$ (@3krlv/0 ]幵P<䛭dIm-UpRo-!q#82,bwUzT ftHrFIҤ3:h Q*X2ԟF8 TtmBƑ+ \ I+ <ڽ8]uIIG2 gNpd!C>3X1KklC (舣0ie>8A@ -eWBZn!ЮG4҃*ʡ@L0(nH#Ͼ!/1o@}P`: v扄sS9hY﷖xQ!0BʻŢ!GÚ­?׭ >|սdYi:WS}V(<&X:v(yaENx .F$KAihh{'NHaQEBU:ʜ c95Բdv6nk*T6ER_,gThƄwҫCP @(P'XmhD0m6+x]Y{PJ3qVyr]١@@(@' CZnz মG`w3~ +'0`-x(0NP(P T*bDV0~SY"9Mc.w: );B^d@yªy+ӱIcU;oqI}C+4{jaDP`9ڈ8JL`Wkdab`  zݶsy,i檐qY+suΊ6z'Bs;p&cʵĬ ɠ@\0x۰u`~NC9;o -<( Q<j[V9/5ߣ;]PzIY5ɆR n(9++&"_70 Z j+炊)es*hRc)?ސ=!يnvQ7i](j\8bә_f3 0M [7oFB)Gs+͈@8?o*# :I[$ dq0la$5;^eOj*#Q9..p҃M`o!{&1ce9HZT`2||wz[1uIgS xVGLnSyؗ+U:'&x{=m^pxtJ., JoC8-FKW | G^YIQ0&sρ~T,Щ3`NWl|i8ݔ͹H SU\IY<Vحڀ1Ӫ[rY%\)1)IfC 0,pu}.y2|h}X~h}6^?#8?c`[ Ce# +uO?mvTŭ"tqq(76^,GC)]Jp%VZw]^0f­9)!tSteӐ P`9K8NVT6}#"ZeMA Ҿt^ݶ)|Š3w.c2!?_Y[yXܮpxkK&9)m[!+VG!PS : I+/T=Cd r*3}}o?]S,2ϑ]CV#=+0 8̐ok@]SJsi@9]͇ƣ@w_9O5w![(l"F‘uL\9rV!BOڻD0:P @(0:.0wX1;EhI T%HV0i L=[]^})Nh>[|Nlp{軣 (0cB(0 &2B8 v}BkA/%ð3;W;Cnp@h0\( VɔkJ VΘB4(0a} @(z`:7\/~$_N4gm'U6K#Zף%o^:9R`x^Y/ٚY kV xt]PLWf凲tjg?;(o8,}Û aHeG3؏C>iL&Z1٪N$Xf)c=Xڪ?Ys6j6+% %$X7`c&0WnR;1h|!A}-׮`3< X{Q=)088@ЄO%sfQŸyҘI'h> 㡀@EV.'0kl/p18lh5^7ty-k{eԋR' A Km*^ЩΆnφ=xvaڍO˘< ڤDt'/G{Q=en\w Evݛ5^T^j[VUn匣vH`;o>8M46.9,`6XS1`;]/\P vJQ:@9&"G"vK`B[ވM͏M,iȰrOz޺mGr(Е|m ,ck}hBSAdKt,}Y8x+0J) i'P 8 ދIN蝆2@ á];CSmRU&##rR@A.(0?`M>xNVPkT ̑i_"~&%'k Rˡ9yCi8y T-lܰȮqnQVE! Q ~$P@)lT@b]=NZB+UXmX s! +P^TC%p#^FsZ5 8nH-=SNo *B(Q `p['Yjt:8ku rD VƽZ`.yrn%Z9- eGq TKT{ڥU'%N曌n9&{ zj4(n?( py fYjt:k5ڟct̥)P^TC"̄Gx9D@= $$# gPr߳{N8˕TC ${[plk9'c  4Q>K&\]߽G edL [{GB)5pmflm6̜XcP5E5Z"StqaBUIx LZXYh]dca|șUӤ+&DNwC%Qw*`BYs`מGVĿa9,?#w4R=kEh4픷Y|}h=On}pMZןeҢ|FfX ?uRH'~rH9(܆"n ̵RV.Ī9ӢbilM=ފBOht+VWVv_#6s2.8{pMgs?8*S2g'n1cwGWOOp/'qƽB+ ^IG3[WU2 ŵFR)8-O`B¤ˮI"+LS-{UOI:I@[9#, o+`lNmӗ׋j"y3% >Ϛ#6v6W8w>3$rsp7xRstD[Σ@2",4֡P]*!Ix%H[[e9 \^{BvZ+-Tͩs$rHjTz\.yG2f{X{In_C巙ٌO&њW2G\I&c%jJy@s'D=|^0IU'C?xUx`wɬhAJje "l洱IuI姂,k8:L7YNt1I!hѺgm7 V1RX)<-$Xt+ .(񔧿Rx:uTj˜n&RNa[͛k]dK=ey1 QGeb`uihL1P` ^rKqI\vE 6Ids`f^F̶JZ9fg ɺSĘ6+kgӟ,yL ay$&Y# uqPx^7l콯 *<{mn -vzXIfZz/?iPCSm85f0Uc\rf2c+{KN`X׬,KVmlv/)y˖kvݛކx]qg#۫.E@ת(T~+-<~CW!z+KNukE|2Z\ݛ]bB ^u6R<ڞ! *6[SPRƠVv)K9hg Dw,`hwO^x>'-RX: @(4E/6]vP6, C(^ZP E( 6V)i9 zJx,AXc`8 ^\3H6W23^ S/B(Ʀ@oz>l kՍ 8tQ\sJܽ%Vn}| 0v0Yx9JOolR U#9hH s/x[ @(S{W?Q宬h[Oo͍ݻyZF)Αv+ӉN'ךysyaMP @ֳ@ųU+y<=*34Ao\5FZVhr4nLJ e1 VH~:)gx>i=st@?"0:P @(P+&j2e[8_AdX p__"tK2 O]I=u;?@1,?ϫh hK0P @(0hRO,A"Lf Lnoi0CgNeV[Q/㴣uw{lVqѦz"WyÑi*EhњYw#Kѯh\<hh@7n,! X\֪r-JgoyӇ a7^G2(:R`\,NPyzs#9l!.)[w)4}eMũGT9MLcVQ,Q(ϽhcGQIz;WwsB1(0*5Pء9߆WA2`y6P|葻0Rcvٮ*::wO-Cd+rRW7fwˏg(s\q/M[UDgҙw8c ƦG7 ٮ*:դӲB~^D"9` exCEP9l D~6Sue4U'5-)(@*I߼Y]@#/I f.hpw ;72)7qOf['Cʌ|ҾS-7lnظ=|$w~($Sa khZ5Yz*?Q٩XD=XiT f%q"1DkpYImB o9BC:PPV,4Cy#/k_t:2ŨY^1pmCpjnzpqpS> @ \=@&0ˮNg+Nt,M~EK`+v )5|2k7:<^Jj*!^cUP;:[p D RkiC^8X.pVٰpO>8+*xx*(0܋ _N(ojHr C(W` <ēwuZ6#npI[+08\!T.5\p?{&gz4(ЅcV@@.^O ='V Qp:  &9sx& *BgalP ( 1IzB;ѭ[PҒ֛)> &-bCKFbT( !](:h^w'rԨ%IͮkHwAM(s6 OoUwG3@~cYK<|K\KݶLl@OKYsjҿ"$SW<TCCP`pk>X}H;T,>99 ,@;4ƣ],E;N6$6a݊ϷѲes.6 _8Pkf~}({1:M`f9M-oNvۄ~E>(ОiZԼvy/y=qe,\exPqAAGcW^Wp% jp(c15S7uvN2 B)VVVsV׷Oʮǫs)`dY8Pi=32rj=pw)9ʞ9O y9ICwU-_l{y;bOƷ߲6$lD7+c*ߌ!1|>ۡ k'/^]nWq'~.ot)~:Kz iX5֌yD>W{AnC(R{W?Qhgͽ":!\YtL}kzEPIL8ϝ?eAE\N4 =V:Ҋ sԢg<+nee΃tQ)crP @]Qĵq, (xg#\"p-ۻ[9kcCi2.A.[W|I2FkΑ>%[8+_n_V.$..bO=QtKzP `tPVANCg` ^k1*\}Ѝ߄9Kwbf5OO2Q;`-Cv*:" =IX>Kg+7M-&&v[uHRxKPx*pZ/~:|{!-tq{WF_t)sS<> ʵOSWx~-@?"pLeMmF\0gj_ չYLIuC~:hÀ}:܋3ŢyP @ 9OJQ X".6ikՀ(FM5SOs03,aÞ>܋5z,m'[_1ɭҠlW@?_.KWډA $Sgu5ɔ'6-1`"]Pdz'яEu(h sCY)ǗX^MHIDxS4:N-S#@+P߳{MT33txV=`8>-caFm^PrqlR&WzIX"$PWlDȳO(Uuk4)Y!5i 1LO/~)/UL\qF~^T k?Z#"XV/-I6ޭՕ8sɖuEM(N"uTtJYw6`]ɉ1`Ž1;W^ϰ_F R:|6ueSG9pTP>%e>dH _]Y\;vB:xv*\$؀1]!u+H`שkg"ce4 &$ ^/S[{ݛԨh =uP @*HfwUvjvƥH<}]ԶHkq uUzRh D+ϷkA(ƭ@cvHi;ʚK/=JYJ`,:$ۉmIlSzSX8Oi ט,Fj(0a:(|6C 'ܿ)%؉it-kmҼA*\onQz\ 4j)-E(7$dNz((0.e*@G449s?';TF[MQ}*U\S@Ֆ}|0㧬a[*5 ?xɕ]mw58,x߈HC&8vP0 x m #pHLJ4?U/MRVY0=4zRxS\Y9|I3;Cb~SR.2#Fpˢl)sZ+a,TBU=YPV9z 5,Ug+/n9iy)0ffvD9$KU"Ym0Vmq ޝ YY& VuͰ Tv_1UN`49spQ i)t{+6#$lxƸwrz{h#-f'뉵`8T]%[Z^kxÙ! #Mv[߽0*rC2(U\k! wsQB1V\H0zن y^)y%t1ݤLz?L0mUāH$'#V>ܻw~??Zx:~(JpB~ G7$yY MݯBQ@ f,S]^I-W^K|kR/KG\BŐ 4T0 >urBC*a{|MR,}Y  a ١PxƎ|(<ٖ}ܠ@kL `< Ei^a`\'KP\߆lP @(P 5 "2U,J5Ly[YͤT^8v|:ch# <3VܰP @(PS} i DߍJS2Vӱu 3,wJ /u'+\`\KP\s7HXްX\oE&#sB-Bv(0NZ̏鲼 ]/Q;m>x 1FAC`l(gdUD͝!|څ'׭%~B3qxv VV8 nVSBB :aґ*fP8Sp7]_j+8pXh{ڰ@Oُ5j̒Jlc=6/* F V،\`鵒<eѕJz0АS3in4xlx=C?Ss#k(ra=n.d^2D&%*)|6't#pˏr>ApA_QN3wCGa``\"sUYk#-E) T48zjH ;Rʩ( LB0:=+:~ٙCYP @(W< C)+ZxBC((00)QPpVlb]դMjA^(@CꝄ'Ԑ G`M T 9#>{OM*ATMjIzz0p뒢@( 0g "V3M#s9HfS)Y8EC%m..iUw3zJ,UzI@_VVUċA!6e6l{564١p$ @<9[G邍ueQG4N,ã-=&3m]1҃m%H %ة int#3plPRp/k +e Vu Xi1B^bQM2кrc:Io :}_1 Wp% T< 4U x]$MJL뤥[`TemdTlx'{3R\6H*'F9T1 ǜO/Ĵq8󁶹=7MH(Nh`"JA(PxNOmqdS'7gzPoqb( 9FB O1,0f6p:`ĞЀḸ|2 U|3VP=5Xᮻ{u;femj*B3ܻ"к9bCuյC*:xY2 T{sGg\/ 5lUAbiKp|!yL|-ۉĮ`f_ɺSRL ]( x|]BXX-X+ۓAL&sGyr"i1vTE`r[ F7 vc'%0ǜlt:{=/ 8u~Eۻ۷^R/{VB(ƫ@_*HXt)+u!;ʬR`pk>Xc_V~ܻyOs!=ޚ 'poR^oA(R`XVl+~ W](04 ~U:u]'/9 v~:RQHfF~Eࡺ @(0^.eBKZZ L99>HR@V &@?"x; @(0a EOR'@xA/\ 8 rz s/սDP @*E-*0if²tߺE%uQ`o s/ ˠC)`AR=a Et8߲7ds^ir?"PK I,fW m )00`&M} 1Q6=Ba`(Ͻ<(,P @*3jMDPI3\a ;(t.0XIPS~Eࡺ @(0^GҊd$2ζb4oy>h;w8`ss%#80N`8O+]<(,P S-8 2ǜx̿BY)LA P'0B몈|+Ͻzp }O(- wJ*:jG5jLI@jՐb4 5eP @y+sF-)Sʞ,)~KAtIje _,*KQ@\ )_p~H;yw/:(xh]fEQ5j/}ZK#K:Xă>ZTT;{N 2\,_54!QseFfI&%%n"cüSa3 LQ0/81m H9Xi(Qo #&bR6&A(0`[0m<?PZUa <@LA;/$ {#[48;157UWdG!?NK7js@+3 =׈U"ٰ < +plM!V .J@+mbeLCt*B0};=SgW5onG/ _$CBOn`1 30⒍{Y%X6zwƁ3 9h?8B(0]?{F(0'tC" ZCO0wY uױJvl`NwRp$uI69i@ YSn-xgo/-.d|gcbio9h]^^89tq~i3:Y̓H @,3d*ӨyCT$%v'fɜ`/Yr ¶28IW#xVB+cDPr,L B(Օ0[t7R& ez.1[h{t:GyfF1Y 4xg|ҭ^ꨈl+%~*qe!HILaBJʖ0)*cފA&ԦM]526Wyy'ٜw[#&E<&B( Я'`]7ʮ6ٔi4^kh[FIq̷IMXޜPByw:5wg"0zP @(0Irf'V<]+=f7Gh4*rSZ,&jbl!L+yYlfXȆ$~C g^JST `u#z)~R}dEss40uva];Yj{+ծm 2Bhԃxk'uf4R7qF* Q;x` 5*` Iٽ 0ŧDT?|n 'N|D8Okm~";?qm{F~*-iʺ t@C6N }-lyC`a0%|f;zAye@x>ŴD{>N]B`l+n,sLbR\oplIt#f@W>0]]^q?q5͎ ]z+}&Pu~#< 6 'DC,=vNMz;P##j$g3W/%W'ޗ/ni9sz¹ff $k[Qu aWFBnbM0n!Uۄ[V:Gx'ڷ1x'0] .˵kOiVs6VAj]m)r@ Jo6!Óɬ6(?fc’*R dUUiHoZPĽ\O,'81̤)Zi*yŐ4Q~bu[Vq Iv_ B(Н5<.Ò (UQ"Kw `Nb%y`Uy#z]XN6/6fԞ!K80aVw~ x9,P]% 6 ?pʣJ_ȍq`&8n@RF`je7n{#αҒ+VQІ<u1D2(0*5Y#W. U ӭ {>SA1Ƅi+ lZT Jc `$+tRq"sk)[Q@AD,J( 4Q5M&ϭhh?cΪ4V JeF~BEXZp.G:Xx,%KUܸ@jB@I$n} 몇>FZ˓qae|H1&)u l(]պt7>m?6Ɇ/2Oy' gNP(V$6!ЊN Z\b= 0(py#·RUr$[cE-]$Ci}*Ow4T2p2 Vu7dQ1vj!K\+3v:YiJY[fݟLoM@EU sqRwbU uvGKVņp%t D`66( ˒Cjz |m?Sy~'kѤj.0 RXхA\7~p)uSO匍S1-凨:Z7e3ֵ%tFwe`.4l9v/+efyJGzD9X&2iheS˯,p Rr4Mba*[KAr-'jRx1ü6Ɔǔ;5v]>xSAD/YZN+ 9jW[`<ކDH:ӫO+rN @؊vƟ!j}&6<2BuyXh]2-.hx %a@7~hN 9]S굪`SGNAaSn҇UQz& ~?xX-= N{-$MQlS<ӲJ/n0oæf Ի/uF?.RT^3]~87y+@gVЦ:p+ \Ҷ0Ʋ>k HݤƔ d{0S"R7#SQxvP=3zJs{k^PU-4OA ,S؆^*&T^_401fdjw0}Z砭r(?՞ԓZ~?ׇ(N9{yl/}||%.‹./>t%v饗^ve[[[_~W\mW^yUW]uW_s5^{l뮿no)n[n[on#vwywuѣG{{;v?~lW>C'Nx8yG}{ǟx w ؎ T1 Z@X {& 0 < `=w2PY߿ӟ83_4fbH^pR-$p]!^ DZL&\E X mu9e}G 2 `Y\u)Q0T lTIEtXZMe\flSV]Н^-C H7O|B^}^QNZ>rySlX]Qƈ_qM~ ﷺRL^E) /J 4kn܄``& ]yFlle$Mt/P\^pۨȩq=.FXxz).AbpyG. Nucҿs{hOV?J-CU!КW]HТ;(l Xrp@Pu0/ L%SJarryڳK \"膊Oc +`}eXucM{p)䎋M둫H* 1 BTR}r*1җ%J{-0n| F_W?XGvz9KyZp\/m!|CGzo>K~ꉔe(b6ku9,Ы΍"V'z@h,z|+xPTɈ֗6yqrtpw Xn^5-ԏ <;~ XɊ!hwɇ:Ä@̡7\Kpkp 25x e0}2q< nJOTphjmݵ{J>p Y_v(OҗtݏΥ<*#mI+&c]>Lc7J馛o3&NVw8J]T.8O7xkXފ}+$d V4_H!Z$1LO݈i+0`A?׼#2ygwco|=Ccp>}ƌ~O޳urO} p D)ZO*2m 8W,W m s@P BK}kzw6,LN2fח_%ysOmE:ic2H3`=~|e3T=< V$X6Sz9bvwM/vDB?'zN]5AѪQrp oOt==Fu '*?M'13q{D+kSi9"O}Sy+Ȣ_ sϕc< _61 E]t:th3ܮJꫯkYo1r˭zml@y]wuQ1l^1 }ǎpe<]DU'J' nhpם`?ELDt&M Rܝ`{ρ^_SDS'D8xOzH`l# IVzDzffPz-z*P &͠'~Df(t[ U<Z$ LKI9@j' {LM V7e~J jW oK={vӔZ59py&NnU1~#ӛ{dc&9*Z'VPlW B*Ͻ R~3z xD!$rkgoUj**i3LJ4)1Ε$O2y [uM].XDoH~:]u ‹ S.!t=x8`[3W/6ğ.Q#*V z=p121v ga6 dnzҧ: bt.O؆im<'6E'>` ^9܋*%KPl:ZWY{nIXc?aˇ^fdMx}MXT@[X[]xf7dɝ KKt t.7&[K%]H، 6c;4M ],Hp8 :UVj5_H;CM_ԙ=:tj%4 h^NoITԊVd4sNFSTc Z0LR$l l d CfPUg_E؛o$",MAb8!REڿ!0G WTJpzΩ4p=`Hi+^b՞j\mB#TZ r}*5ޛ U ukG >;NΩZj֪c}km{sNҙWKiIZ9Hgfw ?c'Jmҧ3Î`pk9Z_up۪v ܮs*m`~`ZV1I2/Ӄ^*VXcVetmy]ĵ4T;2bFqkj|g l$v( P9keeu;nnJh֑`^ ١[-t"&l24KVs?cn=LQVU{]i3/%0\MkVѵ"p 8i :u\5nrr#}5((1֑ޢLְO>sE 'e35jd[U_9*SR>qfqpM 2i9t"]E8eV8E0I.ļpE;6RWB..Vu`VA+9pԖ(TK 8~(`EVTړKu׾; Om8uDALzJa3r|3Y;%Cw4GK,*RYH'5=Gl({ܡOj~H$p#D{xVY& LƓ\!!pEtBړ/[qD{ f%#B:rZRΜZPkv f>:y]2uݺ`\mW9l\0[?WV,Ǭ)f8vd?ZӴ:㲗~|/hJl\RAT A f*Mi`MZ^N\, a{:8T[c ][R`fШXOIil{ lM$y pQ`>BZۙTd}pz;[x4 t"zꝽeh΢0K ¡@ duTȧ677 #5V٪_ieSNPM?#@BoQJq $Iuy>8?+֫7^2^>jZ-n2rB9ߖ_=UZxu@I FP ,YY0  5@b]N!k95ơ=NI9 ɷ3ɝ[#q=]-f6ki-DN MULL%wr \ԅYII l+S.* HC9Bmsߦ^J]蹼IJW`E@xs.7p%uFۡ`/9 Д)kj,I=s&:Z{0}d߯` ?8mO XSPxGp:>RuDqo*"!|} 疓>umSIbƭ5'.~qoizVI:Xqv7 ]"'hg_6oxͽdj3dz FUgk#Ez˪רٷvXcW,ZͯaJ{;z11 OEY-cu9\R8r&v_oYwSG3G.#JZy]b[>xN^O_Sˏ}%MUl5jYW]i1ua$w4uP1Y3MP|<%W {Mnu =vSi&]zylFXt sO\鹚[̮ٟe tz\ɗ_t\ptq{_Jpؠ/(G{S"nnɈvO]=TNi;;2F/Yk]U@ΕZ[R~0Z+ӹĦR_ܟ-nL( L\7XBdmzdkykn9-BӋ)aXGe_2A)U9:sqCW;}黍aZ-kkɖBCvl:v39=4w@qC36U5圠T;Sv14mʧocaۄJf ./Z++jN(!QpnW9nlvHu+]Me(& Vmi; /G5*}A]%616ʼP.ę6o^{BE9@IX!9`_i թv_4(UW6-S ky3$6+ >^,ث N/ȓf}/&wWM1%Q5%Und(`l`!pqP>C`U J+omiˠ=$ͺO Չ:FƁ^bIJ?uNV!7^F9YB$(>=ʊ^[uJ](t{Mid+7^ȥ+xv(+l}9tdW!j3s4*&AϳY֠[_^DS=hL֎b`C^\jgi.@NW ԭ==U7%|NIH9hC+umW)P7TA`IDATd^Q!zpn־_iM'L("v$P$Xz2e,[&B@۝iB5}PP #.~%g<퓴ުsX4d8^TK{N#8*RKv 8ǂRP`lƥS{.ًgU=5(͆Ӆ?T:7kkf|8N+=mZ nD>{%mRo*+ c#{3J#O\ƣx_Z֭[G.hO8>%, )ƵFl(WT#W)./+˺#\K0. (0*#$(xT.5A_zT09BeA5բN\[f<PcѿJWiz LxI.%;.^@ۡP ˕lV$s0nqH`|[rxrmH ]pm:ME(0. TKr߂oE>a{u<Z[xd 4Dh4T!`A'j=zpv Y[ݳ{5VX7˞Ҭnd[Vm:uJP3Dm`̡Zv h(kᕍͽ,ɐdO2y^TQN"eKtN F܎+n SWGz(ZTbx=a3-ޭ]12i ʃo.J NiUC :w\G*vy((IQèp8ԦUJe3U'UTqR/Ȩ6G+"p6,>{V`x3+i#WR^וZ\Iꢵ[uh5qwk 5SJWm=LMj8m7pV" V @o{׈MeBqEX. 8%#PaORr-@[@>B%梕k= Z|Mp+}E&uԝ #[ ǻM|&SX9Kc+PQ Q$Ls% 飓`L\1֚K3\kxӡ@+ D|}=1+فm T<\v5[3Wen>ɯWxr np)@/"oV@ +`9h<SE)-ֆnD,CVϣ19hl/O=ֆB< A(0amB( LXe0; 樄4P ٣E]P @ s L(u^((pҐ @(SʼnT*+) @N lB= Q2hڋI]߻'c>#m $ 0؀̺?"lP F;Q؀e*%B(Эufskk!<#@ g~GP @Lphҡo6qlN%2Rl- WWg5ÇCc lthy ~!tBnϣ-ZͩDC@壇3p!p8B"O(@F2p K. ]Q\E Ưx]ml#2z`2[?jڟ@znl-8VW 0 Gv^in\3U~O`!VlzT!P E` c$8#ܢFJ5C(0f8E Ưx]mxgsѐ(v. Wu*=}*?8B(0m@(Xu(Crx.㓧HJ+7%A-P@?p5J+ݔJc z(J{}7vS|§?lu{5w7|/˃yvS'۲p$22%HQՏ]Co+2M^ZJXx'N0 LD Vpf/nZ,e_37N2H\mp1{[=PE &@ցGDʺ42j:ʬTDUi*`ԡeRie: n`P h@"C"}M%Ω!Z2A)=L(0qlQ٤CU^7:kLr`ٟ 5B'?0 0l!1Aj,2kZupl: Ǵ牐̄P<zP` gMx6  nmKV44Ne1D[,].,^Ub$Ie"1TĩC(GW[XNm+'yYZZ %U/ޢ8樄4P̄WqţZlpE H|alA$(PeLA&g>`&dbV^p>A^(ЩṠQy(]wB$>bB'BH`14$4㇋1ܞ/d+ g[+gSweE%dgo$89^lp:^K(v( C tJ( ^F-!mыoL(]u]uWW\/# {rD">cUҰ3;02 ޅ Z_ kd2뫨E7d^ LH dc:q@K@-;.,ޟ{n؛oz኉ּT 5nnjw_-vS4j_υ22}igbP3S}M j³j dowIњ9۪ 5Fp "`̯\N-8٫[`3fƮh? HFL '.w$շ6rqc< _ɋ`y 61بfb_Yܯ} ˍ3HDjT4l30y;QB1.$Hwկon٫`_B k֘rP6WX%]v/؉?}@P#f+ VQDUA,YސYزGq1sP ;uY]+'W4PhNךox 1 >Wp/D`~ y$"XrZU! Cyz#5㨡j)ݠn]Fs%Uޚכf/Bx(lF:(o/]00u]ݩ0Xp>FX~b8 ;|O [@L I3v񳛳"i v$"`oW9*{R ͐1h Fz[.45fMUٺk4VG凃%WkLjΚ#B0(x?(Ǭǂ6|ذAAKA &Odc:q@KՉޮ`{y,PNuH2yL^5P;bز;?HWިy)S))4"oWSݗM#1#CЊAwD~A9a} sMꃫoJ5G\@b,Пv}I[Vx4m4854肆3ԟ^k/ P AGnHg!k.A2bLI@[S`ZRU^4"T4e4*kvM_L73ԎjWVW^mMiA!4?l<J31ӣ1E͒ [n Qt!wM4Nx̓V Zn@$,M`6UƱ&XVpN`E`l0E[GGYۉHNFM 'hXTq7`&},ф31r1k5A HX 8prr %2FaU 綮&n++ߌwׇcP[DXJK$PO8h#E#@#k͐+ݘ7ػB˥陞ӗn{ߕn ,vAn^䌷T8O0B8 e H8!@KYP+OQJ6ՌI~F'A^LQk~V&CE$1 (=vBQi-@iBVD}:ʅ=gU/0Q Ӗ>NֽEzy8 O_s:B >ҭCy" pܬA&lc/؉?}@Q_Q[ϲHln,8rB5؉?}@jV]c6FyֱOJ{bU$4*}W+j'N6UG"]Ylw2{I0yu1`ITX6)ꑭr`Y[[PZ `pZuTӃU]$bBYeScLjFDX~TOKNfVrCj&K{.幷S)о\:`C |\KrlfeXSG0c9Y=؉?}@aTG?g8,5 $'ť ;g tLpл)1ԄB30 Z Fu}zX`{X^ X'3ƣ|&9Ȃh@@  v 0!bZ{L0c!Wd s` d_M\-+H>[8T: ; AA5lD-A@ 우H񳛳"R@$^b If=蠋`r] $BV 队!/T'ipJv$9D"**gM9Mԏ˲蚟c8@@@ pr ^ Is(ّ  ω0b'{ pجr"$@ 8؉?}@pr 9HPG ׶k4mZïFM!jA@@ pҌ'͡dG}E97w756G40 ؉?}@jӊZNUV(7@Ú؋!@Kͯ{?IEx;!EEB-@$1 z&Ȋ'e@$,٧opp!F"Q)tթ8OvbOa7좃ED}\: _,KL^FC;o/`pa:좃ED`ӷAr SxOΌJCg`qCp&דב-A@ ` `,B@$N1>LXRe:V ͘ը@tfT;"G Sqa:좃EPJy {XS Xب"QGiE2<\M~B) pN귵-.P8#>"99ZpJ:!ı$0g>jC"qD$fL DndlUɧ%!lݸW"q?pv"bJ 15j93fL+l.jC pnj[JK>;>g"@Kc4j읞VhOVmzJ b}COuVtְA mT򯏡Hl@u_\נa)$0%ޡ.lzja,aUYluvfT`($g>j` !B L 0UezgAKB1%5+D'B@A=YB)`woj lVנԻfkzTn{v55-8C zmsMk\:+ $gFp!Acj40+ U%<^/Pe^9Ϛz.˗g{u%ArJŀ*A 8C6(W;-[D&Z(X9D͹W!ǫNa/ 5g>j` !B L jfjzͣ>l \k\piA%\Ɋ* sqEk0l!oadmWWr}$??"7C 8Q,h. `NR?3[b[㸛g l $ 2u[D"sM 3ڒټ C~JEPNa `lHיF`g8 l!{Ҹ*U X9E!@ 8ؒ9PI@eL#3DN5<4c]@0ANa `FbHdj 54૬>+l1@${.d" lXF bj# Pe7DZu k- X%[d~P_E [<XPMDM RHym)p#Sn/Zb1zT -f*81T5gHV'OSq h.&5Qn+_NXPMDMvp\,kj1cHFW ~S=5gL:'#S.f>s,J!.B-Gp, V. z$:Cʸ j4mZü\Ym&7gEw|{g-Xq :0`L)_I(CmJL8'3 u6Y f*؜+M:BJcEgZ18/BCo*<ĸ8U?%zTl"`VCgM*kD_D p37HXv–O269@`˸͛zۺ/a\-#hT85W2=3uw QްA jڳ]c(5P=%Ev~]` l3fe D"lMw+mFوVoUuʹ#ӏyy3<᫙ӌ CxC{~$*݄?")Z60+MLMIqH- jhɫ%O'pt J:(95K{T7.7[,Y_ou]R;.|}:Lv6}A ,8e!@ ZMA3p-v_fX{ePj%T[B] lРlJEdE<=ʩi&ΐ ʩi#?X @ D"[)B@ `Z C& :Dh#`5lZe_ JZS䁕a pr {"`? ' P1 69@l`M:T58p~gf,-KS gYKìGO1oRuTP H^,i^f#V(&d+`%Z6*EYiҜ`"XicZFɞDz?k2„TҮY[J$X/r'EY˿:zpr {"`*X P 69@L Żh֣үPe}31.l!.3q 9Jap/Hk+]Kv8l"?Ted-D[D"kqnllʈ:g@Qp@ñ&\!- A#p/X5##qokբZ lqG-v4J8@]k- 1^#1Kp Xu0 8k*hz4hQ'RMA3p-8Fi8F`in!{2S_)ɖj}q7}?(<祿)+!TQ_\-&: z8x{Gɷ{k!`8Fi8F`in!'=J~L+KRd~Qp# ,g`ÎTu⬸xBH8MA3p- maZj99Rۖ3Q}V8 d"'ܰ3}0Xe K+i)ͦب#X2<|`[Ɗ4P'pr4;I r{)-OJ}X(~{؛Vb{!DIbo!tB\_Q[ n9RYA I9tvN)* NfCвM}Ċb4@f,'͡dG-0k.`%E6rQ[Xm0}$`{ CSK^i'89ĉHp$#Rƀf*\}r >ˋ_I97 v^҅zfrmЪTJ&`[ F b^Jkq'`˹ȼ0"$4'q^lÁH&`[Ɗ4@  'zOpݾ{sxү8}#I5feg BNc/K8i2wgIq!HH-cE T'8!TtpRY@@C؋@+(lXF $8D 8I{I-A@p.}A _CM X~ ; @N@k$J4&;K_ ėP $8_@   N~ @%;<Ԥ;@E0C $8FD@a_@@| 5@b@5L 8IN(@p.}A _CM X~ ; @N@k$J4&;K_ ėP $8_@   N~ @%;<Ԥ;@E@-+ @H>x#5%@p|w@  #L X|& @ ENБ(aApP_@@, 0@@c-K $8AG@apB}=  @PW"X5@b@mA@_!J<pbD&@% / $./ZZ @ A)Љ;$%2@@K0 @~ؙQ%@pZ @ h)." g =M  E@o3Jz ]_@@ ۯU]0<%D@# @@(؍$i | @%``Lvp!Ďokz @:cEc7e@.#[ҥ@H eCU… `/ @]"roޑ$ApGAa@ :ˆ @^뾹  $i  @@Z8 @y; @P̏DУ. @ CE @ `lu!@ @5`* @ +`+ @ @!P( @ X![G]@ @p 5@!@ @  =B @k 9T @ @V VQ @\Cv͡"P@ @BB @ kB @zԅ @ @]s @@У. @ CE @ `lu!@ @5`* @ +`+ @ @!P( @ X![G]@ @p 5@!@ @  =B @k 9T @ @V VQ @\Cv͡"P@ @BB @ kB @zԅ @ @]s @@У. @ CE @ `lu!@ @5`* @ +`+ @ @!P( @ X![G]@ @p 5@!@ @  =B @k 9T @ @V VQ @\Cv͡"P@ @BB @ kB @zԅ @ @]s @@У. @ CE @ `lu!@ @5`* @ +`+ @ @!P( @ X![G]@ @p 5@!@ @  =B @k 9T @ @V VQ @\Cv͡"P@ @BB @ kB @zԅ @ @]s @HY[\ @ Ry@ @*2$!@ @B@+aN  @ Zj݇(C`/?DJ#pf_㸳 @ F:v(Fer5TdʿfPlA]5Fy/܈"KY@ 4;^@ :p|VItG4"W.kgvP4@LG}H osUG讫V4~|7[/ Sp>9rH]]?k0g.mf3D<_ebWNX.Y)=7db;WѝԂ @ `/[b\3Aet@.5/ğbLfdŠ,:|Ήf~uځWro_]Lz&f )#7~-b&u %iEw7u g-~s6?z޶Y􍳦[bkV.\dV9mG'_=KtdچCl@75T<|Ɲċw=lIdiٓn|ux7Z.<$T.ҩLkS:浵C  pzn뮻kR%]VI<*WP<*U/+_t.%%^ò9n# ;x:M1kQ,{WO3By3G&l95{붏_{}VSb&~!FY;vh;,颢J";ozq+{u]"miE#^?j> wt?ڸ7;O6pet[1 Q4oXܿd5]Bl1f[s҂#_קھ6o4!wRe w(1qQ8:M.5XYȇK}}|Y{rV 1fѢM^4og~-c^/%`OW @0#l \tnzsEW_!F -YVe( 'Yy/ f.q/~pA5Ow?<g믿k׮7ۈtQ_̚5k-B˚m * G.`؎l/0"g9r‰K,[i;vܽd{ K7O,\V `mȨw5ݞ*#.{Xa>3\YeT?{i0j޹3U~YWڊ#1R~"=OwN9!eyg?q޶E-cs#}Xֹx13ogzɁ nmrNukM3Fy,S?H3{鬃s;,9#'m8bӽfbdU~;ݨzqk{KR<=TZwgld͌g'88Gʾ9T:BEE7j&l V\+7kTxy1Sq`Q ;AѲsV^UP:~˩361rtxѸ-?=}YҀ|9{&9=v>P騰jЅ7hJ-f>+ۼr[6{-)Z+;^ -/*2rID'eWƬv_0'탂<զkDO~0N2.ߍH7ylb63Fg-2lǤYP<|Ζw&T&\hM?zBioeMӧJN?]!Ñ?u' ?wرcO$Gu\d^h3?I)tUm)kwC]Q{]%- ~nN7uo-J99obܯ&+O}z- Wek=OGwq̕o9xrbOSeE>NWjA RUGsYkkޓ`vӺe))Q麔*_~eΨrW5j7cb+!,, pn#xK0n^Fu6S /KK{ԭwދ2|TXvV*Xӆgδ~+/2)h%|iT7lgʔ)"+4X3oX k|䉎Mn]&~'d8RIz଴L ZguW4o %ݞK͘\۹ϴDƂ2<3}yV6>uUF3ud؜37LX[jCɆU'u/yw꺆_,zkG2 y=Y_db?UԬU{ IS5U|6+ZêSJ=8J=Cݙyd{[?=Tyﺌ䋺?u|xX~,U\?L)廣IBwtq|U7t-o~dqYM}\XY`1ùK6?^sJ{sv8Ǒ:՟>T1X#p7 @ ) hDWd?Or_u_m4p3wZ?+7=֬yG{q{u3rMyEѣd1#/m=p?`1c}iޅ s9Țݦ^S8_cfi]Ʌ^خpB~{ޜ✉Owo*sM"G֭뚖:agS4ݦtΎc7,~Ք+'N^'{JFd7Ƿ"tJZ?z\mWyXs 'pȣƜ?/ wdgtۿn]_{3;?W,_#.^#Zsޮ3g_H+ oմ 5̩>3, ext%Nw l6籙 ʖv?6n=ЩONKjA d4+fA,ꤓl꧙)ѳnoz=/~7z Mo=oS-F/_tS}/?ɔrJTف{Ua3/XczyVoݢSg}\@νƬkVt_Di#7pllW-X;wdL,98p6> :r/0twW[O7iS+w5_q&BjڱWkEׯ^*vy7YzI1;MAXIj{R)К$:i k3f-hFujw;/k0x+_o/[V~~4{9v+>Ƿڽz@ɂ=lxN=Q[Yo*ܻwISKw|xx~MZ%/O_laQsb^ ^^['^K,&Z]uzYZ?b=W7W]eJ\^SRf8-n67o}u߾^NɗZRߗ=.$뫢AN#Z|MJ9>?~g::H##7*)w/[4#"}{wg/ -_D$~R2KϚ5KLVGnݪS8k+&l2bJI!JZXIkߑצwWrmp]wg7_i:xˌx ?vVY^J,_Sxpt% @H21WyǕOzc˯>?UJÏ?./[\ghIjK/ԌuS}~)нz&2?HM٪U/Qxf^zgŵ˯4.nOx >[z(kqE9{"-e-)t޶ x"9o7_}_>TeƌQşbx˼1;v}94_LN[~VoWokZt[k(}iN9W'^wW 'soL/k6ㅁ ϛrӛ5~Y_+4 ߄Xܿ V@:W$ؓO֬fo]#Vnx~XSsKųeN<+-+lqS&H`)E>Q#E^3 oE4s6P\K d{}G^y>D*Bw:O+gƐG_fQj*&ܔw^*Ulb VC;eM4~f5i5}v {g\vKpqo*+v窛^r'\[P,'G*^Ǿ;ulЀ gnاr$a)<X_6Hݺv}6Ku7};_OOCضK)vuejeJg;ˇ|ӿ|d|;aàE2fӼ K,ҿc W&aTn4ud%量 ҶQI}GnuAR jd {һ%$ǚ2!zYK}-_{vGׯݟ| JXj^S̹^yk_Su /\]:{fwyѼqؼ%𪕋zOWwHg .[$f>{}hs]I{h  H &Z='?WSjF/o+^$GvTQ~xK诇䵇}~1]vhi^>h5uƒ-8q~0lm |x,Vڽ;`ݻM.,^G! GjݫGw$۩-=?_ԍD-_5|`xޟ7|LeҖMz^e^_vbʓCO앧8bWU_z驻 X IDATxwxeߔrCB J]DP:HX׶um]ˮ( 6@Q$Sg$! }] LyyIyҵW˲,LIJLL"9>!B!awV>*(*iXi %)ɠ^`(na*mtaQJQqU/^{CRaQqWvCPF +,'aQKy*S9B*Q”*wUqˬߩYߗʨ"rV8o)_Q!eT2rjߣ-.2øʳJeԿJk/#]ߠ~bUz^meT^p؟J +nZ:n^F5n楩'Z˨fiy|vg僥z( 0iI\\, !B!feP-ݲLZƹjG B!BRll 2PMSnB!hNL@O$&&^o," eX(5l (e횕*Dz@QPPP4EQPUrB!8D7-0  ðo5MA4l^mD˲Bea`&e޴ͦUeQihj()d@!B4Wz]w, Ӱ>HxF2ž"[ Y+wt:u2(} iӆTZKnnٵk7[naГ!(/'󓚚JFF{b∊|6mΝ;q`i#*0RbDF$hX(&K!⸠ ߊ oހv't#5ҁe`X("w/\CfQ?f1lPc1 01 i&=99y|`@v@ 8-0ͪٿ?Ete/0P** g] {>2xbaw8pG2()جj3#+6?|FBJU-j): w]:_ffʖ\`p(Ӈ7n rW>ucqm~vWɏc<s=/82􁧹s'MOs5nC*& &B!D A.P#}^g)t`}~͍'@3Ma.:F$%5ǃab0˟94?t3h&)ٽA 557TNٮF8o]H @QBjʞ{6CwSN7fImgۡ]:{l`ǩB-^KAL`ʷDFذ)7-g:N% ŗߞaqߌ27u GSs$4&M< _X򛸺5@O|O[XScFv8eaQ3 Is'O_ցϥǜƾNڡNցH)Ifmfu羏' THj0Ln*ݤ`O DaRjң8~Oym~3 n܅o (;Jkm|G~:RK!B?@i+P~`e)*y'Wr=f,94OM+ (,Yc>:fi3F?S|*= oiʤ`R%~t1{|BjӒSOJ`ގd,2+\P z]?ٳ6^ցN螤lwŽQn"4BUMKCUBoOM堃Y&[Ω|yuhN'I1Ko+fRYD)AҬ B[l8fKaD"~?@<o-'g0|J0Arj2i$BQ'a7 BPU ^i&z 0s itwt)<5g ts UȇZ:jƲLTUU=0O,OQT K u$/(-BUUBOҿ]p0hPTTP},<}7j*KSyIi;$͡Co,v1C?5|r6bR:%)Wdص;̢|RƜF@pk2U՜SUXg٬Eכ @]wDh>*Ϳ 1gCst@v~{ޢ%neY'8zqť^Oq nB!D54l,.["Xz"\<3]i܂, v-]ٿ? ۅa(hzH",dffb* -KT9L*/Gmulth~/Dt4ҙEUc ]8q˚fî Q<%%%ٳMSq8u6M χa&r?js.Lf6Kj;HF;N6N痗ɟLɶ"<D&ʦD8lr~UQB~vEdd$QnN'bY&^bp؝6(k* v@ώ;%笠;vam@,L5v+XxiY1][!B!2h`K+8nQU` @ 4 LBUTUfa٪}^vSlY<`# (m&MǮPT*1Z^e ́B# k6L,͸!FEk:N@B!B4`peE14Usnxʟ PX _yb?1M ]a,Ä?P:!;DVZ*/B!ѤM* 'P}ᣖܗ !B, ńB!Ǎz7 B!BiB!ؤ|żC!Bq;B!By!B I" B!D3$B!͐^݊Yg8B!`qU.68qb#B!h|ӦMv4 B!D@!BfH!B!!IچٱG#uv:aI" B!D3$B!͐$B!B4C!B I" B!D3$B!͐h%[, |y.mk!%gWy|te'l~eޚ~'}'#Z o}=bfv-<|b&zl Mւ%D_ϜZ n15:3k`sWQZuuVS^D Xȧ[Ȏ?g:V5n17u&p8kֻKXU㙭p`QGvEt_@p3gL+AlZo]}s*?ƒ׶1Yyقc'ɸ9lw*JPoxa"zMqPkUh ~'=Sr&Jz?Ss3[OMbܝ24!t{:I_gu-?CSq\x$ =cLU*|IoO~W˙Xa?x}_?'2YWgo˳䠤^akٖ"קԽ|6{;qg<_:?y}ZcLJ}sڙy ;FPƖom冗^Aiu'w%b&ȹprgc,;kI"Srl;k$o~-&cjuV7¤1=HqWyOzq2g$5Ay jiT3dh>= po[h8Qwd_ϟ]O,B*5~A&sၷrrN0YSY*c: Q{O=ލ?09nH\.>lkuf>+fH;RjIg6^}{w|<g t͎b@n33ӛCuWO1lFȨ9]$vu&֐I4B"]<}pQym">wW-DUl/ydCd(lK1yl0ҢHOOK6ݍwdmB1I}k?b?Tg gY1I}Twm:&Ѧc7DMgqƷ{ =:ڬ!z,V{)_T,ཛӚ")1{e͹*>#I5]Ϗ-b龉}vHL0Y0͆y0DG #aeV19í3Ƙ9NL4x9BZ+qv3s?! < 1NKdKFH /Yo\V+ncqֲJ ]ѳ'={gDwfI.W;~lD־ X|kn' Hh~5|_g5$uV%EA `T5 =+Iiusx쿧nch $O\ߍz,cNRgV1lS4bLQq>vFV$>=n5OqZgi5|{,qJ&4NrQ2jۛRF+~u\ IDAT9k\kL}7.gH.{ NW1Zd8 }=l=Cn91ZkY1U3єA[_|1ffĉU"5ӦM"9b:_ȸs|{Y9.߬Xμ Ny64x/3Ha̟ΦC4DL"3>ʌmК 5Yf1Ul-}O缳O&ծٲ6\-}ߟF-kJ!BTK2x yGK-Z:>. {FmunDZ%WY/NGsa|lD@wF.7rnaJ?%^\O]flKZ)eIw*~NO+*FaN=#ތh=Ӱre|z܍!gǨѡ$K1)\;O~Nn.ӘTY4kV3uK&>+@PgKE>${Φ]zIB!$Q/%맾do48AsHb\|Gsgds૸j@<* 8@pϷ=}q/㦮XY(les=2;|tm[˷h9X$vL._VUg4N.tjH`|5wïIdٹt[DF|?3+Rz*zd)))mlwZ,"i]\:6^aQe&yW)Yϖ]e4W$B!Qk"PTXYvVS ˧_`sf>>͉o2MӇ32c53_zm}1dȩnsiLyۦT:0Gı`sTM11MނN#v39DVF ߓ~`pvdobO0H[z]`ㅥ/osTl(]m䐽Wϼsemyb o ػ{aF/*4Ȩh\MB!Q}rǴL[rGRPmAM @|Xoꀉ\>)(XYK0gKa}ݏ|׼|u7qNz^|=cZUVqFUuņA߁uؾ9 5^gu o}X tZ[Gzp)-坍n=:<)QǓޞ}L~+Vdh\eWlf6g ʔM  :F&B!WzMÇJJj+@!cA47I Y-)0u+È|uڑ-al;cSzr[ujNz|:I6!~pӠpUlDŐ8jEtMP瀠Y*}T}2'SR\xEE!!1}{ ]24Bqǎ:UB-d2fPyl ;ܬ Kh4zRܵDuct^:c6͓''=j' k%ңe5ÿ_m:8E?XFVWWg H:GKFbY1q25 #xT5-BPc˅[NQQqF`׼bRhyW^u`B1-m$WSxcIͻݝ@ҡ;hzw'9:MġWYi~ЈLY"ӹ7OMj?Z۸+^<k lEsnZ47Y`oA>%pbf,I3F∻xrKM_yo2)6gzōVx-"qa ژlv;`.Bq4 nÏn.qf/?8",&>zU6r;w"3 7w6a~B!DS*,ȯr !=.q|֩<=8\v}7ޜ58Y!B`gp-EÆ#2Qc;3at9B4EiϡevBq<Ϡ+M%uKi`$I97:J0.¡(*6F p+ PFCR娊y_}Io۾CBWtL,iN *:~YI'"B!iZYEaaq !$& _z !G@qlp1@4!MA!B!!4Ç6O^~}lu5d_Z^`hi#ˌHn!BX4]& t5PNwe7Sٮ?N¡#3NehF:İy|.Ccq[$7y誎83}031 o1j踔M,\g4W֮ۗ|~8/z)svtB!Gia2[8srF&ٙslŌ>eewj|?Px| rnjƹv;q)I{~c[0Hg3˶gyjxZco;_۶,<l=BmaS SpPPwuW\W,Ln_>s<Ġ+(ؕY!B#iP~c7Iǟ,#iī*qߙ|~4$@J7Z3ذE!etC*/)پtWO 6Q[v!Yk|ڎ{XTo |A:sU< 'B!Dc4p?-Ocqq׎;.igߖ)OcIp'(}4"<ĜγbBC@ȭ՜'{ēnDUstWZWʇES3Ur`0K{yq #(\<@O@JP :7Ox>vri *bNBq9B4'f˦/z&WnԢ2d ^} 2T4"zY6]dG[_ۻh1;f>w/'y;gF#rĤMǠcfijS֟6=ɓާx;D!c@B!RM}uƸCV"xJ))1rXcbd &M.sNzm*w˄gVL>[FXE%Sd3 0so\ݻhuS}v`Y_-Bnh U?w4϶yYuR4j&{}v&ɽ9ql {l8'QB !8YUW*6@&߹L~Yڧ윧|5ԛz^:> 3B4O! 8q\Mnm-F*^4O%o#B_ح[EEujBUrOGGPcuB!CѡVB PB#~YNN]0Lx"BC4nwֺ5693g 3.!auߢkBڄ(ꤵ:m1$!DC0MS#[ !8ԩeYFCZq\ hdXiBA}2dBHa~>QQGݤ(Gx+ oB4jx=%BUn,!4Mr1-iZ$BǪMbϒB4#Z | B!Um"DGǠ:"Vh.`a!Yز, M@!hL_!4Bq8:udlo˧oL7B!;:#߾<%f[Wb~?6c5=U!B4:%opmU܄5 n)/35N- /IB7ɑBTD$V|ݕF4Wٷ<B4=K9BXj5`&%9`Ý/ ѥlfJE?a\ryVZd3{f1iъ)_@0NcudTܩuDj%i˴z@'^ p&CGfBA2ū_Aa\<`fG|&v/GXɻYE@%FYlw'e\2=,s7q{+ƞNv{t[b lt?/\1 /h8)K 䱌B!0{P11ޝ= f^ZB'1s E~,(^g圝 d6&*描_.}S56]^?sGl1MextaKeϺ˻(hDe +Fk>.5=#Qb\vA;"M/VJ$jyػ~+)c68JؒDgscVƽ\^X૸j@<* 8G٫>s|ޞїqSX,[DK D|_q0BOŅԈ: ?ᫍCS{&)3aW(>1wrdtSD;櫹~mO܅kq7rш h(v,[ƲCμd\p N h=)>=|Wr'wiNt X0w=܍Eڕm)~XMg$eyFDjwjMż#\ɝұ56k緤}5 6m%خ%Zm6T=&oU kٯ}ߟK .vHNN#!B!]5&E}~~N |`w`;1+i%04?-h#HoA]`O9Ȳ>ڧ-dS)1R=,ظ`7"lA:O\||ߨn@!E  hQ$DkVs~X̗e[A r*[G{wG;<*}NM $;KQOuu Ŷ(E *@&5P'0̐B#̹̙!f{{ p=5m^c_8\h @yn.3 Oc#͹W)p{/|4:yUS ?0N~L(?-K 9W[ IDAT3vr_tJ& {n (hhZ_hy5+e r}g^Vt~@wPcCR:Gͼn2BQTt:4;jQv0PC%\r n%f\䧀f./"bس;gBqjLvcM"o`_'p$6]!<(e3 Gל`,)+@@lrx)mPzn\U{o[3(<֫5LtѶ?r0V"x5B!J4 6kB;wut:.][H )zL:Tk! 3OzE\_QT@ jFy>#0L_K$7{rYp,2cui:?B {&vSY9[ᛘ$A/D0!PgD"aJPXt8iα^UELnp?)FZ9~ӇBm;Kۛ u^M!5@[QQtmjv97*[K9T@o&q6-e7FQGSq;J5cVOx;'ŹIt/S?O˧ڰwר CrIz ˖EY3%e3S1ùevƦWqM|zom(1F]֤ZJޫ.Æフ|_`[uJ^N=D|5Đ;zh2t:1M:P 2hTW> ? |t{B!D},xv*RC|܋a"/_Bx)ZKp3p`-@ ItK|Pzg"yY,K{iZyj@eۅz*Y u仡>uvW_rB/,J!'Z9AA>QՔ"@@`)ts Es `MWm˝|!u~UuZTG3 \A`4?fsN`0UQC !+v_1y{b2]`'m3yr&?w^'B^/#12# ΋X|RӇV0̗zmb )ʙ"bagwIHjQckCBiڬ96m^xF%۷IXxxVq/WB\J  y'99G^orrBדԂ(J"MUq8lv6^|RqM>|wA!(87k)|shb"w+vnđ]ez<|"BB}!BE:K nji/"ﵘ-U7pp[7Gپv5S>YkB!D#$@]*XVϟ~ѵts-ÕtB8{XAsn~<ZHᅒ|N B!"{S!jeܹ1g\ ҲTMš.^r*Uzt:t.egGknӀAufVZ&ЌJgr_[7FW0<sݷ7e]f|ʦ8){{_Ɨy3z|9D 5Z_o"i UЃ.[4=ʢEHOO/}zΜ9<0vX5UũfKkor F Iټ 3}GaW&;'#({A2hGBj|po.@ku&votZ:upgxfM,[R%k,&<v_cæcd3G陉֍Oϣ%=+;h4ze\^4i҄I&}UMé)`0h}))pl? C~,v)[G j!TL|lt^毌K 3B!jb&'㒳rx(/O1EҲc':u*~ 5࿹L??OmUvS#hשmڴM6JĬ}|GIeeiiwM-~~Nho 485H ?SjV|C(gA?n ^2A@|'uD^r}WQU hZʽO}B0T;h>-V~8;.t֍c*lKew4. <8(:q{!ph88u+YtNe[P㬾Tr )[u+~XOm<&)89(I&F!Y. !Bk]+1F06˦}Ztv">(8/c4576[)ؼu{iyvp;C;qhu}ףCȄ]{Bd'uRRCK;: ,o*InLv \=FTg2V^^?ܝ9N\~l7w?sqmK.;гM8QT Q` 2BTW(1*Û^~N?+^e]QFv?Vzlڔ—VaJ7͌5B`D r 9=u(zD-E_i!4-cTxFa %2\¥(nwiӧOgڴi5Vl !h`;wf@h?kJ-dZ,|6/v}cָ\d]T/\Fج'Ó柭֭̀;J.f%ÈÉ-:.GyS8TYזhԒ\NQ-2k @ ϬYSZxٌ30iܟU->Y}-9=FVڠ =jdͰXp!EttYAĶ9^+:Qѱ紺ZQM/:3IA/nTH~~~/QVA^ AINC_$AB\ݦGp>{Qw*nj2|>T!@ҌBԌ~x޻P J9V$z @ԇYw.dqBx( QitOyh }!%07O`0823hץ#2* UUٳ{W#h$B\$hBQ|J 7O"!SiZiJPn=۝B!)>ojPU T( F cXGa0K!BQmjn#/7}`-,;5hHZm^o`&"a25]h}Z B7^.SYmӲu[ko Z^^.2eO\=:E{K=>#Z$$&yӐam>B5;rqwZfP6k%8MW>^F%۷IXxxVPysB rҰB\x|;!,^OBR 22(4Mpټz8قTZ\6ۮ@J%tBIVֲҵ -`v{BUUl9-MqeWنG@XN?4ShszBlK: !H:K Bpmd7 ,.s-@KՍ8' Zt)BZdB!DM@. V+owMР:]dDqp9+f鍣HqSvkՔۘN_Oz7"d@!. ԡ/aܹs3w.cǎ=+pj* p^NU&hPN?4}KYe!t0mz7f*3?w=p q@Xw-B!γ^;6/ŤAJ!{}o⾧'k<ƑuyN2atԶX.ND{I=%5Msߙ?U?@.VWćns}[(ueRq4#Gh"K3g *T͊.#]YnB@31tOH-;JۍHjOpMp$V;d;BAqf `'ud,(ʱO21_yS-|Pպ׫dmń^gzL7-k'z= 7'ۀ˚M\@gɋYdͻI/?Nk>:d7;7b-t{ФI&MEjNMGޤla-8NRFXZRl%6.N.T9Hziڴiz4Ǻo! Wm,ZFčsyld'ʈGt`lg>??Om0~+^LYQ_ڷ [70e&2hi(2-mujG O85wsG ЖGIaؔŬ><"i19e@32ws)AaO~i=п??V`ҟ߭#e6fqMbL&D8B!38X "Z,J9A,\rm/ CpiX*Kr`lDP3Ygd'X:a0.!LcAJ6ji`qrUfc% C([R卷=Rqj ˉvX]JVyMDSp*K!D͜] qV  g&s$A}4[xUmcx3wGvnr_7g0I o>oV Gp0_y%~9jԴ'm'X;hj'NJ[7s[AU7penߛ&M̊qInLvC؏]ǖA01}줮\Jj庭wgh͝#o> lmұ=['aˉML_!b$ !ąZC0yd䞹2( T}}`SAK\C߾}{h8(>3ZMa\7wZ͊%Xs>7 -z[*'r]\.~|wQ DGq5m^T|%į*-&RB4,Z,lٸ>^C#٣F1C8z$*)>Xt͞IrL+hoo3 xF_wXBpe7[Gl7oV+]{U ᕼ8!WMWOTꪸjr#9i8%L;n={G7l|V:YNI  !ąztd$~)^5QY>;:>Vfⵑ,VIXʴo+"Ĩ`oFht|=wYgPl{we_3#IXJUwn"K+qʦM)|j6y̨Q#IJL,}݌(FĐ[@lp@SYGi^\/OԢ\'sMl͡fZ~^\)D$j#`/l3T{xhf`zO> ] ,b|3#g2^{w2*W&rilEL[˒rEӮ}7@ly~HL.m,^҃SٯQ;wf@h?J.f%ÈÉE-.Gyޓ?TYזhԊ#08+B!.?YM1gEjQоg3^\s[&|/oU~=!ޣg{2s Fxؾ/|K=ässfX@?::aC`Bn5rAnՁK{tDD`6yXQ+|{_r !m`ݿ Y#ɘϯ%Jr SƤt_"@ T~34LJ_# h4y9X!DMstzuTA+,((/ۇZV&qa:"_Ҏd@!7G銢`0q:ݝ+*}(4@@7pxQQѕ !. 5^@fBTC mlMلa\APXPMЦ]4Mb  $+$+)BBZUg!kL&30 |N xb0u߈BUUU'$lN2k iCBy(^o y fMJwë:IH!BAv Uu@ `4?\/0 4䩿B!Dmziv IDAT6֠F"""iն-K0zw{g4"bd2렷B)hZq0PV}ZS !Du r99Iiݶ=-[߿6֠r*+_CQcS9rJBb' !Ώ`+(8WނBpy8vAԢ6(ҴYs,!lzjp,ؾL렷Bigğ-VW^&u'ytݼ a/ܜ#j7PTT 99gTE7DFQ?m6VU>m6U<ѴGrt~ԥ)Dɵt!}"L%^nࠪ.؜'ǎeWe& ջq1o2O=8fOs\Ә4V#-yiWF"2Dm(_Qӆgo|w. 2S uȗ@@qsaA~Wb ηb\ܽ'Nӧzql_ds||Qfێs MTGJmL9a5s/p>3C5NrrIPͮ#E3d/ AJ4 ka!+-(|Gra\~WA@K.c_jY)F5¾%faJ^m&٤ Mu]1GLz?Kd,q1iΆhi딡B4N!jeܹ1g\ Ҳwz߆Ж`B<߮Ʀ\7i It"D=qfAVTukr_~!ZR4GwWsZ6.c8mćRI|m(M 5i¤I^|5;9:ןge]6 Lz*P DV6Tlc]Ti`#CBTk,Xz1ebtF}4p.npeśɅX$N10.`<ѕJ}檺f1> x∏;(O`.t|љ4 :ѭku]&<̗˶Iݪ4 |CGZ'9(50}t4TWTvBk%Ac$&X MKRN=FX^uU=ܩOAd%M! OXl$jSmSLAwV!D=VmXowӲ+7\,P̑ANz.FX^^?* } op4S&NTcV5P:E:|Eڽ);CPv!!["-7x_T{-1GFf$17j$ŏPoXNv閞16M d~~`|S>]+R~7_bizŇmLJ[?nc潜*S<;_};s+  9yW/w!eB!j_5Ms>6u&r1b:Xؾ>5;R].ZbaHraLcPzs7}v+OX'{1O lO׎{;{qRq愦dNj=~٬m‹]i\kݸu0Aeےs\MyMbj<7ۆ\_яtjz*p>f$H!J+j?bػ+y DFX1'9<{qMA2 W[K?.UUٴi_ VlQ#ILL,}݌(yQ^>xȨJ=ULq\?Tx~+2xχtw\TCƂ:ƌQn0oFrqۙL0~0Lkw,Eכo%dE\fҼal:[hNC% +_Ÿ$HBzjE}.[ňw1>A,1B&Z"=ëoLk.fYn]@K.IJxbBBBfƠg2F۲5O^EFEӡS\.3p~E(,͈Q@v)@BN\,\)'|lyd 9~fZ4F#xKo׷;ɮvy?{lPˁ/$'+ʴ$Ofs BSoKxuYGٟ@(1gϘWSP^2Џba|`!:rAa\J^=yz'"ߨ3ܖ9e.uq@197dGSY]ׄ靨KZ~RҍgsJ~a8uN蒀ї;0\]3N!v {+kJځ3:@g!hjuF#0sPmLgGEE2\ UUEv7Xп^Tʙ0m+omajs>s+ <-Ae~郈 \ z%?]X\\Kd:ܶ/w=3KPUj$O #pnSY#t |텠@&]HϷC3:y,_oggUokPRv!!Z_3jQbOUV+FƦhtXBN;$ѕω| :k+cOrdɚ=>d&Ə/ɚ\u{">oᳪƬB) kwMɂa!ףtEQ08O}xsz'e}NB\(D9Hx:Mh!A]N*UPp:\2 !B!!Ѷ}l#YQXPMЦ]4Mb  $+*Qѱ"$DU q\1 _kpx|ȍ;iy8|ܞ-:mz#i %Mm}W HB\8N 27O`0823hץ#2xW=wzU/,< I2 Gz|S|Qd ݵ<2}3T bGj{7qO7|k` O։n7VrB?^oDBE٧FEӴҵ]𪎪jnwB ӈۭ">z0D'zr/}Pg*l7Z݃ȄB4T 6T%U/(hB/s=t :B8ׅ%B\HJZ@hS4vy9KM%77kaamA3DDDҪm[,`z{wXh4E%uIQp9vwBq~x\NNeeFZmOmo9*//SY'xнeXV|rT0V2! ׁ`t; I-jOBpH(M5¦ѫwBBc23  !jCMݱ?l/"b!y'99G^orr}ڒOדԂ(J"MUq8lv6.sB\j2u|Fܰma, 0t-/ -`v{/]UUfl;7Ϯ }>sHJ!>f/Imw\zɮƎyyj)L1{(Ǧiy L2/DBC:K nչn'_E\k1Znk)\oR}j|nu߿ㆋC4?{h|;|!55c>=rqD{G//RE(1}\,pjV1r`B42X! RoL/vr:޹JOr7/ 4>72#-3@͖06O3eH&g&MH|I{ZiOǤ`=l>f!ؚ6;2TT5}4s#!B_i9O;2{luê1T v/?s޺188o}njt%3_eBhuŭL|f:Z_o"i UЃ.T]ߕkrbt]L+rF}֭+i9zE^9sx`X۱jSfEB׮,7!:5?y AfQqVaXΠ/q{>ُ~{|N2Ai-!?2=w\IsRdos׷ŗ {g.+ߟQ޾+ Ò?q, ks{i2vH;,:@oi`Syhr B4<I?) rL:WeT 6nZwۅxsoZL 芵tTg̚XA?:weˏڱϽ'Y *陉֍Oϣ%=+;h4zP_V`͏q[3?ry>cAmX0&O˚M\]~4i҄I&4=MSS`RR%-8M:cHٲ8:%;9:ןge];2fhO|o&BqdztG+j0<+PANhZaۗUun %(_Lu? cc5.Y:Rnl7/ e (Aj{̠&>>oӲSXS.< +aj̾7C]cpB ӜnЬ];^DMە4ٱ$6MQ 2';tu?!.utfqջ pG![Z#8egDW] yޖZiw rpCrțyl#(nzE?{T\k+~$XNEј6V/f51L[<\1S.ҋ~uywl'~QR܀ĕ#M>J-vU+0^Ll\FQ׏Q!&rOs@~. ᕼ8!WMWW.ҬtO 9 -zcEH[ {uUߥ׎;%hN'h#{ [FkoqIn0Eѡ9D DҐv{,5Mo٧Ҽ?ZSy3'`ӚK&Wv|; 'CH0y*9i/1kPYӧO/-+ ][y=+>BѐUog@GFOq\u峾 me.^y/+bŌxըJ΍]}#=?װ9t/Ez#|>^~N?+^e]QFv੾uI`io~<;zj ,TG_b3WU6mJUlϛfFIRbbs#b8OLH`SYG눎+OԢ\şvBb0zމGރ;^+J+)73QO3 +m+T,({MS%ou3ɯ!jYog ! D_٢gx|mec789cavڏ8Vr?f$U6f/ ALC]T/\Fج'Ó柭@AUk6'v Хx^;#yUc+dZ,|6/u.]:%K !''Ç[.u] ` (8pLɡ-$%-o%8R|ҏdbیHdgi $$TN(lGENuO/, !>39H-<lYz̚ugWV[4 IDAT~[*4f!Uׅ恗{9cn1⒁~tt4Æc…:jgpۺx?M鈊dmo~5w#sCR=塎njZi#"~>&PB\Js=hlTy0BsVۿ[o$=b;4zf@tt$c??rϗ* " n>O]xf,_'噙fZjԒ|ݵԼ]9U-?!8j56Zpzxd2UP}ڛ_qn*.|ӧ !D=SqSXPPg_ZM&BԺs (|WEewBQwZ8mi$Bڰ{zŒڮ خ]@EұiJP{v3dwI#$;gvY7<k銢`0Z--MpH(|C@7pXa !DKj4XMu>^F@p!{a=lꌢB6n=4??s2Oia"$hPK5zB4N0<`0Zdd1!NHh(s6 &&6^FnQVTj!p!( zxb㺴d܊i 9չB+ĊG "T-7DŽyjPYY)Z.U'(hB悂Ft sT!@~BtmS4JeR\TԒyk F#!t??3z];v4zh"$4_?_L&Otr*yCͩx߄8(/Ɇ֒ԽIjɼkydgeۚՌw0piC)..&6.}Tn4vBѲnX,vlKo'd܂?1Y  @I߲LZ!BNSUJJ3 -ą@t\[.7n(4479v6^'6>{vR혪X-TUmtU!EQF 1 S1ONBdFj Kʞ~~f,eeZNA~>7 | DtL, 0.!: PH㹦6. B2!Њ\i(u !XXן31͍Kصcj_!D\!NMy gFd΀B4/i&@qq1}W]uUݽu4TULxӗ2t8_/πACќ}G&)3#/ 3L(;H>FBz몸;> d>B"bfϞ ٳ[k5]LqqQqjoGl 郈;T?ZNQN!D]̽ƻ[ዹ<~?>9;cj\,\_ZA1D7^;1{^,Y,Z :sp\IoiΜlx+FiPc_B4]G QTrt[=9 xgU [(t\\s:G15V>m_0sg$^tLNמn~Br}70&\NJpc#b#kb NRs^7|w>x=#Re*!D_G(uV#UWwEGM$y\x7zLFW$_`Md"hQUn,dGZԶ(hZJiB&6GϾy_U^OB:.Uu!PߞzI4\=G$_O?_,+ޝH_A[v}h]!Sx[,1OF^ysZi&`4ކ@ ^ȫo|ʒײa&֭^N҅5'DC:Dy{E* BѼ9CUFcDze?T/#;P ;R˝~hj}]o4|FΌwz~%f>3f/d4q7gKͦ=5\o,]]~/RPoOS!NEMYvs_(3:t;^q_xw/w9't?_^w23U.UUY~-^LiiF Lzqq5"( d8NpHSEtp>CBmXw\\_ߋnjqBxN&Tj@KH!Dj 'sx)Fy+bJ,X/[钧x,-xKlp?\!зol|eDDDTCPWCh4ԝ7ة" gヤ$p3ޔv+|!h+Mkx$prE=բU;qKq{E?,,I'1g\MJXXXu56bT=buq"!D[rr!2mUQQ!h9-^w-GbzwZ+-7߈g꫸ZVbyB쁷_*2 'E>f'*[ tH5{M&SKַjB26-- A2* -;SE?|hqq1FI 4;ٕ7tF_1 8="( ~99ٶU{473M;Ahh 4w^B EG={z,QwFQa!EhчY'4а Bp8 (Aj4r8="`2y``7_a@c F#CN;PTUemNLLl!d°BBz1ui<M*C 9չQ*eұBjPYY)Z.U'(hB悂<'a01ZtA'!D3j266ںaRZ 7B!jrieegnr).*jɼkv;=h4& N B"c[=(-MtCJvV&ZKR$&u˫%֮瑝okV3v(5z^Nv&ظxWB]=sUUu8X,ؖNOh<Qc3~ 6©3e3YBn͡Wۋ4%>+a4D! }irqC渴^o 6>#QE*eS\TࣤRUB1B!3R[O\ Wb)րPr ٸaGfPkL&cb4dq !*B.i"WJC2'A`a /\dZᮤeȲBhE}qq1g`z뭵Bw][Nlk&[?N튩i5k-{Eċ!ڋS#޶h~0r"͡ qVi^Oz=|*j'Xz7;˪޻qaΛǑ#G*5k ϱft'j]E}lJIL㚕S"'=SKPƟOT8k.o/Bx f{- \o$D_P.%TW}r?Wb97yt[w#HB8 2vt)ḓ}|;7Ï_HTWo,w:qy7#" <0l꿸:834I&zT&v ^0/0놇24eKy⃵\/RfgnoP㼸: AAl\6=/F:!ݣCѣ"E$!DoT@!D(̼|l7^h͔ܙZ-nwWRՓVw=s ,X~IpQ#)_/``4G]d-CLKӳ(={?cz* Doϻ?J͖#0H]B!h!MAj9-[_3gÖZ=K_i.>#c8` WP'N6c9׮p%{xlB}"ZLv%S]Uw~*!ZX{1HB8IyGe"|#yXDBR70aEtr6}^~PLtr6sɗLgL ƾlA!pM[ŻWLg黏\/_XEI;J7ux7~LJ1vnJK<<<6 *kD@Q3_9e_0pr݇Mgps,ja&7Q_ez_B}DхηEpOeoRп"ǝL\(iiiVϮQB3H9.y_2O:j ~~w?\!зo_#lgÔS:TWCh4ԝ7ة gヤ$phi~@HN~7KWg hFRaBe a%F}fo7=wÉ~^ eI|~y7#DdtyP!C$'pɫN]}q\ g<*;^̰F` \vWx玍SHytBA;E?|hqq1F iwi>|LJKK%,,p<<<yW=|[b (\OJWCI݃2͙ Amo3ug.y&Ŷ58[W=)ȁBTq!^o{d4M|(Rm Fm5cg5m@`~f3[HnNMskβ {1V!SUɓ0W_GXs1!8v4^}[l%jsOլۏ$M)e#veѽ{z=!!!DDDaZOG%aB\j[kXRl?ÿ T8U&TSBmXd6SVVZMjβ KHH_~m AHxBTqq}]_VQr%)nmze}Rߥj5+JlY3hNk,S~;)))JxB˥c׳cefpV7SUǧKٴ/Qyջ8ybyXƒ..u9rzk>jPIc30Vj;Zlh#lєեS^n0vZYn4BDhV_*+Iuk\GLBmbCVU^|]e Slb2n_ܦ_ۛqѻwo1 (RbPFF+VbС$$$uE;dq>TzhPuM{Y?z+w=oCFrmC5k./ssGΗӓ1cpgpQþ}(--l6O||<2Eh{~? !s\p8>tFK7VOG}*s?[ǔo/g^f8[  : Wz&E8Ʊg]+gnɽ+S5X7_ܽ'Oq;=ZdC$VSle{nIolpv|q~M>\#:Ùxt'T9Ls?|.Rڏg1gu:J_< O2Pz{=6`P~oȼ?p$P7l<_Çƞ\>`m0)p(/Q7@TTQQQ1#ܜ' 6"B'|T.wNけt '3d^gbr xda\>xl73g25ù48I<=؜}1azPؾkJu5Gs>0~fud$ #TzGxp.? cgyu7dÀWwylm=.ےWbС|{R|ƅ f揢o6crʼnx?ࣧGSʎ9U A??5˂gi8~@7V6R6òPW^yZ8d믯rՅc9}4Mq9JG՟oa}~j+ L&0c̴8L-^}?}?,t=_T#4M'y,=+]?u@!.xH/}ZONW񻆢\59Fq.w߼o?ϭFX;,w ŧ+rySLt6 ݖHbb:U~4qwL?MTF;?Ccmw?˲^v1}/_!쒙3%F%ASQs7~^6EqFˉ\Jt2iҤ̦ps2Xѹ8YX V+c[%1 j㜳#Xl .Z `bPO_ټVHCy4:^_Qgsp;:3_+b%1%Clt:^~9}ϊOzl8nyqEC$v`7FMm[QφbP6gqgqMC0&Ij`ɍOƺ0tf0x;&3]y~ȇg,gMN\Ȍ?죳o$мe#jKذawq}%!!ݻwqFw^k2'+5%Tyi BРpmb@mhBSN Q7xߙ4җrbA (f{, Gʞ}M*YU hJ,-{Q34/Va/+٪ '"~-GNʛ-Qd"--e˖b V\ @pp0ӧOsΑ=D9BrhPe3UHUӪj!iw}gTf'gq=gjUG2(&PvJPJlZZ̑dΗ^c妉$[󕍨dbX,FcAB!:ʸkU4iJ3}Ww&#c,~M]Tp{=+}*L^:FNi :3y46lDt:]BK2BΥ7 ORbVʋA=#OYoJŽSۄXU%';ZI-!D۪C 5U& !:6F<=ظ/zCyyy54tt^ӪskUPE{8>_yz6nXG\\BsR6ͫ9FqjPԶ΄BB\j śq0w9:v.j &˘vv>:9a˷brƄ [9y_p0B'~๧Vuc\hB㯯c,6-Y7kqTO\? =Y*gfdpEu]-&c^"1qxKrO_̵<|7sY9qx`|5E gA t(xz4~V/%Z {k3Vzm|>|%=ZUV?>X 2Vof֏qq'8"5˝0!j!6s<\ j Z/:x77/Kq9o[FG16/[Ģ|d]x#-8!fi!TƀBt$53kn|A/Ƥ3?i;X]FrR4F< ޟn@kbl]+3э^FrrgKvp=v2&0"#C*l姳"/+*F H첧)IN.`Fo@)=yV^a::NypnVΒՙw?SGG`%b9+lc}H)^ݪF:K%|^`ޒכuj,ˤ:onn+Ġ, G,$EIG{p!Dm.F娀ByQVVJD(,G`uFî>459RT>!>`ɧz?aOA+"͏veu!*k>kf&;mñrq9ehxV[3wsG鸞Z7ך#fz'W3-%eSbtΛų8}̷?)V1%,z) Q8|$s&^όwK'ĩ>*1!pG N@TBF? X1m'j)*, ;3ł߿#BToC@٩3`Q^ ?Zy?kYٹ}x,*Uꭰ+zPZTC|΋ ot( Fԅp/lzK\4*p0Eԙ1mv[=܅/ܑ5b5 0Dd\\i {V͕oŃQL> g?̕xEQPR+ $sݴe߱丙a:O=!gzҏ;; ">\ϚߖS!܋q!̲U tU)C}Ў^T|{p^^&3")cVדsƪwxO7AK(`ix)Ӟ;ԟً]hO=sk4=֬M "dHx;qj4 @rs Eiii>L(Bz^^~:W`<B7X)!N>3:C!K"_ȫ&3!"H̠#4o _܇kn3i>o ˀ:{=W}ʷK>fc9"sWbt~͢*+y -]x; 9PACwh]/7.䛟?坥B;1~čz^b[sIk:k8?u޳5|yذ'ȵz|zscr~A(ъ&V$ !M) >wA^>VF={!NC'&5{ Eixnh 5-ԔjA2" pƩ!?//B>O --m3#-DB!Bt@'|2Dr#s~[C!DR1 hZ!hdD@!กcxB#yS]iWr͓gwoaiܕGw_Uqkyl W 6~3s*D;87 U !ܐK#%Jܳ1$pd=INQcEOk{a)$H:?~WӲ}&z̖BHMMnYZa:<ʀ+ysrK-OFIg}9㱗G=g9<_ޛ%# PĄg|oeΡOOl?'& \o$D_P.%4Z1{ aom'߁Wa,qx.KLX 3N&`agy앛ބrR|Iꖄ7@ /m'=Jo#K^O7YB9jn2`gߋ>`Β_a0'2W2>C&ޚ͆/cu*v }Ky'w<=!`=3G^֤F??Za}OFfxȦxB4 BkbKϱTw}g,Y(sLn 2%L^D9d+0QM4Z;<Wa~1y~@Kҷ݇{wEWhE8/fPP>GϼGċߣ /KEʀ 5 \JL|v}c4z[DPxlVB J\uׅh{|*t}QRv2d-\%;wϐVʮϞoJpLzg ۲,Щ]l$#|"wLY-FGǙMW1w=D'ZR}~ ^Έh'ҿg>i.-- 1 pgqPe$ _F'xZ >}s2)_$yGf@.Mrq6<_7v#0ʛn`eQO'7ݵL ^${'4ǚyP A6k62vt}?su"'%nDg=xgHHb7+>ec/ɠӤgdRA||4 Oq5_Z|b2Oˏ9nv#)_/`vW}7JO]ޝHLJ\- V9WR<n8=:CGЌ/MCË`JcS#_̈́_cnz+1TsvO V!g(&7tQ+h=b..WC7ZuCf˳~4F!rt-1 WU595aB p BY:=crwtd/ٗq՝"~?Xrӵ l.oŀJ p CBhTQNx;DQ<7yGǚٻl JWw@|yfϛY † !z֬Èp:GH`¤M%6>(ߪƐtnVg܅Pٶ=W0u!-i9y:'jt>Bc_sch7 u½Da!ij!|ޗci]ybr]ئl* Gwy Dw t2YxZ&ٵ?Ӌ%DX|C5:F^zOVl`<-rsC}yHEX6\tZxMB-϶t<%ˆsA cO"!,/8̺LU 3xgxE̙1fQi"*S yy4aQ ~ н*~46/rΌ<ds-Sc8p{oQ ekk3pRz)h;GJ?W>+yBf_VJMȃWCdJ3Y92~_WIuR-J/ᓗփ!S2,)½2{Y/#`hD řt }JĆSd($]{/_"DϔK·t,~Bz`vxGaü%"+`/<}f.GGy79Q*q6OOʳy<=ܷB!)esjooCqQ3lyvκ7Ji->7/Ĺ!-7TTw}˞#r X,O6-RuoT{5~ffQ]9<_{b|L>; }7UB[(Ж%{(`Eq!{WM zE/?"(N@p޲)eviitԮ}&<'6yϜy IU%MoO3 71cq@@Q PGˀBT y9n_ꋺ+5kYd4 Kn6Y9y=6*S|Yij[kPd?ٲs7X·?x I2v&FMa%)t|+vky}A>E˗ԼBx:ie=@5%1[JTd;^7<5sR k$2jخ_烜AHl gjj{Ek`cηw(K֍m aI+q+-կ/g= !M=iTaJY /LŅ\noO&O]G>@?<מʉ/^u<]Cɱz+)XM8|sѱV^-haZgo!ȧ !Vj8fj\!DÐ:!Dr* ̇* ԖzwvleWN{xQDJBB!R;'h`7|{6'GTu7jK|?oδwlcs)98';=HkM 1Uo Vp||-Mep^{z[;vM^m<1U[DR 5UVBx"`ZvTEhDo1jEU(hBwWyu:"YvzFL'5 f|/ wCiו*R{S^!h=갠4b8|yyyY,͝-d0 SD뫯4PRRL^n%77Ka5 F#$uJPP0z-6zh"2*@L&_t:]ޱXQ\Fz1aUXTsq''iAmPsپ36d6M`fۖM$wNr5q.C^^.Y>"tz}-(vlwv'13p_~/AQ<㲳29~B|TUm܊{i3/{rٍ\`9מZLxrDxZ4\#lCTT[mti14rsrQTT[o\z= 9/9!OMMCQH›)gXAQWqgr[|ٹr)֜,l3@ǿmPdE#(KQ#AAXKJd"!rN^0&$p ?.qTta)ZBD f&7/YgShs}!B4NưQHfa3-Dޜ ?n x a=[/ysXPm?Kul>Zm9MN~;g/ΒR !5 ՚_ϢR{w9w}ۗ%anhKyk<؅E0Yt4W%ٺϽq-".{Y{0}mͧQFK^l6YXL}ܯ+Us- 9=ξK._ͱKap#oS/ϲbM \_)/s֨7:ʻ-C,g^?۝@ll, {ؒQFlt6^C)Gjm8szlGJ'd'I2)~{-Q] \ ;%N$BTN\8k}̚ Xo& 2p99K #Rq[JYggynT"ל^#)\&T tأe͒o(:p ,(-ͪϐޞ{\.uT|"?2r)~huѫoז‚fʉMK !U{OB1zR7FO~2}=Δ799P_O7t$S+}ys3H->ͤ4#Adع4,E(6RjEE͝! HmZ!9U SI B!D+$B !'@@!hd=HZ`aՊ]ʈ3RF___V+jYmܠ( F q_-Wot Ʋc@j RfHK͝!8hFII1yy>t<,ΖG2 өs"Ajӕ|:L~~>Pkb0 SkZ3MDFEN'uBx;g FT6Psپ36d6Me6rٶe]ܵ~~~M KVi7^_m n#''[ӹsg:[J '''?ȰaPϩLb1l !р4 MZa^s6 zjwجV}йɯMCBi׾A!l 6ZݻݻӡCfȩw &66@lB~ܨ #0(6y4aM[!Bfu#bV=IFUV ?rBI4[nER@QiF^^.a>WkՖm[6aF^^M-ݻ4k^'>3%<"2'BA-; xuw&ךɑ;ɱ+ΈnguD;#|h{/S*49GR"Dp'9H !rN^0&$p ?.Jxq!)Z(Qh0klr5{6K>ׇB!DSj ց0 ,tF+7xrqξ/qOVL xs'/1.ï9+3c 䧃BH:zz BIC 9Xͥz?%o B-'osf ۅ4>X)r'o k1>mvuͣ=?||s-Lr Jl'gycod7:M5i=ʒ%K({gF3/Y~Agy+ T2׿½JI]-'uΣG+5sC{_r[.,.ƌEGdkH g^E;C}7m*<5BLRPJ ߾]DR7c_pb_N{FAX|rJogƖ  "jVc+q+=3n4Y1ExVԎ^EF? Hy2Witw_508롏3q37w7G]&Mc$(Ԓ0Y}D\>'2a"}귿Oޟ*_bTiаBx M(AackY6#V9'a$"%*C~L' @(p<ˊ16p=-X',P>c/b8ʖ*Qeu5SFG;C~&ʉ-[ NXXd~Os9aΜ_P< 8rV*`=myH_'I9mseҚVuB+hiB>{ gYv퀑$v kJ\w3橗199K #\jt5KaãW©#'`Ɩwl@؁+̆Ɖ븠,gbHY]egyͣFWSM@Gy @Q1r ":Q$:ȥm4zәhZ.<YBќ{ڍѓ1zR' .!.R7[X\6}=Δ799PZrׅC_Ō8Btp s IDATc {?]БLR|S)8H%:6ޣi-◮P\z:Q!t̛AoY4i&,CUeEƄͥuޯ &h-^Sv3s$> <5..Djeo|FxN`OR=\u|CYqU깿ʆ rJpuIرlѲ|]ʰ8!Dk@a~ۯa+;\wD1%Lbckw1W#mz3Wx5㘝D_٢gx}#.x+hohKUUzE۶mXᇄÄ&::B! h>Aiiie+ BT,F?,͐RXQ-]ZRuTU9v|jWLež[S53f4YYeێ;΢ŋ눎n[oL~(,h޿cGIHL@PpHȳ("pkuE 58 h`hjaǿsTU0LU9V hx]/^ќEk':c1 ˞ePłdB!Z.a4!U E`f6wVFvva5VK)RzOkYfYTKXX~ `48qQQmB!]Ut I]زqVkՁ‚6o.ݰW_URRgv؁*; ٶmIIhJPPp2OB4DK!!Z MKKk!ZL&a4X Ů` VC!d!66_- ~ٽӭ# B݃d !DshQ(tڵ@\|ΎWPU T7Ю];7aμie]1U;B!DchQ8 `v DqQQsgA!Z שD5*4,MEB!B4 l^c ZuOrB!l6*5l q;jE'ARC_VK}]jk!h2APuV!!hLrP)qU3o7(χq/lKl?ՐB$\g 2͐2&h*uf@y֞ >>^̳1ǧs͌d\B`_^G]T/߲r ]Mz[WǢ^1۸wh7xx|`@Niֿ7eXn 7KoɁڮca;9Y pp Y|~Z=a]g [_ϖC|ILA:^6s0EcM5Qa>4=7uB&w?  dؿ_]nY&U!Ds=ʻzBS8: )U?"v$/MKXS=uM`QQx#[wj 8MqQMk{SBjA^d]w`w,M|})rG@OHx84Nmyf2/u\k:|2V|ޘŷs9 s~{k !BTu4+%V+Ŷ|2oū8$rqg*`-o#ל fuI;@Ϯ171v Ks _NIl|G~>\BXС7{֘߾'}ꄉd"gӊXLO_ gǶSغÀ 861>#?:05P|iZ}$q3VZvUW5rkJ_/!h*Ε!B&R@`\un "{sIhkI'vrQm-wqZ^+chZ6)L^6OC^ (\.ɬep@Dv߭SCܙN۲,7gxz DӨ~LB4cILJ"1!5%fc.7dm+lّ}2QF@1oĭ4;Ƞ3?މ't]`5 ޑVlq@\nbv~5=y9 k~1Eb֣ngSCf"RtYP4MTi B4_O?3cۗ#ې+e[cm>)1. n/"^;MNR0kum"ˏ%dhGu9^xЦ%`7č;t(ER۹k_ {Oh ]Q/pd|)9z1}/}H0~`{崋?!-G6{(~}k zߚD{,^:~\56.KqPv+)/6!ە֊oi@˙zS6Y1:қېVp6Nnc#Igsj9wMR D`cηwWF6$>Gf)B5,cE3nBBƢ,`6rteTs58v4z}4`g|JK)V=xY޽Hmd}淳;{A+&iDp-+Ӵ orsݾf " -V<7Q7+B4ʋU5˄?Bt)wkd>FRywkEGXT|OJ:JqL4fmBP"`ZvT3/@sgAx ſ#}b|!vg` zDUNz=/ቧ'UVM@si(YrB4*M()) ?#)ϧҘyLRr7,MOh`ο79y|-}cЦtV>0ٝ^:|lߞPifRSSPfiB48FnNvlsrWtׯ1&D=v9vL2ҏPXX@ttl]G44=Q#f>E»⡴HP)LŎ)jOworY\'êDq 8!hhnTd)dMtNBsU Ѹ~z\eie#k(JiEAUlۼ(BBBx o599!Bx?Eq" G5H4 o .UJ )״5pM){vK i(@A£9\o +hZᏬsőȽ0PRRܐYBQ6hBђ+쮡iKZSG{zc4<Z99[1`X!DCwׅv ׮i֙UǁNy+ !Z7g k!D]I k-Z^wst80B" B@sgMkWBu*֝9[- :k[H!ozO'rl6U*< Ru1d bQ_+ww5|8U=!]Ʊ|B !J ޿R?㈵A&DTKS8@S͎C*lBh@ 1__lLU)3ŋU# saRpL {iS.?r*x+~8MLNK͜Xƙhn)mr9e>038XGZ*uV ˞oݶxS !hz">7O?̀./06b 5g}t/1vf׽4Z rv7|Dߘo3(n=~vz;F:.Olg!4]G\gRqm4ݻѷoON v /ALL4ct:]ٔABSE(5 E15lʠa!D}4܂b?oy:me,Trt#lѹˠXm$#"SvJ26sfcS3,ѽh5o"NH̹jOZcܸqp,"8H||ԑ}/?iKAPRСa/ y]|\S&$`΅ih}+Շi>>>\{}bfhjp(hV.2&ͭ'kBG .呩8p6"dC! l(nة71mA=b:vc#}CA4U-Qc* e.%//[n Rhs;[4,, !=z:~u=/YG:IWr;YY/fޑL|BhԅKh:&1o d'0I@jӇJ]xξЧO;v$,4REkESA9AB\QZ8+ d5κM^Y}5\n}An^G%SglgKX`}F˰=+Lꬭ/_up|:WVJhe+ JWׯs_? =kI1 :7B3r*vr Bw5d!];&kW}.-I75:ē2FB4GT:$pہۅИy kII퉄n@B`aMӰm퍕!'ZΆ` Gs*QiB1Oh|!ysޅB: !*sA15_{G>mKp?#._ʖ&iRxOyB*lKKKC !\iea/-I0o$~7'oλBͤEHBTT@- :PіϽį=+0n>sL-OqOwϮx͟jk:'󭀂LOFj.Ͽ̑|t~tbě[sرf9cy3k_fEzyV@h tuЇּy3?#?}Hpκ.n9'BZBlvBQYMCUUԲ@@AaXmvN8:||j^Fplrpq`W;zbnV~9c5NF;'?ٶV́gқ<ޞ4]ȎM[r?Y>p?7w7w-h anbJPܓDK ΜӉʪcBWu/-,3.'X҆lI kX/ =a lw6fXf'+ם$<=F@+r`~cHο[֍_U$߿d`_ő_&ͬ 1f;|4M#$4,ƶnaY IDAT8VvL'TBUc Gq `zπ!.ӯctڻc]~.6.U&1R|8aZƧ_gNc:wxSQ6O֙D?GSz5Qsq:>αz䲛FҮB4Ʉfh4I D lp(*h,F?,^Ŗ_.=:kXAt9r(&U )8fJM*ZI%B=ݐS5Ӈz +'~_n]$Q y7 =c/BTGp ElEb$5@@- |!"tIʍ7\LoB!Ψx) ԹkjŮQMp; t :3Ol]5i.݄Jii%%~0g0uԥ+}3DDDhBOQypspjji-ہn#7'];9+]טy̞];?F;Vi=B%FBxT4ca[-ہj}Շ '!Tڦ6MʚjjSJW>SаpCBغy#9ل6ޛBPye4`H-݄GhXxcGjjyY,W t!gVn#.nB4'M+op/+;9ZTᵦ[ \PRRdK!SuAAjg=IjZByUQUtLBG1͵P(Q-ȱbfä9?]v%@AZW(/Ja޹Qy{WjMP՚ !DKvO=\PB% 5)-̫ezXt<ʵ5sN+Z"h(;kqyx;[ jZ!Zj] 8[U@* ;sPgAӴ١b  6\'u^B֭,A`k7!sUH@O5ݎ|aN+D)UU]Z*8!Z3:½l.kp6B VE{2rdёLxm*>Cc;RHIRY-~f?-W$%%yرrWs9afnt#1\} )LEs-WͯRF@۫Brurv2ͤVL, 0}JOXp7]ڜ˼G:, ~fIу:Al|] M3Lfe_Ho;Es8z .yc EY?[g0WjZ^yi5Oٸ1ƙgn4rzuV ˞oݶxS[8ך/U !PYyuaG0`&5\!SQZ;Pπk(t2K?Ǹ$aB!E=fr%Q(XM%} ^ϻ⎑.Olg!:.l K&aXyOOdX(>9eGai%ǙjԬZ;(޷F4gw΅4wF߾}ٷo:%`ٳ/.p11єvt.SkBTRfHKCqYBS@d|Rt߮\1=+>7M2s+$k(|wfCI{fld>u9ÞxnIsy1-ĢT ƞA%G7Ɛ*e2q,\TUcܸK3g.!22GСV89]nGFTl!p:j6cuQ3b끳P!gg `a_rLbލ0¾,>x7=tYtOƯ!_)9k Ctp {;|БS항{0Ҧw'B;G]~MèVu'ؗV:Wuvk&0)..FQfhe( q^K!D+;lNMMBBxzZfE/³zo|J#;jct(}pA:\+CCPn$n`*@vc.AP Wkםh>>'$t'H/,߯ xtsvT8zڮ+MEJ- J]x@@QgƏd_&i&MRmT(be]J AAЊ `y_aS+5ece0uL(vμ{g D.Bk]5<£s(JlC}>geu@CCSOT牀gZgS ~AұҰJR 9BDG;7vg} kVj/ێ8k<-?4'_&!!q0okΙOJ,C] ~u1`OBR*OV^Na4_/JŸ*(_} ZxP2% 뙗m]4 T ppp_0q ,#@,~646oo1e^#oβVRq. `Q5s0xg")ٯz ;H n{R~ˊ'~"y]hBDliž AȲoj[͟S?y){VJ↺p"Z߬AZNJĚ%H<*`t {?9KנUm&۹~~3X|ux7vD> "/+yO y,]T!@?,t)s ?x k7}va #}nBut%'ËrMFݘ9>y2Zp[)H(8U~B{ LߓXץKR k;3ru<< uPt :EN i5 {1T]-fL&;\54jVl~46mT݄;(Bs.O 8݉ !B0 \,A/m @&Y䁀N`xvlYsb4r1x4o }ֺwBnJCu~`رSh1tؕ\[DUlNl.1kO?d"6=hnER E sO_)"L&399M&}d@ *UUymKzܼ|sހC/:Nm3'`u;xJaIzқBn&Ա-H ܷNglAW`iY׵ |+S淎A 4=M@D()tG&'7o $D7Nc b!s/ª'񧒻Y_`oBn`xA><,9p 0 !RO~0Vxc=RI ȷ_j.E?o@i$Sp;<5^5H4nn1' z'Xl oHp,B3JKnvC$}B1{NІg!X:[]t 5D@tƼ/Oْ|yn |°o`ʼONme~)ʯ9&T^,OzW!0kSicB˰YYx$+74ߨo%7tou[{(+ ݨ[XǕwwPm+fbosg^Ù>jcm=}LS#oN9(vuj\]{ӕ+ w@nJ,VFqD$w}Q+:|Sw} U'%0os, +T2UݸB]pu vp4O#Nf9ʼn}#ڼ4-u5SI:_<]*esJSlVu۶pD#N LY3,L B}~9{BRj#WDOl$6Fbz,}gA*sn ,CO4<,++hF3F cm,_hFNáʓ4Kbƕ06ёZ8f71oj@4YM)! n %!b 10e%Fj$)z*AU:֮X֭ u]q :Q&o">6\t vְӵi#5m4x C:;3?eP[mǔ]Șս' !I!=E8{#G쌝=qIf~|x=謁 H/0K֭F9t&ΔDO;~vqٹwv{OeLo޵Fmz6>A LΘDAHZ9c#O4Ldg1fd#pvǼwsp>_}C{oGp F{fײܻGhq+ٌ-)%7ɿNj6 ̞[SQڎ63jmT܀ҹLbuچIGXW!By vYSO u9%i14ڪcLZLSkJfMzsO(2fOe@o>7T R1k-TWu>asEI7;ٿa?g';qSEɩ( cH4({'i14BQ\x.;i]B!Dā@ i|*d=?%&h:hIn&p>c~GVAd[l;ϥvEs QR`GP?ྒྷ䟙1`O#-- =$ŵPq9ݤ{O,23g;wǞU 8e9/Ve~%!ާ&sǯ'm@q'CvԡPzFc=]f/xaF~uLvB]0}gL- dlGb'WѝL yDڄR2u#='>Q=[UтΚz;JM)hqal j\`dh {?e˾rj9^FddZqʎǨGam%9^G[Q95Sc4]ǩo'w|> ",Xh=-{z9Z+M4A.1;a_5ߙ*ĦFq+xqלz^y7 ՔHB QSk9ĦJ#faOnCC^ϐ`Rﭥ&u3cG(J϶w/Gsܩ:LɤP l߿ϏzZ^zxo]Tm}8dR&ai;oa> dNL%p&%-۷*3bIH#Ѥ+̹3%doɾ'@g!0nӐH*[ٳ}11IOE\@FR'ay;ne dN ` 1?k^$zý{>g"Z:^^ckٌ+e?7)WHm/n'1)>V@vs5+96wOt4U7(:o1Hٲo- XR`m3WJ^iΟhsDf7/8 $0l*è ̫s>>/1y-$-Xtǘr0'D^חk8E(`ݶ"he]sAy[%ۿu^7dk8=7t6wډgr_9\{bÆ$eD(o1HWr/kֿSzLF#w_! 4MW]|)?LXeHbo!˟}1ks8 [φ#ؕ\&ن8p)"p[ &z*qR4.=n_LOKgBqZ0eN G9ć냎OR,%77,[c^h eK1 n:N?Bwn'OZ),I݂ |=GH9멺d`+/ln `4p9P !D?kjl$9%Of%|"SJ9AՑ'ބۚ͞Iz\gV5GΞ)'GE|1uZjO5OH3LaX2;y54gVor53I!}}=c Oɿ`TO~^hFN_?57|1qSho(Zn;4tل1'5 IDAT }v41M75sKJ-bݪgx8ܨ`ܴ/qW0#{Y_Ïvv3oXUCӚzFs=S* L!{=c /(&,Ce tvr&KceƆyM N͡ i&F Bᯧ@ Os-8шSS 05+ZǮR29?IkRI h՘J0C*.B!E azvW'0nJ )fpCv;LuMgƞ9 ǐhTq6hKn8Ħu;qYi|=a7V1Uv!BvLمJ醜5y5qhMۻϱ1wJ:sv'[dew=PE` J!BDQ<pqc;b-ٳ54yL)q(j +14r|Fš a!*N+vw'RESubMF=ecTD }7<&#М9TtqvL D?ag3*6TsP}-* dOc]GkQ4t)|lhώ}3iS3;3: !DrMյيTL͑=Y׀kٌKC$Ey_=o'{7l>eWyqOgf,AB!D}3oc۱:7IOK%%=TqGZ/w,e{>uF:cb˦{_9^IGVs?sے1b^NQ5 P@ѡׁi!ջi:uZȚ:32CK(dƜ<44Z?gKhMĤ((f[Ӌzznظ R&a7r"L=k((B13^}#afaO*"<%)߼)C]!Đ@@`5Bm-v'w51l&`K*8=:[Bb)q)SswR' ZH0TàݥypVjlɘZ(g&璵c ~4R$B0}s+]q)5 <VjV=n83|k'3 ?>`Vp٫9[xs2,W;s'sĪu3,/|xĒ˹'bJ98&*[6=ػ=iRG?ȹI;Z;G}oԹ@op=υ 2g峽ٮ@NFC.ē?9!ZuGd[ijhaa@x9y;Z1iVtZZ:ϣ&a <*F?+:p$)h7e ڏm4Ic9l!$sQLK-`[069`&bp3wϹet2-S<X< VU%3}clhse̟=M]'9-Cob̷o1y`CF*gWY?Σ=Ħ{fq73)?ˣsnjP2z~^J;WU\]93:٢X&rN?]LK vi  h IE_`~+{o`wq3&1&,5nT.>9R[k|+ $=MU@њL3.پ7${ŊR7dRDVAPNm`ޒJIkɧ8 <ÔExa!q_|?fύV|DgPpS9 }ƙLu.>\N?fnY4x`7zOdxgծbF55FXɸ\s)YҥqoH*bLV0q3fN%n˙xz]: 4R 1bD쌟#V84dilwAn L=eV]8ݠ_VΔP̤L#5d@a?SC&>/R=qL9~Ѿ^WS3)<猈-ŸJ?NucC{Rrc84jwx^E:魩X i~~걦ZQhWqbmly9 Q'p2@w+)<ɓ߹wϚŌ3Sȶ#!  EKC-u80>B!Dl>e/JݢN(7<8QLP|Ph+?>‚mTGSN\֮u+z{LK0 l 66fE#3B18v1K.9D MCo1UGV:B :t ~Ï)s9!>^e7PA&(zy犛#Ou qĭQbfYtt'R[i393!p4wݣa0$BO]>\͜iV%}Y\2<&,>TjVk#d&rRgO<ċ |؎>|煚HZa7>eky3μ뼸.3,ftQ2-Y>! 4Mhtڊ c=!5z=&nd>BB:oʯW|+Iʚ̅WaJ՗K~JO؝q%[b15ͫ(}Yny}ؘz#,^|^mg0nڗz_a~y۔-(f Më*it)m" KFK ڪ p\NZۨ?UG͎hDiez@nX%;'M4EӓfR!?VA>DB`\G\<ʢY˳77^NW= e܀yn7x"ֱ룵TbNijVu9475l <]7-f3Q,ło?Q!IyXsjS 0m_Ff K978ۙ~rls2!x6f .ކ4Mt`4&9ӊvJIMx]"!Ek/?O?Sm__@HLjns({u 9qQ#?c.X3Ѣ9gv#;bSM8Md!i"ѣj#ּ;OsHiM&B! Ff8тw:l=.+d!B!Yꩭ|z[ 28?Ns黶N֭CQ}3g!`B!D~ t ̘KsRwZ*(F2H,.[رmݺϘ5s&qB!B5tl 0[%5wKUT( 2dkAvu[[.&qB!c]XGѱyTvȠu6h8!B!V GS-m:q2O ЛuzqB!bk 5zhL-OЛ6}o!B1EpAx\g!Gk='9\0&'i hh{" ]il"!t`J&O9B!@~N8 |7EoggŪog*] $OHw|$B!"6'y4R7Qt4%A!B[_` ᡶR[ӄbW[,Ntt ݋oPӱ;J !Bh(n.\iǥ:q e`87mPǏY$&&e6hmm嬳B(ݮ !;c@#CLNF.HԵQo)-)a̙t n 1 C]!1+gaft?N!B '=inU }B!~$Vw َ^@!Bt@@Qtt:%ӨzPUL!B ;ClIJNBQzByrMMӰ'$t8pB!b8@d2c41TW.:X,T<ދHMdB!8} Q ΀f&!1i(0쨪Nխr9B!"@킚chUGk<%=2$B!b.B+M=h 5j+X"n̵((QVD=F!Do ZnڪAoM:+ҥ⾟?ۅ YM|6'Sh;v챐>he꽇/|ioI^#>0o&{uOl 9sf>/SEb$k$ FfFb`k7ѧUh7Z`[3_@@] V; E=|YUm&۹~~3X|ux7v"nMx> ! @BqcIٿhZ *wjk^ב*0MCikӀm!DpZ!6U73 Lha*}L w#HHz:ȭWr -'B)UqE]kqݼ[-uB':N?on'OZ),IM'}=BtI$j Gws<$UƵQFͻ a{Ɇ HNN&//cǨdWQQ` 337;0f.nGHORV˗-6).W{WiqhOpC:ΰW?`5ПXRj {|0-!=At뙢ח}8cb Ba^c kq<&o|_r bmJ ~$}SO=Ņ^_4tiӦկ~W\Aff&?[R]]mtOy`;-WVGlsb"9_ yBX0\'լA `%*nuQtuJ~!sϗ20n`q@Qh[<Ah_(GR\\ 5k(..d׮]?[ 崷G߆ 5k询~$3 !D숺k]R )SUR|tFUT@UUT[σ_|Autt:@QH+c۶mL>={x7K#ʣ+W2i$vx >6k=mʎ;M^^ׯg֬Y}(w'kҒXȧ? '£ˮ 27\pM▉x:K)rϰt{0{:.u< P;XC.BoSJ LTUE 0xݯ3B{c.Uva[ELnn.opݘL\sMߞH*y_0/:> b.}ImBu v7Q4(C6l~8x <mmm455aill !!+Wplνt \ƟJfQ^#k uppy<~<2¥7{߶nboMX4.d}?N0. _+`ugc={6?8\r | G$`&}viL !ٞLz7mT,%=2r7E"jwuWpUUgyfDyvm8n-[G233я~>Mvb1?ӒC!bU(c @Qtݾ?|?t|d啛}bb"wN77\[m>O=y)җm"0XHЗGٕgB> T?eBĈzlv xKɉ(zw*_*1߼\ LhmU8w?\zG>#ܛ\H@$Ͼ.EBqi8N`Gq#++4C@Faj?BLݺl{3r++y`ydKo0m n|z:/ U#ɧ@"?$&8CN$B ᄏ{ߝ 6ޭO<6EEns3q>TORV˗-6).WShPiqh L;6·ai#9TH}Y--i"] uB]`c;B5{#XH0Tpp‰$6M^^ފ4 ÍBA/~sS~zk.IWBeuax)XM i_y} uÔ+j *JP=%Vͮy*Vẁ(H])+xWե|׫N۴To9:]7_Hu9?htn*״7u u>_9åsX*KxkVj/h! 磏>q%K(Aw\Ow#r0=ݵ WPwm 7wP u=zӝX}n{$eTPp_em>T {Pjlߔ'BSl6̙3M-dÆ $''`رcTVVF: .<£kZ7<µ7M,]OyDm*r*[o<r./hjz  C 6Fnn.G)Sey>f̘q᧞z /믿cgӦM[!RoZYJn_v} 2C B L/^3<<<3<ŋ5j˗/gѢE={x7K{]6*1߳}e*S,.h2 򍋝$YM7Dr8}Bo!^K!$bH\ve<ڵI&uVz-.r6mĄ Bq嗓oڵk[na޼y.\Cf%n&%6Q$NJ go),whf? fa. !@@@1$Jƚ5kHOO;`̟?(̛7/~8ӽHc'0=U,տ: :B0Q-|סE8b4<4B  CBq2o˷r\K(!遼-wɅb@@1hE1z!s/ª'񧒻Y_`oBn`xA><,9p g?&adƤ[iXȟ>e[!BAx,@ORV˗-6).WShPiqhaڼמx6 Li}#Ur<.PX[ƾ T@vB|_nV_8PBә=!1lMpTQQ` 337;0f.A;E1pUt B7+ `ݡ>bx =n7eA1!PU|{ؾzj6lQկعs'?իWs HOg16N !N_!믿O?ͬY8vX}/RDy=zb֬YCqq1> ?OxP6lQ-I!ĐxWYdIa>߿?6lYPuKC_!:!DSSݶL&nwDyrJk.fϞ @yy9vs-s͊y7OŒK-}J ~{^HZ!D$B lneiG]wEEEV6yyy_YflZ!6U73w>y2Zػ g|B!DCCkޝ|N2I$!q3QڥU)jUUjZYZMjKQV۔ե*JQ-B\ I9DG!9f2zw|?|lڴm۶+صĴi0WHܿ2 f9wl/W'r2r(&+e=HyP! "q]wDze b޼yDFF2qDbbbڇbaƍY,̙g}FDDm۶-q9߾~@fһ:A""b"0111DGGSPPj{,Z+VЫW/.\xgK,k3Yy\w"0䒑c.VE9?ït_ۋ # "1sL6oLzz:RP>#|Bk2a20eۀB3{!͗-Bu)b(H""O-"NbdggSfM4iwAƍ kVy&c8f ]ey̳|ۇV`9JJ9򏞥КGdV#(>>%o/""RTC?|2228p[la>} -ZT>.<ԱcJ;mv| gIL<#:1浾4`!/MnN՞!wg<#v_>Bқ/3Oۋ7"0шxyya4Q]ۗCHqkCZe)IvHJ=_҈HVq eΑ +/>Թt&OˣN:iӆhL&~~~%?0޽e˖q1V+T[+qG7 ;+ukWE@D믿C}DEEhWÇ3c nz!wB@Db>|۷xb iӦk׎p{ȩS`0rF8Dpp0ĐKrr2֭c_vSN,^XѼr)"}x<̓Gѵ^6͛‚3=axKZLꆅ|r<>;0y7~0/e`I0JS'k طo{'%%|ԩڵk۷oѣ]b]o.8s*_܉_) >@Ĉ|>91Ѿ^?A<;RCzG[Ѐ""RTC7pbbb~_!YsSؑES.>Ϛh˦F׿jҮm}b1 }yAzADD* qs[;kX7rV_"O?dPWؗv< j՝A-Gy5HғK-""@_/"o_I?b 3]{!٬9-"ry$>KSxWv@""T[k3Yy\xՋ:F<"KFX吞!\ۆWQn+֓eTHSSqkPLL&L:z06 &GȻ^љKźv}{ k z^#""Cȵtx4skq?Rj#|S7Vd`9H>zBk釓9Bz~aI|i˶5 z$"R'_d™xF:HpNy9,)ڳ>gdGHzC|F7NVƯ"# !4) K%842 U6qż\1'pͼ\1':y];MUaX|] GrK`(DbΖCyqż\1'pͼ\1'zy9cY6[Z-""U=yp-ybNybNy9""U=iK/ !"""""nH;""Rd|WmadY1蝷^$ssX=ipg7}ۂofn }V-Y^@⇛9kV 9{9GFYryXy_6p8# ބz/ Ípא" ""0B[wnǯ\v+pxoL1 ۻ qsy0; gϧ0~qZ'kS/a~[ e:RZמMyh^<) >W7|A-""RɌ?0g}'YjH+J ^Ȃ;H=׳ѣE'X[W,hFC xc?e>BdxWyٳ UƜ̩|r'~0&# XDVr>\9RƜʇF|FWn-5ȍ0.ƬnD-""\9xg,3vDY}-Fdݘa"ZZ唗S)!'kn ;RjWxlF\NС߀+[yYrSfNjm<\8-,ҋG:-LiĝUk7kvŞ|kN-C?s.MI9;E՗+n<  㹺6'gl7ܭ٭BӴu޼*J!q*'X^*{d5E?yrҏ\xA<}kA>&BdqJʩC*"@&'gl7-1#!gl"@'NT""V Vz*I[F8+>/,Ox=xcbO8ϗ|YrV%uC.9fbs?GEs#%Uٛ3_C؛zhUqǁ;ЭGR q*޵iHaӞL]0 >50LLu5nK!Ϳ a&և[;Jɷ1$o=BeEgC/[%Uٓ_CԹ1ʉ-#{tB-Mj|J^Iܒ\*$|$aCo =N=B} 8ud_} Bid9rq3i s~iŸw,OsxY974W 3V|^eʩf ajɗdG60bٿQsR9pk̊fg[Ѓ4NYV._l}sJIDA@`C'Xf3el+~aDyCbi[ 5Ko{}m;ˮ}V2dbׇ$w3ܬOe'+{^>/%ѓvC;w |‹cd-*bHCOM qE9DDDJ1D;:)*DDD\BGWb'[lƚ4/#DOdᶓ5k9AIDAT~99_wll,菱az:wPVߓ@|Hc>MKX%KX;xq4{~AH.A-cK؎Pp%`RσshP4S;(yv؛Ӛs)@LmD@\ `6~ukqGV͜ 0[Y g<­|8mC}r+w8zvanJD}#ӱGaq;_usoфonmWpidz91!Bx2D `!s׿y|o/' 3nUd[xG佾5$K<}kk qD7ŐE NıT ɲkCtsR8Mйjf>=wA}D&q*DDD\5;Xʙ'V`3A4x\ѝ x^(V Vz*+4qR!߲yv| vMq =إ0}/Qt~; "2]tsƄZxm Rwqze-wfDRش'O/,E^\O"vߘWZĺC%o'REqqowf`J ]~K=C{9MH; ^O^v'?ȩL(*8=O߇pi3I"H "qcױdVl9Ppp-8ϛpR5ՙB\7Z0b.&zjic۳g 7z$AMܯчQV1G'zrһxy233d*ac0ۊ_Qmo 6O)ZZ]B\{s0}Smt|~,I\aև:wqt"܀Rns5F7UH~7YX@a sұ甝is5HDD=X9Z|_O!-+ƝhWE C')`dfRe \Ծ%""&sٮ'/DmW3x^+ [Žy JB@DD-o=)W-T!$""""T!u  s DDD" """"T!"""""nHR! """"T!"""""ny>]QqHӧy~M(ֵKJHDDDDD*!"""""nHR! """"T!"""""nHR! """"T_*DDDDD\X[\n8DDDDD=gsZDDDDD\݁̕5HDDDDM\H]DDDDDĕ݁5HDDDDą];uR v@$""""bA""""".ƞ@vw  *DDDDD]yopM!еK2+*>qFukW;:DO8tIENDB`pycorrfit-1.1.7/docs/gallery/Screenshot_Select_curves.png0000664000372000037200000020320213554642611024511 0ustar travistravis00000000000000PNG  IHDRzUsBIT|dtEXtSoftwaregnome-screenshot> IDATxw|eΖd i$z (4ESDy6^rgzb==;+"{7wB MBp}^fOg_Q:tnaaG B!wh*(*aax%x 3tXF[ijMvBSR[]ڸIפ&^Z|~ֈpRM3b}'ߜy7վkjѬ lD F y C X+^ i|UAMkYOjB:Om Ik4f`4#7"^Ç6+C;;`(K "C<аB!B!N<|TSE3MH'ᘧB!B*""8j !B!0hQ!lii(Jy&-|_&(  *(Zo!B!- @N*A3Lk:׬J,bi`/3ML@׽^/a`&jERZ,XPT߳4_L#8łE%}5^1т8,wB!Bƞ`&D]JOOR-#B Hq{Xs0;p4Tyt.-[Htt 哓sg6Uq|*ǃ&114""# vɁl`bX"DwYIқ-B9d?j&3 <^bF8CQ~FHD(FT!B!~WߞۭkSp^GC옆 0PTTEp#KpjUŨuV+DTT^k`TUbrϢŋuzŒX,* $.QwcrXtn]aa,'虦Wi9e.o_bf qVMxKSy2w#|m'ڴFb+ Qh9Rd~~FFg_cnC=!B!PTTܸ[7 @=$-~~L=lܑ;l&A|3raz 6P\\{ίjq7 b̞=WGӬjL°aC٭`Vxа ƬYq{tlŢ6L"w,}G]Et$-6#;X>/gLz${ulҾCӝGP)7-=@[zt0sa9% !BqkiGw3_:ZI..WY'buT$-߅Vq$v3輁ROųzU>C( js0"4̞a]._p> ee~xZ د*N=CH'ŠPpƥ3&f3(  N(p(6'aiP^RNۃ"U,Vx((v'ѡ &rK9c]ZNIiA-<iu(.uj]pP2VsQ jqJ )h#o}"fC*KBDHPeQ6iP!Bq xF4ѹ(I3=?R7t:D;ݖWJBboY!\¼7ᠴ *V4 "ARJ(躇`9z$ņWOY.\㩳 C^Uq&;Wa0}><b[%M<+<3ZWV}{D%mܛ<'k^fܛl&FlMX':{z0J銗8,^BF>{`dz|!.AwvN'+bwhokQ"k;ppzh?n,rGhdw_h!\Q\x>p; fŷllIB!CEˠ <%#^^]tKCT0xC#xOI!1H$$&PVVWj8߬a$&&pa&x^)/ũW(B!oAtlGAzIv(%@ffdnS ?4VG{+dxojb +b獩EvOܗ)lˀGX63\848Ga^/͆7oI%= _ n*3͉UvE8,x[BV:a>ob#på揞Q0'0'j#̩V߇cQ@?Cr}k/3`]Dn!szd3VNz>L[QB! |1OM*A06Tgt|,BG#OasW UX*Š-sVkwX0L*FE"zUo^IY+)ׯv:.{`ͤ<#vaVCXcЉf¿z$"5/jo8u9C-ΐ5 v,Ve v /Zz%u6h!D&97Y{BoY+*̌4/_[֚Bf*kՌgSKbfB!&[7UweQ ׷9y1mku?Ӳm8mUWTrm/NYi9fzEQ9˫T^YY++šrb z<ѫk00 0q\-ł'w?@(1) YP̥ǫbT |=da#ѫ{!KLSj-`_0ےp\ wZ`Q,u9#I4SjWB{_eC:Ѳ%ӧ-{ū{ꛘ~+P|}V4Ψx44Pj a*LVKޕk\:"iT,D!BWnEt^,ڎ"q>ti;?/\mBhOU,vV*yy:1MUU)--EUY5gUE"oŠ8&s|W%w"BqqaSUXZoxܟVоQx( pDӡm8Z*;ժQa6]m01 ߯Aix~I&#:{ha1DŔk*Z&x-vB={?79/gLBP[s :gG7;,- IgK'[2z]!BKn69%t`Ҏl9F~سwczJ}'[l,Txb(No_ymzXr3eE'gtu{vkvtocQ[dv՞B_g . "u|~TN35]P_B!BXRR?e|"ʶBNኁ shexi`0Ջ.G46YgRа0,0 Lrϋkzx=ɠBAaGgaUZ3kܼ9I$''Ѽys7O$11x]n].;=^m)Ԣm,X 3HBm `Pvl/?\ORx9R#il\u{K1 ? ] cVk{DG"8o}f(X,Y6o DY(GvizT4LT:uL&.*f. Z̎ JOyΦsRfzXm:WaCXYơ[X~;~:e-c[Jb-[o2!7chG][o]cF3zCX .%Y`gB! ״j/X7Mmt\ a^B]TJb呬PNV_9ax U3x77鹪bxMv܁EӰYR0MZJEQ}33|{ vTrm.1VPTfaװTl.qy:EUlVl*iPRc*؂).EQ=G1MtKVR+cN|bꔖy)v:L2nNK0]:.T3HC1=k[Ѩo**w{1L_?..,6œB!8ݺ/ѫLauvw9 iwQeN犢jaalA+vݮu0| x=GÇXTGV r$&TLqJ ^8aX֪B!B!8[zEifx7}jŋŢT]S匜*]cZ+;IMj碌tc>cnC pO7b?cTg;Ɩ<'{9\O?ϵ>^az?̗~^¾M[m~9?͝¤vSܳI=B!a}>^M[HA2| &8P0 0)>'H%]toW3㻵)H=;G%a~@rrB3M'S<37zbKYM?rw#?7n`OWfn1!2'B!I'(ڟw%W۾/141 ֺNBnͨ?hblߩZP11uBꫣE8(t ٔ׍E[[ ~fޢE[~~|ƴ&Qg'nƭh[]^xSQqluTĆ@ޞl\H»u;s,gF/Gt'J nn#Wxbh{b;6kKs--GfSf!Z63mHOGL^~m1}]s%&=+B!Y̒O䕕S\pC;3cwdrGٳW,ޟĖQpqڡ5D7t#a9 nqtVvq4Sq7`&JbH]S-uLd9?ϝބ?RKofƔ3os']/Ȯ Vf!U–k$"{!XK7_?8nn걵Yu({7?ZI# LD IėoV"}5S˭\tXzEk5g/Š6\ѫ5_MaٍA"3c,X7|iqc̊7I[3G3j$B!8ѤI._֭`Ѽ9_kSܬn`TF\>db%,[4H!X YL)jW7t'U+ͺ!,cyԾnEBHr6.dƜE,_WB/ ;0):&dkC^vTTc(m:Ѭ(%f32Vl#4}!Y\o嬜a=*1ϙ>s> l࠳'ol5M:e?Lg4qqeNcJ3k6~dڴ(=ϧcd3fL_mZe0cj0{G$`d5`HOIB!ĤI ל1c鮋CcqgxIB!Mse_j!B!8H'B!g@aԲ&3Os!B!YHfB!,s=gNbl<'s?k IDAT?7ƴl9Mqĩ@{%o.+3?]$i#;YĘ?3/0`4]_ɟs9u+^Yed !B3DIdڴmC0@tmBfn!ॽ#Wbu~;ƿq+5-(($қn6!<28X Τ{F;c5"[b)k7⃷j @O|Z` &y;.n.oam\~UƦ7+GKP;Gyg`$o3o+wglO}D~S?*W mїoΚehhY/fdxF?ᾯ͆/͗+/849Vj$O|I56nx>^P~V[ ={7C;c0^<6}fg_ΈƵ)x}3&#3x|MhSZyOʞ|/csϤpxAzs[Ch[[8a`\s5e.dZVuF)6o#UsI+\XfǗ.z\gj:TO),.-bYɮbQ*tAO)rfl~5e,hṵ bA!(&(kQF2qY,8}>{H2Pz7ޕ?Œei 'b'eǚd]fac扵ˬ _{Ev ʈC*,qyc06U^zv&=>H*-Bw.gO+ns뷆sdW| Σ\ kO+65СM_mNG_B!SYA$''ѣm%Pm9 #bԷG-bD@r ;)V(/v7jO0kLk2PMJ"_"1 s8[3{+@=;d*{ )Cqț˯pEU Yqm7? O cϜY<|4zYcZy TĆ@ޞl\O.#fΜ%˙K݉R[P]29LLHM#)1u [hGi!Bӭ}ltbStlgiq$iPrh><”ڌʿp~ =>gjV#oFDCO+x0^3,'vj|\;_CUORڰ@u_~@~4/=*mjNfW5I}Ŭa0s(Ź DtGw5J.(stQC 7g;|9 2J8{Y#F֫uO 8-,H+۩LZ-qGٲ"o :z)=(joWq~H\J?9^}rkꬄgD N3[~~,~k޽҉a&3^H9)m|WWz`3 va/!'DMg3B!}%z(ks?(ؿ]$63˶O(\89>B)'ʇobTzZ3^35sִ  >y+qmF[Ə?ɯJL8TOiai|A&ޜ0%mKos_~-cZO/3e/XBI}3ia]bW_3w'(V["׸zq 61ywiH:nD0X,>{(Zĩ33菀L?=Ƀa1y{c 8؁1n+y/j9iى0&/$nxϿ6+KZCVO˹|ޟ4MIlu:iީ kڋsdZ>>RC!tRkΘ1;3\_&u+ȓKP<ׅtyUnL?K1͂ֈlޓG4{KԃcobT_yl|r+r]N^k|(܆{JD¾ciݡ-(($қn6!6GZ-Zeиm~=2V5-[ϗ=aP0in;7a&\\OY%| ̏Nv+lx7/_+ξ/1[iW}Dk g _Dq6ao=d:M-b5'Ł^KW&oo4sd>0&NzL !B|njyu\qOˢ("1KY&.岛WT'> ~tnvLyg_z(z^7 bA!(&(%`\s5e.dz>l,/̈́)w|s~k`cFqm 30SkμSN?I䁌=Q )AಆR-=PB}xzWhH!(>4՝n[v={IGF`lͰPѷZ>rdk0[K.w/ ˺ 2 %F;6Ś6m*.#irlw7OywQʁk~\Ҋ !}q؟J?Θh}K[Qopm{گ4B!W,3Jr(IhVuYEtWޒ`rZĔYY$j~pr#HNNz,39 I na^:ne0un6 'pH_:Ko|;u#<܇c)]٥u}7?:X_}wFgK>ToL-~w߲W?xV'uЅ 6 G5ְݺRoeٻiV_}jW$O _k]$M} :8 |/?PHge?&MoʁelaU NLљGx|ڝ{d?p__vG&ƴK)i֘B!_zFϞ6QcPFBVah~t<߹ 垸"ӫsl;7fv CzW5Ø G>0y_}`L!RrKQE*6+a̟5/QU S!oیas7;|v+pa[-Bs 9z#;` E!fT41QʠLLW^\ \uEH7jA4_{ ϻdgxmߗ6Oٿviu`3ޝB'cڭtd%=cq]+u1oxo[҈s_ UҸ`j`QgŜ!BS>zn#Wxbh{b1IPs@!6)W{b}w55j>7=q0E#  ۖ$M?@í\gjuгٔYּ NwRn@;S-> [GeO)ֻ #e(?=y3y9pۭbzdͬ[sfW+khLer4.=-hFR]Ӝ'u [hGRuy{q՗ti!BX,Xw԰qYu({7?ZI# L3̞=`f ̆_<$†K戃I'1-gOvˌysX1G|(3VCccI(]/b$RrgnU&m2}[,q%Ud;vdrk(9^QȖٛ0]#\^ \S=5qL:ܚA=Q'^r6bёd.ޕ,ar^j|}k4').8ơ1;2 Sh`҃.;3:6%VcU x@7z)41C$qeS@FҬxӾ[ö8Na5GИfqG4)+8̖{Awm''Kr{7KﺚJmb "8os']/Ȯ Vf!Ώ'jB!ęf֬Xzԅ]˜L9K6pٓѷ^Cf6F\>db%,[4H!X YL)jW7t'U+ͺ!,cyԾnEBHr6.dƜE,_WB/&֞31o1VN=(zJɲ3cjvCkJ?eg}S~א?!=OqlDn.8/Vh/YΊ)nևQcxj0MumXՈ# 6m5J@-&2s5GGR?D.]Ï9 Xh5f8no}^ d+`1KWnXXONF:xQw m:Ѭ(%f32Vl#4}Eъ]5D! 3w,?r?^;uB/߿L W빑2%B;_XEq0]۽bJ6?, Γ}%B!T)8{臙SޝGQt\\$@HVDPDDT\uE"(끊 ޮW/XCKrɩX{vQѡfd _ٞ&SR+X?y#a`}`[!~7:.HpOAΧ^|?ߜ#9)m"4=lv:7N>$ 5c?s$s(L>~fmrփK!B!>zΔ tɨLYqt?.;Yh;¦xg`={2U#(3th >Ws?O.:{oMOx]3 `._իD q*_|Nb|ܝsIձO:;ڭ3ϢLοdQr)B!5,a[ H;пo~@qw$9{^of㰑wHs )7z:fsl9%o!XͶ`OHŐgN9>Y9O[\ !B!D$K7m c$,]SDzr︞Jcuw1{JÙFiXߡPhM ebb`[! C3Eh:U=荒KB!W0]ᶃ=X:YLpߋ"gc܇MMi.2N:Iw?=#6ޮ7Zy5wDگs 64Wޮ7d[m>p=hr6ޯ cʉOؓW?֍B%ֱ7m5B.B!"00\p]_ۍ+=ߎlr+qLF'+WRqvy3'Sͨ;[~*Ud1zTRے+>py$gnѱ[*lG|[>nWgWl#UA6tNqRtmJ%q|O%"sGܸvC/iUd})gsމ)jth.'F=),pgJ>E9 Y Wpj3J.*ⵂޜyrB!B\\0CHu󟏾g©}RI;~0=M\| q\t4. \—`O|Zvbܵ18́9wtc+|޸ 9+nv^6| Obt:FI;7/VՏѺa1d4y>Iu}֜tOW=$v=!=d})>\_WF\sWR}Oc\7~˞a$=!B!D3ףS/pܶ{#Z!޼N<0!B!歅E[+?!KO'=5r+.~ԳE^B!BS S*vmX͒U2AOcpxzǰB!B @{ .pE!8(6o̥߀Ty+#,B,[N B!B!~ @7 B!BVb6 !B!KzB!~zS{^B_O?~!84kIɇ'B!`AE~! sB!8jzq)B!B!ZI@Og?B!Bd'B! u3zJFzB!Bk[ ZmEPzB!B~[K78cWe*<{'UiA cT'tG@e͢UȤB!GnjدYNO`uGyF;l#:^; yʫO6k:)1/!B!PK7ݰu41WPtzBuuܽq3&*rq];GZUR_F*zhY}\,uZD}?TSb`O p#'&ϛkhZه(K}wqWV,rֹcK!BVtO$\3m &)<~fWF\G4q^1~RUucGZoƿ[WD1N15Zz jzQQCv?QTY|DiށoO'P_qՁ]spt[lʹ8O쏑<]wMQ jU2Z:f1C !#qH+S|Tlgހkin <9^6-rE:8vCߊ, H_ۭد}H\ֶB!-ݬ_6#Ϣxy3Upl/7*,0WD8VE,c` oN4㉻h('QS vVǩe}5B@J(6-B* ϥčW*3@Gy#O-TU+1]S!?jڶ+kZ:l.B!$P9V#%JaaBXr*5Ĉ<-'y-NZ\{4ofqE=^* -FzܧDٶd婞т6T󱣲wžm2 S =>2U,3z?*-s#qf0R%X-a"^~Oc y=<6h!zn=z-=<$/In"D&Lr&f!K['S5>T y9|SGpRڢ:sZ)HEeI|zshMK,k0E%B4ş+*cb&֕7 f^Iw;|.51rfƷd+ y(K7TF"F 42*ق?JΥ5H>B!82Ye|@VI{<#y!\7A.ڶOgyj{jӲE^H:70sNFq\O^m57]&*(e}[swPnu⌚+=Two/`gsA%JOې1\T㐞o ! -|θsYnI$ѓ+gNÑG^U[^9$`R{7㮮|9̽IW8S~^.jPU?TIw'+2yWgNMC!uh}~]e5^ܬ|Yi<{"ם{P=!'T?jWƫz q5\ r yCk0ЋGЗl_~.KwB!H2mZ:ˤ[;.o<] [M2~3͖EBf(o?9k FJ_yof1|XdB!8\;~&k)ny>8n@aٓUҟKo! 0e?6)zB$Vn*N a1h&@OUcYe6O!GDZxX^<8A-fFGcڋPS(Bʷ?g'q8O!(^Aqےy)񄀚b$KT<̺3KQB!{aWMsw㤌 يop_\'׾E6/<'K6B!jS7-Do:v`K󸮯͇y6 f|iXT߶Q^'ɝ?s0pnbc!;i׹3m2B!o[ؓdתTl.F/zң'A1s?xꉛY泑wǚ=,;6x5)7=YaC3XJҥM89KόiMᓕ B!h}%Z,d/~d'ܶ;C@\=m 5tSK~\wy7< =}-h/5|l`|ߒŘUb:~OHrUX9%_^8&܋d<-+6?\O~#; Uòdq nYa5N1*,Ӱgߞ N@ +ٻ`=j/(75c1ؚi#yq#x-bGݏA7Tb}<g[Hv C;m"<\s\ch7[Ba0q<^}?ϱծfڵB? M3!B'3[hw;eA:$z÷N>zz ?HO'zri8Zd4lfxy$( دc=T~UaU4N&-q]p LAmަ?R']4듇)NhKIJwM!P^RX{e!VRFމ+eţ<p7sQϙk9g6!_"sXIp#T cXld~K^v&J](bn_lQy8s>N9Tm-GKLFP&DyH|.F[^!P[Wb1Brmc]6&ʿ9MyB!G# [S|GnV>ڴ q=RT/1{MQh䴶K7CXe>(]YReOCOam*h"i7{rebzbژXDŵGÃ{B۽@NXiMm V2 0N~G ̽ɫ5rsQ,ƑJs-xWݲc`lKTlmĎU3OXT6uO5S~_}79jШ'pG/jhƾ&$0BnվX6,B!)}st˴i<:o/ns> ,e8y _їmUJmqpR/M4z6(KA\7 W)5i?#צkniXDP\׾+ܾ~lvq眍ͪDف<a}9NmB1ݩJ n)];?9j؏ڌb7;Ustcl!;jrӵv2'BܙEe3q^Kqh6V9}_[_W_&=l/ O9{yISΩ^;}>I֕ӗ fs rcLd|uOŬ4֭|[F,u nepGOxCZ͸+ơwrʻ7QgDF썆ҚQ-mjnNOiP3s֨h9oPتE"[mOEwSl}>_2[:܆@!B9ăNN^|k7sQ♗0U)S(;ϊ]PyWS VX@>1z3ōb~y3 Ex54G4et IDATy}*<_v M@@s;Gg߈- jg_4YRc)؏T-m#ѿ֏,L+{y )]Gʉѷ[9Z RT3DD(MQֺ3M;;d_s BߐT:oLoz$Ԏ5 q\p!IARЏ*ڊ X8wwӓ_Gp{JKB|FɿSRO̝E`Kǖ,KB!'ܺh_ܝ8#FcJCwECZjAٸo~ΰ#ZKh{\?ز#bUxk/+4"^g"g[G;+y@YcD Nl\ψֻתk7܅B!8ܣB+o#́z'Na=]dME{|"UyńFMy 8De*:NHKc8lIv ڱW_ *`7E L3qN,+^-#pZ>[Y\LT͏?XMs=o"l[uK2 TψhAQ5eQAhT<6vxg-h9)6~Oc y=jܵ uh{]!B֧-kCz:/ypEq|_pҠF'&%Zђ'z?"pwyk1?Il !GQnDhze*UKk|qēBqE^H:70sNF! ܣgVO!,d/~d'扇`sU:=̍u|fE^U[^9$BIo !m%G};ozqAbxZ)\?Tl/tݜM}3zB!Bq4~3xf!{G%Bn"m2_זD8́nnp&B XB!̠ox:fpD ߬үyfBsTO9 GRy@[B!B4EMjpC\ o1OY8f3k&TybD; v@O_U|9HNk{@}%P !MxY;Q4XUW *{&B4E6iyt^&ڑ7fp}R3 SH³{d-qm B!G,*{ZG5-fֳ?R)>E)1,^2GOyBdK!q$<N_=Mh|^|ڭѓn !Dl(y B!haU؆]^. GOl!Dd8MӅBq?q ;`%3B[3}p\$ .#q2.?B ?gBz_%cf;񼛍JM񄞾A6؆M9d(Fj1|ea"t 8%sKbpLwyZ#VjmT/ݟؚKq ?[z2{03Ʒd1fUS:1Fލw-eтۈ鸆DWa,BB\paNy:%n$⃔D!B!5t [Gse> :E'_s .?ukM}Yqq];GZUR_F*zhYݏX괈 ~9f[1vyy!UB8GNM7hh֣-jCg,90znWDžp_YKfʭuXrp1 0eM!BaOݬS}/ p$J# wq];~X͍1viy6o]b8\ƈ XHhx+=G} $n6Lqnp iށoO'P_qՁ]spt[lʹ8O쏑<]wMQ jU2Z:f1C !#qH+S|Tlgހkin <9^6-rE:8vCߊ, H_wuXLnm胦:1OCSL~_B_ yY~w03/`bjMPfTU8=?H"tՠ5W?SP?=ϻԉ&}3i#`W𾝉aW)bG h{xc8&Oj*Rq}GsZ* :Lp1\8?PBTTctM@jۮzVmsMisnhn3t݇7B!Y_&T͟O/1.[,P9V#%JaaBXr**kh##eųl@BO ;X"[K(hx6}Oh +W `{Tj5]6 0N~G ̽WPn.58{P``.[` -1ۚ6j$;VNSJ߈ɕ[ӏmXǟkX',Zh9o"vsR;Q ՝Op7l) ^PѮAG< :[o x8 M)B|[1DΛ^\3UI[ъMF\ B=e)*%TRՠB<S]N9V. y3*uLkR' -%_c0};lvhV%5[qlN-/VJpKQ]J 1\C_/B##u`nA68ԷN]p^y&l(4ШPZs5m@͠ќCg?&fFji4wlf7TlUU"ݭŶ@ho M^_N<_۫eu ρ8ZB!'3[hw;eA:$zeHjIq\9soy♗0U)S(;ϊ]PyWS VX1$m8MK,/V"^=.)hngZl21`VAK٘3KjzL3`m;hݧ?WoƻcGiw<ؔ#TۭA{ P~)נ*P_6x쌩ND"cH_ Pfqn]I{b~؈e;R^\MW;H>"vDb ѷ@`O z甚fXӉWlwQQoIr^1QpvQEYk)N1[v0 Mp|L܄S,&FUW7_Ge}G BS}g}za{ξ=B~ WTw>z*m!_Pnj 5F*'O\Eog#>h?Ϻ1ۼMލN4㉻h('QS vVǩe}5B@J(6-B* ϥčW*3@Gy#O-TU+1]S!?jڶ+kZ:l.!7QAhq^#~>.W,ϭ'`#kVtޟDEn4W=֚dIB"/ c0RRtfI= =!)f& 3p6_c&İ1/eԉBkk{)߆Yq`5 ( p@[*\C(7n=Ad0΁m0߂wEM-{0VTOƮokڨKX9O+}#'WFoM?*al*a>zGh弉 QKF7Tw>X>$;z=5G3Mx0UcCyNI]qռfe`f<1fG!Gĥh.mPQnRB%UuۜxZP`W5ceZ#:Qo<׵/aʥQߜD9gcC*Qv ϨGXjSePEwh)}1R[WEm1Ǝ8t$4;}0=s 51n=w͔'G]ݷS?418ܪ NYPn:%mH}xJ<_֎w(B!8Xn6(zAҔs?tOҩuee8xJυ\"=뛺ؕy&غuo#lKh=cG:27ix>6|3.'qo,.Mԯ2QhQ3{jTKۀAA391$~LW͌&iP3بh94E"g*ŏٳ"%*P*Զ^m^`ж*jIjJ.ܺVAEI&y~$$gI sys9γ|s-eۇaqۜ` }oo7~7hh=Sambpx *Ul!BnV]Jw|ݲӊm2.kKh~h+y`ts-4oqE8rl;{1rg$=UY*.%c~tTso#k+ 4k`%ZoLğ c6oeS{gR9i]*;>: s]d 6Px7o`)*DXX IDATMp[wl8y:_qCo {Ъob+;:&a'=`+6uF5wqN@5C sq<0͑X6?E>`Zj`qF5dʎRgcٺޡX _Ƈ޳uy` M}a-FzߖZ}oj6D/۷-v=9uo<9Ӌ6O=m=T85:˫6`=L2"a.oIZ!?0чxj=kF^-2 M>5\-QʜW0[=L޽*>K֑w_"QoiY\Kp8 G/+Jkt2ĸʟc @cO<@=e\p^Yq//"oOhO Fms4[GtB[;#keeXR;%h{υ6aLpWsµ䟦 bSaq=n=f6/fգ} 3E2=x^|ќȔ}p3luO|m<\}2^}FZlaKM mh/՛dȘCJT+AA I&а%ɕBeS/OSsţïc P@˺07sUz]'O^O:4Ga3}R*.E!!DenJM|ܽ<6y/ '8Os1wogRHWӕJWMw`yR c^d\h63SaɺUQzU{oR3 ׾bhe=bxG!J[JeثBsE]g,͆8&ܴ }&!ugg#Sg3~s'-/<8C7+kQQ(tn3oK&H{RȑyvXJbEc)3B߆ 20G, oPKw0[K{BOg6nw;y}^w Oݱx0]6fdNw:9:{_ LS/~z($򮾖S}(+i!J''g_C59Hw]mc򹚅e)T\B]dve)LIDNg"@wʸPֲ8w0ch _+ifekH{!"^28B19Qʭ8[Ì./0o{t݁ף'M\ =+mQa^=sx_FB!5=zz!D y:c:-(UPTvB(B!X+ZCW g B!!CY!B!2+!H(aI[mh%O߬HB!5dB-ʺ' q?i6M5%jU!gwy:fY:-`<.qw:V%?&Uq\;o]J`;2ۖљJʁq8 [0byorh:Vs|OWl؂ir`m|\ފy/WPg6XS)erSԃB˯e<ٰ;Xi{B.? ]7M:ot ٳ߁n:a;XUx X8e;4?x0Q9;pC'h0,BYP=9&-:ϾpכJlב80|wè8O\~cʼn-BU}J߻Sr rަp}?o?F Bfd6p'_(.'ѥ [^J;XǏn<"0:.L^k:{_ LKوU7;VMO{>GayLp A-1Zܵ:q܂mt䏉Gb=Wf5OA`7wM=Zf-pVpsiYrΚN{ 29||'ѻ=C~8D܈%ið1Gb-tn=Ig5/yC%ࣰX>v=vPcC(Vvʯp0K:߀wbq?5x^FaxǑy8_[;)g$#aCaz%InCi=1.3 ų)b ]NfB!D`ZZƟBnԎ[]GwWNj,1ӈwQSɟY硥"-Ʊ鷴5DkP (Zt^ E| _# r<\_U ףef1(fu3Ԉxm.Bь vO hjgБ}b@Ao<1V6,"udBʶD @K+<Ū@enB!s&ʅehSC8ukڅ4X; ׹[}[#c[>J|Vؾst-ۉ"b9eku 79ӂ:j yOGj5$Nra[fHyvzc4`z ; u/ 5ݮ͓(;{7<Ϣ~4yG8…|OIq2mt鉸mVL{ B!DOW]6;%wu 8d?b/T316#NVd뼿e qc^3cmV??qlrxӿ*9F;I<_Ns=KM 5Ъ$YvM9y%c <1u V eaDw-lݍKu0,.{ u{ ݀{m2ж)gDH[`Z2"ݿ Blȣ;mz =?ZF]~{4<{46fЄ&"u 5HGs<&fz0E݋ci0 \g#k+ TmF+StPӱF]#oƗ7{'Emsգa-9 `Zj`qF5dʎRgcٺޡX _Ƈ޳uy` M}a-FzߖZ}oj6Te]Ӯ'a<TxwzFR,ܨB!)= pT>,EXG:0I)K 8wa8m@-5p F{_eq-Y+V ޳(TD3Vhh}p] <7 .#%Zo?{yy3$z(`6G /f hvܓ"T[ukrzvq c+]J~U~js!2ף+q k?M>&P#*ܳnabV=ٗ0:~{ d9߶#rMƼw b#lp[] Q!}R*.ʾ̺$K_,oMzA,Ԧo)rjBMX׶g+[(mt'bE=KsUT^՛~*+B"ZrE{z"kwk/Pr#vLyxZQ\[Y+:iK^ٽ Po zj۪BE\_ 쯬XGFYA̺͘r/ !DZ2D[6,Am 7+ dHzY˲] !^?;uJeX֩ɐpe(ދ{x(W:rA=B!~ Goh\I5+]CCeWGڇiS{maL)Kq%9'<N%!bؿnckYFa;Gµzl 8'8~o]I•lzhݹl%%􌖹 EiE*'Q*֊B!n!/yW _B9k#\|5NEgMa ]J.[p?1Dpl?[>j%B)^ oA8ξܟ7JL#~R{!3a2m{8/uT`H[v,Ys'AYrt U{#ۅBo:.Մߑb/|ˆy7L4iԸK9weĤro#{j|<>~)cQXbSMpx]~YK):x\Zxp3b:^(~8'% hwxPAy%iGb;7s b%0lgyX ]zOeYM Zc=(,0]OԱ+?pð+\'Lǒ7ݦbyXhǏC ޷עQq_xWN{g$#aCaz%InCi= i6 i$/W@9BX;}è)ϬR`[ZlUĢv ;Ꚃ㘡o'd dzG g2&j W ΩjP z2e4pӤϙ3/PEQo4K!b =t3؄:t(4Ĩۃi%?ifc'9X+zxW픫q">W[b-K8TnME ״ f{tPFM#o/1a4j>g^Mp41Fa4ups+y;\XM"FRaaɔ,'* bo-0k(=8\'G7;Fk(ewG5lfLO~^ ͇}HgBYz< (0F?Æ@6pN'pѕuAeɠWwu07j$h9$-BU̡(WM 9㰺ֵvƧ+ llxAlG,1i8wvuY7NF2ġ>q}'V }_ZQEO:r8lAہ-p=">O7nBs5t2V@`r#:kI:>hvV^j]3n'Qv=&<{;.3<|ҽuMi[qҥקBԓlz]-Xc*}v5EJ[B $g\%E0z;BLhOlv=w6nжa |Sh۔޳ o3U^AUWR$TIsc_oJǭ\uV YE~z !YD=iKh~ ݀2ۻY܋/鏷h6 '7kmY IDATs5HGk&}L`V=p ?j`APNǫGba=V c:V2sDc:hгk$Sv 2f#xM|Nz40aPZ'Q6 lub<.|ƞUn@'ŬBۦL=)mlJE pqI9k VW>H֪B!RѱKQwf OBwo8܄M8ϸ[V۰1矸7t.Wÿ@Uc}%U!ZW&P#*ܳnabV=ٗ0:)>>*~n5ޅP.틋bqxA"][D ڒ+WDB! ueb;gu `W~C$g?lBoYJ-?&=вZnUE?)5͠554_FD \Hӛ.IɖOq_?#ori?qCxm.=k |>x&ܟ3x&hոoHXq=)#=6%W]ɣio6~/F\Gq“GČX^0=msR;g"^Rj6$~ZYrz͎A!:z #B joɍO40f>;F'ﻈ w?c[E>zAx׭}0WxC:5-Ԁ*bŲޯU'UO+T ,adB&BYțu2OGW\Ąݯ|]9ԱϘ~ʬatrm'kwU OUS *J,닞%zv~s {SdSBRBk!CY! =[a f:cFzךZG=ʹcwɗ#ٺ|eE'SzE2B&SHO MH y"/PX*OPr !^z!ĀcӬs1Қψ|p׹1SxuG]v+v= ~8~x"Ni[J-AY[pPRt/Vե}|EJVtO(B C7l6hF7qy1\z۹znWkOW'Or1fJw{ݖ$<ɩ'b?K(X2 _&P*'zE~pϲ-T85:[_3Vɞ\&L-(Kx$`*2zQ(4A3Do}'Q[z=wRT&|n')|Bl)`g?yh362Z@ eQe[FsOxl1/(G9s?_?sMWGp~>X?Vjvl`_`a|ʣljY2RTTIv+BTC_(pXr&yמGj0<(j/Ѳ^w q/TimǨ%Bx9^jeC<牳v8pf Zފ~ʈ榽}WkcY\wsxvju2Ǐ ~Kĭ9r ߿JvuOee܎"k%=}CS?L^.ꊛB!DWJeX./U8lр{<ùN_Dcv59G!P\;r15[Qtp<[To$w_̟14ΉS>~RȑyvX`+;'qX^(G ްL;߮^1yzb{=++s&3 !@ :.ԄߑۅQ?旅i}:꺎E?u+_~U7q7}SO{KA %{;f^`q[~n v|G > <p3bku&Zb;r'5s꽴}Nt8ҡvաLפr7桴j%b2u舿HOa)ʡАЎz:(4ծǔ^łU!ȪwQSɟY硥"-Ʊ鷴5|ڸGcԤ 'k0a;] B*2 Kc PX/l:.rߋ&8.;`2 Kx[Vy MxUax>9݀Z4zfܓsG[PEX ;\fc W ΩjP30짝Eq_qX5{|_7_$2B@WU}߫&g>|}3d]R ͻjp̰p;VXQav P$5TCEf%B'W 6a6Cc=-V,AM0.s/`b a-qO^h5_=Ge>Doy9ǡ^sM~Oѹp~wngc9ӓ! \@ܚ lcgު_:a4 Sq?󟿥#U_y\]^F\TN^xs̎~3~V| G*-aבMn[VFri\a29BPAr Fǫ !%M ۫CA]8uQw`IKUCϖ#g(W?܉سq7A?jGVcg`n[U:Ն:)tS긮zON,G9?(mXAk԰rk|6Buۇnrih[+3<|OW؎Xcp([GiȰͶ4) K2|I L7/EJ68Seu'fNB!`1tu}?4Goױ:%[Ny 7HOl f .f=0JP%O=B 1{tJe%]wr_7̠lv|(g[b/a3Q`Ϛ"? SQs 7}oX <K }6Q-?&غ/1H,J,-`,l㊠zG6Dz{bO358nh wv5Yq..qתe2zG0z@J!B!zCD^@!Px7o`)*DXMp.G+,c7ڽ1bsm߫ GΗ?^FK ׳+q7FOESx"f^Itk5O FmsGl/g+qy NsP9 ;u#Gwh n~;N7XGx%N.-@ZFW7qdN".299u~rbhHs&|nh>ޢR!ȺC7;Sfn S,VMuGQ ،/D9  #E|1{JVήzIf.Yዪ{*3)%B?Ēe9ff q?7ն C~7,bcf{̓8 B?2tS#z*a'D^y풀P!DL1reVO'2QV*7b޻YT ]:dT9Ui{^аӺCG0 !,kM6&b*ELcW薲oS2.wY3ɡ?B!D:z #qB1hIC(Т e "*=_,e /<+tP5]IAs@iɠWu_`|g"bilCыֿfu͇KpFߊǧe=/͜hxYCYj=XD{% rX^BaI|B_J,e KGeQfYTmVZ4!zBeɮ3]x](z:-UI{m؁f})Q,~A!D bJ@SӖ`|)ԻlNT zq B!7 "МzJ?ugNv;תSO_r$_B%B$)2%M욃{! != !km1袭W/R2Ti6&BG_EdC+C O!e !DM^d'Z.S SzU9rB>!B$WPK{z%Ld!J=G0QB[$B:/y{e:/PLDwbHDBeB.$B 7C9PJANΏˆR!+p !  !D @r7I{ITjoݒuPb&ʚ9tٍB!? L یf銍6w[$J/b* u'kt{B$B$%;'+"-H?BƤNޫ~8@ bLXuIr,""s$B^Г) P0orE3tIHb˥ raB(O6CX᡽7tU!6  LuŁ:3YmZ)&+6p{i]g^^!jv D.#z- !- !D'lɺ= iםz]K B BAHJihiHf!"]lW@!R&VlIrZo/͆~3tNIh*'J6@!jvKQ(TĢ-t=" TI.HB0nAx'.R#B" " .ʒ-^̥?H=R\*r`߇BK=!~#a K2J,oiR[.m ۂkB!zBĥ;wR__#??#G2j(Ng5i˲P\rTIMH:ڄR[DԽrbZ+kRfNJB!2G=!DTa~zy֬YtbZA 9={6(s֫Go0R59B xP7lx+ / UBX۵wyN>B!!"rvI'O~<@\.J)LӤ-[oru1{l?lW;L@}Bduʉ@z*cPٽ= *0`s=B < !:ә>}:.+Fɴiػw/717Jd\)e(Jm&PJoOշ-u" B!Dd1!DTzj{u^Æ c\_zIE{_!хBdzB:֯_jmRU6!~׼4UB^@5E!޽.V!  _n777s 7dFHyAL$h`PƝbor׍a/・^糢3jܹZՒ/ lBhhcJʉqPCfK !G"!D?RUUaczٴiS‚ IDAT57 .>e"ʏ _e:O>{1M[#qY GZoez^h 4QTPs'E=V;wK )=xB!%"{뮻z A _O_7x#fYڳOk6`\׬C'sHHxi+M_"G?翼'WtCGQ^An(AQeuvO0=t{)Qz,""U"ロwy^xz\ l|>݋֚ɓ'3|8∘e-4\GcgD>=ヾ:v6 F̹/ws-7bOt\diYԛ,})NC0]x'TN}dB-ABH'r1c N9v͛QXX?~&pL_L@\xd{V'1,tƔZ!V9%܉ Äʘ ԥV daNo !HzBl6cƌa̘1xbz+Z(PAzpSXS< 0 !>Æ+ #ZfP",i1Pg9EoUBbTYm޲L!" 1|>֬Y÷mR{hApPZZL[z+iq*>ٓ˄)#:2<}cW@ ov %bg+uf73Lo !H,"iŊ,_}h?֯_Ouu5ռTV&ѵaͩ?9W㑷7}Ǽxo*IҴF.$mXJQ܀((`&"@Q"LAQ. ۃJiJbi)-mҽdg3ɾL2޾Μ9s̝!k{/qN~pwܲ?܋O&{X P NxT:ٙY@z3mPI+V{:f* u""YV-]??f۷mN 3<Ò%K 9RN7nw\r3"4lxGpvdèFWq㪻,C'~s5ObquWϔ=BJ:z*=χƕ(/4DDdPV{L2ſ=vX B( 9s&w}w-9 K:?`1}7u7}jG28WEocMZ[]MvkM;x""2s=@C7EMHĿ^QQI^AZ!l u)D~YľGf&[nc۬\Dཡ9EDF?Ktfʕ̘oV\qj[;:,\ےhȣAr['""mRVq,^w}7[,k-.<gqFϰ K 'Y|)t'XřRֱbjGqiseٲe\}s1̘1իW|rf͚b!%pG/RCoۘg+r0G,yu`Bբ-""KAOdx翱~2d?_s=_W+'>n AK5z)Mjnty:s z >) G>6ކ=zܐ!C?>ԧhll ##_ySoJ,$X0 \K1y""2(铚 aO̥Bdff*pq%N %u7H[iMdx51d駣ku{ijJj)FDD4tSdM9]Waz$8V-9$9t%SRgCPmΞcJ,.""4;tS*]¥z*D.8.Y(5'""=ABC7kUܱMBdwX֭ǧ輼\<'XV( G>6"ԗy xUZ֫:>-[q;SϠ޼vCo!Q0N[Tࡠ'2Hhf,5OTxug$zYHS銾.{DW%~` !\.^; ^ z""ңz4 ۉh+:^,]!ODdpS,#::1) M"n -0 ""хKLa/-2zZ/ e]F '",K%2ᭉ Z[Y3`Hѡ1)EzJ2W*0xADdPIŅiRSDDRP*49k5`IUKAOD$ Rsܮ/ҍ" 81+nlm(.=r{:sɜ[ED+=TsI$dc y-o?ʰ^%DD=R[h^a )"2(艈$Ijs 2Smc45>M y""MzO@DDz6h_=)ł=I mcLQN*; ꉈ & z""Xp>V*jJlCV,k¢U@2%uTӽCAODD$E܀ݺH*PIX7:H*usMx)DDdSw嬄׋̬;N G{a!eIq z""9*5nEmܩ""H*03Jk T f@S^ "")OAOD$<=SH/:SCZ $SR׽~OAODDd @p}Rn<DD$e z{=)\j+.T  z"") n-ppzZcJr+%EYHjSh"Vh`+)"2)艈 p1+oXQV^=ԥ'"2_3;cf/SCAGAODw7p=֜fcyz׫߳>MAODDd+SI9 z"""-I6 f;`l3+#DDd@Q,9s[,Ғp L z""!{Wm/]i'"2(艈#.ھXLU%=z y"", z"7";Yrrqr,tH=,qwb+rb7bVx*"2)Hhf_ o`U0?c)ܰٿ-2[C6(2} W'يZ cgYE+r H z"6ǖ3<&O;sMU ]޿isw)LJ#ҏkCqa7EDR:۰e31g Cmic9v|+ؕ6_-0!}Zޙ%Z)`i9DHH鉈=u=Yt`]NȆÑ I3gp@\ޙ`^P"7ED=pևi\t'9D)/ut($<=Dׅg34P)y7[vÈuruQcJΥo&ˊ>KL]O^tB]rX`.l*ñ]uƾ@%y~)"20pu&k*Scߜ|۪ycsG#wޱ7:l[{O#1F_atG8EDR>HK?=oYĝR;agx-K 6u*Sa3<͐50زPDڑ`a_`8gU 1I5>ƸӿÍngtu?L ަz_D:Օ7brd} 񶑍)?O9 z"7B8sQ;r7dIu7<8jHWYX`,ַEDRYb MIyZ<6TU-(|Ra"2  z""D:VƆ975 ۞'""$XJ-m=F6ED=TK9XSODD=A"v1TR'""2@qWL:EDQĂ $YR N<KAODDDZ NDd S,Nل H'"""-aypN~ɧֳX=~LAODDdb)P m6xJj1F 7 z"""ؕbn-0)""KzO@DDD)[ xN=D=z"""iE v""taf鉈# z""",ҙ""ү)艈""DAODDNH7TR\17EDQN޿eȣ$Sܯ7– y~EZEDP+.l-ԧ!""mSYջC;ED)艈HpmSmMdS_{%* kgJr)XJMzDDHAODDD.nQ扈 z"""ilb`)̎SjbiEN'""I ؊;s51HS+p5sܷ̒WOOD)艈HXjdcN/Pg&"" z"""Ҿ NaZ]y3ɧܶ)sDDDJ.`Jr)JrF{g^ z"""}m= ^'"""fcgvJ,^<+{Wϥ<L8K'<05)nЛ}"""29w-2Tu<,)"]ױa/{6$| fg3jdH!WtYP%1DDk4azp6`۶lܰ=|!>v6lX(p.eS2 ""z;o'#-gc/6&:8#x8$l=19o.gǎ}PHOq kh n1-""=7ofDEl״tN?-6%|EDDV83*Z8 CgAkDDHfff̝bF"~]Ĵ^~; y8̔F7;TADD'0G/^ N聲 a6~bzJÞ C3vDg̖~]/`~Rna1 KF}nO3nQ M}DGY>bMk# ֿv_7'W2O#*ࣣVws0ll;v6]_,yx"{pxh-|s+6mxgztߞ^ 5tSRJFru5iq՗oW^p&s9'_7v<g}I"2~dP=,!X>!x:[Wn<>HȐ|⌃goC?giC&q#8v%wt}=3fX?lmw81zznnn(I kY1sNϱ]nдw!{CD!pgg c6YH7cq(q(V_%bY翱k?#1o}cccwksC>1gE}}9;&| \9S|1N;XD]dgb҈ƴv dtck9c>qG'C>cTa@t[b,ޤhaKӝ> ۰?I\Wwg?h׳дM!36-B4Q93O1gOyau?xƅ"t;+:o``C7j1?)g/q K9O)TZȰX-""=WX"^ȋ {% +{p)G1}Bpż&:%׷ᴑoy˱~q (F=61COI08c|_~8>}m^~b\9!~`llIٌ4 nܱͻٲF ~̎o%\=.e?>CD+ofP{R 97#|=5ocٟWp`|h?Dqpϯn/[l|aP7}B9t'8|1"hqNks^6 AC7%|hb#_[>w[j؜Ebavto-K{w~YfiȦHJ*#ߟgdL7!:00/;սH2$H&4a?os=,0/p~8µQ|,\v3IƼNky.GNk9yg.ӌk))&}";~{7?e/NWOSg|{7nO7}_ew$C9hB@UdfQl%gaE\DDU1C7n2/9Bw^y`#:y#쿟^j-gɂGfв_?崙5)G dMI)i;;ܸvq-OKglY\KаmWv 2ask!RO).PX)p,ƔQ0@5V=z"=7mc=.bs6fY_[{ke<1܏Sr:'^9cp`MI9Q}(ѝC9ѧ%vD7Kg8 s0,'DD~HEWDce#9CpAw 8 o v uzDqT7Y=A 9c !+Pndk%Nv tFoNi8sȌ^?]Z^6 u&5SЀ;Ye Tg\,5`s)(.T ?eb&r6cK! a!--P(P(DZZ&dt \vȘP~!=C!$JKKc72uz_DDDo}' IDAT CQEJrp6""?t9'瓑>mU3"1me=s;^c^}Eg""" )Yb).L!/a}>  ;#n->G64$S㣧}<٧!""ҿ0JI""[YYY̜u3gq{8[YSiOD$^]DDDD#YCcQI$=~:6e^ kfԨ>B%;:PL0OJDdы4azp6`۶lܰ=|!>~͚47@yz;o'#-gc/N} `U$}m߶4#Gرѣ56L e۴H`͛nk-% /6/0_`3ulٴ))+"" 3K Y}3ޢUtf""̌3B 1HޯKb#oG9>05#"""mPXCy,2 zHĽfc.0N 7_^6Ĵlvp+Kƅ7,xҩrioQg!;hㅊ_kظs?`9Dι˜3ksm,{˒>*s_9Ë7ظ7Ĩi'q~0-zDDDZWbz,5s"!Rjԫ'"JG{lz5"du rZ9n<右[{=|#1=Ulr :p~Raw}cfѓM|kB(ng []Ww#w &.&eYLb~"""҆ '4LAohZ}H `m$BP l'1嚬ÎÜS,'$]dؔ h?xa^=I5M&#pȎs9pc0/WGinږ11 ZIMN2%uP:m=̥Zm*DDR[`1H jT H$:?F"$5Gcb[bHH?6B=FA1nb49sH{TpqRHsc/۱P(Sf! $bvju‚+-e%tB""HG/ڻd##]ɪ B#e|:wvCӸq~˧0q3;x:к `׭ 6G-}c^Cs0N>3=3paȘ^lykӮn`uI {yS4P8j LfE$ul$;wbɲ^VEmq_G/va}Ͱ6Ⴣ)3OSgS9 [Y R*r!C9z,6?|N?b{aסn2& /umV~ķ0c#`CXtMeP\h!йmȅ8{'")%P^z C {f9h.Wt3{/n251kL./l{f2F'aGyt!b_*2'p¥3)#}u?_~d3dG>ΰNZ,yG_z8Ca y|=0"~)""eEXZJqH2==s,}|\~S$iݿ`yBiiL!Cc >~Y}p25:٧+܊H26 =wMTC1njaMUvRI-kS= (+dqM; pz^)g_PkzQ+5hHBi Kvۆ:l_~TSe.v{DDREM$Nd93LZcc1A``ϕQIEX u%lLފHjWz9ynhׯΉxc4q{0&5nD{Tգ'""aV n/RqbR < 4oODRffa#.™hO`2(~xm ELkT,0}VfkHOG\l8Ph󃡈̡Y,߄B&c{ 1B!1  HKK#"„..^"Js/g(]iii,_SNHJi݄gCt~Οwܜx_vJHoYL1`[2~ߞ"3m?IA rsݞ d_ ""I ;#n->G64$S㣧}<٧!""r=vPvVd3:ա~". {"ҿodee1s̜uDGDDDOᙁ;gg.ְD(]ҧ("ewI-,*ɡwK{H."""" EXyPX,R K8=}va rVKTJDKAODDDĭ穤D (+Y8;ثWaWD$4tSDDDUvVq嶞%Na^>_OODDAODDDe^* PnY`y{b~udޤ'"""`K8/:D adSnfM܂;?JP$艈t(xӜuS8u x}~"2)艈R!Ͽmȋߋ)^vz0SYNbbc[, \˚)Ο+]+/pBW= KRnQۋ)]s5lg|a/nȣZ9D;4tSDDD& c_ Ѻ{>%PGya 9$PPN2艈'܅޵c 8m?sSC*L!"2GODFd'+t\)tmODj6c;|m.aC1>&Yc(55μ ;Ua0%TRKC?й;4zEHhf_ o`U0?c)ܰ]ٿy<ΥV\>!0۹)/pzE y\lE-亽xsYpԸ/Q;m'28(Hk[S<8νRr7=V5t~ODYjZ\wVی5i-ǎov;NODܕ;|3SI- tz,-2-rB"8s*>Wb Þww_ 4*I'".]6ii#7v7vx""`]x}s_ylyx)\ aɔP8+yVaU==0 yʡ `'ž)DM$IHoo'X%JB'ؕdC P6L 00Ru8^!Nht]<žZo^;^DKv!^jp{- YĹ%* ShJ@ya UaC^>120 SQ;`X BVa-V_ 6BgQ'/8=YW΢ʮŗ|1T k(55~+]nEU(_ ]{s[%ns wa|oݡ-¢[,Zł+m㕑8cT_? ^{ooc_띳])?tYXF;?ys?fgscYcV\lfc_{"ӬϽ2l{ۃsm_wD YgWI-ky~G,M!^K`rY'2iKcC/fK'x=wg&YI3Μ)nX 2[ ~0iNr{q Vㆼ9{| V]Dd1u؊J{Bιմ8nѐqz [[_ƫ 15m^`&:~sƷOgHp6c?0& QytOiS'(+D9#U^GOSNm:M1`$ xg?$X ȷ^S?<Z?Hbc#ǘ&ι Zs075P^,Vy NwӜ5#'0ns<4']8ؾsb)s}ߤ{$nM)%nM{ԟT&汇g;;""I0rdt7:[=j(4nՇq_]q\RrǍI{wB>3y|LG9@tm";yYWذ/gQS`z&U#tm>z!e~$ҭSQ#D3=#5GODDDDD$(艈='"""""bDDDDDDR<}y\R9;@- .߯s/6ݦ "1 z"""24W1BC9Ԭ~'%핕39%<~E'''")b`WfQsLCDP>p¯]4!Kʳ61~r3ظF+_ÂIDAT#G)| _k-;^"nRj5ÐQL>^7I.R>`9q7`_0gIQ3Fֻͤ~ʷ́xf/yH+DDDd2nN<+^X,m'S7_)32 |˫xgÏ+a8pgOu>O??]A]L*#s~^9i/ul;>6&qs0|a8]Ǽ21' rr8/[D9=v8T>l.2$v7Cc]-[$?b!G3ys|tMMVz s[YY;V~Fy<wC6蓮=i3oyE1ϝx?S?z KODNAODDDz w=ȶ?:֭\v 41m:gam7~s 9n,_{W}x,~rH=-լ>0Ox&Jifǚjve9M;Z 2iy$jHilZpME%ۿQ۞5H+DDDץtByiyMXMxK9O?q/Ͽk^{LҲƖɺܵ8ع\r}MϷٰ}֬|%wd݁6ANnk^[w'|h)zUG:FDDDDDz](\}՛|\p$Ǟ%#x` {g}~174cO9ݯ?Ȯ&sz>}>Hי~9d$Q} ""jZ _e m@-.. иw\ur>Su Ѽ=i,M,{~?R[y wۣl=9)\&Ȋ>K`vv;rՏASCuDD@C7EDD{nŤ9QJ9oH.oFsMOpJo$= #ft8H4tSDDDDD$(艈='"""""bDDDDDDRHQI1>tCDDDDDDz`x~IENDB`pycorrfit-1.1.7/docs/gallery/Screenshot_Graphics_output.png0000664000372000037200000021033113554642611025064 0ustar travistravis00000000000000PNG  IHDR<e0sBIT|dtEXtSoftwaregnome-screenshot> IDATxy|\y]fh,@ ! & Y$4KӦ[[I1ۦ0I%{҂-'q[!ؒ XbU#̽?Ռ4#`=KXss\ͣ|m\ab@!B?ޯvzΗ []OnlْiRۓ{t&TrʻaerL3sguPd=0;yY? yxֽylڹӜ_[9wfgZɥk=z7%gB!izyGx?B!gʇyURR|J XiX=@˲P9c(Ws|^X(  *( lG!bGЁS LJA< KtMå9G[e{eF34MLBMmrixIAݎG;:vP(B!3}'X0HW.(-B¼6<^Oid/nĉbX˗`BJfp$kq]c"CXl+WVQZVFQa!ccc 9x vۅilgNqFG :EgB! C׼***`˲0MX`[e,3e( j EizeNDnԟ1rqG?Jyy)I"abqTUG.@`'z 0p\S2ai*\!.`1'O!fxn4]} h%2nf۩-ۿy~~==A 3a8%)[PKB!ļ0:#XX21bqUYf,x%Nf4 Kwn h3qD7*((.qG}x@] 㨊ºu7v&v#b+1< PaLd(ti,+/-s08˗/ !94dg1IUÈl2N;[s0,YB?uW 8c# .f͔͆6BOzS /?ώ_ݽoJLB!n鰙ׯ`&ӫ`L{{GOrˊR~ZևB4L$پfBX,j_}R46. %q2~bXFp8D"3AAم=.*O> ?tƂpLJq_χy_偛fmsf#M7P=n7,`ӗzk;WRsS-%=q ]^p}B!8_G0992>E믾{>n3@|$0q5 'gZ(8]쳿y~}ι4aN;Ov~$ۙMJpC "C\K.c@~iwy3/2)"D Xmq[a)1)cݍϊ9SB$aղiGQ< xs+o Wýfť%\Vv+X8bwv;P˲Ek? LI Ø vHDQ u"V4ML^$&xZO&Լ^|)&X`N.;c]f %rvhl xd҆GZcQdo'F B!a U80ݚpQlռҏ}֟<.)].T(jrְ STDzLTU% 3|i:_2::lǞH $۱PUϱOux"َ؈s|z}zS$32ǏW{B( *@,P.KK0_`NWN )8;} zBJ1"ߣB!O6M.?݋Jv qTrs+O =1`!W|^¥x7_rΚ%a!bȻk8p]<1[PTxe1T%61CqT14 Xp!ҩ ZBKɮO"a&L;fxhBPOX\uՕh&V3v0* ' 0Y.{bneٯB$7\#wQNf4u5>zvկG Kw|\(m}RJ\Tc5ut+iɜz9B1(}`]K[SF&Uw1h:ZD岯cF,F4ZdӋ?gVŪ:nUUvLˢE33ڄB__vQg%9)It,JУfYX.U-"'[(_U*?<^2E q3yKxJVQ&KYF j$Hŵzك@xP ]VB!X̰TۍaD"l(397Y ZQP5uxqqnTӴ'l$v-uCGET<^oDӴE$&˖-9c'(3 :i\d|,ba?l0O{kxHJ%B30 /|ǽ+fH$=U\bW,<**6<4#T-NAiP`h4fOHT4  18`pp˃q7EhǞ/mrF~RUURT<˕ZN[#scr:^b FHk}5O"[B!Dv,]/{9Kb0!BwB!SB!oookX!B}_ʖ}B! _7A!Bq^@P!b@P!b@P!b@P!b@P!b@P!b@P!b@P!b@P!b@P!b@P!b@P!b@P!b@P!b@P!b@P!b@P!b@P!b@P!b@P!b@P!b@P!b@P!b@P!b@P!b@P!b@P!b@P!b@P!b@P!b@P!b@P!b@P!b@P!bR5M; B!0M$B!$B!4MC%B!wt !BOvװB!gB!dB!<%cB!))#B1OI (B1Oi.cߺX,ag6B1 . }o5 ]Ǽ5X/kv*_[}NLfz}>=|G>$%,gi.l?7o&/4 ]).&T\Lxe?OR\\ I (qN8sv3,dk'}g랪ϩ31Lfz}>=\Oe|oϒ|'m>4 ]͟?5>>M7݄ O~<76{6pe7]DKYM`gU`)cdfuqB!?4]6 ]37L"}ټ(zl$Kٕ&;HX,n!ęvzg6YvAMa9|;6y=WF;gb,b'`I69Ԑs)PVVF$9۷"ļz`0Mӥ|_ecfH4^J^VB̥X,F$7ߔB%###$I <<_{h01vVәnt(..@Pq4 U>>].qg'41'@Ɂ`VBqҥ|eʱ9q-Lu7V ̧kxrp4Bq8߿]v̓>8ccc{o"@O}_`b,۞җ!|;z:,ێpB!/~gmM7Dmm-K.e׮]9}ꩧصk86r>t=!=8Ńjlە"B!\Y+ݝn%\SO=~tMo\{dLbLL bOINo3LB!M"3طoo@oҶ'Mf{̠/%:u/zcdfB!Odq 3ܶF~1ӯ3+{,&wIq !E;_ŒmdddJPw&{Ő2@d?$ $o٭<'ZOm !DJKK ۶m;۷1NwC081<\7s۷oDz,***蠷{>VZE `ƍw^% O]]lٲNٻw/ꫯl:x ===ttti&VZEkk+6mzm q43 <ǸgUax  gÅ]f9ݻ(SW$ aONN޷B=EQ4cw:;)Rشi7o￟zvM p~^z5###Ni****`ʹfd*++tppmYw㶴ގ(l߾+WRYYɊ+Xj{mC3  ;ƴD xW"Lg>䟩l^:H;,۷gL+))q[n&ZZZVɦ]6>kݎy陨oP^^NMM }mX{{y]QQASS󺯯 l8I x$;eK1m)੬2r,L#L9=1J (U[[ / Ұg_t\r1v]ӉcOUvvKk'n6a3t+--'Żu9jډB̖sCz0` Ld'm B-*yn"yn\c SFRLt#K (86l 3Gn.f-b?@pI#)G瞛\;<- dmFB!$#;/|@p;; z\3qT IDATKbԹJǤD1}Y;#(B$+1aW"ny.2 B! q{`Ӎ2;dcB&:s T7ˑKNV2MTsj<v -`bHL1fĝB!mΡc3f 1 cýH`jVвX2UIثjv D'(B_$C.a]+0Q%E,J{RVgxq'Ǯ8Sl`Le,B!ĹO9tgq]!˕W *ݒ+F, N.@0W%^$s\b,Y,BssXa!XIcB!VN7ui$B"rMO -f?~3 s^Y??}%B%:'lRp_MyA& }6B! LF|i]Ðpq4o.9:Sk0^/lp"<8e'p&׽eɮSav]Bb؁]x`yYG,O#8uttJcc#ζ*innuֱm۶z/}}}g[Nyy9]]]9iiiAUUܞ6n:>z6=r]UUyNү9A=lXaFR]qM# ؁.kxڿ~b1*yybkxa(;K TH:8VCיR<ؓLkޗ 쬣ݪUp4.ݳd ;qW_}UUc}vl@0 6 Nuu5===o7ގ(9{ؾ}󺣣fz{{gu/ kii 6nHKKKϵD$#8ǎ,k8 e5ǔ\[D$#8ǎR;0sz 8LQ2͢E y\<8ȫKPSh\:8Hi$¯/&SÜ*x-Uf&ײtYY]]z .b3NgUv0|BK헖2hׇ(\uU9̚kjjBQؼy3455pB_zI IenedJGF%0s;{#u(.72uGB{{2JJJ9OvA2޽UV8YF*++fxx  vޝZSYyfZ[[q2?\f ;v젶rvؑڧlMUU|_j,h|KUUE>]w}vM p&۬^:#96v3r9\`v1(f|Gy2T3O_tSe5\94kX25Lo x|>^" ('ߋjYDXd.I!su/v}AAO}!ٔfLY`9߹s'MMMVZEgg󺫫Z===e,jժUS())z;wƦM,Y6S8&˝{Lnoo@ @[[۔l[y6)6l`Ν(wIWWW։jY|ڜ;OիWg 7mĦMزeӯ E* 皢8+dSeEy=|}o[tM B?, X 8 lo/ EW]ŕG/_|c\uyEЀ/]w8k6|32ntUVOjO8\TK#M#xӾ}q%`d0b#rM,vOr{y$BC"L-}8Jb~Yv-@l߆H@48##X`D5Dr`— &.M<z& 3 rhʜ1 f.Hffi78S˦^^.lCA!쑮kxصBhffx^: vFQ$k ,k2pa@; p2.ĕeqa,IoJ\^ө !gs3$.-rZKƃYã `ZF`גK!K 53 BvGQwsly(DDw6s08\\L4Ae3aeBqH 8o nF>フ@)8O.GA!ob攃)D8&xd8d:tݖ4"u?pqqAk J[ xZB!8$+uLK7 [?P Be| 2E&D"l]#n, .Y6Օ 9Q$%ׄP2bu '[ NG#~E=vy!BΕ/c+pbQ$paYgO^~r1w o*l{!BG2I_y9쵅haA>%qIM߫x'>Ui'~4J P!A<袉ѾbK?h~:Gװۍ7ǓHpi4L&'w O#z}OVVrՑ#С)a( a**7gL񎎢'.]ʅ \'z#ؓ;qΙ&s]p!&i2pjh.Z>@jČ"Ez;ѓdoay쯴^Ĩʾ"}.xRg4k&vã [qU%ict HPS}M#i@rs*#hY\v$Y f.=yrʸ0Dx;#3/2ugJ8` 6>QNa8Œ)cP}>&-lX{XC'M0 G^7`>Lח/ un\fv L.&gwR3ɀcO{/c\06ƽO %`||ʪ")/gvmqhV*Y`vQcgi -eC6P*"kyo??q`XWx !ЀO]]r]wԄl޼9g֭ϹUUyQ__Occ#hmmeݺutuu\!iWVFy(DIrl˕uU~čɢP( *?|_]p c,'mbwaGN1]]Gq洵QYYW_MEE;vn燐/g\9oܸʜ[ZZϹ?_MMMvm455e\Ei&P~k q.@4 \)* Z]@@w^yekDuͲ2VII̊b׫~_8g%↎'.Y '@pl{ $F$ξGWtO^u0{/w^M*\==1J#LByׯ`x^%<[ouӆLDz.49<Fw(>?o޷(cQXrpGi)OVTbr4؁`*+ YkxW;F8E,4$;ibB0dA__vMQؼy3մd(++cxxqFgg';v젺Nhkkcլ]}k,\={p뭷fuXp!j:::룽7:_{{;{eǎcQVVۧ\{ux;om۶9?صk7tӬv1|Yʸ1'ӕkr3%,8WUL OLLR~QeeY>v51fA?/`W}\H=PjSF!EYlᨮu" dHFnmu(z:7VuXس%J񅍉Mugұ1^+-?v;}7gqZ _h"{ׯ265529dZ[[ٹs'WogUUӵ<88HUUU***zk֬ZqUWW3<<<óuO q:/(EEE۷o^wuqڵS~XwS&W@~th7[&Nr8U7PO# - >s/QR5o/}8rN:%6F0W)TFpA8qJEiK<!khhǙ$RWWGOOׯ`ժUlڴM6e˖_-ˢdHcY---?uuuhkk8 "{zz(++rڞvkk+C88Kٛͱ7t^{-jfI W;6t )MT׳N1q298i].g%z"5@j1U$A*>?dpX8qؐW-;c,(:k/:q&qEq)!^EED5kP[[tnذUVQ__OssٵOw:::# fwɬ_:bرfvAccc}nذZ[[ioowJշ IDATtvvhooR蠿|@ @IIi}BiB###Sƍy{{_tfHd7h%,+=ǎev,=:)3Le/;q} :ǧ2eN$U-@CQ880 Pzx|bW'PL B̐L[<`Y:y0LYQ:K̹g<*Ԥ\n:圵kHφ hllkb&d l2v|}L{KG}G}hi;/Kwތm^z鬎}';X{ٴiSw(W^Κ~e F_/)#F6*v7h.Q]YpzC,:rx9E8qU%*b,˾|<9 ~!YOD`u~~?B!T"S2QN|8x=ہ\gn.RVV6m}A!ҍ7ȍDַ}e X[[Q\{끁-[6\r /v?kNɴag~s_U"ʹ_4FŠS4˅cua=ccGX)|G_Ğ]q;Lf(`y(Ġqa%|/9@~~gm6lpB nvڕ7MnvgvZcصk`S7j@04zm.C>'~k?wpqFATy Qk*g?{Jװd!..k.V$8b$8JR9BAX%LFD>;j`eBhZ<<^KL 8lH2 =}8IT҇>wq p$+II<<+/DD?Xw_EPũGI$CL)/1\⎥!pQH"@xY[ANOQ#-3MS["a`7Mם߁o=#-~W^!s`ǘmDRΜĊx.-gB>D0/ DdKDاBUJ+a1p"=1-*}>Ep<2hPNJ$DJ{7Q, wJKA#}t.q$$fj*һF "a>$D"rB1,z @: F] WNzBO?Q 'k8EBPI7j537C\{t:Ǔ>2Ng 6Ѩ(R&&T x&'yT%D"ڐBГ+>( _!8\Ygp,**pbOбt#1"s6!AN)&'q8u+زD"\H!xظy`)sc5ǁV?N>3K$Dr!%  x??%$!"p",@lvGZ{l :O/d Z33J~q\{y;=@a=z:'ulXF7j1H2~<6 ӟ8{VH$ɕy1=D\IEm#T!=wEn.' E<;,J1WV+^~"E1#^' װ;.H&&4VQ +cljAoEzzP"H$i#0˞'PŞoְ/hՁsg*L17+" LMHvOUѣW6F0PdLW Ą&\\.KDDČN:S]]=WUUa00 vվL&fYCl6f3&i, LSe2Hixu T\ D "}ʙ“ LAMO}g<- 0M_r2:6ԙqt`hw wbWOGQCLR8["\yfv;6̳Bp8X,m?)҂jKKKinnbMcc#555AC)))vN]]_EFjkk(**l6J^^^# ?EIb~ #B.T`u,ef&\S\;>PN޳%k{ϙWAcc0\BP"*1TVVλ&|Pv8lڴiU={l{9 Kǣb܌l6j4%ၴ^|?vsHy}kd +u#1gnBp(@`YL^F7χxfwQ uON1~lRRHꦦadqX,z{{)((RSCYvٻwo1UUUdggxOw6M#9nc6ϊ5lصkWPsS @zz:.״?tb۱Z`6l6:)x2d"Q_` EE"ܽ9#X"͓hmV,^xwzZ_F}tQEzQLi3~F MH`U0?prS$1O=~~4D\))k /_v7&&x"!HNqq16r5_&77wy76p80 "immڻw/MMM8N'eeeEԿ=<8t&)LWW`0(,,b$ˏ.1h{_҈122*=&|\11|DD> %oL}f:<qbLnY^7osK xGGN ߽ "DxFFNRw+b퀈1=D0, [|jl6nY]kxl8NV+. ٱcG{l6ikkS۔V픗 Fii)mmmCQ\`II 155Uvtti&rrr4BW_ ,R.1+ЖoB.~us>nª7Wk"G$(QLgZJ.@Qߢ|D n>mvg"V˴=ʑqq '?|1\qq ƒ<>-3ԺlEkH$KbQ˘X,JJJp8Drv{Xu֩׾.mNkkkH 7No@aa!VU=ͦqWv5rQXXɓ_:::HIIX8m67K̭>٭{ShEDd g2;S>FA_ *Dl8=d{R`BTִ4~J'޽zj$z1cLfnB߁G3 H0ǵ:XRǹ"cpѨ('&aB %ɥCөqjbfp~U:M^!A(b0v;& +W4}.K >v9jkk6O>}inn^OooofJ}aUrexx[ >YW 瀯̞0Dᖝϩ$I")1@NS==o$`pmhy("lA0Mkm?"NjBsJhrbgCXEK}Zb`|,W>} 2 7Н) :dD"HD\B&(d!4^V"Fgx B ~|[6^@l}YN JwD-u-|+GK˫k֨}QQ$LNB_s7 8J*GV-G (H$KF{b'Qhm!p_0|-}>|kFF!tpB *Bޯ3l>%q { vb]~1L'<ݏFG?1ACD?79R~| \BDG3C@kJ$Dr%!]~œ-pNޘ QL ,]ғI |-07aT4b8I`!!-P˯/ kb8;^nEHAc"Y*H$KDYm=L1g:0!B@$kNĹ٧}+T!pӗ60&( QFF"H$+) _o,"u.[U!5 'kS:gF0GX" ^,:ij2i#B >|՜Ǵu8&:p藽  \"ޝ q D"H$WR.D{l܅)E"ܦug,3 :TeF!SzGʕ_s紮a໨i#op3Ty`/k p1D2w )))!776Ν;GUU.fl6zbc&''FMMEIՅnr۫+L& NB򨪪Rݲe ---b^-"bv+++ikkqP}AZ"07-иs2@Y_O!"u" Y\VriӈvJU:^ '\ ^W. h *\~멨 --j***vD @]]d---XV@"˥>^N[[[Knn.F@kk+8NN'tg۩ z=fÁbajjJڛld2af<{>IxqY Ps.=-@Yt!\S1τ:s _#,)̞=_V=nhCXI޾V}~xL` sq'H|p:~bd˜%[bOyZ2pi&jw^Ebr0F#!/_WW= `P ~>Heee꓄ak_ Obw68o. o_ٽs>"np%,kE",~, i!v6afRQWEpG"7o5FdCA_v6:-\ v;YYYv5)UUUdggkv;ANKKSyefӈؒ>^CHV+999fzlUnWۃ`0rgϪR&*ScCx<(꓄a-w KO>\[ZE}O $ι!jCTA Tc%p4?> sF!N'hl >QxM' fh*o T25K$aba˖-#55uVkZ nh|vuua4gukwuugZ9tz/%0??_&FU_VVjTt:g"~,på/Co0I Zs=l65O^- kɫ#tr0%Pm fXF"M[E}f Hx}Ѣ4`USu:9K"P AdꢱqɡS+:Ӊjraٿ?;vDEuu5YYYnRSS3ETw^M֭ tTFQC+?[ v"33 x

lB.D|\b t`a$+w1aD7~y&?.$"ybb8Hn7mmmپ}&a)1Lj<áqVWWSWWfφ "k)..T3JLccvVĬnW+e\Rv=***l6cy'g3EggKk>Ix3~cX1w< <Ȍ#Gysx_ٺu+n[!+DaAĺ}!ǸO̘@$4#U>y?k!~kW_掷y”"<_q/sի_"}o$Dri 55jR&w <~8{,Pc<ȑ#G8x 'N,k`X𗫱w _ID#J{훕^Os\r`D甎W:Y!\͟~(H$IB`߾}裏RTTO?=y&[RPP@FFf233ټy3ɼ⋋LXw}s,Q\BDRJ 4<3gx?Va<;_H"vr?,6g1$D"YZ'455Ԭb[cݰaG.lGU37o޼UU[ hyW;Ѿ+Xt8>az22Kh ܶqiA ]H$dqk!800?NAA6l;wvC%''sرy=v9rdN:qGT|[WE^lc:sj =KIa"2ͧNጋc .Jr5YI&g8D"H$MX a5^ĉ 1=/)I{D\s16oެ Ræp˒EL'I8]jq ^DN\ḇtJ{{y[;\B?p.Oƽ<pIB"H$c***xt$''38(xꩧBKJJO!fƍ {nbcc] cFYZ!p={}[Ww1(w{\{v;f.ESD",=// LeAX 63/Gff&{oPQ1p_Q7 mwދ*soSYz ݃sj+i*_ڧoMA_LD7=O⼗X`'hc%D<jַ;Z~5 Bm۶*mFRR!߯^8q{G~zbiqcDuVr ,JȪ}k i00ii#@ɞrXaatEr c3H$d+Onl6ISS"N": +!ܶmZnKKJ&K(K)B{ 72Y-^BICN o<tP ~2X"q83l/шnv/Y6nrrrlAR .J43mz{{^ܿՊ`n}%͆tݭ` #noo`˖-ݟ9l$+!GKK }}}|X--Yx%ZC/uhpcp'r(N<7O2>%4~,b?c,H:'\.L&ǚfv;6Mp8X,M?PWW&'L&f3FOynFFc9%%%8N@HMMMvRgCP#Z[[Ξgwuus)AӅl6mҴͶN9#,EO8O?ӔuB" 7>8] a'i/pw eDjm눒2I8RWWfnJ@-&jv@I{{;dggc!''Guۓf޽!h1fdǯwfSczz:.K;{dggdIroe "]yN٬~V,x}Dj`s$G nyg'ٻI9zĒˀ{? ܻ?qLxT ݈=#$KOWW{!%%%Kt())D N:E^^vjjjhiiQ[^^*X8DۭZZZ8Ѩvխ Kmm-eeeTTT:Dlass=??b@X+wnv?F1N9" ~<33H$p⃈?w!,&$qxK:B~tJdd_"QPD fQVVFGG~;eeejDmm-+V^Ozz:sJ\+b`0+B-ٲe lٲeID^Wp:3|$1;R.zhhhB}(Duu5x ֮|SxvK?q8!H‹ DvHvQrY9D~}/_II\3 _=sf;ѓɉ FGG9}JM6ax C=D}}ZlXVΝ;Gaa!8ֆᠧt5nSWW$n20L X]]M]]l]]]tvvjG^G^OmmZv'T߳i2=ؽ{7)))AFf:pNJJJv@MQVX 4+Wny1hPs$}aUr# b?l*bxx[v5xJN2^xhHٚ0 ?_o;GO wp[uH$F]]TTTPXXd"//u֩*Ţٟ@u.F*++b``Tu㼵6lqf%nN%͓͛y{+I@ &&,V#>#\Hs6'$0.۞%Kh~Q2F#Hn"8 bͦIuH‰$JJ+4p@T&"ŋI>;6(J%D||׸{ft:<L5+#"w0|`t4DC1Ǣ7LH(\]K$j$֭[T#nH.5!Wmű[n|35:-n%Dr5V1Dy͗b;Edl$E{7O$=:aD" +!}QTTĚYwMrYq {mgWג>2B8,H$HX 'x$/J_yg ܊7ĹKx6W#ڃE"H$%bQYY / -v$ .: ࿀e۩D\._XXHII q9p\477cٖHOFww7999l,B gp88NijjRX,zzz\.uNv5SLv:rN`Z1 v\.jq_!X{u$KGX[8z$%%-ӎ$sڷa'=G~ȳ$W2mmmnL&ӌO}}=Q]]MEE555.ٹuuuTTTP\\LNNNelF#pvꨩ^OKK|Zrss1 Z[[glo hEEFJl6c2l3llB"=JQQc׮]˽5d/^iӿ {GKCdqLmm~߮^[,JJJ뜜%ٗbA׫56TߩSvǃNS>Waa!tuulpi&ra6)**xjon@{" l֮L8#G#)){WZ%-4Ύ#0 L>{),mmmI)gvwސcqg1 uZZb6ױ|-ݪ+==˥={VOv_l6F([Vrrr0.n9P^^Nvv6 3l{ dk!xAer!"^Oǣ+}˲cdp8&bbe˖y:6nƒhD\Ǻ\项={h4j,}vslw:uWWFQsݎjСCھX^jkk)++ m!6u$CX{mH$3_^DDr>4̥ݢD$X,MB^^Ac:;;-t:g>yyy455q!sc6"==]X+bˣ`Јl,K9rػw/MMMj2IYYY{pC-daeI{WɑHh&#g$ˎ@qi tvuu;a`᜜:;;5mecsssUqp8dsb4PUN'V˅ngh"4؜{kooWߦFiii}?X:!/H?'N`Æ dddxBPr^DQ?˖EpN^1 D"H.Gy衇fQ\\L]]ƥ(>p.,5\\\I^lnWql3\s\rI@dvttmTqܫ;vh~6۷c6ժ|yyy}HêG@INN@x8r=2N0<<֭[qKV@r>,T .x'0רDv֭[rt;wC[[8jkkپ};KPuJKKS3^C g6UfAֳlٲE֮͜p8v؁ᠣ^Ooooa9r"%%.:;;illbllb``TܠD"HHZʬaB"pc|G IDATD\.˽ 6 DWW d2a61L]]]L&{DDČN:rfSQ窪* rnr(++ !X{u$KGX {1x>s΀sqEUɥ|p'&2 !R=79O10p)%mmmnL&roE Pc֒h`0#\.\.Ԡ멫Ӭ҂jU)())h4RQQ>BEEFJ,--tt:imm%55@6-{ `֑,-a-7l3#_&1ih8J灗cqLmmroBH׫{l>ۍlH}}=V{s X,`ӦM= ^ ~=!A.wـ"l%CX9rM6zj9~8!tww^oذ}uyn)$%Oa@gOS^9;>[mikkS-RL!- YDn[#QUUEvvnc0봴Z`6l6jjja;|:_&77Wlhl6999@sRRRݪ导lԽ)d6Ͼ}x`֑,a-kjjG V+ݚ4'''6a"z  \X<?|DZ˵Qd8 kDqqtժOm6YYYl6, F1SSSUK|pK|Ǻ\}vʡCԶj@ꢣCl4U\̞={u+++2#??b5q6lu$OX9|r۶m8p a4mCCC͛udQ~H4Dm8\::Ix`XS(,,Ĥc(//Wcd^׈9Cel>B]]p84@vNz=9yyy455q!U+jSSөIQęo%1o-gHc4g}󒒒SfkBHHVeQ`&0f v yܜ$| @qqcXزezmٸg]c᜜:;;5mmcsssgBrrr4-4TWWft:ZUqرCcX4nPsv;]]]qJz=倰QZZޯ6uلf=B֑,a-+**عs'ddd0kZBeƍ{q9vCCCs_I$0 逧|xtD}yOF2^@=nC=4=//bTbQˋX,JJJp83D/ u kWl6*fUjlqszGfffȱ.ι>^|Ő"pp}q}Y%`<8Xgar0|SX<[Hb)++ nu\t:5nMө^\rssl^ & Ʉ`ph,թknn^Ooo*XvjSR?;p83,z~FH9FQ-ioo'%%ݮ\.۷rJu.:;;55"=ؾZg1yxeՁ}aUrc9qǎ#99~~aGyD'~~pQ*++gan݊햮d%aBnϰ"=qۀiH$ 5C&]BR!R3  q?pY J$eBuְD"I4Ђ7U^@'\H$eeHp/@\9z\; [`ngH$jF A2_LN,oYI$2A A2e "ߙ@ߜ< kcD"lBP"Lqxr:( \ J*z@ƛT+8)DضmXnT+8?sBM<xn|?y>~_ڪ9=k T ?c"\.~SSӨf466NZZ5kx馛y;w؈<3C^gǎ۷t\.WIJt555455t:;'V ׽blݺ50>|xXs`),,f녗s:,Z(bd qZ3\o(B)Z]569=S(&[ޮ*"|8NN'[lrXjk׮%==u zF֭[ڵkYj+9K.eZ 6 zPVYYIuu^&\SS;v  _۶mCUa΅n:VZEyy9|Yf|r233ٲeEe+ Z2X %Yfi&|xsYۻw/<򈾽h"v19;v젰0M6( ;wyNuuu;'ŋG[|9Wwff&7xyf!B۱cvIIrxW8/_~Z'2v `Cn+0̙3illd߾}R]]Mfff̘#h֬Y̙3oll[cvu.]7|S޻w/f:YYY\VjmmXnaoX1dff:u.]t}:`_Vњ}1pӅب_ ##CoѬW^y"6I}2pW*jHZ0c&-Zoo޼DpΝ=Q mҥ,]TO̤ ȶmXv-zpA FTRRByy9ц b&};wm۶!ӷŬ3r}i&z!RSSY|DaƌsN/_ WeeLP}_dÆ ̘1}x^쵷G=466R]]oMA!ƑO oy;G^N7Ok2s91ر#"[p!-Ht‰Kٳjjj뮻ضm ,஻q_C%M)lf̘^:iiizmKcc#;wܥKzj֮]buξ}p:TWWrhllg{>a3OoBHBo/F$[$b2)N;>fKWiO(p/L1v;__p![.+uַ֭o]wݥo}n%V3Nii)iWVVhmlldŃ++9}llld˖-JAK }rhѢq?8ΨeWyyy p 5hMkjj8ךh$b~h6]ߛKOI[x 6-tZH\.}Go{a(իy>QQ.$999Iҽ˺uillo8͚5C^W^\˗ӷ֊/X1Z*9aMMMd:H"(b9s>ok^kk+3fп_qyEddnC|T~6x䳊 12[n#~6M8?"(8Wb6Tޛ<{'-;Uos%%`x+Ƶ˗_UJ.䵄ȤEPqZ+>M_i$+Lz#r'B l`.pdr:z~4m'_hK !$bY $uYY,,mACKfy_(B!'I`yyClc7\Fu57-R!$BL0 ?4@ Gץv1M% WB!.*IC I|oN#^:! ! 1AeL_/hy"`}'l\"BqI"(,׀@_CB!$BLp_|kXB1I"(wprID֩p8D@U]|BqI"(Whk ugxpnT5^ !H$BڇƣT-;x`M`ұcSlB!.IoRJEϯso5F0B\$ !tUG V/Alm8LUBqI"(9p l c 5٨?Qx'N !$BDw Lg玽 Rvd\"BqI"( mnV7K%B\$BD5ƣڷ0aXF!.OxpS__O~~> Xbev;---x^wX/D7kT+W+| ,r3n?7oLEESU{c^KEE+VfQYY9w D|E[v.,a~;4cB5.ANrr]TTݻGT^xA_\\nxA qHAK-hSK(`8)be"p8HMMշSRRQR|Z .^B\b>6sFL6-T_%R6.l6[>;}԰f͚ 6P@# 1=my h#oOo^z`u|CB}jn7O?t#q<TUUBKKKd~꘶ `!F!I"(`&ˀ'Rxev9jǀ'5nQ !DPqA}m>N7٣ArcB!4 !.2@_I2ie7b;H_o8)BA!$ `X2?N/T!$B\ˀẖE&9")^A !'Pz 2O)ӍfHB-I,9]6(Z2۪"ҿ1~0b’DPqQn/XZ?~4zPHbBJA!Ę6Ҹc$A[(BLDx ۳Yӝ`deB Y#5: =1 !x'-B1uRB q%@࿍:CY}D!..Icn)P>\[@s0}%`?K,ɠB\< !" xa4—,;Kr$2ZW:<PbܒDP7ih,rwթlb_}#>cB1^I"(/ׁ>e#HmmNF?pcB1I"($g߳@_+O_qV!I o`fX ;3ZrBQ<42D!·$BKJ(SxE+7I4Ӏ+0w^'ui* [0tÝb1^ !eLA!%kp_JD>9w'|g8S9& !eHA!%iSUX>l}'B\F$B\6Ζ/gd*(P_y(~! !%MA!e v) F+~$c:j^.:+ϪB2O;B\$B\v>6} {n( ڬ=m&XPꇷ|ms Y!.I !.K>?HMse7ÍTcŏPu`#Cib"3;!/ʏCF&koZη-kJޢB1IⲖZ@%G~Aѷ3jHЛ*<l b\PОf#7a0\Y|&̋Wũgq*:6bLIװbܰUhyV3W?'&[F"'l-;KuΏ>)B?!A!ĸb}DQ&tBa Wo}lQ3 S m:!/$B[ `1+SEQz'V>ޛˮ I |7AҀEld'b\36@{7sY۱suz,xrXΓ|_?w|avZzzloI!.yFP1,/%~"ӟ#Լ_F-}́_qWy8…m\w1{NS? (C[O!.u !&5_}}7|9wyn1 | j?fr 20yti|>Rkkq\`L&B\:I!zh_ϽWsRcϴ(Ӧ"7I?lYǍFvSЖ˓t BQ|m^36=!ɪ  F|ާ@ۡE,\m )2h3ǀ?zA`]~!m"X__O}}=444bŊ5U} CzJOI|O鶛h8ɉS9At)NV:P TQ>Bse3cs4mKbHyGݬbڇ2bVbٰ͛a^****FTv$BoIe^Zt@ (vP~PF+ dl6p8nzR;JBnI3'L&p 2GQ&6m9HѸLv;vQQ7n*?k!& 3A3z-|/R(Eo|x1M?NPC5߈D*ҙjyTF*%]X;P̝KI|f"6xۢuK¬{-h Nv`p/R|O荟B`).1Dpz4%%mii5Oa~}Y݅Pn>|?hq$C-!EkŎ;:m@xB 黸rI%dl`:iȰЖVx X`h`FF;lL42B .CAlJn#&-p)&0$0  ,0 RWon$ûi)(P=o9mp / ZB;-QD}i>LݎŋG'*C_t qD5b0&3_3gb9SZֲZzTPςNBj ` N\nA4FPltc8i{  45z~+g1 V1b4-jfEWZU 7A4TBȨ( &U 4 tXBAj ATB+oR(J`[`d$OQ1BTU h@QTTB@@o5 F U%h0@AQCC*&  Zb0 F!z8UQ1*Ѝ5Œh T`FPWojo(Q1*FB`B hZzʪT{_ chnT7VEQ*Oƿql6v$uC5bVD9^ 9p(> : | 5)kazm'@q PUIpu*U&/ޯ>*>>vhk32i~:B rM:}'iv=#Oy~Q݃DNjBd*Y#ʾh݁::9}`~~>GTv$x衇Z[n[ne4 !&+9(IhNy}h-3 pS>ڔ7zp=Zˊ 5m$(ZP  A0H`35GU ((詠jB!J@Q0 FIQPU5 V5Dk22H0C(8kP@QUҠ0=-fE1[ޖ; X RjDW:J|3* =Co_y_/e"XZZJee%KmAAAeF;!BW_?2i\+PB1fy&a^e¿zձz8Gt骯^`K/1{lZZZw8$y M>#ݻdvEJJ eee444pxt***C>kh"JKKq8w | Kii)uuuGIET^7jB}}=UUUv^za]Bvw!C YOH묡A555uBrgQ,#ٳgSVVFeeN5,ؽ{7---444 8yf}BnKUUx^v5|ii)TUUf͚XX4hh9zoEl6Wf͚5UUU#*zzTTTb l6cxB!׸Lv;vQQwQٺ:^x}qq1v-B1e"p8HMMշSRRQR|I}mm-/h!B16.l6[>;}԰f͚ B!D"6 áoHVVV>"B!X1X,xW2ϧ:b_qq޽o<^$p B!̙3yg&t2xQ:XKK 'OBee%[l_ZZJKK%K--vZ~p1Go~3ThGϥZO Ou4pe<ՓtIKt1&|ވZv; ZqF~_G=~zEgPVV?3x*RRRhiiG𩧞bʕ,p8Xvm5-[RSS#>ssPe;ǭVb0XdžSOm:u\K;&率{ix42)ly^hoq~7_~=ׯ{wM61{l}妣 cƍ&u4e׮]\|@l2jkkc^#??K/zjv;kִ֬qD>L&u4:kҽ,B9al6#7l11p@׫OE3tuuE;'O @{{;n{B!u-W୷yޘw͓O>9DO0vvv>k8N:;;裏X,B!8Dos 7>+VYYhK֕k r*ӟe̙C-n"--mB!u^}ǚ5kKNٳgSVVh`YYeee] 6O8?G3D0%%@ @0h4~?g !b|;D^{ݻwp8HIIaɒ%6UL“'ObŨ'OR[[ŋr~P(Dww7ӦMu" x<|>c 8TvX,dϞ=,YDF? !,{nXr%'N'СC1Yl<UUUX/}K~zbxޣӧc0p:ݍdff: ?ki2$|mmm$$$  ٹsք{;v̙3ղM!cZz-΍Ws=R|M}W_՟ٳS2c rrr8s 1j< ٺvIMMxNrGsssDq\ &111栔~ERRҰMCC,[,;Ce޽L<9b5!bq"qFzꩈɐLjZtl6eee<\n  ,зsssq8̝;79]]]$&&8 ȑ#|>N<ɓ{ǘ%$%%I"(b\q"x}ayx@ ɤH<iiidD"ʁhoo'-- ؅X322X,twwH~lbvvuua2p\^KUUѣG4iR3. ٳ9rHD ;p筷vww ]!kR^|EV\9sꨨ@UU^/---8***F%whjj*&IO 'V@wwx={6Fw}K>}ӧOsan7ӦMvGb6lXֈc#v\̘1$:;;.I^^.`05>x gϞ՟i6773ef͚obM4 C0H/˅㡫KYA|>_p!rw^Eb%7oymիYf ?O<ĸ\UDQrrr8}t2DPQ"Zۼ^/6)S N{{;e:::3̀Gaƌb8s~GF)op,8C~?^B!: x ZZZ"V 9y$VzNm6---%)'''jEnn.@s>RRRȠM?O|466r)X,z 9~8_=VUOj 2pW7o%&&SX,:;;L!Iff&III(Bkk~,9r\R666ׇf裏.Nop[[yyỷ'-LOO'99s&߿C(ԩSB!-[oD瞞|>ٳv%11@ sf׮]\{L>=jfٳg:ujԲgϞŋ( H/ !}_~@ /@jj*l6TUe߾}vmQ+ 㩻vY"F԰dɒэ}W '}!<7n׿H‰pD$B)))1CUeLnn.|_}G ;8NEZ#|хnTiiil6cZ$}c;a#9B~~>`E0[jr50eL&6M3X"X\\LKK w聆B>cݿ?|N^.;;ڨZ̙39x TrrrX,9sI& 8p]V㚚9{,gϞeܹ$''zyw0tvvu]8t7x#sV9}4ǏSNQ__'?ɘtKK ӟky=Hvvn={6IIIc}r RRR5k> S.VZ[[|\yQRUgp8x<:uJŋ&NS?++KEFF ^/'N`ʔ)ĶJOO :h2ABB°}Zd RUUh:uԈz)χ(q{2Z`EQ.`4!3Dnh roѢE&آ;y8e[k &%%<9¹D0/7g$.\8`d:;;#o"ؿ.1 N8/^n7N";;,I~>Gb`0EJJ Gĉ|-aW]u:u*ѡPӧO3w\ 9z(>:tUUYp!PWWǴiHLLVo={rQU6j~?`ƌ(tΦ,ijj%K=s ?nrssپ}KjΜ9̝;@ ˮ](,,[x9Baa!555,^p8(++ӟt>|& 3gΤP({UW]|f+tЀd޼y t:y1}tBdff~m&Op:ɓ᪫"-- Omm-;v`ҤI̛7ӧOAOOiӦ޽{X,$''ȡC|ARSSIJJP8pcjՅ" aA|jii͟?٬Bbb"& CKK mmmzȴlzݴr9s&CDd2A   BB!W‰hDQN:lE! 2o<ʙ3gCss3zl᩹jى  L6,}f?Ng㣺΄iB@c0;$. '68vMbfMd_oa$`7m76)c;Nr .$#2q^P/ @󱥙s0syN[cƌ6x<8N=J("99h4uy N'~"477`ִaH0D4~?NvWSSMMM~ ==H$봶 ג'vDzv ׶6t]'77^LxHJJra( U":a_ZZZy;NoFu>hfׯjEQD"D" `nŀ3u/_ʕ+ 3PXXHqq=k.\Ю;}a:mijjqޞ9|AMছn⦛n:-x<ASSHc%N 8NTUxf(<5lmm1Sg|.dZ[[ B~+A]׻*ȑ#w Fѣ;UU;Z3f zӰk?,M}QQQAff&`f?UUVv܉aD"IJJbϞ=aONuu5W]uƍcΝx^GmmcƌAu6m~g׮]3g;>r7pow+>ӧSQQ͔֒)Shhh ''Yfh4|].]w>UUUB!W^y%ƍgdgg3c ^uƏog&Lyؿ?/2^y}v^x hjjb֭~rsscV^DZ[[9pGaً0555̘1\{=jjj?>r)?ӧ#//bBǎ̜9yyym) ߏ?حQDBuuuv 7@$!--SNq\[(Bvv6鴵BQQ尮g̘F[SlN'999̝;DjjjhkkvP(ĉ 466CV;uT"555AN'&M"))Ok1'  ϖ)Sp8bF|Z{Əo^\k߭஽x<"G!55c~4gu0HHH  צ&$''F ðo4 0'UU fa5 0].Z+l߾~oC\,`x |I̙}wIqq1;v;ذa}9d֬Y3tŮ]:]?l؟cr|;sx>KvvSSS x<|>h(njkk,Zp\$&&R__oV )2uzA{{;=l5 v+L0s.mnnq) XÝV :uuu]&+i} ;v *++؅TVVRXXHcc:|&L 0fEP;v,*J+LIIkxxyWHIIs璔W_ͳ>˔)Sxw9}4ǎe9RUUZEo*ӧOnFTUĉ3gkmmy摐… I@I&vyHJJbڴi(yWHLL$77 X3>fNm@~~~~?\s 8@5kn=Mq% ĉ;]6mӦMP ̈́ RwN?{?L-{vQ0n8ƍ̙3y%//_m: 뙏ӝ.\+=SLc$~p糟,`&x|2 iR‰';ܞ?Cnݺ.;L͛;,.++ւŝWYY… 񙾞1~x+PKMMMnW [!ҔZkX">#x3U0Wpi\w\.=< F{L>0̉ȧL¢E}VtN>ݥ677cF}>N3>}:z\wu1n8Nj;Fff&G,8|>-ZiTWWh"QXXΝ;ofĉ\L:PZ[ڭs͘1/ɓ'w[L0n.QBQ.S,W_ͥ^jÒ%K=ok(=55JII9oWk_ s׬ UOA`ae׿1WsVV+VR2ׯ,axUVu >CiJ<۶m#11N5l2{~Ovj>|>:uoC&L~&w7}0?KXDkBCÑHռ=[ h}755uZ0|>\.W@z3s<O` O>MZZZtCKK(cƌ+ƳYYY;$$$ʬYؽ{wo*3]{vE\g5SNСCK=Vvs8B!vٗriqw ٺu+TUUMqqPѭ.Kގ5yd{"5@ΙC7gʁ8yv!kN5,iZsx7$='$$ؓBvvv$PTTg1yd(VQh4gwRSSy*)Hzz:Gt{Q`6tZ.nKpVX !0 );` M6ի)((qHqqqp~Ciy-##i-.wkeawr!*xkhQv襧|XSHvwp̀v7t9:ΚR__mƌ/Ewpʔ)<׺)btR X\\_W;n`QQgǎ8y{eqOdv}QÇIMMijV'vDn5)K/%99ٞgu64M6[g4_ĺoo>3m̘1v9&h)%%)S؋/+''B),4,3^WZٽb6niپ}XdIs 8.Vyyyݻ:{ 'Nu^FFFZ9uuu;vl/Xs=YсKinn̚5k%B bnܹ-[m%%%prS)$0޽{rMMM%//og5g.N]g0 RA!B[x{k֬AQVZŚ5kf]zh ͹ eeeuH2 M!@P!YC*\JdeecaլXSVbp8K{7К#g?@ȫB!ocׯ_ϢEp՝v Ɏ;VB?) Y^d&L0],B18C ׬YC ࡇWR[!ެ,֬YæMXb\}zŹ1AϕW^yVC!=rk`ǯ\*VXAvv6,_|Cu{B!nH`0_du۶ms55,rVjаB!F֐￟+Wf-Z zٶmWbլZ[@1X  L8Qq!!lݺ2\f1=۶my+W3gOаB! )jV.;W(ξa!BC y'ijjbܹ,\-` t:QUB!x )\f]ܞ+dɒN^+gW|@!BC䯨 ;;۷uV*++)((dggx^8N !̐G}G}@ {m6Gž'B1 iBX~~>=/f߾}us;[l5 _HRSSGB!8]BV\ɝwɖ-[Xd W>wy'K,r͛!B1@C^~=رjYpaپ};TVVRXXHVVaor9 ***ʢ;cPǖS]]M0Xw{馛F5~QH?MhtRF]vdlºu())uً/HVV/Dub96  ))); PZZ:lm=#݄QH?M&}4: ){ذa(Κ5kXjVgժUs=CiS-B|y'ۊ(//6i0s\w{|Je˖-\}~\wPhH1}z;O]ԿZmZs)Oo~H9lڴ&{`0ҥK_:cW\ڵkt}8{g!lvtSFD]M2ڶmۨ˗:u---6pױڙ;vv;v 11 PYYɉ'FI> 6[neΜ9SUUE0&7y/U>ǹw<۶mj/^lݻwylUUW;dѢE,Zs\`BEEeMufϞ C _FYee?))iT|8O{QOG,XRɘ^.*((֭[ׯc]42XrwA?ng0d۶m ͞Z ꧋`̺F?]gvɇ*rrrMcG`h4l?]E=LځcVV _.ʌ8R1^yy9ׯ';;*x≳7~ؽ{=5g%%%UQQѨN_9So4ދz[-Y*!??)" Št"7Q;~WGhv[?Iz룬,{bmܐa1(@OoG3駁>QH?MHH (%++<=kͤNoG#7#!j0 IM&}$x#q*//O$&&h^N7[o֎駁>QH?MHtOi#!BqTаB!h%B!(%B!(%B!(%B!(%B!(%B!(%ذaK,a6nȚ5kcGUU-by)--; !FH7@1:\NY sۗ IJJ:+5+W).. EQBAA!93P.B !.x7n&!I2BS^^֭[dŊ,XmӦMdeeQ]]Mff&eeelܸSfqƍTTTP]]͚5kٿ?[neԩ[7qF޽">֯_OEE .4\UU >p0mڰa=n=߬ambt@P1bV^#<ٳ)++GիWSZZJ~~>TUUu;nٲe߿d>1l۶R֬YÒ%K{)--%tR\իW3|JKKARYYIUU@TUUe{ B BlٲN`08|(++5+gF sN:@ @ff'Nϟ@QQk׮swٶm%%%zi/Hii) .dժUvR!KA!ĈYxq S\\Lyy9s\ɎY%~Uq|PeVBvv6---2ւ`0h6eggsSZZj?# !DA!9A>Omf^RRBii)EEEp=1JKKYbEݗ ,//Lb˖-TVVRZZ֭[}B_jXqZ%_k֭(B0⑾\,Ƕ._V{L:6M`.]PP HNN|B !Ή2yZZZؼy3/˗STTʕ+yGxxG|=ݮ]d V3 k֬_|'Nyf;(UTTPYYIqq18pV ={66lGaƍ֮]s;sȸ7VVVV6YƢ";K( =Ps!@)Oo~Hv!*n:̆]_U0dժU-//3 .dr^!ůJ2BOYYY9w`_@ 6 y-b0$#(8TWW~z{E]B1j$B!jdհB!h%B!(%B!(%B!(%B!(%B!(%B!(%B!(%B!(%B!(%B!(%B!((}HC!BC/uO__0fnoڡ?o=DmC=Q tM;.䊜,RURg74PzZZZG" T%9) G5aO @7 TC ccýjx?s!wdzX\vC0yvdz_?6p51w Z OK~oXgW<=<~sփA]7Awc;h3P&4 t]wky珿!ї?ZuƑbaNÉuaq8d$v{[ޑ23ghj?xoqC_bg ;0GAngv#4>L| #[oCrr2J>̂f=9JJO~BRoofrBAyPO!)9oaDA?V+4%Q9϶[w|tPBq~p[aNg˸fZZILk Pzݬ8T|WӅ{\.'EXMぱYDSXǿ??̻ c7l9{gVUq]8t'Z4ʱ?$%%qx-g?#~0PԎL9B1k˻8p4jyeQc"8ܠuqU sz _M⣸*z!/9 ~^}UTb_L{9wA`Mg yL׿u<|yՆpD#cLw|-;7O|7Dh j 懰)hN/~瑛2< 9v׋@UѢ@Q0 ,n4 (h*aʿopT=)Ҝ Ʀְ? |ucȋE"j=.4u( pѦ6,oZq㴅Qm9q zkdkٌSǑ0 cCW\q 8.\AA`]3߃É $M8X}چXQa)T`c;~yBq. :<]:ūz7HIJҬt$pi=l ϑ KfOAUUBm!Ƶ>Kjh.)N8!;gO$y8Mөo b8T3t Pp mLBQ\. ~Ƥp 6b0@4s:x2wSԷł8rdѯyy~hZ ]E5AU3DOF-dqhE2I8h՛Fj+zszۇD"?pArz: `]]JBgʾ.pwH$˖-fmEhmG NT7t7=z=k K*PW !P *6/=5S搀}y~+ocidshΦgex||I~RQ"Ftz%bNL亼/pYP#aN՞j`gl~@4bh:iiDQ8A}ɉ'I :QC<]kv.c_=dx럍-8aﻏ )&`~>"n TrKcT sgj:=bYV1tEUF"75nܰ/d1WY3K : SFlޟSͰdXfAu3s8)Iy]8Mщ*FIL!I H=VB4r);nAqLL+#9Ujs|} ,' oZٗ51 0G̀.:E4ϾȻo+'\;XLBq'䩷c\%ID6VF `.äfdpw˦ઐ_ťó)d}8SͬKT# Eh..ť(Du^Oē+cUѦi`޲|<]7F( :,~x@ϱ+hH{ q:?N/eSh=ɜ:uT"33MmMz?C7F44PcA -+g0{d4@7!aO.vnBqw&np4RSSK0 ۅ! 7qټO(霥Gm?AJq9UWol5al(ʤ9|hG_]?+͈b1Gp7y~ +^έs:bXT TՐ2 Nan1g@ef %2A^TzuE|zQUKKs#NJ3Yx{_<̊ܲtaH]q[ :g1ĀBqW /pb^n;)r^{G=&KFo?I{~GV _V6\ľ".ޯ=lRíŏLB$t=,lM6e: 0:Qľ¸ O8<+z$_-/wZ{Щ^8wo[HB7}ADQPt CUtE ?Sl# btt#Q\:%=ܮJ'ǑûA\N3+{رcyk ѧNSq#=)D 34h$L$jF !կRM:mǿOױtɵ<.  S. _?eNMe/K~Ƅ+ .SQУ:zg?2&l- :V-5+Xs* C֢*Vŗe[tCɎ 7;0&Tt0 %<.ǰiDhj ?7o{ahAD7hQ( bs:C(-st_ůq]&NO:`k֬k׮P5~4]Xyߊ##hjGxuBAW x7̣󥏓75z4%5ViC3ZIJ\fg]Cntp%P c0 j.v|RbD j;T(I>??TUE Sw6? o 4]烣*a/@<\,OUd&yP5{OY]˭Ca=n>9PD#ud}>' brrP΁yӺ녦Ң(l.V@8krӲ2?骫bw =-GUII Ÿ'n0DbXYhTyB\bMWCCc~ & &edsCM]CU/qc|^;~b4sqN =9Cœ@{([ `( --[;V   >8z,n9y񝕂] ECA74Te277U̷߽oJj$Y~$_+ Ǜ5u,$6 @%SQaD5H,3ѢfpZzz:_yg|5~"of>o&:TZOu |I#EY@Ѣju>JUB!.s$Eu|S$D g52gf!!0.ˮ}_#3aHBq~t ؑ301C UUIGCN8)fNbֿ Iz"ѨYOv0茴ŵCNn$'sI3Zu)n#q5;z=$ZìcHlaZQlb7\z͒2|ކ=޳PHprq2 ±L9tUWP:فdT  _ hQUUUyMK!\WKũ7[CQ 3./UP_G `~.@A/h+ifn]jhѨ${d÷^!ePG8ڑ3F%X6 09H(pˬIeO&M:͎D%' 1bXa]YZ=_.$ÖHMIM3Ay-YBUQOcae;=]搭n}bټ4 y<Ϸ43k֥`jCd&*c9|Xgm!4z*.`?LJurJUU0s&@eiL;\O.;^ÕiGckE;jQy8R!0T PЂ8A`hZ&̡Z58)oh=`l^0]!2ka(7)tho{5ζY$';Ԕ䳚w \ӱPknS?&9lgF+Q03 0w 6 (dhb\H}9O0{|Bss3(]w?X9:? L:#GtY3Th9zT3FTޅZ!ąj @H[,+ ?J][$fR9o"0 bqRͿHaqRȟ2'x\4]' )\. C2>;a.Vcs6%vP@5 #c&RC'mI[`ZaΓTb{ f%CzL_RsW̓>ȧ?ix85 ƣ}{]ꪫwunC9;gf;gmFPXB נuA1h(Fn4wHNaxZt( װY v:Jj8'&ryn.0k(bV8H0TLtINL [@:6cP{[ٿ~scXYuyi63'7Nh1usahbfؐY6Fs [We۷~[gY#2228YdvZ:]oD3PTcF)@ǜDk%3J,[dXad c?bX0fV|9Q\Lб{TK,(Ì}g1nz:'OٛfmlӶuqb;g22VCa2EQ̕9i>:Bqv *|ՕyVC>kQKjXnjp ߹Z:K%bj:N5T@pD|cW]5BqI>CT+J3Zf.9U c3AUN lB!~">CuQ`PQ{vat³fiWS4߻¢|JB!LzpH8UH.׾Wvϼð(fA f7ET((zB!"Qvs݂>xJkK9{S#7B!D$e/ftSB1dhX!b@P!b@P!b4Gp3όT;Zl9MeyB],> !J !BR !BR !BR !Ĉi'޺ёnb4@P!F@'Z nb4@P!F^=dnb4@P!FFݛ;8΢"? !Υ.ubT!À)We%IoQ[Ϸ70Nf3 hyo?DSPLÒ}@GQ0 3oЧz?oS,YYzyolns23 cbm{0'"Ԣ)ɻehфq\x%$J@Bq&{Q7|iEIy)x0-܀i7p?/%oPg+?ӂznʇVP?|d2P~~|1`G<̺6,~ `>O?/;cx7Tl&{^%G6&[,O15L4Og"⾉w?4S0!Н:N^~!m-@P!z?ٳ&fCY?>._5x9WL4͟ȇ/?$9&`ɸ%cO^o̜DULPșF;6۸į`4/Ԑr\Y?Ȗߦ+W1Iqoɸ)$䫼lN]WK(G>,v{BHi}oNwvՇ+ (FpL"?QתWDOͼ)# ZsrsVFjq,cq'njKx\$u$)3Fxp$6b !'E1ȱ?Oo.8H1Z:/~xm=|4EED@>ʼnDB\NTw=BZޯ.` BU{f K>u=3'1)o 9qlNv\m?ԸwiWF )"֕m9#⇃ehX!(L7_T}n DγwI6$4'[pIvTMsLt\> ?kglq6(xd36r>uCkZ,d<}ba (8F1:44,B@u5o?4/<˦POp <_?s܁ W 0R*/LtāQN:'_t_O?ᡫ)k'C W>- *B\W{w2 i!%s΢8䜃I"Y "Q `D1DPb@*"^nVvՙU>}B}%jxx0H;c=^W7^+ zEX>a[bwW/  EuHx0~~ Qda @F@^~PPH.0zVH>z'=~E:T"A@Tx05y35V;mΛj`?93<`' |#~l£v0?7<?9_jg2K@َ`|12@b a {.o?|7f 6ݙ c0ss!9> D¿@ X}`W * E !0xLy 8?x ` ?S.# _#elՏFɡQz(MJ((v R@tQ(uO^_!}c՜8;᥎W}G8~Xx|d4.HzIK*'ĬhW,(f^$p?kzDx@{(@>g9ԁ0f8W>m#AHi BPA 8Ns"mp#1S`,e  !@B$@Z!dA> b$ CP- ]BAhz .0bB0G8 D QhA#n#!#5$@")H>RF!}dd.YlEv!); "xQRQ^T2*U:jGQoP˨h"-VC]~8t&݈G?FϠb0 F1b1|L s 3ƬaX6VkbMv GpnP\:ww7aQ񦉧)yH3Cψk4|ߏ_U M-=O{ wAOp'D"QCt#FWot$:i:S:o v14B{K/?_bafg2$3T0\e`Xc$12Z303adc2 32y3e0be&!I$} O!c"dSr 9|!Q1ɯIլ՜ڧuRkJO]VG@[QgVWL7PE^u}5 ÆL冯-+'2ALrz6.)03'ۛDXY|a%dja MZf}o'kd7`O?cAϡaQ1Ʊljݩiye{nX7'F݆Kvϸ+g?#g{v{{P=.Cswf&՚ZG]4\:}{GӧgWӷwO߼R~@yJI`MzuPSvsp[.d_PРо0aSj%ˑ摍QPԞh2ޏ9&V+"[SܥCߏωM0JhHD%z%$%%9{6JLIHHI5N=O J{.^ ԌCƇfeFfNdgdssNʓ+:,{#JՅ'EEZm?{,ؗRҚ1ǧ,:O(7{Nͻw ^R긧t+\VnsDukTcƘq;L{lx㓧SO= ~</^|UZu۔T7ڿ^|ns&=},ly 3?2?0~('O]gV"W?篲6}Qҳfkؾ}`ycG&vlKl!HT 7/|F)N>;rpD@ù90zP|c wHcƄ`)4x)Z,aKMaqi 1RtYXغW8BB|'օlD%,S,WRS'kiIk_5כ43\7N7%XYVngh?tE5m̝}o$+Ϝ|u_Pb𡐼ЂȤ芘pLIXYSS i'2]lɪ.piAdaiѣK%P)x rߊʒ#5,uTU O[4;w6%\n뉶F/\ڸBlPTԵun7nIݖQ7c:`~rr}l>yX;:oLw\ݣSO&.>||.A|IwTGndn\gy:88-y {iDD&$8$a?`WUQ*G2~4BCEdGFeF$Ćyw7LKH"@XM~&k:A CYe9s~{K!X[_}:Q$U T+t ;td}GXӸf3g=ZRΕk8{4 B:vv`뷈zz}$m=a·2#FncAIWvϭ6n/~yFk/nX̺uVj_l۝|Cz ?Wy 6\A!9V4d;rŇrFA݁-t$ÇqnjcV<g iiӌxW| =m m7E % È]th:*z,'p?w8"I$Sl|Dd1`9šφcfDzgs8rs6psr;r/d sB xш gJJKNJ5I($)[_Tx8^yIeUuK)zݡSj`mhboldbihF5H,`c;b~aÉy1Dw={{.M_ @Lypi0(ؽqc& IZ.ȥK;rH!VuG  )u+-gWOU74E5{u<~aߥ+w:t^{TB]Y#qO:)MVBO}>7{bGe._̿J~Ԗ߯! 3,"pI4ED@nTP1KU<:ݎQ$anbQXa8G5Vz C<Num׷ńDy!8]2(}2c),7ekLvLHLhY\ˌccSγfXX7Ry'9%9lq'Px.:n5;u_A!CaH~1}q}I)eiA ry J3UU55k5jdED%7m0k7\ڰ۲ 8h8Z:E8tvv˺S{;Uړu/p.}poh}bfdeԷݱ& HN~VA= K#5?a#uEMG%o?[>TV&޸aYV ×ҮȶOwVv^YxۭWܙ=TjdlQOG?4rmVnv1eu@a)8TFTs_ACGƏW'ܟ6<[4~qu#3f7*>b5V?V9o{}[w ڍ̍?Z6AmⷺB?~m}zQr0bmoo ů/HF_ƸZ pHYs   IDATxw\ԣ(M(XUŮ1vEŚ5{#bEEDw\1qFM}vfgyfR  `0 ۡ 8 Zw˖-qP(h1 `0 H8bqƝ?ӓ$Iaj+Z `0 `[To޼yE@0eb0 `[6mZ]v 0ܿMH;wܼyӧ^BNfffun֬YPPEQ&Č1OOFrԩe˖T7ީP(.\T ܹsԩS/^Z)?{n…͛7tgMjRA$մӟ={V*˼yz[]eʔw(ɇūWlwY[[kYzkA0CFFVEI4JڻwC{nB_W.PxLUHNNVT5VTO< sN x&#W^?˲E5jԈ(H4,$ %%%U}ɓիWAX,fYɓ'EEEHO3 $acbb&MUI9x`xx8tܹ}p޽ӧO0ܪU+tE(2 S~aÆT.^֢E&xb'oqW}/{2̄aZ_޽ɓ'z[._+6.]JMM7n\fͮ_믿~N%z_>y$)) XV1ٵJJ5 "00~ݷoٳ}}}e|qʕӧmDpppLL ˲Rn-0i!WES`0oy׍jfXxi֋MIbgBeN> bZJEB 4h6{+W,Y$??޼yoߞ:uj-z… ϟ??fy)S7b^{IMM7o^4=k֬#FPUQlܸ100k׮W޳gVZռys8vشi ?tR|aÆ&W6rh4ӧOoӦ;}Badd\.$l%OWނ lllݻ׻woKK9s栿 d''{}]tiٲeЫ ,W}ݻaaatb8.22C76-r(x=fV;w9sF$QZeY@иq?&#m~:w|ڵfddh45k7|ڵYh6Y Th_`/NmԨ˲(s?~q5uq'QG{ÑP N /i_TtoC駟zݻ-,,'FPPPHH?oŤ~ c&MsrrߟoZzqBP*gΜ8p x''>k*R P_~Æ ݿ؏?KJJz24n822RRVhqƍӧ3 ӹsgVnವN `0!Lb}ml4?p>|!CFh4YYY hpP(<==V֭[lm۶-[>}ZR͟?* T9s5o|s V5ѣG'MTu͜hРAEocǎ=:11q׮]xɓDҭ[={>|8vz1bƍ ;w ~znn.rY1bC%V#++:u,˲͛7b qU'!!믿&IiIU'?iRբE ~~BCCYEh`UVrlF̙Ӯ]'Odx FR*R0N=zӧcccLP5|eP( Aed˲Z ^WT>>>$I,[TT4i$Z|r)ʡCG$ZYY?l999+Vh4?~<*MR9h D2Edu͛7ӦM;uԅ N>8(3gl޼>3f̡C.]ہL>J0K.cǎO |ə7o^LLSxxxgJx`YvG2e oCRPPx۷o ͛O:h>7n|nݺtUV-Xa۷ZJ5jhӦMz0`@/K,}v'OP(v}i^߽{L$jPN DDD><00oEӄp刈O?%99yٲe(ׯ?vq}oM.=L&kܸѿtӝ;w޶mĬ^:!!ѱO>G%˲k׮=yP(4h^m̓ P k̘1ׯ_?śʐ3|pd2ƍ]jժJ!|^ƎxB1~`Wllڵkbq6mдkEEF4Ç߿Ð@a/_Owaڵ}2d-''gΜ9wss/҆ jɓ4i_~}Ŋ .D[?3EQ|&` vaxfpp͛333;t0m4|bssg 4mhuX1w}x␐RO8qd;?nݺuU(~3f朗PNNάYvAH/:::vСdmڴ/JJJKJJ?p@%hNĉӦMt߯WH$RTM6tׯ_OA7~I&QFMtnnƍn݊ pJ*ƍhɓ'M4A&+oϞ=_m۶=}YC8)9I(ZXXI IIE빵 V\\@ Ⱥ b^.ʺ+?βX,Wɓ'O<9vX8zhBBB֭Q BQ-,,l޼m󊏏:th=͛wʕy#GL81((h۶mYYYFjPD=|ڵ>>>/F}hY,,,O0 :..nܸq[xERQ?|ӦMVVVSLٷoߚ5kvݫj*)ZluU(=ztΜ9&g8ZͻW. kKvvG={6dȐGٳ?իE`>hʬ <[ ~7@UlllΜ9tժU_(B74M'%%EDD>| |hooJLLTHf&330Ç$١Catzꢢjܹ3g3Uf]&H߿ߴiSLf $%%_|񅝝2HDA|iT%Zss/bÆ &MEϷ[dN6lXF*|R\\|ڵޓx޽sӧe˖/H(y/ԩZF.[lժUh*&;)8NՖ+Z-qͮ&XEEEk"( *r +.}zȑo߾0 Pnm޷o_VVV``uXH$6lX~ƍ3tPa4 emm}!XQ7n(W_gڴiӽ{r+V*qx˗/njee5jԨ~ի}ɩb8w؁HYdɥKv1mڴK.=| ^$1M41\H$666NNN5%IOO`C ׂÇnhqF aΟ?_ny=zӧ˲ΝCjR);χx7ϛ7ietpBpǎ?NSγgϢ]&NODPPӧ٨"H-Z¨^z;w]vw%0۶m?^LJJ_Kݻ{ݣGaϟ5X[&;vrJlll߾}Qy{{/^9\p\j˲Ϟ=YӧϜ9sQ QՕ͛5qĢ"33[R)A4MWT2(iܸqLLL~߿?i$ 0۷Qh.S07nyfݯ]o>Ν;PVڵKTJR诿:!!&!=xаcMLe^zuM8wܫWRRR233u:WiY"ά6]rqqquuuuubDk4MQĉ[li]zSN0aBP@0ĉ/\8qrrr\R4*++YhQv=}^CB-..ؼy36+99ɓ~~~O>?+\noogeeeB$SOOOTZCU833\rER=aÆ ={}e̛7y7nXr%z7n4ir͎;V>aYvƌ^^^6g/_tvv5j_ΝН2lя=:~uV^͛"/ZCAAAHDEE=zKǏz}Ek]բ 0wE$͑hJKKk*..NPiӦW^茥 kk봴{jdJ4((((7iYh@@: )iϞ=-Ri C 2_G֖_sC͞_CQzyߙ qB0Rz '$$P5e4!233k6TZQF/^@2Dz&"!!9H뮧}{ѣWѾ>UQ =`g#GEv W$ɝ;w۴iӯ_?;;;_#e;..n^x\4z9W^988)?yE`` ]@@#GrrrJKKKJJQ[eVRkK?]]] ҲeK~*44"$$$ G)JEtVVRyw ɀl.ZT*}ⅯP(4tHZle˖ ,y~۰]7iB.Zs\]]QYvj{E" ;w888p7`cj˗/˦q\HH+. 0 jz 6_z=>5 of3t^TcKQe3KZ8"yBwg,mv̙DVgeedݻwqѢE/_F&A TAb#$I%e2t:~ZVTdF)b(942 qFvyÎz֭!!! 899=}ƍ}YjjUfΜG&a( JG}dB$XXX,]tՇ>|{nff&DwV]aÆ5klC Yn]@@0))).\|?P9s nj4~ĉ˗/7&M⧘ od#Fu}GM6JRRR PhѢڵkwn8F7Ԏ̲>!!!2 mEH$B^n=zdݼ0gΜ۳,xucǎ{=sLVVVPP;0!..=h" ^1yPB6{tc| =!H#keYHԷo_W}&.bi"fԓ%D8x͛駨-[ENǷ@j Pym4z"&O?,6௣KRX@V[ݏrdTC*/y>]9aVU$U-6 +,,tww'ӹQEǀZFAW客Zƻ}BhժUy˗͈əBI? lq4b@M&Ra0EjP4hYEU BmۉeGj qyy YQy4 eѣG9[z5I_IF FyE([;MN)Zd r1G}dos=Xlkkkoo|h1^G~ƲnǕݢ^Q+p苇ǽ{Yk6dn$j%fҥKJ\j(SRt޽!!! HRWW}SUUc>G7޲eKZZQ?\y`Yfv.00ٳ5|P^]y$=ruuEM |G+7Qwof-zvdXiiD"XdIbb"WJJʍ7><}t'''DRrMx4QlawޭBBB%qKeYh&ˏ;vرׯ1Yfhq\aa5kX5_ZZNi3;v,,,w6F[iJzaÆr77t+++ggg$huQTBi5y󦃃C%'W? bԩ#F?7ltttzz:2TeAÆ [lٳĉ9z(ƊlN8+ (ǏQBTEܹsў)P\\ܼy}yzzVqFu͘1̙3G0cشX(XlMhxժU?C b*W_KZZZݺu 666Cݸq˟{,**jRʕ+Ǐp}-˰vEipaÆ'O)k\T&%%=ze\kkf͚mڴiȑW\!Ӏ<ݪU+^Z#p(y.]j۶2i^ƍwȑΝ;߽{h&acǎyxx[M*e4.*pv޽{[[[?M"C1 ӿ?=GZдiӊFnN<٣GCpبHQ4l0t/^l۶ T+I&2 lmڴ)((H$$S WEO(_yW"E^){ =rrr^|IA۾}˗/srrx*sݴilH3ܹsݻg̘aF {EKKKE"QT* Ҳ\]v:u*tUR㏼l111[6 ~\;uiӦs} }4::zРA Øeff^zhtT*&Mx}lׯ/>}z9 ꨨPkkk9rgdd),J$ C39 lԩ֭[ÛL0 _׭[w'N@vvvק(J*_~ XZZΜ9e˖%v &([~I~wdkr [zlh ֮];wѣG5Hq-Ydܸq"H,ϛ7?Ffk֬5kV>}Zr%ZL3T'wIT*=z4-dԩsιs2D*2 '͚5kΜ9gM0aΜ9(`P**ϲ 0_8͛7_W_}6qDC!.;FEEfÆ ۻw+<?f̘K.z +/Kqq+,X`ee߹s2 3dȐEuҥI&Ǐ3gΚ5kƍW3;;իW^EW֭os-..0a4ٹ{nxxW|}}͛W^`5nݺ`aÆ1cO?dii6iDTT>{{{sssx+2`z}QQJ2e _LERʭK,yC̰E"T*-**JOOwvvdFRqGQL&S*:VVn +++da:: ]Y#t]ӡ : (kimm͗wv +--I汲BWd,sm\./,, rt"]8N"✜[[[Ng!(QZRn]P7'Nz^.Ƀ"A>r(L&affVPP HFը7(**REH$B69ZW^GFQI$^M5o ͋Pb8++Ύ8%Уh4hCE@Rz|YD2gaخQ! +TZRR²lYQ+*yt ŜȑJUB*J$fVxʏj#bbkkkZbiEʤl.eCD"JQfmҥfŊEEEhAVcg ?^HAkj? ^^^K~:2{$Id|7\>wܻwVKggŋWtX,~𡗗h У={, H[ZZ4mڴd\/YtEvܙwҩtNfff^^ ?YKJJt:ڣ->U4HaqttT(* I $I;::ؔ1YCZ<11q̘1 j=5#`ٳ&H$JMM=uꔥ% =-^/((ꫯk\ ѳfׯmHhwr oI0Tо+ߊ:AT!!!X`0 `0̿#(]`0 `0[Z `0 `:x`0 `0 jF`0 `0 V1 `0 bey,˪T\Ve 0`0 o@,}@{Xliiu`?V1 `0NNN"(??ɓ']$I  Sjj_+ `0̿+++$z?-[JOO`+``0 7 @ӽN* `0 P^C>D14s `0̿4'] R X%cPV `0 `0*̘c0 `02c>hgtD>`0 c!1^Pꜜxu `0 Z|̘w T*uuuuuuurr}VZ( `0 ۠f͚n%^^^ڵSP(8Oa0 `0&qW}1wRbccРAOJw-`0 0 M ükA@ (J kA0Q޽PT.]իӓ'O*`0L%i~ '8eY^w-K94oޜ $(vvv`0vxЃ`0oh~RMB]K):(jXI`0(|׍5a yp?0 w{#*j 4~0 Cpc0L킬ǎS*"H׋be0 I&kOm$ZRY4e DV:H$޽; ²,clXEѰ `09e[$l+X `0ʁH޽GݺB($u:N#Ie9`9 8W\RaZ-)BiԷ^ 0y`eivqq iҤ㻖`0CFFFll[^zFK_< yKLIW?(::I@qFhYX@ Hr(N'B!0.AȸpE t] ۍseQFN:}dYVվk0 S;(׫WOKKC&_c0@h@y3N ,͢{P0u:˲78q+I4M,:pD"7WYx;c{4ݤIz꽷 1a݃ y Sp@YPhnf|}5 RVn=R@@i5:X)BVVVW@4aԫ/fP{uC+D&)JNOTt'X`08kԨkb`j8gC\~RJE"P(dYV"D"4n7hZѨ BT*(Jכ$H2YAzyTIR}E,[^=X–i`ccSѝ\Ofeeee! -,,BaE'kJeBbb~~>TBPx֫gffV(c ޜ:`xP?ob`$%43eD„iaPjrV(%bߋ[TPWHY*)F*)UZ8ɉmô+y$&&>|P.+T]PXpfue'>eVV24{y}Tn?~,ˬRX **ϯ1liZRVVٜ%y/ c0[!92;izSXX !8F[TR^ϱ,qL&#IRL"eDcn%H]c> ެanz$84!8(R,1 drs?F ^YbqRRbVVaF.ZbvZ&%%;88YY[E9999$IIr&xaccPX(,,- ⼼/׺u+ RdX0/'/''[yD @,J k6IΎy_}65;SWY E`/Ϟ[W>e6t8{lƳۗ"'ƞ:uifmwܓ<8t:+9SB=;㗻-Gn:lq=|}ؠ62ًgofϗݺ6ywcj~ru`  D6 !%sj+Y" B RZ$"d̲B"`uyj+TVL1oR IA#z|չ@/  IX _Ū&OneisG2j¥{ BC+J2%%V"HR)X[+$L&KIIAVNP$b :A$AXBP$8J]*emJe߂h(3 =sGn/xǃwO-JA뢴NKX|}˲3f Uj1k%"Z k(Ժن 2_EY-xrY[ippkEO4m~m;upDqVo:l'TYaxJNcqjv<,8qW W쾚W(tbt4 qO/^ZXm·>  S蔺@0j4Mt:|$qX,Dȡ $K!zH ,_9 0\,* $hTX(YZ'Yz{4**9LJDV 8i! kmR(iiiQR8sqqssrcP* 'O@HGp(xAAA So@[W VSJojm)p/3au=#[@GSڻmu)lw-D'Wt-b" -4GC1){Wu;*Q t?om;͘~\H` <O1_tp k*O ŠQJ4wsN]|A/.:_V IHT;ۚ Nlʴ'#v@h))֍~?p ]pbsPs˜IYR֜qsĺz6*s0I:6IN3[SoퟞxV&C_>T,OĠMz dzȥ'ܽ o]F |q_4tHBTszB ׳;WwOBU2<‹ Ss28F}"J  z=-gȥa)U<>2UX J`5@hhXL KG {O\R*R;W$=o1lJgn)?w߄4M3$I04@p3l3V28N"@ǯ fx ^s@$I}߃=#u`΍(#JpK|]vY<}?h2xN]I;s͇p%ߔ)eWLE< rrrL9ɑ}tTo kz|zo䚔+{7K")(Nz&KRȳFt/:Y38-X~C=1+; IA!U&HӼ)DP"b0 -$099]f-$wЬF?q{v}8pt|lGqK zM)ӱ NbD Rn`@ZOP؋(Nދ`0&`hG)DMHF˹l$,pIt:TjH$hlvLgAr`t - ޠsfR b(J~BUӨْҋJMղ, EB̺DZ Ciӌlx`y h-!}Pn (ƧOk$YnpSgP9kvgWd{_'(*5 y==_.h',_Wghֳûշ2' ;קjGW*]YeYX @ӿjlc>eɲ,[s5j 3]dYX֨i)yxoaDu,qwpi/2I E\9YGviod' 崃٢wG?s>n]5og5nGU,hz @K=b8ؿa9zoCS1@,+;-8 2-|dra$6u\ ɿIͲMR^ JzC&dF|)e)9騿NX%^65ئO:oAh+M0UITk&eCx, 1#Ұ%"3K^ 4MP(72 K$e2DV BE)J(" @((Q#rUt ƈrFGUX8u:7Ij E"Xt^N|0l D'KլFdVO$iJj xÔO%B8$)rqqIJJ5Xgggtq趲E"WbQpπM|pjFEg8 c3,I \B%8N6J+J.P@k5 iJ 8tTȁ8JSLprrʩi̸;w 7q֫m] !(epͬl$jF'; S8/'Z!Ke!ckL^gSqFZ1'J_ pdW_N^6֨44ZZ8cit:$9 3eUevqlYN;8៪:Oɉ0D%"E +i (+wADEWE>Ar<йz?ܙ4=_OuSֽ] #&]w^m:?=ucsU>-9†a00>隿}hq)IIAyjEģ9/6ss݀sT,˲fk 2(1NXABjaD &%NpJ @3TDݠÌ%j*5bacƘ,[T5B" !F:%LnB. !ԣGq]]RPn'-fEţq`۔Pf%nzQ؜lwXWÌxdiR,`hcĨQ rX3W3Bi[JNF)3}S Dr?EcAtUG  !3@CUp!xkT/;,[7r̠ Cfy˖FEUI4*ʀ1|jJ{%'zAjƕɚ *?'G}է.ԓJ,% ~mM eX(ee4cSo߂o6 FJtֳ=D^ҙa&XIqOp@J_kS,r^^(`~DQ!d$ifqFN%8c~q;/:FhXێby˟}%578K*V~ R2O%[89hJXetIRQ LF)̝^; 3$It7S%>Gy!}Ùn|XRgn1[j={G}i'b!.wm1l䑒 xZ11Qz| @$P =>>,z$9s`CuݩVኢxp8z>ϩ["2X1\٨Kv%ֶ,RJ)y~1,Z6<7s12vٓO~7~aW?^xl/jP@Ugޓhႅ~8.q"̗^̫o>\ k{7 bf[Abm`\yX=<)q Ol23o" !aW)Nk=zf47 >VM%@#>? Ͻa.=l`9Hs='c[~#d|TEgR-_,~/sL@ޛ.+ϝGese (:nhhhiM%Yr:l.MD XA\rJsc'tv ~BYWGqlu8ܫƞ}"{ђ?PI P4gQ#Q@]-KIǍCȂP(cg #UU _3U clڴi^{0s&h*lf.7 # y<p8 > BYYY6\o{^՚^ܬrvS\uS<}-i ?KƒnZؠX*$L:lڳ!aXg$ӊ? )Dy~ t$X:Vj tMy@i$EOӎ_!j +V"` DDfXX5+#4j*[y%lu4l􈶯~\i 7VTFaoZ.C5B lsZ!TYc0">NIFH-L9k56=貹G6E(J6|!Hn肰23O3/s?i8CO8&]b螦=_//},brRhؽ̙S.hP䊾"qWqqYVN݁0^RZRڋQj$MmtǔLqofoðu}3IT8cp8I|xF}N1ÑM'!(ҧOτ(TSuD OB !$ss,01sä;r!I˙H%NoN:9QDa_ 47Aj @ 1 DKVp p84l0jXXܼBMնnYmU?~o?9?QmMJc_\u`9<7HJ?0!bzq8'qnKxEw!>r$Q!@)AX#$QF%IUM]WEQB*c'] pm;4 s 0#J BB)&r7iڭ_ Xm.Ś+6 xacfA ˮcFV5MuJ: 7|iV"U69sP>{㎔p8NgI|bGZ$Ҵ{y|I pb-(ZVsBBcc!$Ic ؜e7:sRM-$&XRJ %Eؑdv`# yvFujȣJH$W+/SR˙P0D !˴?3dp8Ó #gx=z`5M pc,I($ɲ,!$ ;r_,>*p8 I F}NY5Me9W,k|>]!`myڪn -BR\3 @8F{cEb;*L#A S b)>*t8aAf5_N;̋F~ykK=Fje A /]8L j+sٜ&桾P(DdY6_3R#Hٿѣ!fO|^4ᤧ"c0 UUon~e 1^o~1ظjժN)' &GQUU9t"oc? DQE|#&YQBtb1R=$XD i44U{Z0a 뺮Z'}t _BH޽O9weee@.l8Ɣ)S_~I*>7E}}ڵkWZgϞՐ"ܡ  `@`1 A88J@̮ގvePB 1se X풤/tc'N߿up8,wq~}}y$01H顔4m[иs!P!N3ܞ j%uu#aF 1*˖F)ily2~RzI'+8pR0gIn m: !*2u9^RQe!v]e]uY`W^yT ܨL,Xnrj VAҡ5ѡ1c\p8RڿEA迌#m/[N'XPEU50IL7kMza!>.$i9]+~jNy&n;63JUU "KGRܛ6nPUEQVpΈ޸^tʘEzuX;p8N*G [I}62; fl IDATO?AQJdYŰ>Ӣ%ӣؐf\ؔqQL%'͐)IIy9q&CĪ,i<D>I['mf+]OF*UtxgG_LYp8noc-[o9ģ mZf&i׋WɊyy߲jO8jЀ7nz*Y\KAA#ε뷏>i)9zR3϶u`;bĈ2g|c+_Z uvBq"[p/b㽐p8pp0GC}t)'0nlt蜴mDW$ܡ>6?I9<Z,oIjD9$b폶'4>}2D6}dW9`rSMQUuvT,9⨣aqWUxZybeg;{(>fcAqݲ嫷YlB8oq9mzptO?vwU7KW#TB_}F(G}')2m?,> }y7Nr7^~'ΛF|M}trܶ^w|ޛF;Q~;oobUݦk-s<'YWo+gՉfLhGg]0{je_^)cUZ~WgBy|݀FWw1"1n xF4#p84KŔnvA22{'?[}wg\̙$KWq^9Z{ן_`b݆'}yTTUu犔"sȍٶg -JmZtţoN+~m ^NON.~i.K7n/"<%}Fr+sx-fxI|鴤kNZ:\5ockdRbT*7^ɔR ~fyErt7K?YwgY_LH?vЁbF!  fenς١7ve"9Lp82<E'ڲmӻjQ/jcbrr-V[w'oʕ۫jQ|PUU!7;jvUy⛶U;=Kv?XPh$9V5U' y/+|{wUs'v;^_ #. G<;ԠYKpu7O!+. }3fS%VzKp{ ^_{ɰ#vt}A ķ?ܒ[{ψ [om֍  N֧seJ6ҙcp[\4 ֗yK?(-rk c:F*Hsm@Tz%n pI't)vgcCs`啷 G?bi~YW>OiIEb&NRͼd"wHb+Ю2ׁӽSm34Wwƻ 2txݠnH4z.bQEP(#,b‚FO0xK 7mVMb$E"(6nee )[E,adQ6 J (bGv ՟J0zjk"G ~Wm+O2{ϫ'Lm~)eWG1{):{”-5jԨQo{iSfx FMX566666444{ J7}Ru33` Fl^J뗬kF5C\v4{/@Pȳe\㇗%Zd44Kn#[iuuEW\3FM(H)o2>u/L`&N_mE.lFZRJ;ߟ=ݢF(-x`Ԅ &L?]~wR`CG)SRJW?E[5_Ԗ8M^W߽;ZcKUEqD5j„ &L2u)Sf/Jc:fM^0506YnR8;e9Nth7@4 b%=ܣ.GNs!b0`D찷h<!v AřfbtTT)%W2Tr: "ӤtsRy:\[H&RRkIO԰kwA}>vE?dW;uW<6;~!PDea1BDc2 BjꛓFnvEA,`c%Pjb;;1x(r9$QEQ! asTs8]1L(ijc6c rxVy~ɩs^#spL'_\tc'>Ѹc#^[κ/l_U7VX˪^ҩ;HM [KN  o(񻱗=kY۲,L(f? `Ӟ{8K{f\v;ݵzRܜ0j¤2@B)+N:f,9?˷Qw<=u\r뜢S~[Xrޛ_ьi8uCu[zo Viٽ-%>ٌ7?91?}W@g@m|kuR>VL|hUw]< ;WI OJyᛋ*& T?=|X >u.숯Il f9iijw}oN^ΗcK1y4N+f,K(юAઅOZ8v7ƴwsiV`bO?*9TFcfytJt;-Y[CH%x E$b5#Qyfg?i10W9TB&:U] dRQ&'2,Ô31NNrK]+'KMSmO/'UQ ɵHU{&VD5ٯt/>S͋[6a  (.(jKZblvMDQ%IafX;lVf;I%Q$Qa] ]Wunlkp"9`o_mj8/!3nZ|Q?VY?c= nSS`xox;]Y}|Ȝ^=p;@[x6˹TXLE+**i15 !BlF47iU% ڴћNee,h<{{/B"sy4XѿHPyn17y<ek?v^ٓaCHA)\Q *WTFy&!_#~_[QL # 9b$߽۷׼aWUG" k6Sr,lOvrQxepw;gLMl l!ewY%~U;Xx]MJ ̻_B/eh @6L>Px`҅~S)]1ϸOTJs8%q@'9N( }n/.ᛶB slLYdX4Vf|nֿ^|>0pn\\k;')ϚI~Ey(l6I%QEQbN'PZQ6,K$n0bh뺤(g'/)̗Dn*,ɢ,I anh.j(kp\z3{\=/ef2sᮇ_;}޽ 7e?#KH-ظ=Ok@$*>P ,PZͺJ=5 X-};@9Ds|>_\[@V;B0g) ?r3޹>8_^ x0d3|/uK@C@TׯA]-s?,#䚋󸇡פ_0n|| < J_{u3 ;[J agPWycVx??m7ᾉ?ql//0+I$ l@]bD/˞5$ZxǧMz.}I[ 1ّ5 `I{9,i Vlb`!A_z_ߚqԆ [%-YYl+'fकљd(bknmgԯ7 / ώ|nTWvNH&jH #bQIEQ F 0tQ`0|f]EXIdQDDDC%@ J1&s:"+EDIa`\q :x@0 />#K~sN- Cx}^{b%ca::MS I(Ds})cAfݽ;f7) LG0In[K6֟_ZƯrߞTo8Rٽ?GW~׏?Izc,,:i ^~]. 06~׿έ#!ښf=kϼGN3 RJ/~S%S׎sG>X) r %zuOM@/=i×w_~w.8w~>Pަi $:Y+F)K4 +7Z'= >Q]b:e nI*]bRЪfi hɮ3sXt#h30l_^f~4Xrnq%/mueddPoPr/j\}~s.ָ'_\J2p81t`춇)-)޼ (uɲ$ a jHoljj8QOEѺuOܩȒ$-p$rdenE7z.,K( )ˆ5(-rkjp:0&x`~"<NOk~ۗ{}2 W'uW`0]z@ k6$we};V[fee‘pf櫧uggA-$$DyqҬ_2C6~dO`l|?kcg<'pKKԴ@KM]EӰ+6Y79;w[?5zܣ{%$\tʲ de1BH\sK^zc7lku)O9f?Hٳ:w,oW(.,E"JC !XbÐP3ɕj?ryaG,S=Ng}%w_zb՘7%$ 45`EucH`ͺiU!*\GL ?FS_ANIH-`˚5#wp~D@'9 @ߊ>2w~!b7zF]`5eS2 :Ե?chAvp ]E#sēWyiPܚ&Q\gF˜m(Q#"{{Q a 0%!O$ߡ{hE½K9b7aa_՞ʲ|5`NwCKrk;?0Z'.]X1ڲ9뎋gE#{{<@@/b `K/>>|ޅ}cRe3VuӃ^W-Bh']XUBҳn{6|Ͻ7)'fDN]4+[,ӵ/ ~^gk>[\JJ;gLęS037;6)O(H5GA;c!1;dY-Zr_}Hiyf#{>7b!jmK5={}c fF7zpBH0}gmN|gSj4JtsE {.l׍ qVLǠU˵}e !ت2@j|W Mf%:J q@ < h{b]du'FfKrT B 3DP)t5 55W=gpޛzpY_=g2m4+bFCC~6tN5M?J ().wl#M5uUχqL&OJ r6+C֪Svk_RX_ckolhk(wYeJĂ2ڼ޿w:ٸ`M󳉿}%l/v4De=ܢ|;U1\Wۤ1,pM #i]<;jld/+iջ UPb P/+&MAs={:-!u9P|!*jQ쬨pCMGAy-&LCջ4J$_7?z[AtDAQM:مEX3k 欲  UCX%aevV<ϢR,)r!*v詮 *b(hẚ&mo)lϯȷڬsK-$ley$R p9eO}'B@qZ(*6м:hM4pAsEDgA{ﳚYzWKп|~_P kx9f̿j`R9|3g4_PCtaUH^꿫v%Ȇ p1sXkXܯ>?zbTG̹-nq s8NÿiO<renP5*HGY5="QfwlܼK`  # d"renGtAA ".v!+.K{{B%/( F 17 $3t[ !U4UKLd#F'u3AvY}l^{y$Q Ql D#jFJߜ8DIJ,adfaA0PHNE@ $1jDeU{^rӓ~rIc,D=ۜd s83w\qM6w:{Dv΃;e!`(#k8 qF0al0)1,Ll]4Q6Z[wO_`-ޯJ1bJ B,!Iju]bS)ӫ(N!s.t/c>b- }ET?64Zupo PS)9pcUC&IiMZbJ2jwo]Ҫ;Vgt lW1do=mh~Gt@HP$6YX*FOHD! 0l`6Ljj)ah'r4Bsؓy4i87^V8pu.RHp}c5XRlKƜз 9aٷ`i2A!HDDBB,{PwP,v{A%M0"H@D." }FdYj P{p,p8.2fp8JX7Sh64tF~FgF" wBKgf {_QXS{pSghr ?܂p~/w9Ğn"Ր񝏿7@!_#UaH-u$ @U{5Ks{,k=v"W6E_? }cq?V73O ުOoQu0p8.4psbwNWb螦=_//},b&barJ5, $C #sZq vrDjse E;݀z:x5%tX4Rp8mc}`}Z$ܸbABA*lӔD#a äU7yKhMmngkp̂ 7d3tՇj~F6]xGv۶m;`p8*++~}dFb1Tb!XΔQaI `ˆo՞\*ɾڝ-`== _GWQH l?^(<6p8NR7%񫒒p8΁&.%: ڷJ7LޭE* 2B !,eWSuZ#C1 gL[i%&P@~ݴ6@|,4r8p0ߤCząVMuZ J1 al:/ZɈ"EzF`h-N{Am3u쯶r89pL:u_KA(x0f2PCQ1Ţd?K+ B\MP "Qhmc~%TK>,XpWƥǥtc](ysHI39LdN]bBHCCw;!TPP VSSSs#7p# F,xk ;ލJA{C.% S*zAG@ARp L)΃ܪÁX:w:]oٲ%''GQ p8$b_P0@eiv=a{جBV6@AP I"T$!aFL,R?Ce+>TcбUREEͤDR>%i!quO&R*9 c+MuR'U;3sr v_---C e%>8Y`p8dtqHj{4 sbò!ECE1# d -bdmk@l 8uR_źqVb3)'QDLRR53UTd">IKsPӿHzfUWJ;?g3Ƹ9,$=7JF0[C^ޢ`),mLu C"aE1̨Vx'vKr~yȐH 9v$sR饒֞aLZp8p~KĞB0 E,~P!~ X$ĬT@(B@2`x[1 ~!3i LkwMp8p8EܞXScIE9lb,g[lGx+l9^ME;Wnjb`3.=qBu7&6}}_מIqyU͹Xs(ppzZZZ^}թS.]{u}׮] NiE)ݫL&׿KO{ME׮i.ghX__~߻o0bqK-J}KspϬ\[ƍ{衇 FX|yEEŐ!C rwV 8#D7o'^P0SO=Uٳ; $3¸q}#>Ϊah 6\pgϞ8qoѵb] ߲e 70x+WO,qe{Ӎn~~y睗\rIuNk֬?~Y;?k&eZcΜ9}=nT{_%:#*QwfL61+p29HtxQ܋̤>Q5M[fMl ۶m;fĈT)S\~ .?O>?>!$`$q8?]wݕթXpW^ _y &\pG}qpxڵ+Vۇ[o%>v&MJ`&Ι3'MӜd?455ᄏk5MunSJG / 80l=֬Y.)3f.23e}{0ԔYYY}ټyn+'Vj޼y۷oV88#D5M;ꨣ⋢T/_O>=}u]0ƍx.Mɇ0Ʈ;cСvƍ{G:lȡ`W^yr;s]lY҈Nѣ_^zui鮆{<zןxiVX1wܗ^zKMO~FQy7wycƌICg1iҤn_~-Zfw߽VhgZcΜ9~z2?8#:Uᕕ)w^VVBcq8>I9c]fRw߫2ewx㍻w;cEEEW]uUo)< !O<9h޵k׸qnwYYk֭˳aÆD^}Yf 4hܹfʊ+>+b̘1 8?9;fj矟>f:Q8͛7?#O=W_}5se˖ꫲ,nmmUUG{!?w~SN3fhhh//>2G6PXX-[2Bۆjjr(aIe`08u{,Wa>,#$r(?<#l={ܹsaeeezt.+I%4M>X|TaÆ8+RYr-\jժN8bn'*== k\z0t3-ɦmz HFR"* T,^`PPQT&GJ${m$-G؝y˙a3Ϝ0/θy'ߴ>>>gfYvʕ!!!ڬY=Сָb??#F;vl׮]Gӧe˖]x133s޽$##c׮]Gsԩdb>ذaj}QΑ4p-/VZ͜93((_[|رcн{wrO>J\|9?|ԨQYYY/+ߵkW6mvܹzj۩o>~~… ӄZܖx{ѣG״#'N:u=0nRo͜9ᆱE#'=#wvHqq[e߱ceܠnL޺uSd2… ׮];vVZ9sf7nt luӽ{[߳g\.NϾbq)))siѢNxb-j|j4yT;rZԩSN8pQs 5k{ժU5va2,Y"߷~nƊuµkkq@V2d͚5.\ vFtBf6mڔ#.㓮c ;wWFCv J|&Mr aZlٲeKN7TJ:~X)0""bҥݼ@&u1!!u%Qk~e#`糲 c ˨Vx{'ii8[-ih4TX'U=ZGwqўLr{J &ġC&O[|r~ x 7o޼ys$U*Ν;'MԱcG\tW^ye֬Yǎ:ti,oݺuƌRe~hQnݺ`%M]v]fM̙}ܹ_~cΜ9/0{_ʕ+Ve<2\jk׮5kٳ3gdgg?3 :tq㆔hExx/~ 4f;K.,kyO2dȪU,Yҷo.]uY\uVĉ{1+;.]RbAb1c^|y wv¾}[Viɒ%uXݵkƍkqN0y䀀7n,\P|*N>:quػwoN'zخw}?yfk{-/Ix'h֬YNNСCk 7oSO=նmy5o^Ztm>FP6Haaak׮MIIY|yǎ[j.n NӴeyIVdb\TT4gqAtANsq{VVt@cbcOјj9`MA[ ===\hd2D~a2nݺVq R"m$y8^&=P|A/t}7 ɔH8N`Y6;;yNfMPk7jZ'XM0 :p-ӷKpop],}BCCm۷A/SY_#QVTT0 3|pp`0XrB 0MA\ݸϖjS|-֕XBlJ/m2r.k<_M %Vi EeudbJV%Z9GP c;aHc&>>>ڷP`R\Twӧ縏7d2u0.ֹ7j$|QbhW ޼ysʕEEEfzħq^z-!biw@WbJYo6b%ƶP)|+@HB#0O78 nx K&ֹ6;Ç_)ya)#Zlo u1M1$_}8BS@ BcQcU(r>ydIIIdddx7 :Xb`)2,BYYe,r/k?)^vG)l`c> @p:Ī())2gs&#ǚi~… e2\.4hPǎy߶m[nnW\[,@fIJ j}RkA?Uuq%]`Mh4X0@ .RPPP \j5J n U1 ˋi\VV/:u @EEEvvBcfYҽ nd2iF̂LV[+BYFvҦZ^Ws%H'N 4<@ 4Qj+(ZԼCaF `-%488w߽pҥK5Wz)YP 6L4i^g3 IDATjnݚp& @ 5fH mBol`a|||ݏ(h\Ġۆۆw -W¢h7 h ^/)))++InLr$@ f%_jyʊ 53d](ks@ /o|ldi1eYQ堭 >Ko*Yu@5덫 gDfVxʘj6mVѺ}õD@  o6V{,>~c%:1j̲lEEEYYYyydוI"ƶ^@ ܍rХ_Mcoo,^CCDm u2iZDm|m5c e*˲*_ea㵫f$@ I۵|ryN9i}z~}˷-y%ѳ =_݇-sw_-k:׾y49;_w*%1sկfCa߾ rQ,n_g`{_0g{dT?љGv~C,`e{;?./Njos~~T\@ #8h(e닲M%k7Fj>KխP%ng)MI&$6I}]ovv? ml1W>>@p/ h每`Y _P@U}uo7mQ݀Uf{&y`=pnoO{D9pU/cO?%?^גW }~Ԗ!m:V1&,m6z}YP3Wq>*YL Bq~ZيײV9ϰvpOdY'{\J=Yh~׮{/By9.l;M?9Ij^<=b`+3VksUmGGܺɾgaV :T8 Ď,[x'Ș}~%;PZ1;:H⚫ڙԂ:.׹+Ď%aՒ@ BS¥*޿VjVlYRX $Z1ڶtNL֨lڝnǚN]뜌\\m/W8Fqc^(..bYCQޅӣWaQ=qx[p!zeWO~LJ_E0(#-+j܋+}i`Z=GnjĴi"Wo?z{|s M}eP?5=T~5bk}d{vc_{tfEqc"Zmܣc 0-J~sntVF<4nVMOxw`ѕG}{W*0͆ʴZmlMGn+3?Jj|۷2ǮՓѯMi[M@ ,˲,k2,SťRW*k_4MFlFniw{ 8u~-@D>?aCuexy_)0*+;W^F[2\@ UIǮ(cxMC R,m9|xuoAScN܋_|v3-q]M*5?EW6?=x@pd eYNP(qGƷ휠Qd9tвezqō7JƎ۫W/Μ9)St^ @ %Gfspp…  r\Lof(hR ( 7Iu,QU{vrR0;N nYlLc`YTƶ1L3g>ԩSR6\|fjI ];H?K,feed2qYKKK=<6.U-sYuE ] kwźP\ͮe7{'puƍ6@h4<<<"##CB.Լys___'O-Z_\t)88Xwׯ_ ,,eٴvI]>Ruֱ,+\M` 7U usL&3 !!!O=W```JJʶmL&^bXl6[n,^l 7ndY6''..p/BbݿUV-_}k2h4Zx///Ri?ph,++k׮]ppk=Ν;|MmPPЍ7<<<,ۜ;wnر_~eNt:݈#N>-2eʄ 'm[[?ӧh)WK bsC6׷IVG #il&LQ˲ ml[F@ܥ(^`&''rZ͝;モd: V;}Z+M=jԫ) &gs3憢)T/)@xM774m扢)AxW,f =<-s Аj{1MV+{ǏOHHpE磣QFYI&M:}t߾}u:JjݺuYkVpP] 4 R]=c-(Kx)J&\En[Vd1 >x$JL  z07k֬5jTϞ=SSS?s~hϝ;{-[?N:-YdҤI={7X\TU^^;bIҝb_flT;{ [?[y@ MZݲeKdVZ٪`pWR\ThTRrGQ2L MHh;g^ʙ4mEF t";[)J:̧} BN* z?{.\|m{M$ឤvEͩNW%; 2e4@|~IeR歇j֢";sAj;uVxf',J/H9E+?1!@Ur$D%fyA/65euIJ+#[C_v;:?bwKXg@ 5D ]K`0TlEdDfē^Z\aMf,v}"Z < eiz Ys=_QmdJ"lu-zYNWcu]_u];ʖ `  L u͘1>F&E]kwyq #phJFsF(gQKү^ $а뗍EE_!a/\eO\y}7GgMQs m{I.r'Uo&B8]-5MXbL&)pW^^>o޼6p} NR ]{ EQ2 @`и>M2%ChLK tdž <B>䞞Wwl;6YXfh,tbWěmA)G#;iS#{.Hvd+e;Nl+,**ۭ,$:uۮwdpBc[ppz$L 2(gӥ,)9 CP{>8>oWnCz6MD$vʨWYIO{8@G[ 6LJ(hlpuOv2s`<}ț?)ZAtY޻moD9h:{N0gbZ Pw0 C¿BA˲JhuUۆ@`ҴLL&B/2 *{w{7)U?\|z42DCQTc*l#GRKk=ɓW Ù7o޼%}hBuq Bר`A\?Ah&@ eL he.+)K1&_țM䊢 _y^LM&='@ *W,8*Q(fĈ{lO&HgNIzc?qq].RbcGmuԻ0uU3qL+,cϿ<~ɾZKqg'xZ; NݼՁ188݉kfIF˲ kE(&w5IisY_[B9ttIl(J&OM}~?&%&3ѾT(OAҝ`eQV,F|sgW1MG;&Y(JR୬";>;r@O4]u9zu._9o ݜͶޟߩvXl_@4@ LCGg>uXϨ՜Ht?NdX̣)_Mj==~6{֤Z[v;8t!]ҾS&QwU¨T .ʡ5dyuDeͩR7Ϛ`VB EN6};Փb6figo>wD1ẃ ,Arr[MJXXj\7lShU>cWF"45Z`aM#DUJ/h d s#$<}Ϸ t)k!݊:ur'aYe;+#׎Dw8i]C SF9S,ŀ/{l]]N5 RG3&Lņ/0Gpo+Mӳz?pN\}putbf=haQ2Xes%n/,yc:̥yW'\=ዟwe=<6!~7M.Z Q[Nx^Ƅɇg g ǀS&G uyvwaib.M_zޘ9٧@ZV*.E q,)Ax|tז{Y>5ٙCx(e -ZiunKCDz)ˎg=\N pr֑4u4m/b]ձrmj_r}{uh[իW0 4_Z4{D۞v@o 5p 0~`":ezDlz/wh B sԷsS_4uXA>/v!݁SF<+<3o^ݟxk`֭;>ʞ(_E %9?2롹g24^~^ BrPbYPhQF %-Rr*<Pa $*giO?xwE٫nTr`QBުۗa:@%ǖm@&<ү_ ˾YxɧqJB#B,Zi4 fݺuV +)М \:&3 1`  ɞtד>_sGzQ6}gDp]5Hyy뙄Q~x(.ȽtmS N0`4- k泟ǎY\،œ?6PSy* IDAT QTXt,~ޯ bˎVbܾ@ }i_Kl#2?M񋯇h`Zk ?ԱҦ@xB\pwv-zdz`u 5ILbÃ:ξ 0gGQj\ݴlգMd옌/YZB LJEn,t?B2rKw-0.0< &jo5Gll\\\\ܼ+]Td@ :qx}m/ Кziն.K|v>zilR' =DFFNH).zBddd)1ᑛ5")Щ I;/෪t,KU]I% M@ S 0![W /\Ln*z_A(F&L,-V+{l3f8Dd qrW4$Vu/övů+sن 7>nQ/攚?ЯJZOZإf@ӨReF3AiUkc`i~\? I' K0q´q:L*L|1yAPX5ʺ&&v]N}VP`UÆ (()\_D +Pz-ݤZƶ~bb72g0spB/POh'''BZ,h^8Zj ߀<# c&xjSӁ`;B PÒ Z./.b{3; Pp榱Ф!R@p/M`_``c@ Ԓ߀f9F&3T1,{#MfoF:V2KЏ, ƺuf̘QNPVKxAh3uvG&klsׯ g v@ `ZJ\eFR =2& 1WuEU[Kf eqЈT A2(푏 @S ]&Y(N`T*\ #i0@ q,J !-;1Sxdr\\.0c2JY )fiџ!Xj6ܽ@hT#/sGppIY& o1 eu\9S5KyPJafJ@ nldl)xK$6fY^,d2RHsP(DF/W @ A@kb8#2&qi pѴ>P`BPUyRj4d.#|;. `6+'6HDnnk5jed2)S]yUuZ@F@Q*_qF&nH `hV= yͬ{xxlyyyy}W[DYf~72@  ˲ (J(m\K8L!WHy*,yADlL ܖ`˯3LFӴZnݺ}à ?䏤;v,//;k#eXUDFlVٝ˶eG\Հ.l{\WGG tVK &?U*hTT ,# %m"@K2K[8+I,LwXX+$,M۶A-5>>~F#G˕z+dAף=(>tǁJN^v.8xT6y5Z^؉TvˇVO dX@  Ejڮ8N.KkJ%߀-˕^K`}U/,ˊcZA"m^d2O/>{=zCE~qStdxYĢU̦%eF`0S<ǍŴ*t7 deri2Y u$ǩckE@ !fYj_ 1,6f10_H8 AGUl9P[`L gXqqq#GAI $3\Sq2HM,-MS r#Wetq9 ><99-vg\_Zl J՟\/@hL&SNNNxxխŪ]+,Sn,l4E3 #R@&Ik%,=< yJXK(k@7vrN^^^g9f@Rl?PG~eʕjZ%T <77ʘ{/00011Q:x|p͚5{>pyZ[*ZP2匴_^wݺu̓`yӁU#B6[!@pCEyy~݃?`'eRZR10[% Wg2+48%zez}U䢜a[ VS繲IlRW,OFJ]ev-l:fĀll+Fњzd2͜9ȑ#w^b;#cǎ ,wFJIIyhpQ܊D=<<RlJGWUX n <Zl0LDh p-T7sV<(F<2̣𯜿`FF 8]X̜K clnkGGmࢦuۅNT;{ǕZcjCGC9I kTTT40I]***rत͛w$߆@ BCC}-j.̙3=<iZ ੢(@˔fח+BnE.]TWTT s Tne]Ɛ+@ wQPP .^xyG]233TTT8Hp!l0,/,8'yA]2q-X 8\upD BɕjomPx" oDkS<*9Wn fGO\r(G%cs^*ßC ܅kډTK6kV;qsr]ugN;@K{{O>={\?<ƍɓ ˿/Ѩ*p'R*vB@<% `٬r4Àc ]>D,q sh ; h`rwlĠO_EQzhv|<g9tyiʯ&Ǚ[CšשU+MfKbțE-[]vj8N )=[VmJvH`sK9=g{x3ݛ;<4QAEETU=ƆMK nkM!{-[^Pi)K9jXeU"QC&`4e n1L&s$bų?}J/pdGrC mЛ|}4%p,KQYĪB{T>'S{i `B͚ f7Yy!V5 PTM7)%fcEInnw=VQ˛bZeέsF(A1lEWt['9qpG*RWO8֤7񅥔 p>>p'q|ķbjq\vl-t)*)ڍc;;kzvX)^G/`ڵӏߙ%=$ GO?mz~vCgٛ"oйc~L9OFj.?@G|fe{W_V2m[YL-ܗU5[?h/r^u} -ǟ_\>vkORd_6tG|ta jgT[VH>:;?!$`Lp["mX.K)ТEhGX$Io?O֭UHӀ0N.@ ǚtߖ~uk A _*i͔rr})e&DS7*<<9ag=izC V> T.]x^nV~f2H-z֡K&Ő@CJO_/lko#6*DKbǽ=׻^یM:`u-}0}p/S5 Oھyt,s@}~ݾ[,J07GeCq- q=狷@>|a}@/yʞ(ݹ0&Zt{ӞQQ }Vb 6v^8I׉fFZ6 X/AIS38~P- F}IP7ΤLF5drgZ`M'O;M>r\^2?g2nm#[g*StYdZH• YCG[ef^CJJJ wLIAۑ]9bK-e{u %qӣGNY M?A&?|DG,V1CG 0^Tњ^,BYɏ yHՊhkY|2t\A@O0 r<b1!#Qgs2KfLBf7 A}ZE[uY+*n<TO*Z!l!:r~rLy|;s9E&s(u.ޡJ+ kkׯwGWQks\mYS' C*@CT_&rNDT.1ڀk~zv# ,U=U}%< sbl͏MmX#,m03BJ{wQB !u@¡=XZ.#jskI.n~jr̴ juir/ &!9%gH'57q$x3DFx"i4QYba'svD"~Ŀsog'_zpIC/O nܼt.h&0ad'=ėg#6O:PnvɆ~ޚ &Κ{o y0АGϿt8kbG;p$00?g ?~1t7|yxY.ßa-e ˦F-"$X^C~ ROr8gk$,G0\S1]O [qD8.#,:yhsAzj< 6u] *7<Wp9$gV ՅRIa_Wud!ٺ #ՔQw]4{oyd 1ee1>_rϧ\ R 넉7 qjLB y\ yfٯ_?-[@8~7f̘qСzȐ!Jׯ裏ڴi@vvv֭o&Oovm+ӧO_bEǎ;wܣ{w)ǑEhSRR*ĔRE< 0cR%(p؎UVBZe&ǫ 5.:-GM7'4ɓ%QŔ*@9Sp4hbFE{TǓQB '3lj9sQk[mܥ6Q0Z;>Ғ$E[Mg:~1,`XdV9c:!@c:t,#-{7"zJ,b(ҡEo%š]v;4OF4B2҈ۡ:|xMkm$p\VTEp8.~?fY@D ʌ6l4c˸p8qc0L%WlG&ܺ]8ZԃJtN6ҵ+<)MA(%@;(꧟~VTT-:t(t:;w}ҒDX`dYuݚ1 )_n8p$X,Ǵc4p8M916h7o2x@.pL]҃Ybg8dʘ++MFl7V[kNY9<pZ\d#)AP(~BHVV^NE\Ko+л}flt$lHh ߰Y-'s2H%2,FcilpZ&0vE&'˪;ejw]'Lw$9m>_]Iiq~YeQaa 8_PUUsx(诩dLB%'D :WӚdddx.pZƘՅ>yt߿1doH4HFl/l4E1+b0lV } ptp8-IөD& -} (پXeey|U !P (J!`:apZǓdCp#S8>fOTmS5 %|Z$BHt$~k?A`nO/g6s.p40A3A_QӴ_(@Jea" Q@@@!ΘZ1.}9q8 p bԽp8>5v._.ۓiX+ʽ{3`\~h+G 9s̉!" !hr7~,C;P(d `sqcO[ts$ Ft5 QwKKK+TU%f.I$@DADAtfT % '0i]SȵF^6[z ̇$/> g<0~? {K)in zMMhX;`mpNSIT’DD"D 0Uh v* aa„Ƙ2p8$Bt$#ؐĒˮ+$\Kps 1lq†5TB h1ʻp.N-(cdaR:v؁㈛8pZEQذZ!9 :+%m`lؘW7`9jpl-s8s @$emWVZ!rlڭ۶o5np89#DXP ،ȵI;IJ:Y=#!'oo(580\ǣ/yl1\s8sS;뮽\HB`dsf A8Z\&33'#+Et씓^p8E1<Q(ؤswa[Ym/֔S2].Ц!7iF,6GI?Nw2$d6Št՘0BlTpBfJJKYٗpk[R^p80lYFf8yAHJZVRaÆ^=y襵6 k9:̔RílI1 `_](TVUէW^(&r$g؝$wpm\d_,wue}d^`9p"l6#1$\0Vy's̫u1V i:_Z`|QJRuoe~߯[٩)%P2fz]M5?ۓ&SLA욜z_p8!u7ܘ k)WUx)@84f6\#Y:HeY@ ]s8O%NvoN kkF((A%Iex$+GEjhp8΅(=ݻw}ù)neYw @*f?~ݽ1 !eY.3l30cL'đ 4Gwk:1a& P"! y,KQg펺:_p8Vfʕ۶meZ8>1pD a+6-/uӮ#rx,fƲg_CH0>M8&CI tPX$ 1R@lJpXT Z(]ө$IS^9O)yqŚEr,E ~.Uiq~j46괜]QQ[_y3ejwblق{a8wȀ=֤%䕒H0rh85jȑQWVn\RA~( (=풒SlBzZVyo/4-p8ι~~Y^+" c v0 `=6~ 0,V褤$t:Ѡf=m|+kj_}(Cڐ _|qΜ9qq7pIR ŽrU4ˡϘnk!HTٓnoҥC{"$ 04M?dk>3cF&qpwµJƲ=\dųg64iAU*FiK5׍NV4 `N+`F4 4ͦ(n`gʰ[.ƾqOZ'6p iLrfL SXLraʲNEtu[Hr(eN!,\rN!f5k׮:v,-)iM%! %3O 6’l8?q扯uGϜ݈up8m۶ڵkJJ^H󩩩yJӴիWs;YAm۶wu+'?7Ńi{}p~#edz 4fs8iD$VWWzidUU2Y.,,\[6o>Zt|䫫{WXF9~x޽I}:NɳE"RƉDiq-תl5[;s%ᜧ^)q Ex<O~֭[;t0zuݓiZR"M7h7QZQ)5}.ԑ~8RH+۴כ n}yYgjxĴFXEiYp c^XZ]2ĶH RؐOo9]%ޢG%D$1Jr-۷oo׮#Gعs?SL8rȉkP W^qZ369?9ef=S"( ðV8l#RppHNoأa=󽇮pٵ׍6%R'1$Y-$R%6]#0OlÃq,}c.SU6ws4-)@ =CRRR~_M:uĈ(}$@vF[-|8}􋶎k=%1s^[ZKa%F'5_K~o˞=;>[7璟jϞUf<7몥T+͙۱E}￾FZkwٹv~Ue(ڹggT,_0t oaɲi:c+yG4B몦1fEuȒCi}kE*T{̌$m6g^ 9(ݸ[:9iqYŭؤ a1 qHo 8_v tD,Rv6p8-Du)~ \Q_򗅅|#G~B $''iiii1ADQ$|`b1UH S<&JR b^%p8 .Z>{=ʾ=+&ځeOx+n_rxp|0Àn_>/kg,`yW \ĪiU]>g>S?4F]Z"!l6]5M{4]44pؒ.CDAn?#cS4rĺ[1p~2Ҝ}ciiiƼl4bݍ7m<-LlHوX_aKx {# Y-Cg&x 8дF!e뇉"Y;tpѢnݺ999:ty4]jkk^% 6l7|svƉzm%pDȚOgIGLu>p8࿖.@E?ϵ|?;  WY/"piWE--XܜП4Hn4`dI_͝rn5%@z^ءjǡ}O9*eTݺuѝ{𵑟o.lft]'( 0!$E֓IٙY6J)k:cL԰9-OHHOp8|x훾޸wù_ )/~{p88ꫯ}}isocksXKk+w7Y#d8 Utgng)s''>,(kph}[^jk;ÇK-/Y !FqZ@Xzt6=)Lfl6I+is]|xɆ)7>F^(,|Or΅Ŏ;:t萚 `С,ou]q^^Ծ}cUUc2Z`EeC- PJ$2}蕸#n]).І\7,IxWp8g4M---Сh۵k'BII !pzsG-,,LMM+/M+IU5mg㈞n_ |ru?}ٝ-T|e@Ƅ>q{_?1SGzCN/_]pլI9f/?|蛍hp wWG>Nm}]u%_![f<ѶU/ 1zhz=4MFhAtʕ+'qfv\&LhPTTԿ833Ng!Ǐk~a.//\.Wc`R)1 $YR6BVW}jnߙu/s|M 0ǀ?㯏p `Q}>_qq :#15hSRRRXXXRRv{N,˚T;W]]ݾ}Bp8 bNq@n5{[B'e&`eD=j{{sV_QVV7 P &mwclĖvt_]]]UUU[[B)2H\rN;v2--mӦMg]{s 㱤C˳{fH]@%V t oxV 񊪍1Y4y79%Hl{9RVVVZZZUUUAeYw<|@4gwi({)τMP79sVQPpgz TK>+Ti'e2Hw焗n2JHi2zހGZ~S#@e?Hi2O,e>B&Nk.. mp$I? k׮z %KBŋc4i̙3͛yEO7eîCgKRר8~ p]9sb6$`YյUus:k~ : X,_ (ù8'X4γ>Een;ACoK#t$a5>?|)'1qSukqL߈ͻ?B{2%RWtgtrhotn.rhw{_Fc##I F۬[neʕcƌ1=jzN9rСŝ:u2l>ٳ)K,)))q8'u;+W,>PePUȤ$X `H[ 2x`@o_mmUUi'RjQ{ix|㛅eLhY-p8> gw%$* 5ۦM*zKKKkkk^Q>f;3泃`Lxg3r{:Mb?1pÍ (Nkcǎ5+rA-G.fFжmے,E/GN>+IR]]Pot~qr%_r:t1 if:E[1`SO/s$*l߾RZUUe|B''vZMĖm*;Te-ᜏX^^^[[i<˲p@:KkkYx "@DWd*r:*CULfa5e`НZ6ܕ˒ڞ,˲)/~wp!QL)5^(Jt)hxVrJ$"G&hjnlp8Ncv۽wޔ#ISj޽nnON|.f3m&0`$,2d(*L[qL osZcǴ١.Fi#&4w~t@ `٬8d;\s83FQBlkm~if)RB@@LJşH\]20E  wͿ:N3:9`JPXXO> Д8Pih1@q+Ǯk8Ƿ" :N:4`IDp*ՌN8&)xL*Y0C?z zPaŕcH4-}/}f 2'_pNLuG`<`T6]},= LL ̿rlBVhk,ٰkK/1ƌ]g  t pND0IC7]!bAU h55~y0pk/bf8)W*ٳg[= KR@Q[_):WC(>RvY>Zcucf6-BFFq|qpZ\ cZejdV]9CU)ve 5ߐ,<;mhVp8svPz{r8?-1oVUWo ;R4؇;eg"l:f`4X h̪a=ݢ \49甈o&`L*m~Ѻtɓl;^ :Ư{ղZN-^ثzrZjZu Zqؑ.YeKI6|/p82+Wܶm[~~_IV]]-'@d$1@lj@)UU]z/3|Kc@'Dwp8@\ 0!`DMK\Y,Tns&N% RDe 5jA4 6 vIw k Y] (z mNLMkp8֧kUȱ#_KU93rrrZy㩪2@:ZNimzJSJ`_&r֐Li*g54AKF:l!^.QF6W{{Ixcu; 0]g$SH0]I^.',kRgN7Z/q"$ҙk`s_ֿóʢHa"0C^x~{{jw Zbjͮ{ݹ;D;[r!tNz r@@Jss|_HrqCY)@ gsc.Ц(a4MUUYnjK|U_TUMuD}ci)R.IZ%W(!A:_(|iה@HcuļОӿtbQQV!9Qec"Zg-B" F=0ΐDjr`IJ_uH7SssϹ\=:#hx:իWڵv*ƘӧϘ1cGĄ?*ghRJ( ޽{󟄐y}ik~fʟ7~= Z'tOzў-Au$86Mf}N _8~&'׼>Os<-S9xŋϞ=Z%X4EQ@#4ͥtFgd+X:6C__lX,\jz$f=QWakcoON[67H(Y%55M6&?=,uUJ/0j6y2H Netusz^V\<@ݹs,뵵o&'nSOoF]bI9$PYm"WY6QtK%DnHlua}oV%04?huZ%;e!1F+U_&[D`ܥ#.d`Umw5~3=L4ɷ?7w oiV^ePև֣t!)#lSZZz:b! 1 `* g #uhI,!w?E@fopdK"R2$*T , QQE29KJYI"UNha:Lש2L#2VCqcU*<-ZbҼ)Ʈ-O>el޺qݻ-~}lzoO<8ir#g<4aN_[ѝ@p[~_+w4tu?m3Ffޣ^|6Xy|<\cca2 7QܳG 5;ܥkVD;Ui}.,lx_p`'J_g`lY]_1O:F;V]:zv6{ߌz"DJ%*JT6AQITqoO;swH"Ș3]AS5FF"Cqu鬚7UhD\Sss|c}'.yFLұ?UF) cOZkwĦWoXvזַo_;׭{ނѝnz1u_i@Ν뀾6eYUUUDBViUUy9s樚f<5pȒC$Ҽޗ0:cpc aa%4&U?tV2^3$yFЌI۩1Ø۟~ElM6Ystp]е@R6Y pLhS>nz{1UUUÉEoFZ~8lt]4{ﵞt]4NJaKJrQAyĂ?z҇RgiLBK:*8M8rš퍵W]=㦗>zm^INyAn4a Gd)HNU:^}h?81փ=Ă;(`B%"Hi &L$RB f&6E~ا^iVR T @EBEJJ)p5kR3&nù|xQo$nөn5x7}c..ijw {\KVpC#v};sƿ/3#ŗkFuS] W>V2<)TOjǓmyo'-xRRSSSR3bdNxqM$u:lZoܞ7mt"2CVPE(c2猓J)=jG >5`mhƓ`yg/mס5KOtp#ipN& "TNrTJ)a؁R0J BcLenpY]Ӧp߾hմƍ\bᡕ'U{ՂOm)QM7M| Kg`m"1t]'ٰ_!LI$gRvfnRJ45(eNA˖|YTm=Gns>pC LK'תRqrC eWs&0&{PPDx qhg`9) 1$fO!  'Y#AcYcUUSC|)cKv{(ʱTp8NKZcU? PJ@t FmAm62Iy!rkT+Cҥ=(B(T|j;p8N pUל"}GVZX~aSn߼k! w&7_* Y !1f(VSB",(ݟMr} WT]|x(^yI{ f*5X+6{Gyx^MĤ$SןY06)wJaᔆAIS;!M۾=/ӹ},˰y=?ՅT~?g$?`?0>).۫rxMn1OeEqCs IDATS0]zf mϡ! uƎg_/(@  #@]!U }^JjJ(!``LV@'shClΆ? y86\=t;Ms|&tUg jWG_{$i.@ߵN.7F=,i~:':=)]֍Nk~%yN_[3 h32E@(J,^i@W`Y8lڍ pG<~ov V+ 5R+t[ŗ2 "(k!D@&zDT~MUt0P3b|=vPعLKY잕DHDj3ܚٶ8N+`>`2َ™}r vnqT@tvvvC\X55-N_US_0]!n }Lٳ}WVVfMTkRr03&Ν%?EtoGB^}Nw`psQ*3]m6;@&02ƺB\cz[-(BkcRmp{GxjsVw/W# 1h"PPJL !@i`eJ@HJn3+ty EM\!MzgT50sA!yjiltZg1vr޸wo|N#BZhxx3-HYݑॿ7{W uJ^7i9F.)r)+ 8pzթaܻucY}̥wfZQ.FOr̜;({J29IpD@ ‚xz⹫_]BWOvo'*^"ș@ȝU?CI&Bzxxf>50QII1t3fiEju=ѽ K6M} fE= (hI̲X W'cNS >0-NtR׏RG]8省1˜KK#pu:c8 8Af> /aVّDl1_|]˛ZshKx#hL6f@p1gwa'3eLyv,^_uEM&vy1`eS+.gMțTϱN3OfsyA>+mG# .k?ajɁ@uELf͚aSN9o'nd"H'=6Jt]l݁ 0 C՞#)2+pk)I~.D~gSGFB F)S0t)+9(G1ΩD!iL7 J)V/~zrB0MGYuUk6Nmɯ*(i)5*5\7}oh1^`5$1,Rν{i_5\Wïz'ń1=$B. 6W^y,I~-u=Ɇ[$Qy5g6BMlX"$yCj<=*2X$ 07e~f otӃDnl T30 ,f.ͥ}[TɪJ\{@H׸q1]cz!kbk!kiL`\x%U 9o7G(>Ѱ6q4mf@pgddEԣ8Ɔ($!iiiƍ#=Cf""TQ3 'Y@,3Fivv%.!@qnhHsjYK@MYAx\+'4"sHT,zߝq~-np&3CP!C3Ԋ] ?{\(6N:H+ jj|(mBiPnFڱcǘ1c̺TRϧiv+|/vJ5 P,K!ݘa@1\ vvELق5S_:{j³4f*MՕi˨=H-}7` y_ pKjNݮvΗTD@[PG hNcIfRwawnF&OC ,TUUYjwu TVVDiN^a9=j0I0Ҫ )?ٮn2{@Ƕ[HF s@e -m?3ݙO馺lDb2¹BAYyJ[\О2Tpik?\Jnq^XƩRrU&]A+GUU˕vE o94 o0psgJ(8C+ |`{2Nsugo&$KV+7JxvobTʆæRI% iM6R6uGny4:],-s9m\BTU޽ҥKo>`p$a[j$ICXpBlq5fSs#|N 7CJJ7 JC֥>{8+?zMgFjBYE@T+cݞ}`߯;V| V uZ,p 'Ph߾}khi^#ZlP( =$IRRRСCnݺ|=74>>cǎIII; fɒ%>hx㍔!ӟ~^x᪫@$3؉IXY2wu#iPS\I*_%rbO&?( 5tT2F Ռ]$4 Pױ2x>gX NmuݻwoYYYbb?[ (4qM :̧n˪dZC׹љFu7qwcD1[ r[X ***,YRZZ-st9)(ʌ3Ə?` 6f͚ _z6ysV7Yu]o@~i|cA lI1/]5Ѩ;l`)4"bV\9(j$) ݽ}$Q;!PCVlOpH4(BЃ $f@RN-zMV׻Tf>K }>ze|x&ka3\W& ~3/1f͚~aر ذEw V gZC;7yBAe :p@VV瞋p-{?\1,/[̛[oV~߬e8XQsjAC/[畕HM=4Y.ZP>1]b휹TY44]-N"l8AoV] IdIB2N3 pqjFXҨI##"Ӊ4J1M:] w:<6m0<ϝw? #ݻѣk%jQZZӋ1zuݔ"akѢE |F&|ˁ5#-*[Yb:w|Tj(Z79z,6-(q/rM7GUU-))QعljuFlEǪ*:'dIrmQd,*tȒDe LJv*TwYzMM"=b8P&FvV,X{) ]a@ ӧOr i>PhӦMю|W^yGo,G})um\k]uIOOҥK||vBVVV{0ggg_wur3W_//Ұ5 14_Jx97]d"9 =J1ߨ<}USB ׋Vޣfũ)QZS|؝ ,jqत-8Tbp0-RdZ*pXVfX(VlKPqIBbb CNe9d#:Ҫ4p)RWzmi 7njΑ/cVXNӈP6ivZ(Ҧݿ,K@ uֻKuMf͚uӍ7I F{ 3fje*k+gT=r9ȫӮ~!Lq=^y_'{qR޹n=|8|w?0 ÿu;N Ex `In$QJ9c+22*>hX,@vg:q0sLgWm@ h4"9&1٢j*AXAEW,՚iiqgA)nߠr Y P]P˲I޺<1h@o4}mxX$\bgn1 ŀz_ 3vDMnifr ~:u!cD0j `Lźs f0}tL|OqggWui='>rK>t۞><+N9SIl}/^lpQgnxR'bnxz) azq-5M3 Ce+-movN6@|>!r%߼1r 0c. O@ hk4Al$ T Wi@eA;|WG`K0t,/8ex;qX6.$٦ʊ,KV5'Dp2p裋p  aflony> '%r]?ꍶC/|s !fD":v] ]7t}g7 u]m6[MGV+!R-%T!˻# #39|02s@SۿkGޝm>p2G `#X/}sݡonnn4c(Gn ,e~\GUU ^t*{Pyy`~7d (#ZWV^V!%P#yw,/S+[.s:++8Խ^Po󰐏0:kA 2X_a >i71GS/]8 뷭x˼5?`;so͜<>^ }ی5 G7# ,snXt] q-]}ƘnZ*I, 69ڢ("IR-d=RŲ+]!;T8:x3ze^䚢(S,|g1@h W 8rp8,ib|^E B>n˲X9c7pg;@ NC!Єse{RέLz%حOHD$Q!dhU_zȯd5`%*D?5K T_A>= 7{iY29o]r]u*b<ܸ{=i>7#Z; \:j΄ <{՜)w&ڣmm~f:iͬZۖIMHTU5,ˑQм]9y{utQ,}Zﻴhân} 9 L\Nu )4EQ$̜Y`~]д `%$!!ܘ-y =Ltm$q O)(Ķ][C6j[6eVyۚykהlջs}پE^@vм=%<(5@ Ow|wk>(;Xյ: d!GmGpNX̙{ j{t3MYᜆWf2ء4Bg_pfX,jZ-5XV;r$SΞ^oix)hPt`dgi0c`낫9Ý^oeц_֔$g;=ɆRo@\Z Ӌۼm$Ij\UU~$0*++(8 0Oa@ 8hl/RT !LpgK?eݏȆJq\89 }Q"PY 8.T\"5|+u ߇݂U^a5}gu" XӠEin5&, IDATFf)U,#Nt&B!Y WUU۷/++jW~kǏ3 z@@I` Cq7/{NAn|zCf  7IVMy7Ϭ|Kn޹JКQ%  0p10,L\IIlI!]1y(;`>zn&J0Q d4 '4B3sE 1j6G4Ur=#"Јe #=[eU;ݎ8SZv,jT3<5e`<`Unw>{KPG7#bB ܼ_!<,^p߼;W^fͰa"́O}kZW_SJԾ}>=&o"=uѿ&.+VPuR}+pċ"Xc,>Hr=av8|_4 eGql23PtXnmMLJD7xFg=?xJV{w|P*Gn[ hI'Fq[B -X5 fs-V@0 }%yzwYja3M00!MyS[aPBk `Iƌ3w>N svɎgհ=|㨎 Gcҁ%%e;N@׬}Eɞ{ ,y*n7B_@(1PSB a9S1iU7+2jrƹhUO,j= M0X$Be]a3D+%1Fi`oe8;G")Gr0NQD`r.߹]QQ98V sLef ݻw0cC^Fkvfjz;aslqJ_l|8?Zڬq٣ YӋ]hZ:\vEvde#ACveddrlnF)($I$ɲ6n8Bѣ;t ˲$IҰJp"!CePmn+1sWxv}mrwe0Q d4lYPrp!z|]Mr9DGuu%cz0*-%V? Rdm[nKݳ*\sdo,7oM@qJ] ,QiZ( Qv1fR4MbI 01@7\S83>ιi,V5۟RjI;]3T 44527۽OjgBgKpǗUUWU,JKK;X\sW"QIQ f-MmC썵_2]i1Y37chȑe*55ռ)X,wuW1VYYYRRҭ[j@Z?jƹm^p$/:`i$6.4f(N2[PZzleF jXvBeUU,vUVY,J"m_@pPUrB!3rҼ眇B|˥bw].#'De6 +v~$I26ЂͰR L4a7-~]={po/mq'II)q ԭ;˟{aƞk ;i ,ԯ@p9-c.թڽ{]vVڵkמvڵjժ׮]ݻwX 0i Rz_jȲaiٹ;7̓Tcu+a@ 8h6H(%K|啕 ׄv@k,84W haBо}E9I慣 BPC$%%% :t֭˗/d5J;fgg'%%5)hS 89r|EL.tY2Cv'뻆coa@ 8ihC).ڽwj@ hoYX,))).+''G"˲6/Q 8!9l~&AI2=1F)=Zk!|0 x pXj$5[ T>SтRjSSSjHMMMLL W&GRa9c=&5V@p|Lԩ*+|$))߲e?|>E\0jt̬_-~챇/g^LIvVfnzhXLFl1߆'"tumgiXc]1รz^^ƍwxZէj[nvB").C999={lIWK{.!Gb%gaiR}-/;MX D9L[agno%eNSDal{w :TE_/yi۲?1[_/yQW(4n ꓠ6 uZ&]:͛7ϟ?ts9^{X,ۮz4lܸqɒ% ,=ztNNN{6؉qQp ITNfltjo>ce#-c@ h4YͲ,viq.WqIYv4]\MJN,--y6bU_x~O _w}ɆaVS]]vs̙3)#G4ފb Gik'RUeE 8\ZH0 $iBc6{|0wΰ%r9=Ǜ簦1/zu_2fy`ݺVWN٢hKjŦUs\Znh |ƍyyy&Lp:./X*n`^j=B5\qW\q'|ٻwowEǂ~8:;eyzH* 42Ɔ9 r ,l>cu~Сɉ 8дؙ_5۲bAt䧟yۇBU%v-HD Ful<|3dj,B@ІK,2dHJRR@T-UjWߞX4g/?-l_pÈ{`7B0튔1ΙqÒk뛨viZMK su]:tuVyVVV#+ _9O>q˯!&DX>|=MN:+mv{k߾}{Ϟ=m6[}07!'6 @SJu]Wey$e%IjQ~Ɓ`!}[Z@ )\Ve%׭]ݾC|IiKV́ {|T*\5vQU}~ 'U )Z[(#.~ɲ,cL4Wx|^>t&|ۀtMM{Ƿޝ6v݃/`/2C>׮|Tg}gI^x}>V{=gA$I唔*Ê`SqĊ|~_n>zaC6]}/%_޽xn{K}Uϸ\iKwVc~|{f̵梪؟6}ѣ/>55_[#Ƶ~ORWvt(ڻwo7B~'؍S4 %Iu]QAE@s%ٰ+, 𶐽фNYYݻXۦv鎃Kڟy~xf]:W^ym;}J=Yɏ?- M.PrKD)5>Y J))0kF+$I%fa DBjP*˲ZȖ}=C0-X[hn?cV}=Տ.($钇 LtRß޲ÎK{Rr?ZQ _|-f7_Yw]~Qy. >gi'~0JISFLw<Ƶ?}}<;P(tO `ɒ%^x3ƌS\\ys3Ϥ{ƥK^x/\wߵJiGe7!"XaeR9*;ТV 6CLsN)=1;xYyu#&gvֱװ3 ?bgviq=;wnիͦ\VF(,ahBZuΡ5/\XTDxhg~ÎRz9 (?_?!~I$Y -נaEahۏ{FcNaЖZ IX:uYgq=,CN?rA7|3s2\ԕsܷcjb3z &Qlu`~mY2x1-yCw\/.HqN/]Eq:F(>䓧~n>}zQyϞ=xFG=O<ċ/ز{{ZRO5| <K jѸ6%Zs4x}_(U~^5k~\?{ |_mrZr(r:m(@ J8fpXY%Y{'!/-!;SJ$eeoʵs\8ߓ{XMKH)3 #YOe߾g+$μ1o߾eșL'-/OIlF\R?t/JDUWv IV ם߷\RXښXzu 47ވ f@hB,yC;Ծkv%YUI',]kRi裤ҍ%$RZYY"}mT2,u[&vOBO3W[}EZYZSN͍)onr>pZd=0%^FGR_]]m&ƺ"KEwܪbcC@]аy95m1>9%@95>6pic>^JZ<L)54`y^JY%{nh˩(jؿ~ש>U~9sϟ lcHj%CeY`rcKB/z0C50«nIINLsdqYTK9릩Y+=+;]Id}~@ XV/4F6FM׍7+7zRcO|Pʿy\wQ)KRK,XS1o$ERjFւbF(zH+xO6>״ 9c{%9zM/7V+W0J%.eUkZkf@[U^t֍KtC~NX# L{ pW'%^pǦ8%;g~yeF׮5%jYaӼ;[pa\\ܱ!Մ@K05ʲܽ :x%*d&V94?-35ۗ_w_ T3YΏ[ų/#33#~-E,kOOOZle,hq׭[g.**RVXp7|p{f^5!Іa* IDAT-zbYh@pDŽqVPpvI!-+OiEilzCn⥂۷Ϩ߱kǸq%}Wd{@>#/9s471Ɍ3ˈvxaRjME~L!k@ !.+77wѢE|u]gَmW%Е8ij0|w`nn9yTWtpOX]{Vf_^~Vw']zU1|A8p ++z";r-o7nhܓ3.\$t BE1 # ;j*2Y+@ ǃ|% q[]t񟆼RuWܶguR]}&k .PnĨu|P(Dkp<֫A0Æ [x?|ϬԬm Fk? 5#wU1MkXnٳf>f=+:d>wrszVk*l6[ΝP' cfaL&4NJe0+aK ~NF?V횋=RUUlٺKWm T%ztHJaN7zX}ˆ%ӁHFŒ5rȕ+W몪~jE={-[jժ@ pꩧq6it 8_uu3 tvrvnX}VV;] 80]s.IR/{'oa,EQ K-s_>O t]m@ h=SJE^]E@Sv{wzꐦYg$v =OI k^UeY\M`:NVkBBaV+:LmgXQľvDLqWt7Π4`E$n󌫓w}%88.Z{ B|iR;E&93s4eOHt'u xEx2-ms_)[@pRQKo)=Q~D\p?~'˄ĺ0nqӘ{ oXoCjonu얚bQPh׾=_oy+| EQ$*į@ 1u,fKe3ZUU7܏qOw_Ak Og.]zop8YXoZuMn ITU%m;L @{c$)ҍfw q˺-Zn~啗wu@!!劣oW7B7HeI#w W Zm=U 8BW ,5MCD4c,0&( mvg@ hUػ5WeY5K@h(c 'ҡ@ 8 Dprz[Y@)53#`BcD!If[q SW?=LҵҭasOX@(, ι>vo[GԩSl3 5P| W)k@ h5J2xzsSTgP,ȓ'\`aH@в$$$x^xWC億q zc$1M멪zXDž0g*g[6Ҁ[ HP@H4P"WP.*\EX"OPi"U4`A"EPH( =-,K&4+{Ιy9}fWȽ^oQ)1&7Xy0d?gf]\պ`,@ʡ=5s#9b׿cܖI]5%\d02J_yŸYT\| īR$&w^Oe=Czhh Z3599DJWA ٢"EyY4\qtџj8Pu3 FF!f|}>"ב,@Y\تw{REJtR*g-QY%_Crf?!m& ILF Fm4HիWv%ITUVif?]Uw_NA%SB8^x_%9[9QozEKZSG2ZmpeTw5 R.5B+l`]<;NKKi' xWK}r֋t1ѫf0hX(Fx0aի)S-8ׅ~8+꫐JT[gۺ"RRR7GӦMjFu\M)A f 5)vDi*s|&-ĿNH`BfP!2;vʒ-;-3׬yoGOx!%%UҞa]qr)n22+@IvJN\z <==1 ,6-!!_0VA*"p'i FMRL)"}d]|fNQ*_ƃGkZwټ}Ç\j[hѿvNFh,jkQ#xZ"̹+ SC/];D:&33=|SD_Vkr2i5Bȩ>#)׮eT*Rt/Ӱ/G |JjΟ??߿HHn?>qM4V VA3(.~ IyO$sꠦmIeFxZM)R%߹m7K23!Ʉb`0 ,SN}W-Z\>o޼}s-nߺu+88Em,˲d Pnh5 ƃK@i/:XkT1p%-*ʮ"ƿ|p:66;=ԱJ`0 Q,]SN999 0lذ)SO? 4hŊÆ }-J~ؠA͛7; ]6hР?xNҁOaJPvNE.U4Ug`0J"¦Me۝9qFzz:~-::㋵V'LpI&Iӵh`ty~~fa0ՊK`B@{0qae6%N7*WyXK`0jǤI&LеkWJb>|xƍg.TyŊQQQ>h\\W_}`ڵ6O>)o=a„G}رcVb)@s&ꌩyy!5N^ѓ (*VQ (X ]6MDRl]BSTعs]\WUpo_N~2) AڿsXqrچ5Mjm&=IynE̊;um>Y1O~iooo+'MݧOnݺ(z.]\M4^;8'Vpڰ}oXXÆPB@xpNH(Y+|2蹄+t-GtKyh!V8pQ;Xv;p*;w㮾.eKp6謁 ;:<0pѹuQ"g\j׿M %IXڪ-uř^_;4j#v 08e>p)l 0T[UR@JV#al3^dy |˗eY(~z}f\7R7o^-{E?>^񁿅3u u8ެj&۷/F}f؎=Cǃ@NEʟ,RȎ RPQR~I6IV;F+;u։x֭*}JYL=ֹq](ӾKS6-..`nn.ϗyè_F01L}b=56 İ|}by~1.m/ }9㋫bпbn*i6\}W2E?>#>@څ=Q7j=uʕ[stj^0ָ[DF8s;(&''TŋƍSvFEEYVbq<32~y;At͑j㾥߸$ 2LJB8$XJڪ& ҭ4NX$}&t",n& `"tFrIn:̀Ō,zŦۺoȵ+Vرr~mFk+FxLq' N5ziU ,ВLN8 J+I)k,55H!Gx $H |D@SC!ɐvj`1jL3ue/C$jZM3ͻܿG/B>U/Zt:M(y`!oř Qu@UӔ/nU2^vvf-O[/:ZpxtՋ.bO[v0+e.ܛGz<zO{G_n?[n?8i3 LN֨.+W]GuVōfiմූlԩ7;}ALڝž>>t|vsOo3t]vz`X(]#6;#38ٌͶ Gϙ'J\túd2˿!y@m.rQpnPE%`ظqٳg/]tΝs>%fZx7s.`< )d@UGA vGoGmH2)%D!T*ة`Fm)F*֪PUMm ۩BebG.1()`ivNXu4[qm7'E<}Ӊ5]X(WRZd qGM ?Lb"&.6%۹K7e-hF/{cէjSz䒰 ]'-ؽ 3wU8q#q e"śv@ٽwVBZ1tpp]IK̎kP u JԩӘ1c^}վ}2[Ghg˫aW Fuo~X4dc6"ۨ wANv*۝?I+T(\YMUz \O%ݟR\fU {FO-z֮;sԤ$6.!G̲Z&Yq ]t| iiE= \NH5|#b܌nHk.?y@grزLW}t΄m9 GSҒ>i֭^ y|-н{q|]gf7cWjN;uZn-ў'//pQ DN_N;8X5 F5M^99jQ="),CL)?p6[;j]o9-Ȕ DRһgQʲ,vvvH근qڢ1aRIpL抴s<ΡBϴPy.+1Pq>H cV]n^T$^`vxy;g%""""f@*:ahꬁnbi3t-G޷ö[Z,֒4it[}O@}Ã֭#FX0w-O=x<*zf @Ĕ/kC|wa10e֠G,XnX㦍W7'j탦m5 M40j$ >>`-Ǭ6,3xu:'&U^s^մ? ([+a\P@@AA lBEIP"L)tY  |U\T^-$\Q5be%M9 VZXf0eK뚺'{*iPg7$P^"MpMǮŸ5#ju粸;`:i2߸ 2LJB8,ТHm6Y8YJR26I; I<$c2dB`pUTO`0 F) | ty.㏯b,]b0U X蝛47ʂ@yRB,&yd ፼\82dg:RO]-0X`O`0 < <f1]b0UJ$\ Xq'ɸVk(㨧_'[! 8ΥYHe0 FyYd`Pܓ~E$>o/Cj+QuXL̩53myV6~;J7%Y#DB8PF%TVtb*Ĩ |Yq.x95k^5q? 0eR < +B!)Gh>Ĵ^Ic5Z-'I%DE9"T4XY^T@2 t :YKtRjՠ +ࡋ\g? 0?Iwɒvxٽ}gDEE;7%ܧH Nqlǖy{XYL4 K`T)=ht>t^ f0k\LLDAA4zɗs#jچdɕn#3;&}F*4: "n]-uQ6=mU=,RZU' ةN\)XA[~g?3zM63 XNW1J)Z|gJ-IRnn믿^RŲDŽPJ׳ĽJ`Dn)+u,4q?S& " "^^PCoiNQeT-TIJTBJ2O]T8)S4[eU B ǥK\.A8?ؙ/L+`H<ݚR ,~}ھճLJJvװ`өa7~08{z8ču8{,Ч"Ꜧ_a _GӧA̎7thIx̯~Wڧ /q5:t`4G3Eǃxk\J l޼OuboI֭[O8OQJ !_|q|O?J%d0qR)m$O*B AءAHTIJdVb Ty2q`W)Su}C"8]SR_[tTBr\,3IѼػ`ɤsl8j݃O s_~d[o⣘5cz63:!ս3?΅[礝yg[/}{kk1=TYh=ѿ](OyGg~6@؄eʨ\ cVtFQ]dzҥ3fH"ڵk'OyPY~}~bj5ކV3J1&1/8Ȋ8Դ? (;,p܊ ݪ Y]є#DT*oee^*ӗ+SQԬ{l0ぢ$e/ IDAT-=xL[:;ֺ_//GY dsǭ?R?.>޴݌Ki^xEˣ`OnX^%Y-RZ qo=~xƍ##Tez$IrQ3%1 $EKS0*%1J9NxWO]k?UֵT_ba)+܋+}ן n`mH فkMyG./ރ9N(.I_}[}lzX cb6Ѱ׷[Ho^dž#n)}V/]?"n;rcw$wƑdr_K!㊘IJ݊((J(͔={PJwޝrS$IEQX^E"fZMYAV JOVP~-ݡn=Sj /aQMTRCVCwQ*8?R`)mCv{c^S|M(Q]saGv0nÝ˭-OAZTp Tш(JOl$"-[N*JZ-+Joni!Pl6.0 !۷/odYV.jO@@lKNN6͢(*EBI/kZ;C)]n(nݪتCZPopz,fs6[qWz/Q bf?ܗϽ}-v3ƴ6Edd̹a>yè{#c [SMYGFZhyo w c||> jM5Ck:6|3 .Tdgs~GVz=Iˆh^uoldqzrk>\|Iffɓ'CCCKp.]t}j0>Z>$nrYga: 9s~ߠ3|1;-FJ*V'ZlYVJJʮ]^~eVR -)AիW1" DQLNNnݺuܫW^WES1܋/૯bg>d߯Ndg0j5UU4!C|||PǑ"/ @eFӬY'ߦMSY섨W^\:t[%Irk5к`0Nry; 4Z Ш0eh, _K˿ 3'*V^ݻ쭳X钄% 06"Npӯ+[z|Á9~?1kLuiMmΕ?7;*)c`񄜮3sdFYTѩA孿/ۧaWcwGxu1/IX;ң*)K,.7SQ_|z0F-`EMWnqfV0XQǵn&uҏ?Wk I`j\ >}Ԗ-KNNV~dvž 2|؈? +裨u&SAwoU1qah?l`hX1, G^<ؿ'lRxo?rNi N^0!̶lO{lj˳RcH{XrTjʷYɽ6:4Mb/qU+Wь9rԨ=ԱjVũ@$I6:uz=^ѣGU {u/Q^rǞEŃ -턥.5θ.4R@Lӫ &}th^dH`LB;,h0|Uչ[;YEi[piK.< akS\^h4BPPeGuB34bNd}駓'Ov@`0\5 6u ?CϿ4r\r3-S9 RB)qjo!r5&Mmٲi``F ]ҡ}F+8 {w-Z ?hɱk觻yנY 32r,^A j|oPh= )@E+ʺÁlXryޝ{"IUlT^ `BB Pd$IfWA|4ldWxv`׎ATLjf}CGuS&ɲʹZxXPuEHr3-{ijBwʑO?3駫z0 w3ίAPP] u^~ o k>TtcS{kC:ǒ j=ɯr4YWn^%W-HmloܸQF\"իWVkxǸo֖yUz5p7e `ewO?PXDž~zP'ްA'DnmàUZ<9"qZEv'cǎܹ3J=ᄐl)z i)\v">`07S=;jr.2:|41oɛs=y=ξpG`=b<1\=loǭ>F @YO?4hР+V 6ηV?pg}/T ϟ?߽{+VDDD>|&\~Y>&Ġ7nԴ; ͔8eFς=EZ4,/Y`:t=_ƗYgՄ% ڵk(Z6}:8"˵6 n6mڑ#G|}}ݻdɒŋ;nٲ%,,7EG  ̙u7xo"931@t8彴aÖ$IR*}95V/<ñmip5%JV)K@vc*YjO#Y-L' j_)<'íAReˌ$;Ss%cԥ4qEڹ]U?3<Յihs\l-*`4 &|v&dJ2ݲe'AyMMɎ-GȾdJ=+A6LgWYnGT*J5j((RJy^8jSN5nX9[n֭scǎ<ЪU8YvՈ8z97SRj#NJs'rƍ[jɺewpgDK8NE GeUF-=\K3)qs&.,jv.[ǩR*GkÁZo,O"לYjH$;"'*1K4g(q۵xu (+E>RuŬ+\\7jl7jhH-]WeQ& $''>C>xFpLe5:fip,<[٭[2`0奌=z3!)irmL\\ S{ׯE"w 3ik٭g(?)[TH ų )X#şTĮb NE"*(H1H7!d<%˒$|߯p0/Kv2^蕊}RAL~=ܩcS?Iv ݱщt{UMzgJ\}SxdB!Mb8ݻw;vٯ;vlb;j>|ƍO ###==I֭[@Ӵk׎3@ttt`۝0&x&ӧϖ-[!䔩#zxr{)4ax;/L:X#,_{5 6ď#抎>٣]%w7zsʼYeNg)iT)_U-ǩ]jFGXUN?8UU={M  V7Ȅ: sHbc4hPRRRp<^0l"#7Cs=7qAڵ7xb /L2eƌfڿM7ݔ੧0a_׵kΟ?pݺ"2 28}b}Р?dX޽kYs^x鋎.)Hl[숹|hksK7<<˴-/&,]=-`?Zy'~[Q0cJKnFˍ!QaB%K躞4Av97n{"-7rr:<0fV}z9[޹sgUU[ngj^PPgϞ̒`/IСCccc#Gwޠcbb}Uѣ;v t:###%~?33]vB?&OC ~tqcxnѢOlc 5*&E]t:IĪۂs0YX.(e<?y~&:Bvg HVkΝkZ:dK3M@mXlA.z V7HKňrFլV՚vu&U#.*,Ը;9ͮl Slȏ+&}oDI#8ʹtyˏd tԵsBQQ>=$B!`r$.~+?LC"u4yYtf(_a<ĪhdJNIva]Scs̺,dcHuiNB!p ?9"BȩQ[3 qӦe>~q[yrR)O+6d̾|>BN`! mmyٶ$mY׳ݷ/w$)~Bh{={Q3ټbWĖ}Qݪ̲HvN!B! \\ 2߉!DM_>뎾٥lv'V# C0J9\psp 7bo؛09F]>zp40B!B^> D),=J/B"Q}Wg8Xq= 4` d3գE2-3=Yy/08};~Ng9c,..N9看꜒'zA4oՅJV}+BH&sLqjޟ|>!Wee/7}y]GۏB`f0  B״Xaפݫ2o%m8P9lقo 0h*_\K 6}Z^%!B-%ݷot1BӴpB >˳ij+\U^oddd"McIgmX!_B-9\e Ƹ`0ksvFOwMNO{6w[*Ih(O;jBiױڟJBiJ(IcB缸8QrO`I-~Q wH? 0q㶜n[_r0, (K|[/9׶s<^A~_U@Xs`B! ħ26\gے˧Iu)+--|ᎂ$`SҵhۗVgzGC"M)c߳?&\2qf9d.py,'w:CŞFgVأ5fj^BI@؟ ěgG+Vl$=mrWyd~៾e۳`Դ9W9f-$I:ѧDHx`6wowo7~ߵ… gΜ!Q8_m[wox}t>Wj2CsyӞ׶((,9cpv23}*_^ryl\zڿ懖-R:eh|H9P\ o-E?}`قkN }_-X ""BOq|S2 |Μ95miھ}rss^ohbi۶mJJ~4e`Rݼ̎!nf wtkLx{Ą {۵ρZ`R 0!ukP*t<%:u}Tc a!}+բ8m ˶uԠJ];yˢۦl7O`N`Ԝ=uv;rG^ck~bָuL}MMM5L~zzzMk5M۲eKllȑ#v{h(om޼yȐ!?U՘ۆ lBՑK@[N!jzX94ȼ@eP \BBHSK p!UrrC+~Xٵ< i~Up,xF,&4+r~_ܮ{3yQ+S.X9ٝ+||sWzZj.[/\pA JJLLҥ w WRRRXXX*!Ğ={bbb҂%ݞe˖={ݛ ȟEM.8v=;\pFi: 鹪T2ZՒZ03<ѕ8 ,~+U#֭} -aJvXpAڔ.?{7ZϯnGnF IDATiU[p9[هV4sFqO;ߛǂ>˾txttqdEeCڋK,o2KM卉YPUEW0L5jVPP0tPnb붭 6ҹ4>9F8GV~q1{x^e!Q[̀nnX $!Nl J-1&]ufU"u"!4Wo6*/0\sv{@KUly[k0~wO7 Xyoِ la˝yj^o}h/}GF8W]|>-7n#bsbp#پ}###ufB^V$,,[ȯ瞃R㉈t\ᎋ%J0 CEv7 Uk'7]zG^֡e*89 m&qu(U?r9ЁUwˇO끱Ж561J-3t3_)0w0'6G{\^R @# co`[VC9ZrRsE 4$redd!vݮ];*0 ( MHT7|oH8̭SLHUG p "eXuռ`897fU Wm, !I U{n4XJ}t׳jrcu=O]1Mޮ*W,jKn3MTVJ6G[mHc:u65+,ˆaȒ9]vΝ;,Ѣ&-$`,tuݘ@ 0!-S@ RƂ/O|"Ȍ!P8rjw v࿡#@OBZT]U`IՔ^,xuِ-=qoI5c}veL];64QUH~ݫ鍼fo^*[>rwI5ve1^bA_Bn%60BlYBǼϢ]!QHP*nY@˘, D +1`L; @HvF}[YkҢTںJ#?Wt՗=aYEY#LTI0#/IcXRҦ_l˾OO-+ڞc?#ykYJ}t>(W򳁡 j(oZoWjEQpeY/D6y/Q  &e C8Q1@`2$&30JLoR}^=+́T)Tqj>B :+*4`ɝxe^H=hioTlcujxFv%JϙO:/G5.&oDg/i>;~_e K`czn랻yr0XΝJk_wiK_wԛjIX Lh6٭Ԅ8u %NJ SӋb~o9*rYe`м&]"uHBQ)9Tc$ }Jh`BHk!~6")["zTɐ}9-YdРAQE'0;Zf1 Kf 9=Rw݅8gqY)IP8$˕h+B ]! ZoW@r#߾}{nnn<66v! T|y1&]SuX:z@x j=% u$ΠKP2snOX ] Lrg{2b4@Hk2fOBiZgW`KMM u*1&S;pRR㜻n (|U\\ΰ! Q[; 2) Ĵ]Gqݹs<Ԗ5Bpl \`XOj~ƃ*Zۢn)7jueY$Icvw޿KݝN'*ğ>&d2iF#Kj-s4attj['4[L2P,cx>F55'" ":W/].6+PXdCЂ!䏫iX#&4.t3d>Js0E8vS%ҵk=z^ .c<| O"_}90D}zG:Dj,$ɛ[x< ݦeVPfDـk %TeBHv8h`Deɔo6VkfɆax<|D-IyR,., wD-8=jxǰ~c\K/&l4DP5]I)ɶ~J3~#E0mm=׻VPk B! :s *UU233۵kp8m9eeeGauЁ%M3cj\4!B+/SoBZ:kP!Qf6Kv\¯:B%][92cM1wl3XCۄ5B!5٪6瑺j?ծbl:fee|`3QQQ1116цSKJJ6ȣG:ZXTT$vث =999IIIᎈR/uƊ}5\$DV28cϿr* u&5OE9kgt|jaʁ !?,555!Qr$v_[$Y7}kΟ?K,z_}tt'MtW4mܹO &n#G(&4HT'~\-3cah`VMwE J>_[}NĴk`Kާ&)۴ŝ͘)B!cd2;zu뢢>䓧~:4}wsw>bĈ;/11O?圿{222;Ҏ8 ohA9,BH]AB@B0r-+`B`B\Y+>3BL Йu~l79S~ `bB!Vm۶mQQQdɒе|Yg@Q#G^+f̘ؠW^M2t`X.oH^i>]!-H `$)B0$G00b9$;$"$"w*IHd(B!_NNNmڴa}# G;vW^I5d J|ƍO ###==I֭[@Ӵk׎3#<o<R:= wH-fs`Le  A0&` !G&\7Q#hBi^\_[ x_e{7q;zqs}`c_OAiHU{nĉwq/5o=> `̋Uwc&v3&?@Pۿ򻒒^+&?|Wu`z]r/1nWU+1w}}WRҘ1cƌ3yƌ'?|wGr^'wTуh偃ͨ.BHk206~70GD[@B9mTz^tiҒϻ?zor齋oxk {0>tZsiMuOW,<}QoۢYӿc6uz}] {K?]46x  Q`!-ݕ[@1t }R;J !7F5rHN̓,qmcCFAk*445'*M[ǡ*o[2{W/޲ɛso|bGd [ vWD䢬8!YOS"o@4E,Kf75!B͓u=`K]͝o?xߵC52^|vPZZ\%<=9Z>tPvnIG,=~{ ~+*ؼ|b]=!(^!usOmcf8!zr.mb]}n~y{9DP5l`ѕ ػ͹w↧^陷OŜ.L_ן;8m:k\5y=sNmK6 @1%WBZ pp5oQa $BֻwR͔@~+z I3S)+r mGyom!Ѓ+tozG;(B?UXXX䢋.r:T$VEþ &1 !gU21Qq ڷ٬/:`BiYM6hWWڨC5‰wWNX~駜пf—^t|X_z'􈌰 "sιBH3׷oT@Zߟ|i㏾s`]t*& ITQ"4()-[v/PU c'J=h%0aӁ&йG.yk(&?~戸cW|?m]ӣߨ6:u|ˬۆ _ɲsL]SK!-@bbb.]MSRRs[_qð4լzkC0@pU,sH լeXں;~닫CB"bՔ?41* i~%);+׌\Ys~ɇ]LD7]IܭSۿ/{dUe%e>n$&TK!-T[&)Xmxݴ{^Ms}nuye.o]%e`u}^Mޱy<!с IDAT_!qoMin$Iwٱ*w H~;~r&&UU]^{ڽ MwKd>z|~Yy4 ! /Yqn7vEtM!12ݷvԫ.݅pina6vr |k0w|>J injeZˊޝsDGG9Zp4/}(-/>)ص=_Z汛~10 !4Ke̿_H]YYYLbqChƍϺH_37XqC7tй!,V[L!4WVq'z xW whNcqQNgddɝǂ򋳳s?r$HN~vNC9[wyG$/M.A !ٶlټɏ}ϗ}$!5sBHĘ5nϤ~_urMVԠBZ@E%l4sB!b0$& @ ͥJJOdaB 0!-[$0(o6//oX!9cpF9Mz~Q\f m9f戎!״`!놏;3$ȖҒﵶrO^'; ~gZz?ce>wC`K6圛!9~[( @pB09"e{]bFnc٥Ç}>_C#W4HAi~ ]v[aJ1>/5s'M[r`v'/q /ļ;>1~v@څwO+z:5 ]/gWN\Ӳ+ϝ]C-\p{C\1籘eYVJ˽.Lbu<xXrI&$$ܻod?U뢊m۶Ç;:BР1IcLH缸[ReIb c.Tϼɤ._jFu)EkEu}ij3`^9M_Gc+_j=Ӏ2`7#f/+u{+1miXUպt\e״aQlVUɤ(YU%8§iɫ`\ŵ= Ό~raǎ/hРAY!afɌ@](&fE1I*IdYUdIBU?>S?:oxj"wDpjy~q\д2`Mba۝ePQSFv7DrI٬iZC# Rdj-1H۷m{ʄ-AzSn ! Tϫ\|U8n?6+#Uu*XO:ȀK%͎GG7yMMUYVTE5YL2{Q1o,nd nȲ"T5;4ڝs4=22=u,&l^f;4BHcP|']tiӦMvvvlRP-}r`Z.{z۴E>{db7ۖ,Kb3f;?mٻ31잭SQ$"UEUT0Q]RطkrIfU3)PMY/FZr V]Fl pn;&&&ܡBNE\;!\eÑb2ZY9 W=Vvℐ;*$oUΑ+imzv `_2u@'Vf @5' U@ڬN0/bҳ{.1$MYUEX7͈oW)!bVUIUf{'A m2K.ls0Ń\;.B 0l6{L!G^|o~k۶m"H;v233E w=o !bVū+v=e]:-W׼%ĒG~ѳ^?]ai%_͝|H*+"*:{^g\~  CER|_:!԰ZzAa_k;WxWaS;w\i Ţks9۷+:w9y!5jԨN:-[,;;[ffpXCtvhaL&Squ$1EV !GzCβ|׮„$1P\6LM4!Oy/~_lֱW#9ᎋ4q @QY[CaCرa ѱcaÆ-_\e4^6Mt]TIA{V!kNj8_xf,Lą#v{%QB 0Z2>}DŽSBѷow}7$z$f;tZ,s`NdLv{|[)["%&His\V/ᎈ%nBoF}C iZyy+&^QQ;wPU50M;pxk:bbbRtw>v/DG:N!DIIIAaaQaсXrV3t2 萜\ڳgO|||6mڴw:7;gI:uT͒wX@)":SgnJ̟zॿvm|]Aޖ>ᦿZ+L4~?0[)P"o~?R؁Zed2YVn6)m~޺nݺO>駟~'k}>}q1KLL9={vN79E^/:1N!_u%W] 0cq͐Tp. !+S9oӠՔR۝e rDDb2YYY11gff1Y̌ItDTդ1ѵޮ]8˜̔ՖWSok7{'j}KGMO ҟd;w\kɒeS$YL ˅Jvn= ;9ZϟGYmdBV7V(>1IVk1s-`Mo+m۶䨨(CkC>{(ȑ#W^}UWx9Za@WߟҼ<9ՑmÐK g5lM7 lr sYVZV[Z@j:pƪ @@yBl6q$''::ݏqFF+ tcf9>>s~ȑتDGGmVQ %ŒɩvfI0R]2d_}>w|9|5QwF>`8B&؛@"݉ uG#"tWG|<3 Oq,˦F$\y C$Z3Ju2ڍ]NSQ@S %%%<&&&8q T30YGQQQ\'(..n98B<Sɳ_i^*XoojBWG3 қ/=zf_ZeJ=i[yRrVy~l7_u{ 0{v g;o2uʣfif_;V9ao~7xW濚EΜЩJ޼_,} ?~yDM{q@]4=lpmXn|K3$y^~?aD/r5C-9{GNc-[_vO}o+CU{|7MO-}HT~w\u#:b_;iK.M=ǿƾtרZm9+zKEI*<S}v9gӬIgLzߣQv8Wq^OOQVP;rP7s<JR?Z?ot?X=,yju =@/{jàGO"R%oK%11qǎ5~o߾6mHOO۷n{ĈX3Cw).. !VQdy[NUB`!;y_&I,3UU[Z |\c֔2OY%I ́Oy}T{.iB:߽Y B?p^^KsK /{<Srl,qfI%HҾlh:7W٦sCn{pyvk>e/J۸ޯk>=gL}` ~#ۻu4b7:=Fzea_:%G뒒CkKUv^Ys8h MF@'kD@O~s}JY+z3=OGwT4[|wqQs>(c(* %X^QfTRiK55[ӍV2,l6lKҌZGF(m.g~a9y<ܢrƉ_w_1ůx~\^ +ѣG=z~c=`޽(qAmxri5G06xiE2 :.^vѸNK-Vp8q5؀gvA\GC=X2_\ba{p |H m-=[Wc^ ŏם;(j)fAa*ɐ˓~A 0~0e;5rx~K!!qE WX1cƌn_~),,j*ә7sٳgϟ?СCJ\D(LN>$ėl `AI$ R5~!h޴m?ھ0 Gl͇jO;]f;2'zӤH Rɽ|޽9 $*qTcnF>ϿxO4Ƚgb,C?%-uh`Ԅ 7FqF/$x5g^PmW_Gsey1oW ro\u׷e>̷d$+.dK_.xˇGhFޭ/YiZ ,_HȂ!Y Uj\.BzIH\s^lƭ 4OeJ5A;{曏 Ec.UhӕkҰB1{)~_R49-'AֆڭU&l6x:[NY4bAloWq1TKKht:1rVxRKQt777;F#,+Zj: {q*wlGlٚt:/B'w9<4+)WV+g9I;2dZ]PPyMx!v$hIJ#x],'h\kC@ԬNQ7j6RT2!rєB:Zס?A)ZFsZfwrG;::K\L|IF#ZX ΝTss'|gkkfaÆ:='$x .qűތ14z( IDAT^0aB_+%!qqӧO{뮻%,хs@L)US +F\)U\RF)AJS<ǫWAEӷj&qr|:^_B<ڱ ވi4f*d/K{Kk$f'$NV@[[$`>.BmA8I L"ouh_&=iV:-N$ s8]\p6qn"ޤ+[iov$Ap-h̐$9 p3 UL έN4tJ7sxDD>"]S] 񐐐sux>*Ny!06󣏝hbnzjܛb#Vװ}>:) M>=ULM)~:xv] 9ǥH OIH-C⺸QĜ|u#x3Zd󼻹%qK-oHܘ!qm?>UG F|tؽ߳zhl6ۅSSBJB&jڞJH\4&!ۂRƽnIrϑH6T׳zy[q KIW_jW > lxSRHVWR]_7ٺukh&!!qma {G !!!!qe4}+xG*U_+%!q q.pQg]<9w8(epq~;NrW#UFBBBBŰluƫCJIH\C,Q2.4R#(Rt'SBCx#e2=/W=HHHH\(­>r:>M u:Z H=ө_a`AW\/WdruU욨舳^wgơz]ĕEUg:P:Zjrn8&5JX/x]+B7 __+_7+-8Y [~C\K_E>wX<~x}ٍei kst&Hn:q,EQ YK4V?KBLQI]/6Uo,ĭTS٦4Ӻ䘳-ԏ2N\.qQ&g sHB7!@$)TJJ(R`9i $) BTdo8Q^V~/8888843CBBBRf͚<9b\{Y!!!!!!뾣Yomޘ=9$$$d֫YTm|OI!!X{Súdzj !!!gǻi7u}ĵ9@ P= Ԩ){2*'َ> 3Qybu.WpI~P5߶?R5~w]3_dS>}sq^ӌ{襏O8_c`%./q΢ǦR=ך Ӳ2URhiq3V/RYgw:Z 8c~in["aj^\<./%i{j"q߆VVH͜UmM 2J]yr }b"U/޽ @bJ Ko^Ԙ @ݞ-@F)Qׄ%3_W۷e `H~<).w-%r`Zha}};ײ%/'?%Q7M͚edzkmogT/>X;wJTV輜| CyAj:6ۖgʷDgOxtPڟ7_껉 Aᡡ:ݙ}{!ei|gA80؝Ĝ k=x뭷x≫_RBw g)/_(/$Ih yűegExŐeʮ.5nF?x<4aΒl8la"Ȕt5 򃛗Z'®TWa=u71 wiSq[́0GX &ϖe)w~5-/vڸhm}ʐ`~E%Hʝ7:fW]YU[r+ӳrs,y{0$y~Ckwhp%eMTS' UJ*bL{t'[j{|'I]iɰhQ R3<\fRo*K9յACtH-+Ge q L|`j+2sg򲲗^jhXsƺe /\Za._X;\@$y|{s熢ԂukG * $Lu_dbo<]F0Çcb:|D/%B&SӆjC ?$ JWi.P Th!ڐڐ mP)S\ /.,㜕g$tR e` nԣI[7D'%'uQ@B!-'=L/S6aaa4`ZxJ\ WLsQL|ZXI(/Ps eT.2..L;cVc'`a䄄 S= /iWhe3cc"^4Ps 2] ]H7?HdgWeq[ *iЈyZ**yƔ]!'yɥ:EU Č܅ccboc*c1:]@4 1sܤhJɘ23!>6*H嵸ɀeO\I7tcz#à4Do~/@C'%QgH;eF.ڻ&aO?Uhohl3rrr~7$?;m5tgM/6f祆^ թ'6=^ʺݖ>3q;Y{~ _>eP?*JL2 dusgj]`M{ٕKC"` ˊ`jU?K6$:ǎa8pUprM6NIj~8Y4drt>F/W ɞq0~8sɱ4(]]ږH?]vms+A_3yYS<.: Z]0c`(^*Tk PEON?j.0p^? ^UM!ǽh"'M O^p,L)e\>uJepY-ln^qE5zT(G=hhϮ(!Ŕ93W) 9޳CsAQ z77k 0Z\!3yynV͑տ3L)+`1/g='Cw˧SDTYF ѪYo)*FFE7ZrBfB[-Z˩0.Y=#\+z/ Ȣ;{)8E6=j~&>tΏ_Y8)w9:?dIK?Ф!!&.xgMj4`?~oHC&.xiKΐ_~di{Żjܸ>{js_8o#y^򂖐Hfi6q'I XOQq]E I+톳؁ ݼ:)/L|GZNQr,ڥ.(P2(j+Tr_ ;͆8< J)ٯX4wݴ}l0/-*94pku:hMQڨ RhQy5 hk$tVAIhvq^;UgWL_H9*^}v!(fuve/$f,coܵj,#wՔ(mݶnṷs}'y{:* W] }OGZ: qٺuT f@SܔH NWW3C趉SeM5*֐Ѱ]<{4d*~6GK:)mh$ m. 4~y= ϖ6O *=hv:L_ ? z9rӍSTNcą/}GN'%n2) ETUYY9;W=ji ⥙%0&TͫekJ7QٹE&s ͏M_Wτ,dg|R n>GIXk^]"NXIꖛ ghAےiX@'{n"z y"H[8?R@~sPQ=,ʹ*9&9XbʚW+LoRMyT}*'/g9醂®_NЗdC?==5$דd.S{.!Y+o b(-0-(R ͙,+zسeVz/.9P_>-ދm/z@7\ ) <2!/(;)&먊g}TeYfFtoܡzn"sĔoieL[2eمm~ԧo%zZWLPF(z*T+VγUx;;}ުV4;>y@ f{ァhV闎ş|;j-))9R(V=msAUQh VEqAmٷaekFx1dB:װ cA,д Jճ⾳NJ14*qϓm4J[YF.h 1s֡ C.> ! 8FS*z׳ދv-K[76\m=_6MQ UJr LU#܅7X^KlJ7cY]jst: $FYb'Uss'|ĉ[UFw>#_b}';~Ϩ?0|n4(!qq\O8s]wDy p=NJϤTZ-4.s)7+Z-$d{~ϪjI],۫{VdOetWQ-{m>Rdo+82$>&gSW^ՎzqvNXbJtjsnr.ÍRiU]KŹt:8EZyy """z6/noS߿Tr:%WB"уܽ*ٱ}i 3#k뚙Gn 6.3 U3.5fl|5(ɤ+ ,t1 yx n K9qbW%=7x#˲hpϸ{$]:ݻ iL4PB; }D}NPPVuKH\#"A8^#KH1'IM ňDB2%$.=C*mm(8v_>kG|p"A ': A)xL68I8i('0k֔p;6<2/)y7>Cݎ|}&s :jZ9ҋ1-yqLWI μacdU,sKVMBGWN?J`dӨA;0R0hcw2-T=| p.ݗv٩撲dYZt]m{ҵ׮.,'8)RϞ nGѲ_47,%&ZXZ\ .[YR) n[v~e5$.|thї%Y3#`;啜ճsx{OC_̜ZmiW7BRL͇+,C^;Aٲ\`d䮞?oZ/bfۚVe.R({y`zFSb|k|X\PV H]37JLMыkҒ/rt`-]P0빭z:N=Y ƼwcknaO^ +J*dM'.S-  /eNܖ_ SΛaǾ)o^Ԙ w-%r`Zha}};=v.xi .SSb^b < u[67bxt(X)f5 3](Z}x#7yN h>}Q^^Y5`@l@my9h/oݛ r+$L;s߶ RQͳF;}=ך Ӳ2:)ٿ1󗐐0Qto3oe-$x{QшLiXBB$D@a;Eǽ ,#l,$&'ړ[E~M FB#" < ,p 6AR>EWqP4yH|zZJ_a~Y+V,Ɋ9Xhz',{Z6rYTn.cʚ,]0/kyBSΜD}Z,//'#dOLX-?׋y|~Au~n/׺ꊲZ0l)Fw`;]Yu~*[́jKH[œ=8hU%˗)'h)}j&&LS Lmż)K Ma)^Gz|7bj+e-/GJve"(o땗el@T@i~bz);h)3enjڊ̜Ayi(^"X%/"=+w2=go^a/M]h[B/ϞWQKwO)9!.XZ.ytvnwwtwhw os,M.ut\\T4/ $f.L{]4Wj.lpLLLT_L 6& -V]HJ ЍWBca1111CʱsV.gXh5K1.D Q'm%bĤ@7FGސ╤X; ԉ>ͭugL 2Lߥ&FSbAƜ QA*oR2QgiX)[<J{ԑ/C΂qQQ1Fc@9+gNK%`zDX=as[ *iЈy 8JIH\=M +=̎8F O?ߟ%Bx YKط_Sݗ43k>3r(; A@ӒZ PEcRFwTn1"r6J4Y9;ߕ#9eaRp5&hs~Fp;[ng2s0~ )dJg%OWzvKyka!퇗hglz7z=atm]ߝTȩfK̩ú+5Tin)x N܀mQ ]P?M8e,ԓ@XGd*m˟cibw jDj:)ڻ>bW/hpRI.i]& Z~ϱfݱe}$$.=rrRdnnp 򧪏s */L1 pvr+@8PG4ۅ m_rNP]oD]MNWB7{vk5δ2Usi9Eɱj>H Qq℟V{O8uTTTw5 gg'l0oL(!q5_kA8q1Īr&J)mAe7|N y+,q1 p<$|% ? WS hߨH o0H1P +,+j,[^NGdl--}\ Dۭ ij3⽼^?B2 9(K@^E>Hn94ZܠTA.jJZUen1adGu*WE8^S8,ΧEm {B ϽoxqW(NDE%GWTTc)8֖o,@jz\.E&MGi~٣2qLW SoAq^N&)!hbC |ٖaM үx  ]26ri9# ߫C/^jGMM՛ +/Ў2 >Gݑn.4o|{|zCc= Qn}wΰ,ks8[=L*2UjH6DWn+ƽ{^:,,MO6cPhM5D 7eoNH;]. ewAEA4ӪiK-5!%sej/*tJ3Vd刾FhudZhT( Bgʕ*/]a)E_ ZD:Mwcu0tb,?' %yDDiyC*Z9*(3񫹺ŧ%]=TJ"^b*Ȯ(cN:ά̶Ǽ I 2!/k{ytt^Ÿ\zj큁7n\dInnnogeYvĉv[5wꫯJ5 ځ_VqM7I.a0x˺i~@Aӂ\FPb  1-N^FB B+p +4F% \(N'u2l6x4M׫A6mvoGnm.<cl ԙ^m6RT<)hzʰFhvf)T^eU{$1ݼ8)3=\LCy*rJGHq6MQ l6ahqmp^+C{N# $E7H3O>qOoZ\<;zh|v?Xa - P< =>m'E2W=rDA>Thl៸#矤~q~qAn*vq@ &o1R2{/Tpyb!A߾O?s ׆ 0ce_ N0,+ an52M-VeYrWTv \rv1 @QTU;zlgUCFa\Elϸhd0V"4n_\G>zDo}ݻwϞ=۷`f֭z+zg_&[G`ʯZIH\`.ᓭ-74 r Xj՛o `̙w?=?_~|\;F{GK\e `c?S}D7\]*ݼsA1?!?jE A8x ꄡz_FHK #\U-_wt&k]N$A^M!!!!![yǐb*{c>/vr**ڿܸ6/O1m9y\T?w8 &h (fn喈[E~Giihkn$% (6 cw=sa=9yR櫦<1{$xK3T {@P'?M;4d}K$k;n4 z7YffBAhu:%v`cN)BY#hu:A m;::v._tK(n߾=;;̬*˗#:A{{ga)`x7p7yX=6psYPXL ıs̝Àe!/ cCa[w77reOzKx,ût z!s3iSΓW_3ðe MaX?D_D)a00ДX 434O@wrBKC_@Io39<MA4ò,Lư,,A~iD2,x<˂e1-(D, 0,AƧp)0 :\Gu(>>Mjrp<~dާ y IDAT"^r9iW.HvFV8_ϞK4Xڏt_))zK_8| tpȄ OiGe\U2W;Z$''_˾tog%o]ޠp}_XGgh_%~a飗> y$pa}>ב|l)vzqeK)^ g!}f , sON  }y=?i$ID $IfYhd+*$I ds_ H=\gkoVenpppԡkƌ<$IG$I x<)dӲ,˲,0FaXa1Fe17ߔ>>ib.7ʮudJM uTY\TW}P),ڙY3 ,b\ f,vӱ*x/vߔgÑnUm~'}?V>>̷}9{4LUM5rSLNׇ%o;x6Kۅ e>qd }?Ԩ0|]OӭN؟ȎNY1o?zO< G24q<U^^")}l-Peޣ 4-)4 VB>_K 8  '@ 11HrJ `Ҍ]!j5M&[dRUcXj`Fכ `lymA{9 p6׻KGGDsy|ݽM>ۇR:}QI켂{%MEB[+9˲4 J~%4|vxy9z*{@6-C t+K,]FbWj^Rί ~<5|a}(X^%񷮒@zXΤ=&\RW@ӛr|F[|j*jPbMQ4Ei@@, IKєt G3~njfŐh9ͼn322Ν0Lr})!\Mvr[dp 1+rׇv7'$-TkKTJn޼IS$IO}F}ǮxM_G$IIgz&'\lSvrvH`0`$IGrY 댿l:ۙVu}Rqp<+4c†8jQa30o'v4'nC`4ҷNWWWhۙ99(l͗yH )Ъ+tW^i ~I$)e5իWխ[7>ktW\iӣC{ p6n) 3>ʗ|1o?y ):w#l(Ul޾ֵ_G½pF!eJO^;<o0yW?*?vW@WQ驹]dtីN]DTi-Gx4T9EO@S4E`ۿkOμF@_1BL2Bۭv<\#@{iG|&N7YU4 (Ͻt`ח;ãt1#cM f h7,5cK7&.z7UWmٜ獈Yú9C_gtV= z|7ſS=`+7=USJ˔:`0 Ųlzz\.I!k HϞ=sϵv:t\VVV\\୧Jm}ya3U}~dUhMZϾJCfcW[D+(:]:#azy  MO`W0(J$50gpx3y?׿6iKňU*UߡzjE9\nmvRM뱟&.TU "\X$y~%Ʉz]RU <_ xJG$ES$MQDuw{ qB,HQ7cBG_/D%>H98ebރ~Z$912]t)!1㥥&?b7J-4}@{@cM|UgX(`ja&OfuπS_7GNR4ڴ]Xu@vν 77D!?^pjz=0BTcff֥K`V$,2O}Vk)`'g_ Y V&"1Jdc7e>P&n a=GziS! >i.-+"IQ|>jbXDGJʹ,Gq~} >w|{@@pSiM0I3zӓX+(*,Д zEQv p+ξl@Yi@@ΦFP+Gwlլ*5n4hZ?TUu' ]:0b}ph~H7ۺY?[|}4ҭ50 EQӶxAG 5%xtǿz΃<{D,sJ%kKRg;OFER0.+/`Ш֟<+hznxYٔNhsL\;ŹͮV.ɤy+@h ZRlYHZ㖹,b:zaYճWie#k:R&탷}{ۘfSo(d{ 5 ݋9Ow{ZY[ }ok{wurJ] hP) wB`|)^<on1h{!Cܼu}iWkJ@1K hƜۊXJ?:0(y+F-[ضh [c͋N՘3>}DM5E/ؿ1zF`dwڴbyHJS;)d"lYh^xEM^u>`)Luu_xcya$8,k:ws5Vy_g;Sg(P+~ZȝrE>BާO &[TVV6+fff;vfaaE; 3x/^ 98} 3׬P)mt ]7D0$_j&1@N o# p{pեݘ hI&1< H mk2v+@:a)ۃktF/$EYҾSoySB ]mv Y=^\ {۵JpfH}Ӑ~ĵ?~]-ЭR64MIDBHVʾWTDl{*Օ:T*4M$iiiٳgOww Ng46$|H$H$~988%lC.C-sp4@S0˲3gUT\D)Ǘ]]wr0Fc zbOsKDNCO)H6*'*l%$Ϻc!&ƆA}_ŵ?N@R6RPn.0T+OZgTjma.7IBTm8371KULF[xq`~|_5hs46tPYUW;Xj)1_ *aNkH (1"'D;>-?HPqse |ZZcmt %w~Eg{%oz(e`˪ -|\ dR6|$ ^>(R"ersJ~O'I5z s^)&&>i882)4hPF'e%_><Ӓ0SgXp6s5 ̟^{e Ew~$t*֗&蜝J|4"3JѽJ:t zDP% (8(nX3zdz EQhzAoh$=ǃEӔT,xJP֚n&'Jql5-sphmP(5rIĺ2EzX^Ϳj.xa;] iUM91ΏbloS9&+ޅd'ԶW`ḝ p[B{Q' ۏ+[9p x1 'slDn[4J275[(-=?}h PEQXxsppppRt=~ioZ}+_ OZ6'NSnn$RB֩`%Rw$F{ɟK*M A?c/ duGSi$ 4qDW^&.+.ylhm2̻d;vw6g2L  L閖/>Sp+N7,ik׌jN3LE..~߷O8888888.T neHL#98ڤၧ֫1%A`n-Vc8`2Wuk `ڊ!ŮLn!TwYe.\{7*Œvn=r<g&+?}4Һ1x<^n0 òlnxDMJ+M*(T?VqȦ'iQA RSn=l[)).kͭ#vܹ/rpppc{pijI$x$A0rw ERB|OYe5d4X@oԳA0H$f.ymm@J#A*32^#h˰,0i9;;uڵca'-f+i5(5 tY\ICv-u8bAecћ!hA;75!T~l˾S53#_gފ&d,}hw|7Csj>/h3`H\ӵ\!XկOS %3ĜG)$X3@ F[jyQ@StO>Zq5]󾄑Q/䱠C6IZ(GQT޽ij+**ڪ0֡`Q 0&<2r=&rW7]FoəzB(=:׏j8ŵJL1 +FsON7.n+#&k|w @'x +Wh+M{޼+4e9X,;p}?C|J?lZMBgݙshޤb~)uz$[IhY!l (EEEƴ Za4MQQ@ h>8Zm汘4 ^1ԥVhJ(CK.4`qOd[< b}+sB+̝I@Rb s5@]5#F-y9;O;Ԉx0H UՐHQ(eYǶQ8::n,}*$i5sb5Egj [W 3j[5G-ޙ|pB*?0`WA]6}Q[k`Ԓ Eƾ@sjcD׸p}~ߡox "r60YYY9z Wo˥Hち /_&u+֖ jO:)T򽫋||W,/K<0)HZݝְkuAQ$`4M[YYeeeuA*xU) è꼼<@ӭh_ƦTi`4JUe瑇N}e룐i ^U}^?g(0\}]˹B9kWK ZGhD%/GNY  jƸ@WwbIԯGGr:G}luMv%mtZǯ ѡaQiS?bi+߾Oxl=?ut+:ːT%C4Cu{z:zc;\m( }uVP{xO,93-()ЯŹwn\IK֕%ŎKu_짡=FR62.ҡLc ⋎?4T)CN3S?= IDAT GwN)G)EV_xsΕo@ܾ@G<쟳~i,7X.)=#d͍Pvg+^[^i_.[XXH$G(UctzNk0S6Tn0h5tGbGq"}tL}5t>jr`wl-SP$i<|=/E hRчI^58PpOJ`KM:9o[qXmaEZdՇZgP @D7{{iivj`N2-Dc߷,l ]D#joz5d&Du~f ZVm ,%4K}M&t BSˏ.h#oDma.&(Cg`?Ἣ98XZZ5ܹ3lXH%$OZBVht&{NBu%$5OLI' kӾa<#-:FOBtF 0BAYUgGk  2[+9uV]X\n.unxZ |||Zr n==h;ͭek?wwap{IoK\عp;wDNƺIpwZdxUⲺFHRVTn=[Oj=aqK㭐i!2[X-Cޟ3A.B@sj#cwwwk%Ӡ5ˁ^ jAh㿂՝RBD&go~/7AVi>{s缽{0Zn\ݕQr/ku+[ZQcݽ"xmeXh4ӁWAR:\e틠PqG)>ܜ vfRuy_BQT,hgfLPY |>RǣE`cl:wU#M=~w!RlZ *JISזhRp;EqgRȜ4&ׇz܌j -[ ݶ&UIZ[U.J; E]$͝RR'vVjч{6:VT_,Sbζ́GG.?:9Ԓ*Uu}FHZ{d@=бc^ E(Uaq*&䍣 R`0I 4iիWS 1Kk@*((#cRW; * `X!Ah9DZb;#ICYEt{"#(Xhk%uvPtv{vVrXErx Pb#9Sr4[)V,ޙR eYQ橍t9Ǡ@Ұȵ)9EE9Ne@$K~lWhLMPYVTsdÀRY 8Q<`J"?ߖ^i$0>KکBuO/%G)J ܤJHi{M)h $!scM٭ Eb$sǮN}lhwJn%NR ,݈oD6-zp+i4E9əJeܹVu H`qW&iʴ`Y?~|#F(P~/8X" t y*rW^kuE蚢_)`,kNHjo6+wF׹Cր($bѓ;sǡώ NmsrwyowCᲄ.S_ pq4=42tZH0xw#Y&Q(Ab.o/  Dl C3i!@tjNBÛ^Q>uיH;$@%z w~]9cv 66G!}-A\dW3x]oRDtkkn|}FكwsWGD.Y3{ѩK)~:eU>]U7zGO Yf 7do޼yĉ޽E^iWzE?`c.f({>nOZ`GmSPY\{g'Fg UWw=+2wyHV\~JGF%F>CUJ6W3q74xBiLL m۶O`S HH5!Mj={m^{M*x5M9ZU\\\'_677=C3?' ޽mff&mG‹]SݍŔXp0(c - ,{+Lc+,8BI,mٖļܼacPBs:&ܼN(Q j=,BIsVfa3p#-R#j?ccc]QZ l0E>=xq%d ?'@` O4-/\0u-ݻWisCƆ E?͸8/DrH_J$C ` ?N70L\[y0ѹӮo=^Oj]*d20999۷sx㩧y @ ))iq_|g^~%a72 QZb`$BH;-tn1]x 8y";vO>IKKשּׂkniiӦ7o175uٳ_|Ea/ڳgϚ.]ddd~~z}@ǁxS1Ah%dMP`3 am"B$4:as慎gP*8#}Qi, z1-S^-Y1:}H$jp/GC͙3ɓڵ矗-[tҚ;v| C>} ԡC?8*** 77wgΜuhm|ijt~[z5_~1agjj7a„oVFaBL\ݻo뉉3ka`Y$I333F䳘_ycDB[Q^(L`dtz}^0xLd._x:~yBNoAjۣONm'Htsn 1sy{{v mٳgѢE(ׯoo޼yݺu CFF?/xoV픡CH _MN{6hVkX~lɜQ BP BAթ[GL^Y і$۷vsvDʬ_CC>,+3 s0;P?G^^^=L666Aڵ[n999w6m=гgϼ,3Go><|b<=.rX.̷Ia-$BMoQ o"PQ_ZkPǑ8ˈ%M):'IиFcGX۟$ɚ Ø0 5LMZfDiii bi>L1Ôa6s~~Kb7nһ7n|@NMyJ&BOnbVz,k4 @"֬ɪ2NhGm|c?E_5z)#]7'-PM/?*C)cȫڂ7Xz* x)jX7yu{ƒ׬O&FnLCH%Yus B*Yy* xKhW{ݨ7O*k,KKDD/5@IU5󗞲tEHGڔ[}Y1/fYDKi2hZ3<! Gѽ{ѣG;޽{ڷwQb |˿:Q3'yoZ=lV=laVfoJّ#/|jV[; )>pȩü$ Zf|B"`֊׻11uCt n~xl[[YXm~aKW[tҒbg'Y)KgrV.mGBe1.Uy|ݤwbC/ tӁ}nl<c+8 Wdr,X}N Eg> 6ݨP{Rply;aAî7QI˷Ѿ[?˃0dž|ہ%_m~!gy>s>_ސ+C=4{A];;8=z[aQa]I>%y1,CW=pfEqf?PMӫV իWJJ͛VVVYf̘1'O;wz'MԫWӧO] Mē:o ,˧Lb2eJzٶm?!CjT?nR|cF^R_01vŻـ?oxeX7V~YOU0wj85q V%JK3b\"yX7m,6knނ{Zީg?J F&o"c{賷|ܟ L\5Y,Xc+Mvܺ=8GEK~,ӗ lVaٵ.<<`\[+>+fLSK*I^wS f6 YER34OM?} &ɻ{`7Fvw,Tg7l\fj>yGtؙ>K`Ƅ4([sn/`_w.4/moC0*8[pwBLt*rP'-ˀCQ$_/y(mr'S㿾5 `HGGn=TD6g u]EՋꇧASy4Eg$35]$Ayg[I^[#ˀs|]$pymQsϦ[7[+*!82I?_ ty㐄x7|_KzxsX"N<(qXqK]hmgS'SWT2p& z-;ιZg;::6._tK(n߾=;;̬~}'|Rg-F1< YgNgN`<(@f&c:9 dk6- < [4dG kF OQԖ#CI.9 eg;w- }mjJ%eͻfd<w~}=o^iB]/&vmcQ@alp (( iLSD`s`٪)k* JKUDs=OMK@GHoBhx,43 jxpf_:,Z=ByiM@2+5Ԡayn;yCPxttQ_~y1݀{;F_λޘ-ffv$zUT*E-jRק=솥_Ο,*=qͯ/̲t|#򏿗;ގCq? %jrucԪ#Qit a 4cnzz^Q5kC Y|fL pjm@{L_h&$)誇-<՛MJ!c?bUp- P3 wEMզvJsB`e֥aq6ߥGC"k>VIPOn6 .\8À{ZuAb}ϰ{Vou\}6}{<ǏJU;~1ǭL]-5cX 8wv&y$cd2 skJo2 F.h("WG݊BnKu[-WZR [j/TZzJ [F\2@ pQ/S3gy9<<9 Q0 z`~cReݴ0L @Zݼ0@"#BȢ@Ĉ C8Wf [-MもLe5RiŀWa) *DReM6T/ta} bz7Zb}k$ ˀzQcwoj+f*)14>*FovIY ?_V)r>j]_MI1EeZu & Ӣ+ۈv%`-v']2_ؔl@ 檒ICIAH w?\L s' ˲TWTQrEs<4.SMrQ >I5C6WEYo:ss@x!k_t87e.UY >suyC?cmߍ#wI Ů'[ac++ Rz|6T5yд͆5ůT~F]K]DD#!d399LE\rcúE/ZtU;rU_p۬//,_޲&J`e[=9|^ڢwf<=B^:6 `777uz`#!:a'oܸvL lV3ݓy& $@昌3D$@?,:9nM2ԋbքakՍ$4ʞ!@ #5I+c4sR(m lȭ+ 0Q4P_[ 虼@+.tE](V- .7.S7qь7l==J ^>Ĉ2s|PDOJ!_EuaKFhR2̮5{I쇥S.{q̅a@K-+uDn"8q?>|u`ā{د1ˍss( p "kjsa5ʋ=MC;T4HȽZڿ4OFq y[L^Y[?&"p5sMwAZY?&Y珞g@`@*il'Gb*CE{%k~`Z S.M09?f?/AE=o:BɡzE; R՜;)9}]nǠmk[9689A6tg+cVu4}KU"~R?pھ\Cܕ&FfԸhQ;wgJzqsբW[~@퉟R+ےԑb4le謍),sjN6O~m]C8RezQqr/w}z7ϙ24~'/HkjݱP0n_;"w~ݔQ !狅M,TW<:YXl!!v <~n"ie?" h̬+,!eX,@z/}=4>UI&oNnh$r*IN޽{w-G˗6̙3nXX؃%푹NJJy6n'NX~1c_)SZn@i{ٻw'' &4 Eppp04ZvS3x<I.\0t:|򉛛[_j[6jkl0;y{+lФX*۟4e6[hXl?LwH"c Wsf K/>yYֿ[@KdrHw]tJFBluUU@)oGƩ4Uc4д:{Upt:gvwwg@T/FO;7 |I,=/_7{~kw֊CيZ_0aZKJJ;zbϞmьsyyy=SС)S{f^%lIGehN=˖-j%%%O^T{dqƭ[7l0ܹs}={;W_=H Ӧ̇?~H$JLL +WOƫnYɌ3d2$m@[VB< Hd=%nf3 CӴj x$#77 ɖEjԗvv{mܬ+6*RkBT{z;HifWtJ!RO=͹ry՝ I <=]r }~P wßn):K;QA1|f%wYj˗ DMV_M:f;y /u2iS` Ҷl@TZ֫W N=JӧO>}XlelN{dfuuu|_;a Q䋄n I͓X H$D"E  @( ~#"H$bv v\{9{UvܰaBxL(w<̙3gƌֲf[v׬YxNiS懍f߾}{v}Ĉ7o^ [)evۍ©˖-3gΰaúW)I<|{7htA}{xz BqXHwjqw_5Vz/ .dggGEEEEEsqa~ߪT͛7?~ S-MY@ `fie6 Cs?6 3\ <G'0LYYٸqL&[nT 11Qt_W)FK}||.\`6b13gn1Rm|Ƀ~ϕy<ްabcc|Zz]"7O x>|o0h? j>|pEEEˣ6H dgg3ƾa ~ṽ}#F8˜{J9 FD'+eѽT/.I93_^xIuٟ\nw04bN2Rqy{́M?stݲ RV?S˗/1+BڪϞ=Mӻw˗/(f+lٲ+36H 0o~=r2邂>}oA06T l6lj,hjPMLfx.s^bg ZmH;-ug{w!<p?q* Me(hw~A:}Kb~ZY[-5EF9~x.]VY[W߶JVfmOeZ1@dů;t:J\=5O=7"ns:2nChz̜P| voN^=%H$k{j^#IqɅ@ܵ+ KG,~Q]VTܬ}%Ƨ6;Uv,@(f]x7@_93.%Y<j ]{Wh~y;R%VPGyi8Sc6g?1H*6șweUs<s֖l)I/=Z!h&cznOٜ @3-6j~\A&?ckLz6E}ΟŒyJ1r> 햧Z:Ų~\׀}_ڬWjussZxogdd0 W_ 6FVU"8І+ Pr"FD3 .yoclylCuYihۓ}ȇϞk]*M; ?OώJj6JVF'A?7@0ׇ@f=#4\LFEǪ}5$}Ċ(lytvi–Ugڲ!ē=+):S(:iώ6'hiz͊꞊fRLMF^5v@}y)_F4_zMDt\La.Ms䠌uq ^8q;gzEJI4k[bw&.bedkQ[R+bw9vWhy>!'Hiy`yjab4Q hNl+0!֚B}f>,rutkF\ҧߍj,4)Y;5@kcfqv`)gnG6d⢤I.Ǯ<}eL&G'<׫ڎzрHl,8".,<Si[2#'#\bP Bji>A|kMfvzW4"C\۷o<)d2eddj*U^Xݻ.00 9v,%C3 ~L6z&@R԰-#fl^P/ںif#Ť肨5P?9*Alv)> hb P(>%-QAC L[i^p1a5C<D'Fz iҲLC'h75ACi[ެ!J[-ݲv'3B+[_a#fh2)3S B༜u -I9{i艮|TV rӸ j.:紂ٕ-F>>?cFFgst;m@&[KmG_cf `$q!,-jBiHy,##ǩ;z »MЌ> ZI csLFA4i?K`Фf[ڴ 7A36n KХdjH-;u^Ί&Q֔@?,:9nM2ԋbք(\_{9N+8R -c,zh&ؽ"79P]G$L@HNvỲ!`cWn&jp'{(c&5~=Ub9v ѹx]~0ה®Mfޢm4,f?P9888t/رcGWt7iҤ5vX@o߾.caߺu-ܶJax<^YYYmmmG}$I 츑[-I)YP;'YX@C\C5SXk15)7J7UoHž6ܵsi/E@֕BjSBc1]JUIkͭw꼜uD̆yflMɱt;m[ʵp8dV(Z*@~nvp/MtohTUJ4h7+P9 ƨX?Ga|킥+6joVf,RBW?D"k4_rq`G]vxg,VogΜY]]o4,y;t;CDJJʑ#G'V IDAT?7n8pK=L>=))ݻW ~{ʔ:7o4 ૯r. 5*!!a֬Y˗/_d\./v) cbbF@T.0o޼YAvby|>IbC:lS%;Wm,z?n_1~"!Yt߰+)6enD/TA!J@??JYCtv&V@ZZoߴ<]KnTIb²Ғx~RVt^ĩIjVh_r3w0L@zvQXLnw?P?PYRAJ/?E -VbQ;EW*QDD>RO~)G7ttr1*ОЦ&=4B){873C',ENOb&gd\Q+-0ڞi6 k ߷}S~₢boP($D0`G=b `6=_$ꫯn۶fegg;v,<<|SN]~KQT]]]|||VVֻ۷o͛7?3yyyJrÆ | )S:uʞgڴi äϘ1pĈ4M9s&!!СC3fغuo޼yʕGhx?hiZ( 'I`0x{t IٟDĦG@؋s V%"`BM%Pjc q)1Y lEjR9|6%ns l銹) 7*q5^%:>[4>U)hv##I1Qhm+RoLIf0 ILdĭI.ԤE{e:uU1 %L W7'7|\J)y(cqvC.EVJȸ3.nƸdDDmd "pj${nʅi#bYiYi[..nx{ffʵcF Xs60 Mc#G _O Ǭ6'[3lˁ5G^` i gi `GX?~`0TWWgee-^X$=3 bԩC .\Hoݺu54PQQQ߾}S%I;ㅄ̟?_$ 4(//O*R3{짟~љJm2u:O>ͭs{(ef`HOOl'N۷+u1 sBVRMR"vp8RFE( :L@K㇥F3)uC@ lZh&R l4C(k,+ ytI~ڼv\)e6Z2-hۺ6uOY36!uFsr Mlh޾p-M#Wc2{lwww!>UZfGӇ㓪z Ã܆=77wԨQMCį7WYu?;!ӧO;͌z֭s͘1,4M4Ç;xZ%%%=gO.;=G7&z7JJJ.\k8p`QTOV*&III{SSSjuaa!^{mƌ/_3g{npp{9G?ܹb |2_~yڵ޻w^M&_0vM68{}~VEeee3fd2IT35ⶋh 4ŋV wH* {4#I:>.,#ōto[tE6pZɕyCsMݓi:hlimƯtzV!"֎mR ͸ G}JJ/`[Y g+xaB988˿ .֞?aS>|xN*..nV_._vYa8y={ >8p 88񨗗WAAA960//oܹC YfM7Axyy;MvNxk}ڹtS::>FSTQ]]jLip<ʘNGE_.]&bٺuO?a~*#l6. ǣttH$3fLEEԩSϟ8iҤf1cƼ;ׯ_WlwÆ xg>S/B^^޻=z􈏏O?u\3; D"Q+%< 2:媐vxx|UUC;-5zل'ϟ'Iqk\6GVjݻV&1 3`IBRy̙Joo%l;Niv`GN;|B1{l OR\<x<~9Q.\>Zdo2I>>>ۥRiKE(9{ԩS.o7|q2Gw݄rlRwo^JZ?YTTD敃 (& ϟ5k;GxuuuEq#/ˑ#G^~}^^^={>|N׿pp0m]@ {*zQUQ*U2C*,ue%ʾC9ˠKֹb˚2/aK|Eа.OWK!epxjD5P!r1s<___ k֬glNHsppp<dQm PRBѫW>}jL)uԛJk+ iy?_oٟ=ot7uP?ĥE|VWzj|²IJttvd.16 R7wiΛ·.ݾdM&aBXuA \t|et24awW.UOd}+*W\x6oG,{xx\rE&r@`a\l&' `aOxty4TZDB7WfCͭСʊvU;?d {vރBVhmjvn٨<-m譵3\ )K" PE'/uݯy#݇vq<%Ń :qD^^ȑ#{M{Mtqq3gH?~zEV5Q qjMVmNWGmp >nْ%sMfE?Nxpq~|hߤ;m‰@ZfoZ8Z/ܡK; ,2:jfU;Po]Y"uD15Gu;PStm6*y}8~s>d0(!g졦ciMUljK 8 Z |X,%ODEyO$u{ $-M?`d/ bMEN {z"M;y7i!@0 9TIVTlrߨ-IȈt N:.!))ad}t FSF2T!(lytxi–)qj/ ZYbP1@kcq1aio65@R%Q_$US郬/tf!2^2Jg:@kIqc.fo<[BJOE&Kޔ)2HT蠍1"tk#3淣GGw/YvWt|bbBt@nں%i֚BIq1Qa̸}U\$Ó "- 01.9cZ]RRBTysʬscSqoFEFtMݦDOO)>C.E.[lيȱ2>R1uɉTVJLf&nOLԭ6q;b.:qW֊MI1jwښ#λ+Z"b7m[;F!nq޽\d]*Gzh׷gϞO&mh]r@eM*8i/)tʼϾ:~€!HCL3?߲ʔl!CBG0}Pp(6<&C 3q9"9efXFڡ4@V)|1E5 ʌM:(N&_%`ty!a#|6h;Hʳix-B9g2@ 3Ўl83NkxhPؔg5+B,^S[p(0yp7꠼b&(dA&1Drѥ 7{ G{ysqPշ ^&H0_:.%P@_OK۸_ԋ)d @昌DWV3ЍLЌ {HŸU1Y wt OCr#X^Z&ӢhfƻtIUWC3uK;hC&"79P]Gգz4$=DH~vPr<͕Jn_РJ}]}pR9ܟmӭE>NڕU΢VCJmܤt}vEB(;vV\+ bI1nRt)![syww@Iݻa# Y*GІLM/^$BF59o@A>=TB6hȭjxB )-u8z( 3((ڵk檺 Ge d=܁ɛv.S8R2WoOZ̯H7e""z0כ,ҥʆ:yF_6)fs fr蒷dbJU26~Xsafብj+L@CB`]dV WrفzPXnANY8`}tk:5$_ hdn߫ l ]ѻ%l۠  :Z=SGDHPGlvfC—yqb>N[k~-+zhw(x;uNCVV{{Z/_x fgo΃)' )PT!!!|#waE4ژ.4ݳgA/笹Q;9PEC |V!}Pe'Nh 6= %%B4@t4v%m>pMhiYT50`5jJ%g: 1 xZL^2X@~V߾iUy6qyU~~;z4wRN7dPO_@@r= CJ64~! `/ _'}C/z.RfR!'@Js 08GDJۣ@Oov){873C'Ҷ.IsպAF q@}$=ЫU;h?|$"f˼1K0[zvQXLnw?P?PYRAJW w*rT<$\v׆uQoؕO7oJɠezȜ;KOmOS[˴50z믿vww4iŋRP(n@Q`xc80}''GX,W^=vؤIp>98 D|!)B b-" ZmDDĔ)SKrM**ȧg՜BvAn߸AՖM_~hó׭[e5Bh8kRldV-MFJܚ`HM.]Կ7m_ 8a&2–NiBHy[[㣏M.OmfyNOm9` ǖ77H89fuHLF_` 29RhII MRRK}_[1EwPO;MS&OIX[nc MDԖ!.X$ELJ@A +-:\&\Z$o=2/QTbR̪$@ڄiK%82p ufrl-MKOBaZYF> Md̼O󕄨uI 0/?<*;tcs+RKf{B5_ ik; Vjc q)1Y lEj}~~s5dʹp􅦱iСC.\$ID'N00!! <<∃%W^ݿ_9#F!bG^=zyT[)?3Z}b!sPyn߭VN &0;C.TzEqqϿ|l'x _O>OPVʕ4:'|΃Q3 c`دF!==uKh&IXlfK$h Tܚ[2-4)]LhL bA{'|l!vI4e4[մ m6i84N忳8flBfl-lдP,miܠg؁ےhK2- fjpZhhVCfϞNAl ؤ ؟Nh|X9>X`0$l:zjoooGxu0h|KKKS>oZ9ZRR݂pptɃ.u7ݧs88Af̘!'Q ޽'X0Y-Yﰘjkjj>NWSWOьomclVL&77U_nUݑ.\prľY6ǝnN_ŀYsTT IDATӄ[eEm]=Q^mΜ e3IqcOl[5;2iW6-tFѮ4іK ig@wv=m_#Jg:@ ̓[sst? \xsuwwg 9؍y}嗽{2dCx<3 YL88xI<>x|j*㹙ӟM!$fQѿ ۢs.HȺ3۪9wKzZ 8@mWZn{iԄx-\ֻ]/L&ӱcǴZ=U3yw xq~o>=쵷g79 `r9l6yxx7.+++ #8888*z]__o긩8ǓLAb$S?N#Vh/)mډЏ~s9} 43a*`cu(l mm6EQAʕ+BEQ$Iri9dlT}]/~dž@y𻺚6A?ɒ% H7 J]>{(O_MR@2F;ꪭ'~E e{?ĩZÝŚVx?6ݥB~U7_7G#jZ," |D'04 !\ E30k_oJaщEEQO ENv94AZfՉD"J,8޼^={lGwp;VNy};1 m0 RS?:u/䖜).Ϋ`*JC7jMÕ>}HyM$wu?ׄhE=߱cl99~d2 |ZV #0؜SY2ۆrm39~mc߉)0U{ x}/?Rq7lBܰe5F1L ne8M՛j*,fCB8l +ݧ%ϵTMw|k*?1X3Ͼ-5jz(Sb1⺺xPX,LF$#I1Y"` veJ4xn{jߣO/>xUygy7_Cy9khxl"h$ =<<-}m(FuرӧO}piu9888v5 m՘ZjhkcA͙3_2wR3d xtjn?п@ӧw\USKXe|㡀 !|>nvȱcǎ]U[ O(8/0hvĈPݍ6jB$'b|>/ݭV+兣K9tбcf/zeXnܸuht,awoq98888a7n]M^NuK9֐3J&Ksoֺy_^vNGOCFjy7o0aCj W~ukJKKf3zl! B\P(|||ZIGG`SIQe*Y#2JZ #_2ԱGHe3<EQ0lXAl6gZq=98ONNδiӾ_~yҥOl}c׿]|955~l6+r}׿+/O?7nY>}ܹsM&^U*ʕ+ׯ_}"! `ig_1g8lUW^w̩Soc_Pfu:'ں:;;:#>s|Qeee S @:B {hv?rH/aaC^(RT6,9AâW&yf)hL&AQw;.yYtRyK’(XX,*j!Srrr}ެzk'[QQQbb(NO_<rYo9sZ+V8D C]+N2VZs+Ov٬\zW__3˟{o܈--aa,쎔K>&ByyyKKӵZ]bx;vI!N:ujaa!EQ#G,Wdt@CT*d:ydlllddk `(?4JLA/ɿ[㤐?dfYV4L&Ju'^8y>7^}jllF#EQVUՊ%vfff]]}]w}?'Lpv@ .MNAR%@﮽ 'Nt i_2g {gyf۶mutv; ;YTG[,o+BSSSSSӘ1cJdX_I[2'F:J;vѣGCCCccc,Uj9ZKX[k(:G{\h4M61B\F+j`q vo^u8Cڮuؠ 93ӹsyHsܹsǩE4qG<2L+TUAtpv8999AKhoo_dɓ'N3h朜vbh%%%/^}v \y֯_os}E`p͛@ S:(e׬YٳeYq(J8|.>ۯH[[[XX(Yy^.^lJCJ29*)/t?=~Diqyro~P% CKK !XkHikbfiY@YU2ʔ ?û7:< =:'O4  @4Ϙ1c۶m>%KJszСC|ADDٳcbb.7 O>?7ёRXX8p}BrEM4ëWj*oxwqC=TQQl2q O{{댌'O>0p tz@ gS^v%~XH2V'"NSՊ3EN(/uy#B˛M poX9:XI'̚222p98ϲ77e_;%r7?YG,3<_'VpGdZQ!Qv܂pT=E wZ'&Yv;kffHr<mV)Fj9mX*[_o>}5TwUu8͗ϜlTy_uw K-,Ϛ>Z7: `9qL(dO68v@x|<?횬XXRUykhj@pCFeY"2rjR>\qt\<w(ɮ<7zP*jZ++@4 7܂'t7vL 0NGGpeS-G3,2*zc꧚ )āKT ~_/qCL^*&ACce.+w"U_SْeTdW>cTMG#NN T.'<6%+:GY}FP'UUUz΄:vw[=sfΌn߿ι;#GNSCe-ib?%mp*G1(ۻe#9]QSLQ.C5SL a:8\.gYMUUƍa9;EQ**!!!444==رc,((`&8E)ѣG'$$ /!h;w=ܳuV|%SF%WbbWJ~q>;M NJL*JL)+HlÉ HAQMe 5ű@lMF0-kW89>&*@yƞjIy9X(1# ݦ(ʥd#BCHCkQ`1i(p@U7̉,kKTRr \gQƌ6J3.S(wF]0F,/E)۞*im?RP%MH{5b'*9!^SZ5sZD^xtT*UpʪᢲrӦMK.n򈈈$cZ9 \ BCCZVU*A+K8qʕ+}Nj!k EףfBYo@KB@Yl3U%U*~֪@ձ3h҆"4!MӁ)VXG+**57VN)Z(٢ᡌNwmY7}[zKZx(_,@Tqv'/E4U`g!pQr׉8k/8o59S ~SW}5}g 8:T*F$+%FDSJ WnHX\0}!\XV(?~P)((XbĉKJJhin{ë/^d;vӟWTT?aΜ9ZL`  G .r`3)(6aVdE;9 4];GۡK9*B8YrBZW[3[Fȥ7ׁ](FeQ7MsƟrT} -(eY0Fqq&89ȔƮcǫ:G]ga:XҪ@b}?}I`#|ܭ.+ cz `16yM4Z}-=<i4!,S_ӧARZvtX\G)tZ{QUȻzDk᝻8А<=A7舡|˖-5S1N8p //o׮]w:>֯_+O?tܸq˗/gYvs5LZv͚5Fq߾}? Z,r'N)@ ;'F1""B2 4a4CCCj "222::Zb'j4訨(1)5x"ZHXZܢR)efeegggefMXJ[i(+,8ݻ-?@9-i:$%X,Dd|mRG]Ueregfegggge^>9hz*Fj]͓o2ZvVū6M^z[΀V3jԨK<_{_L O ۷o˖-b6m P(ed^祟>hƍ6mt '*:qb s@8}Bv.mMBWB!6xQVV)wEA UdhZ2~pGlFT R 0eeeb*3caq4 Q ?= ʠȈQ>+5w^Duq}0 veɘx͎/nk#^r!+;uV_[QQv.H..qX=###W_~yNNM&ӡC  LdĈ墄 IDATsqqqVV={lڴ_P[ww@U  rqZD&ĥxVP*&&L@)9Q!9㧤dF\#FW:| &I1GOˉR?*b id%B'NvTuZ9y#Fş\TTTR7o)ϔ]g JӴ,6K ? ,kZJ[lZ%~… 8*''=""bݷ~;333/^}S2 SPP#xw/_>ħeO@E1\w ג!8Pka(@<tn&gbCBBfsUUՎ;FcG'>~(Ósz[[[qƍ=z۶mfy`P(V\hѢI&>|xVZp8xo;x衇***-[ ~v@p@x8*NB(^5` dWT!_Ph#߶O$JO DGX'L& dd&)11`08e٘x^/y2:Ƞs v _3!tgkO \}>\4rd1ٳ򚚚ŗKV'A\ȑ#5qN|q = PnWJS:b %q&.9|a~L&d^(e\.0_[HQԢE< 8&RdQ@ҳqz~PQ]]!nd2j]fhx1 Ţߪ2iF!Z~6P eT`R̘goG#LD v{jy޳^L0 *a^16xA9gZV~0HU%/% ^;w4;v숋s ]F8PS?Zc ` /A\ a?[ b 4@" `qRqS*i*&-xJ ^@U1S&iH g?a,UB~ݻwu]3g8q֯_sΗ_~Y-_eӧW&_~饗ݎ(RnZ ҀZ D3NB`%fs)|X B0SJwm ^pH3WˡiZoL/~-~T:F <k!UkUDx4U@S b.՚PXtTnk1s)l!vqByY2>,Z@ t'h@zZ=?zmK=( w!aaGʺ[VXvڵ7L F1h4(..:EftӉt4W@cFNpJ&@oBEW2NȍHrdMrPL Sy||R) \jil!<.EVZ$i;O _e) Bpfee[.ܝ={v^^^SSSrrX^wO>`0zϣ֬Y5 ˀAw<vĹ:X{ahV!apv(\4ϣ N&(հ[\%>ř&@N;-s hJ&8zV( fU* rByQd. d2V>ivm.^jPYs}y3@N|ZIJyq#dw:>֯_+O?tܸq˗/gYvs5LO?SO=5mڴ z#Ӆ$׶L po&:& եM+W]VM,'ǽq@D 01m8!YEҾ@E;+B:sZ$C\Gf61 `A'_s1u4P5wt%&&NO_<rYoիWX-++5jƻ:`5 X Ⱥ\6h$ 1 Ia @%@kTis7ŀs(˿S-҃_豦˕ɋ}C5,Zguxe=㡿 Yl&A)5kGx4!_~p3 y wwhd^'%#]Da+wɢ_!1;v(,,,))+ؘ-KQjBg̲uuu999*+Unnnccc }ThrJ01[Kv.AhF'N(TXeP(U`}@w}?Dݝ@2ج_Bsu n c!{ W(QďG[30#"V `m!jqgޫA;BYu4$$ ^_>kEDwk3ok͊:y?-yW`;' 8+ii'2ڽ   v9@S4\_Y+4|wvK x)}G>`̘1yyyyyyCO48L&׌w t&0HcVU< ,'0,d9ñ< '8y /0ǃNx/pz:;>ihGsG@|84&Ellw}7x=7&СCvssBh4#F?Z>w^6"<~0&j8QkaLGG ipU`AX˸-ne2 N;*(T`zݕ(u[= },;f,=d@Ecqt'UݎbNج`q~$Gn/j-Wۃk)هicغ*McˋhoCY U{qo‘}ȸPWShnĵ~*D(p' d@ H 򚨧x~':B % 4Ex YZ2QN]ιgF?g*MLU P>"A^?駟Ι3G ꫯP?''=""bݷ~;333/^}S2 SPP#(Kݻ/WiiinCe}caV v 0&=a9XPB8PO) DqB 5J8]X QW7? (@@ PyuxY1t!2p `k/N\jKhBpsȽQ&aWn''2-8`uyAo :`Wpy?G~؏nw+#1Ud %/&vp;oߏe9-hE|C>c[#F%?HL͊N,P-X'uU~3 oW%<3gmXF6YxV{NpD3N;0 hrW K@QhfJHae@ %CSl6GCP(V\hѢI&>|xVZp8xo;x衇***-[ 駟^tIoz>(37d^|/_5QȭSCBByuBk<wXSz^CPCA;mCCU:~~rJuu5|upX_5)pLmޓj[gܿN}}w0 *5P@SŜw5jfSxU|FI%3_oP3xz2oL㱥*7=O]VZy|)ۃ/8 ;C0cך|1|ya-_V&ݵms w-֯]w2ᇊ7YQF iM{oxݸ5cPK"/r':5rU h^Qv]:*L&?gzݡٛ69oe]uŭZ4䊏]U?bm-F]%iH'>h^izIWIkʇfܣO-EeX]G 9vBn[>{GEt{g$Iul~Y7PpG_5Zy+Cbgxkj}b}.~ygOZ,>]6˰ZOUS-Vx'R'k_U-B棱~%ϚjߚV폅e9رx. cwE4 Y|n!OgߍUqߨZQ} 5x, ?/+| osL(u{'Y % ^݇'r?7Sx8:DZ߀FBC F 7w&"C.k毐!^/~ڋ&Ch2}?SwGKM/q;vw5OvWc-|֊[ΆZ; OnKbX•Dzu 53U 1~R`.s1 \OvmK(+/Ai1u>4 ~* ~ u<y߼xx-^_ : u<;YB,9㡿ph/8->'qhOky 4 O߆#kϽ#;,B<70aXg~11/O?\҇yWMzj\:I}!)O\{{̟?`08;lvB)^x4L/rŁ|^Pe39C(K1@dg`phfsrJ&lOq4Bpp{WhOTjWg{Z"P8w() %ᚬ!u8ڭ41%JV(Pr3N(HHCAYd@w X- na@ G{t@^W|/U*6X{z{ҀыiDs t(`k7.G_+~Wa.7B&IUK"\Fd+'EG{pN@g: 4ZW ɠ)]!DJWR8$lpOۇx:k\o$䖍򖝳ƵO$ݝebΥu?#}ߟQz%MO|Ձ;gX\Or݇+J,+ IDATzmn']}h/5I71Ł4^Ҡxt~Nw.JW&9EI{=/3&OA08BXN= <Njp vif hl$5>y$ƌ@ \ @  XPC҈$v \`Y{ָ@!2,P@8K(<5D[ హ UZAB ) @F-W> ]`6!rrڡ|*~lr_8+ ,o!F}ܲe@3ƻwq@D t2 /p"P)| Xz=hmf噘6R0( '?.+{]XJPG4A@A RJw@  -[XV z|?[oiMy1uEQ,+~cD!J˜DޣN'csP{! }Q pS@M}Zrf(w_ HX yX C;T~_?(:;Ak7a[~[Dg3N Tp8vcX^\KE(IB8)ZZ,& 4( ,<@ BpS]])|0nܸѣGo۶l6 9sL<@dd?xر(--MIIEGGOv]VػwO<1t!4ۻ06MG=JlCGƺM\NX':l-5!n˭ X~C0D{WP!:`&ڋ [ Q`[ +c ;*pt)FBd pt ښ](tW^E{66ht[~[[?#:@8s`0(Z(ZFlEiʼnWDX xXYUjCI<<@ :ؽ{wii]w5s̡0 L&KMM w/[CՈp47?LB7!ZJ5(=``-lݮB tx:ZIh ˰BOpIk-![~[k@ OqQ .2ҴL>EA4#"쒾.h$ATڐsp!@ V;vlZ}'ydKZ("}LGsFe=(p5T,ht}ٍ:غAtqX:N .:(J~P02kG"3 -m.g3(Kp՞MǷ}GQB7@ CW w`Xv[o5@G}tÆ E-_|ƌ]wo뮻Mа`B6oIAs  L jtGNjt\&`{-w#08*m{askWC luve=uk (.[J*ep A @  q"7:$_[[;fq;==Jښ#EmllZ~G###z#!MBc9 LTBPr\K p58OWIA `H#:[+` 2tF}F Kw2'@bAVy2=>{fvv7N !$M$ \XP-jOcEX[= E\r K\L F6$;ٹlvg7 |=|=kK.9NeE8z{l֬YIII6lʲZ=wڰa޽{h…,˺({0$I,KD>3)))?u;-=E| &$$̹4q#FQdddnns粲Ksttɓ'A0DrJ"zwSSS݇z+i>@Q)""{4b2D'&23έ#s=)DD$sDƚ(r~h"[~-- h|hhO>dzz:q!!!!!!V5**"''端}bcca1R,fff>S x߿m6ԩSwڥ޳g̙3uԐ!Cn! ~nx#$:)$f"^ǜ!a$mS"Òܬu(kH$cbU`EtJ$ɲ((ZgmRhJ ̀wo߾}ĉpq}Nڵk322(66Vϳ,e%%%/_n`wvЎj427&"jb:1!1 k k$U"D 2*ꍙov2vă555Dy;ww}ڽogɓ'k))).]rUUUnjiȸ|rrrrSSSfffaaannnHHHgO`&FMCt`aUR&IiКxo4䪪J,xaYeYaFc @'Onnn&9s/aÆ{… YeY[=9UaI\/E2jr>ׯONNc o$)o!D NinnCJ*K C,iU[z\*bĉ#F seeeQAAAbbXttɓ'A0DrJ-?޾}{'2(z"@w ey7Jr* wSN^L޽{2>'*****J[:u? /0 gϞ3gсccc/^7f̘rٜܼs3fHKKd2%ۈ -أ WUUlޣ|]=Vj 0ZNwڵ_|1##W^sέ1NJJʤI͛IDSL!%K+_jŋ~СիW3 c2o߾cǎׯ?s]}FRi[@ deCZ1/O>ID/~jYYY~t7YbEmm ښgj ۵kWIIkflyT_WWqfuC5ZnE @|䌯8=Znu-׆%%%IIIrXX Del-㼟u|?ZaHTu[Mx/{G[@-FQ$hΝ{;v̜9>[dIwY 41`.99y׮] ü[O#ި(myԩ? /0̞={fΜIDHLL]xq^^ޘ1cfsRR=3j(mۯBCC=DD 4NE 3 }uPUOl0\0JeןE2iҤyEFFє)Shɒ%WZxÇ:th=\XQ-~&w;.nv ǣ@CC`noAAAEE˲Ng5kVRRRpp VMVXQ[[+BllٳB~vURRkys>pwm. B 0'{m',hN4hPJJ67beYQaE$oZcǎ,묣/^|V˗'LV8n}VXADV}@[psttɓ'A0Dr&;6|С;vo끳:,:_^jռy5Ʈ/@ Ä#"m!a%888$$jFhTd$8IDI6$J*|ҥK9 }ޯ_gyF{˖-[FC޽}ѣ333333\(ȑ#q㪫hڵ<Ȍ3rss;\ _/98pOӏ>HQG<#/)S^z%ez)"p(MMMo%$$;.] S`a8=WUUUU98ܯ~'|o߾Dd2dYtͽ{lZ%\zuРAD6p{hl2zMDFQQ5\?F:otNUUeYVź:aq}'UU5LW^cw p޼yɓ'UU2eݻ[^^Q><xّ]'`ګSf֦#Ԛt:9knn:cC ( g<4rAwhm'ZB>xWtN6땢(ͪ< Jo|nED۾}+xVp'㉨tk,]}ׯj]^^ kF^[=hO@ ftMM‚{|=dĄ}'3 kX&իW paϞ}WV*I2~@DoFrTW:+wN*;"#Cw, G\T-Vl_h 2yϳ\|31=Y45V@fkre%{Ա]wj<x[7_|;Zhe{e̙oy#O*d4Qfի(r'd1xn#>{a~Ȩe2x""g/݁;(M[ya Xi:`WF=ILt>Bןm:]zmtQ[yl̠l>#ɚfJ\wf #" tLubM=$zcMWkIOKa|O>8VcxY_4d颱6yN^J( ^>vٶtΗѭZ>==-юaIE1<Y$GjFf"CX_]7{5Kn+!Mh;HqADDDΒCo=XpHtr(fAbFdcfl$h4I=$I{ kOHgw,g۞׶ D$߾X YgNS9c㉄O>nx7(w׶EDDF_?է9"1SM*r?q!`l)sOmuYGDRm{ʋg" 3wl8'n80]8Sd'[Db?])f!.o½vD1NgbӦcDy{1f#sG,"7h꽛>ͯ`fRDIJ98bD3 Fs4']9\B|ܴ[Ȗ4~4펿Eȑw‘j v`meoG]їm+i""K/e)L_`vcg_h׏|<۲F eϟh#"Ͷd y0ߘ}!j 74 n@Ѝ ԁq+5U#qP Ӗ.(|JFIJ7&5xlu0ĊOl{ nvND9}DXD|l\l1nȣwO3mGN5WR¤x3W bgk7$)ӟHDRv+ɖ4}D}a ,Z:Vf/3b$;\HW:?ϑ&tpS䠛N7ؿzh$=o)_Q8ЩGǚ_yKf-뾻odH#6ى793b">ga;Qʘy?EBʼnVsH͓vh_9QVQ[wҥYsdYfeYYeYfYFUUQtq$I,0  "a[=_Z.m [ܖ>--Neo;V5.{ݱ vAֶi7 ^Kݩmҧ=l҅D\X ._h_T /Z4+^p dzIpOO5;sr-5^\ L\Y}DIDu'||ӥcWn v$"~E3FH,9mhD/2]|VZöϏԊ^ rؠ#TLDl_I|QNO/ tG.;>pJQ7&EUO\ǧ?sj9~ĸ$s9#;FćLjv:j+I%]X}5?|ӳj*D4h@;g `ek2{\s&El.T_N1Il9}ڀ=\n!sO9}[A|I77skĺmҧ=l٢S{Tw*{gy޳˗J;mkL'.YptTuk|UQ:شD*D"lneWlh[y՚FYֿrH[sҥ۷uSԾ.NaTU՚s%I2 1ZqMZ+0 Fܾ6NSGtvL3\rOe&2<3P- DKΖS̨̤((kQ-?LYbQ1naDE\ Ls;^~@w/nPb:sq#523Le7>wc Mط>XXclprT%^Da5$h$:kqNz]9ruw;v,^h <*EQQH$허TUe`0< 5 N[JKt!FQY"2 DZ$""IA"r߆qG)ڮ<(8dyՠW^(l1iĄaOH-WMD-!%X(rs[%2B$"">8%W3 q7z󎁈q-[;ެz |_aa9xVe͉x%u` ADUe߉i-]o=Ǖؑƞ:o揎y١Q:hDx/,OHy0`0Ք}Wwh˴KZ>*n|E&"?8^ o MΑNb[ZE.3SUHIDAT8cO8srČFVrpۑHlItjPbjd"2o.ɺ?oS|gg;6WÆ'$;LWsN`uu!K;zRI̘ǭZzov}=nT+}ydw%1ou,aA7On< u;b+GreW-mߠ\YBYQdYeY%IDQr:͢(u:x0s歍+Ѻ3ܣI1e )r"7BN{OGىd{]k/).}TrlNנWHr(A:rlU] $%T9㣃ZߜF+l9>30euAѡyg $xQ;!,SRj:sB&j,+(!jg|Lb 58VFDBE~~wfYOOl;%+h !Qpij@$ٻH{pAq$dKqHDX|Tyֹ0$3?=%(gSվl[˗H94ޚ4$jO$J5kN c7S2Qcrӻ0b*!%)H)!-SnEbvYfIǟ8,/H*F[PKn6m颤S>߹nN"YPb遣]{1 ,7{csw w-OoiD}aoD4hV"^n$K4esHNunV&KdKK_gյt^lI㨠.edѴ|Fzgɜw0EaÐ~ )54Ŷs/]UB,TYyqYf?]۟}YjFIMM vUUqG "!aogXEUdY6,FIRQQq Ylx׉?~ pHn /7/볳 CQf*j& hA@tvUDDL$ ⫤(LD_]vFf^/QYwnV+<˓}e8Qh9k ۣA+pT:(dyN8c9𽴇,">Ym}mlRJDEA)o*} q$66f{ CRt.ݼknGH>Q|~mX5'.[/ -^]`К{[TTsNmy顡e_uxfFƆ޲V]5yb8"l7^VjEY """8u:͙k}:۱ rWc-%"O߿vUΓ}g+DQso6]*X>hgf^8/XQg7OU࡙Oyn h6U[w`+?5hNyENυW D}Z};2|[?@m,(˷2]Wȶm'"RfC=tʱqqPgCE(S\ǨG&ڐ png2d\e;e &di۾m9w_%%%j|m$XFeE כ,,9Eg,9%اw^{Y,ANQ>҅ EQޙtџ9j B vz@{~wI$f tdY%bUc9KQYQlPe$I4hp޽][w{]}u+ygˮ~, mjjZCJCBmK0 1,{Q嚚8%Yd 6<(?5<]ORR6ϳej}#qSԎ 1 q\cccIIɄ'E],.i.,"K, <ϛ{o4ta`YVKo7k0 3c o5e4\<{p ~k&Ȯf{_C eK<Ͽee55׮9 $)תSScd4MS7C++׵av[(7ΡB!0>[p<_|V|!O/ڛFT<Ʉ^" "pOx@ p;]\b?^k﫹gxRTڮjE@D@D@D@D@D@D8>p4̿ZXd.Χ.6޶EI_}E@Ξ"G@D@D@D@|I~KOEj>[LW\0>O+1I&;D@΄=B<LZkFa9dҕ,/f8$u>O\6nڪaGe:Di*g!aC|~_^^BE8da8\X̛_wV+Bo|4MknFשz2MZlc|Ӡ^ Z/Ҿ|>X\KR\ש%UYWl&ƕK~v6óJ~B?Tb?BP?F|xf}/E6E~`1#Y9]ڟ:jt,T9=f~z_7-IB>3 Jm_cR=fhӶ݆޻OF0ezTQS+wmn&1V_~Swpq{v͇7t:l֝~3]>)7k&νOo"3V/^$.E5?dlyiR򛊛/2ppM ltnz\m4;%So3RBDGF8.)7`N;؇"gM'81lC\.<ɖ^d|@flSϥm"U*dJ!0sys_oSj!W|nQjeR6Hyv:KָV*SiΜ|Tb.ӱىP[(BΰJ.))3]0ϑY GQwHWZ'fKQEoh/?뾋BnjXKHq؝?qpf·삿,R򡿽嘞UΉKT'&3m姟#{Z@\bm8NSOEEot{Z@BcQD@DX_P7"{7&\&+Oo:җsj0"_42xͩ$w&gSM%(, U5Whhl@PRi63&LU14=l6,wz\p3{3 )Ku=+5Lwct5no[@E#5L~|`WxX& )$˳m0QyIos˭>zF5~@6מ|Tb..cQ3Q0ۓۏJfTž|zp|o_y+3fD1ګɠE5yq~Y;wFc޿)5Cx+!˗;͛G@;;|}`QJ9 <lК_M [.(uvw1Θ*zhS_#<ϞB0={sUHZO`~[xj6K V>k4[f f:1|[ϘW,zΉL/08E@N@ ~z5S1rR{Qkڕ畬`ikcA`?3[#ӁiV F*?m2iM.%snr0u{):S^C i6iAᔈlި)5m*m,KWsKv,x$fnSHxc1t~3wz#[Yݝ}n|eQZBT]7Uu7 5foh};1OCS`wh㹄-D8=jL4AAQW/jij:=,0(ļ1_N[YV*)s]]2,d8xB6G &%Iqlfc帊|zƝ^/Ie;m:s,񙦓)= [ehNd͛o[v4cFy,*s{<}!HuF?~T#5s _ *&%CE7L4[F?wU3"^n c?.F|uz\UeFw,jvb&Co[28Q-a2|P?h|쥵QuF #)Pb+m @7&/yC?r|Iu'hbq~kOVemtnm)]'ľ?X.}&U`#ͳYcGiC^Y*Ϙ" "pFW~:K5W#ɬ!1fDF޶p&@Ѹ65Mc{LnK,oO؀w,z?^x:)m~mv(aa^Ha$F19e=f EhG3 y>i@fR.#qy%fXg׌s9vh8R;Dײ&RޡfA*o\]ܥosƌhylτbl zIgɱY=r|Tb.ӱى`Dcnͭ. A jbڲ=ә-=y ;/R3v<2~o[,KOS]lY{c5|1v^al6n l%0=>ёiX" " " "pO{Osz55^XKL*aѭ0黔+=L,tOH*&" I +gk't1CGX |:F2["<'y99 MzU6ɏ kB(% 'qYKdfӁCn>[@{u"c"6#vi߼]]kҭwUzUsfQC L] k{=pDpq)}W.+{J\Be+Gkmȟd " " " " " "{WL&TQ>-ʛx657X}Rejo u槓YEi/|ը؏6+ҥyp7(ϣqs*4I>OK{|?+NOW7D@D d2s8=9Y;v>qX|vw>m*˖eBN̎FU4)1=~nh<_P茄&>tOT#t:ˤX#Z㰍\lTRH||Ofck5Qߛ(T(/=u!uCV t:-Wŗl:kۗ˵-Cxhl6]_C>p6~}< vp0MS~1.~xͥu+,\LĤLfxKDG5SXFNSxYph4V{Vu'PJκ`3_7֮(:5CS(z_znߨ[N.9m-eCɐIcg~SL&t5vĸJ?!)(vk=j[u t0?$'u*q9;;Xo F.yھKn{q)ɤ*IUN^;cs0gIm]63]7ϛ-т5)ED@N~?'IJro2k]xlh4gfH]t֌YE@D`hw/[VFbcj0*5Wך&[әM(*T6-Ũb{ocȠ`$RkLDoV4AK>lA@i01t'ad 9 G`m[Nk'?Wk /<,x[z9$Qog8d⼏Il)ҨtW%Xx}(CձKJ9$(ؘ V aI%3,W}K}4L^Pֶl{\Ru:=B19uv֥eADiIb<Ԝ0sgdspޔFy| ŷ" "px &4wjv'iFxy<2n~=[M3Y֛y IDATbr ?͘l$" "OHD#_<ĻeU⢬3JbD ivsކvY5:W 1e@h`lvn7%cL`L|̯ LS1Nh VAa 'RzMl_XӧF&*S ," N@`Eakڭ/FEi0:J@K/ʃq[ /멫L4.~ +]D@\!}`D{27+-9v[g|l=xTإ?(dUس t({F^__ɕ |Cq)Ir+([  ΐRt>+嫩y|&trXs8Q6(NgNjf~VW@i\(Q;F}x#\," "Ţ?w|kyWvPP~Blժ68dZ " "p#{_zPB-oܷ3 i" GM Nc]ײF^Z*ZfDimvȡ|4F?FXaӻ|`Űiu{Gw)(;a/NR׫dzTb,oʓɄ F#R -R +}46}Ǹ~Be+j>`-7m]E@D@DGs/2?^;n;Z^Gl^{[?"p!F#7Upl6I\}zӬZ|C [j'^fL6_CTl41 O+{ 914)O$і{EȊUH?ao60/.f4tTn6UCW9;y(%w,%UT#|EW']/g}ۛreNȉkowxγmwռ}Y.-[[A&ű?;m9nsz)D ]l .kN )uқ͋,Az+^.e[*͘W r"C]E\UFڧT ꋞgŮ6U~(IH7@/_^js9U!NwQ}Vz WNV3 し=Œ>|wxʅ7'Ud3{jix.])讆֯D8 4+X]W񑱒d*/*뚕4̎1f΅|.MgS3XYya[ Q)]D@D =Ɓ98E@aϏz'" "pf>1wn[՘c0wA|uHX`y;G E@K@xպDZiWC!eTt>uؿ>y ?֖Jfif'CG*tkQʛL&0a/bw5\  [*39${;>.T*y ÿ+.fm[wb/QeGgYw&n3YV2~faE>RWE@DA߇#^(޺f[M1o02ܽ鵷‹'W҃un<E}7AoIP,T⫢բ\bD#e"6f1׋GɃ?G `c NQj 뢁ل'SD@!̂ " $Ji}ladh4?:3O3HGaV'F-(v!ъ2ǧG 9*f-۷6gc3"DmpX|Y4&\UBqdeob.E r9$t t?ld]߇v,o9&_mI~ϸ?K P-NShv(-pB̿xEl Hk" ""05F7 Jl!N,{+`֛X~SnjcDp[{5*=;5tר;K,x1nl6lH%'5Fm6C̥$'B!&'&gX?#9A|jg+O˷qYuFBlB)>>9+i" "p*NWp5q,ųZ,QqXtIfH]΂YR 6O C;I"l XiU֗5j|vB s0/qk 3Rh~o"uL`iqaE{-Ug埊)[,'ynUS@pG%{QS˹[ JаXyqa;nr֦XۦE*`OCԐC+!{]EZK,%:l3#:c.wFV*_/c,ݾf Z\hQt) g6@/ O`7BV[}ȥ;{?p bM< VD@Dp 30qk8b)OI!&5y,F`g~JwnDckfpeC3G 9*=Qk]E"PG2E]ǥ`F/bq }s \PEus1omËe6;m%9gL%|ע C808kTmǯs]|䄫_Nc{`]gJӣNN{[D>-j]/}SC"]fu 9X3RhU$Rb7fiE塟KXZl†=Tjz!^Wd:j5;|6' p(%jPSpP8 OD Hjp" "X?W;" ~kWկ)XL^D$H4j" "oވV|ߝR"pF;^}_NέF&" " "p\QzD55U쑀b}Zv}R" ;%`թW 'W#KfN{D@D`O$^͊^ lw=U" ;&eo%(MƛV" "r FU"" " " H|o9j}cwUH}8'4R O83 W$I>P7D@D  5 "p788XRD@D`/$](I89VZvU(욀\wMT9f]! 4pR?E@D`0_}5rs.?[KD@D@O&" I $ 5 HyU{}Ae>0D H k|" "AtN}6/k>/8I]\N5<Nj|s[7^h4ED&[+eM<ʧi]\V- HчlT]^^EޖߔKښkf[uәяQu&hęuIN +1)Tc81'6$%PyS)08G%Od2>5۝ Jr}8@Z\/UڜGk'Hǖ+Nبg)1ٰiRTU\~-}hWmkOh%4ϝkFs6EW"pOuf5.8U:'` zJ!#"#omutFkZ3L W9*jYxn5[hlW>8lLn娠^/Oܳ[t-aWcy;Hwؽ+]N4ͩF$'L@jh" "p[6Uk~?5NgS-sN鮁ٟY0VVFEO~Mycefl640'޷+;aWcy;7弌oq!K" N ɋ7Z' X|tUD@D@D@"@˕;'ڍc6ߨe׷hX,jP,¯jE@%OA'm]*yuED@N@_ -vՁj?ˍ\dkU7$.i=aど=BMs"kSPϲ t׵+躭0= Pw9o]X|90Tih" "@@qžr#> cBNxkN`xW#Ao:E~w׾p83R lvV*!@Ь*"^JyuX˅ҽ{I!@|b45lR̛W g]N=l y ;K'sc+*f!R`hkzzo瓑tDϝ*Q=iN_UR8w\Nn&~%}OS+oYkX/]_VQSd#ѷ{#zŢWƪg%Lqhkb7iսiVBꗪPz>ih~Ki̤F!gJ`.KAՙlZ s%3j" @M4w3]XD uhZ^pتeC "M[>yOfS$wW-yЮ*]D pjuID@A``c8pBtVqq@Bu)2=&ʨV.k2nqū6/;'+2A|'r8GԪdyŸ:.D͈̳^ʛc(V\t,6X:9G#qZ10:M~,˜xW^Sĝjܣ2gdž@0tuh"K#p;KU}qOTg꼺W>[̲@O2>),u|oWqeŨtM}9X6쬾%괻 vzF[}zQ!-2wmB3SM -D8"r`>RWE@l㮫M[]2/1xQy." "C8|ZZ?^Qkjl[9QOe]b_m)1.n+>]" {$o mu֨_ vhR{0i#H'6"p0hkiOF'" " >iy\i~-=I;j+t%~ԂlFn~جr 5+OPK ۣ|{)KojED@D@D@F:TzoE\ 5QUZ΀W.mN˼YwRVj" " " "pX ̞_+}C&`'#$g u{[zĻ y] *v`x}KuYD1sc|`K o4bm Y~'e|KFҜHoQy8!n1I'GQ슀 zD@6"p@ب_ku .]_]qzux Y;k",x_}P" gK`fc%s]]ngw[l'[-]^%oh~٥7>z>MNZ"z#"p) sqc deu>jS-==-:{mόgj5RxZucĿoМˤd>if5+QDzAZ"!" J`_fgfK'xܼV{Fɝ $5݃~՘4f}Њhh`9/ܳ\]/[W|kR..ztT!myҧiR}愷n '#SԻc8_QM+*h]{c>" I`cE&z] .ίWsw7@ R[" CGt:xۚ-;=-)33FCЄ t*(L~Oxqڵ zpޔv$+uUD`Q#piUVD@McEݾOis㓆^_B" "pP-LxHTc.LSgch A1BOz_{~-M%QmC}sU8ԨEh͈#gNhy&tK!=Ƅ|%U'B|$" gF`k\|Y$p8tD=76`#n3J8R\:u?;BuIDG=ZڜE@D@D@N0&%j.r(t|rl$<]bT6%wU\Ļ_l}-6rac,˥t,ِᄕ,trEW/k_kLb. Rnu"@` M 0&,nV>ܨ!D:91r[R|iٜ,M#>_^~t`mDMNA|#ì@T:ͱd!9)xP{}]m s*8+9EYkbxo.2r(J`FyPD DW,e8boVZ7['vKBT:iz7#RߗʉZ6F`Luf8*h*Dط4AM9,ƌlz1buۘϋ sl u R<8*Yˉ7~Qyr'1z]+o*vokbFbEf tj܍Wfj^AӃW" " " K`J_g9gcr_cuQ= Bx1x0#Wf2] /umڻ/{Qy6EaӮLARR>xlmQ1JG~yi(E@D@D@DX ؠY2"p'-d^c1no4oCWxFP‡= -=#&E` v7Vk*TTD@طvѫZo7`opV4ED@D T&5a5{u^D@J`kv8֍CD?2tz;i@" " " " GC@k}fQ8 '19k>A" N |]NDR~]l6 "͙y l"L!" D@ZC'PUG?Fk2DɊJMڂNBP<&bAZlQm@یk" "'0 0ifdrrw<j}hy8t߰6DEE@D@D@N)Ϊ$" t:1ւ~~x!ykKzpDv!ۃ`MSJf8vB]MͿs/uoץleehǔ(B/h" "@$!`,2`3O3Oa^[9v45{σ qqWG#d^r|7LOyT-aJ^k6l#a)e'\-Y [.pcT^^mjGon/_01]MX5$lHD@D`_Z!/6dbϏ%ݍh'+x|O!xk:myPհ߯-~Œ,;_*Mp_T <G2 $%X؜j8"p 1{X47I8nl?3'mO>.]_C"B0vs Kih\+/})dtiM?OLC$IdT*%)<;!V P[*sJ`^g+JJł8ɬPR|[cq͠z+GD]5[GRAb@{FE@_I`ҝĉcI<浃tj Quf7 W4fs$GvD j@ꓬPHRQ`m |Nx@ ^biLu{j<#8X/}٬7NX٬{8jxUe6b-BLD`M;BYWE@D ,}:‹[8ʉs9AM<֖¾gK w1%$]QIJ"N2ԯVykZ\&lITmDشΨ ֣" "pv: d:IGV6G{SyS>N]oݴp7[xSjcd%? R1{MTNL=pn, ǭ17 :U8 |r uvx|G6'.ñ'¬ɼ ` h JbL:OW\qݖ%~bۇpǔ(VI:rPU& cIQ]Z)Q1+6X2/ XKc"8c**Icmw$vVy,[.:ַvDLܸ`]7&ܱZ]s?wf `o}bVd_زҿ" 'I/Rƅ-׍<{&lN6 cINw{}TSZn!5d-?az,;0#(sJ̻ y<&jBzSD@D[uƁ8sτgϡb*a%3b 7d^]LqZ\+p 01m|X[pK\WVυ3޵ᠨӈģL7^,pr(yc%soU?kM.v͙맯h~w3>Jl0G t1+xSO4x>|.ǭ&Dڈ2x&?ZM -Ve6,(%OhG1҆VK } .RaX e"" gB d}Nazմަmn3R,kocV($`,_IZxL.+ƫ1%ם y<3;k9+Og1gEm-=ߣi>QD1lSu:Zz32{MˀMED`~jJܞ1ڕHZ<ͼNh!)  pr=֙.5. ]f D7ҁmAEȳj: zi;+?䛍ÄN{w>X'sw\UP}oU1K \THhZ^~ 7Ks4!SD}tn'KC;D8.hx#" " @`[cU[IP m(.МKdrkہ*ED@D` }-|s'ł>yרEl+gYD@DA #W #A͉lJ`kB/" q]W>PGD@D@Ε@pv/,tҸE@D@D`7VED@Dz_z'WC|Duitȷ)JT=d{-  hpRD@M@TPD@D Tkя_dlˤ4Jɤdz}ķB[O}ޟVj5Ob(A AL:!" %l6OZZQ= æqC=,WW_m羬bkR../tڙN];ƻގŴ?8(%9f)ʶt 3jR" " "pV$j5X!ps a`"u٬ k+;p f,OMb3b"KK$eL$" " G@.Ч7lF*\.G1N۬Ulso[,6+IW7ySᏡ]p^Hu1Z/bZD;mph_z 2S[I FܹvK 9Eo " "p^FZdNnSyr_[h['fF5u+y3wh3|{pۍoz#l߮lksfHӊm_P IDAT)5brߠ 26& yfA +Pv8b ~xtz&(u)1yPx1T5ph;#ח匹Ђvז(v`_cڪT*زrL} h[5߻*G#\l~xՐ>٩D@D ! Ef8 ,'Qypf ¨b+8 \{G~yFfZn!L NkQިQ5 AfFႁEa:O:3S @&T얀/" &`r؅f"'S }qĉ+ "Og`2 5YK-BмQҞXA.N3ؚj7?y v,~;.R4uf!m—--g. ӝ[LD@1Wkpc˽ chh;OW03)M&1|_.Qm 9p1uIBj͐_)>XD@D`{ރ KV&A^jaU(lDGGjա" "#" " " C@aV'g9" |TӥΊX.>k" ;"oT5" "plz8GD@D@!̂ " "Hl)%x>*" " GM@OX+_Dֺ@"l6CJKD@h p R HD@D@D@D@D@D \è(MD@D ,wޗ^I:8A0Ѧ_.)]D@D@D Ho" " HZ~L2(Y(N?tX_<&2O3RdBU@<-磫" GWE@D /WԀfiWC˛X}ۨ_Qo=y6ѨOj@s ŮZܨ{,Nx7uQE@B@`W" "pX׋'r4O5!8?#M wkb1VTx*L,M[*+" " "pzx{98Ep,3a<716iQlD@p)&̒_=ϱf׸Ҭed2cIw#Y);;ߓ\6W~SnE jJM+W~ HoP5qX?_1t]jdžt/ߕz%@]|/l\JؘYDڸ  " "p@أ‹EVNPw̱oyU4& |ӣΉ#'M0TI:$Ev:" EF" "p&&ƴ=n" "oV;}єA6HG6ax`W~/{>󮑊lD@["mKE@dg@D@`0`wս{kID@D@M@x3E@D @yTE%6tU^acV;"p:$Og.5c"pº;a@ UMZ)xL<5tut]vFlձuvc+^lo$eC+m&oE9E@D|HeF:5wz\.%ƃvJOS髢y~cJ8IVd!(&Zxcj[?áˤ% SzUMxCJn6E]U!Q km9(6e&" "p.駹"{&noc6\:ƿ&,L~Mpe~ש?+?3t들vE@0"rWs_DtWm^jGMeTcIn~I_ΣFt pp{U^NNm&,S,n'0<fx'aQEVLf]Lj+7z;,"Щ|uXKҩ,2y)C=Z8[f1J_,I<ǒdG0}s#޺_RIR5Ycs̹F{؄Ii6շUгzNio%o5G(\xQ IئlVSD@·AXq]k,R٧i[mzo4 qyՋfq,j/6qB@N +R²9pJD@Ah]ZyJs8HL_)Ev%S"@7 isOe'*ϱ;qBΦsck?OL㧛0bQ(^&U*bp|q]="Q.VځxϽC*kxmWYh6?V&"1^-Blzzg^/"ql4 ^c*E@΋S"bA&K6oy]F~?{g Fo"@D " DĈ ĊDbb"@D "D"1}|66q鞦NS2Y\M's>3A$F>5Qj"D9t?}-47>?3'1GEn  ȌwӮ~1tO Jm@J]h[ r7RT6a}WmdB8:\ KOuS4H"*ss5ѽK/XҋQ_#wR3W}X~+_fOQvl=QyN%=XGLUo (m27n>X߳nO^p[49+^]% xs>10Y,_ Svc\*Qj}&ol}/crYpݜv|˿D(%%0 PXy2@ceRT҃pK, jk\y8a#sP+qh$P&֒GDbhjuT%4,i5՟|^yXjW9Kg+6f板 p7`O@w[/gurV_ LN"̮n 0TTM2o  l'@".$vm|j9JxH(D !"pt~5C{'f ?Y;l=OBĵxEh4g^Տf3޳R9;~U,+QDOle_zA~{/Vk~9Cz{QoTN@>oF]`>a[G0/UǣQ|r^nו2Ods7>bm^2^>8VFY^Y3|&foHXB[ݟfgO9u[D@DL (XuKvI t:n9;/ԛzװXX, + 5>rнzv.`e>%ĦD@Έw>ZG ]1Jܚd4/+9{@$-D " >u&fKJnx7E6 0^͘hŶ/:0#=>G Ϋ8*oK{e><<7R2!-锋0iAD@D A`]Ut`ǝW~jv!j5Ѱ!嫹|57)ufsY!E=MP}}>'?L؛"J qK.m0pH$HJ 0 tf")L ֌r{qSL SXn+R gBxJ)h?Fl?ľ %6c8%,8|g(ll}/W& ulMJ*٠;б@At:_2yX&/?ޫT=Hx UB{/7J/E$~T&•)jכ}ֲf͖b GY-бcIJ"  f5@kB0E =Ϲ+emp4^T-"փ/MaM[͖ /e?>Fĸ2'̬ ؙ63 ꣄֩H^pJy>$#:f^ȗb)UdɏI tƋFUY k6bf6F}j^szP}4cp3_7+QD[odgk%5A=0[=w1SD XSsb/gگjff:6n U[+FO|Ņ3i~@uϮ6L/t𺓍%I*ar4VD@N@`(,Fi0.`YR{^ IDAT"EL 0T Wf-fval+vvj){S$'NT̀ouxr6U(l_Pe{Ps pܛ/&2o/I|*" " )!R1xUFwΩQq۳QeMɃ%1DT`:FϼY"#p arxtzK9~sE QLTˋ8UR\^۬jnY8=q 0ȉ!K"~ńN\\% AI`K^9JL EAbHwql^B2FnH($"pxo3o,1HbUtB u!2CE`/[*~]Ud6y`Nqlt\Ees(IVV " " "pZRIZ&n] tKIhwqUqװdrԿK[ڡ$?6kNejMSURk)̣8X))r<iX/qQOE@I:}t@Sw߃i!Ym<_hSS" " "GOɯk" "!1☎焒Nn>K_i%M}]"Gm2W6Cck"p_mWE@¥AD@D@΃NcSxQL&{juʧtx" 8`\+fTX}A]epF R+TVe8;{no?_ 7{ڎ|JnuHD@XMA.e5-Hm;"yD r-Ek6RvYh4(u!]{r'x<Zy!cbCA!D@FX,矴R~4ػ\[w:jhlUvyح0B||FQEnlc|:R9z/~Ͳӽr*ED, `~;TA6 "" " " "pd۷`3R ]\ ۛzO!M\_yo;4U3#K?S>1䚭z!_KOLF Ӵƽ[ ~1 Y +2Ӟ$& E j\vOF;jس X(|{;Ytóދ's-\__/Q[C0X+ǦU>j ^yΨڔ."pQ ]Үz "Yv*Ѹ?X\)"phz81]+40X.~4}T_?f G`.ɕs+i^͸ߣxg~Dx嗈Jeyw=%|l`J ό1+/B0яvmr3Ӻ.8Q2],!+*lUD@.&" lg-8jvX+;[d̡3$cSW7D1EV{ᄯJX@uJSD@`C";O=vk3A 't]1W7KA ޡx0e7+N;sBXNw\ 0nK0dd{ݓGwv':B7 ->'O_M\Jto$H1G_AZRT_^9T WaHw6D8'Ivot%H#* [vJX@okWN8AL^5-JDPu.+>lW ]11p؝d FXYiwHI6LN9KMRumfuq&G#*{odzXAO]_А-d̝th &<9wKUEFf18!@q]nڱfm4Rg`u~*1.kH̉)ڮ"$H98ؿnʻ!D@NFǻ/-tg^/̿ؽN&>\@CKV8g[ 3:[s– CÉY ӯFjM[i~fl~nQ:2]U&PQ)Jԏnl/OT\4M CP[m{꤬`{N_i0_x]^MmNuɺ_ųͷ3N!Asb`%2˟CXN5e-9@q̹7ȿC3"T S aF#'(:$ ѐU߼/!/ԆVE`C2@Yd& KQ_ m"(ʦI_ ̷7nڅPc9S Q*==ec&>5W ω5/%*h4x98 8DD@L lDr3ɫ1i33l<*f )!HC@;L@m dm;uh8[VnGx6&,nFt-._wV/<cF ]KJ4( Ky&*KkTsMDIt(L(M]6DU- dCl>\V51f|*σQi߮r d<#!"8xYe׿VE312`8h =n )Xlfz#M8@D^ڊt 0-L~8ђwLNCٸ_%lIj푕0 ϭjH Bv|qw[RY*,E ko)aTsMD1iӄac{jaZwEwGY@ٶqXu'x `ڹ!#ࡰTZ#)>dD컩 7ň` 5exe騔Y?FpbfӅ~B|>)k\r D͵ſv{uN˓|e[!wX^UJΙGK:;rr:JBM$3ac_6L$-WES's|(;5~k ޡLaveG #%K\hK]s$m(?r!^+=}fpW\C0DŽۿ$X[+]DNgxi`k׿e}ПM|:7X(?-RG. Sfo{=O 6%4å~_7j^Q)LEhI&)W J!$yɼ>xk@LeLVusPr<:h*l6lp/#C(9mzTsM,u՜i^,PWN@֣ " _K_/t9Goqefy&͌Kl, &%@D1E _w1%hK In(کS}2xѶgH LX:[e aM^·l~_Whd 2S4ˤwkVzsa<f(3iWQWh8 6~n筹(tbeE1qYOPeQI6Z͇4Vh a ~uG=0,G=0k"vԢ׬|l,M:8܅ s'=w(OumgĬOas'G̕ɠ)%8xsr4V19 mT7cM!t IK$u>\NvV&Â&V/S&9ļx5!k" "p6QmSpAI?^DOKOD&Y}SEZ,,DYn''ćۨڕf[! ,fB͑5,ΥMQoL'SS"ñT94램"{=ŢmvcGcxymߞ}gw,/O3XHwmvjS  &ڏ;\`E{4wpA8]+gI." Q0E]OiہK.qedt,q@t|BBM*3QAvdcM" ZD`g90bU'Ykz:jhUO ΩhLJJxD;rGȃs)xPYh+`6~5ZRPK 2 HO y]l4yL;(+Z{QO:+"414uT 9끯.x5)8Ͳǩ3HZ/؅'^pmf8u 0^hqܞp1 sZ&bF8Y,M_a"`ɟq!5NГm܉Ο AP cȟ%nV饙Y?n u{kpwK=1 FvW z46}10rVqnu;[-L@L_h1W>uQ3_4D0ftJ,r)(<* w> ?+t}" "pz'" Q0fVmsqQ6A< p9GEo|=B-İD/NkR:k|)Ff]VZymRjְ:+6U,U_*َ(LsYplLD ,ɩ4$\"q*}"Z~5'f ?\C(QD@ -gB<|y҂ˏꬑjtuJ`=2rN/ xtc4vY)"  "pV@_,u\D@P!gF.WȮ_09dS`btg6h`u9| ]`R!H̿Rs;$3W" A &n̽ۿS'>!]}},x(.#_lK@.Tys%˕uw^tW@n"K$ %uYD`tK務L6wxsϜx^Y&WJѰeU(R/v" I$|A\./-7 d a)"YR," "pxuxjQRB #%RK ?$Hp8IwDmʷܩ^_rҺn[Z獯MWE S23U8_?{34 IDATlM/|Ⱥ^kZ{ |NGR )sI(uA/N80@8%7+VQ鷧GQAH8Z_|w,K=6E@~'p:635`Q:Aك|4[׬X[ķqfH_A;>OH6 *_tՔnRnW 7mT**fBZJ_봮tv!JlBVQWW{3L+%]#,]yb$v,6x| Y֪ϲw P YUvH ;< E~'1G >'1Ӽî* ((gZ>`]d4%cM#O=\'=?χPucW/CCcx}5.u9'FA{8ʒwBaiN \qwքZMw}{""ӬӮQvSx5u?u&&~۴yDeHWʕzTmy`2X]$yddIGE}?HSTx4 _\W*͡oӣe KDUzȐظS rem*. $pP/0ACc0/)cW ̕QCouC@>0&Ypƚ^߼ v leWh=E!l0ב`v^->Cd,`8G9kF@ OV`U`y[ӱ6utvM'h82M-(dT䌺#(ge&̻Gt`t|.6O)9PmND.\0k4PI=&]x`W2?c>Q#?4áCަGbo!vwWOT]'x'y#w;S31±y2fDe\]ۅVjʿXN\Wrb=7*{+>1kt.;yS\lx~j%GQn|i%V&x6>y0/V~ uZ%۵jdh 8tUu iPΨT6'ٍ;IX?,kP{ۍۥ3y9ɰV._tu;I\GK4ʙjwsC*K.[)QyKΦuL(/3# (XBU@>;7^f9qdT~޷dC-A9cRY,NBQyv#.Y8l"Kٌ +_82+}y%v H~ O~*4TwaJQ^Jj@T(#"U ӵLm@2*%2_{Sah7cvBSXBc(+x [X<e(o-YX8-3,C;{bT[70b' ÈQl7r۽5qpmʕDfJOչY1RY({` 2晱9 q1XLK+> N o˲1וj>Ý?5g=o2Ľ[ᠱѼnRyU%uW O>t {ꈻ/ 7QKCm1;"9Sf&K`nVT~ΜLzxi3+_Y͆NFUazJ+ο=E~=.)]GwŖ+\'jCXQ7tws>fiC#JH1ѓM.c6.3Ng'8P8ʬɴu:#%-AإCmtklˡ`F&pWm cA* osILz!kQkpZK yQ#֘ɆH0R; F*/b֍!sY5ݘ'1wbedeSDH10ķUkf 'c"vsBNUS/k7:pԚz;=nd,dK'uue;lde ݆࠙3=j]a֪<<{<Ҭe>p: Jn.yzUWĉSMDU*b>++O9UU('pkuZ,Fȇf{5>ý  ߘU0~̍<³ * QIi&'"5 l(3 Yم" &P!vWboBN.'V.K!0F(6~+rӊߊMVCcnaW QuDfPf#녁fMB>ߣb1M>ZK~~uC Կaj R{[l Ety! }⁜U?8vwGHAD@DO=<.*o¿ZБJݡ8.2[Mjv7yW'I[zb/!r>gC(f8Ec%R(l+FTYƬxD61*ub;eҞ?j eRqu9"p~5['f ?aJgh- vyk^cքf 1`.` ~/cVoq7/X6%O@z&SVtXQ,6;5eɓ er pE |~Ϧ轊|6w;l2W ~%$"p8c/V4-]o1ԛo",|vOQMe+Id¤grLHE@$Ussr"" " " "p X d2dg&n63[ *.,$" "I`d<e[F֣D+.e3 ph0r3JY|771"?ᣱR@/Rn{(hh"[5V+80Q ډ1λ'ӱܡqgT/vCcmF67;IW$Cl`FŤiuZ\g&>Ud( ;ċoe[ R| m]P(nsi*~+\ZAQ"$p}˯[ ߭R^BĩmN-nG[ ]| 0za?ly uwb_6\|ȓ_xD(|p^d/bZ7סWc.-o4ѯ~U)N{1/0y_K"Mc(ttKxy֭O_d >R"po#lۜQy,c$U6zz*+Hp 0t ,]l3˅9Hg9 m=û>5x+L+Gkao]Gh1kwעmN s-_c)lŨyb; fbkV濼iljft]NBب< pX5&J]ڙ;pwˀܦ8/,^v;ZĻ2AMw ؘ y_[2NȶΥR\e`H*2\G*3J=X[}S_ra58Y|szVD >堸~oIuz닱D_zeJ} :MXY=afI o9 ?m[n:kV?Ff:8 fz:Ak rO&@ ya5ww8{(7V텮[vLq)uvR@(NӮ3U/"@Vgq {a1CQu.~U)A DˋkvITz%~r N5 GAVJmA'3Qu ;J`"Bθ?SýcHSŒR19ecxsA?sF`7s7uIO jvy; ݸU'3' ]0G!3L?~I8}V!׮.fa͎Cg8ShD5b%n%6ktun.oEW+/%/JTk;1֤!$T鏩0 :D@N#{, |ZgSc:{@Sp$ +O)K$G+>9t?<( K3v+/RY,"ӱ%-NthhI5yxD$~_?<]o h2ÆC\ 7}EY*ZE@BV(N&I*| >NGy7+pf)5ۭKJ9[xd"K ÃȶSm^8}<}s QyH^IID`%FJ,^ 5ƫVq[" 0AV8n\(vm4ɏ 3JrpVDu_yT]#*؆'ME`[MIh YPt,>A1~Ѝ OR@4"֠=Fg ʯ {c1;U^KP,XV 1 ;jcTHDdLA!1 8K/&Naj|ghFsoUT/6+6Җ _NvVuS[̨ag|eJ'4|&U )==5M;2Aټh|2yg9B yvΛ +UP"y\>{$!4̕`%p$z cY{j$&VYDx J|1{IƳ T*vm؂]->hoT@bGR.iva5⭗ϳ$f7$v}\F=A$!>csZKab4L)j-qNW򵺧L$`cq_|y)8*.v`{Xkc>{8r2æ-i`;\Vf;y4SlU8QwdQt irro=pj^D@KУo#c>[׏EcțY% T@eLb% ݀؀8*c+<ҫR`wj\tZ-.uQe\cm(7@Glk JkEmv c#|hgQiӺ_͆D %.ZfNF_G F`cp7&jFAr q!m {mq\ޘNaJ !"p,L1 ^:oIXu!QT|@HRJ(&-1UQư77_sj; V^X1"d&oxxv͊xat$h I& )" h^.5YJ_zS2B-ٲD6S.-~)"pNЄKAzOʭ1^ynVε5N!01YG :ԹTū_8页 _ƛ[2ex׸ޢ-ۆHCIyO@k$l@Z_;?C@p}SdZڲ=c:5x7" gJg=̺ceAssKsyVD,Xb7cU!`x 20a*UV01:Z["D@D`G.]H;0#j?"|,re4_C~nm_e0*DYU#"p>3J@UvkpWZRٯ1 0#VwWGk‡ʩޔËegE`.^hLZW7q$4I'2 ]6NfhU&b/үMAbԀ:m&7h°`@f5>"I,hN%Ic"$`<4XBRL@٪<$HD];7ȥSӒ*sbwZbKZ8:ɰhGfw џ "` H^I`qP TH;MoCOe/@`@_év!)I,YE@D hC@~#{'H+{k"Q@(T*D`i/ MA*"'De^'$DXI@ JD " i!@6N^ϱU=isr 격" " "L@.D]D@L؁{Yf&" " " )! 8%7Bb&་n1' ^mRyl֜Rz`)8H'" " XCDДYRN@ Ao;n>~2AgASc" 'ES0)@I: +" " "pRWqZVelӱ_?X`HGGfleR^D1ioR3N5 *micǼ.:." " " + H^={F_GSvSkɴF㭫uu(5Xr,©뼯p3Æė^I:eGD@D@D@N7 ,|>չ67BsnxM۽ߜfEa'Y{RD>nQi%j]85,\t''ZxjwR]|L ԶN H68cbSUXx^Xbˀ3R%\5az8w*$3uJ _7DED@D )]{yؖ{>c*k3?$[~U{XA╈R|wcg웵T) U$" " 'E@ ]T޻2+2RdwҋɷOj䄯Rp5ǂ lVs+ ;Au0d \0xP;bH$:/ݜp닡;'iw;fqN[Q"p7qXRWk< v`Za&erX~f3Y4~ӊM2dʇZetw^fsg8C|^imp>N1c&u]M g^M~S+{kK/qR" " "pL\A[.{ׯ-lZu20ُQ ˆ*u= ڜ1l%jTFUtX"@,Ia`e8͐7Z{;WgnLڴia2nJV쏳j>c,\5ϸ\)nß*kP㵕ӱ3c0V)" 1pQ55&Z<M]Gu:-/N鍪[qw:sT6.1Dmq պlKoh A Sn@ +AK38#?3.nw BRoKDE }",9<3@J"# n{~FYcpC(h~"_`#&.Y6o[e Q\槈7:DtU|3tQRYJMH")@S8>i(j^1Kd}/P98ͼTxmĪ`"cU^WP5I`[b?I$XV)" " >=\n``U" &jؼvG\%`>҄$t;//"ifc2|ye=(" T$ڦE "lUU.XmTBށ6A)ED@NR(@nHT+]Ӗg_5~ZNZyC*zh&&Ë)a!X+f/ٯ+j4WTc|fS! ֗` 88a*w mT˹!v('23U8;^}FwtXS~UcRף ~6[MUఄRK<"[eOif@l['(w|)o|^Ϭ" #6[ .ܲQASE@74U/qLl,E]'O)Vb G" &p^*zc /v805Q]NrI[8ݒ!Vb6 .`b2ض\~Ih͡C6q:ns:8OiP jKl x.1NŜK%ŘkfW]q0C$5 M].p( PPV" "'T35>_V.PmެR1}`=AkXgRXxl3,fvyU Z-_lQ5>SF@Av4 Z%b8e鬭!lRfU0M!V2&6ꍥ yE=&>C5N'N*W!g|Jʄ ;VVHi" k`lO~F-YK,e' XA1HSt?vK =C P,=Klp()% pԍSFlvV՚@݀~'"*Z!aD@.!Yp\wukfL {*){^ù(U.@M߭n6+uy?^PKף C$ߌ52Ɠ: ly:GD@D /h8UROIn n;pI;Xvʙ\҂6Cc9vŗܳ\uq ;@"/oedÉ1%2!Pن_G`ľt(K@F`= "pk R܊B.S=af!"\وf?9WZJ" ;!ng[VXʵsW؀:3 /}]z0{}6+n ?d¥ T0 nlev Vt20T6%:zJ#)$ xMf;ٕ$%;dHcXXwnl6?ku'E@'0:LZy5?)$-#KZgbL& <iFD~pOXl/Zgǎ >.UqdfÏ k_\WE`,6, 71x}kl u#'V׬]@"@'{w׉Z9Xfrb7frd 'pWZdlּDゥ_s&Շ_XIPctVl"k:bF9jZ ^WD`^g ~F˯/FED@D@D@K@ p2j~-+{>qĉNVWln&NpFn\u'3\wz5+ED`/Ci;U2iiqc,ܘl Wwv.Uvd? a]RQ_iŌOTYz^ 牪'E@6'pi⋒\mV6:{]:El^[_SɎ[OO"-Sq8o|GmVY(D)X,( IDATPSSSDT o#=ަ클SJ@ ^r̮_ i ,iE@.\.:.G! (ը XmLJPYHތJZktgtJ$¾݆-XĭP`pTy_ / |@!&e5_o?= :] ,7794й{s]-,8y_<EV­fD`kRW [w>ƣVe njzc:{+eS&l6m>;yzPy@׵+&I!a\9:_7.!ֹ/+W "D@T " D"Q@T * D"@T "D wl6,!0ug޼PoG*/0Lk/DžK<2<Ίt?2-*֭ˋR0/*/+%na<ކ*ev^K/:ܿslZ}jsʫRl\?.Z/FJ 8ZVM2V.ƪ`i#S؎v5fۘvreXJ0MՌK@Ad˭R1TTmݘXA$=QEXCw>O]# c6Xs@{Qj*6,+D0S<81kMܲ73`EX^ͿI=fq7Uw_Ԩ5X81:1pu'>_VcƽGӀv]I}ԩ 43,qvmr_Ya_߷%S,iNF+YY ,!F9%\ZsF+֡WEəCt^D@D@D@ 2} <㦍`)\D r;Hg~Ct^D  >쳹s&-xXxDm#cN ȷ{+F+TQjvM&d|}M%" "pZ|ERP*\D@l*K'_ Kv.%tخ]<\ v'b QYB SVGNG1׫}#`pkB?iȰ%^R8>5q QuT~~ S-\4 "[oj5؋# L ߕNM?!d3 D!X/pyPYq{UL!'֪Y|' mH4Y9L }" L@!kD@&kr9QFDXx+Z6ݨ|)RW" "p\8}\Ē.N/ٻZo7 n5YDgHZנ5*s@i:%"` -+St8 Ya䒀)%J}!{{dq2ZMbX.tV,T(u&S#!@ Ms&vƨ HsN'`s.K%GYmM شdUfd4(¡3ֳjsllcp8>:mw͗ ;GdYn;ߠ<>zD .;O:MLƣD@D ~HEZPWRyut;Պ5V_j\he\זO G֖q\.Y@hZQf*fF5#w=&?5Cb-cՇC쁔lԗW&k(M\J+DKuD@nk' 5Ka1n?] 0o--6AشL\|imZnJn ojȷrJώ& -`:#IH`'\&;'6<؏֗AL@5;ƾ sb)$ctb޵H(EsbmmȬo &2n[zl}A'P=<;8cs2dA2U.WL`"w]/1̈pYKQ($XQfj.e-'QǞ`j$+~̛GA2}Ey8$Sl͸ N]Ykߏ+odBiNl7+Ln72-V1;ۓ7EM īm8>71(9c*%WN`DŽ?K|8?X=]m0+Vq8j粼q[鬾Hb]_?Bxʋy,yX5pP mrb9fסq;~J-D T+!AӦe]۔p ݀96<żl8X0WWzt}Xz5MsgCԠ^Q \q-;@Ʃ/[џ7|'812V.1X,0MgK8Ej<(4vP?D['+d2?\˝:?u\=ձ'bPH-i/g}$tO9~g3|>騆lT*g0>I;%kyRfyWIjîͺ.T´ ź$'ޱy \, ow47Pg'd ͛!}g䠶QZJVRw:7@1焌`,WKu"^.wak\vpYe5)Rps }[:9l2%qqЌ䘩^\G$GWD.9Aݙm@gHE⣧rmů[1Y)-e3"Q" L@[}]D )mfQvF=0;+10lo0y-T,QoSH2kkm3x2xv46d–v%D'#xbת5>FjmG:M˥rJ*dݷ=hDqHDmtPyV ɃEo h: c~<క6-Rd+_D@.muT%I{V%" ^={%8wAs4v_Za0U+>۪\1XWE~%XYvmr\}HD E$$iPTC,%\]o /@bVHdK@E$:xk2 k)ʻ˥sXxͩfIz*<NػOqGo˄UO&{?OWC)M~gtNk\v< dXMeɃdj"" " "pk'+iHݨ7`1hGȞv0+ T6 v (6e:0^_V,ձۆ*>; pg~4-9 ccjBy E[>W+nWjI{Ug" $fL^}a7+Hj"r99.Udz~V9?1}ye]n쾹ʃ&=YtONIBD@Mӗ$XE ZE ~xtRD@=:ӊ M_PMciaVb1Xu.’ vd/ 6޵,Q}Y!2 ww޻c0wFTP52%.0zEщYt |ھT("py(.x:EPnoH[G" "0դvb XPÃ{jXNqG:Ά=aZ4x﹆sq T偈s zWlr +t#wJ5W\8Y tq8?,|_b Œ6_5Ǝw7`X!ߎT("`cK݇U!nܻcpՖ" "J䈀D' 8:3$ZH%>|ԫ=[ǔ CjF\%" {|$P$yut$npltԋ䍀VmFMͦAxDY~&K'{{4r,"Xnf4؉u<,r؆w4_B,_u+ӹ+ 4$4s| Zq7G1˖{tBD 4I֏;w90U mOx;{>Gyi""Cl%zI%r2Lp Gf؜^K{_3LxnɌ2k|}f3'^\{?9"#NCTZgLz^z$pwd^&7ÛߦǵU!dLꯐ :%"pX!@? @n+-WUjsCc͒hO]#rV&1>dۈ\b3~W–5E˴Ck>#ڷ R6}PD "(|a}XS+vBc |.$_DE@$$Km/0zٯ~8bbgbvHSKH63ɴ' '3c8]q X v5Gd'WҲؘZw߲lkcrs7vViIJs퇚k`v:毩,cAEC e a5o<߻[B Ͱ~rH!E~k 3[oy:mwq{FHfa@Y:L\Kuw;ݠVVտTasഓcQenD fq;lY㥹N.K<{yHJJ?%;Ua2" y& ?X60<*D:>0$EeW"ePj;N IDATٽWK9v?[_ 6DIU(7K) ;h̒598Za|?N@zB&SUi%"[ [ \@'ɟI=9ʨ@m(/殕{Ǫw,yŋۼd^V @~U t~C\1ycO>ey$vǐܛΈd@`q~{&\- Υ\R"p̶M VJosؚ<zsLctҭmg*oH8ƈ$SD@. 1_ĬIK$ gM:x~MRݏfo/< ZE@.cgGmnۄpLxWdqGm2Çqo6R}[h$|SJ%(+C`? {*"pp_jL$ آ_Naϋ>ZNpg"|kzDDc7Nh,UE@D@ 6~`E!]ǜjy ,,>h2X;L8]^sLrLǿM[ߚAbU."shu.ϹROD@D?06o~5|;{Jz9۵D/ݮaP@Qri-++\Y;&Zq䇀qόH& mwڬV6Xwm}P@{|5ٞ^NE/o]l|ۼ8506`pMcXD<dΦG A5mU""[$jؼt72~٤"dzH+eNEI@p0N"NmD@A`Z+zCi!" " jS8 /$# 867+cIj "c<,7S$D@D@Ȇ0ll*$8!-D@D@|M %9jie }=ʃjy""p :{ h/~8|3A| [vpӿ*o=M/ȋS;{4L{&< >q6žuɉu/ (Kv8r~T^;ʠ:A^ *Ce 6 " x߄ɟp_T* Ǚ~<[Xv4~U^>Vۮl/ 9J!Fi:" .ݮmGpӍuvW,I^˕`0:3^X G9ˁoֽ:AA}\ l„Fs 3XS;`|o <^<;r9H6 /ߏ!W@8ND1gYK0sudy^M[ s6dehJ<77s38H{%D`%lS.{X Ur}d[s l:oݕW[/BDv18d;"I˿Y^.5HgB_L:_@TDAuʽWRtg~^K>YEAdbKB{ >&WxSH)mFAEtZk:m0[cL~/RQE: m!v/%k ^;켋73$ ݏˁ9wԤ:AY%"W4LY< ``͙\Oa)S3dʫյzgsٛuTYuj 04(&z!aۻec~UeZHD@H[@|NfbHU6wfI 3>d+ݗЇ6؜9&iATǕI2or LYW ƙm|Ji˯R6t c>X,\}]e7J,mK9c5{a6ѝK8or詶" "pT$Jskۈ[n5Hg4};+twΫNd|i>ռ$-<>GbBD@l׊MW*ۭa3VEv}Kx\Vˤ0>Ma+ߋLE߆+Ui2 ~K>'ݷ#_,6QgE J}PpE29:eҠPgg9 xp{N&/Q|ca8]iȎ:^zdy̔ڊ>1ո_ƙv0t y#iW@1TJ1p RiyUyxdiz%JGڸ:ϱV`S(8\``3#!^.yX{];Kֱ7 <\ΊU*f\R'| |BJD gwWiPXvˍPHbXB ˏ>Gf"e0F,Kw0H&>uPcm\ ?`lfS|wX>M75'XPF:idl 6.9*>8[N+tWEϓQSE2/g+m#$-J \ ͨ ~>?6+dai^?dBheIߘ=]t}xHJ<LS]4j28}BT:5}Lc"ꃻZ.&yN-ؚS;ϯ`gtH'7F2$ܷ{je.io:>xU$!`S_ߕ ~65]=I֮L\yr'c]o E?h5u_ُ8>KihpG^_rVq ^~L=͏}E Ҹyn,:4" "1a~ϐaZ?{!ωoXi M_+Fr}<>A2&1Sm; }MSKc|m\?4_unhb3B8p u?!d=Ah<({X7<=v -Wڴ:Np۞+R Wis /DX'}ek|Q ܬ aZ . S"pp5,b \:ozG\}&/o<Μq%?x>:0tZnVճ~ѹȿ/?F(" " " WK@kvj50M,BK^\NV)^" " " " ^2LT""pXZX]|V1˵Rx115k%kYKn9.VZHtb+ " GGAx.n,RX. '˕ <{ $Qnۆ3ELCQgus I=9,F^:+:1F58%b47A)oM*+95+& 'WC"w{l̍yWgj'dJD@D@D@rN@p'HꉀD%&u!3>ꄩߵ/ʥRo>z?(5mrД>< *Z,kk< L&Y/54o{R^?uI#7|ިbQ>Je$\Ί@db:PnGQW^dW_S\o]sQ2Q8> 毫pQkm_QƙW=wwatN.cړ[+Ur99}<ɾj%" g$z|ll7 ~l6/,&9(lxqN+Rc߷?6I1Ϸm9v 1Dw(Ju;;uFFa~햟z*F `.^gsJ C[',GuIn9mm5XWÑ%6\N?W݅_Dr<9rK+&U8[pc'4vOFT(H4{ 폫VFWS'gO.EpW͟&+H@Adrr@8O(4V&NF?Z.p:k^jţ;$Wi;kfW"ǃuϺ0:@.}wB1dߠ^]) " "(Y B掎JBӜ;1k,T-]ETh#Ctnv~GM;TA-%2EUC}o3=Xo4WHIAcKUR-56 ꈀ5*{9˗-ÉM,}z}**9Jk͉,|$kr2 ߁-" " " ਤ׳6kncw Q7n%}(1{WkW3.ש(iַ@, 7 30I:HE@D@D@n '<;tPB9]B9.[p8AY2f3-X5Qd2PaFvl6e@Ѽh_{:;6q|>ĄH.ysˢ"ۣJD +=zD) @28a,KonpZ6NgהaY"˞+vpW1i4 Og|Ae t_9fպxb9mbE_m<ƘY[wH g?2q8#Bs_ !!\e]+/rJ N3j+",޹U" "  l! Jdy,U12 IDATLtrlipn1S?ujpi@)XE}^*l7JW_UI|{tg.A޳~̛>z넗o" L\F\ \?g|wTU_D@D@D@O@5G&$; +B3AXآ|%,*p1, 4<̥}vć(\HնY@[Vr4L8=ۦ6t|zԣ 1wm1AɱKr1q,6ln݄t6kl6EݟbL/lJ/9L'!\ ]1XVtT+C `h캄"ۛ@!x?`8 x(Æ˘Xn74M/=9~1UZf9ࣷJDcr0u(D@2$qa:d*+!a" ~/L)^} f<\r٠?pѸYLpal.Mh?CNFOSUM\|ۃVˇjo-͓KgE@8(Q廆_+twbe§F_2W%WV$(pksXnWU]h\k]ꘚΏQ\U"RjVP ~Μ倏A5U.DTz~sR KA/q }xp)mP,0~|mlͻ,M.)k]] gbo ]›%M27|u{ѿv}jH'o1J}_Bi. L ^!!up~z!3/yJ V"29ʶ\a8cY/| J%͗ +{PWVGg]3Ehkc7v'N#h=wU_.2 ^;(u2O4`ȴЗʕ2kajj&429ʰ2ڜ85rzλhSODJ$,7Ba@4ϫuSP(uŭMS+$$X'MXWcixXa9pQ}no[Z(dXHdʕ>"Vʯ) 8G\uD@@ݟhB o5T7ue:Ύ?*Pt9S(,'*g`ɦQDlXsd,XGa SmTTH0v/l-a77 +?n}_!*[ fYc$62TAbmG3< 1s`,2jŻlL,҈ؙD C`$R> " 7E@MM+WE \zXHlMJD m6a% ]LO//(.Q8%7uֲ_@&'tH;G+" G'@:AOGNj0U+ }VD ^1pqKt;GL;B6Ѿ J" $5'\Tr?}* E@.@Uw2X X&KD@@XyD:@2@S /N=J`7d䄀s_8'FX>Ө^D@D "A@ $ Q8|s=RN.f3>3дڡ̉ƉF£(:" ' գ@HxSd;?Gu%QD@\0~E7Y@Չ[$?Ym&klEy99 '"@'tMzZ@`v,!ZN,>-7([9Nr3܎Mx;#=I]@b2SC|`w ݝ ,o9RC'@F&}D '[‏ކAu{%$J3uLaD(U2c5 BZ|ן &ֱ{|cp&"yqk`66'lU8Nr4%-YG2VņM+D`ȩ@22qS+<}%VTXqÓM<;j("L{-^ڊ|/7|:N܎{qK9;N>Nol;eyR9R7# |dH۪!p'" G%ڰϹv{J fS+=~.Vd~Q5(1zM>1T&N2;2Wn\{>}Pc.|م6LW5/%SY2#S5)2oj5Wpt:^+Yj˥B1YV6pD:+y&@sz/5_y9Չ[el+η6GE3b5ӵ4GζSdӋ)>3뫼]ǘvb^ 9JW%nQ\ 2S st:7<z *JȤ$+]↙}Pbc " D@MTi|h߾`ٽ(J"BԊ0]|ӰoN>/Z@.VADHmT' l:P/@o <(ͫ@ܒgTۛ!+ꋀ sW"=wجt~iڧB*wVex;6TlhiP^}]#By{Q]!`xo+L٠ !aL}8:N`ݘEt:_P[nmQ$#G@ͩFtVv8~-ݗf~5*՜`*vn'fiZlgś>Rũֻa۽ f q%.k/,%UdyicCj`8o=8$^.y ջbPbq%f%<q7O(YH8L_ `_g@A8ZilVp@i8BgGQdT,e{ qH\W0­jx6Z2vfP[]L[!b,9&EoVqW1F=j,vƕ IDAT$0o2QSqTID OcİXkF6Ӝ Q:AĭZ|'oxow-lvܨpg~AD\/xp$VͻrX {Cłp<ջBP&5{!MtJD %P֏-66 *vc$Mc{3٫O:Vq{{T725 (8;RaKVذV#ud*+!ͦPxӷ]]fV*T0+:UzOUwVSFzg(SuԏɪiP##; CYd_#Փ_=E@u3mrujhgbp2%PD&eu8WwRwV%FMY՘ C##4ٰ6nN/79w%"y,]?6gmZ-| 1RɆD ?!PyYqrM //F(u:*B9Ab2#LJH >vJؽ;u$"p 2&AU2E@D4dzi_yU!SlK" " " "pd_j`9!@h"5B!Wh E?̂N`'߁ձ9ѮuwQ"U@DAD@D4QU(0}l{>K&ƭ}kʃ ! X(>6MXERuoŁo(u.eSD@rE@pCʈ@Z+Bjg "=2oo5b?7G\\]e"pTf5_cU w5a]䛀 |Ϗ8FqeUG#'~2<8t:nF!u|Ȇcŝ:v۷ڑ ]0bL(?Qe['4K%@"!-@gg.VE@.{b c"%Rf`{eDD@D25g0o7J[B]W]rlϒS>:`cN_2%s_@ [H*8Ŝ@vu$BD@._IwL T^U|ѤD STsȊ0۩gՓ䈀@ " doN 3$*Q" " " "<٩ L@ ͗M j*" I^^v^fERrD@rNNޓ`|>'r,(76FoJePelrp.e7XjǕ'LCf4%q[?nKT5&v\b)crUMp)5e+\'nw2XQ eHSD@rL^oݲ~TʌF2ՊRc9%4u.cI]|Z,Y3pJ汔E2$dIŴ <.{(#@&mtI!Зu" i b\{qXx_}/|ΓP4 u{__ QmxB_e& ^Ӌ>kL_eNS N%q_Sv\}ϰï4ɸhmLD@2WǾ]7e>Z,F^-ٸ )#le:J eVWF& R,HG-$Ρ䨿eNg%Gc~8XHwfh+Vr&b  aZyqVE@l)W IV :-kkٌM@aVn6 ,f!̗/M(C@FLlʸG$T-2V5&ecCDed…(2~I҄ʴ _&RϛeY#`VV|eeto("@swusN#Ae}ؓtSZűAN3sU:2ٖ*s_v1u.XD!<7`E@LZOm֏ĢPD@DFY561J1??v6ǘqiVDAu_Tۗ~1q>j55G5_2}PD@ %&~fᩡ(6t:&ef$cc6%2_g92|bN2^°Y~[G" \D&*)8e'F" "pǒŠRl 1Ayz=_>;3Z )$- 5=U6lr5&h&" TMD Ĺ7 /" I}l`wHO(<,Efh)t\(|?$wre<68OܑW=rqo2oMx+DP %AD&pT/6`ėR<>%:>cYE;":#U dYil" 7M81:5p`.oaHF@dJD h'h" " " y$ pgE:!g?F}9iB=@&U2+!" " " ȫ_#(˛3i," " " 2uDA/QQ=|*"p$^>$Rc|UD 46ӨسX/|6XD"j+ :1:5L f3x) V)@ H'Hc3^r;RLD@D@D 9E@2$h4 )6P_ܤi" " " " NMD@n@I3~׆i$" 865mWzbN\ E@D@D@D@Z:N~#'4~|rPD*mN<9SCHL@pbtj("pb]; KD@D@D@Dtdz2:iF+M j+" " " " XTYD@dJCdTXi$ 8&0U~ǮNCPmE@D@D@D qh|OS~YNCti@t2RM+to *mOA@A@@@A@@@@@+\rIg7ו壣W#" " "2AU2E@ޢWQE@D@D@֗ ;i."P |1)Dz]+" " " " ,FD@=Vi)f;|IAD@D@D@D`d/Dl ;uz3z-!bJ^ ve*"i]}('0aaoGD@D@D@D@pjC-no'Z H\D@D@D@RNS" "AZ$%b$*b.>=Cf8bsC@<::~7mH\D@D@D@dǙ(FD@'}nX=eCJJ$2(ND@%!ֹ6t$E" "0?֋Et4RD@D@D@D!j" "P< >xtx9 " " " "j" "P" " " % u#D@6@eBM_.D~(32sRbx#?$BD@D@D@VG`A/ժr}m$qu'6Z" [Mo#n5^D@D@D@O@3V" "cfapJ"" " " E ׮Wk^Z@V[K@Zjm'*F`S~_߫oTTH%4:tRD@N#-(PD@D@D@ȵ6ʊUD@DO̅fFtU" " " "F^ʒ" [Dݰ*,΂K6R9<</p)?r]+" " " A@fԣJ!"bBg|*$276U$P٭>gIћ8+x8o&JD@D@D@"t u L&Ǭ 1~i@D@D@D@6&Mp"E@y iVcj%JSkhu7\ݎcMըXZLu W" "07Wߒ6瑸5״vݚ(hv|5oeφyչ|fR (%#"  `?aWloBJSTBOn8{ӑD`gg)" "Pr2K^AROD@frvGьtzIr7zdͮkLo+" "" XAD@֟6xrŞQJSwtIѵZl F'E@Ddd"0N,:GLߢjxp;9mlDZ=~w+M/AI UW" "P8sE۸6Uv۹v@:nQj4v>::^OX +LE@D`?|4xK¸+J}d8>tr3l+sUU!pz=/~6;֫&.~7NYH oF@D@VG@+gX͔x=m軾#yǓxx}Y;P2RD@VBGo0W"2{ۨh"dw裛|S IDATf$OQZ}lT*"Dh<]`V58_*g\vl23ϫ\E@D@N2X:~?4((cSYOLRc bkKr7ߒTED@JC`kU}3M}wM x:̧[A]PW%iyYRNFD@D@D@D@D_RJBliWu*<&4A׋yעVP" " " " " "h2p Lo}oghTk*vU C`}WSL1j5W~8%iq *JI'" "do`^$cއ)fﭱrvt:L&ӛie2-aB7S4;=L*JVo>oc)3*-^ ^2.6>^5n,a ;[W-,y;6?3Gq[qϪꪱWodk<#k# (" " " l;oƭo(!kGc73 t?6f0a~kv*aqyy9{*{ aD{`0,jx2C`#9[8h8y#[j^ WwM:EPD@DQ2uZΙ.yR+XJIthij\S36Vٳě5)\&~^k#c_l],L\B = 2m=oV"McNc#b.ONT\:y69 45)CyD7S{m1FԳF+S[pFF~pzyHG2o?W-'`E5H$ NKQ 5]tvar[prR CD`uϼك`FvG@b`n6XnĐg8q,O7s"&ƞD֞k:krYͦ b0Q>6&4\8=ΊL2g"R֭ 0ۙeS:@36S {p(0sk+IST>Åc\r39ñK,kKLp5w~bu;_]_!]+j)j AsTS3Ȭuez'?.9AQoZj(2ͺk&g6&L" "N?]D=z{8㬧e$^:rGySBR^ͥ]TU=okt;v*V8D=靰UE;|6]+YLn]/.,h4yfw.|j#\撯" "\#)4y(SS3n(_OKHyC6xjۇ " GwW'+#.2P6QLfbҪج4a0rQL%p|`#̔%_k4^J,K[8r%jD@D`|dW&OE& PYcS:Ic?#2%XcN9Lxw+fSLY6O_h:UlYZsuǦȪc ]x#Dc'YJND@D@6@o^\" Eлv+,şN"-EZ8|})kYsRzNv9e2X.[nvMD@D@D@D@D@D@VC[2ʵsI+Fj! 8mM;ÿ@X{2׾ SF@y G(xM %AD@D@D@D@D@D` ^J" " " " " " '  %AD@D@D@D@D@D` ^J" " " " " " '  %aj-*ҋZ-^z (Cp277xkm\Wx XE(婋5Ɔ^m͞DsXA>45L ߌ+?>({8x}0iX 2^;ᐆNdM$!  ΦieggnYQV~_ch9\VΫǖ75^LoJtFGT*]NNOOCO' gݕA.MRcEDavŸ1yz.JZ=ϛf٧3ʄ흒LQ>Wa/..P{Zt:8B4Zb/J _8D ZBD@D@D@D@ ୪ɷ67Gؽ?`0Ax;*m2,׽^%e`??Ri&cL%QI{ \]]Y\x HNk*,0=b0g9:R/BcnR.蓮|]X#qLf7 6m$^DWOn&m,aEjhl gB5 Ť+pH\Z Պ=aA)ؠ ;5=1{M'EqaojI T/L#+&U8m6qJa\.]y)^D@D@D@D@Q{rv@+¼\\;UjelcټA-}Ik!\R _}psvuZ\|`KNb-;)%KpEF剑!ҸyO]" " " " "j !pn[lT^<ɕ YiEǟ*2/$fǒ[1e#ʖ*/V\, (0a4H ^ @W..%:`}%6|9hk&?Z˩^mbLR gyM,S͈4W`'l'Dz ccy= byb g\T[o" ._haJ1ή]Ĥ]z)\UZ0m0@Mfi$Mֳޤoeha Ng,e_y 4_zK#e'" " " "BBu j.g˩2Ey6*IEH X." " " " " "P2KRRCD@D@D@D@D@D`x|7F:5ژ " " " " "  SE2kQMW!^ i " " " " "\|i.671ҫO6jg3RQ*r^Mew ج2h`TyT*I0j"kT.D@D@D@D@D@D@VD@+[\MD@D@D@D@D@D`EdX.D@D@D@D@D@D@VD@+[g[-@VNjϧ&4m'J]٩*4`< X He|Ye݌7v@@NeSe|E7;LcH«5=o$J(2Uʈ?ϯFFj:|s~^:f.mo:ƾ1\mx76ǏG#5UC`P/@jI:V7LMo%Fو<$5j"p#?99vrMb?ixq-V#m\P;;;zNsJxZP"I55ش{x.ߣ7G/0wϚJ73 &#=.b!.bdꇇLTkKמ֚ϛ_.ku3 ?>>NfGtPyr4?Qd^jl։'Cozakn^0i!ު˗=ywkP#jrs@dFΗ F.12F?K$'F<#H@8$?i\NPIRD@D@D@D@֓ h4*1̭``mɴ[LpzaL;w Yw?tأ#h!1KC } @d'gЄ":w6nͻyoOQ%C%15lC {08HacVAIQ#Aq KXY.㱴S# [$@d LJP#CW$, %޼lN?b8}E mlgoĦVv6 26a+!O -Vn Bl<{ c!O!\!1s  x>1{8THJD?(N6 bI,0Jqmtr%м;!eHBb@K @ߌ,-"B3߈_[+x" ŗ> kœ5B췗쟫WAz?ckf1+? Q (&cR^f+6]K}:7ntUFFS~Y9HS}x_MՁU)H6a!NO_#fױ! +'Yo%:r6bjT֞a3iYib}aR=N,\004ثL%,W.Xj!QH$+TvRZ\Y.əfjܼ~ޡ 3vջml!09mWFc۵…*dr 60.q\aKx \;f)(2;0.rI4 IWW, `=֐m hͲ{2ٰ͈q^ dbcڍ0GxdJXA23Wr7utgfx_ވiۉ_z79L?_jղsҸso&OČ\TؽXV[AI]:$liP/Dw3 B k7rp8|@IdgND@D@D@D@HS9`⚯zx^Y+l0u"^X33GlgܰLR2VI.]`#gl^̿;fRD*$%ݭǛTM 06k懳dh7ao%TCwf4U,UlksoT$1s) }ːx$0˰=3$}cZU_܇Juw{zz #ᜢ/[mc1T0W-.짲]ZJʃ 2W7G+ܯOm&Yp{- ݘÌe Z =|XOW,ŒYt(*="ғwv:l.>7wMzQ=mo8PN" " " "Q{rވg .. yv%c0疝0g܀'K 2V4<-%ඥ8A7̇|]ї<)7YFZkX'XN1}y kD@D@D@D@K@ryoYn̏-ʻrI]ǿe ɔ}ЫQ+:YhTly뱕`P" " " " s<'-l%YKȢ(P3?Q%谄,R S" " " " N@@{ JLdg¤D" " " " " " N@נD@p&LJ$" " " " " "d{ JLdg¤D" " " " " " N@AZK`{ _ʒ75J9 \^^s^\dZJTV+^RJD@D@D@D@D +Yz^Rɞx-R^j" " " " " "P vEudFk׫d NU_VD@D@D@D@D@D`N2D@D@D@D@D@D`Lӫ+~7,3 x&"% %paFZŒ 2@W?9Rf @Q" " " " " "?~bfZUD@D@D@D@D@D @Ek[ LOx# VJD@D@D@D@D@f06&@oLU " " " " " " id9! xcRH#5ittND@D@D@D@D@66|Ue2]d/" " " " " FRt^vOgI^i4zKڊI@tJLo.(ނYeuEo?x^M|ԩ A'E@I@S P@e\@k 6p9ԑ"H." H@6w5n^1]p'z&>,OD@454mƤ]E IDAT̹O,ʚNQVk>orrs0Yڐ+Ѳ2J[RMG᧳ѧ9xWnERN'ɏt8 D5>6.<ۿ ӒD@D`# j-S~]ەқLzC ЫV2.]D@D`6zn i N|֬ՇF[ϣrkZWch" "`3xnjOk\klgxBux2=?7}հW* <(%@<ܰ޺s'/vߟaO1 w^G_.3L AP:&0^ O$s4ˎ_.NۭG"FB?9r\xir]aVa# eEP&XWK.AC1|tVD|*ܳXR~v} *2[/Z3woML_Cѝwޏkgܳ~ X_Υϩd3iX FP͕?\l./qO^cbSyh}5(ogM>{6^]g:8G up'1=wr1ٝ=~bD@D@ )?=,vrhhe911,-<19c?S?=ʝ`9B|6݉zNwVr ڼ- jsiSIp " e[)a:1<N 1 " ")0Ib}/@"-"qL0zaH_pkT~b|/@1c 9OIυe|Y4ZUت̻7p՚-7YݧJ;jXs|ͬz/c>|zϸ9.d=f'!Sl"y7VKU&2*b$ӧ:\;nQPrIp۷=ø|/R!=r- 3%60'7? Ϻ\6Ͳ'|ܧGJb~ x vqN|;xw2g*ψYN"$1j`=ssy#jt9lcƫzgojYKOE`k5U=oAKND@D@D` hY8wz6) gX+,@kt" K#]Z@5)Z }&[,Ⱍjp_B$Q\ue p0\r=γ< 5jG@֯Τ@q`aYQI"2WST>$رm.l}_ß)˜ NTFQՈC9cˇmHçzR.9|MY}Re3o ~ oF#V(baFxkA骑OԔNR+Ti|6R ̶jW;Lŷ_ti^϶<|zBdsr.WKψG^"GG޻;E㫹Uz$ ʘ^y%\ }7B[k{pSܿXv|_ܹN'p}#Dh(W+ckggg=x}qn*)і.m"`ҡƯ5m^u۽N= ix72MEAD@E;pҡ[Co:\ Va9.p/(΋3x{?wq\oƒ+BD` Z_vywl~t/;wgc=謠;Ð_lOD` ű9m.3Îr(/dιc"?ṋ~`T xx/ y8q3!A,!QNys/)zMq(z>59y[nS뙑4WXL62ְ䘘x 'N_qzbK5l ;W"La/4 ~B#\(^D`>S@8uw,<9`݋L" " u;;?bδ[37!{|k&w[o^L~l`/5h&.Jf"gVKey}nf^z6CJ; ;K>r .I?8AwJưCB$!y1Sh`3C^`cÎlFqbp^|ZWKxY5`X|O~f4Kَ&I+{ŋlW+)1=7%t%|@ww(5㥬{K@q2#e]2A0,e -0AgÅ83qwW |R5'AAo]4,Fks7r+>VgF;g\oseKU0"HD@@okͯkR+a " "R&̾Y}M&XvY`v~62;%>,ݛaQg jG[~Yk\5SiܭfusŻe0ҙR؍ßAhϣ)>u+3\)^D`S 5[r*sph0?]7D@Dh*&D=W;]~ST'5f.;5ŝtP#şG"?wI{R,Tܴ2dd^d&`\/hߩNjEx"G%v=$}䣛/?7c%&(Õ496+Yvklś).\Ʌ@flm>ywbOuk!]r4`SBkvb8ݳY$6)uJD`k ڪ_V{~1Bw g*[ Zس*T>"){mva\Xw}Hq1\#r?]1#K; 0Ù/̶'q3: Q!c&}$ּt'%?'~?\]Y$DaӺ&[vaR»8KL`6y\a/qppM>7S2x277/* #"PJzpŭ%<8lښ{2X`E:/;[+e+" "P2/Zf4Nֆ%#"NwOOc{Y;Afzqi^[IDLO-EUD@D`hM. _}~+ZH!al]OZגHo2" h2v:lv Ʈ`Jli+TX&ˤy^{,zR350;p6GJ4*ˢFz) 6E?gI{GYR퇎Wg/Vn3Ka_6tFxL\,iW-&~txw\yZ ?m>D-}TjjM{FKHY6lmw2q=<=ZxBScOa虨"E@D@ h t"EG//7f;ⲑ$X,/F |4ɟĮx+~OF%?F rk7?{OkrL <\.Ta7H<;}]5zem}p !ަ5'I UqQ}HS" " .2]d_MC&(É7SaSXD t:sCMX)ƞ4<(U٭jc7g `W{׵c|\VJ"g9v=ȎF<彀]zrb^N2/}_, RN/4~Vް7%V fn[M }\]FFwFw2SK@p^bJp6Lٟ}6pr]NIC{8pr6nxd2ooxxsK) >$NO^ :]Ϛv`]h/cl~y9.$cL!0N[_/_{Jotw¨)V D@D@d , ] L>1{H#E{I"`0`ÓSvR `%f1=#x=-r&.+I?P8K岜/y>z#z1 +7)#S+9JƸ]#Lo}:]rL|:`&6I4~iz ķ*dS" " `&`t&?Ӛ |ܘ7`g\?/"0)ޖ;lwu7>+1q#{b^5{f,kڝ\V{YL<5z */%9])oŶܭ\&%[?+%'5v7`D8c`npȬ(Lʐae?(KAD@D '9)cl ?fU0&s/o Y +J `:[5.4QWL Ley8wKYkmIJ5Ϯ8ҟ/гbp2s&3_ froO~ ,2n \J%" +$)+Y?1-toi?`7k7o;jU"p^~]nc85CaKԊIo{,ti3/;wx\ ;j,'L#p9EZ%(uW/YeŊl,ܿ+qɲTXÍmU* ^0`PmВX" " " V=y]K$E@D`; yR@ .wH;.Ĉ r׏( @J@ .wH;.Ĉ r׏( @J@ .wH;.Ĉ r׏( @J@ .wH;.ĈNՓv%#pY2}xz0}~VVJ." E\E(=M.}P=m}82a63?1WG?ژ[y?<1:o =YdS3yZt.yGNBYE?<<璟X_oJU|i'="djIok',1R_v%ߕx8 &W>-߿J-" " e$^ALvIhl*zzl'J U\| IDATȽ!gP.Ѕ9-~{ˊNusLLPDec*1}$=|aJ;t?|m`0!wz$fٿs ܃)"o&0s71cy3N>vwrGD9I9RyNzMsH\W(" foMY+%{\ŵÙIWsqv1 pLL1߹azaPlyn}߹O[\6R^EBȤx߯! ̜} rǩ?mfX]\x폘 ǟc6ع}/" " s<6]4XAʌ7Fl^c~6W+hq|j1%~= &KX\ysϘ0=?w닌a\} #f>m&OxfܿGGFKC5)i (Job 4v}ʟA8d%fݟpSg\N<~m@,|g0@NvTp_ k?}g2AL}M0;DZd\˄>+0 5.̶s M,+vL w韘uXGvOÿ_wT۟ 23T޳r2`;):y/>doq6%(|ۏ0tՂWW=ZԪq;{_ߋn<<^OӲАFO}U{|@~23y خmn ~o݉ SN7|c.Gwf3򔦘.KПWzqOKyy0f̰&7]Dy#SG[DYyDU1Qh'j;rx~dV3`V8&~0gM,$t绘 Nv8Ǟ}[W=+Z ʆ g8~r! 0h<=KlH" " "@MBH?8%NRА0 u'6?? a&υLGz)<4tۡY,):ͷdx*E1ȕnF6?,)|g%yH!5)ptjunqx3R5tŸۉcb]ɜxzB!aMgTmҐ/ǹʛ|ks ʿsXijP{&H/-Ekzl[j| o1|gWpoxyZ{@'80t [;dx {z ?x.[ɯ| s-XqEER6cSqT߅& Bv/oE<*'~s+ߌ_՛7ĸ +(EJ)ԋc|ݤz=\OnPy:p~aHl13P+XCNPm<qySY4R9/|=n'GW x_.g{ə=rUh t+HꉀC@p1%ED@D@D@D@D@DXj  jav RD@C@pV." " %<۞$dߔLA#" K`۞^%슺`8ŕc9l[yY j^KN׭\]IIU_q|=kF߶ƶ7Z{ ^+áwuxөWyj߳ޯ7xOkfy$o"ԼN#R͐vQ !l)jzym%ϻ4wgq[7Mz;=Lʔ>IX.W {dz7oaSDyG>4'Ϗk$y0i<gʅһj?)SzyI$A!RUޔ5GxRqNş\?O/o> ZE@n\/ᡷ[16m睝KWEk:xOa|qaL]IBx: lw̼jzϿ7 WczMOzZ>tʋF ͉@{ qzpOWR-a.]/֯KNV{K~ѥ 퇮?}5n9 `|w~\x=-Rj0rslL%21 ab[8&@(.]XbNP^iB60r\Uv4дvɹs{?Ph95|w~\x=M>-ӥ+>qi*OoN5 np~m'\OvF7<IQҞJ/fʟ~ңsݶ3ն f7}pyvNX$+\r 7.$>4뻋X@b[{~r?>_;ݵCX1)zS^uf>!k`*vĂ˨zFhDBD~cxdǣ e>[(.7W}J;`Yr1D[f! mؐHoGyr_k3!uE7.]޸\6~bD`^2% AC3CZ `O"cF怀CgS$`_Ќkrش9It=-۽T'خ'HaU 1mC1165LSjr-y-:K]rRTJWeU%cb;"5_jfy0Zn|!1}gV3GŨ;#`?0pHҟNafshpD[_+џ6\ؓflg,R ]Q&֣IL3\wb;{d#b&W&@77`bI]3?/uԣ3kR3}8gvbsĬ"O0 7T U+w!.R[=8FL4-퍘;Ksb, >]r[z* ңR$De;r_~?G>Gl*[ xhc\J[t]rRG>W)=rM;!r},lm-j{21}z.'˲L Lrf|QWML& Hvq)ra7K4Ǐ"c4ރZz9o{`CJ{3YxpSXXѾzX^> B5-# x*| ť(8v[6б܍Zhg1Ah ǝNO!O?51U#(P~hÇnO7[ R׶\v d1/.9 3ޏY!/uZ,if7?2?'ztw"R|31UzB̍]|TQm}fe3SӪ \ a8,(q<Us$gڇnt:M\<kUG=\Bʅ53#=߮\aƻ)Wu Q')Dyd } 10ˈw:tHw[/l.'LO~zra|`m҅Y|2''fYL zxKS`(JKW<][;=Yjr- Ǹd-u_y?vr ǻi9j?|]1=&oy]]j?)p=W0/7v질wk2IabzD2*=n#wU4{iJv!;`ˀgz %B}>U_ҟtX0B4{Aʅ  lo3. p7~I5FGtTJX^Jݺ(^= EAlk,w . ^Elzۈe^A$Lf˜xZ;o06)%yȰ70*㬌g]|]ņy!]HΔ.wOW\֕K+=Mu_]`:7gv .=]rw~X>.ݲ\.\u6=d9;LNHR.!wʎ~(rɨ_$bdk1NŢ D/~wr1;CyO5ω}}{ŰUlZwD"h8:/ |< |u8PoJeVU[ B|ᔳA 7 B%wݏh[u2CᏑ MzѪ9\]]{~L31̕Uޔ"%kN1=}l.ܕ1[^9.){ׇ] hy.>yw/Oau"\ޠYx)cރyӕ-nLƩxzy3d$" 2Ay_.' Od:g"ʓdokh4Go0鈻ⷁcʈI₷}#ZY|LP,L> zAQWы/~G{b i ;䫪V?0n1qj4;]mv %u?3FnU$e|r#S^Ln_>AC)?ŅKHiT<<rRU^>93Hs@%39vT2~䴰@na|]vQ_&xY&=il ,c3H((8~ӓT7}Z4}pb=O(1hb WW7֠r=󴀝쌆hfP? F}x~O vttd-Cz=`.Ca|T (s(0t=XྣHh*&쬵lR sqH/+xy)K]]ǻ敃*谲z4\σx;o;ܦ.$@T !1}|N&keHoF"5êxnanp]r\s+!p֋~|e^b2.PH'o}D*:gZ_.f] G7t󷊹#jgby1zo]t>Tÿs7¼6Q"E`seGl:vY=txr&tDӬ%OD{clwWl1;(H}`<",ʥCňe*bټ8~wbŊ4[Z[Ġ%c)AgO׈Af]<LzV={tX)gr]|3GL IDATF?">Lg\ZVB}ƸѕWWzW||A~s`fF9.}A5{ٔ ?T\b1o^AV-o"thƶ|vӒ#Iԟ-E!OicVgr[.x6BJ{#m3"15w ֏k;Qu1XCzX^P.n3SJ@pVRJṠf+$rp+ :x0W|J̠9 R.ܰSz<~9xR|/ )amboy \enfVf9UJ| ߎ@%&a '4a "^A&& }grj8dHrY#K'C\*yA9ohWxinE*+Q.3xwd:ڔHڭCE`=5]'3L ewׇfgu"@`Dn-Cn .NfffR)c9Ys̅0́ĥo߰늧kr{Kq32[@9X]LFi"߼sHY+ mvRkKJۏ){p2sY\]#(>uC7ͯY۟T6AxLƤ+*px1g/r$\5~Sll!ΜD\k~s^d&O*i'|̿\jr .1p$}j;3!QN檯Uo띗p ) KQ vG]`{YwmL}Ǔ72HP K)ϽG"0y瀦K` 3;v EfcϏ66Ռ f-Ȼw9u2dd=p]Svza'$z1K[Cx^bTvVp٘ E㓉Lo~wbliP8@2Nf03X.3-پFKיwc 3{`]v;kPb6va|=[z\) p'JbST" -Z~^ЧT*練Kuvf)4̵S J> M^" " " K }Ke6$*J,DFV[Z[@oIE" " ·q4}G@ @ @ @ @ @wvNt=yo3kv=>_),/ORPb'v}tږGMޑ'K5Pj+xXWa6hRrh+7?<Բ1ea5u=O}6Qb a VtGF VLQVjgGkhε'e{kkO;H]}E~n,–k)rzsCamt-WMϛN3] v`;>zNteɄT- މw䄷2ş? ly<zާ=vGT0\W~ƶnF<@'0e? -Y_[v98ȆB$]. "Nzl+ʒ3c0Ҕ*}t1Q->(n9̏sTMOpPA'T͐TTgYR.+'o}zS1,ODxu;AjX}sCm h3E-e`.ZKW`ʟe#)SrRzXZ0MeQAkjݎ:{6-C`u M/k8U|ʥgMW!S +`y_څ5~$80LINNocaiYi'┆CgG>KT]eGFdPB#&?q?=Oif<)L|X*CodH*%?^:vR,Oez1j{7z:PJFI<9䑥Vp! |"C3qqBϕ?qP3'{,#}acȤ %Yj]Wx-z꟒W O|# Ƣ\}fz&ϣ' c` FyC4ϼd-%lʔVˉ^K0=荐ϊ꫰h$Yh/~]:9ypH!8e!);Ш=(cg (kS5_J<,(׿O9g WS_wl}_\,ASRr*H?SYzA,x?|PιnW^tC) |nK7L[8;pUq9)QI`*]"[l$Sʌ&*F6hCBW9;c?+[^kG>onf炎 \%rVHFGBoCUc :w['<7{):(VG*Q|S:r<>p:_tԆGCЦC]@ ֈCi{.Uey'<11r >(-t'Z.zX O[^)<}i5aSԝ !cܳBυooQu|qSi(׷&ձ۔΅tFh\">CcSxrK}+D饂U]AaVhj< SvB=gy+@K"'?,(p4λoa#'w- 9z+]X[k,"pfg) ҽǡ? ciEIHYZJN*KܗiP|^} :;yƑ Ӓ@M;C*O4=|͑dsp|O?D!p:௘~ƺ*\>nUz ;=4etH_'rUa)L7ǧ٧$O<2񉦇 pCO:[<'ɧY ^ ~nBܾW1{п`ʷz=O}}˭/GaEXi"V 6{>,0Q4''2rЏ亙gD:4>A1j=N\1PYTjwX( yyoǩ:2yUQAsgwF ZEkD>ZVr“I4gޞ *V];Y)L7'! Y%;@z 7 C>Wrp  ߡ'i*1j̷ 'us#|_YBs k ->[v_O}[GTmmmH^.;dz|҉;NVG>^" ծtKћE^E-'Y檅7J4F*ki:v[P;.|e`~=e Hޒdą$1);i` >y@%BXvKpq4~3u+1Iٕy#dGrЪ:FbkAB Xykw%ԐVթsq1ɼ McĠ{d~+ 7+Szv 9?X;\VĈ8/`[c喖OL ]"FD͜g]Vj,̓T/A }pQUk\&pO [n?_vDg >eZrQ5%վiMW חv`sv/§Tr~y`?`Ơ*Fw/ہKz{$FDdiN:iȃO8)ב&',l;Y}N=e0p&wQw[}UۃCKQ)e\e%Oƫ3@|Y9~[y9A^S6Iȵ˛!*M}GRvvty( OA5jxlưx]͋a{YDa.S.NݔMoep2p{tK[x2My?s^NW׺>8P٥;=œ:3˪WLaIME직%ɯ'ޭV,Ln|,ϒ%AwypR'pШiR*ĝ"kɸ<%\3JoEK/!j]S,Oe&^^@f'*0sM!H /ŤAd^9Ĩ|2ݪ˜5"y%ϱg&b @uLW/>>19{W5L*G unSqa,fVrjYёevA$Vl[)V}5*emswh??eyZu|~TB5 DŽ@͍t{Ǥt1_mCXŽ,ySj+|iyv'~&I6|guLo%[GEk~9vMlң[Ie6.)W,0;g-1Wm֟$fRoQ))l8ReW76eeErYOl{#vlKN٫nlv#ln%0$l} /5&+6rgg 9~ؒ:'$Áal.)/%ߔzDGO= Y^5gc,]cm=/;xө2r#Fnp4h01.䉶#.,f>Uf}udLR`'ٝ?zUE8!+ ?a1>`E9zcTOʞ>C"/Ou #"tL ݂BNL8m;E:=Y/g)EO^e X{Zw:4T$r)rwDLG"gu78G$ŒRjGQoL9#\)OJUL6`_R?)a;8rRxxɦǭ"y(x?eZP#e?){c/?_9NTtfa>lz 4crQ eZ"/ռ^ѭ#U߅ELd!7EWF *A!h×t2QQx2i'98 +>UmD/NY@K֫Bim$C>-z0@lpi6qJZ뫉^3X0`՟NєQz:۱\`W9wPɲ>E2| !yN7e?21ȟGG^%5 ak,QX86)$GWq!ၳ2 M;+‰"|E}cD1|HI]\*ptb IDATJLLq_}DaSD!N`X|3>JUlA`-Q+ڣqZ`=8Gt!0)^L[6cqY RxzU诗Ǽ5G tX5>3sDgrsCg5ݲXoto bZLS5Z/+acCMCu\ }"lfBV) %OwETNȋEFawR%eFG_G @'E_=YQr2 9?Oi-ӚC5X:>Rxi*HCwϐC tC0p]x| Kg :<D#w ]]^1L:ąYMIxŚr9̼5xBX#~Hѥ٥}j˥R#a*P]/:[Ԣ84 :fW րYy!޶ 8jQ8j?ъ,ێQ!kLLJ_SѬXRh?<[ ۩QЬ-ب.d).B}~.dJscE?mK7wn-@nfI{|{!% l炭|8:ȓYz5_ C6U`!plEח{/({/x{0*+"[CxY!$i~O3h*&Y_F'=KYC ]mjp"Q/'w*;dE_Bq(ꏫDP/:ȟB?n7ol;T:K[^Š-UG LZ{'?sw4nK \w_ >)-0 [Y߷8 ߯d.Zi*S0 1x/V'9K!a!D'P!`/ " }0|Ε V)մ!\ҒwVBL+l!`!`  ]/<[EdvyX C0 C0 C#`7}*1 C0 C0 CN0|'>3,jd1 C0 C0 ~nU6 C0 C0 C%"` V:!`!`!0ݪl!`!`KDKlu!`!`!`/#/ѭʆ!`!`!`DV:!`!`!0ݪl!`!`KDKlu!`!`!`/#/ѭʆ!`!`!`DV:!`!`!0ݪl!`!`KDKlu!`!`!`/#/ѭʆ!`!`!`DV:!`!`!0ݪl!`!`KDSVtx<|GIy 7*ي]Z#ʇp[Ɇ!`!`<[Z'''+wMdn{eu̕Dt2P*˄M;+~)lI>ֈϠtLyC0 C0!ϗԂgf7r;o$ Z N&ta6[^ kvW~U747kglzuu5:7$=9eT-O]}܌^jȰEᮝ_vȱ[<ֈ9iܬ80 C0y\LdT'zKڐ%$xr'-a8~o0n JS_N+ wM&<1daYȳ՛lmyYH'o7zr;k) [N *Jn!`!B~^$!w%ƛ;Lɡ&2u}IaҙL>]$/Ul:9+X1ϰG#LqjCnCkm'׹S 7gR3^( рi+) uqIt_3 lnduԾW*W* [}U i̵@͔}*XWk[ m giL[ ε>ƭhGa!`kD٧ |a*E| vs|| ~J<"z|d.4H{ Q9:?(""KKy d_gPJ|v>{Tt[ܛsf%NiM۬}p"F~ZELgͫ4nL~rw\S܈"0.] ^ ibZ9O}jR.]2g5STXpRMǧndZz?5'GVL4jC0 CXEbȰ0OBFOMt"B*-L3NvGBK;o:U9&YtlձZ g4{ojtC/4}m|ztՉ9d}c[J pGN&KBF|Sq5ۇj52j79T_;-`hY%;ՁsX/]ٯymz^E?G+6YŽKUVr'0x:'EYh:0ۙ縚1Wv?nԟJ=mvE\vݗ~M{gWhw^wWc lp8S*y0z6e;160CXټ [ 'KӭEJZ!`!`<}7~_΁ ;b:I B5Jdx}q!l&pTuH 9aJy7ή$%'21/:[/XyqULF L ̮B6B]%>Xq"XhyaSpSdKЋ0G|0Yɾz.w>a[cQ`%5L &W)'¥FfK:J]X]6i8lE^=NḠ`Py]DԦ9$#Ga6e x+dQoъ Icl6ppu~y FBYVg,Vty!쪼 6V yPEi^F /[w-P1Z%!`3A^V9¹B|tjLn2[7z/S X1;Ws1k\=xs[꿲\m?t3Fr8&Y~w?!3.Jpân=*~M= 3\@ʐ2[䖒vZ8FM* &Ys:6;# unitJ^+#:zrD9ϺXLjT*|oSAn) 3WIs_x9ۙpx @}=s5P YPM0¿ю==yK\2fݾ3y d՗uRE]h!`kC`6IQAK}9[ !5UWW')t߲z{?<$3=@!;svwƛI 5kOF3ZG WZl8ˁ[Jqp/pPr]V,@ !]{zrJ%Q'ʰ&nCK8V .*% ҀU+][eWp3gbD`+wǂX՘9H/)h_;p~v{<^Eȃ.8Qgmj|#dKƥyx}:0 lq %vf FvvinqrzVc)dO682Hbptxr{yR:D!B 뜟c<(#TOӫJѹI3BTYW5/Ǘ 7 2GOT0hh] 5;<:dLsɕwL}*8gY֏[ Ag9Oݔbg[NC0 Cx h6u/.V>z |6L?_xtUmy^`g:{"UX޿tNVt:]ᥬe/x ,"С衋KQױ s~QPZi%NKx3@[BW@N~ҷj3/Я;߉Ns©y_WmӲӻ>*T3 ȁIW{3Hq~şe%[k.qβL&'WsIF1V\B1?ELsQih-J ! 20ې5\u T MFA:{{N>„|*1 'IcU'C4/<<$_'?Q5]^j"bD)Q̷$lK6 LW"p`s3+d%OʌJ*!-F:zow~F*̋o&A+})l:e (BFDC0 CxTBdJ<]pHOx)K+L䰿U8 9朤 X?%Mʒ`wxW @R3p U2i̲̚U`YA8#GŹP ^k;9<~t@vTzZ)^V庝.hf2^Wa.7& *DF|A^ɖ²gP#i' 6?du$(٥Q)OF dEjԺܸuK\S)CMC<-_W]2*Q4NB$6䙑' T-)ؼgԅ:p)GpS.%E(bT;$ZgBpDIJ*4_GWp6Z@ oԻUy^դ$=a RVMB J^&nsN4ιī%Bi O( ^EAՍXq]2 C0 #0XȂ~OV{(!0^\p=ҵ'{_Q Uzbùk&nV}B!5:X@$s_BqFp8rS8 پ;VE+Ăil4Tt( `!Z4) 4F1>zQT(8KRv#]+@ϣ vrr-2Y\`s8LHq[ ڢSK^U]ʾ BXXX`MJ5=|tL *oA[=>a%'e1Ex*(z] 6Koq`daS"{=q۲oZoԽ:ʁXAC"dccS*wbAe^IXpq0w4H K]q:+<86? ;J("BJ oN /&X\_fDžEhdNB$vA~#ؘsh_j tGfznR:D%N 5G.`F+U15Z%ol8v[]dЪj pG8ñBNpjӗqW{NU '![S@N$C$Mne8lM&.֜MY)tau^t 7N<6>0u5Lj; vHSZ'?gt)00C~u ,Z (xV4X_  fֻ us/ بСeR :j [=$VQtj!J+mJ3k Wo\~i\ScʴivRZlB\r>E^uasпв[Hd8E#sRP(z0S:X!`!`<$YDYHIhĪí#G>K1f#%K84v9G%aW+.1G C(Gb u&kA򗩩I_ KL _uX2upg鷟YWR 4n??%!L{2;8?s}:kyMl2w> }U~Xy)r_QsEv!`!`AL^!1'/酃N8xi(/ U[2_)4փ?cl_-ŧv2VzҀ~W^$U e4\]OnP6(fl✽>y(R|"qu-h2/,/~aϲ/M3iz[cqvZxg -")MPcN80e*mT1ɯ CTbב÷>w gy 3XHZv =v+n!`@"~Du]\;y=W\VGhE ٲ*udXJWxY&Ja!ף3 %"E&S:]PP)we H2mC1A`ѹ1~!K.Z(_m]!L}Mrs=V=_o tcӒir8!յԜLΈ햫ZzHJ A,ܡM&M3W[D #4n@MF4{}~ nwF\|b!`dJfý 銳/9:<ݝLMkW5tX*sp/Jƭ>nSGQLa`QNܔZff e=g\nZwr!;,Y+/}}-@hwȝCxx::[]]Y 5 Ӄr[˺޵:h$smŌu(ޓ]%m6L qRy0"]]7)M?+  b!`wOXjq Y;*@ onlS"|ۈdi딟oS."mSKBXC+!6u>a!|u,Qλ-D6O_M)T,#ײ< "RU^/4=1.6.4 sphinxcontrib.bibtex sphinx_rtd_theme pycorrfit-1.1.7/docs/sec_getting_started.rst0000664000372000037200000000174313554642611022122 0ustar travistravis00000000000000=============== Getting started =============== Installation ------------ - Windows installers for PyCorrFit are available at the `release page `_. - On Debian-based systems, install via ``apt-get install pycorrfit``. - If you have Python 3.6 installed, you may install PyCorrFit via ``pip install pycorrfit[GUI]``. After the installation, type ``pycorrfit`` in a command shell to start PyCorrFit. Documentation ------------- The documentation is in the process of being transferred entirely to readthedocs.org. Currently, it is scattered across several places and it is most-likely outdated: - Original LaTeX-based PDF file (outdated): https://github.com/FCS-analysis/PyCorrFit/wiki/PyCorrFit_doc.pdf - Wiki pages: https://github.com/FCS-analysis/PyCorrFit/wiki/ - A tutorial: https://github.com/FCS-analysis/PyCorrFit/wiki/Tutorial - How to write model functions: https://github.com/FCS-analysis/PyCorrFit/wiki/Writing-model-functions pycorrfit-1.1.7/docs/README.md0000664000372000037200000000074413554642611016626 0ustar travistravis00000000000000PyCorrFit documentation ======================= This is the new sphinx-based documentation of PyCorrFit which will replace the LaTeX-based documentation eventually. To install the requirements for building the documentation, run pip install -r requirements.txt To compile the documentation, run sphinx-build . _build Notes ===== To view the sphinx inventory of PyCorrFit, run python -m sphinx.ext.intersphinx 'http://pycorrfit.readthedocs.io/en/latest/objects.inv' pycorrfit-1.1.7/docs/conf.py0000664000372000037200000001440313554642611016643 0ustar travistravis00000000000000#!/usr/bin/env python3 # -*- coding: utf-8 -*- # # PyCorrFit documentation build configuration file, created by # sphinx-quickstart on Tue Sep 26 17:55:31 2017. # # This file is execfile()d with the current directory set to its # containing dir. # # Note that not all possible configuration values are present in this # autogenerated file. # # All configuration values have a default; values that are commented out # serve to show the default. # If extensions (or modules to document with autodoc) are in another directory, # add these directories to sys.path here. If the directory is relative to the # documentation root, use os.path.abspath to make it absolute, like shown here. # # import os # import sys # sys.path.insert(0, os.path.abspath('.')) # Get version number from PyCorrFit._version file import mock import os import os.path as op import sys # include parent directory pdir = op.dirname(op.dirname(op.abspath(__file__))) sys.path.insert(0, pdir) # include extenstions sys.path.append(op.abspath('extensions')) # Mock all dependencies install_requires = ["lmfit", "numpy", "matplotlib", "pyyaml", "scipy", "sympy", "simplejson", "wxPython"] for mod_name in install_requires: sys.modules[mod_name] = mock.Mock() # http://www.sphinx-doc.org/en/stable/ext/autodoc.html#confval-autodoc_member_order # Order class attributes and functions in separate blocks autodoc_member_order = 'bysource' autodoc_mock_imports = install_requires # Display link to GitHub repo instead of doc on rtfd rst_prolog = """ :github_url: https://github.com/FCS-analysis/PyCorrFit """ # -- General configuration ------------------------------------------------ # If your documentation needs a minimal Sphinx version, state it here. # # needs_sphinx = '1.0' # Add any Sphinx extension module names here, as strings. They can be # extensions coming with Sphinx (named 'sphinx.ext.*') or your custom # ones. extensions = ['sphinx.ext.autodoc', 'sphinx.ext.autosummary', 'sphinx.ext.intersphinx', 'sphinx.ext.mathjax', 'sphinx.ext.viewcode', 'sphinx.ext.napoleon', 'github_changelog', 'simple_gallery', ] # Add any paths that contain templates here, relative to this directory. templates_path = ['_templates'] # The suffix(es) of source filenames. # You can specify multiple suffix as a list of string: # # source_suffix = ['.rst', '.md'] source_suffix = '.rst' # The master toctree document. master_doc = 'index' # General information about the project. project = 'PyCorrFit' github_project = 'FCS-analysis/' + project copyright = '2014, Paul Müller' author = 'Paul Müller' # The version info for the project you're documenting, acts as replacement for # |version| and |release|, also used in various other places throughout the # built documents. # # The short X.Y version. # # The full version, including alpha/beta/rc tags. # This gets 'version' exec(open(op.join(pdir, "pycorrfit/_version.py")).read()) release = version #@UndefinedVariable # The language for content autogenerated by Sphinx. Refer to documentation # for a list of supported languages. # # This is also used if you do content translation via gettext catalogs. # Usually you set "language" from the command line for these cases. language = None # List of patterns, relative to source directory, that match files and # directories to ignore when looking for source files. # This patterns also effect to html_static_path and html_extra_path exclude_patterns = ['_build', 'Thumbs.db', '.DS_Store'] # The name of the Pygments (syntax highlighting) style to use. pygments_style = 'sphinx' # If true, `todo` and `todoList` produce output, else they produce nothing. todo_include_todos = False # -- Options for HTML output ---------------------------------------------- # The theme to use for HTML and HTML Help pages. See the documentation for # a list of builtin themes. # html_theme = 'sphinx_rtd_theme' # Theme options are theme-specific and customize the look and feel of a theme # further. For a list of options available for each theme, see the # documentation. # # html_theme_options = {} # Add any paths that contain custom static files (such as style sheets) here, # relative to this directory. They are copied after the builtin static files, # so a file named "default.css" will overwrite the builtin "default.css". #html_static_path = ['_static'] # -- Options for HTMLHelp output ------------------------------------------ # Output file base name for HTML help builder. htmlhelp_basename = 'PyCorrFitdoc' # -- Options for LaTeX output --------------------------------------------- latex_elements = { # The paper size ('letterpaper' or 'a4paper'). # # 'papersize': 'letterpaper', # The font size ('10pt', '11pt' or '12pt'). # # 'pointsize': '10pt', # Additional stuff for the LaTeX preamble. # # 'preamble': '', # Latex figure (float) alignment # # 'figure_align': 'htbp', } # Grouping the document tree into LaTeX files. List of tuples # (source start file, target name, title, # author, documentclass [howto, manual, or own class]). latex_documents = [ (master_doc, 'PyCorrFit.tex', 'PyCorrFit Documentation', 'Paul Müller', 'manual'), ] # -- Options for manual page output --------------------------------------- # One entry per manual page. List of tuples # (source start file, name, description, authors, manual section). man_pages = [ (master_doc, 'PyCorrFit', 'PyCorrFit Documentation', [author], 1) ] # -- Options for Texinfo output ------------------------------------------- # Grouping the document tree into Texinfo files. List of tuples # (source start file, target name, title, author, # dir menu entry, description, category) texinfo_documents = [ (master_doc, project, project + ' Documentation', author, project, 'Curve fitting in FCS.', 'Scientific'), ] # Example configuration for intersphinx: refer to the Python standard library. intersphinx_mapping = {"python": ('https://docs.python.org/', None), "numpy": ('http://docs.scipy.org/doc/numpy', None), "scipy": ('https://docs.scipy.org/doc/scipy/reference/', None), "lmfit": ('http://lmfit.github.io/lmfit-py/', None), } pycorrfit-1.1.7/docs/extensions/0000775000372000037200000000000013554643106017541 5ustar travistravis00000000000000pycorrfit-1.1.7/docs/extensions/simple_gallery.py0000664000372000037200000000321713554642611023126 0ustar travistravis00000000000000"""Show all images in the "gallery" folder Usage: .. simple_gallery:: :dir: gallery where "gallery" is relative to the folder containing conf.py. Changelog: 0.1 (2019-10-24) - initial release """ import pathlib from docutils.statemachine import ViewList from docutils.parsers.rst import Directive, directives from sphinx.util.nodes import nested_parse_with_titles from docutils import nodes class SimpleGalleryDirective(Directive): required_arguments = 0 optional_arguments = 1 final_argument_whitespace = True option_spec = { 'dir': directives.unchanged, } def get_files(self): """This will return a list of files""" root = pathlib.Path(__file__).parent.parent gpath = root / self.options["dir"] files = [] for ff in gpath.glob("*"): if ff.suffix in [".png", "*.jpg"]: files.append(ff.relative_to(root)) return files def run(self): rst = [] files = self.get_files() for ff in files: rst.append(".. image:: {}".format(ff)) rst.append(" :target: _images/{}".format(ff.name)) rst.append(" :scale: 25%") rst.append(" :align: left") rst.append("") vl = ViewList(rst, "fakefile.rst") # Create a node. node = nodes.section() node.document = self.state.document # Parse the rst. nested_parse_with_titles(self.state, vl, node) return node.children def setup(app): app.add_directive('simple_gallery', SimpleGalleryDirective) return {'version': '0.1'} # identifies the version of our extension pycorrfit-1.1.7/docs/extensions/github_changelog.py0000664000372000037200000000450713554642611023412 0ustar travistravis00000000000000"""Display changelog with links to GitHub issues Usage ----- The directive .. include_changelog:: ../CHANGELOG adds the content of the changelog file into the current document. References to GitHub issues are identified as "(#XY)" (with parentheses and hash) and a link is inserted https://github.com/RI-imaging/{PROJECT}/issues/{XY} where PROJECT ist the `project` variable defined in conf.py. """ import io import re from docutils.statemachine import ViewList from docutils.parsers.rst import Directive from sphinx.util.nodes import nested_parse_with_titles from docutils import nodes class IncludeDirective(Directive): required_arguments = 1 optional_arguments = 0 def run(self): full_path = self.arguments[0] project = self.state.document.settings.env.config.github_project def insert_github_link(reobj): line = reobj.string instr = line[reobj.start():reobj.end()] issue = instr.strip("#()") link = "https://github.com/{}/issues/".format(project) rstlink = "(`#{issue} <{link}{issue}>`_)".format(issue=issue, link=link) return rstlink with io.open(full_path, "r") as myfile: text = myfile.readlines() rst = [] for line in text: line = line.strip("\n") if line.startswith(" ") and line.strip().startswith("-"): # list in list: rst.append("") if not line.startswith(" "): rst.append("") line = "version " + line rst.append(line) rst.append("-"*len(line)) elif not line.strip(): rst.append(line) else: line = re.sub(r"\(#[0-9]*\)", insert_github_link, line) rst.append(line) vl = ViewList(rst, "fakefile.rst") # Create a node. node = nodes.section() node.document = self.state.document # Parse the rst. nested_parse_with_titles(self.state, vl, node) return node.children def setup(app): app.add_config_value('github_project', "user/project", 'html') app.add_directive('include_changelog', IncludeDirective) return {'version': '0.1'} # identifies the version of our extension pycorrfit-1.1.7/docs/sec_gallery.rst0000664000372000037200000000007613554642611020370 0ustar travistravis00000000000000======= Gallery ======= .. simple_gallery:: :dir: gallery pycorrfit-1.1.7/docs/index.rst0000664000372000037200000000072313554642611017205 0ustar travistravis00000000000000.. _index: .. image:: _static/PyCorrFit_logo_dark.png | Welcome to the documentation of PyCorrFit (version |release|), a data analysis software for fluorescence correlation spectroscopy (FCS). Documentation ============= .. toctree:: :maxdepth: 2 sec_about sec_gallery sec_getting_started sec_contribute .. toctree:: :maxdepth: 1 sec_changelog Indices and tables ================== * :ref:`genindex` * :ref:`modindex` * :ref:`search` pycorrfit-1.1.7/docs/_static/0000775000372000037200000000000013554643106016770 5ustar travistravis00000000000000pycorrfit-1.1.7/docs/_static/PyCorrFit_logo_dark.png0000664000372000037200000001047413554642611023406 0ustar travistravis00000000000000PNG  IHDR<r>sBIT|d pHYstEXtSoftwarewww.inkscape.org<IDATxyTzwٔMfiE@a28рK4Dq\A#q' 1J9x4$qd5.aId# Bw]ukW]@9}h}w+QUQZtq|nyL Q#.|ͩ:xN˴1x+nM8NiWJp 0Xey8>94" UձILlc{/Ң4"@D:~ |[L?K&y$;EYe ?Cn׀9TJ`lq |t֥AZyV`-^&q׈fwI#ȭLc*ZKU2۾UO5Q7-K"a4c-.˒]h6swͣ+s^dp|_Kd㛸FDð)f63R2:,mYb!0Ļ /w@҅k>d K1(g"B-頪mk&h39W!` X椛L*s?*YލkX1H:c[Xļ҈HGLBPB(Աmc)ihR=.'fc3c)%XB+D42B4`yAUS#"C0Y1Z5,u.N mbeY\2d;ų=돥sҜMg."rw,jJUHcU|)TљsβfRCgDz5n|E0M UՄj+8۠ s,6)"jA:U8Lqdy5","?Kr<&Ui,f$ nrX%pUJU \D*ˁכM,Kk2PTb+A<;0{Hbf&~)!V9=`Q0F`k5Ͱ 36QEs͍[!)vx C'Q@tJBa'jEt$:!cZle-}ZW7rh'!UMb6oHl[O!;KqqqRM["t!U= <ƴp>%kpKi:ґ@\0b+9jFg.=,f7W2S8|AgHY1s 2MlaLJ"̫YqvsE`c*+"ضf <˯("wOuVɘq0V.33F4s 1|АZw0foPxB5mc=B6r.A=4u tzpDd40 G3)" L%kTDaX͘#u8f{@UCDPѨ#0Cƃ"E[чzкZjpOKC|‘[`!SN`:;Ե|%ϯT㜏9c7aN1"|LJ#?|ЭǤLJ3|ta?~?q_8drIt&|K6/G8dPJ5Y%XhPo:uK }R:?jΈYPyWE4z;}7 Yh@aua>la-qqڭ3~HmaoXV*ɴ)W^ U| J=4.9G#˕y!s )oPpX4cgdpwNvظ>r5PwT*K|mR_(WK=91Ei&AaL=;8J% ]Vujশ#DTgk6|Q a xڊ!:aL88Zd݇ERi^zqyɠ/~ gsE4# `Y`pB/L'6مp"9ɰ@E2RyUƣ}QiLPџԩLL.?PՔGx؄U%dJW8\#T7UzִNGM7mGٙF!.B(n,<33q!R塺zjAVsC>\ pQU䗒!nBGr$H (ee/Y Ԩ?tɭXʋ^sf0 e&'bDvMu/|J04ðГb<<AG!SZa:K%Ӏjnb ~B:=ߙؖx'+2Uy$Ko #N8`4V(ߒ慭x -"#?XH{[{?_Uו@D`N,3*4ؚ>XceIHJ$%H`6N8[Y*﫪[j<<’292H&m , +պJda}=sG[r92Ѻ%/3`1aXpeyj[/b*m4&Sq*caE0V ~KG*w>tIENDB`pycorrfit-1.1.7/docs/sec_changelog.rst0000664000372000037200000000016313554642611020655 0ustar travistravis00000000000000========= Changelog ========= List of changes in-between PyCorrFit releases. .. include_changelog:: ../CHANGELOG pycorrfit-1.1.7/docs/sec_contribute.rst0000664000372000037200000001114013554642611021101 0ustar travistravis00000000000000========== Contribute ========== General remarks =============== PyCorrFit has no funding and a vanishingly small developer community. My personal objective is to keep PyCorrFit operational on Linux and Windows which is currently limited by the free time I have available. An active community is very important for an open source project such as PyCorrFit. You can help this community grow (and thus help improve PyCorrFit) in numerous ways: 1. Tell your colleagues and peers about PyCorrFit. One of them might be able to contribute to the project. 2. If you need a new feature in PyCorrFit, publicly announce a bounty for its implementation. 3. If your research heavily relies on FCS, please consider diverting some of your resources to the development of PyCorrFit. 4. You don't have to be a Python programmer to contribute. If you are familiar with reStrucuredText or LaTeX, you might be able to help out with the online documentation. 5. Please cite: Müller et al. Bioinformatics 30(17): 2532–2533, 2014, `DOI:10.1093/bioinformatics/btu328 `_ 6. Sponsor me on `GitHub `_ or donate via `Liberapay `_. If you are planning to contribute to PyCorrFit, please contact me via the PyCorrFit issue page on GitHub such that we may coordinate a pull request. For documentation writers ========================= To build this documentation, fork PyCorrFit, navigate to the `docs` (not `doc`) directory and run. - ``pip install -r requirements.txt`` followed by - ``sphinx-build . _build``. This will create the html documentation on your computer. Syntax warnings and errors will be displayed during the build (there should be none). After making your changes to your forked branch, create a pull request on GitHub. If you only found a typo or wish to make text-only changes, you can also use the GitHub interface to edit the files (without testing the build step on your computer). For developers ============== Running from source ------------------- It is recommended to work with `virtual environments `_. Windows ~~~~~~~ The easiest way to run PyCorrFit from source on Windows is `Anaconda `_. - ``conda install matplotlib numpy pip scipy wxpython`` - ``pip install cython wheel simplejson sympy lmfit`` - ``pip install -e . # in the root directory of the repository`` Ubuntu 17.10 ~~~~~~~~~~~~ PyCorrFit requires wxPython >= 4.0.1 which is not available as a binary wheel on PyPI and thus must be built from the .tar.gz. Install all dependencies (https://github.com/wxWidgets/Phoenix/blob/master/README.rst): - ``pip install cython matplotlib lmfit numpy scipy sympy`` - ``sudo apt-get install -qq libgtk2.0 libgtk2.0-dev libwebkitgtk-dev dpkg-dev build-essential python3.6-dev libjpeg-dev libtiff-dev libsdl1.2-dev libnotify-dev freeglut3 freeglut3-dev libsm-dev libgtk-3-dev libwebkit2gtk-4.0-dev libxtst-dev libgstreamer-plugins-base1.0-dev`` - ``pip install wxPython # this will take some time`` - ``pip install -e . # in the root directory of the repository`` Testing ------- PyCorrFit is tested using pytest. If you have the time, please write test methods for your code and put them in the ``tests`` directory. You may run all tests by issuing: :: python setup.py test Pull request guidelines ----------------------- Please fork PyCorrFit and create a pull request (PR) introducing your changes. - A new PR should always be made into the `develop` branch. - If a PR introduces a new functionality or fixes a bug, it should provide a test case, i.e. a new file or function in the `tests` directory (see `here `_ for examples). Note that currently there is no recipe for testing the graphical user interface code. - New code should follow the `style guide for Python `_. Please use `flake8 `_ to check the files you changed or created. - New code should be documented well. - Make sure to update the `changelog `_. Windows test binaries --------------------- After each commit to the PyCorrFit repository, a binary installer is created by `Appveyor `_. Click on a build and navigate to ``ARTIFACTS`` (upper right corner right under the running time of the build). From there you can download the Windows installer for the commit. pycorrfit-1.1.7/pycorrfit/0000775000372000037200000000000013554643106016433 5ustar travistravis00000000000000pycorrfit-1.1.7/pycorrfit/_version.py0000664000372000037200000001146313554642611020636 0ustar travistravis00000000000000#!/usr/bin/env python """Determine package version for git repositories from tags Each time this file is imported it checks if the ".git" folder is present and if so, obtains the version from the git history using `git describe`. This information is then stored in the file `_version_save.py` which is not versioned by git, but distributed along e.g. on PyPI. """ from __future__ import print_function # Put the entire script into a `True` statement and add the hint # `pragma: no cover` to ignore code coverage here. if True: # pragma: no cover import imp import os from os.path import abspath, basename, dirname, join import subprocess import sys import time import traceback import warnings def git_describe(): """ Return a string describing the version returned by the command `git describe --tags HEAD`. If it is not possible to determine the correct version, then an empty string is returned. """ # Make sure we are in a directory that belongs to the correct # repository. ourdir = dirname(abspath(__file__)) def _minimal_ext_cmd(cmd): # construct minimal environment env = {} for k in ['SYSTEMROOT', 'PATH']: v = os.environ.get(k) if v is not None: env[k] = v # LANGUAGE is used on win32 env['LANGUAGE'] = 'C' env['LANG'] = 'C' env['LC_ALL'] = 'C' pop = subprocess.Popen(cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE, env=env) out = pop.communicate()[0] return out # change directory olddir = abspath(os.curdir) os.chdir(ourdir) try: out = _minimal_ext_cmd(['git', 'describe', '--tags', 'HEAD']) git_revision = out.strip().decode('ascii') except OSError: git_revision = "" # go back to original directory os.chdir(olddir) return git_revision def load_version(versionfile): """load version from version_save.py""" longversion = "" try: _version_save = imp.load_source("_version_save", versionfile) longversion = _version_save.longversion except BaseException: try: from ._version_save import longversion except BaseException: try: from _version_save import longversion except BaseException: pass return longversion def save_version(version, versionfile): """save version to version_save.py""" data = "#!/usr/bin/env python\n" \ + "# This file was created automatically\n" \ + "longversion = '{VERSION}'\n" try: with open(versionfile, "w") as fd: fd.write(data.format(VERSION=version)) except BaseException: msg = "Could not write package version to {}.".format(versionfile) warnings.warn(msg) hdir = dirname(abspath(__file__)) if basename(__file__) == "conf.py" and "name" in locals(): # This script is executed in conf.py from the docs directory versionfile = join(join(join(hdir, ".."), name), # noqa: F821 "_version_save.py") else: # This script is imported as a module versionfile = join(hdir, "_version_save.py") # Determine the accurate version longversion = "" # 1. git describe try: # Get the version using `git describe` longversion = git_describe() except BaseException: pass # 2. previously created version file if longversion == "": # Either this is this is not a git repository or we are in the # wrong git repository. # Get the version from the previously generated `_version_save.py` longversion = load_version(versionfile) # 3. last resort: date if longversion == "": print("Could not determine version. Reason:") print(traceback.format_exc()) ctime = os.stat(__file__)[8] longversion = time.strftime("%Y.%m.%d-%H-%M-%S", time.gmtime(ctime)) print("Using creation time as version: {}".format(longversion)) if not hasattr(sys, 'frozen'): # Save the version to `_version_save.py` to allow distribution using # `python setup.py sdist`. # This is only done if the program is not frozen (with e.g. # pyinstaller), if longversion != load_version(versionfile): save_version(longversion, versionfile) # PEP 440-conform development version: version = ".post".join(longversion.split("-")[:2]) pycorrfit-1.1.7/pycorrfit/correlation.py0000664000372000037200000004520413554642611021333 0ustar travistravis00000000000000"""PyCorrFit data set: Classes for FCS data evaluation.""" import hashlib import warnings import numpy as np from . import models as mdls from . import fit from .trace import Trace class Correlation(object): """ unifies correlation curve handling """ def __init__(self, backgrounds=[], correlation=None, corr_type="AC", filename=None, fit_algorithm="Lev-Mar", fit_model=6000, fit_ival=(0, 0), fit_weight_data=None, fit_weight_type="none", normparm=None, title=None, traces=[], verbose=1): """ Parameters ---------- backgrounds: list of instances of Trace background traces correlation: ndarray of shape (N,2) correlation data (time [s], correlation) corr_type: str type of correlation, e.g. "AC", "AC1", "cc12" filename: str path to filename of correlation fit_algorithm: str valid fit algorithm identifier (Algorithms.keys()) fit_ival: fitting interval of lag times in indices fit_model: instance of FitModel the model used for fitting fit_weight_data: any data for the certain fit_weight_type fit_weight_type: str Reserved keywords or user-defined strings: - "none" : no weights are used - "splineX" : compute weights from spline with X knots and a spread of `fit_weight_data` bins. - "model function" : compute weights from difference to model function - user-defined : other weights (e.g. previously computed averages given in fit_weight_data) normparm: int identifier of normalization parameter title: str user-editable title of this correlation traces: list of instances of Trace traces of the current correlation verbose : int increment to increase verbosity """ # must be created before setting properties self._backgrounds = [] self._correlation = None self._fit_algorithm = None self._fit_model = None self._fit_parameters = None self._fit_parameters_range = None self._fit_parameters_variable = None self._fit_weight_memory = dict() self._lag_time = None self._model_memory = dict() self._traces = [] self._uid = None self.verbose = verbose self.backgrounds = backgrounds self.bg_correction_enabled = True self.correlation = correlation self.corr_type = corr_type self.filename = filename self.fit_algorithm = fit_algorithm self.fit_ival = fit_ival self.fit_model = fit_model # Do not change order: self.fit_weight_type = fit_weight_type self.fit_weight_parameters = fit_weight_data self.normparm = normparm self.title = title self.traces = traces def __repr__(self): if self.is_ac: c = "AC" else: c = "CC" text = "{} correlation '{}' with {} traces".format( c, self.title, len(self._traces)) return text def background_replace(self, channel, background): """ Replace a background. Channel must be 0 or 1. background must be instance of `Trace` """ if channel not in [0, 1]: raise ValueError("`channel` must be '0' or '1'!") if not isinstance(background, Trace): raise ValueError("`background` must be instance of `Trace`") if self.is_ac: if channel == 1: raise ValueError("Cannot set second background for AC.") self._backgrounds = [background] else: if len(self._backgrounds) == 0: self._backgrounds = [Trace(countrate=0, duration=0), Trace(countrate=0, duration=0)] elif len(self._backgrounds) == 1: self._backgrounds.append(Trace(countrate=0, duration=0)) self._backgrounds[channel] = background @property def backgrounds(self): """ The background trace(s) of this correlation in a list. """ return self._backgrounds @backgrounds.setter def backgrounds(self, value): """ Set the backgrounds. The value can either be a list of traces or instances of traces or a single trace in an array. """ backgrounds = [] if not isinstance(value, list): value = [value] if len(value) > 2: raise ValueError("Backgrounds length must not exceed 2.") for v in value: if isinstance(v, np.ndarray): backgrounds.append(Trace(trace=v)) elif isinstance(v, Trace): backgrounds.append(v) else: raise ValueError( "Each background must be instance of Trace or ndarray") self._backgrounds = backgrounds @property def bg_correction_factor(self): """ Returns background correction factor for self._correlation Notes ----- Thompson, N. Lakowicz, J.; Geddes, C. D. & Lakowicz, J. R. (ed.) Fluorescence Correlation Spectroscopy Topics in Fluorescence Spectroscopy, Springer US, 2002, 1, 337-378 """ if not self.bg_correction_enabled: # bg correction disabled return 1 if self.is_ac: # Autocorrelation if len(self.traces) == 1 and len(self.backgrounds) == 1: S = self.traces[0].countrate B = self.backgrounds[0].countrate bgfactor = (S/(S-B))**2 else: if self.verbose >= 1: warnings.warn("Correlation {}: no bg-correction". format(self.uid)) bgfactor = 1 else: # Crosscorrelation if len(self.traces) == 2 and len(self.backgrounds) == 2: S = self.traces[0].countrate S2 = self.traces[1].countrate B = self.backgrounds[0].countrate B2 = self.backgrounds[1].countrate bgfactor = (S/(S-B)) * (S2/(S2-B2)) else: warnings.warn("Correlation {}: no bg-correction". format(self)) bgfactor = 1 return bgfactor def check_parms(self, parms): """ Check parameters using self.fit_model.func_verification and the user defined boundaries self.fit_parameters_range for each parameter. """ p = 1. * np.array(parms) r = self.fit_parameters_range for i in range(len(p)): if r[i][0] == r[i][1]: pass elif r[i][0] is None: if p[i] > r[i][1]: p[i] = r[i][1] elif r[i][1] is None: if p[i] < r[i][0]: p[i] = r[i][1] elif p[i] < r[i][0]: p[i] = r[i][0] elif p[i] > r[i][1]: p[i] = r[i][1] return p @property def correlation(self): """the correlation data, shape (N,2) with (time, correlation) """ if self._correlation is not None: corr = self._correlation.copy() return corr @correlation.setter def correlation(self, value): if value is None: warnings.warn("Setting correlation to `None`.") elif not isinstance(value, np.ndarray): raise ValueError("Correlation must be 2d array!") elif not value.shape[1] == 2: raise ValueError("Correlation array must have shape (N,2)!") self._correlation = value @property def correlation_fit(self): """ returns correlation data for fitting (fit_ivald) - background correction - fitting interval cropping """ corr = self.correlation if corr is not None: # perform background correction corr[:, 1] *= self.bg_correction_factor # perform parameter normalization return corr[self.fit_ival[0]:self.fit_ival[1], :] @property def correlation_plot(self): """ returns correlation data for plotting (normalized, fit_ivald) - background correction - fitting interval cropping - parameter normalization """ corr = self.correlation_fit if corr is not None: # perform parameter normalization corr[:, 1] *= self.normalize_factor return corr @property def is_ac(self): """True if instance contains autocorrelation""" return self.corr_type.lower().count("ac") > 0 @property def is_cc(self): """True if instance contains crosscorrelation""" return not self.is_ac @property def is_weighted_fit(self): """True if a weighted fit was performed""" return self.fit_weight_type != "none" @property def fit_algorithm(self): """The string representing the fitting algorithm""" return self._fit_algorithm @fit_algorithm.setter def fit_algorithm(self, value): # TODO: # - allow lower-case fitting algorithm if value not in list(fit.Algorithms.keys()): raise ValueError("Invalid fit algorithm: {}".format(value)) self._fit_algorithm = value @property def fit_ival(self): """lag time interval for fitting""" lag = self.lag_time if lag is not None: if self._fit_ival[1] <= 0 or self._fit_ival[1] > lag.shape[0]: self._fit_ival[1] = lag.shape[0] return self._fit_ival @fit_ival.setter def fit_ival(self, value): value = list(value) if value[1] <= 0: if self.lag_time is not None: value[1] = self.lag_time.shape[0] else: # just to be sure warnings.warn("No data available.") value[1] = 10000000000000000 self._fit_ival = value @property def fit_model(self): """instance of a fit model""" return self._fit_model @fit_model.setter def fit_model(self, value): """set the fit model """ if isinstance(value, int): newmodel = mdls.modeldict[value] elif isinstance(value, mdls.Model): newmodel = value else: raise NotImplementedError("Unknown model identifier") if newmodel != self._fit_model: self._fit_model = newmodel # overwrite fitting parameters self._fit_parameters = self._fit_model.default_values self._fit_parameters_variables = self._fit_model.default_variables self._fit_parameters_range = np.zeros( (len(self._fit_parameters), 2)) self.normparm = None @property def fit_weight_data(self): """data of weighted fitting""" try: data = self._fit_weight_memory[self.fit_weight_type] except KeyError: # Standard variables for weights if self.fit_weight_type.count("spline"): # Default area for weighting with spline fit data = 3 else: data = None return data @fit_weight_data.setter def fit_weight_data(self, value): self._fit_weight_memory[self.fit_weight_type] = value @property def fit_parameters(self): """parameters that were fitted/will be used for fitting""" # Do not return `self._fit_parameters.copy()`, because # some methods of PyCorrFit depend on the array being # accessible and changeable with indices. return self._fit_parameters @fit_parameters.setter def fit_parameters(self, value): # must unlock parameters, if change is required value = np.array(value) self._fit_parameters = self.check_parms(value) @property def fit_parameters_range(self): """valid fitting ranges for fit parameters""" model = self.fit_model.boundaries mine = self._fit_parameters_range new = [] for a, b in zip(model, mine): c = [-np.inf, np.inf] if a[0] != a[1]: c[0] = a[0] c[1] = a[1] # user overrides model if b[0] != b[1]: c[0] = b[0] c[1] = b[1] if c[0] is not None and np.isnan(c[0]): c[0] = -np.inf if c[1] is not None and np.isnan(c[1]): c[1] = np.inf new.append(c) return np.array(new) @fit_parameters_range.setter def fit_parameters_range(self, value): value = np.array(value) expect_shape = (self.fit_parameters.shape[0], 2) if value.shape != expect_shape: msg = "Expected shape of fit parameters: {} (vs {})".format( expect_shape, value.shape ) raise ValueError(msg) self._fit_parameters_range = value @property def fit_parameters_variable(self): """which parameters are variable during fitting""" if self._fit_parameters_variable is None: self._fit_parameters_variable = np.array( self.fit_model.default_variables, dtype=bool) return self._fit_parameters_variable @fit_parameters_variable.setter def fit_parameters_variable(self, value): value = np.array(value, dtype=bool) expect_size = self.fit_parameters.shape[0] if value.shape[0] != expect_size: msg = "Fit parameter variables must have size {}!".format( expect_size) raise ValueError(msg) self._fit_parameters_variable = value @property def lag_time(self): """logarithmic lag time axis""" if self.correlation is not None: return self._correlation[:, 0].copy() elif self._lag_time is not None: return self._lag_time else: # some default lag time return 10**np.linspace(-6, 8, 1001) @lag_time.setter def lag_time(self, value): if self.correlation is not None: warnings.warn( "Setting lag time not possible, because of existing correlation") else: self._lag_time = value @property def lag_time_fit(self): """lag time as used for fitting""" return self.lag_time[self.fit_ival[0]:self.fit_ival[1]] @property def modeled(self): """fitted data values, same shape as self.correlation""" # perform parameter normalization lag = self.lag_time modeled = np.zeros((lag.shape[0], 2)) modeled[:, 0] = lag modeled[:, 1] = self.fit_model(self.fit_parameters, lag) return modeled.copy() @property def modeled_fit(self): """fitted data values, same shape as self.correlation_fit""" toplot = self.modeled[self.fit_ival[0]:self.fit_ival[1], :] return toplot @property def modeled_plot(self): """fitted data values, same shape as self.correlation_fit""" toplot = self.modeled_fit toplot[:, 1] *= self.normalize_factor return toplot @property def normalize_factor(self): """plot normalization according to self.normparm""" if self.normparm is None: # nothing to do return 1 if self.normparm < self.fit_parameters.shape[0]: nfactor = self.fit_parameters[self.normparm] else: # get supplementary parameters alt = self.fit_model.get_supplementary_values(self.fit_parameters) nfactor = alt[self.normparm - self.fit_parameters.shape[0]] return nfactor @property def residuals(self): """fit residuals, same shape as self.correlation""" if self.correlation is None: raise ValueError("Cannot compute residuals; No correlation given!") residuals = self.correlation.copy() residuals[:, 1] -= self.modeled[:, 1] return residuals @property def residuals_fit(self): """fit residuals, same shape as self.correlation_fit""" residuals_fit = self.correlation_fit.copy() residuals_fit[:, 1] -= self.modeled_fit[:, 1] return residuals_fit @property def residuals_plot(self): """fit residuals, same shape as self.correlation_fit""" cp = self.correlation_plot if cp is not None: residuals_plot = self.correlation_plot.copy() residuals_plot[:, 1] -= self.modeled_plot[:, 1] return residuals_plot def set_weights(self, type_name, data): """ Add weights for fitting. example: type_name : "Average" data : 1d ndarray with length self.lag_time """ if data is not None: self._fit_weight_memory[type_name] = data @property def traces(self): """ The trace(s) of this correlation in a list. """ return self._traces @traces.setter def traces(self, value): """ Set the traces. The value can either be a list of traces or instances of traces or a single trace in an array. """ traces = [] if not isinstance(value, list): value = [value] if len(value) > 2: raise ValueError("Traces length must not exceed 2.") for v in value: if isinstance(v, np.ndarray): traces.append(Trace(trace=v)) elif isinstance(v, Trace): traces.append(v) else: raise ValueError( "Each trace must be instance of Trace or ndarray") self._traces = traces if len(self._traces) == 2: if self._traces[0].duration != self._traces[1].duration: warnings.warn("Unequal lenght of traces: {} and {}".format( self._traces[0].duration, self._traces[1].duration)) @property def uid(self): """ unique identifier of this instance This might change when title or filename are updated. """ if self._uid is None: hasher = hashlib.sha256() hasher.update(str(np.random.random()).encode()) hasher.update(str(self._correlation).encode()) hasher.update(str(self.filename).encode()) hasher.update(str(self.title).encode()) self._uid = hasher.hexdigest() return self._uid pycorrfit-1.1.7/pycorrfit/openfile.py0000664000372000037200000007650013554642611020616 0ustar travistravis00000000000000"""PyCorrFit - Module openfile This file contains definitions for opening PyCorrFit sessions and saving PyCorrFit correlation curves. """ import codecs import csv import io import os import shutil import tempfile import warnings import zipfile import numpy as np import yaml # These imports are required for loading data from .trace import Trace from ._version import version as __version__ def LoadSessionData(sessionfile, parameters_only=False): """Load PyCorrFit session data from a zip file (.pcfs) Parameters ---------- sessionfile : str File from which data will be loaded parameters_only : bool Only load the parameters from the YAML file Returns ------- Infodict : dict Infodict may contain the following keys: "Backgrounds", list: contains the backgrounds "Comments", dict: "Session" comment and int keys to Page titles "Correlations", dict: page numbers, all correlation curves "External Functions", dict: modelids to external model functions "External Weights", dict: page numbers, external weights for fitting "Parameters", dict: page numbers, all parameters of the pages "Preferences", dict: not used yet "Traces", dict: page numbers, all traces of the pages "Version", str: the PyCorrFit version of the session """ Infodict = {} # Get the version Arc = zipfile.ZipFile(sessionfile, mode='r') readmefile = Arc.open("Readme.txt") # e.g. "This file was created using PyCorrFit version 0.7.6" Infodict["Version"] = readmefile.readline()[46:].strip() readmefile.close() # Get the yaml parms dump: yamlfile = Arc.open("Parameters.yaml") # Parameters: Fitting and drawing parameters of correlation curve # The *yamlfile* is responsible for the order of the Pages #i. Infodict["Parameters"] = yaml.safe_load(yamlfile) yamlfile.close() if parameters_only: Arc.close() return Infodict # Supplementary data (errors of fit) supname = "Supplements.yaml" try: Arc.getinfo(supname) except: pass else: supfile = Arc.open(supname) supdata = yaml.safe_load(supfile) Infodict["Supplements"] = dict() for idp in supdata: Infodict["Supplements"][idp[0]] = dict() Infodict["Supplements"][idp[0]]["FitErr"] = idp[1] if len(idp) > 2: # As of version 0.7.4 we save chi2 and shared pages -global fit Infodict["Supplements"][idp[0]]["Chi sq"] = idp[2] Infodict["Supplements"][idp[0]]["Global Share"] = idp[3] # Preferences: Reserved for a future version of PyCorrFit :) prefname = "Preferences.yaml" try: Arc.getinfo(prefname) except KeyError: pass else: yamlpref = Arc.open(prefname) Infodict["Preferences"] = yaml.safe_load(yamlpref) yamlpref.close() # Get external functions Infodict["External Functions"] = dict() key = 7001 while key <= 7999: # (There should not be more than 1000 functions) funcfilename = "model_"+str(key)+".txt" try: Arc.getinfo(funcfilename) except KeyError: # No more functions to import key = 8000 else: funcfile = Arc.open(funcfilename) Infodict["External Functions"][key] = funcfile.read() funcfile.close() key = key+1 # Get the correlation arrays Infodict["Correlations"] = dict() for i in np.arange(len(Infodict["Parameters"])): # The *number* is used to identify the correct file number = str(Infodict["Parameters"][i][0] ).strip().strip(":").strip("#") pageid = int(number) expfilename = "data"+number+".csv" expfile = Arc.open(expfilename, 'r') readdata = csv.reader(io.StringIO( expfile.read().decode()), delimiter=',') dataexp = list() tau = list() if str(readdata.__next__()[0]) == "# tau only": for row in readdata: # Exclude commentaries if (str(row[0])[0:1] != '#'): tau.append(float(row[0])) tau = np.array(tau) dataexp = None else: for row in readdata: # Exclude commentaries if (str(row[0])[0:1] != '#'): dataexp.append((float(row[0]), float(row[1]))) dataexp = np.array(dataexp) tau = dataexp[:, 0] Infodict["Correlations"][pageid] = [tau, dataexp] del readdata expfile.close() # Get the Traces Infodict["Traces"] = dict() for i in np.arange(len(Infodict["Parameters"])): # The *number* is used to identify the correct file number = str(Infodict["Parameters"][i][0] ).strip().strip(":").strip("#") pageid = int(number) # Find out, if we have a cross correlation data type IsCross = False try: IsCross = Infodict["Parameters"][i][7] except IndexError: # No Cross correlation pass if IsCross is False: tracefilenames = ["trace"+number+".csv"] else: # Cross correlation uses two traces tracefilenames = ["trace"+number+"A.csv", "trace"+number+"B.csv"] thistrace = list() for tracefilename in tracefilenames: try: Arc.getinfo(tracefilename) except KeyError: pass else: tracefile = Arc.open(tracefilename, 'r') traceread = csv.reader(io.StringIO( tracefile.read().decode()), delimiter=',') singletrace = list() for row in traceread: # Exclude commentaries if (str(row[0])[0:1] != '#'): singletrace.append((float(row[0]), float(row[1]))) singletrace = np.array(singletrace) thistrace.append(singletrace) del traceread del singletrace tracefile.close() if len(thistrace) != 0: Infodict["Traces"][pageid] = thistrace else: Infodict["Traces"][pageid] = None # Get the comments, if they exist commentfilename = "comments.txt" try: # Raises KeyError, if file is not present: Arc.getinfo(commentfilename) except KeyError: pass else: # Open the file commentfile = Arc.open(commentfilename, 'r') Infodict["Comments"] = dict() for i in np.arange(len(Infodict["Parameters"])): number = str(Infodict["Parameters"][i][0] ).strip().strip(":").strip("#") pageid = int(number) # Strip line ending characters for all the Pages. Infodict["Comments"][pageid] = commentfile.readline().strip() # Now Add the Session Comment (the rest of the file). ComList = commentfile.readlines() Infodict["Comments"]["Session"] = '' for line in ComList: Infodict["Comments"]["Session"] += line.decode() commentfile.close() # Get the Backgroundtraces and data if they exist bgfilename = "backgrounds.csv" try: # Raises KeyError, if file is not present: Arc.getinfo(bgfilename) except KeyError: pass else: # Open the file Infodict["Backgrounds"] = list() bgfile = Arc.open(bgfilename, 'r') bgread = csv.reader(io.StringIO( bgfile.read().decode()), delimiter='\t') i = 0 for bgrow in bgread: bgtracefilename = "bg_trace"+str(i)+".csv" bgtracefile = Arc.open(bgtracefilename, 'r') bgtraceread = csv.reader(io.StringIO( bgtracefile.read().decode()), delimiter=',') bgtrace = list() for row in bgtraceread: # Exclude commentaries if (str(row[0])[0:1] != '#'): bgtrace.append((np.float(row[0]), np.float(row[1]))) bgtrace = np.array(bgtrace) newbackground = Trace(trace=bgtrace, name=str( bgrow[1]), countrate=np.float(bgrow[0])) Infodict["Backgrounds"].append(newbackground) i = i + 1 bgfile.close() # Get external weights if they exist WeightsFilename = "externalweights.txt" try: # Raises KeyError, if file is not present: Arc.getinfo(WeightsFilename) except: pass else: Wfile = Arc.open(WeightsFilename, 'r') Wread = csv.reader(io.StringIO(Wfile.read().decode()), delimiter='\t') Weightsdict = dict() for wrow in Wread: Pkey = wrow[0] # Page of weights pageid = int(Pkey) # Do not overwrite anything try: Weightsdict[pageid] except: Weightsdict[pageid] = dict() Nkey = wrow[1] # Name of weights Wdatafilename = "externalweights_data"+Pkey+"_"+Nkey+".csv" Wdatafile = Arc.open(Wdatafilename, 'r') Wdatareader = csv.reader(io.StringIO(Wdatafile.read().decode())) Wdata = list() for row in Wdatareader: # Exclude commentaries if (str(row[0])[0:1] != '#'): Wdata.append(np.float(row[0])) Weightsdict[pageid][Nkey] = np.array(Wdata) Infodict["External Weights"] = Weightsdict # Preferences preferencesname = "preferences.cfg" try: # Raises KeyError, if file is not present: Arc.getinfo(preferencesname) except: pass else: prefdict = {} with Arc.open(preferencesname) as fd: data = fd.readlines() for line in data: line = line.decode().strip() if len(line) == 0 or line.startswith("#"): continue key, value = line.split("=") key = key.strip() value = value.strip() if value.count(","): value = [v.strip() for v in value.split(",")] prefdict[key] = value Infodict["Preferences"] = prefdict Arc.close() return Infodict def SaveSessionData(sessionfile, Infodict): """Session PyCorrFit session data to file. Parameters ---------- sessionfile : str The suffix ".pcfs" is automatically appended. Infodict : dict Infodict may contain the following keys: "Backgrounds", list: contains the backgrounds "Comments", dict: "Session" comment and int keys to Page titles "Correlations", dict: page numbers, all correlation curves "External Functions, dict": modelids to external model functions "External Weights", dict: page numbers, external weights for fitting "Parameters", dict: page numbers, all parameters of the pages "Preferences", dict: fixed page parameters "Traces", dict: page numbers, all traces of the pages The version of PyCorrFit is written to Readme.txt """ (dirname, filename) = os.path.split(sessionfile) # Sometimes you have multiple endings... if filename.endswith(".pcfs") is not True: filename += ".pcfs" # Change working directory returnWD = os.getcwd() tempdir = tempfile.mkdtemp() os.chdir(tempdir) # Create zip file Arc = zipfile.ZipFile(filename, mode='w') # Only do the Yaml thing for safe operations. # Make the yaml dump parmsfilename = "Parameters.yaml" # Parameters have to be floats in lists # in order for yaml.safe_load to work. Parms = Infodict["Parameters"] ParmsKeys = list(Parms.keys()) ParmsKeys.sort() Parmlist = list() for idparm in ParmsKeys: # Make sure we do not accidently save arrays. # This would not work correctly with yaml. # Parameters Parms[idparm][2] = np.array(Parms[idparm][2], dtype="float").tolist() # Parameter varied Parms[idparm][3] = np.array(Parms[idparm][3], dtype="bool").tolist() # Channel selection Parms[idparm][4] = np.array(Parms[idparm][4], dtype="int").tolist() # Background selection for ii in range(len(Parms[idparm][6])): if Parms[idparm][6][ii] is not None: Parms[idparm][6][ii] = int(Parms[idparm][6][ii]) # Plot normalization if Parms[idparm][8] is not None: Parms[idparm][8] = int(Parms[idparm][8]) # Fit parameter range Parms[idparm][9] = np.array(Parms[idparm][9], dtype="float").tolist() Parmlist.append(Parms[idparm]) try: # We would like to perform safe_dump, because in the # Windoes x64 version, some integers are exported # like this: `!!python/long '105'` using `yaml.dump`. with open(parmsfilename, "w") as yamlfd: yaml.safe_dump(Parmlist, yamlfd) except yaml.representer.RepresenterError: # `RepresenterError: cannot represent an object: 0` # In this case, we choose to use the normal dump # and pray. # However, this should not happen, because in the above # for-loop we set the correct dtype for each parameter . if os.path.exists(parmsfilename): os.remove(parmsfilename) with open(parmsfilename, "w") as yamlfd: yaml.dump(Parmlist, yamlfd) Arc.write(parmsfilename) os.remove(os.path.join(tempdir, parmsfilename)) # Supplementary data (errors of fit) errsfilename = "Supplements.yaml" Sups = Infodict["Supplements"] SupKeys = list(Sups.keys()) SupKeys.sort() Suplist = list() for idsup in SupKeys: error = Sups[idsup]["FitErr"] chi2 = Sups[idsup]["Chi sq"] globalshare = Sups[idsup]["Global Share"] Suplist.append([idsup, error, chi2, globalshare]) with open(errsfilename, "w") as fd: yaml.safe_dump(Suplist, fd) Arc.write(errsfilename) os.remove(os.path.join(tempdir, errsfilename)) # Save external functions for key in Infodict["External Functions"].keys(): funcfilename = "model_"+str(key)+".txt" funcfile = codecs.open(funcfilename, 'w', encoding="utf-8") funcfile.write(Infodict["External Functions"][key]) funcfile.close() Arc.write(funcfilename) os.remove(os.path.join(tempdir, funcfilename)) # Save (dataexp and tau)s into separate csv files. for pageid in Infodict["Correlations"].keys(): # Since *Array* and *Parms* are in the same order (the page order), # we will identify the filename by the Page title number. number = str(pageid) expfilename = "data"+number+".csv" expfile = open(expfilename, 'w') tau = Infodict["Correlations"][pageid][0] exp = Infodict["Correlations"][pageid][1] dataWriter = csv.writer(expfile, delimiter=',') if exp is not None: # Names of Columns dataWriter.writerow(['# tau', 'experimental data']) # Actual Data # Do not use len(tau) instead of len(exp[:,0])) ! # Otherwise, the experimental data will not be saved entirely, # if it has been cropped. Because tau might be smaller, than # exp[:,0] --> tau = exp[startcrop:endcrop,0] for j in np.arange(len(exp[:, 0])): dataWriter.writerow(["%.20e" % exp[j, 0], "%.20e" % exp[j, 1]]) else: # Only write tau dataWriter.writerow(['# tau'+' only']) for j in np.arange(len(tau)): dataWriter.writerow(["%.20e" % tau[j]]) expfile.close() # Add to archive Arc.write(expfilename) os.remove(os.path.join(tempdir, expfilename)) # Save traces into separate csv files. for pageid in Infodict["Traces"].keys(): number = str(pageid) # Since *Trace* and *Parms* are in the same order, which is the # Page order, we will identify the filename by the Page title # number. if Infodict["Traces"][pageid] is not None and len(Infodict["Traces"][pageid]) != 0: if Parms[pageid][7] is True: # We have cross correlation: save two traces # A tracefilenamea = "trace"+number+"A.csv" tracefile = open(tracefilenamea, 'w') traceWriter = csv.writer(tracefile, delimiter=',') time = Infodict["Traces"][pageid][0][:, 0] rate = Infodict["Traces"][pageid][0][:, 1] # Names of Columns traceWriter.writerow(['# time', 'count rate']) # Actual Data for j in np.arange(len(time)): traceWriter.writerow(["%.20e" % time[j], "%.20e" % rate[j]]) tracefile.close() # Add to archive Arc.write(tracefilenamea) os.remove(os.path.join(tempdir, tracefilenamea)) # B (only if it exists...) try: _ = Infodict["Traces"][pageid][1] except IndexError: pass else: tracefilenameb = "trace"+number+"B.csv" tracefile = open(tracefilenameb, 'w') traceWriter = csv.writer(tracefile, delimiter=',') time = Infodict["Traces"][pageid][1][:, 0] rate = Infodict["Traces"][pageid][1][:, 1] # Names of Columns traceWriter.writerow(['# time', 'count rate']) # Actual Data for j in np.arange(len(time)): traceWriter.writerow(["%.20e" % time[j], "%.20e" % rate[j]]) tracefile.close() # Add to archive Arc.write(tracefilenameb) os.remove(os.path.join(tempdir, tracefilenameb)) else: # Save one single trace tracefilename = "trace"+number+".csv" tracefile = open(tracefilename, 'w') traceWriter = csv.writer(tracefile, delimiter=',') time = Infodict["Traces"][pageid][0][:, 0] rate = Infodict["Traces"][pageid][0][:, 1] # Names of Columns traceWriter.writerow(['# time', 'count rate']) # Actual Data for j in np.arange(len(time)): traceWriter.writerow(["%.20e" % time[j], "%.20e" % rate[j]]) tracefile.close() # Add to archive Arc.write(tracefilename) os.remove(os.path.join(tempdir, tracefilename)) # Save comments into txt file commentfilename = "comments.txt" commentfile = codecs.open(commentfilename, 'w', encoding="utf-8") # Comments[-1] is comment on whole Session Ckeys = list(Infodict["Comments"].keys()) try: Ckeys.remove("Session") except ValueError: pass Ckeys.sort() for key in Ckeys: if key != "Session": commentfile.write(Infodict["Comments"][key]+"\r\n") commentfile.write(Infodict["Comments"]["Session"]) commentfile.close() Arc.write(commentfilename) os.remove(os.path.join(tempdir, commentfilename)) # Save Background information: Background = Infodict["Backgrounds"] if len(Background) > 0: # We do not use a comma separated, but a tab separated file, # because a comma might be in the name of a bg. bgfilename = "backgrounds.csv" bgfile = open(bgfilename, 'w') bgwriter = csv.writer(bgfile, delimiter='\t') for i in np.arange(len(Background)): bgwriter.writerow( [str(Background[i].countrate), Background[i].name]) # Traces bgtracefilename = "bg_trace"+str(i)+".csv" bgtracefile = open(bgtracefilename, 'w') bgtraceWriter = csv.writer(bgtracefile, delimiter=',') bgtraceWriter.writerow(['# time', 'count rate']) # Actual Data time = Background[i][:, 0] rate = Background[i][:, 1] for j in np.arange(len(time)): bgtraceWriter.writerow(["%.20e" % time[j], "%.20e" % rate[j]]) bgtracefile.close() # Add to archive Arc.write(bgtracefilename) os.remove(os.path.join(tempdir, bgtracefilename)) bgfile.close() Arc.write(bgfilename) os.remove(os.path.join(tempdir, bgfilename)) # Save External Weights information WeightedPageID = list(Infodict["External Weights"].keys()) WeightedPageID.sort() WeightFilename = "externalweights.txt" WeightFile = open(WeightFilename, 'w') WeightWriter = csv.writer(WeightFile, delimiter='\t') for pageid in WeightedPageID: number = str(pageid) NestWeights = list(Infodict["External Weights"][pageid].keys()) # The order of the types does not matter, since they are # sorted in the frontend and upon import. We sort them here, anyhow. NestWeights.sort() for Nkey in NestWeights: WeightWriter.writerow([number, str(Nkey).strip()]) # Add data to a File WeightDataFilename = "externalweights_data"+number +\ "_"+str(Nkey).strip()+".csv" WeightDataFile = open(WeightDataFilename, 'w') WeightDataWriter = csv.writer(WeightDataFile) wdata = Infodict["External Weights"][pageid][Nkey] for jw in np.arange(len(wdata)): WeightDataWriter.writerow([str(wdata[jw])]) WeightDataFile.close() Arc.write(WeightDataFilename) os.remove(os.path.join(tempdir, WeightDataFilename)) WeightFile.close() Arc.write(WeightFilename) os.remove(os.path.join(tempdir, WeightFilename)) # Preferences preferencesname = "preferences.cfg" with codecs.open(preferencesname, 'w', encoding="utf-8") as fd: for key in Infodict["Preferences"]: value = Infodict["Preferences"][key] if isinstance(value, list): value = " ".join("{}".format(it) for it in value) else: value = "{}".format(value) fd.write("{} = {}\n".format(key, value)) Arc.write(preferencesname) os.remove(os.path.join(tempdir, preferencesname)) # Readme rmfilename = "Readme.txt" rmfile = codecs.open(rmfilename, 'w', encoding="utf-8") rmfile.write(ReadmeSession) rmfile.close() Arc.write(rmfilename) os.remove(os.path.join(tempdir, rmfilename)) # Close the archive Arc.close() # Move archive to destination directory shutil.move(os.path.join(tempdir, filename), os.path.join(dirname, filename)) # Go to destination directory os.chdir(returnWD) os.rmdir(tempdir) def ExportCorrelation(exportfile, correlation, page_info, savetrace=True): """Write correlation data (as displayed in PyCorrFit) to a file Parameters ---------- exportfile : str Absolute file name to save the data to correlation : PyCorrFit "Correlation" object Contains all correlation data page_info : module A multi-line string containing information on the correlation that will be written to the file as a comment savetrace : bool Append the trace to the file Notes ----- Note that this method exports the plotted data: - Correlation.correlation_plot - Correlation.residuals_plot - Correlation.modeled_plot which means that the data could be normalized to, for instance, the total particle number `n`. """ openedfile = codecs.open(exportfile, 'w', encoding='utf-8') # First, some doc text openedfile.write(ReadmeCSV.replace('\n', '\r\n')) # The info for line in page_info.splitlines(): openedfile.write("# "+line+"\r\n") openedfile.write("#\r\n#\r\n") # Get all the data we need from the Page # Modeled data corr = correlation mod = corr.modeled_plot[:, 1] if corr.correlation is not None: # Experimental data tau = corr.correlation_plot[:, 0] exp = corr.correlation_plot[:, 1] res = corr.residuals_plot[:, 0] # Plotting! Because we only export plotted area. if corr.is_weighted_fit: weightname = corr.fit_weight_type try: weight = corr.fit_results["fit weights"] except KeyError: weight = corr.fit_weight_data if weight is None: pass elif len(weight) != len(exp): text = "Weights have not been calculated for the " +\ "area you want to export. Pressing 'Fit' " +\ "again should solve this issue. Weights will " +\ "not be saved." warnings.warn(text) weight = None else: weight = None weightname = None else: tau = corr.lag_time_fit exp = None res = None # Include weights in data saving: # PyCorrFit thinks in [ms], but we will save as [s] timefactor = 0.001 tau = [timefactor * t for t in tau] # Now we want to write all that data into the file # This is for csv writing: # Correlation curve dataWriter = csv.writer(openedfile, delimiter='\t') if exp is not None: header = '# Lag time [s]'+"\t" + \ 'Experimental correlation'+"\t" + \ 'Fitted correlation' + "\t" + \ 'Residuals'+"\r\n" data = [tau, exp, mod, res] if corr.is_weighted_fit and weight is not None: header = "{} \t Weights [{}] \r\n".format( header.strip(), weightname) data.append(weight) else: header = '# Lag time [s]'+"\t" + \ 'Correlation function'+"\r\n" data = [tau, mod] # Write header openedfile.write(header) # Write data for i in np.arange(len(data[0])): # row-wise, data may have more than two elements per row datarow = list() for j in np.arange(len(data)): rowcoli = "{:.10e}".format(data[j][i]) datarow.append(rowcoli) dataWriter.writerow(datarow) # Trace # Only save the trace if user wants us to: if savetrace: # We will also save the trace in [s] # Intensity trace in kHz may stay the same if len(corr.traces) > 0: # Mark beginning of Trace openedfile.write('#\r\n#\r\n# BEGIN TRACE\r\n#\r\n') # Columns time = corr.traces[0][:, 0] * timefactor intensity = corr.traces[0][:, 1] # Write openedfile.write('# Time [s]'+"\t" 'Intensity trace [kHz]'+" \r\n") for i in np.arange(len(time)): dataWriter.writerow(["{:.10e}".format(time[i]), "{:.10e}".format(intensity[i])]) if len(corr.traces) > 1: # We have some cross-correlation here: # Mark beginning of Trace B openedfile.write('#\r\n#\r\n# BEGIN SECOND TRACE\r\n#\r\n') # Columns time = corr.traces[1][:, 0] * timefactor intensity = corr.traces[1][:, 1] # Write openedfile.write('# Time [s]'+"\t" 'Intensity trace [kHz]'+" \r\n") for i in np.arange(len(time)): dataWriter.writerow(["{:.10e}".format(time[i]), "{:.10e}".format(intensity[i])]) openedfile.close() session_wildcards = [".pcfs", ".pycorrfit-session.zip", ".fcsfit-session.zip"] ReadmeCSV = """# This file was created using PyCorrFit version {}. # # Lines starting with a '#' are treated as comments. # The data is stored as CSV below this comment section. # Data usually consists of lag times (channels) and # the corresponding correlation function - experimental # and fitted values plus resulting residuals. # If this file is opened by PyCorrFit, only the first two # columns will be imported as experimental data. # """.format(__version__) ReadmeSession = """This file was created using PyCorrFit version {}. The .zip archive you are looking at is a stored session of PyCorrFit. If you are interested in how the data is stored, you will find out here. Most important are the dimensions of units: Dimensionless representation: unit of time : 1 ms unit of inverse time: 10³ /s unit of distance : 100 nm unit of Diff.coeff : 10 µm²/s unit of inverse area: 100 /µm² unit of inv. volume : 1000 /µm³ From there, the dimension of any parameter may be calculated. There are a number of files within this archive, depending on what was done during the session. backgrounds.csv - Contains the list of backgrounds used and - Averaged intensities in [kHz] bg_trace*.csv (where * is an integer) - The trace of the background corresponding to the line number in backgrounds.csv - Time in [ms], Trace in [kHz] comments.txt - Contains page titles and session comment - First n lines are titles, rest is session comment (where n is total number of pages) data*.csv (where * is (Number of page)) - Contains lag times [ms] - Contains experimental data, if available externalweights.txt - Contains names (types) of external weights other than from Model function or spline fit - Linewise: 1st element is page number, 2nd is name - According to this data, the following files are present in the archive externalweights_data_*PageID*_*Type*.csv - Contains weighting information of Page *PageID* of type *Type* model_*ModelID*.txt - An external (user-defined) model file with internal ID *ModelID* Parameters.yaml - Contains all Parameters for each page Block format: - - '#(Number of page): ' - (Internal model ID) - (List of parameters) - (List of checked parameters (for fitting)) - [(Min channel selected), (Max channel selected)] - [(Weighted fit method (0=None, 1=Spline, 2=Model function)), (No. of bins from left and right), (No. of knots (of e.g. spline)), (Type of fitting algorithm (e.g. "Lev-Mar", "Nelder-Mead")] - [B1,B2] Background to use (line in backgrounds.csv) B2 is always *null* for autocorrelation curves - Data type is Cross-correlation? - Parameter id (int) used for normalization in plotting. This number first enumerates the model parameters and then the supplemental parameters (e.g. "n1"). - - [min, max] fitting parameter range of 1st parameter - [min, max] fitting parameter range of 2nd parameter - etc. - Order in Parameters.yaml defines order of pages in a session - Order in Parameters.yaml defines order in comments.txt Readme.txt (this file) Supplements.yaml - Contains errors of fitting Format: -- Page number -- [parameter id, error value] - [parameter id, error value] - Chi squared - [pages that share parameters] (from global fitting) trace*.csv (where * is (Number of page) | appendix "A" or "B" point to the respective channels (only in cross-correlation mode)) - Contains times [ms] - Contains countrates [kHz] """.format(__version__) pycorrfit-1.1.7/pycorrfit/fit.py0000664000372000037200000011451713554642611017600 0ustar travistravis00000000000000"""PyCorrFit data set: Classes for FCS data evaluation""" import copy import warnings import lmfit import numpy as np import scipy.interpolate as spintp class StuckParameterWarning(UserWarning): pass class Constraint(object): """ Class to translate fit constraints to lmfit syntax. """ def __init__(self, constraint, fit_bool, fit_bounds, fit_values): """ Parameters ---------- constraint : list of strings and ints The abstract constraint (e.g. [1, 0, "<", "2.3"]) as used in the model definitions. fit_bool : list of boolean A list of bools indicating which parameters are varied during fitting. fit_bounds : list of lists of two floats The parameter boundaries for fitting. fit_values : list of floats The initial fitting values. Notes ----- - the first item in constraints must be an integer indexing a parameter - the second/third item must be an integer as well or an operator (">", "<") - if the fourth item is omitted, it is assumed to be "0" - the first integer must be larger than the second integer """ if len(constraint) == 3: constraint.append("0") self.constraint = constraint self.fit_bool = fit_bool self.fit_bounds = fit_bounds self.fit_values = fit_values @property def parameters(self): """ Returns list of dict for each parameter. """ parms = [it for it in self.constraint if isinstance(it, int)] id2 = self.constraint.index(parms[1]) p1 = {"id": parms[0], "bool": self.fit_bool[parms[0]], "sign": +1, "value": self.fit_values[parms[0]] } p2 = {"id": parms[1], "bool": self.fit_bool[parms[1]], "sign": (+1 if id2 == 2 else -1), "value": self.fit_values[parms[1]] } return p1, p2 @property def operator(self): strval = [it for it in self.constraint if not isinstance(it, int)] return strval[0] @property def offset(self): return float(self.constraint[-1]) def update_fit_bounds(self): """ Update the bounds with the given constraint. This only applies if one of the parameters is not varied during fitting. Notes ----- The fitting boundaries are updated in-place (`fit_bounds` variable from `__init__`). """ p1, p2 = self.parameters op = self.operator os = self.offset if op not in ["<", ">"]: raise ValueError("Unsupported operator: {}".format(op)) if p1["bool"] and p2["bool"]: # do nothing, this case is handled in `get_lmfit_parameter_kwargs` pass elif p1["bool"]: # only parameter 1 is varied if op == "<": # [3, "<", 1, "0"] -> p1 < p2 # [3, 1, "<", "0"] -> p1 < -p2 # [3, 1, "<", "1.2] -> p1 < -p2 + 1.2 # [3, "<", 1, "1.2] -> p1 < p2 + 1.2 bnd = [-np.inf, p2["sign"]*p2["value"] + os] else: # [3, ">", 1, "0"] -> p1 > p2 # [3, 1, ">", "0"] -> p1 > -p2 # [3, 1, ">", "1.2] -> p1 > -p2 + 1.2 # [3, ">", 1, "1.2] -> p1 > p2 + 1.2 bnd = [p2["sign"]*p2["value"] + os, np.inf] bound = [max(self.fit_bounds[p1["id"]][0], bnd[0]), min(self.fit_bounds[p1["id"]][1], bnd[1]), ] self.fit_bounds[p1["id"]] = bound elif p2["bool"]: # only parameter2 is varied if op == "<": # [3, "<", 1, "0"] -> p2 > p1 # [3, 1, "<", "0"] -> (-)p2 > p1 -> p2 < -p1 # [3, 1, "<", "1.2] -> p2 < -p1 + 1.2 = -(p1-1.2) # [3, "<", 1, "1.2] -> p2 > p1 - 1.2 = +(p1-1.2) if p2["sign"] == -1: bnd = [-np.inf, -(p1["value"] - os)] else: bnd = [(p1["value"] - os), np.inf] else: # [3, ">", 1, "0"] -> p2 < p1 # [3, 1, ">", "0"] -> p2 > -p1 # [3, 1, ">", "1.2] -> p2 > -(p1 - 1.2) # [3, ">", 1, "1.2] -> p2 < p1 - 1.2 if p2["sign"] == -1: bnd = [-(p1["value"] - os), np.inf] else: bnd = [-np.inf, p1["value"] - os] bound = [max(self.fit_bounds[p2["id"]][0], bnd[0]), min(self.fit_bounds[p2["id"]][1], bnd[1]), ] self.fit_bounds[p2["id"]] = bound else: # neither is varied. # Do nothing. pass return self.fit_bounds def get_lmfit_parameter_kwargs(self): """ Using the given constraint, update the list of lmfit parameters. """ p1, p2 = self.parameters op = self.operator ofs = self.offset if op not in ["<", ">"]: raise ValueError("Unsupported operator: {}".format(op)) if p1["bool"] and p2["bool"]: if op == "<": # p1 < (-)p2 + 1.2 # -> p1 = (-)p2 - d12 + 1.2 # -> d12 = (-)p2 - p1 + 1.2 # -> d12 > 0 deltaname = "delta_{}_{}".format(p1["id"], p2["id"]) kwdelt = {} kwdelt["name"] = deltaname kwdelt["value"] = p2["bool"] * \ self.fit_values[p2["id"]] - self.fit_values[p1["id"]] kwdelt["vary"] = True kwdelt["min"] = 0 # note: enforces "<=" (not "<") kwdelt["max"] = np.inf kwp1 = {} kwp1["name"] = "parm{:04d}".format(p1["id"]) # this condition deals with negative numbers kwp1["expr"] = "{MIN} if {COMP} < {MIN} else {MAX} if {COMP} > {MAX} else {COMP}".format( COMP="{}*parm{:04d}-{}+{:.14f}".format( p2["sign"], p2["id"], deltaname, ofs), MIN=self.fit_bounds[p1["id"]][0], MAX=self.fit_bounds[p1["id"]][1]) kwargs = [kwdelt, kwp1] elif op == ">": # p1 > (-)p2 + 1.2 # -> p1 = (-)p2 + d12 + 1.2 # -> d12 = p1 - (-)p2 - 1.2 # -> d12 > 0 deltaname = "delta_{}_{}".format(p1["id"], p2["id"]) kwdelt = {} kwdelt["name"] = deltaname kwdelt["value"] = self.fit_values[p1["id"]] - \ p2["bool"]*self.fit_values[p2["id"]] kwdelt["vary"] = True kwdelt["min"] = 0 # note: enforces ">=" (not ">") # self.fit_bounds[p1["id"]][1] + max(-p2["sign"]*self.fit_bounds[p2["id"]]) - ofs kwdelt["max"] = np.inf kwp1 = {} kwp1["name"] = "parm{:04d}".format(p1["id"]) # this condition deals with negative numbers kwp1["expr"] = "{MIN} if {COMP} < {MIN} else {MAX} if {COMP} > {MAX} else {COMP}".format( COMP="{}*parm{:04d}+{}+{:.14f}".format( p2["sign"], p2["id"], deltaname, ofs), MIN=self.fit_bounds[p1["id"]][0], MAX=self.fit_bounds[p1["id"]][1]) kwargs = [kwdelt, kwp1] else: kwargs = None return kwargs class Fit(object): """ Used for fitting FCS data to models. """ def __init__(self, correlations=[], global_fit=False, global_fit_variables=[], uselatex=False, verbose=0): """ Using an FCS model, fit the data of shape (N,2). Parameters ---------- correlations: list of instances or instance of `pycorrfit.Correlation` Correlations to fit. global fit : bool Perform global fit. The default behavior is to fit all parameters that are selected for fitting in each correlation. Parameters with the same name in different models are treated as one global parameter. global_fit_variables: list of list of strings Each item contains a list of strings that are names of parameters which will be treated as a common parameter. This breaks the defaul behavior. NOT IMPLEMENTED YET! verbose: int Increase verbosity by incrementing this number. uselatex: bool If verbose > 0, plotting will be performed with LaTeX. """ if len(global_fit_variables) != 0: raise NotImplementedError("`global_fit_variables` not available!") if not isinstance(correlations, list): correlations = [correlations] self.correlations = correlations self.global_fit_variables = global_fit_variables self.verbose = verbose self.uselatex = uselatex self.is_weighted_fit = False if not global_fit: # Fit each correlation separately for corr in self.correlations: # Set fitting options self.fit_algorithm = corr.fit_algorithm # Get the data required for fitting self.x = corr.correlation_fit[:, 0] self.y = corr.correlation_fit[:, 1] # fit_bool: True for variable self.fit_bool = corr.fit_parameters_variable.copy() self.fit_parm = corr.fit_parameters.copy() self.fit_bound = copy.copy(corr.fit_parameters_range) self.is_weighted_fit = corr.is_weighted_fit self.fit_weights = Fit.compute_weights(corr, verbose=verbose, uselatex=uselatex) self.fit_parm_names = corr.fit_model.parameters[0] self.func = corr.fit_model.function self.check_parms = corr.check_parms self.constraints = corr.fit_model.constraints # Directly perform the fit and set the "fit" attribute self.minimize() # Run a second time: self.minimize() # update correlation model parameters corr.fit_parameters = self.fit_parm # save fit data in correlation class corr.fit_results = self.get_fit_results(corr) else: # TODO: # - allow detaching of parameters, # i.e. fitting "n" separately for two models # Initiate all arrays self.fit_algorithm = self.correlations[0].fit_algorithm xtemp = [] # x ytemp = [] # y weights = [] # weights ids = [0] # ids in big fitting array cmodels = [] # correlation model info initpar = [] # initial parameters varin = [] # names of variable fitting parameters variv = [] # values of variable fitting parameters varmap = [] # list of indices of fitted parameters varbound = [] # list of fitting boundaries self.is_weighted_fit = None for corr in self.correlations: xtemp.append(corr.correlation_fit[:, 0]) ytemp.append(corr.correlation_fit[:, 1]) weights.append(Fit.compute_weights(corr)) ids.append(len(xtemp[-1])+ids[-1]) cmodels.append(corr.fit_model) initpar.append(corr.fit_parameters) # Create list of variable parameters varthis = [] for ipm, par in enumerate(corr.fit_model.parameters[0]): if corr.fit_parameters_variable[ipm]: varthis.append(ipm) varin.append(par) variv.append(corr.fit_parameters[ipm]) varbound.append(corr.fit_parameters_range[ipm]) varmap.append(varthis) # These are the variable fitting parameters __, varidx = np.unique(varin, return_index=True) varidx.sort() varin = np.array(varin)[varidx] variv = np.array(variv)[varidx] self.x = np.concatenate(xtemp) self.y = np.concatenate(ytemp) self.fit_bool = np.ones(len(variv), dtype=bool) self.fit_parm = variv self.fit_weights = np.concatenate(weights) self.fit_parm_names = varin self.fit_bound = varbound self.constraints = [] warnings.warn( "Constraints are not supported yet for global fitting.") def parameters_global_to_local(parameters, iicorr, varin=varin, initpar=initpar, correlations=correlations): """ With global `parameters` and an id `iicorr` pointing at the correlation in `self.correlations`, return the updated parameters of the corresponding model. """ fit_parm = initpar[iicorr].copy() corr = correlations[iicorr] mod = corr.fit_model for kk, pn in enumerate(mod.parameters[0]): if pn in varin: # edit that parameter fit_parm[kk] = parameters[np.where( np.array(varin) == pn)[0]] return fit_parm def parameters_local_to_global(parameters, iicorr, fit_parm, varin=varin, correlations=correlations): """ inverse of parameters_global_to_local """ corr = correlations[iicorr] mod = corr.fit_model for kk, pn in enumerate(mod.parameters[0]): if pn in varin: # edit that parameter parameters[np.where(np.array(varin) == pn)[ 0]] = fit_parm[kk] return parameters # Create function for fitting using ids def global_func(parameters, tau, glob2loc=parameters_global_to_local): out = [] # ids start at 0 for ii, mod in enumerate(cmodels): # Update parameters fit_parm = glob2loc(parameters, ii) # return function out.append(mod.function(fit_parm, tau[ids[ii]:ids[ii+1]])) return np.concatenate(out) self.func = global_func # Create function for checking def global_check_parms(parameters, glob2loc=parameters_global_to_local, loc2glob=parameters_local_to_global): for ii, corr in enumerate(self.correlations): # create new initpar fit_parm = glob2loc(parameters, ii) fit_parm = corr.check_parms(fit_parm) # update parameters parameters = loc2glob(parameters, ii, fit_parm) return parameters self.check_parms = global_check_parms # Directly perform the fit and set the "fit" attribute self.minimize() # Update correlations for ii, corr in enumerate(self.correlations): # write new model parameters corr.fit_parameters = parameters_global_to_local(self.fit_parm, ii) # save fit data in correlation class corr.fit_results = self.get_fit_results(corr) def get_fit_results(self, correlation): """ Return a dictionary with all information about the performed fit. This function must be called immediately after `self.minimize`. """ c = correlation d = { "chi2": self.chi_squared, "chi2 type": self.chi_squared_type, "weighted fit": c.is_weighted_fit, "fit algorithm": c.fit_algorithm, "fit result": 1*c.fit_parameters, "fit parameters": 1*np.where(c.fit_parameters_variable)[0], "fit weights": 1*self.compute_weights(c) } if c.is_weighted_fit: d["weighted fit type"] = c.fit_weight_type if isinstance(c.fit_weight_data, (int, float)): d["weighted fit bins"] = c.fit_weight_data if d["fit algorithm"] == "Lev-Mar" and self.parmoptim_error is not None: d["fit error estimation"] = self.parmoptim_error return d @property def chi_squared(self): """ Calculate displayed Chi² Calculate reduced Chi² for the current class. """ # Calculate degrees of freedom dof = len(self.x) - np.sum(self.fit_bool) - 1 # This is exactly what is minimized by the scalar minimizers if self.chi_squared_type == "reduced expected sum of squares": fitted = self.func(self.fit_parm, self.x) chi2 = np.sum((self.y-fitted)**2/np.abs(fitted)) / dof elif self.chi_squared_type == "reduced weighted sum of squares": fitted = self.func(self.fit_parm, self.x) variance = self.fit_weights**2 chi2 = np.sum((self.y-fitted)**2/variance) / dof elif self.chi_squared_type == "reduced global sum of squares": fitted = self.func(self.fit_parm, self.x) variance = self.fit_weights**2 chi2 = np.sum((self.y-fitted)**2/variance) / dof else: chi2 = self.fit_function_scalar( self.fit_parm, self.x, self.y, self.fit_weights) return chi2 @property def chi_squared_type(self): """ The type of Chi² that currently applies. Returns ------- "reduced" - if variance of data was used for fitting "reduced Pearson" - if variance of data is not available """ if self.is_weighted_fit is None: # global fitting return "reduced global sum of squares" elif self.is_weighted_fit == True: return "reduced weighted sum of squares" elif self.is_weighted_fit == False: return "reduced expected sum of squares" else: raise ValueError("Unknown weight type!") @staticmethod def compute_weights(correlation, verbose=0, uselatex=False): """ computes and returns weights of the same length as `correlation.correlation_fit` `correlation` is an instance of Correlation """ corr = correlation model = corr.fit_model model_parms = corr.fit_parameters ival = corr.fit_ival weight_data = corr.fit_weight_data weight_type = corr.fit_weight_type #parameters = corr.fit_parameters #parameters_range = corr.fit_parameters_range #parameters_variable = corr.fit_parameters_variable cdat = corr.correlation if cdat is None: raise ValueError("Cannot compute weights; No correlation given!") cdatfit = corr.correlation_fit x_full = cdat[:, 0] y_full = cdat[:, 1] x_fit = cdatfit[:, 0] #y_fit = cdatfit[:,1] dataweights = np.ones_like(x_fit) try: weight_spread = int(weight_data) except: if verbose > 1: warnings.warn( "Could not get weight spread for spline. Setting it to 3.") weight_spread = 3 if weight_type[:6] == "spline": # Number of knots to use for spline try: knotnumber = int(weight_type[6:]) except: if verbose > 1: print("Could not get knot number. Setting it to 5.") knotnumber = 5 # Compute borders for spline fit. if ival[0] < weight_spread: # optimal case pmin = ival[0] else: # non-optimal case # we need to cut pmin pmin = weight_spread if x_full.shape[0] - ival[1] < weight_spread: # optimal case pmax = x_full.shape[0] - ival[1] else: # non-optimal case # we need to cut pmax pmax = weight_spread x = x_full[ival[0]-pmin:ival[1]+pmax] y = y_full[ival[0]-pmin:ival[1]+pmax] # we are fitting knots on a base 10 logarithmic scale. xs = np.log10(x) knots = np.linspace(xs[1], xs[-1], knotnumber+2)[1:-1] try: tck = spintp.splrep(xs, y, s=0, k=3, t=knots, task=-1) ys = spintp.splev(xs, tck, der=0) except: if verbose > 0: raise ValueError("Could not find spline fit with " + "{} knots.".format(knotnumber)) return if verbose > 0: # If plotting module is available: #name = "spline fit: "+str(knotnumber)+" knots" # plotting.savePlotSingle(name, 1*x, 1*y, 1*ys, # dirname=".", # uselatex=uselatex) # use matplotlib.pylab try: from matplotlib import pylab as plt plt.xscale("log") plt.plot(x, ys, x, y) plt.show() except ImportError: # Tell the user to install matplotlib print("Couldn't import pylab! - not Plotting") # Calculation of variance # In some cases, the actual cropping interval from ival[0] # to ival[1] is chosen, such that the dataweights must be # calculated from unknown datapoints. # (e.g. points+endcrop > len(correlation) # We deal with this by multiplying dataweights with a factor # corresponding to the missed points. for i in range(x_fit.shape[0]): # Define start and end positions of the sections from # where we wish to calculate the dataweights. # Offset at beginning: if i + ival[0] < weight_spread: # The offset that occurs offsetstart = weight_spread - i - ival[0] offsetcrop = 0 elif ival[0] > weight_spread: offsetstart = 0 offsetcrop = ival[0] - weight_spread else: offsetstart = 0 offsetcrop = 0 # i: counter on correlation array # start: counter on y array start = i - weight_spread + offsetstart + ival[0] - offsetcrop end = start + 2*weight_spread + 1 - offsetstart dataweights[i] = (y[start:end] - ys[start:end]).std() # The standard deviation at the end and the start of the # array are multiplied by a factor corresponding to the # number of bins that were not used for calculation of the # standard deviation. if offsetstart != 0: reference = 2*weight_spread + 1 dividor = reference - offsetstart dataweights[i] *= reference/dividor # Do not substitute len(y[start:end]) with end-start! # It is not the same! backset = 2*weight_spread + 1 - len(y[start:end]) - offsetstart if backset != 0: reference = 2*weight_spread + 1 dividor = reference - backset dataweights[i] *= reference/dividor elif weight_type == "model function": # Number of neighboring (left and right) points to include if ival[0] < weight_spread: pmin = ival[0] else: pmin = weight_spread if x_full.shape[0] - ival[1] < weight_spread: pmax = x_full.shape[0] - ival[1] else: pmax = weight_spread x = x_full[ival[0]-pmin:ival[1]+pmax] y = y_full[ival[0]-pmin:ival[1]+pmax] # Calculated dataweights for i in np.arange(x_fit.shape[0]): # Define start and end positions of the sections from # where we wish to calculate the dataweights. # Offset at beginning: if i + ival[0] < weight_spread: # The offset that occurs offsetstart = weight_spread - i - ival[0] offsetcrop = 0 elif ival[0] > weight_spread: offsetstart = 0 offsetcrop = ival[0] - weight_spread else: offsetstart = 0 offsetcrop = 0 # i: counter on correlation array # start: counter on correlation array start = i - weight_spread + offsetstart + ival[0] - offsetcrop end = start + 2*weight_spread + 1 - offsetstart #start = ival[0] - weight_spread + i #end = ival[0] + weight_spread + i + 1 diff = y - model(model_parms, x) dataweights[i] = diff[start:end].std() # The standard deviation at the end and the start of the # array are multiplied by a factor corresponding to the # number of bins that were not used for calculation of the # standard deviation. if offsetstart != 0: reference = 2*weight_spread + 1 dividor = reference - offsetstart dataweights[i] *= reference/dividor # Do not substitute len(diff[start:end]) with end-start! # It is not the same! backset = 2*weight_spread + 1 - \ len(diff[start:end]) - offsetstart if backset != 0: reference = 2*weight_spread + 1 dividor = reference - backset dataweights[i] *= reference/dividor elif weight_type == "none": pass else: # This means that the user knows the dataweights and already # gave it to us. weights = weight_data if weights is None: msg = "User defined weights not given: {}".format(weight_type) raise ValueError(msg) # Check if these other weights have length of the cropped # or the full array. if weights.shape[0] == x_fit.shape[0]: dataweights = weights elif weights.shape[0] == x_full.shape[0]: dataweights = weights[ival[0]:ival[1]] else: msg = "`weights` must have length of full or cropped array." raise ValueError(msg) return dataweights def fit_function(self, params, x, y, weights=1): """ objective function that returns the residual (difference between model and data) to be minimized in a least squares sense. """ parms = Fit.lmfitparm2array(params) tominimize = (self.func(parms, x) - y) # Check dataweights for zeros and don't use these # values for the least squares method. with np.errstate(divide='ignore'): tominimize = np.where(weights != 0, tominimize/weights, 0) # There might be NaN values because of zero weights: #tominimize = tominimize[~np.isinf(tominimize)] return tominimize def fit_function_scalar(self, parms, x, y, weights=1): """ Wrapper of `fit_function`. Returns the sum of squares of the input data. """ e = self.fit_function(parms, x, y, weights) return np.sum(e*e) def get_lmfitparm(self): """ Generates an lmfit parameter class from the present data set. The following parameters are used: self.x : 1d ndarray length N self.y : 1d ndarray length N self.fit_weights : 1d ndarray length N self.fit_bool : 1d ndarray length P, bool self.fit_parm : 1d ndarray length P, float """ params = lmfit.Parameters() # First, add all fixed parameters for pp in range(len(self.fit_parm)): if not self.fit_bool[pp]: if self.fit_bound[pp][0] == self.fit_bound[pp][1]: self.fit_bound[pp] = [-np.inf, np.inf] params.add(lmfit.Parameter(name="parm{:04d}".format(pp), value=self.fit_parm[pp], vary=self.fit_bool[pp], min=self.fit_bound[pp][0], max=self.fit_bound[pp][1], ) ) # Second, summarize the constraints in a dictionary, where # keys are the parameter indexes of varied parameters. # The dictionary cstrnew only allows integer keys that are # representing parameter indices. The fact that we are effectively # reducing the number of valid constraints to one per parameter # is a design problem that cannot be resolved here. The constraints # must be defined in such a way, that a parameter with a larger # index number is dependent on only one parameter with a lower # index number, e.g. parm1>parm0, parm3 parm1 < parm0 # [3, ">", 1] -> parm3 > parm1 # or the four-element form: # [1, "<", 0, "2.3"]] -> parm1 < parm0 + 2.3 # [1, 0, "<", "2.3"]] -> parm1 + parm0 < 2.3 for pp in range(len(self.fit_parm)): if self.fit_bool[pp]: inconstr = len([cc for cc in self.constraints if pp in cc]) kwarglist = [] if inconstr: for cc in self.constraints: con = Constraint(constraint=cc, fit_bool=self.fit_bool, fit_bounds=self.fit_bound, fit_values=self.fit_parm) self.fit_bound = con.update_fit_bounds() if con.parameters[0]["id"] == pp: kws = con.get_lmfit_parameter_kwargs() if kws is not None: kwarglist += kws if len(kwarglist) == 0: # normal parameter kwarglist += [{"name": "parm{:04d}".format(pp), "value": self.fit_parm[pp], "vary": True, "min": self.fit_bound[pp][0], "max": self.fit_bound[pp][1], }] for kw in kwarglist: params.add(lmfit.Parameter(**kw)) return params @staticmethod def lmfitparm2array(parms, parmid="parm", attribute="value"): """ Convert lmfit parameters to a numpy array. Parameters are identified by name `parmid` which should be at the beginning of a parameters. This method is necessary to separate artificial constraint parameters from the actual parameters. Parameters ---------- parms : lmfit.parameter.Parameters or ndarray The input parameters. parmid : str The identifier for parameters. By default this is "parm", i.e. parameters are named like this: "parm0001", "parm0002", etc. attribute : str The attribute to return, e.g. - "value" : return the current value of the parameter - "vary" : return if the parameter is varied during fitting Returns: parr : ndarray If the input is an ndarray, the input will be returned. """ if isinstance(parms, lmfit.parameter.Parameters): items = parms.items() parr = [] for p in sorted(items, key=lambda x: x[0]): if p[0].startswith(parmid): parr.append(getattr(p[1], attribute)) else: parr = parms return np.array(parr) def minimize(self): """ This will run the minimization process """ if np.sum(self.fit_bool) == 0: raise ValueError("No parameter selected for fitting!") # get all parameters for minimization params = self.get_lmfitparm() # Get algorithm method = Algorithms[self.fit_algorithm][0] methodkwargs = Algorithms[self.fit_algorithm][2] # Begin fitting # Fit a several times and stop earlier if the residuals # are small enough (heuristic approach). nfits = 5 diff = np.inf parmsinit = Fit.lmfitparm2array(params) for ii in range(nfits): res0 = self.fit_function(params, self.x, self.y) result = lmfit.minimize(fcn=self.fit_function, params=params, method=method, kws={"x": self.x, "y": self.y, "weights": self.fit_weights}, **methodkwargs ) params = result.params res1 = self.fit_function(params, self.x, self.y) diff = np.average(np.abs(res0-res1)) if hasattr(result, "ier") and not result.errorbars and ii+1 < nfits: # This case applies to the Levenberg-Marquardt algorithm multby = .5 # Try to vary stuck fitting parameters # the result from the previous fit parmsres = Fit.lmfitparm2array(params) # the parameters that are varied during fitting parmsbool = Fit.lmfitparm2array(params, attribute="vary") # The parameters that are stuck parmstuck = parmsbool * (parmsinit == parmsres) parmsres[parmstuck] *= multby # write changes self.fit_parm = parmsres params = self.get_lmfitparm() warnings.warn(u"PyCorrFit detected problems in fitting, " + u"detected a stuck parameter, multiplied " + u"it by {}, and fitted again. ".format(multby) + u"The stuck parameters are: {}".format( np.array(self.fit_parm_names)[parmstuck]), StuckParameterWarning) elif diff < 1e-8: # Experience tells us this is good enough. break # Now write the optimal parameters to our values: self.fit_parm = Fit.lmfitparm2array(params) # Only allow physically correct parameters self.fit_parm = self.check_parms(self.fit_parm) # Compute error estimates for fit (Only "Lev-Mar") if (self.fit_algorithm == "Lev-Mar" and result.success # If the covariance matrix cannot be computed in # - lmfit <= 0.9.10: result.covar is None # - lmfit >= 0.9.11: result.covar is not set and hasattr(result, "covar") and result.covar is not None): # This is the standard way to minimize the data. Therefore, # we are a little bit more verbose. covar = result.covar try: self.parmoptim_error = np.diag(covar) except: warnings.warn("PyCorrFit Warning: Error estimate not " + "possible, because we could not " + "calculate covariance matrix. Please " + "try reducing the number of fitting " + "parameters.") self.parmoptim_error = None else: self.parmoptim_error = None def GetAlgorithmStringList(): """ Get supported fitting algorithms as strings. Returns two lists (that are key-sorted) for key and string. """ A = Algorithms out1 = [] out2 = [] a = list(A.keys()) a.sort() for key in a: out1.append(key) out2.append(A[key][1]) return out1, out2 # As of version 0.8.3, we support several minimization methods for # fitting data to experimental curves. # These functions must be callable like scipy.optimize.leastsq. e.g. # res = spopt.leastsq(self.fit_function, self.fitparms[:], # args=(self.x), full_output=1) Algorithms = dict() # the original one is the least squares fit "leastsq" Algorithms["Lev-Mar"] = ["leastsq", "Levenberg-Marquardt", {"ftol": 1.49012e-08, "xtol": 1.49012e-08, } ] # simplex Algorithms["Nelder-Mead"] = ["nelder", "Nelder-Mead (downhill simplex)", {} ] # quasi-Newton method of Broyden, Fletcher, Goldfarb, and Shanno Algorithms["BFGS"] = ["lbfgsb", "BFGS (quasi-Newton)", {} ] # modified Powell-method Algorithms["Powell"] = ["powell", "modified Powell (conjugate direction)", {} ] # nonliner conjugate gradient method by Polak and Ribiere Algorithms["SLSQP"] = ["slsqp", "Sequential Linear Squares Programming", {} ] pycorrfit-1.1.7/pycorrfit/PyCorrFit.py0000664000372000037200000000034613554642611020671 0ustar travistravis00000000000000"""PyCorrFit loader""" from os.path import dirname, abspath, split import sys sys.path = [split(abspath(dirname(__file__)))[0]] + sys.path from pycorrfit.gui import main # noqa: E402 if __name__ == "__main__": main.Main() pycorrfit-1.1.7/pycorrfit/__main__.py0000664000372000037200000000006713554642611020530 0ustar travistravis00000000000000"""Run PyCorrFit""" from .gui import main main.Main() pycorrfit-1.1.7/pycorrfit/models/0000775000372000037200000000000013554643106017716 5ustar travistravis00000000000000pycorrfit-1.1.7/pycorrfit/models/model_confocal_3d.py0000664000372000037200000000374513554642611023633 0ustar travistravis00000000000000import numpy as np from .control import model_setup from .cp_confocal import threed # 3D simple gauss def CF_Gxyz_gauss(parms, tau): # Model 6012 u""" Three-dimanesional free diffusion with a Gaussian laser profile (eliptical). G(τ) = offset + 1/( n*(1+τ/τ_diff) * sqrt(1 + τ/(SP²*τ_diff)) ) Calculation of diffusion coefficient and concentration from the effective radius of the detection profile (r₀ = 2*σ): D = r₀²/(4*τ_diff) Conc = n/( sqrt(π³)*r₀²*z₀ ) r₀ lateral detection radius (waist of lateral gaussian) z₀ axial detection length (waist of axial gaussian) D Diffusion coefficient Conc Concentration of dye *parms* - a list of parameters. Parameters (parms[i]): [0] n Effective number of particles in confocal volume [1] τ_diff Characteristic residence time in confocal volume [2] SP SP=z₀/r₀ Structural parameter, describes the axis ratio of the confocal volume [3] offset *tau* - lag time """ n = parms[0] taudiff = parms[1] SP = parms[2] off = parms[3] BB = threed(tau, taudiff, SP) G = off + 1/n * BB return G def supplements(parms, countrate=None): # We can only give you the effective particle number n = parms[0] Info = list() if countrate is not None: # CPP cpp = countrate/n Info.append(["cpp [kHz]", cpp]) return Info parms = [4.0, 0.4, 5.0, 0.0] boundaries = [[0, np.inf]]*len(parms) boundaries[-1] = [-np.inf, np.inf] model_setup( modelid=6012, name="3D diffusion (confocal)", comp="3D", mtype="Confocal (Gaussian)", fctn=CF_Gxyz_gauss, par_labels=[ u"n", u"τ_diff [ms]", u"SP", u"offset"], par_values=parms, par_vary=[True, True, False, False], par_boundaries=boundaries, supplementary_method=supplements ) pycorrfit-1.1.7/pycorrfit/models/model_confocal_t_2d.py0000664000372000037200000000461313554642611024150 0ustar travistravis00000000000000import numpy as np from .control import model_setup from .cp_confocal import twod from .cp_triplet import trip # 2D simple gauss def CF_Gxy_T_gauss(parms, tau): u""" Two-dimensional diffusion with a Gaussian laser profile, including a triplet component. The triplet factor takes into account a blinking term. Set *T* or *τ_trip* to 0, if no triplet component is wanted. triplet = 1 + T/(1-T)*exp(-τ/τ_trip) G(τ) = offset + 1/( n * (1+τ/τ_diff) )*triplet Calculation of diffusion coefficient and concentration from the effective radius of the detection profile (r₀ = 2*σ): D = r₀²/(4*τ_diff) Conc = n/(π*r₀²) *parms* - a list of parameters. Parameters (parms[i]): [0] n Effective number of particles in confocal area [1] τ_diff Characteristic residence time in confocal area [2] τ_trip Characteristic residence time in triplet state [3] T Fraction of particles in triplet (non-fluorescent) state 0 <= T < 1 [4] offset *tau* - lag time """ n = parms[0] taudiff = parms[1] tautrip = parms[2] T = parms[3] dc = parms[4] triplet = trip(tau=tau, tautrip=tautrip, T=T) BB = twod(tau=tau, taudiff=taudiff) G = dc + 1/n * BB * triplet return G def supplements(parms, countrate=None): # We can only give you the effective particle number n = parms[0] Info = list() if countrate is not None: # CPP cpp = countrate/n Info.append(["cpp [kHz]", cpp]) return Info parms = [4.0, 0.4, 0.001, 0.01, 0.0] # Boundaries # strictly positive boundaries = [[0, np.inf]]*len(parms) # F boundaries[3] = [0, .9999999999999] boundaries[-1] = [-np.inf, np.inf] model_setup( modelid=6002, name="2D diffusion with triplet (confocal)", comp="T+2D", mtype="Confocal (Gaussian) and triplet", fctn=CF_Gxy_T_gauss, par_labels=[ u"n", u"τ_diff [ms]", u"τ_trip [ms]", u"T", u"offset"], par_values=parms, par_vary=[True, True, True, True, False], par_boundaries=boundaries, par_constraints=[[2, "<", 1]], par_hr_labels=[ u"n", u"τ_diff [ms]", u"τ_trip [µs]", u"T", u"offset"], par_hr_factors=[1., 1., 1000., 1., 1.], supplementary_method=supplements ) pycorrfit-1.1.7/pycorrfit/models/MODEL_TIRF_3D2Dkin_Ries.py0000775000372000037200000003564013554642611024167 0ustar travistravis00000000000000""" This file contains a TIR-FCS kineteics model function according to: "Total Internal Reflection Fluorescence Correlation Spectroscopy: Effects of Lateral Diffusion and Surface-Generated Fluorescence" Jonas Ries, Eugene P. Petrov, and Petra Schwille Biophysical Journal, Volume 95, July 2008, 390–399 """ import numpy as np import scipy.special as sps import numpy.lib.scimath as nps def wixi(x): """ Complex Error Function (Faddeeva/Voigt). w(i*x) = exp(x**2) * ( 1-erf(x) ) This function is called by other functions within this module. We are using the scipy.special.wofz module which calculates w(z) = exp(-z**2) * ( 1-erf(-iz) ) z = i*x """ z = x*1j wixi = sps.wofz(z) # We should have a real solution. Make sure nobody complains about # some zero-value imaginary numbers. return np.real_if_close(wixi) # Lateral correlation function def CF_gxy_square(parms, tau): u""" 2D free diffusion measured with a square pinhole. For the square pinhole, the correlation function can readily be calculated for a TIR-FCS setup. This function is called by other functions within this module. Attention: This is NOT g2D (or gCC), the non normalized correlation function. g2D = gxy * eta**2 * Conc, where eta is the molecular brightness, Conc the concentration and gxy is this function. *parms* - a list of parameters. Parameters (parms[i]): [0] D Diffusion coefficient [1] sigma lateral size of the point spread function sigma = simga_0 * lambda / NA [2] a side size of the square pinhole *tau* - lag time Returns: Nonnormalized Lateral correlation function w/square pinhole. """ D = parms[0] sigma = parms[1] a = parms[2] var1 = sigma**2+D*tau AA = 2*np.sqrt(var1)/(a**2*np.sqrt(np.pi)) BB = np.exp(-a**2/(4*(var1))) - 1 CC = sps.erf(a/(2*np.sqrt(var1)))/a # gx = AA*BB+CC # gxy = gx**2 return (AA*BB+CC)**2 def CF_gz_CC(parms, tau, wixi=wixi): u""" Axial (1D) diffusion in a TIR-FCS setup. From Two species (bound/unbound) this is the bound part. This function is called by other functions within this module. *parms* - a list of parameters. Parameters (parms[i]): [0] D_3D 3D Diffusion coefficient (species A) [1] D_2D 2D Diffusion coefficient of bound species C [2] sigma lateral size of the point spread function sigma = simga_0 * lambda / NA [3] a side size of the square pinhole [4] d_eva evanescent decay length (decay to 1/e) [5] Conc_3D 3-dimensional concentration of species A [6] Conc_2D 2-dimensional concentration of species C [7] eta_3D molecular brightness of species A [8] eta_2D molecular brightness of species C [9] k_a Surface association rate constant [10] k_d Surface dissociation rate constant *tau* - lag time """ D = parms[0] #D_2D = parms[1] #sigma = parms[2] # a = parms[3] #d_eva = parms[4] Conc_3D = parms[5] # ligand concentration in solution Conc_2D = parms[6] eta_3D = parms[7] eta_2D = parms[8] k_a = parms[9] k_d = parms[10] # Define some other constants: K = k_a/k_d # equilibrium constant Beta = 1/(1 + K*Conc_3D) # This is wrong in the Ries paper #Re = D / d_eva**2 Rt = D * (Conc_3D / (Beta * Conc_2D))**2 Rr = k_a * Conc_3D + k_d # Define even more constants: sqrtR1 = -Rr/(2*nps.sqrt(Rt)) + nps.sqrt(Rr**2/(4*Rt) - Rr) sqrtR2 = -Rr/(2*nps.sqrt(Rt)) - nps.sqrt(Rr**2/(4*Rt) - Rr) R1 = sqrtR1 ** 2 R2 = sqrtR2 ** 2 # Calculate return function A1 = eta_2D * Conc_2D / (eta_3D * Conc_3D) * Beta A2 = sqrtR1 * wixi(-nps.sqrt(tau*R2)) - sqrtR2 * wixi(-nps.sqrt(tau*R1)) A3 = sqrtR1 - sqrtR2 Sol = A1 * A2 / A3 # There are some below numerical errors-imaginary numbers. # We do not want them. return np.real_if_close(Sol) def CF_gz_AC(parms, tau, wixi=wixi): u""" Axial (1D) diffusion in a TIR-FCS setup. From Two species (bound/unbound) this is the cross correlation part. This function is called by other functions within this module. *parms* - a list of parameters. Parameters (parms[i]): [0] D_3D 3D Diffusion coefficient (species A) [1] D_2D 2D Diffusion coefficient of bound species C [2] sigma lateral size of the point spread function sigma = simga_0 * lambda / NA [3] a side size of the square pinhole [4] d_eva evanescent decay length (decay to 1/e) [5] Conc_3D 3-dimensional concentration of species A [6] Conc_2D 2-dimensional concentration of species C [7] eta_3D molecular brightness of species A [8] eta_2D molecular brightness of species C [9] k_a Surface association rate constant [10] k_d Surface dissociation rate constant *tau* - lag time """ D = parms[0] #D_2D = parms[1] #sigma = parms[2] # a = parms[3] d_eva = parms[4] Conc_3D = parms[5] # ligand concentration in solution Conc_2D = parms[6] eta_3D = parms[7] eta_2D = parms[8] k_a = parms[9] k_d = parms[10] # Define some other constants: K = k_a/k_d # equilibrium constant Beta = 1/(1 + K*Conc_3D) Re = D / d_eva**2 Rt = D * (Conc_3D / (Beta * Conc_2D))**2 Rr = k_a * Conc_3D + k_d # Define even more constants: sqrtR1 = -Rr/(2*nps.sqrt(Rt)) + nps.sqrt(Rr**2/(4*Rt) - Rr) sqrtR2 = -Rr/(2*nps.sqrt(Rt)) - nps.sqrt(Rr**2/(4*Rt) - Rr) R1 = sqrtR1 ** 2 R2 = sqrtR2 ** 2 # And even more more: sqrtR3 = sqrtR1 + nps.sqrt(Re) sqrtR4 = sqrtR2 + nps.sqrt(Re) #R3 = sqrtR3 **2 #R4 = sqrtR4 **2 # Calculate return function A1 = eta_2D * Conc_2D * k_d / (eta_3D * Conc_3D) A2 = sqrtR4*wixi(-nps.sqrt(tau*R1)) - sqrtR3*wixi(-nps.sqrt(tau*R2)) A3 = (sqrtR1 - sqrtR2) * wixi(nps.sqrt(tau*Re)) A4 = (sqrtR1 - sqrtR2) * sqrtR3 * sqrtR4 Solution = A1 * (A2 + A3) / A4 # There are some below numerical errors-imaginary numbers. # We do not want them. return np.real_if_close(Solution) def CF_gz_AA(parms, tau, wixi=wixi): u""" Axial (1D) diffusion in a TIR-FCS setup. From Two species (bound/unbound) this is the unbound part. This function is called by other functions within this module. *parms* - a list of parameters. Parameters (parms[i]): [0] D_3D 3D Diffusion coefficient (species A) [1] D_2D 2D Diffusion coefficient of bound species C [2] sigma lateral size of the point spread function sigma = simga_0 * lambda / NA [3] a side size of the square pinhole [4] d_eva evanescent decay length (decay to 1/e) [5] Conc_3D 3-dimensional concentration of species A [6] Conc_2D 2-dimensional concentration of species C [7] eta_3D molecular brightness of species A [8] eta_2D molecular brightness of species C [9] k_a Surface association rate constant [10] k_d Surface dissociation rate constant *tau* - lag time """ D = parms[0] #D_2D = parms[1] #sigma = parms[2] # a = parms[3] d_eva = parms[4] Conc_3D = parms[5] # ligand concentration in solution Conc_2D = parms[6] eta_3D = parms[7] eta_2D = parms[8] k_a = parms[9] k_d = parms[10] # Define some other constants: d = d_eva K = k_a/k_d # equilibrium constant Beta = 1/(1 + K*Conc_3D) Re = D / d_eva**2 Rt = D * (Conc_3D / (Beta * Conc_2D))**2 Rr = k_a * Conc_3D + k_d # Define even more constants: sqrtR1 = -Rr/(2*nps.sqrt(Rt)) + nps.sqrt(Rr**2/(4*Rt) - Rr) sqrtR2 = -Rr/(2*nps.sqrt(Rt)) - nps.sqrt(Rr**2/(4*Rt) - Rr) R1 = sqrtR1 ** 2 R2 = sqrtR2 ** 2 # And even more more: sqrtR3 = sqrtR1 + nps.sqrt(Re) sqrtR4 = sqrtR2 + nps.sqrt(Re) R3 = sqrtR3 ** 2 R4 = sqrtR4 ** 2 # Calculate return function Sum1 = d * nps.sqrt(Re*tau/np.pi) Sum2 = -d/2*(2*tau*Re - 1) * wixi(np.sqrt(tau*Re)) Sum3Mult1 = - eta_2D * Conc_2D * k_d / (eta_3D * Conc_3D * (sqrtR1 - sqrtR2)) S3M2S1M1 = sqrtR1/R3 S3M2S1M2S1 = wixi(-nps.sqrt(tau*R1)) + -2*nps.sqrt(tau*R3/np.pi) S3M2S1M2S2 = (2*tau*sqrtR1*nps.sqrt(Re) + 2*tau*Re - 1) * \ wixi(nps.sqrt(tau*Re)) S3M2S2M1 = -sqrtR2/R4 S3M2S2M2S1 = wixi(-nps.sqrt(tau*R2)) + -2*nps.sqrt(tau*R4/np.pi) S3M2S2M2S2 = (2*tau*sqrtR2*nps.sqrt(Re) + 2*tau*Re - 1) * \ wixi(nps.sqrt(tau*Re)) Sum3 = Sum3Mult1 * (S3M2S1M1 * (S3M2S1M2S1 + S3M2S1M2S2) + S3M2S2M1 * (S3M2S2M2S1 + S3M2S2M2S2)) Sum = Sum1 + Sum2 + Sum3 # There are some below numerical errors-imaginary numbers. # We do not want them. return np.real_if_close(Sum) # 3D-2D binding/unbinding TIRF def CF_Gxyz_TIR_square_ubibi(parms, tau, gAAz=CF_gz_AA, gACz=CF_gz_AC, gCCz=CF_gz_CC, gxy=CF_gxy_square): u""" Two-component two- and three-dimensional diffusion with a square-shaped lateral detection area taking into account the size of the point spread function; and an exponential decaying profile in axial direction. This model covers binding and unbinding kintetics. *parms* - a list of parameters. Parameters (parms[i]): [0] D_3D Diffusion coefficient of freely diffusing species A [1] D_2D Diffusion coefficient of bound species C [2] σ Lateral size of the point spread function σ = σ₀ * λ / NA [3] a Side size of the square-shaped detection area [4] d_eva Evanescent decay constant [5] C_3D Concentration of species A in observation volume [6] C_2D Concentration of species C in observation area [7] η_3D Molecular brightness of species A [8] η_2D Molecular brightness of species C [9] k_a Surface association rate constant [10] k_d Surface dissociation rate constant *tau* - lag time Returns: 3D correlation function for TIR-FCS w/square pinhole and surface binding/unbinding. Model introduced in: Jonas Ries, Eugene P. Petrov, and Petra Schwille Total Internal Reflection Fluorescence Correlation Spectroscopy: Effects of Lateral Diffusion and Surface-Generated Fluorescence Biophysical Journal, Vol.95, July 2008, 390–399 """ D_3D = parms[0] D_2D = parms[1] sigma = parms[2] a = parms[3] kappa = 1/parms[4] Conc_3D = parms[5] Conc_2D = parms[6] eta_3D = parms[7] eta_2D = parms[8] #k_a = parms[9] #k_d = parms[10] # We now need to copmute a real beast: # Inter species non-normalized correlation functions # gAA = gAAz * gxy(D_3D) # gAC = gACz * np.sqrt ( gxy(D_3D) * gxy(D_2D) ) # gCC = gCCz * gxy(D_2D) # Nonnormalized correlation function # g = eta_3D * Conc_3D * ( gAA + 2*gAC + gCC ) # Expectation value of fluorescence signal # F = eta_3D * Conc_3D / kappa + eta_2D * Conc_2D # Normalized correlation function # G = g / F**2 ## # Inter species non-normalized correlation functions # The gijz functions take the same parameters as this function # The gxy function needs two different sets of parameters, depending # on the diffusion constant used. # [0] D: Diffusion coefficient # [1] sigma: lateral size of the point spread function # [3] a: side size of the square pinhole parms_xy_2D = [D_2D, sigma, a] parms_xy_3D = [D_3D, sigma, a] # Here we go. gAA = gAAz(parms, tau) * gxy(parms_xy_3D, tau) gAC = gACz(parms, tau) * nps.sqrt(gxy(parms_xy_3D, tau) * gxy(parms_xy_2D, tau)) gCC = gCCz(parms, tau) * gxy(parms_xy_2D, tau) # Nonnormalized correlation function g = eta_3D * Conc_3D * (gAA + 2*gAC + gCC) # Expectation value of fluorescence signal F = eta_3D * Conc_3D / kappa + eta_2D * Conc_2D # Normalized correlation function G = g / F**2 # There are some below numerical errors-imaginary numbers. # We do not want them. return G.real #FNEW = eta_3D * Conc_3D / kappa #GNEW = eta_3D * Conc_3D * gCCz(parms, tau) / FNEW**2 # return GNEW.real # 3D-2D binding Model TIR m_tir_3d_2d_ubib6021 = [6021, u"3D+2D+kin", "Surface binding and unbinding, 3D TIR", CF_Gxyz_TIR_square_ubibi] labels_6021 = [u"D_3D [10 µm²/s]", u"D_2D [10 µm²/s]", u"σ [100 nm]", u"a [100 nm]", u"d_eva [100 nm]", u"C_3D [1000 /µm³]", u"C_2D[100 /µm²]", u"η_3D", u"η_2D", u"k_a [µm³/s]", u"k_d [10³ /s]" ] values_6021 = [ 9.0, # D_3D [10 µm²/s] 0.0, # D_2D [10 µm²/s] 2.3, # σ [100 nm] 7.50, # a [100 nm] 1.0, # d_eva [100 nm] 0.01, # conc.3D [1000 /µm³] 0.03, # conc.2D [100 /µm²] 1, # η_3D 1, # η_2D 0.00001, # k_a [µm³/s] 0.000064 # k_d [10³ /s] ] valuestofit_6021 = [False, True, False, False, False, False, True, False, False, False, False] # For user comfort we add values that are human readable. # Theese will be used for output that only humans can read. labels_human_readable_6021 = [ u"D_3D [µm²/s]", u"D_2D [µm²/s]", u"σ [nm]", u"a [nm]", u"d_eva [nm]", u"C_3D [1/µm³]", u"C_2D [1/µm²]", u"η_3D", u"η_2D", u"k_a [µm³/s]", u"k_d [1/s]" ] values_factor_human_readable_6021 = [10, # "D_3D [µm²/s]", 10, # D_2D [10 µm²/s] 100, # σ [100 nm] 100, # a [100 nm] 100, # d_eva [100 nm] 1000, # conc.3D [1000 /µm³] 100, # conc.2D [100 /µm²] 1, # η_3D 1, # η_2D 1, # k_a [µm³/s] 1000 # k_d [10³ /s] ] parms_6021 = [labels_6021, values_6021, valuestofit_6021, labels_human_readable_6021, values_factor_human_readable_6021] boundaries = [[0, np.inf]] * len(values_6021) model1 = dict() model1["Parameters"] = parms_6021 model1["Definitions"] = m_tir_3d_2d_ubib6021 model1["Boundaries"] = boundaries model1["Constraints"] = [[1, "<", 0]] Modelarray = [model1] pycorrfit-1.1.7/pycorrfit/models/model_confocal_t_2d_2d.py0000664000372000037200000001031513554642611024531 0ustar travistravis00000000000000import numpy as np from .control import model_setup from .cp_confocal import twod from .cp_triplet import trip from .cp_mix import double_pnum # 2D + 2D + Triplet Gauß # Model 6031 def CF_Gxyz_gauss_2D2DT(parms, tau): u""" Two-component, two-dimensional diffusion with a Gaussian laser profile, including a triplet component. The triplet factor takes into account blinking according to triplet states of excited molecules. Set *T* or *τ_trip* to 0, if no triplet component is wanted. particle1 = F₁/(1+τ/τ₁) particle2 = α²*(1-F₁)/(1+τ/τ₂) triplet = 1 + T/(1-T)*exp(-τ/τ_trip) norm = (F₁ + α*(1-F₁))² G = 1/n*(particle1 + particle2)*triplet/norm + offset *parms* - a list of parameters. Parameters (parms[i]): [0] n Effective number of particles in confocal area (n = n₁+n₂) [1] τ₁ Diffusion time of particle species 1 [2] τ₂ Diffusion time of particle species 2 [3] F₁ Fraction of molecules of species 1 (n₁ = n*F₁) 0 <= F₁ <= 1 [4] α Relative molecular brightness of particle 2 compared to particle 1 (α = q₂/q₁) [5] τ_trip Characteristic residence time in triplet state [6] T Fraction of particles in triplet (non-fluorescent) state 0 <= T < 1 [7] offset *tau* - lag time """ n = parms[0] taud1 = parms[1] taud2 = parms[2] F = parms[3] alpha = parms[4] tautrip = parms[5] T = parms[6] off = parms[7] g = double_pnum(n=n, F1=F, alpha=alpha, comp1=twod, comp2=twod, kwargs1={"tau": tau, "taudiff": taud1}, kwargs2={"tau": tau, "taudiff": taud2}, ) tr = trip(tau=tau, T=T, tautrip=tautrip) G = off + g*tr return G def supplements(parms, countrate=None): u"""Supplementary parameters: [8] n₁ = n*F₁ Particle number of species 1 [9] n₂ = n*(1-F₁) Particle number of species 2 """ # We can only give you the effective particle number n = parms[0] F1 = parms[3] Info = list() # The enumeration of these parameters is very important for # plotting the normalized curve. Countrate must come out last! Info.append([u"n\u2081", n*F1]) Info.append([u"n\u2082", n*(1.-F1)]) if countrate is not None: # CPP cpp = countrate/n Info.append(["cpp [kHz]", cpp]) return Info parms = [ 25, # n 5, # taud1 1000, # taud2 0.5, # F 1.0, # alpha 0.001, # tautrip 0.01, # T 0.0 # offset ] # Boundaries # strictly positive boundaries = [[0, np.inf]]*len(parms) # F boundaries[3] = [0, .9999999999999] # T boundaries[6] = [0, .9999999999999] boundaries[-1] = [-np.inf, np.inf] model_setup( modelid=6031, name="Separate 2D diffusion with triplet (confocal)", comp="T+2D+2D", mtype="Confocal (Gaussian) and triplet", fctn=CF_Gxyz_gauss_2D2DT, par_labels=[ u"n", u"τ"+u"\u2081"+u" [ms]", u"τ"+u"\u2082"+u" [ms]", u"F"+u"\u2081", u"\u03b1"+u" (q"+u"\u2082"+"/q"+u"\u2081"+")", u"τ_trip [ms]", u"T", u"offset" ], par_values=parms, par_vary=[True, True, True, True, False, False, False, False], par_boundaries=boundaries, par_constraints=[[2, ">", 1], [5, "<", 1]], par_hr_labels=[ u"n", u"τ"+u"\u2081"+u" [ms]", u"τ"+u"\u2082"+u" [ms]", u"F"+u"\u2081", u"\u03b1"+" (q"+u"\u2082"+"/q"+u"\u2081"+")", u"τ_trip [µs]", u"T", u"offset" ], par_hr_factors=[ 1., # "n", 1., # "τ"+u"\u2081"+" [ms]", 1., # "τ"+u"\u2082"+" [ms]", 1., # "F"+u"\u2081", 1., # u"\u03b1"+" (q"+u"\u2082"+"/q"+u"\u2081"+")", 1000., # "τ_trip [µs]", 1., # "T", 1. # "offset" ], supplementary_method=supplements ) pycorrfit-1.1.7/pycorrfit/models/model_confocal_tt_2d_2d.py0000664000372000037200000001127713554642611024725 0ustar travistravis00000000000000import numpy as np from .control import model_setup from .cp_confocal import twod from .cp_triplet import trip from .cp_mix import double_pnum # 2D + 2D + TT Gauß # Model 6044 def CF_Gxy_gauss_2D2DTT(parms, tau): u""" Two-component two-dimensional free diffusion with a Gaussian laser profile, including two triplet components. The triplet factor takes into account a blinking term. Set *T* or *τ_trip* to 0, if no triplet component is wanted. particle1 = F₁/(1+τ/τ₁) particle2 = α²*(1-F₁)/(1+τ/τ₂) triplet1 = 1 + T₁/(1-T₁)*exp(-τ/τ_trip₁) triplet2 = 1 + T₂/(1-T₂)*exp(-τ/τ_trip₂) norm = (F₁ + α*(1-F₁))² G = 1/n*(particle1 + particle2)*triplet1*triplet2/norm + offset *parms* - a list of parameters. Parameters (parms[i]): [0] n Effective number of particles in confocal volume (n = n₁+n₂) [1] τ₁ Diffusion time of particle species 1 [2] τ₂ Diffusion time of particle species 2 [3] F₁ Fraction of molecules of species 1 (n₁ = n*F₁) 0 <= F₁ <= 1 [4] α Relative molecular brightness of particle 2 compared to particle 1 (α = q₂/q₁) [5] τ_trip₁ Characteristic residence time in triplet state [6] T₁ Fraction of particles in triplet (non-fluorescent) state 0 <= T < 1 [7] τ_trip₂ Characteristic residence time in triplet state [8] T₂ Fraction of particles in triplet (non-fluorescent) state 0 <= T < 1 [9] offset *tau* - lag time """ n = parms[0] taud1 = parms[1] taud2 = parms[2] F = parms[3] alpha = parms[4] tautrip1 = parms[5] T1 = parms[6] tautrip2 = parms[7] T2 = parms[8] off = parms[9] g = double_pnum(n=n, F1=F, alpha=alpha, comp1=twod, comp2=twod, kwargs1={"tau": tau, "taudiff": taud1}, kwargs2={"tau": tau, "taudiff": taud2}, ) tr1 = trip(tau=tau, T=T1, tautrip=tautrip1) tr2 = trip(tau=tau, T=T2, tautrip=tautrip2) G = off + g * tr1 * tr2 return G def supplements(parms, countrate=None): u"""Supplementary parameters: [10] n₁ = n*F₁ Particle number of species 1 [11] n₂ = n*(1-F₁) Particle number of species 2 """ # We can only give you the effective particle number n = parms[0] F1 = parms[3] Info = list() # The enumeration of these parameters is very important for # plotting the normalized curve. Countrate must come out last! Info.append([u"n\u2081", n*F1]) Info.append([u"n\u2082", n*(1.-F1)]) if countrate is not None: # CPP cpp = countrate/n Info.append(["cpp [kHz]", cpp]) return Info parms = [ 25, # n 5, # taud1 1000, # taud2 0.5, # F 1.0, # alpha 0.001, # tautrip1 0.01, # T1 0.002, # tautrip2 0.01, # T2 0.0 # offset ] # Boundaries # strictly positive boundaries = [[0, np.inf]]*len(parms) # F boundaries[3] = [0, .9999999999999] # T boundaries[6] = [0, .9999999999999] boundaries[8] = [0, .9999999999999] boundaries[-1] = [-np.inf, np.inf] model_setup( modelid=6044, name="Separate 2D diffusion with double triplet (confocal)", comp="T+T+2D+2D", mtype="Confocal (Gaussian) with double triplet", fctn=CF_Gxy_gauss_2D2DTT, par_labels=[ u"n", u"τ"+u"\u2081"+" [ms]", u"τ"+u"\u2082"+" [ms]", u"F"+u"\u2081", u"\u03b1"+" (q"+u"\u2082"+"/q"+u"\u2081"+")", u"τ_trip₁ [ms]", u"T₁", u"τ_trip₂ [ms]", u"T₂", u"offset" ], par_values=parms, par_vary=[True, True, True, True, False, False, False, False, False, False], par_boundaries=boundaries, par_constraints=[[2, ">", 1], [5, "<", 1], [7, ">", 5]], par_hr_labels=[ u"n", u"τ₁ [ms]", u"τ₂ [ms]", u"F₁", u"\u03b1"+u" (q₂/q₁)", u"τ_trip₁ [µs]", u"T₁", u"τ_trip₂ [µs]", u"T₂", u"offset" ], par_hr_factors=[ 1., # n 1., # taud1 1., # taud2 1., # F 1., # alpha 1000., # tautrip1 [µs] 1., # T1 1000., # tautrip2 [µs] 1., # T2 1. # offset ], supplementary_method=supplements ) pycorrfit-1.1.7/pycorrfit/models/MODEL_TIRF_gaussian_3D2D.py0000775000372000037200000001474713554642611024402 0ustar travistravis00000000000000import numpy as np import scipy.special as sps def wixi(x): """ Complex Error Function (Faddeeva/Voigt). w(i*x) = exp(x**2) * ( 1-erf(x) ) This function is called by other functions within this module. We are using the scipy.special.wofz module which calculates w(z) = exp(-z**2) * ( 1-erf(-iz) ) z = i*x """ z = x*1j wixi = sps.wofz(z) # We should have a real solution. Make sure nobody complains about # some zero-value imaginary numbers. return np.real_if_close(wixi) # 3D + 2D + T # model 6033 def CF_Gxyz_3d2dT_gauss(parms, tau): u""" Two-component, two- and three-dimensional diffusion with a Gaussian lateral detection profile and an exponentially decaying profile in axial direction, including a triplet component. The triplet factor takes into account blinking according to triplet states of excited molecules. Set *T* or *τ_trip* to 0, if no triplet component is wanted. kappa = 1/d_eva x = sqrt(D_3D*τ)*kappa w(i*x) = exp(x²)*erfc(x) gz = kappa * [ sqrt(D_3D*τ/pi) + (1 - 2*D_3D*τ*kappa²)/(2*kappa) * w(i*x) ] g2D3D = 1 / [ 1+4*D_3D*τ/r₀² ] particle3D = α*F * g2D3D * gz particle2D = (1-F)/ (1+4*D_2D*τ/r₀²) triplet = 1 + T/(1-T)*exp(-τ/τ_trip) norm = (1-F + α*F)² G = 1/n*(particle2D + particle3D)*triplet/norm + offset *parms* - a list of parameters. Parameters (parms[i]): [0] n Effective number of particles in confocal volume (n = n2D+n3D) [1] D_2D Diffusion coefficient of surface bound particles [2] D_3D Diffusion coefficient of freely diffusing particles [3] F Fraction of molecules of the freely diffusing species (n3D = n*F), 0 <= F <= 1 [4] r₀ Lateral extent of the detection volume [5] d_eva Evanescent field depth [6] α Relative molecular brightness of freely diffusing compared to surface bound particles (α = q3D/q2D) [7] τ_trip Characteristic residence time in triplet state [8] T Fraction of particles in triplet (non-fluorescent) state 0 <= T < 1 [9] offset *tau* - lag time """ n = parms[0] D2D = parms[1] D3D = parms[2] F = parms[3] r0 = parms[4] deva = parms[5] alpha = parms[6] tautrip = parms[7] T = parms[8] off = parms[9] # 2D species taud2D = r0**2/(4*D2D) particle2D = (1-F) / (1+tau/taud2D) # 3D species taud3D = r0**2/(4*D3D) # 2D gauss component g2D3D = 1 / ((1.+tau/taud3D)) # 1d TIR component # Axial correlation kappa = 1/deva x = np.sqrt(D3D*tau)*kappa w_ix = wixi(x) # Gz = 1/N1D * gz = kappa / Conc.1D * gz gz = kappa * (np.sqrt(D3D*tau/np.pi) - (2*D3D*tau*kappa**2 - 1)/(2*kappa) * w_ix) particle3D = alpha**2*F * g2D3D * gz # triplet if tautrip == 0 or T == 0: triplet = 1 else: triplet = 1 + T/(1-T) * np.exp(-tau/tautrip) # Norm norm = (1-F + alpha*F)**2 # Correlation function G = 1/n*(particle2D + particle3D)*triplet/norm return G + off def get_boundaries(parms): # strictly positive boundaries = [[0, np.inf]]*len(parms) # F boundaries[3] = [0, .9999999999999] # T boundaries[8] = [0, .9999999999999] boundaries[-1] = [-np.inf, np.inf] return boundaries def MoreInfo(parms, countrate=None): u"""Supplementary parameters: Effective number of freely diffusing particles in 3D: [10] n3D = n*F Effective number particles diffusing on 2D surface: [11] n2D = n*(1-F) Value of the correlation function at lag time zero: [12] G(0) Effective measurement volume: [13] V_eff [al] = π * r₀² * d_eva Concentration of the 2D species: [14] C_2D [1/µm²] = n2D / ( π * r₀² ) Concentration of the 3D species: [15] C_3D [nM] = n3D/V_eff """ # We can only give you the effective particle number n = parms[0] # D2D=parms[1] # D3D=parms[2] F = parms[3] r0 = parms[4] deva = parms[5] # alpha=parms[6] Info = list() # The enumeration of these parameters is very important for # plotting the normalized curve. Countrate must come out last! Info.append([u"n3D", n*F]) Info.append([u"n2D", n*(1.-F)]) # Detection area: Veff = np.pi * r0**2 * deva C3D = n*F / Veff C2D = n*(1-F) / (np.pi*r0**2) # Correlation function at tau = 0 G_0 = CF_Gxyz_3d2dT_gauss(parms, 0) Info.append([u"G(0)", G_0]) Info.append([u"V_eff [al]", Veff]) Info.append([u"C_2D [1/µm²]", C2D * 100]) Info.append([u"C_3D [nM]", C3D * 10000/6.0221415]) if countrate is not None: # CPP cpp = countrate/n Info.append([u"cpp [kHz]", cpp]) return Info # 3D + 3D + T model gauss m_gauss_3d_2d_t = [6033, "T+3D+2D", "Separate 3D and 2D diffusion + triplet w/ TIR", CF_Gxyz_3d2dT_gauss] labels = [u"n", u"D_2D [10 µm²/s]", u"D_3D [10 µm²/s]", u"F_3D", u"r₀ [100 nm]", u"d_eva [100 nm]", u"\u03b1"+" (q_3D/q_2D)", u"τ_trip [ms]", u"T", u"offset" ] values = [ 25, # n 0.51, # D2D 25.1, # D3D 0.45, # F3D 9.44, # r0 1.0, # deva 1.0, # alpha 0.001, # tautrip 0.01, # T 0.0 # offset ] # Human readable stuff labelshr = [u"n", u"D_2D [µm²/s]", u"D_3D [µm²/s]", u"F_3D", u"r₀ [nm]", u"d_eva [nm]", u"\u03b1"+u" (q_3D/q_2D)", u"τ_trip [µs]", u"T", u"offset" ] valueshr = [ 1., # n 10., # D2D 10., # D3D 1., # F3D 100., # r0 100., # deva 1., # alpha 1000., # tautrip 1., # T 1. # offset ] valuestofit = [True, True, True, True, False, False, False, False, False, False] parms = [labels, values, valuestofit, labelshr, valueshr] model1 = dict() model1["Parameters"] = parms model1["Definitions"] = m_gauss_3d_2d_t model1["Boundaries"] = get_boundaries(values) model1["Supplements"] = MoreInfo model1["Constraints"] = [[2, ">", 1]] Modelarray = [model1] pycorrfit-1.1.7/pycorrfit/models/model_confocal_t_3d_3d_3d.py0000664000372000037200000001327413554642611025130 0ustar travistravis00000000000000import numpy as np from .control import model_setup from .cp_confocal import threed from .cp_triplet import trip from .cp_mix import triple_pnum def CF_Gxyz_gauss_3D3D3DT(parms, tau): u""" Three-component three-dimensional free diffusion with a Gaussian laser profile, including a triplet component. The triplet factor takes into account a blinking term. Set *T* or *τ_trip* to 0, if no triplet component is wanted. F₃ = 1-F₁-F₂ particle1 = F₁/( (1+τ/τ₁) * sqrt(1+τ/(τ₁*SP²))) particle2 = α₂₁² * F₂/( (1+τ/τ₂) * sqrt(1+τ/(τ₂*SP²))) particle3 = α₃₁² * F₃/( (1+τ/τ₃) * sqrt(1+τ/(τ₃*SP²))) triplet = 1 + T/(1-T)*exp(-τ/τ_trip) norm = (F₁ + α₂₁*F₂ + α₃₁*F₃)² G = 1/n*(particle1 + particle2 + particle3)*triplet/norm + offset *parms* - a list of parameters. Parameters (parms[i]): [0] n Effective number of particles in confocal volume (n = n₁+n₂+n₃) [1] τ₁ Diffusion time of particle species 1 [2] τ₂ Diffusion time of particle species 2 [3] τ₃ Diffusion time of particle species 3 [4] F₁ Fraction of molecules of species 1 (n₁ = n*F₁) 0 <= F₁ <= 1 [5] F₂ Fraction of molecules of species 2 (n₂ = n*F₂) 0 <= F₂ <= 1 [6] SP SP=z₀/r₀, Structural parameter, describes elongation of the confocal volume [7] α₂₁ Relative molecular brightness of particle 2 compared to particle 1 (α = q₂/q₁) [8] α₃₁ Relative molecular brightness of particle 3 compared to particle 1 (α = q₃/q₁) [9] τ_trip Characteristic residence time in triplet state [10] T Fraction of particles in triplet (non-fluorescent) state 0 <= T < 1 [11] offset *tau* - lag time """ n = parms[0] taud1 = parms[1] taud2 = parms[2] taud3 = parms[3] F1 = parms[4] F2 = parms[5] SP = parms[6] alpha21 = parms[7] alpha31 = parms[8] tautrip = parms[9] T = parms[10] off = parms[11] g = triple_pnum(n=n, F1=F1, F2=F2, alpha21=alpha21, alpha31=alpha31, comp1=threed, comp2=threed, comp3=threed, kwargs1={"tau": tau, "taudiff": taud1, "SP": SP}, kwargs2={"tau": tau, "taudiff": taud2, "SP": SP}, kwargs3={"tau": tau, "taudiff": taud3, "SP": SP}, ) tr = trip(tau=tau, T=T, tautrip=tautrip) G = off + g*tr return G def supplements(parms, countrate=None): u"""Supplementary parameters: [12] n₁ = n*F₁ Particle number of species 1 [13] n₂ = n*F₂ Particle number of species 2 [14] n₃ = n*F₃ Particle number of species 3 (F₃ = 1-F₁-F₂) """ # We can only give you the effective particle number n = parms[0] F1 = parms[4] F2 = parms[5] Info = list() # The enumeration of these parameters is very important for # plotting the normalized curve. Countrate must come out last! Info.append([u"n\u2081", n*F1]) Info.append([u"n\u2082", n*F2]) Info.append([u"n\u2083", n*(1-F1-F2)]) if countrate is not None: # CPP cpp = countrate/n Info.append(["cpp [kHz]", cpp]) return Info parms = [25, # n 5, # taud1 1000, # taud2 11000, # taud3 0.5, # F1 0.01, # F2 5, # SP 1.0, # alpha21 1.0, # alpha31 0.001, # tautrip 0.01, # T 0.0 # offset ] # Boundaries # strictly positive boundaries = [[0, np.inf]]*len(parms) # F boundaries[4] = [0, .9999999999999] boundaries[5] = [0, .9999999999999] # T boundaries[10] = [0, .9999999999999] boundaries[-1] = [-np.inf, np.inf] model_setup( modelid=6081, name="Threefold 3D diffusion with triplet (confocal)", comp="T+3D+3D+3D", mtype="Confocal (Gaussian) and triplet", fctn=CF_Gxyz_gauss_3D3D3DT, par_labels=[ u"n", u"τ"+u"\u2081"+" [ms]", u"τ"+u"\u2082"+" [ms]", u"τ"+u"\u2083"+" [ms]", u"F"+u"\u2081", u"F"+u"\u2082", u"SP", u"\u03b1\u2082\u2081", u"\u03b1\u2083\u2081", u"τ_trip [ms]", u"T", u"offset" ], par_values=parms, par_vary=[True, True, False, True, False, True, False, False, False, False, True, False], par_boundaries=boundaries, par_constraints=[[2, ">", 1], [3, ">", 2], [9, "<", 1], [5, 4, "<", "1"]], par_hr_labels=[ u"n", u"τ"+u"\u2081"+" [ms]", u"τ"+u"\u2082"+" [ms]", u"τ"+u"\u2083"+" [ms]", u"F"+u"\u2081", u"F"+u"\u2082", u"SP", u"\u03b1\u2082\u2081", u"\u03b1\u2083\u2081", u"τ_trip [µs]", u"T", u"offset" ], par_hr_factors=[ 1., # n 1., # taud1 1., # taud2 1., # taud3 1., # F1 1., # F2 1., # SP 1., # alpha21 1., # alpha31 1000., # tautrip [µs] 1., # T 1. # offset ], supplementary_method=supplements ) pycorrfit-1.1.7/pycorrfit/models/model_confocal_t_3d_2d.py0000664000372000037200000001065513554642611024541 0ustar travistravis00000000000000import numpy as np from .control import model_setup from .cp_confocal import twod, threed from .cp_triplet import trip from .cp_mix import double_pnum # 3D + 2D + T def CF_Gxyz_3d2dT_gauss(parms, tau): u""" Two-component, two- and three-dimensional diffusion with a Gaussian laser profile, including a triplet component. The triplet factor takes into account blinking according to triplet states of excited molecules. Set *T* or *τ_trip* to 0, if no triplet component is wanted. particle2D = (1-F)/ (1+τ/τ_2D) particle3D = α²*F/( (1+τ/τ_3D) * sqrt(1+τ/(τ_3D*SP²))) triplet = 1 + T/(1-T)*exp(-τ/τ_trip) norm = (1-F + α*F)² G = 1/n*(particle1 + particle2)*triplet/norm + offset *parms* - a list of parameters. Parameters (parms[i]): [0] n Effective number of particles in confocal volume (n = n2D+n3D) [1] τ_2D Diffusion time of surface bound particls [2] τ_3D Diffusion time of freely diffusing particles [3] F Fraction of molecules of the freely diffusing species (n3D = n*F), 0 <= F <= 1 [4] SP SP=z₀/r₀ Structural parameter, describes elongation of the confocal volume [5] α Relative molecular brightness of particle 3D compared to particle 2D (α = q3D/q2D) [6] τ_trip Characteristic residence time in triplet state [7] T Fraction of particles in triplet (non-fluorescent) state 0 <= T < 1 [8] offset *tau* - lag time """ n = parms[0] taud2D = parms[1] taud3D = parms[2] F = parms[3] SP = parms[4] alpha = parms[5] tautrip = parms[6] T = parms[7] off = parms[8] g = double_pnum(n=n, F1=1-F, alpha=alpha, comp1=twod, comp2=threed, kwargs1={"tau": tau, "taudiff": taud2D}, kwargs2={"tau": tau, "taudiff": taud3D, "SP": SP}, ) tr = trip(tau=tau, T=T, tautrip=tautrip) G = off + g*tr return G def supplements(parms, countrate=None): u"""Supplementary parameters: Effective number of freely diffusing particles in 3D solution: [9] n3D = n*F Effective number particles diffusing on 2D surface: [10] n2D = n*(1-F) """ # We can only give you the effective particle number n = parms[0] F3d = parms[3] Info = list() # The enumeration of these parameters is very important for # plotting the normalized curve. Countrate must come out last! Info.append([u"n3D", n*F3d]) Info.append([u"n2D", n*(1.-F3d)]) if countrate is not None: # CPP cpp = countrate/n Info.append([u"cpp [kHz]", cpp]) return Info parms = [ 25, # n 240, # taud2D 0.1, # taud3D 0.5, # F3D 7, # SP 1.0, # alpha 0.001, # tautrip 0.01, # T 0.0 # offset ] # Boundaries # strictly positive boundaries = [[0, np.inf]]*len(parms) # F boundaries[3] = [0, .9999999999999] # T boundaries[7] = [0, .9999999999999] boundaries[-1] = [-np.inf, np.inf] model_setup( modelid=6032, name="Separate 3D and 2D diffusion with triplet (confocal)", comp="T+3D+2D", mtype="Confocal (Gaussian) and triplet", fctn=CF_Gxyz_3d2dT_gauss, par_labels=[ u"n", u"τ_2D [ms]", u"τ_3D [ms]", u"F_3D", u"SP", u"\u03b1"+" (q_3D/q_2D)", u"τ_trip [ms]", u"T", u"offset" ], par_values=parms, par_vary=[True, True, True, True, False, False, False, False, False], par_boundaries=boundaries, par_constraints=[[2, "<", 1], [6, "<", 2]], par_hr_labels=[ u"n", u"τ_2D [ms]", u"τ_3D [ms]", u"F_3D", u"SP", u"\u03b1"+" (q_3D/q_2D)", u"τ_trip [µs]", u"T", u"offset" ], par_hr_factors=[ 1., # "n", 1., # "τ_2D [ms]", 1., # "τ_3D [ms]", 1., # "F_3D", 1., # "SP", 1., # u"\u03b1"+" (q_3D/q_2D)", 1000., # "τ_trip [µs]", 1., # "T", 1. # "offset" ], supplementary_method=supplements ) pycorrfit-1.1.7/pycorrfit/models/classes.py0000664000372000037200000001065113554642611021730 0ustar travistravis00000000000000import copy import numbers import warnings import numpy as np class Model(object): """General class for handling FCS fitting models""" def __init__(self, datadict): """datadict is an item in Modelarray""" self._parameters = datadict["Parameters"] self._definitions = datadict["Definitions"] if "Supplements" in list(datadict.keys()): self._supplements = datadict["Supplements"] else: self._supplements = lambda x, y: [] if "Boundaries" in list(datadict.keys()): self._boundaries = datadict["Boundaries"] else: # dummy verification function self._boundaries = [[-np.inf, np.inf]]*len(self._parameters[1]) if "Constraints" in list(datadict.keys()): # sort constraints such that the first value is always # larger than the last. newcc = [] for cc in datadict["Constraints"]: # check for integral numbers to avoid comparison to strings # in e.g. confocal t_3d_3d_3d model. if (isinstance(cc[0], numbers.Integral) and isinstance(cc[2], numbers.Integral) and cc[0] < cc[2]): if cc[1] == ">": cc = [cc[2], "<", cc[0]] elif cc[1] == "<": cc = [cc[2], ">", cc[0]] newcc.append(cc) self._constraints = newcc else: self._constraints = [] def __call__(self, parameters, tau): return self.function(parameters, tau) def __getitem__(self, key): """Emulate old list behavior of models""" return self._definitions[key] def __repr__(self): text = "Model {} - {}".format( self.id, self.description_short) return text def apply(self, parameters, tau): """ Apply the model with `parameters` and lag times `tau` """ return self.function(parameters, tau) @property def boundaries(self): return self._boundaries @property def constraints(self): """ fitting constraints """ return copy.copy(self._constraints) @property def components(self): """how many components does this model have""" return self._definitions[1] @property def default_values(self): """default fitting values""" return np.array(self._parameters[1]).copy() @property def default_variables(self): """indexes default variable fitting (bool)""" return np.array(self._parameters[2]).copy() @property def description_long(self): """long description""" return self._definitions[3].__doc__ @property def description_short(self): """short description""" return self._definitions[2] @property def function(self): return self._definitions[3] @property def func_supplements(self): return self._supplements @property def func_verification(self): warnings.warn( "`func_verification is deprecated: please do not use it!") return lambda x: x def get_supplementary_parameters(self, values, countrate=None): """ Compute additional information for the model Parameters ---------- values: list-like of same length as `self.default_values` parameters for the model countrate: float countrate in kHz """ return self.func_supplements(values, countrate) def get_supplementary_values(self, values, countrate=None): """ Returns only the values of self.get_supplementary_parameters Parameters ---------- values: list-like of same length as `self.default_values` parameters for the model countrate: float count rate in Hz """ out = list() for item in self.get_supplementary_parameters(values, countrate): out.append(item[1]) return out @property def id(self): return self._definitions[0] @property def name(self): return self.description_short @property def parameters(self): return self._parameters @property def type(self): if len(self._definitions) < 5: return None else: return self._definitions[4] pycorrfit-1.1.7/pycorrfit/models/cp_triplet.py0000664000372000037200000000030213554642611022430 0ustar travistravis00000000000000"""Triplet components""" import numpy as np def trip(tau, tautrip, T): if tautrip == 0 or T == 0: AA = 1 else: AA = 1 + T/(1-T) * np.exp(-tau / tautrip) return AA pycorrfit-1.1.7/pycorrfit/models/MODEL_TIRF_gaussian_3D3D.py0000775000372000037200000001567313554642611024402 0ustar travistravis00000000000000import numpy as np import scipy.special as sps def wixi(x): """ Complex Error Function (Faddeeva/Voigt). w(i*x) = exp(x**2) * ( 1-erf(x) ) This function is called by other functions within this module. We are using the scipy.special.wofz module which calculates w(z) = exp(-z**2) * ( 1-erf(-iz) ) z = i*x """ z = x*1j wixi = sps.wofz(z) # We should have a real solution. Make sure nobody complains about # some zero-value imaginary numbers. return np.real_if_close(wixi) # 3D + 3D + T # model 6034 def CF_Gxyz_3D3DT_gauss(parms, tau): u""" Two-component three-dimensional diffusion with a Gaussian lateral detection profile and an exponentially decaying profile in axial direction, including a triplet component. The triplet factor takes into account blinking according to triplet states of excited molecules. Set *T* or *τ_trip* to 0, if no triplet component is wanted. w(i*x) = exp(x²)*erfc(x) taud1 = r₀²/(4*D₁) κ = 1/d_eva x1 = sqrt(D₁*τ)*κ gz1 = κ * [ sqrt(D₁*τ/π) + (1 - 2*D₁*τ*κ)/(2*κ) * w(i*x1) ] g2D1 = 1 / [ 1+τ/taud1 ] particle1 = F₁ * g2D1 * gz1 taud2 = r₀²/(4*D₂) x2 = sqrt(D₂*τ)*κ gz2 = κ * [ sqrt(D₂*τ/π) + (1 - 2*D₂*τ*κ)/(2*κ) * w(i*x2) ] g2D2 = 1 / [ 1+τ/taud2 ] particle2 = α*(1-F₁) * g2D2 * gz2 triplet = 1 + T/(1-T)*exp(-τ/τ_trip) norm = (1-F₁ + α*F₁)² G = 1/n*(particle1 + particle2)*triplet/norm + offset *parms* - a list of parameters. Parameters (parms[i]): [0] n Effective number of particles in confocal volume (n = n₁+n₂) [1] D₁ Diffusion coefficient of species 1 [2] D₂ Diffusion coefficient of species 2 [3] F₁ Fraction of molecules of species 1 (n₁ = n*F₁) 0 <= F₁ <= 1 [4] r₀ Lateral extent of the detection volume [5] d_eva Evanescent field depth [6] α Relative molecular brightness of particle 2 compared to particle 1 (α = q₂/q₁) [7] τ_trip Characteristic residence time in triplet state [8] T Fraction of particles in triplet (non-fluorescent) state 0 <= T < 1 [9] offset *tau* - lag time """ n = parms[0] D1 = parms[1] D2 = parms[2] F = parms[3] r0 = parms[4] deva = parms[5] alpha = parms[6] tautrip = parms[7] T = parms[8] off = parms[9] kappa = 1/deva # 1st species tauD1 = r0**2/(4*D1) # 2D gauss component g2D1 = 1 / ((1.+tau/tauD1)) # 1d TIR component # Axial correlation x1 = np.sqrt(D1*tau)*kappa w_ix1 = wixi(x1) # Gz = 1/N1D * gz = kappa / Conc.1D * gz gz1 = kappa * (np.sqrt(D1*tau/np.pi) - (2*D1*tau*kappa**2 - 1)/(2*kappa) * w_ix1) particle1 = F * g2D1 * gz1 # 2nd species tauD2 = r0**2/(4*D2) # 2D gauss component g2D2 = 1 / ((1.+tau/tauD2)) # 1d TIR component # Axial correlation x2 = np.sqrt(D2*tau)*kappa w_ix2 = wixi(x2) # Gz = 1/N1D * gz = kappa / Conc.1D * gz gz2 = kappa * (np.sqrt(D2*tau/np.pi) - (2*D2*tau*kappa**2 - 1)/(2*kappa) * w_ix2) particle2 = alpha**2*(1-F) * g2D2 * gz2 # triplet if tautrip == 0 or T == 0: triplet = 1 else: triplet = 1 + T/(1-T) * np.exp(-tau/tautrip) # Norm norm = (F + alpha*(1-F))**2 # Correlation function G = 1/n*(particle1 + particle2)*triplet/norm return G + off def get_boundaries(parms): # strictly positive boundaries = [[0, np.inf]]*len(parms) # F boundaries[3] = [0, .9999999999999] # T boundaries[8] = [0, .9999999999999] # offset boundaries[-1] = [-np.inf, np.inf] return boundaries def MoreInfo(parms, countrate=None): u"""Supplementary parameters: Effective number of particle species 1: [10] n₁ = n*F₁ Effective number of particle species 2: [11] n₂ = n*(1-F₁) Value of the correlation function at lag time zero: [12] G(0) Effective measurement volume: [13] V_eff [al] = π * r₀² * d_eva Concentration of particle species 1: [14] C₁ [nM] = n₁/V_eff Concentration of particle species 2: [15] C₂ [nM] = n₂/V_eff """ # We can only give you the effective particle number n = parms[0] # D1=parms[1] # D2=parms[2] F = parms[3] r0 = parms[4] deva = parms[5] # alpha=parms[6] Info = list() # The enumeration of these parameters is very important for # plotting the normalized curve. Countrate must come out last! Info.append([u"n\u2081", n*F]) Info.append([u"n\u2082", n*(1.-F)]) # Detection area: Veff = np.pi * r0**2 * deva C1 = n*F / Veff C2 = n*(1-F) / Veff # Correlation function at tau = 0 G_0 = CF_Gxyz_3D3DT_gauss(parms, 0) Info.append(["G(0)", G_0]) Info.append(["V_eff [al]", Veff]) Info.append(["C"+u"\u2081"+" [nM]", C1 * 10000/6.0221415]) Info.append(["C"+u"\u2082"+" [nM]", C2 * 10000/6.0221415]) if countrate is not None: # CPP cpp = countrate/n Info.append(["cpp [kHz]", cpp]) return Info # 3D + 3D + T model gauss m_gauss_3d_3d_t = [6034, "T+3D+3D", "Combined 3D diffusion + triplet w/ TIR", CF_Gxyz_3D3DT_gauss] labels = [u"n", u"D"+u"\u2081"+u" [10 µm²/s]", u"D"+u"\u2082"+u" [10 µm²/s]", u"F"+u"\u2081", u"r₀ [100 nm]", u"d_eva [100 nm]", u"\u03b1"+" (q"+u"\u2082"+"/q"+u"\u2081"+")", u"τ_trip [ms]", u"T", u"offset" ] values = [ 25, # n 0.9, # D1 25.0, # D2 0.5, # F1 9.44, # r0 1.0, # deva 1.0, # alpha 0.001, # tautrip 0.01, # T 0.0 # offset ] # Human readable stuff labelshr = [u"n", u"D"+u"\u2081"+u" [µm²/s]", u"D"+u"\u2082"+u" [µm²/s]", u"F"+u"\u2081", u"r₀ [nm]", u"d_eva [nm]", u"\u03b1"+" (q"+u"\u2082"+"/q"+u"\u2081"+")", u"τ_trip [µs]", u"T", u"offset" ] valueshr = [ 1., # n 10., # D1 10., # D2 1., # F1 100., # r0 100., # deva 1., # alpha 1000., # tautrip 1., # T 1. # offset ] valuestofit = [True, True, True, True, False, False, False, False, False, False] parms = [labels, values, valuestofit, labelshr, valueshr] model1 = dict() model1["Parameters"] = parms model1["Definitions"] = m_gauss_3d_3d_t model1["Boundaries"] = get_boundaries(values) model1["Supplements"] = MoreInfo model1["Constraints"] = [[2, ">", 1]] Modelarray = [model1] pycorrfit-1.1.7/pycorrfit/models/model_confocal_tt_3d_2d.py0000664000372000037200000001207013554642611024716 0ustar travistravis00000000000000import numpy as np from .control import model_setup from .cp_confocal import twod, threed from .cp_triplet import trip from .cp_mix import double_pnum # 3D + 2D + TT Gauß # Model 6045 def CF_Gxyz_gauss_3D2DTT(parms, tau): u""" Two-component three-dimensional free diffusion with a Gaussian laser profile, including two triplet components. The triplet factor takes into account a blinking term. Set *T* or *τ_trip* to 0, if no triplet component is wanted. particle2D = (1-F)/ (1+τ/τ_2D) particle3D = α²*F/( (1+τ/τ_3D) * sqrt(1+τ/(τ_3D*SP²))) triplet1 = 1 + T₁/(1-T₁)*exp(-τ/τ_trip₁) triplet2 = 1 + T₂/(1-T₂)*exp(-τ/τ_trip₂) norm = (F₁ + α*(1-F₁))² G = 1/n*(particle2D + particle3D)*triplet1*triplet2/norm + offset *parms* - a list of parameters. Parameters (parms[i]): [0] n Effective number of particles in confocal volume (n = n2D+n3D) [1] τ_2D Diffusion time of surface bound particls [2] τ_3D Diffusion time of freely diffusing particles [3] F₁ Fraction of molecules of species 1 (n₁ = n*F₁) 0 <= F₁ <= 1 [4] SP SP=z₀/r₀, Structural parameter, describes elongation of the confocal volume [5] α Relative molecular brightness of particle 2 compared to particle 1 (α = q₂/q₁) [6] τ_trip₁ Characteristic residence time in triplet state [7] T₁ Fraction of particles in triplet (non-fluorescent) state 0 <= T < 1 [8] τ_trip₂ Characteristic residence time in triplet state [9] T₂ Fraction of particles in triplet (non-fluorescent) state 0 <= T < 1 [10] offset *tau* - lag time """ n = parms[0] taud2D = parms[1] taud3D = parms[2] F = parms[3] SP = parms[4] alpha = parms[5] tautrip1 = parms[6] T1 = parms[7] tautrip2 = parms[8] T2 = parms[9] off = parms[10] g = double_pnum(n=n, F1=1-F, alpha=alpha, comp1=twod, comp2=threed, kwargs1={"tau": tau, "taudiff": taud2D}, kwargs2={"tau": tau, "taudiff": taud3D, "SP": SP}, ) tr1 = trip(tau=tau, T=T1, tautrip=tautrip1) tr2 = trip(tau=tau, T=T2, tautrip=tautrip2) G = off + g * tr1 * tr2 return G def supplements(parms, countrate=None): u"""Supplementary parameters: Effective number of freely diffusing particles in 3D solution: [11] n3D = n*F Effective number particles diffusing on 2D surface: [12] n2D = n*(1-F) """ # We can only give you the effective particle number n = parms[0] F3d = parms[3] Info = list() # The enumeration of these parameters is very important for # plotting the normalized curve. Countrate must come out last! Info.append([u"n3D", n*F3d]) Info.append([u"n2D", n*(1.-F3d)]) if countrate is not None: # CPP cpp = countrate/n Info.append([u"cpp [kHz]", cpp]) return Info parms = [ 25, # n 240, # taud2D 0.1, # taud3D 0.5, # F3D 5, # SP 1.0, # alpha 0.001, # tautrip1 0.01, # T1 0.002, # tautrip2 0.01, # T2 0.0 # offset ] # Boundaries # strictly positive boundaries = [[0, np.inf]]*len(parms) # F boundaries[3] = [0, .9999999999999] # T boundaries[7] = [0, .9999999999999] boundaries[9] = [0, .9999999999999] boundaries[-1] = [-np.inf, np.inf] model_setup( modelid=6045, name="Separate 3D and 2D diffusion with double triplet (confocal)", comp="T+T+3D+2D", mtype="Confocal (Gaussian) with double triplet", fctn=CF_Gxyz_gauss_3D2DTT, par_labels=[ u"n", u"τ_2D [ms]", u"τ_3D [ms]", u"F_3D", u"SP", u"\u03b1"+" (q_3D/q_2D)", u"τ_trip₁ [ms]", u"T₁", u"τ_trip₂ [ms]", u"T₂", u"offset" ], par_values=parms, par_vary=[True, True, True, True, False, False, False, False, False, False, False], par_boundaries=boundaries, par_constraints=[[2, "<", 1], [6, "<", 2], [8, ">", 6]], par_hr_labels=[ u"n", u"τ_2D [ms]", u"τ_3D [ms]", u"F_3D", u"SP", u"\u03b1"+" (q_3D/q_2D)", u"τ_trip₁ [µs]", u"T₁", u"τ_trip₂ [µs]", u"T₂", u"offset" ], par_hr_factors=[ 1., # n 1., # "τ_2D [ms]", 1., # "τ_3D [ms]", 1., # "F_3D", 1., # "SP", 1., # u"\u03b1"+" (q_3D/q_2D)", 1000., # tautrip1 [µs] 1., # T1 1000., # tautrip2 [µs] 1., # T2 1. # offset ], supplementary_method=supplements ) pycorrfit-1.1.7/pycorrfit/models/model_confocal_2d_2d.py0000664000372000037200000000620513554642611024211 0ustar travistravis00000000000000import numpy as np from .control import model_setup from .cp_confocal import twod from .cp_mix import double_pnum # 2D + 2D Gauß # Model 6037 def CF_Gxyz_gauss_2D2D(parms, tau): u""" Two-component, two-dimensional diffusion with a Gaussian laser profile, including a triplet component. The triplet factor takes into account blinking according to triplet states of excited molecules. Set *T* or *τ_trip* to 0, if no triplet component is wanted. particle1 = F₁/(1+τ/τ₁) particle2 = α²*(1-F₁)/(1+τ/τ₂) norm = (F₁ + α*(1-F₁))² G = 1/n*(particle1 + particle2)/norm + offset *parms* - a list of parameters. Parameters (parms[i]): [0] n Effective number of particles in confocal area (n = n₁+n₂) [1] τ₁ Diffusion time of particle species 1 [2] τ₂ Diffusion time of particle species 2 [3] F₁ Fraction of molecules of species 1 (n₁ = n*F₁) 0 <= F₁ <= 1 [4] α Relative molecular brightness of particle 2 compared to particle 1 (α = q₂/q₁) [5] offset *tau* - lag time """ n = parms[0] taud1 = parms[1] taud2 = parms[2] F = parms[3] alpha = parms[4] off = parms[5] g = double_pnum(n=n, F1=F, alpha=alpha, comp1=twod, comp2=twod, kwargs1={"tau": tau, "taudiff": taud1}, kwargs2={"tau": tau, "taudiff": taud2}, ) G = off + g return G def supplements(parms, countrate=None): u"""Supplementary parameters: [6] n₁ = n*F₁ Particle number of species 1 [7] n₂ = n*(1-F₁) Particle number of species 2 """ # We can only give you the effective particle number n = parms[0] F1 = parms[3] Info = list() # The enumeration of these parameters is very important for # plotting the normalized curve. Countrate must come out last! Info.append([u"n\u2081", n*F1]) Info.append([u"n\u2082", n*(1.-F1)]) if countrate is not None: # CPP cpp = countrate/n Info.append(["cpp [kHz]", cpp]) return Info parms = [ 25, # n 5, # taud1 1000, # taud2 0.5, # F 1.0, # alpha 0.0 # offset ] # Boundaries # strictly positive boundaries = [[0, np.inf]]*len(parms) # F boundaries[3] = [0, .9999999999999] boundaries[-1] = [-np.inf, np.inf] model_setup( modelid=6037, name="Separate 2D diffusion (confocal)", comp="2D+2D", mtype="Confocal (Gaussian)", fctn=CF_Gxyz_gauss_2D2D, par_labels=[ u"n", u"τ"+u"\u2081"+u" [ms]", u"τ"+u"\u2082"+u" [ms]", u"F"+u"\u2081", u"\u03b1"+u" (q"+u"\u2082"+"/q"+u"\u2081"+")", u"offset" ], par_values=parms, par_vary=[True, True, True, True, False, False], par_boundaries=boundaries, par_constraints=[[2, ">", 1]], supplementary_method=supplements ) pycorrfit-1.1.7/pycorrfit/models/model_confocal_tt_3d_3d.py0000664000372000037200000001205113554642611024716 0ustar travistravis00000000000000import numpy as np from .control import model_setup from .cp_confocal import threed from .cp_triplet import trip from .cp_mix import double_pnum # 3D + 3D + TT Gauß # Model 6043 def CF_Gxyz_gauss_3D3DTT(parms, tau): u""" Two-component three-dimensional free diffusion with a Gaussian laser profile, including two triplet components. The triplet factor takes into account a blinking term. Set *T* or *τ_trip* to 0, if no triplet component is wanted. particle1 = F₁/( (1+τ/τ₁) * sqrt(1+τ/(τ₁*SP²))) particle2 = α*(1-F₁)/( (1+τ/τ₂) * sqrt(1+τ/(τ₂*SP²))) triplet1 = 1 + T₁/(1-T₁)*exp(-τ/τ_trip₁) triplet2 = 1 + T₂/(1-T₂)*exp(-τ/τ_trip₂) norm = (F₁ + α*(1-F₁))² G = 1/n*(particle1 + particle2)*triplet1*triplet2/norm + offset *parms* - a list of parameters. Parameters (parms[i]): [0] n Effective number of particles in confocal volume (n = n₁+n₂) [1] τ₁ Diffusion time of particle species 1 [2] τ₂ Diffusion time of particle species 2 [3] F₁ Fraction of molecules of species 1 (n₁ = n*F₁) 0 <= F₁ <= 1 [4] SP SP=z₀/r₀, Structural parameter, describes elongation of the confocal volume [5] α Relative molecular brightness of particle 2 compared to particle 1 (α = q₂/q₁) [6] τ_trip₁ Characteristic residence time in triplet state [7] T₁ Fraction of particles in triplet (non-fluorescent) state 0 <= T < 1 [8] τ_trip₂ Characteristic residence time in triplet state [9] T₂ Fraction of particles in triplet (non-fluorescent) state 0 <= T < 1 [10] offset *tau* - lag time """ n = parms[0] taud1 = parms[1] taud2 = parms[2] F = parms[3] SP = parms[4] alpha = parms[5] tautrip1 = parms[6] T1 = parms[7] tautrip2 = parms[8] T2 = parms[9] off = parms[10] g = double_pnum(n=n, F1=F, alpha=alpha, comp1=threed, comp2=threed, kwargs1={"tau": tau, "taudiff": taud1, "SP": SP}, kwargs2={"tau": tau, "taudiff": taud2, "SP": SP}, ) tr1 = trip(tau=tau, T=T1, tautrip=tautrip1) tr2 = trip(tau=tau, T=T2, tautrip=tautrip2) G = off + g * tr1 * tr2 return G def supplements(parms, countrate=None): u"""Supplementary parameters: [11] n₁ = n*F₁ Particle number of species 1 [12] n₂ = n*(1-F₁) Particle number of species 2 """ # We can only give you the effective particle number n = parms[0] F1 = parms[3] Info = list() # The enumeration of these parameters is very important for # plotting the normalized curve. Countrate must come out last! Info.append([u"n\u2081", n*F1]) Info.append([u"n\u2082", n*(1.-F1)]) if countrate is not None: # CPP cpp = countrate/n Info.append(["cpp [kHz]", cpp]) return Info parms = [ 25, # n 5, # taud1 1000, # taud2 0.5, # F 5, # SP 1.0, # alpha 0.001, # tautrip1 0.01, # T1 0.002, # tautrip2 0.01, # T2 0.0 # offset ] # Boundaries # strictly positive boundaries = [[0, np.inf]]*len(parms) # F boundaries[3] = [0, .9999999999999] # T boundaries[7] = [0, .9999999999999] boundaries[9] = [0, .9999999999999] boundaries[-1] = [-np.inf, np.inf] model_setup( modelid=6043, name="Separate 3D diffusion with double triplet (confocal)", comp="T+T+3D+3D", mtype="Confocal (Gaussian) with double triplet", fctn=CF_Gxyz_gauss_3D3DTT, par_labels=[ u"n", u"τ"+u"\u2081"+" [ms]", u"τ"+u"\u2082"+" [ms]", u"F"+u"\u2081", u"SP", u"\u03b1"+" (q"+u"\u2082"+"/q"+u"\u2081"+")", u"τ_trip₁ [ms]", u"T₁", u"τ_trip₂ [ms]", u"T₂", u"offset" ], par_values=parms, par_vary=[True, True, True, True, False, False, False, False, False, False, False], par_boundaries=boundaries, par_constraints=[[2, ">", 1], [6, "<", 1], [8, ">", 6]], par_hr_labels=[ u"n", u"τ₁ [ms]", u"τ₂ [ms]", u"F₁", u"SP", u"\u03b1"+u" (q₂/q₁)", u"τ_trip₁ [µs]", u"T₁", u"τ_trip₂ [µs]", u"T₂", u"offset" ], par_hr_factors=[ 1., # n 1., # taud1 1., # taud2 1., # F 1., # SP 1., # alpha 1000., # tautrip1 [µs] 1., # T1 1000., # tautrip2 [µs] 1., # T2 1. # offset ], supplementary_method=supplements ) pycorrfit-1.1.7/pycorrfit/models/MODEL_TIRF_1C.py0000775000372000037200000001731713554642611022313 0ustar travistravis00000000000000import numpy as np import scipy.special as sps def wixi(x): """ Complex Error Function (Faddeeva/Voigt). w(i*x) = exp(x**2) * ( 1-erf(x) ) This function is called by other functions within this module. We are using the scipy.special.wofz module which calculates w(z) = exp(-z**2) * ( 1-erf(-iz) ) z = i*x """ z = x*1j wixi = sps.wofz(z) # We should have a real solution. Make sure nobody complains about # some zero-value imaginary numbers. return np.real_if_close(wixi) def CF_Gxy_TIR_square(parms, tau): # Model 6000 u""" Two-dimensional diffusion with a square shaped lateral detection area taking into account the size of the point spread function. *parms* - a list of parameters. Parameters (parms[i]): [0] D Diffusion coefficient [1] σ Lateral size of the point spread function σ = σ₀ * λ / NA [2] a Side size of the square-shaped detection area [3] C_2D Particle concentration in detection area *tau* - lag time Please refer to the documentation of PyCorrFit for further information on this model function. Returns: Normalized Lateral correlation function w/square pinhole. """ D = parms[0] sigma = parms[1] a = parms[2] Conc = parms[3] var1 = sigma**2+D*tau AA = 2*np.sqrt(var1)/(a**2*np.sqrt(np.pi)) BB = np.exp(-a**2/(4*(var1))) - 1 CC = sps.erf(a/(2*np.sqrt(var1)))/a # gx = AA*BB+CC # gxy = gx**2 # g2D = gxy * eta**2 * Conc # F = 1/(eta*Conc) # G = g2D / F**2 G = 1/Conc * (AA*BB+CC)**2 return G # 3D free tir def CF_Gxyz_TIR_square(parms, tau, wixi=wixi): # Model 6010 u""" Three-dimensional diffusion with a square-shaped lateral detection area taking into account the size of the point spread function; and an exponential decaying profile in axial direction. *parms* - a list of parameters. Parameters (parms[i]): [0] D Diffusion coefficient [1] σ Lateral size of the point spread function σ = σ₀ * λ / NA [2] a Side size of the square-shaped detection area [3] d_eva Evanescent penetration depth [4] C_3D Particle concentration in detection volume *tau* - lag time Please refer to the documentation of PyCorrFit for further information on this model function. Returns: 3D correlation function for TIR-FCS w/square pinhole """ D = parms[0] sigma = parms[1] a = parms[2] kappa = 1/parms[3] Conc = parms[4] # Calculate gxy # Axial correlation x = np.sqrt(D*tau)*kappa w_ix = wixi(x) gz = np.sqrt(D*tau/np.pi) - (2*D*tau*kappa**2 - 1)/(2*kappa) * w_ix # Lateral correlation gx1 = 2/(a**2*np.sqrt(np.pi)) * np.sqrt(sigma**2+D*tau) * \ (np.exp(-a**2/(4*(sigma**2+D*tau))) - 1) gx2 = 1/a * sps.erf(a / (2*np.sqrt(sigma**2 + D*tau))) gx = gx1 + gx2 gxy = gx**2 # Non normalized correlation function # We do not need eta after normalization # g = eta**2 * Conc * gxy * gz g = Conc * gxy * gz # Normalization: # F = eta * Conc / kappa F = Conc / kappa G = g / F**2 return G def MoreInfo_6000(parms, countrate=None): u"""Supplementary parameters: For a>>sigma, the correlation function at tau=0 corresponds to: [4] G(τ=0) = 1/(N_eff) * ( 1-2*σ/(sqrt(π)*a) )² Effective detection area: [5] A_eff [µm²] = a² Effective particle number in detection area: [6] N_eff = A_eff * C_2D """ #D = parms[0] #sigma = parms[1] a = parms[2] Conc = parms[3] Info = list() # Detection area: Aeff = a**2 # Particel number Neff = Aeff * Conc # Correlation function at tau = 0 G_0 = CF_Gxy_TIR_square(parms, 0) Info.append(["G(0)", G_0]) # 10 000 nm² = 0.01 µm² # Aeff * 10 000 nm² * 10**(-6)µm²/nm² = Aeff * 0.01 * µm² # Have to divide Aeff by 100 Info.append([u"A_eff [µm²]", Aeff / 100]) Info.append(["N_eff", Neff]) if countrate is not None: # CPP cpp = countrate/Neff Info.append(["cpp [kHz]", cpp]) return Info def MoreInfo_6010(parms, countrate): u"""Supplementary parameters: Molarity: [5] C_3D [nM] = C_3D [1000/µm³] * 10000/6.0221415 Effective detection volume: [6] V_eff = a² * d_eva Effective particle number: [7] N_eff = V_eff * C_3D For a>>σ, the correlation function at τ=0 corresponds to: [8] G(τ=0) = 1/(2*N_eff) * ( 1-2*σ/(sqrt(π)*a) )² """ # 3D Model TIR square # 3D TIR (□xσ/exp),Simple 3D diffusion w/ TIR, fct.CF_Gxyz_square_tir # D [10 µm²/s],σ [100 nm],a [100 nm],d_eva [100 nm],[conc.] [1000 /µm³] #sigma = parms[1] a = parms[2] d_eva = parms[3] conc = parms[4] # Sigma Info = list() # Molarity [nM]: # 1000/(µm³)*10**15µm³/l * mol /(6.022*10^23) * 10^9 n cmol = conc * 10000/6.0221415 # Effective volume [al]: Veff = a**2 * d_eva # Effective particel number Neff = a**2 * d_eva * conc # Correlation function at tau = 0 G_0 = CF_Gxyz_TIR_square(parms, 0) Info.append(["G(0)", G_0]) Info.append(["C_3D [nM]", cmol]) # atto liters # 1 000 000 nm³ = 1 al Info.append(["V_eff [al]", Veff]) Info.append(["N_eff", Neff]) if countrate is not None: # CPP cpp = countrate/Neff Info.append(["cpp [kHz]", cpp]) return Info # 2D Model Square m_twodsq6000 = [6000, u"2D", u"2D diffusion w/ square pinhole", CF_Gxy_TIR_square] labels_6000 = [u"D [10 µm²/s]", u"σ [100 nm]", u"a [100 nm]", u"C_2D [100 /µm²]"] values_6000 = [0.054, 2.3, 7.5, .6] # [D,lamb,NA,a,conc] # For user comfort we add values that are human readable. # Theese will be used for output that only humans can read. labels_human_readable_6000 = [u"D [µm²/s]", u"σ [nm]", u"a [nm]", u"C_2D [1/µm²]"] values_factor_human_readable_6000 = [10, 100, 100, 100] valuestofit_6000 = [True, False, False, True] # Use as fit parameter? parms_6000 = [labels_6000, values_6000, valuestofit_6000, labels_human_readable_6000, values_factor_human_readable_6000] # 3D Model TIR square m_3dtirsq6010 = [6010, u"3D", "Simple 3D diffusion w/ TIR", CF_Gxyz_TIR_square] labels_6010 = [u"D [10 µm²/s]", u"σ [100 nm]", u"a [100 nm]", u"d_eva [100 nm]", u"C_3D [1000 /µm³]"] values_6010 = [0.520, 2.3, 7.5, 1.0, .0216] # For user comfort we add values that are human readable. # Theese will be used for output that only humans can read. labels_human_readable_6010 = [u"D [µm²/s]", u"σ [nm]", u"a [nm]", u"d_eva [nm]", u"C_3D [1/µm³]"] values_factor_human_readable_6010 = [10, 100, 100, 100, 1000] valuestofit_6010 = [True, False, False, False, True] parms_6010 = [labels_6010, values_6010, valuestofit_6010, labels_human_readable_6010, values_factor_human_readable_6010] # Pack the models model1 = dict() model1["Parameters"] = parms_6000 model1["Definitions"] = m_twodsq6000 model1["Supplements"] = MoreInfo_6000 model1["Boundaries"] = [[0, None]]*len(values_6000) model2 = dict() model2["Parameters"] = parms_6010 model2["Definitions"] = m_3dtirsq6010 model2["Supplements"] = MoreInfo_6010 model2["Boundaries"] = [[0, None]]*len(values_6010) Modelarray = [model1, model2] pycorrfit-1.1.7/pycorrfit/models/model_confocal_2d.py0000664000372000037200000000303413554642611023621 0ustar travistravis00000000000000import numpy as np from .control import model_setup from .cp_confocal import twod # 2D simple gauss def CF_Gxy_gauss(parms, tau): u""" Two-dimensional diffusion with a Gaussian laser profile. G(τ) = offset + 1/( n * (1+τ/τ_diff) ) Calculation of diffusion coefficient and concentration from the effective radius of the detection profile (r₀ = 2*σ): D = r₀²/(4*τ_diff) Conc = n/(π*r₀²) *parms* - a list of parameters. Parameters (parms[i]): [0] n Effective number of particles in confocal area [1] τ_diff Characteristic residence time in confocal area [2] offset *tau* - lag time """ n = parms[0] taudiff = parms[1] dc = parms[2] BB = twod(tau=tau, taudiff=taudiff) G = dc + 1/n * BB return G def supplements(parms, countrate=None): # We can only give you the effective particle number n = parms[0] Info = list() if countrate is not None: # CPP cpp = countrate/n Info.append(["cpp [kHz]", cpp]) return Info parms = [4.0, 0.4, 0.0] # boundaries boundaries = [[0, np.inf]]*len(parms) boundaries[-1] = [-np.inf, np.inf] model_setup( modelid=6001, name="2D diffusion (confocal)", comp="2D", mtype="Confocal (Gaussian)", fctn=CF_Gxy_gauss, par_labels=[u"n", u"τ_diff [ms]", u"offset"], par_values=parms, par_vary=[True, True, False], par_boundaries=boundaries, supplementary_method=supplements ) pycorrfit-1.1.7/pycorrfit/models/MODEL_TIRF_3D3D.py0000775000372000037200000001173413554642611022502 0ustar travistravis00000000000000import numpy as np import scipy.special as sps def wixi(x): """ Complex Error Function (Faddeeva/Voigt). w(i*x) = exp(x**2) * ( 1-erf(x) ) This function is called by other functions within this module. We are using the scipy.special.wofz module which calculates w(z) = exp(-z**2) * ( 1-erf(-iz) ) z = i*x """ z = x*1j wixi = sps.wofz(z) # We should have a real solution. Make sure nobody complains about # some zero-value imaginary numbers. return np.real_if_close(wixi) # 3D + 3D no binding TIRF # model 6023 def CF_Gxyz_TIR_square_3d3d(parms, tau, wixi=wixi): u""" Two-component three-dimensional free diffusion with a square-shaped lateral detection area taking into account the size of the point spread function; and an exponential decaying profile in axial direction. *parms* - a list of parameters. Parameters (parms[i]): [0] D_3D1 3D Diffusion coefficient (species 1) [1] D_3D2 3D Diffusion coefficient of bound species 2 [2] σ Lateral size of the point spread function σ = σ₀ * λ / NA [3] a Side size of the square-shaped detection area [4] d_eva Evanescent penetration depth [5] C_3D1 Concentration of species 1 [6] C_3D2 Concentration of species 2 [7] α Relative molecular brightness of particle 2 compared to particle 1 (α = q₂/q₁) *tau* - lag time """ D_3D1 = parms[0] D_3D2 = parms[1] sigma = parms[2] a = parms[3] kappa = 1/parms[4] Conc_3D1 = parms[5] Conc_3D2 = parms[6] alpha = parms[7] # First, the 3D diffusion of species 1 # Axial correlation x1 = np.sqrt(D_3D1*tau)*kappa w_ix1 = wixi(x1) gz1 = np.sqrt(D_3D1*tau/np.pi) - (2*D_3D1*tau*kappa**2 - 1)/(2*kappa) * \ w_ix1 # Lateral correlation gx1_1 = 2/(a**2*np.sqrt(np.pi)) * np.sqrt(sigma**2+D_3D1*tau) * \ (np.exp(-a**2/(4*(sigma**2+D_3D1*tau))) - 1) gx2_1 = 1/a * sps.erf(a / (2*np.sqrt(sigma**2 + D_3D1*tau))) gx1 = gx1_1 + gx2_1 gxy1 = gx1**2 # Non normalized correlation function g3D1 = Conc_3D1 * gxy1 * gz1 # Second, the 3D diffusion of species 2 # Axial correlation x2 = np.sqrt(D_3D2*tau)*kappa w_ix2 = wixi(x2) gz2 = np.sqrt(D_3D2*tau/np.pi) - (2*D_3D2*tau*kappa**2 - 1)/(2*kappa) * \ w_ix2 # Lateral correlation gx1_2 = 2/(a**2*np.sqrt(np.pi)) * np.sqrt(sigma**2+D_3D2*tau) * \ (np.exp(-a**2/(4*(sigma**2+D_3D2*tau))) - 1) gx2_2 = 1/a * sps.erf(a / (2*np.sqrt(sigma**2 + D_3D2*tau))) gx2 = gx1_2 + gx2_2 gxy2 = gx2**2 # Non normalized correlation function g3D2 = alpha**2 * Conc_3D2 * gxy2 * gz2 # Finally the Prefactor F = (Conc_3D1 + alpha * Conc_3D2) / kappa G = (g3D1 + g3D2) / F**2 return G # 3D-3D Model TIR m_tir_3d_3d_mix_6023 = [6023, u"3D+3D", "Separate 3D diffusion, 3D TIR", CF_Gxyz_TIR_square_3d3d] labels_6023 = [u"D"+u"\u2081"+u" [10 µm²/s]", u"D"+u"\u2082"+u" [10 µm²/s]", u"σ [100 nm]", u"a [100 nm]", u"d_eva [100 nm]", u"C"+u"\u2081"+u" [1000 /µm³]", u"C"+u"\u2082"+u" [1000 /µm³]", u"\u03b1"+" (q"+u"\u2082"+"/q"+u"\u2081"+")" ] values_6023 = [ 0.9, # D_3D₁ [10 µm²/s] 9.0, # D_3D₂ [10 µm²/s] 2.3, # σ [100 nm] 7.50, # a [100 nm] 1.0, # d_eva [100 nm] 0.01, # conc.3D₁ [1000 /µm³] 0.03, # conc.3D₂ [1000 /µm³] 1 # alpha ] # For user comfort we add values that are human readable. # Theese will be used for output that only humans can read. labels_human_readable_6023 = [ u"D"+u"\u2081"+u" [µm²/s]", u"D"+u"\u2082"+u" [µm²/s]", u"σ [nm]", u"a [nm]", u"d_eva [nm]", u"C"+u"\u2081"+u" [1/µm³]", u"C"+u"\u2082"+u" [1/µm³]", u"\u03b1"+" (q"+u"\u2082"+"/q"+u"\u2081"+")" ] values_factor_human_readable_6023 = [10, # "D_3D₁ [µm²/s]", 10, # D_3D₂ [10 µm²/s] 100, # σ [100 nm] 100, # a [100 nm] 100, # d_eva [100 nm] 1000, # conc.3D₁ [1000 /µm³] 1000, # conc.3D₂ [1000 /µm³] 1 # alpha ] valuestofit_6023 = [False, True, False, False, False, False, True, False] parms_6023 = [labels_6023, values_6023, valuestofit_6023, labels_human_readable_6023, values_factor_human_readable_6023] model1 = dict() model1["Parameters"] = parms_6023 model1["Definitions"] = m_tir_3d_3d_mix_6023 model1["Boundaries"] = [[0, np.inf]] * len(values_6023) model1["Constraints"] = [[1, ">", 0]] Modelarray = [model1] pycorrfit-1.1.7/pycorrfit/models/MODEL_TIRF_2D2D.py0000775000372000037200000000774513554642611022507 0ustar travistravis00000000000000import numpy as np import scipy.special as sps def wixi(x): """ Complex Error Function (Faddeeva/Voigt). w(i*x) = exp(x**2) * ( 1-erf(x) ) This function is called by other functions within this module. We are using the scipy.special.wofz module which calculates w(z) = exp(-z**2) * ( 1-erf(-iz) ) z = i*x """ z = x*1j wixi = sps.wofz(z) # We should have a real solution. Make sure nobody complains about # some zero-value imaginary numbers. return np.real_if_close(wixi) # model 6022 # 2D + 2D no binding TIRF def CF_Gxy_TIR_square_2d2d(parms, tau, wixi=wixi): u""" Two-component two-dimensional diffusion with a square-shaped lateral detection area taking into account the size of the point spread function. *parms* - a list of parameters. Parameters (parms[i]): [0] D_2D1 Diffusion coefficient of species 1 [1] D_2D2 Diffusion coefficient of species 2 [2] σ Lateral size of the point spread function σ = σ₀ * λ / NA [3] a Side size of the square-shaped detection area [4] C_2D1 Two-dimensional concentration of species 1 [5] C_2D2 Two-dimensional concentration of species 2 [6] α Relative molecular brightness of particle 2 compared to particle 1 (α = q₂/q₁) *tau* - lag time """ D_2D1 = parms[0] D_2D2 = parms[1] sigma = parms[2] a = parms[3] Conc_2D1 = parms[4] Conc_2D2 = parms[5] alpha = parms[6] # First the 2D-diffusion of species 1 var1 = sigma**2+D_2D1*tau AA1 = 2*np.sqrt(var1)/(a**2*np.sqrt(np.pi)) BB1 = np.exp(-a**2/(4*(var1))) - 1 CC1 = sps.erf(a/(2*np.sqrt(var1)))/a # gx = AA*BB+CC # gxy = gx**2 # g2D = Conc_2D * gxy g2D1 = Conc_2D1 * (AA1*BB1+CC1)**2 # Second the 2D-diffusion of species 2 var2 = sigma**2+D_2D2*tau AA2 = 2*np.sqrt(var2)/(a**2*np.sqrt(np.pi)) BB2 = np.exp(-a**2/(4*(var2))) - 1 CC2 = sps.erf(a/(2*np.sqrt(var2)))/a # gx = AA*BB+CC # gxy = gx**2 # g2D = Conc_2D * gxy g2D2 = alpha**2 * Conc_2D2 * (AA2*BB2+CC2)**2 # Finally the Prefactor F = Conc_2D1 + alpha * Conc_2D2 G = (g2D1 + g2D2) / F**2 return G # 2D-2D Model TIR m_tir_2d_2d_mix_6022 = [6022, u"2D+2D", "Separate 2D diffusion, TIR", CF_Gxy_TIR_square_2d2d] labels_6022 = [u"D"+u"\u2081"+u" [10 µm²/s]", u"D"+u"\u2082"+u" [10 µm²/s]", u"σ [100 nm]", u"a [100 nm]", u"C"+u"\u2081"+u" [100 /µm²]", u"C"+u"\u2082"+u" [100 /µm²]", u"\u03b1"+" (q"+u"\u2082"+"/q"+u"\u2081"+")" ] values_6022 = [ 0.01, # D_2D₁ [10 µm²/s] 0.90, # D_2D₂ [10 µm²/s] 2.3, # σ [100 nm] 7.50, # a [100 nm] 0.01, # conc.2D₁ [100 /µm²] 0.03, # conc.2D₂ [100 /µm²] 1 # alpha ] # For user comfort we add values that are human readable. # Theese will be used for output that only humans can read. labels_human_readable_6022 = [ u"D"+u"\u2081"+u" [µm²/s]", u"D"+u"\u2082"+u" [µm²/s]", u"σ [nm]", u"a [nm]", u"C"+u"\u2081"+u" [1/µm²]", u"C"+u"\u2082"+u" [1/µm²]", u"\u03b1"+" (q"+u"\u2082"+"/q"+u"\u2081"+")" ] values_factor_human_readable_6022 = [ 10, # D_2D₁ [10 µm²/s], 10, # D_2D₂ [10 µm²/s] 100, # σ [100 nm] 100, # a [100 nm] 100, # conc.2D₁ [100 /µm²] 100, # conc.2D₂ [100 /µm²] 1 ] valuestofit_6022 = [False, True, False, False, False, True, False] parms_6022 = [labels_6022, values_6022, valuestofit_6022, labels_human_readable_6022, values_factor_human_readable_6022] boundaries = [[0, np.inf]] * len(values_6022) model1 = dict() model1["Parameters"] = parms_6022 model1["Definitions"] = m_tir_2d_2d_mix_6022 model1["Boundaries"] = boundaries model1["Constraints"] = [[1, ">", 0]] Modelarray = [model1] pycorrfit-1.1.7/pycorrfit/models/model_confocal_3d_3d.py0000664000372000037200000000637413554642611024222 0ustar travistravis00000000000000import numpy as np from .control import model_setup from .cp_confocal import threed from .cp_mix import double_pnum def CF_Gxyz_gauss_3D3D(parms, tau): u""" Two-component three-dimensional free diffusion with a Gaussian laser profile. particle1 = F₁/( (1+τ/τ₁) * sqrt(1+τ/(τ₁*SP²))) particle2 = α²*(1-F₁)/( (1+τ/τ₂) * sqrt(1+τ/(τ₂*SP²))) norm = (F₁ + α*(1-F₁))² G = 1/n*(particle1 + particle2)/norm + offset *parms* - a list of parameters. Parameters (parms[i]): [0] n Effective number of particles in confocal volume (n = n₁+n₂) [1] τ₁ Diffusion time of particle species 1 [2] τ₂ Diffusion time of particle species 2 [3] F₁ Fraction of molecules of species 1 (n₁ = n*F₁) 0 <= F₁ <= 1 [4] SP SP=z₀/r₀, Structural parameter, describes elongation of the confocal volume [5] α Relative molecular brightness of particle 2 compared to particle 1 (α = q₂/q₁) [6] offset *tau* - lag time """ n = parms[0] taud1 = parms[1] taud2 = parms[2] F = parms[3] SP = parms[4] alpha = parms[5] off = parms[6] g = double_pnum(n=n, F1=F, alpha=alpha, comp1=threed, comp2=threed, kwargs1={"tau": tau, "taudiff": taud1, "SP": SP}, kwargs2={"tau": tau, "taudiff": taud2, "SP": SP}, ) G = off + g return G def supplements(parms, countrate=None): u"""Supplementary parameters: [7] n₁ = n*F₁ Particle number of species 1 [8] n₂ = n*(1-F₁) Particle number of species 2 """ # We can only give you the effective particle number n = parms[0] F1 = parms[3] Info = list() # The enumeration of these parameters is very important for # plotting the normalized curve. Countrate must come out last! Info.append([u"n\u2081", n*F1]) Info.append([u"n\u2082", n*(1.-F1)]) if countrate is not None: # CPP cpp = countrate/n Info.append(["cpp [kHz]", cpp]) return Info parms = [25, # n 5, # taud1 1000, # taud2 0.5, # F 5, # SP 1.0, # alpha 0.0 # offset ] # Boundaries # strictly positive boundaries = [[0, np.inf]]*len(parms) # F boundaries[3] = [0, .9999999999999] boundaries[-1] = [-np.inf, np.inf] model_setup( modelid=6035, name="Separate 3D diffusion (confocal)", comp="3D+3D", mtype="Confocal (Gaussian)", fctn=CF_Gxyz_gauss_3D3D, par_labels=[ u"n", u"τ"+u"\u2081"+" [ms]", u"τ"+u"\u2082"+" [ms]", u"F"+u"\u2081", u"SP", u"\u03b1"+" (q"+u"\u2082"+"/q"+u"\u2081"+")", u"offset" ], par_values=parms, par_vary=[True, True, True, True, False, False, False], par_boundaries=boundaries, par_constraints=[[2, ">", 1]], supplementary_method=supplements ) pycorrfit-1.1.7/pycorrfit/models/model_confocal_t_3d_3d.py0000664000372000037200000001064513554642611024541 0ustar travistravis00000000000000import numpy as np from .control import model_setup from .cp_confocal import threed from .cp_triplet import trip from .cp_mix import double_pnum def CF_Gxyz_gauss_3D3DT(parms, tau): u""" Two-component three-dimensional free diffusion with a Gaussian laser profile, including a triplet component. The triplet factor takes into account a blinking term. Set *T* or *τ_trip* to 0, if no triplet component is wanted. particle1 = F₁/( (1+τ/τ₁) * sqrt(1+τ/(τ₁*SP²))) particle2 = α²*(1-F₁)/( (1+τ/τ₂) * sqrt(1+τ/(τ₂*SP²))) triplet = 1 + T/(1-T)*exp(-τ/τ_trip) norm = (F₁ + α*(1-F₁))² G = 1/n*(particle1 + particle2)*triplet/norm + offset *parms* - a list of parameters. Parameters (parms[i]): [0] n Effective number of particles in confocal volume (n = n₁+n₂) [1] τ₁ Diffusion time of particle species 1 [2] τ₂ Diffusion time of particle species 2 [3] F₁ Fraction of molecules of species 1 (n₁ = n*F₁) 0 <= F₁ <= 1 [4] SP SP=z₀/r₀, Structural parameter, describes elongation of the confocal volume [5] α Relative molecular brightness of particle 2 compared to particle 1 (α = q₂/q₁) [6] τ_trip Characteristic residence time in triplet state [7] T Fraction of particles in triplet (non-fluorescent) state 0 <= T < 1 [8] offset *tau* - lag time """ n = parms[0] taud1 = parms[1] taud2 = parms[2] F = parms[3] SP = parms[4] alpha = parms[5] tautrip = parms[6] T = parms[7] off = parms[8] g = double_pnum(n=n, F1=F, alpha=alpha, comp1=threed, comp2=threed, kwargs1={"tau": tau, "taudiff": taud1, "SP": SP}, kwargs2={"tau": tau, "taudiff": taud2, "SP": SP}, ) tr = trip(tau=tau, T=T, tautrip=tautrip) G = off + g*tr return G def supplements(parms, countrate=None): u"""Supplementary parameters: [9] n₁ = n*F₁ Particle number of species 1 [10] n₂ = n*(1-F₁) Particle number of species 2 """ # We can only give you the effective particle number n = parms[0] F1 = parms[3] Info = list() # The enumeration of these parameters is very important for # plotting the normalized curve. Countrate must come out last! Info.append([u"n\u2081", n*F1]) Info.append([u"n\u2082", n*(1.-F1)]) if countrate is not None: # CPP cpp = countrate/n Info.append(["cpp [kHz]", cpp]) return Info parms = [25, # n 5, # taud1 1000, # taud2 0.5, # F 5, # SP 1.0, # alpha 0.001, # tautrip 0.01, # T 0.0 # offset ] # Boundaries # strictly positive boundaries = [[0, np.inf]]*len(parms) # F boundaries[3] = [0, .9999999999999] # T boundaries[7] = [0, .9999999999999] boundaries[-1] = [-np.inf, np.inf] model_setup( modelid=6030, name="Separate 3D diffusion with triplet (confocal)", comp="T+3D+3D", mtype="Confocal (Gaussian) and triplet", fctn=CF_Gxyz_gauss_3D3DT, par_labels=[ u"n", u"τ"+u"\u2081"+" [ms]", u"τ"+u"\u2082"+" [ms]", u"F"+u"\u2081", u"SP", u"\u03b1"+" (q"+u"\u2082"+"/q"+u"\u2081"+")", u"τ_trip [ms]", u"T", u"offset" ], par_values=parms, par_vary=[True, True, True, True, False, False, False, False, False], par_boundaries=boundaries, par_constraints=[[2, ">", 1], [6, "<", 1]], par_hr_labels=[ u"n", u"τ"+u"\u2081"+" [ms]", u"τ"+u"\u2082"+" [ms]", u"F"+u"\u2081", u"SP", u"\u03b1"+" (q"+u"\u2082"+"/q"+u"\u2081"+")", u"τ_trip [µs]", u"T", u"offset" ], par_hr_factors=[ 1., # n 1., # taud1 1., # taud2 1., # F 1., # SP 1., # alpha 1000., # tautrip [µs] 1., # T 1. # offset ], supplementary_method=supplements ) pycorrfit-1.1.7/pycorrfit/models/MODEL_TIRF_gaussian_1C.py0000775000372000037200000002122113554642611024172 0ustar travistravis00000000000000import numpy as np import scipy.special as sps def wixi(x): """ Complex Error Function (Faddeeva/Voigt). w(i*x) = exp(x**2) * ( 1-erf(x) ) This function is called by other functions within this module. We are using the scipy.special.wofz module which calculates w(z) = exp(-z**2) * ( 1-erf(-iz) ) z = i*x """ z = x*1j wixi = sps.wofz(z) # We should have a real solution. Make sure nobody complains about # some zero-value imaginary numbers. return np.real_if_close(wixi) def CF_Gxyz_TIR_gauss(parms, tau): u""" Three-dimensional free diffusion with a Gaussian lateral detection profile and an exponentially decaying profile in axial direction. x = sqrt(D*τ)*κ κ = 1/d_eva w(i*x) = exp(x²)*erfc(x) gz = κ * [ sqrt(D*τ/π) + (1 - 2*D*τ*κ)/(2*κ) * w(i*x) ] g2D = 1 / [ π (r₀² + 4*D*τ) ] G = 1/C_3D * g2D * gz *parms* - a list of parameters. Parameters (parms[i]): [0] D Diffusion coefficient [1] r₀ Lateral extent of the detection volume [2] d_eva Evanescent field depth [3] C_3D Particle concentration in the confocal volume *tau* - lag time """ # model 6013 D = parms[0] r0 = parms[1] deva = parms[2] Conc = parms[3] # Calculate sigma: width of the gaussian approximation of the PSF Veff = np.pi * r0**2 * deva Neff = Conc * Veff taudiff = r0**2/(4*D) # 2D gauss component # G2D = 1/N2D * g2D = 1/(Aeff*Conc.2D) * g2D g2D = 1 / ((1.+tau/taudiff)) # 1d TIR component # Axial correlation kappa = 1/deva x = np.sqrt(D*tau)*kappa w_ix = wixi(x) # Gz = 1/N1D * gz = kappa / Conc.1D * gz gz = kappa * (np.sqrt(D*tau/np.pi) - (2*D*tau*kappa**2 - 1)/(2*kappa) * w_ix) # gz * g2D * 1/( deva *A2D) * 1 / Conc3D # Neff is not the actual particle number. This formula just looks nicer # this way. # What would be easier to get is: # 1 / (Conc * deva * np.pi * r0) * gz * g2D return 1 / (Neff) * g2D * gz def CF_Gxyz_TIR_gauss_trip(parms, tau): u""" Three-dimensional free diffusion with a Gaussian lateral detection profile and an exponentially decaying profile in axial direction, including a triplet component. x = sqrt(D*τ)*κ κ = 1/d_eva w(i*x) = exp(x²)*erfc(x) gz = κ * [ sqrt(D*τ/π) + (1 - 2*D*τ*κ)/(2*κ) * w(i*x) ] g2D = 1 / [ π (r₀² + 4*D*τ) ] triplet = 1 + T/(1-T)*exp(-τ/τ_trip) G = 1/C_3D * g2D * gz * triplet *parms* - a list of parameters. Parameters (parms[i]): [0] D Diffusion coefficient [1] r₀ Lateral extent of the detection volume [2] d_eva Evanescent field depth [3] C_3D Particle concentration in the confocal volume [4] τ_trip Characteristic residence time in triplet state [5] T Fraction of particles in triplet (non-fluorescent) state 0 <= T < 1 *tau* - lag time """ # model 6014 D = parms[0] r0 = parms[1] deva = parms[2] Conc = parms[3] tautrip = parms[4] T = parms[5] # Calculate sigma: width of the gaussian approximation of the PSF Veff = np.pi * r0**2 * deva Neff = Conc * Veff taudiff = r0**2/(4*D) # 2D gauss component # G2D = 1/N2D * g2D = 1/(Aeff*Conc.2D) * g2D g2D = 1 / ((1.+tau/taudiff)) # 1d TIR component # Axial correlation kappa = 1/deva x = np.sqrt(D*tau)*kappa w_ix = wixi(x) # Gz = 1/N1D * gz = kappa / Conc.1D * gz gz = kappa * (np.sqrt(D*tau/np.pi) - (2*D*tau*kappa**2 - 1)/(2*kappa) * w_ix) # triplet if tautrip == 0 or T == 0: triplet = 1 else: triplet = 1 + T/(1-T) * np.exp(-tau/tautrip) # Neff is not the actual particle number. This formula just looks nicer # this way. # What would be easier to get is: # 1 / (Conc * deva * np.pi * r0) * gz * g2D return 1 / (Neff) * g2D * gz * triplet def MoreInfo_6013(parms, countrate=None): u"""Supplementary variables: Beware that the effective volume is chosen arbitrarily. Correlation function at lag time τ=0: [4] G(τ=0) Effective detection volume: [5] V_eff = π * r₀² * d_eva Effective particle concentration: [6] C_3D [nM] = C_3D [1000/µm³] * 10000/6.0221415 """ #D = parms[0] r0 = parms[1] deva = parms[2] Conc = parms[3] Info = list() # Detection area: Veff = np.pi * r0**2 * deva Neff = Conc * Veff # Correlation function at tau = 0 G_0 = CF_Gxyz_TIR_gauss(parms, 0) Info.append(["G(0)", G_0]) Info.append(["V_eff [al]", Veff]) Info.append(["C_3D [nM]", Conc * 10000/6.0221415]) if countrate is not None: # CPP cpp = countrate/Neff Info.append(["cpp [kHz]", cpp]) return Info def MoreInfo_6014(parms, countrate=None): u"""Supplementary variables: Beware that the effective volume is chosen arbitrarily. Correlation function at lag time τ=0: [6] G(τ=0) Effective detection volume: [7] V_eff = π * r₀² * d_eva Effective particle concentration: [8] C_3D [nM] = C_3D [1000/µm³] * 10000/6.0221415 """ #D = parms[0] r0 = parms[1] deva = parms[2] Conc = parms[3] Info = list() # Detection area: Veff = np.pi * r0**2 * deva Neff = Conc * Veff # Correlation function at tau = 0 G_0 = CF_Gxyz_TIR_gauss(parms, 0) Info.append(["G(0)", G_0]) Info.append(["V_eff [al]", Veff]) Info.append(["C_3D [nM]", Conc * 10000/6.0221415]) if countrate is not None: # CPP cpp = countrate/Neff Info.append(["cpp [kHz]", cpp]) return Info def get_boundaries_6014(parms): # strictly positive boundaries = [[0, None]]*len(parms) boundaries[5] = [0, 1] return boundaries def get_boundaries_6013(parms): # strictly positive boundaries = [[0, None]]*len(parms) return boundaries # 3D Model TIR gaussian m_3dtirsq6013 = [6013, "3D", "Simple 3D diffusion w/ TIR", CF_Gxyz_TIR_gauss] labels_6013 = [u"D [10 µm²/s]", u"r₀ [100 nm]", u"d_eva [100 nm]", u"C_3D [1000/µm³)"] values_6013 = [2.5420, 9.44, 1.0, 0.03011] # For user comfort we add values that are human readable. # Theese will be used for output that only humans can read. labels_human_readable_6013 = [u"D [µm²/s]", u"r₀ [nm]", u"d_eva [nm]", u"C_3D [1/µm³]"] values_factor_human_readable_6013 = [10, 100, 100, 1000] valuestofit_6013 = [True, False, False, True] parms_6013 = [labels_6013, values_6013, valuestofit_6013, labels_human_readable_6013, values_factor_human_readable_6013] # Pack the models model1 = dict() model1["Parameters"] = parms_6013 model1["Definitions"] = m_3dtirsq6013 model1["Supplements"] = MoreInfo_6013 model1["Boundaries"] = get_boundaries_6013(values_6013) # 3D Model TIR gaussian + triplet m_3dtirsq6014 = [6014, "T+3D", "Simple 3D diffusion + triplet w/ TIR", CF_Gxyz_TIR_gauss_trip] labels_6014 = [u"D [10 µm²/s]", u"r₀ [100 nm]", u"d_eva [100 nm]", u"C_3D [1000/µm³)", u"τ_trip [ms]", u"T"] values_6014 = [2.5420, 9.44, 1.0, 0.03011, 0.001, 0.01] labels_human_readable_6014 = [u"D [µm²/s]", u"r₀ [nm]", u"d_eva [nm]", u"C_3D [1/µm³]", u"τ_trip [µs]", u"T"] values_factor_human_readable_6014 = [10, 100, 100, 1000, 1000, 1] valuestofit_6014 = [True, False, False, True, False, False] parms_6014 = [labels_6014, values_6014, valuestofit_6014, labels_human_readable_6014, values_factor_human_readable_6014] # Pack the models model2 = dict() model2["Parameters"] = parms_6014 model2["Definitions"] = m_3dtirsq6014 model2["Supplements"] = MoreInfo_6014 model2["Boundaries"] = get_boundaries_6014(values_6014) Modelarray = [model1, model2] pycorrfit-1.1.7/pycorrfit/models/model_confocal_t_3d.py0000664000372000037200000000510413554642611024145 0ustar travistravis00000000000000import numpy as np from .control import model_setup from .cp_confocal import threed from .cp_triplet import trip def CF_Gxyz_blink(parms, tau): u""" Three-dimanesional free diffusion with a Gaussian laser profile (eliptical), including a triplet component. The triplet factor takes into account a blinking term. Set *T* or *τ_trip* to 0, if no triplet component is wanted. G(τ) = offset + 1/( n*(1+τ/τ_diff) * sqrt(1 + τ/(SP²*τ_diff)) ) * ( 1+T/(1.-T)*exp(-τ/τ_trip) ) Calculation of diffusion coefficient and concentration from the effective radius of the detection profile (r₀ = 2*σ): D = r₀²/(4*τ_diff) Conc = n/( sqrt(π³)*r₀²*z₀ ) *parms* - a list of parameters. Parameters (parms[i]): [0] n Effective number of particles in confocal volume [1] T Fraction of particles in triplet (non-fluorescent) state 0 <= T < 1 [2] τ_trip Characteristic residence time in triplet state [3] τ_diff Characteristic residence time in confocal volume [4] SP SP=z₀/r₀ Structural parameter, describes the axis ratio of the confocal volume [5] offset *tau* - lag time """ n = parms[0] T = parms[1] tautrip = parms[2] taudiff = parms[3] SP = parms[4] off = parms[5] AA = trip(tau, tautrip, T) BB = threed(tau, taudiff, SP) G = off + 1/n * AA * BB return G def supplements(parms, countrate=None): # We can only give you the effective particle number n = parms[0] Info = list() if countrate is not None: # CPP cpp = countrate/n Info.append(["cpp [kHz]", cpp]) return Info parms = [4.0, 0.2, 0.001, 0.4, 5.0, 0.0] # Boundaries boundaries = [[0, np.inf]]*len(parms) # T boundaries[1] = [0, .9999999999999] boundaries[-1] = [-np.inf, np.inf] model_setup( modelid=6011, name="3D diffusion with triplet (confocal)", comp="T+3D", mtype="Confocal (Gaussian) and triplet", fctn=CF_Gxyz_blink, par_labels=[ u"n", u"T", u"τ_trip [ms]", u"τ_diff [ms]", u"SP", u"offset"], par_values=parms, par_vary=[True, True, True, True, False, False], par_boundaries=boundaries, par_constraints=[[3, ">", 2]], par_hr_labels=[ u"n", u"T", u"τ_trip [µs]", u"τ_diff [ms]", u"SP", u"offset"], par_hr_factors=[1., 1., 1000., 1., 1., 1.], supplementary_method=supplements ) pycorrfit-1.1.7/pycorrfit/models/control.py0000664000372000037200000001704013554642611021752 0ustar travistravis00000000000000""" pycorrfit.models.control Controls which fitting models are imported an in which order. """ import numpy as np from .classes import Model def append_model(modelarray): """ Append a new model from a modelarray. *Modelarray* has to be a list whose elements have two items: [0] parameters [1] some info about the model See separate models for more information """ global values global valuedict global models global modeldict global supplement global boundaries global modeltypes if not isinstance(modelarray, list): modelarray = [modelarray] for datadict in modelarray: # We can have many models in one model array amod = Model(datadict) models.append(amod) if amod.id in modeldict: raise ValueError("Model with same is already exists: \n {} vs. {}". format(amod, modeldict[amod.id])) modeldict[amod.id] = amod values.append(amod.parameters) valuedict[amod.id] = amod.parameters # Supplementary Data might be there supplement[amod.id] = amod.func_supplements # Check functions - check for correct values boundaries[amod.id] = amod.boundaries # Add model type to internal type list. if amod.type is not None: if not amod.type in modeltypes: modeltypes[amod.type] = [] modeltypes[amod.type].append(amod.id) def model_setup(modelid, name, comp, mtype, fctn, par_labels, par_values, par_vary=None, par_boundaries=None, par_constraints=None, par_hr_labels=None, par_hr_factors=None, supplementary_method=None, ): u""" This helper method does everything that is required to make a model available for PyCorrFit. The idea is that this method can be called from anywhere and thus we do not need to do the tedious work of adding models in the __init__.py file. Parameters ---------- modelid : int Model identifier. name : str Name of the Model. comp : str Description of components of the model, e.g. "T+3D+2D" mtype : str Type of model, e.g. "Confocal (Gaussian)" fctn : callable The method that computes the model function. It must take two arguments. The first is of shape `par_values` and the second is a 2D array containing lag time and correlation. par_labels : list-like, strings The labels of each parameter in PyCorrFit dimensionless representation, i.e. unit of time : 1 ms unit of inverse time: 1000 /s unit of distance : 100 nm unit of Diff.coeff : 10 µm²/s unit of inverse area: 100 /µm² unit of inv. volume : 1000 /µm³ par_values : list-like, floats The parameter values in PyCorrFit dimensionless units. par_vary : list-like, bools or None A list describing which parameters should be varied during fitting. If not given, only the first element is set to `True`. par_boundaries : list-like, floats The parameter boundaries - two values for each parameter. Examples: [[0, np.inf], [0,1]] par_constraints : list of lists Constraints between parameters. For example, make sure parameter 2 is always larger than parameter 1 and parameter 5 is always smaller than parameter 1: [[2, ">", 1], [5, "<", 1]] Parameter count starts at 0. par_hr_labels : list-like, strings User-defined human readable labels of the parameters. If this is set, `par_hr_factors` is also required. par_hr_factors : list-like, floats The multiplicative factors to get from `par_labels` to `par_hr_labels`. supplementary_method : callable A method that takes the parameters `par_values` and the countrate of the experiment as an argument and returns a dictinoary of supplementary information. """ # Checks assert len(par_labels) == len(par_values) for p in [par_vary, par_boundaries, par_hr_labels, par_hr_factors, ]: if p is not None: assert len(p) == len( par_values), "Number of parameters must match!" if par_hr_factors is None or par_hr_labels is None: assert par_hr_factors is None, "human readable requires two parameter" assert par_hr_labels is None, "human readable requires two parameter" if par_vary is None: # Set par_vary par_vary = np.zeros(len(par_values), dtype=bool) par_vary[0] = True if par_hr_factors is None: # Set equal to labels par_hr_labels = par_labels par_hr_factors = np.ones_like(par_values) model = {} model["Parameters"] = [par_labels, par_values, par_vary, par_hr_labels, par_hr_factors] model["Definitions"] = [modelid, comp, name, fctn, mtype] if supplementary_method is not None: model["Supplements"] = supplementary_method if par_boundaries is not None: model["Boundaries"] = par_boundaries if par_constraints is not None: model["Constraints"] = par_constraints append_model(model) # Pack all variables values = list() # Also create a dictionary, key is modelid valuedict = dict() # Pack all models models = list() # Also create a dictinary modeldict = dict() # A dictionary for supplementary data: supplement = dict() # A dictionary containing model boundaries boundaries = dict() # shorttypes are used by the GUI to abbreviate the model type shorttype = dict() shorttype[u"Confocal (Gaussian)"] = u"CFoc" shorttype[u"Confocal (Gaussian) and triplet"] = u"CFoc" shorttype[u"Confocal (Gaussian) with double triplet"] = u"CFoc" shorttype[u"TIR (Gaussian/Exp.)"] = u"TIR CFoc" shorttype[u"TIR (□xσ/Exp.)"] = u"TIR □xσ" # Create a list for the differentiation between the models # This should make everything look a little cleaner modeltypes = {} modeltypes[u"User"] = [] # The order of the import matters! # These models perform the integration by themselves using the `model_setup` method. from . import model_confocal_3d from . import model_confocal_3d_3d from . import model_confocal_2d from . import model_confocal_2d_2d from . import model_confocal_3d_2d from . import model_confocal_t_3d from . import model_confocal_t_3d_3d from . import model_confocal_t_2d from . import model_confocal_t_2d_2d from . import model_confocal_t_3d_2d from . import model_confocal_t_3d_3d_3d from . import model_confocal_t_3d_3d_2d from . import model_confocal_tt_3d from . import model_confocal_tt_3d_3d from . import model_confocal_tt_2d from . import model_confocal_tt_2d_2d from . import model_confocal_tt_3d_2d # These lines can be removed once all models are converted # from `MODEL_*` to `model_` syntax. modeltypes[u"TIR (Gaussian/Exp.)"] = [6014, 6034, 6033] modeltypes[u"TIR (□xσ/Exp.)"] = [6010, 6023, 6000, 6022, 6020, 6021] # Old models from . import MODEL_TIRF_gaussian_1C from . import MODEL_TIRF_gaussian_3D2D from . import MODEL_TIRF_gaussian_3D3D from . import MODEL_TIRF_1C from . import MODEL_TIRF_2D2D from . import MODEL_TIRF_3D2D from . import MODEL_TIRF_3D3D from . import MODEL_TIRF_3D2Dkin_Ries # Load all models from the imported "MODEL_*" submodules # These are the models that were not imported using the `model_setup` method. for g in list(globals().keys()): if g.startswith("MODEL_") and hasattr(globals()[g], "Modelarray"): append_model(globals()[g].Modelarray) pycorrfit-1.1.7/pycorrfit/models/model_confocal_3d_2d.py0000664000372000037200000000632213554642611024212 0ustar travistravis00000000000000import numpy as np from .control import model_setup from .cp_confocal import twod, threed from .cp_mix import double_pnum # 3D + 2D + T def CF_Gxyz_3d2d_gauss(parms, tau): u""" Two-component, two- and three-dimensional diffusion with a Gaussian laser profile. particle2D = (1-F)/ (1+τ/τ_2D) particle3D = α²*F/( (1+τ/τ_3D) * sqrt(1+τ/(τ_3D*SP²))) norm = (1-F + α*F)² G = 1/n*(particle1 + particle2)/norm + offset *parms* - a list of parameters. Parameters (parms[i]): [0] n Effective number of particles in confocal volume (n = n2D+n3D) [1] τ_2D Diffusion time of surface bound particls [2] τ_3D Diffusion time of freely diffusing particles [3] F Fraction of molecules of the freely diffusing species (n3D = n*F), 0 <= F <= 1 [4] SP SP=z₀/r₀ Structural parameter, describes elongation of the confocal volume [5] α Relative molecular brightness of particle 3D compared to particle 2D (α = q3D/q2D) [6] offset *tau* - lag time """ n = parms[0] taud2D = parms[1] taud3D = parms[2] F = parms[3] SP = parms[4] alpha = parms[5] off = parms[6] g = double_pnum(n=n, F1=1-F, alpha=alpha, comp1=twod, comp2=threed, kwargs1={"tau": tau, "taudiff": taud2D}, kwargs2={"tau": tau, "taudiff": taud3D, "SP": SP}, ) G = off + g return G def supplements(parms, countrate=None): u"""Supplementary parameters: Effective number of freely diffusing particles in 3D solution: [7] n3D = n*F Effective number particles diffusing on 2D surface: [9] n2D = n*(1-F) """ # We can only give you the effective particle number n = parms[0] F3d = parms[3] Info = list() # The enumeration of these parameters is very important for # plotting the normalized curve. Countrate must come out last! Info.append([u"n3D", n*F3d]) Info.append([u"n2D", n*(1.-F3d)]) if countrate is not None: # CPP cpp = countrate/n Info.append([u"cpp [kHz]", cpp]) return Info parms = [ 25, # n 240, # taud2D 0.1, # taud3D 0.5, # F3D 7, # SP 1.0, # alpha 0.0 # offset ] # Boundaries # strictly positive boundaries = [[0, np.inf]]*len(parms) # F boundaries[3] = [0, .9999999999999] boundaries[-1] = [-np.inf, np.inf] model_setup( modelid=6036, name="Separate 3D and 2D diffusion (confocal)", comp="3D+2D", mtype="Confocal (Gaussian)", fctn=CF_Gxyz_3d2d_gauss, par_labels=[ u"n", u"τ_2D [ms]", u"τ_3D [ms]", u"F_3D", u"SP", u"\u03b1"+" (q_3D/q_2D)", u"offset" ], par_values=parms, par_vary=[True, True, True, True, False, False, False], par_boundaries=boundaries, par_constraints=[[2, "<", 1]], supplementary_method=supplements ) pycorrfit-1.1.7/pycorrfit/models/model_confocal_t_3d_3d_2d.py0000664000372000037200000001326113554642611025123 0ustar travistravis00000000000000import numpy as np from .control import model_setup from .cp_confocal import twod, threed from .cp_triplet import trip from .cp_mix import triple_pnum def CF_Gxyz_gauss_3D3D2DT(parms, tau): u""" Two three-dimensional and one two-dimensional free diffusion with a Gaussian laser profile, including a triplet component. The triplet factor takes into account a blinking term. Set *T* or *τ_trip* to 0, if no triplet component is wanted. F₃ = 1-F₁-F₂ particle1 = F₁/( (1+τ/τ₁) * sqrt(1+τ/(τ₁*SP²))) particle2 = α₂₁² * F₂/( (1+τ/τ₂) * sqrt(1+τ/(τ₂*SP²))) particle3 = α₃₁² * F₃/( (1+τ/τ₃)) triplet = 1 + T/(1-T)*exp(-τ/τ_trip) norm = (F₁ + α₂₁*F₂ + α₃₁*F₃)² G = 1/n*(particle1 + particle2 + particle3)*triplet/norm + offset *parms* - a list of parameters. Parameters (parms[i]): [0] n Effective number of particles in confocal volume (n = n₁+n₂+n₃) [1] τ₁ Diffusion time of particle species 1 (3D) [2] τ₂ Diffusion time of particle species 2 (3D) [3] τ₃ Diffusion time of particle species 3 (2D) [4] F₁ Fraction of molecules of species 1 (n₁ = n*F₁) 0 <= F₁ <= 1 [5] F₂ Fraction of molecules of species 2 (n₂ = n*F₂) 0 <= F₂ <= 1 [6] SP SP=z₀/r₀, Structural parameter, describes elongation of the confocal volume [7] α₂₁ Relative molecular brightness of particle 2 compared to particle 1 (α = q₂/q₁) [8] α₃₁ Relative molecular brightness of particle 3 compared to particle 1 (α = q₃/q₁) [9] τ_trip Characteristic residence time in triplet state [10] T Fraction of particles in triplet (non-fluorescent) state 0 <= T < 1 [11] offset *tau* - lag time """ n = parms[0] taud1 = parms[1] taud2 = parms[2] taud3 = parms[3] F1 = parms[4] F2 = parms[5] SP = parms[6] alpha21 = parms[7] alpha31 = parms[8] tautrip = parms[9] T = parms[10] off = parms[11] g = triple_pnum(n=n, F1=F1, F2=F2, alpha21=alpha21, alpha31=alpha31, comp1=threed, comp2=threed, comp3=twod, kwargs1={"tau": tau, "taudiff": taud1, "SP": SP}, kwargs2={"tau": tau, "taudiff": taud2, "SP": SP}, kwargs3={"tau": tau, "taudiff": taud3}, ) tr = trip(tau=tau, T=T, tautrip=tautrip) G = off + g*tr return G def supplements(parms, countrate=None): u"""Supplementary parameters: [12] n₁ = n*F₁ Particle number of species 1 (3D) [13] n₂ = n*F₂ Particle number of species 2 (3D) [14] n₃ = n*F₃ Particle number of species 3 (2D; F₃ = 1-F₁-F₂) """ # We can only give you the effective particle number n = parms[0] F1 = parms[4] F2 = parms[5] Info = list() # The enumeration of these parameters is very important for # plotting the normalized curve. Countrate must come out last! Info.append([u"n\u2081", n*F1]) Info.append([u"n\u2082", n*F2]) Info.append([u"n\u2083", n*(1-F1-F2)]) if countrate is not None: # CPP cpp = countrate/n Info.append(["cpp [kHz]", cpp]) return Info parms = [25, # n 5, # taud1 1000, # taud2 11000, # taud3 0.5, # F1 0.01, # F2 5, # SP 1.0, # alpha21 1.0, # alpha31 0.001, # tautrip 0.01, # T 0.0 # offset ] # Boundaries # strictly positive boundaries = [[0, np.inf]]*len(parms) # F boundaries[4] = [0, .9999999999999] boundaries[5] = [0, .9999999999999] # T boundaries[10] = [0, .9999999999999] boundaries[-1] = [-np.inf, np.inf] model_setup( modelid=6082, name="Twofold 3D and one 2D diffusion with triplet (confocal)", comp="T+3D+3D+2D", mtype="Confocal (Gaussian) and triplet", fctn=CF_Gxyz_gauss_3D3D2DT, par_labels=[ u"n", u"τ"+u"\u2081"+" [ms]", u"τ"+u"\u2082"+" [ms]", u"τ"+u"\u2083"+" [ms]", u"F"+u"\u2081", u"F"+u"\u2082", u"SP", u"\u03b1\u2082\u2081", u"\u03b1\u2083\u2081", u"τ_trip [ms]", u"T", u"offset" ], par_values=parms, par_vary=[True, True, False, True, False, True, False, False, False, False, True, False], par_boundaries=boundaries, par_constraints=[[2, ">", 1], [3, ">", 2], [9, "<", 1], [5, 4, "<", "1"]], par_hr_labels=[ u"n", u"τ"+u"\u2081"+" [ms]", u"τ"+u"\u2082"+" [ms]", u"τ"+u"\u2083"+" [ms]", u"F"+u"\u2081", u"F"+u"\u2082", u"SP", u"\u03b1\u2082\u2081", u"\u03b1\u2083\u2081", u"τ_trip [µs]", u"T", u"offset" ], par_hr_factors=[ 1., # n 1., # taud1 1., # taud2 1., # taud3 1., # F1 1., # F2 1., # SP 1., # alpha21 1., # alpha31 1000., # tautrip [µs] 1., # T 1. # offset ], supplementary_method=supplements ) pycorrfit-1.1.7/pycorrfit/models/__init__.py0000664000372000037200000002127313554642611022034 0ustar travistravis00000000000000"""PyCorrFit - module "models" Define all models and set initial parameters. Each model has a unique ID. This ID is very important: 1. It is a wxWidgets ID. 2. It is used in the saving of sessions to identify a model. It is very important, that model IDs do NOT change in newer versions of PyCorrFit, because it would not be possible to restore older PyCorrFit sessions. Dimensionless representation: unit of time : 1 ms unit of inverse time: 10³ /s unit of distance : 100 nm unit of Diff.coeff : 10 µm²/s unit of inverse area: 100 /µm² unit of inv. volume : 1000 /µm³ """ import copy import sys import warnings import numpy as np from .classes import Model from .control import values, valuedict, models, modeldict, modeltypes, supplement, boundaries, shorttype def GetHumanReadableParms(model, parameters): """ From a set of parameters that have internal units e.g. [100 nm], Calculate the parameters in human readable units e.g. [nm]. Uses modeldict from this module. *model* - an integer ID of a model *parameters* - a list/array of parameters (all parameters of that model) Returns: New Units, New Parameters """ stdparms = valuedict[model] if len(stdparms) == 5: # This means we have extra information on the model # Return some human readable stuff OldParameters = 1.*np.array(parameters) Facors = 1.*np.array(stdparms[4]) NewParameters = 1.*OldParameters*Facors NewUnits = stdparms[3] return NewUnits, NewParameters else: # There is no info about human readable stuff, or it is already human # readable. return stdparms[0], parameters def GetHumanReadableParameterDict(model, names, parameters): """ From a set of parameters that have internal units e.g. [100 nm], Calculate the parameters in human readable units e.g. [nm]. Uses modeldict from this module. In contrast to *GetHumanReadableParms* this function accepts single parameter names and does not need the full array of parameters. *model* - an integer ID of a model *name* - the names of the parameters to be translated, order should be same as in *parameters* - a list of parameters Returns: New Units, New Parameters """ stdparms = valuedict[model] if len(stdparms) == 5: # This means we have extra information on the model # Return some human readable stuff # Check for list: if isinstance(names, str): names = [names] parameters = [parameters] retstring = True else: retstring = False # Create new lists NewUnits = list() NewParameters = list() for i in np.arange(len(stdparms[0])): for j in np.arange(len(names)): if names[j] == stdparms[0][i]: NewUnits.append(stdparms[3][i]) NewParameters.append(stdparms[4][i]*parameters[j]) if retstring == True: NewUnits = NewUnits[0] NewParameters = NewParameters[0] return NewUnits, NewParameters else: # There is no info about human readable stuff, or it is already human # readable. return names, parameters def GetInternalFromHumanReadableParm(model, parameters): """ This is the inverse of *GetHumanReadableParms* *model* - an integer ID of a model *parameters* - a list/array of parameters Returns: New Units, New Parameters """ stdparms = valuedict[model] if len(stdparms) == 5: # This means we have extra information on the model # and can convert to internal values OldParameters = 1.*np.array(parameters) Facors = 1./np.array(stdparms[4]) # inverse NewParameters = 1.*OldParameters*Facors NewUnits = stdparms[0] return NewUnits, NewParameters else: # There is no info about human readable stuff. The given # parameters have not been converted befor using # *GetHumanReadableParms*. return stdparms[0], parameters def GetModelType(modelid): """ Given a modelid, get the type of model function (Confocal, TIR-Conf., TIR-□, User) """ if modelid >= 7000: return u"User" else: for key in modeltypes.keys(): mlist = modeltypes[key] if mlist.count(modelid) == 1: try: return shorttype[key] except: warnings.warn("No shorttype defined for `{}`.".format(key)) return key def GetModelFunctionFromId(modelid): return modeldict[modelid][3] def GetModelParametersFromId(modelid): return valuedict[modelid][1] def GetModelFitBoolFromId(modelid): return valuedict[modelid][2] def GetMoreInfo(modelid, Page): """ This functino is called by someone who has already calculated some stuff or wants to know more about the model he is looking at. *modelid* is an ID of a model. *Page* is a wx.flatnotebook page. Returns: More information about a model in form of a list. """ # Background signal average bgaverage = None # Get the parameters from the current page. parms = Page.active_parms[1] Info = list() corr = Page.corr if corr.is_ac: if len(corr.traces) == 1: countrate = corr.traces[0].countrate else: countrate = None # First import the supplementary parameters of the model # The order is important for plot normalization and session # saving as of version 0.7.8 # Try to get the dictionary entry of a model # Background information if len(corr.backgrounds) == 1: bgaverage = corr.backgrounds[0].countrate # Now set the correct countrate # We already printed the countrate, so there's no harm done. if countrate is not None and bgaverage is not None: # might be that there is no countrate. relativecountrate = countrate - bgaverage else: relativecountrate = countrate # In case of cross correlation, we don't show this kind of # information. try: # This function should return all important information # that can be calculated from the given parameters. # We need the relativecountrate to compute the CPP. func_info = supplement[modelid] data = func_info(parms, relativecountrate) for item in data: Info.append([item[0], item[1]]) except KeyError: # No information available pass if countrate is not None: # Measurement time duration = corr.traces[0].duration/1000 Info.append(["duration [s]", duration]) # countrate has to be printed before background. # Background might overwrite countrate. Info.append(["avg. signal [kHz]", corr.traces[0].countrate]) else: # Cross correlation curves usually have two traces. Since we # do not know how to compute the cpp, we will pass the argument # "None" as the countrate. # First import the supplementary parameters of the model # The order is important for plot normalization and session # saving as of version 0.7.8 # Try to get the dictionary entry of a model try: # This function should return all important information # that can be calculated from the given parameters. func_info = supplement[modelid] data = func_info(parms, None) for item in data: Info.append([item[0], item[1]]) except KeyError: # No information available pass if len(corr.traces) == 2: # Measurement time duration = corr.traces[0].duration/1000 Info.append(["duration [s]", duration]) # countrate has to be printed before background. # Background might overwrite countrate. Info.append(["avg. signal A [kHz]", corr.traces[0].countrate]) Info.append(["avg. signal B [kHz]", corr.traces[1].countrate]) if len(Info) == 0: # If nothing matched until now: return None else: return Info def GetPositionOfParameter(model, name): """ Returns an integer corresponding to the position of the label of a parameter in the model function """ stdparms = valuedict[model] for i in np.arange(len(stdparms[0])): if name == stdparms[0][i]: return int(i) pycorrfit-1.1.7/pycorrfit/models/MODEL_TIRF_3D2D.py0000775000372000037200000001047713554642611022504 0ustar travistravis00000000000000import numpy as np import scipy.special as sps def wixi(x): """ Complex Error Function (Faddeeva/Voigt). w(i*x) = exp(x**2) * ( 1-erf(x) ) This function is called by other functions within this module. We are using the scipy.special.wofz module which calculates w(z) = exp(-z**2) * ( 1-erf(-iz) ) z = i*x """ z = x*1j wixi = sps.wofz(z) # We should have a real solution. Make sure nobody complains about # some zero-value imaginary numbers. return np.real_if_close(wixi) # 3D + 2D no binding TIRF # model 6020 def CF_Gxyz_TIR_square_3d2d(parms, tau, wixi=wixi): u""" Two-component two- and three-dimensional diffusion with a square-shaped lateral detection area taking into account the size of the point spread function; and an exponential decaying profile in axial direction. *parms* - a list of parameters. Parameters (parms[i]): [0] D_3D Diffusion coefficient of freely diffusing species [1] D_2D Diffusion coefficient of surface bound species [2] σ Lateral size of the point spread function σ = σ₀ * λ / NA [3] a Side size of the square-shaped detection area [4] d_eva Evanescent penetration depth [5] C_3D Concentration of freely diffusing species [6] C_2D Concentration of surface bound species [7] α Relative molecular brightness of 3D particle compared to 2D particle (α = q3D/q2D) *tau* - lag time """ D_3D = parms[0] D_2D = parms[1] sigma = parms[2] a = parms[3] kappa = 1/parms[4] Conc_3D = parms[5] Conc_2D = parms[6] alpha = parms[7] # First the 2D-diffusion at z=0 var1 = sigma**2+D_2D*tau AA = 2*np.sqrt(var1)/(a**2*np.sqrt(np.pi)) BB = np.exp(-a**2/(4*(var1))) - 1 CC = sps.erf(a/(2*np.sqrt(var1)))/a # gx = AA*BB+CC # gxy = gx**2 # g2D = Conc_2D * gxy g2D = Conc_2D * (AA*BB+CC)**2 # Second the 3D diffusion for z>0 # Axial correlation x = np.sqrt(D_3D*tau)*kappa w_ix = wixi(x) gz = np.sqrt(D_3D*tau/np.pi) - (2*D_3D*tau*kappa**2 - 1)/(2*kappa) * w_ix # Lateral correlation gx1 = 2/(a**2*np.sqrt(np.pi)) * np.sqrt(sigma**2+D_3D*tau) * \ (np.exp(-a**2/(4*(sigma**2+D_3D*tau))) - 1) gx2 = 1/a * sps.erf(a / (2*np.sqrt(sigma**2 + D_3D*tau))) gx = gx1 + gx2 gxy = gx**2 # Non normalized correlation function g3D = alpha**2 * Conc_3D * gxy * gz # Finally the Prefactor F = alpha * Conc_3D / kappa + Conc_2D G = (g3D + g2D) / F**2 return G # 3D-2D Model TIR m_tir_3d_2d_mix_6020 = [6020, u"3D+2D", "Separate 3D and 2D diffusion, 3D TIR", CF_Gxyz_TIR_square_3d2d] labels_6020 = [u"D_3D [10 µm²/s]", u"D_2D [10 µm²/s]", u"σ [100 nm]", u"a [100 nm]", u"d_eva [100 nm]", u"C_3D [1000 /µm³]", u"C_2D [100 /µm²]", u"\u03b1"+" (q3D/q2D)" ] values_6020 = [ 50.0, # D_3D [10 µm²/s] 0.81, # D_2D [10 µm²/s] 2.3, # σ [100 nm] 7.50, # a [100 nm] 1.0, # d_eva [100 nm] 0.01, # conc.3D [1000 /µm³] 0.03, # conc.2D [100 /µm²] 1 # alpha ] # For user comfort we add values that are human readable. # Theese will be used for output that only humans can read. labels_human_readable_6020 = [ u"D_3D [µm²/s]", u"D_2D [µm²/s]", u"σ [nm]", u"a [nm]", u"d_eva [nm]", u"C_3D [1/µm³]", u"C_2D [1/µm²]", u"\u03b1"+" (q3D/q2D)" ] values_factor_human_readable_6020 = [ 10, # D_3D [µm²/s] 10, # D_2D [10 µm²/s] 100, # σ [100 nm] 100, # a [100 nm] 100, # d_eva [100 nm] 1000, # conc.3D [1000 /µm³] 100, # conc.2D [100 /µm²] 1 # alpha ] valuestofit_6020 = [False, True, False, False, False, False, True, False] parms_6020 = [labels_6020, values_6020, valuestofit_6020, labels_human_readable_6020, values_factor_human_readable_6020] model1 = dict() model1["Parameters"] = parms_6020 model1["Definitions"] = m_tir_3d_2d_mix_6020 model1["Boundaries"] = [[0, np.inf]]*len(values_6020) model1["Constraints"] = [[1, "<", 0]] Modelarray = [model1] pycorrfit-1.1.7/pycorrfit/models/cp_confocal.py0000664000372000037200000000033013554642611022532 0ustar travistravis00000000000000"""Confocal fitting model components""" import numpy as np def threed(tau, taudiff, SP): return 1/((1 + tau/taudiff) * np.sqrt(1+tau/(taudiff*SP**2))) def twod(tau, taudiff): return 1/((1 + tau/taudiff)) pycorrfit-1.1.7/pycorrfit/models/model_confocal_tt_2d.py0000664000372000037200000000606213554642611024334 0ustar travistravis00000000000000import numpy as np from .control import model_setup from .cp_confocal import twod from .cp_triplet import trip # 2D + TT Gauß # Model 6003 def CF_Gxy_gauss_2DTT(parms, tau): u""" Two-dimensional free diffusion with a Gaussian laser profile, including two triplet components. The triplet factor takes into account a blinking term. Set *T* or *τ_trip* to 0, if no triplet component is wanted. particle = 1/(1+τ/τ_diff) triplet1 = 1 + T₁/(1-T₁)*exp(-τ/τ_trip₁) triplet2 = 1 + T₂/(1-T₂)*exp(-τ/τ_trip₂) G = 1/n*particle*triplet1*triplet2 + offset *parms* - a list of parameters. Parameters (parms[i]): [0] n Effective number of particles in confocal volume [1] τ_diff Diffusion time of particle [2] τ_trip₁ Characteristic residence time in triplet state [3] T₁ Fraction of particles in triplet (non-fluorescent) state 0 <= T < 1 [4] τ_trip₂ Characteristic residence time in triplet state [5] T₂ Fraction of particles in triplet (non-fluorescent) state 0 <= T < 1 [6] offset *tau* - lag time """ n = parms[0] taud = parms[1] tautrip1 = parms[2] T1 = parms[3] tautrip2 = parms[4] T2 = parms[5] off = parms[6] g = twod(tau=tau, taudiff=taud) tr1 = trip(tau=tau, T=T1, tautrip=tautrip1) tr2 = trip(tau=tau, T=T2, tautrip=tautrip2) G = off + 1/n * g * tr1 * tr2 return G def supplements(parms, countrate=None): # We can only give you the effective particle number n = parms[0] Info = list() if countrate is not None: # CPP cpp = countrate/n Info.append(["cpp [kHz]", cpp]) return Info parms = [ 4, # n .4, # taud 0.001, # tautrip1 0.01, # T1 0.002, # tautrip2 0.01, # T2 0.0 # offset ] # Boundaries # strictly positive boundaries = [[0, np.inf]]*len(parms) # T boundaries[3] = [0, .9999999999999] boundaries[5] = [0, .9999999999999] # offset boundaries[-1] = [-np.inf, np.inf] model_setup( modelid=6003, name="2D diffusion with double triplet (confocal)", comp="T+T+2D", mtype="Confocal (Gaussian) with double triplet", fctn=CF_Gxy_gauss_2DTT, par_labels=[ u"n", u"τ_diff [ms]", u"τ_trip₁ [ms]", u"T₁", u"τ_trip₂ [ms]", u"T₂", u"offset" ], par_values=parms, par_vary=[True, True, False, False, False, False, False], par_boundaries=boundaries, par_constraints=[[2, "<", 1], [4, ">", 2]], par_hr_labels=[ u"n", u"τ_diff [ms]", u"τ_trip₁ [µs]", u"T₁", u"τ_trip₂ [µs]", u"T₂", u"offset" ], par_hr_factors=[ 1., # n 1., # taudiff 1000., # tautrip1 [µs] 1., # T1 1000., # tautrip2 [µs] 1., # T2 1. # offset ], supplementary_method=supplements ) pycorrfit-1.1.7/pycorrfit/models/cp_mix.py0000664000372000037200000000430413554642611021550 0ustar travistravis00000000000000"""Mixed components for fitting models""" def double_pnum(n, F1, alpha, comp1, kwargs1, comp2, kwargs2, ): u""" Double component models where the particle number is given in the model i.e. for confocal diffusion models. Parameters ---------- n : float Total particle number F1 : float Fraction of particle species 1 alpha : float Relative molecular brightness of particle 2 compared to particle 1 (α = q₂/q₁) comp1, comp2 : callables The model functions for each of the components. kwargs1, kwargs2 : dicts The keyword arguments for `comp1` and `comp2` """ norm = (F1 + alpha*(1-F1))**2 g1 = F1 * comp1(**kwargs1) g2 = alpha**2 * (1-F1) * comp2(**kwargs2) G = 1/n * (g1 + g2) / norm return G def triple_pnum(n, F1, F2, alpha21, alpha31, comp1, kwargs1, comp2, kwargs2, comp3, kwargs3 ): u""" Double component models where the particle number is given in the model i.e. for confocal diffusion models. Parameters ---------- n : float Total particle number F1, F2 : float Fraction of particle species 1 and 2. This infers that F3 = 1 - F1 - F2 alpha21 : float Relative molecular brightness of particle 2 compared to particle 1 (α₂₁ = q₂/q₁) alpha31 : float Relative molecular brightness of particle 3 compared to particle 1 comp1, comp2, comp3 : callables The model functions for each of the components. kwargs1, kwargs2, kwargs3 : dicts The keyword arguments for `comp1`, `comp2`, and `comp3`. """ alpha11 = 1 F3 = 1 - F1 - F2 if F3 < 0: F3 = 0 norm = (F1*alpha11 + F2*alpha21 + F3*alpha31)**2 g1 = alpha11**2 * F1 * comp1(**kwargs1) g2 = alpha21**2 * F2 * comp2(**kwargs2) g3 = alpha31**2 * F3 * comp3(**kwargs3) G = 1/n * (g1 + g2 + g3) / norm return G pycorrfit-1.1.7/pycorrfit/models/model_confocal_tt_3d.py0000664000372000037200000000655413554642611024343 0ustar travistravis00000000000000import numpy as np from .control import model_setup from .cp_confocal import threed from .cp_triplet import trip # 3D + Triplet Gauß # Model 6013 def CF_Gxyz_gauss_3DTT(parms, tau): u""" Three-dimensional free diffusion with a Gaussian laser profile, including two triplet components. The triplet factor takes into account a blinking term. Set *T* or *τ_trip* to 0, if no triplet component is wanted. particle1 = 1/( (1+τ/τ_diff) * sqrt(1+τ/(τ_diff*SP²))) triplet1 = 1 + T₁/(1-T₁)*exp(-τ/τ_trip₁) triplet2 = 1 + T₂/(1-T₂)*exp(-τ/τ_trip₂) norm = (F₁ + α*(1-F₁))² G = 1/n*particle*triplet1*triplet2 + offset *parms* - a list of parameters. Parameters (parms[i]): [0] n Effective number of particles in confocal volume [1] τ_diff Diffusion time of particle [2] SP SP=z₀/r₀, Structural parameter, describes elongation of the confocal volume [3] τ_trip₁ Characteristic residence time in triplet state [4] T₁ Fraction of particles in triplet (non-fluorescent) state 0 <= T < 1 [5] τ_trip₂ Characteristic residence time in triplet state [6] T₂ Fraction of particles in triplet (non-fluorescent) state 0 <= T < 1 [7] offset *tau* - lag time """ n = parms[0] taudiff = parms[1] SP = parms[2] tautrip1 = parms[3] T1 = parms[4] tautrip2 = parms[5] T2 = parms[6] off = parms[7] g = threed(tau=tau, taudiff=taudiff, SP=SP) tr1 = trip(tau=tau, T=T1, tautrip=tautrip1) tr2 = trip(tau=tau, T=T2, tautrip=tautrip2) G = off + 1/n * g * tr1 * tr2 return G def supplements(parms, countrate=None): # We can only give you the effective particle number n = parms[0] Info = list() if countrate is not None: # CPP cpp = countrate/n Info.append(["cpp [kHz]", cpp]) return Info parms = [ 4, # n .4, # taud1 5, # SP 0.001, # tautrip1 0.01, # T1 0.002, # tautrip2 0.01, # T2 0.0 # offset ] # Boundaries # strictly positive boundaries = [[0, np.inf]]*len(parms) # T boundaries[4] = [0, .9999999999999] boundaries[6] = [0, .9999999999999] # offset boundaries[-1] = [-np.inf, np.inf] model_setup( modelid=6009, name="3D diffusion with double triplet (confocal)", comp="T+T+3D", mtype="Confocal (Gaussian) with double triplet", fctn=CF_Gxyz_gauss_3DTT, par_labels=[ u"n", u"τ_diff [ms]", u"SP", u"τ_trip₁ [ms]", u"T₁", u"τ_trip₂ [ms]", u"T₂", u"offset" ], par_values=parms, par_vary=[True, True, False, False, False, False, False, False], par_boundaries=boundaries, par_constraints=[[3, "<", 1], [5, ">", 3]], par_hr_labels=[ u"n", u"τ_diff [ms]", u"SP", u"τ_trip₁ [µs]", u"T₁", u"τ_trip₂ [µs]", u"T₂", u"offset" ], par_hr_factors=[ 1., # n 1., # taudiff 1., # SP 1000., # tautrip1 [µs] 1., # T1 1000., # tautrip2 [µs] 1., # T2 1. # offset ], supplementary_method=supplements ) pycorrfit-1.1.7/pycorrfit/gui/0000775000372000037200000000000013554643106017217 5ustar travistravis00000000000000pycorrfit-1.1.7/pycorrfit/gui/doc.py0000775000372000037200000001004413554642611020340 0ustar travistravis00000000000000"""Documentation and program specific information""" import platform import sys import lmfit import matplotlib import numpy import scipy import simplejson import sympy import wx import yaml import pycorrfit from pycorrfit import readfiles, meta from pycorrfit.readfiles import read_pt3_scripts __version__ = pycorrfit.__version__ def GetLocationOfChangeLog(filename="CHANGELOG"): """Find location of CHANGELOG""" return meta.get_file_location(filename) def GetLocationOfDocumentation(filename="PyCorrFit_doc.pdf"): """Return the location of the documentation if there is any""" return meta.get_file_location(filename) def info(version): """Return info about PyCorrFit and what it can do""" textwin = u""" Copyright 2011-2012 Paul Mueller, Biotec - TU Dresden A versatile tool for fitting and analyzing correlation curves. Dimensionless representation: unit of time : 1 ms unit of inverse time: 1000 /s unit of distance : 100 nm unit of Diff.coeff : 10 um^2/s unit of inverse area: 100 /um^2 unit of inv. volume : 1000 /um^3 """ textlin = u""" © 2011-2012 Paul Müller, Biotec - TU Dresden A versatile tool for fitting and analyzing correlation curves. Dimensionless representation: unit of time : 1 ms unit of inverse time: 1000 /s unit of distance : 100 nm unit of Diff.coeff : 10 µm²/s unit of inverse area: 100 /µm² unit of inv. volume : 1000 /µm³ """ if platform.system() != 'Linux': texta = textwin else: texta = textlin one = u" PyCorrFit version "+version+"\n\n" two = u"\n\n Supported file types:" keys = readfiles.filetypes_dict.keys() keys = sorted(list(keys)) for item in keys: if item.split("|")[0] != readfiles.ALL_SUP_STRING: two = two + "\n - "+item.split("|")[0] lizenz = "" for line in licence().splitlines(): lizenz += " "+line+"\n" return one + lizenz + texta + two def licence(): return """PyCorrFit is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 2 of the License, or (at your option) any later version. PyCorrFit is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see . """ def SoftwareUsed(): """Return some Information about the software used for this program""" text = "Python "+sys.version +\ "\n\nModules:" +\ "\n - cython " +\ "\n - lmfit "+lmfit.__version__ +\ "\n - matplotlib "+matplotlib.__version__ +\ "\n - NumPy "+numpy.__version__ +\ "\n - PyYAML "+yaml.__version__ +\ "\n - SciPy "+scipy.__version__ +\ "\n - simplejson "+simplejson.__version__ +\ "\n - sympy "+sympy.__version__ +\ "\n - wxPython "+wx.__version__ # Other software text += "\n\nOther software:" +\ "\n - FCS_point_correlator ({})".format( read_pt3_scripts.version) +\ "\n PicoQuant file format for Python by Dominic Waithe" if hasattr(sys, 'frozen'): pyinst = "\n\nThis executable has been created using PyInstaller." text += pyinst if 'Anaconda' in sys.version or "Continuum Analytics" in sys.version: conda = "\n\nPowered by Anaconda" text += conda return text # Standard homepage HomePage = "http://pycorrfit.craban.de/" # Changelog filename ChangeLog = "CHANGELOG" StaticChangeLog = GetLocationOfChangeLog(ChangeLog) # Github homepage GitChLog = "https://raw.github.com/FCS-analysis/PyCorrFit/master/CHANGELOG" GitHome = "https://github.com/FCS-analysis/PyCorrFit" GitWiki = "https://github.com/FCS-analysis/PyCorrFit/wiki" pycorrfit-1.1.7/pycorrfit/gui/wxutils.py0000664000372000037200000001123513554642611021312 0ustar travistravis00000000000000"""Module wxutils""" import re import string import numpy as np import wx def float2string_nsf(fval, n=7): """ Truncate a float to n significant figures and return nice string. Arguments: q : a float n : desired number of significant figures Returns: String with only n s.f. and trailing zeros. """ # sgn=np.sign(fval) try: if fval == 0: npoint = n else: q = abs(fval) k = int(np.ceil(np.log10(q/n))) # prevent negative significant digits npoint = max(0, n-k) string = "{:.{}f}".format(fval, npoint) except: string = "{}".format(fval) return string def nice_string(string): """ Convert a string of a float created by `float2string_nsf` to something nicer. i.e. - 1.000000 -> 1 - 1.010000 -> 1.010 """ if string.count(".") and len(string.split(".")[1].replace("0", "")) == 0: return "{:d}".format(int(float(string))) else: olen = len(string) newstring = string.rstrip("0") if olen > len(newstring): string = newstring+"0" return string class PCFFloatValidator(wx.PyValidator): def __init__(self, flag=None, pyVar=None): wx.PyValidator.__init__(self) self.flag = flag self.Bind(wx.EVT_CHAR, self.OnChar) def Clone(self): return PCFFloatValidator(self.flag) def Validate(self, win): tc = self.GetWindow() val = tc.GetValue() for x in val: if x not in string.digits: return False return True def OnChar(self, event): """ Filter the characters that are put in the control. TODO: - check for strings that do not make sense - 2e-4.4 - 2e--3 - 3-1+5 """ key = event.GetKeyCode() ctrl = event.GetEventObject() # Get the actual string from the object curval = wx.TextCtrl.GetValue(ctrl) if key < wx.WXK_SPACE or key == wx.WXK_DELETE or key > 255: event.Skip() return char = chr(key) char = char.replace(",", ".") onlyonce = [".", "e", "i", "n", "f"] if char in onlyonce and curval.count(char): # not allowed return if (char in string.digits or char in ["+", "-"]+onlyonce): event.Skip() return if not wx.Validator_IsSilent(): wx.Bell() # Returning without calling event.Skip eats the event before it # gets to the text control return class PCFFloatTextCtrl(wx.TextCtrl): def __init__(self, *args, **kwargs): wx.TextCtrl.__init__(self, *args, validator=PCFFloatValidator(), size=(110, -1), style=wx.TE_PROCESS_ENTER, **kwargs) self.Bind(wx.EVT_ENTER_WINDOW, self.OnMouseEnter) self.Bind(wx.EVT_LEAVE_WINDOW, self.OnMouseLeave) self._PCFvalue = 0.0 def OnMouseEnter(self, e): self.SetFocus() self.SetSelection(-1, 0) def OnMouseLeave(self, e): self.SetSelection(0, 0) self.SetInsertionPoint(0) def SetValue(self, value): self._PCFvalue = value string = PCFFloatTextCtrl.float2string(value) wx.TextCtrl.SetValue(self, string) def GetValue(self): string = wx.TextCtrl.GetValue(self) if string == PCFFloatTextCtrl.float2string(self._PCFvalue): # use internal value: more accurate #print("internal", self._PCFvalue) return self._PCFvalue else: # new value #print("external", string) return PCFFloatTextCtrl.string2float(string) @staticmethod def float2string(value): """ inverse of string2float with some tweaks """ value = float2string_nsf(value) value = nice_string(value) return value @staticmethod def string2float(string): """ Remove any characters that are not in [+-{0-9},.] and show a decent float value. """ if string.count("inf"): if string[0] == "-": return -np.inf else: return np.inf # allow comma string = string.replace(",", ".") # allow only one decimal point string = string[::-1].replace(".", "", string.count(".")-1)[::-1] try: string = "{:.12f}".format(float(string)) except: pass # remove letters string = re.sub(r'[^\d.-]+', '', string) if len(string) == 0: string = "0" return float(string) pycorrfit-1.1.7/pycorrfit/gui/contribute.py0000664000372000037200000000517013554642611021752 0ustar travistravis00000000000000import wx from wx.lib import sized_controls import wx.lib.agw.hyperlink as hl class ContributeDialog(sized_controls.SizedDialog): def __init__(self, *args, **kwargs): super(ContributeDialog, self).__init__(title="Contribute to PyCorrFit", *args, **kwargs) pane = self.GetContentsPane() pane_btns = sized_controls.SizedPanel(pane) pane_btns.SetSizerType('vertical') pane_btns.SetSizerProps(align="center") wx.StaticText(pane_btns, label=contribute_text) wx.StaticText(pane_btns, label="\nLinks:") for ii, link in enumerate(contribute_links): hl.HyperLinkCtrl(pane_btns, -1, "[{}] {}".format(ii+1, link), URL=link) wx.StaticText(pane_btns, label="\n") button_ok = wx.Button(pane_btns, label='OK') button_ok.Bind(wx.EVT_BUTTON, self.on_button) button_ok.SetFocus() button_ok.SetSizerProps(expand=True) self.Fit() def on_button(self, event): if self.IsModal(): self.EndModal(event.EventObject.Id) else: self.Close() contribute_text = """ PyCorrFit has no funding and a vanishingly small developer community. My personal objective is to keep PyCorrFit operational on Linux and Windows which is currently limited by the free time I have available. An active community is very important for an open source project such as PyCorrFit. You can help this community grow (and thus help improve PyCorrFit) in numerous ways: 1. \tTell your colleagues and peers about PyCorrFit. One of them \tmight be able to contribute to the project. 2. \tIf you need a new feature in PyCorrFit, publicly announce a \tbounty for its implementation. 3. \tIf your research heavily relies on FCS, please consider \tdiverting some of your resources to the development \tof PyCorrFit. 4. \tYou don't have to be a Python programmer to contribute. If \tyou are familiar with reStrucuredText or LaTeX, you might \tbe able to help out with the online documentation. 5. \tPlease cite: Müller et al. Bioinformatics 30(17): 2532–2533, \t2014 [1]. 6. \tSponsor me on GitHub [2] or donate via Liberapay [3]. If you are planning to contribute to PyCorrFit, please contact me via the PyCorrFit issue page on GitHub such that we may coordinate a pull request. Thank you! Paul Müller (October 2019) """ contribute_links = [ "https://dx.doi.org/10.1093/bioinformatics/btu328", "https://github.com/sponsors/paulmueller", "https://liberapay.com/paulmueller", ] pycorrfit-1.1.7/pycorrfit/gui/threaded_progress.py0000664000372000037200000001766213554642611023311 0ustar travistravis00000000000000"""A progress bar with an abort button that works for long running processes""" import time import threading import traceback as tb import sys import wx class KThread(threading.Thread): """A subclass of threading.Thread, with a kill() method. https://web.archive.org/web/20130503082442/http://mail.python.org/pipermail/python-list/2004-May/281943.html The KThread class works by installing a trace in the thread. The trace checks at every line of execution whether it should terminate itself. So it's possible to instantly kill any actively executing Python code. However, if your code hangs at a lower level than Python, then the thread will not actually be killed until the next Python statement is executed. """ def __init__(self, *args, **keywords): threading.Thread.__init__(self, *args, **keywords) self.killed = False def start(self): """Start the thread.""" self.__run_backup = self.run self.run = self.__run # Force the Thread to install our trace. threading.Thread.start(self) def __run(self): """Hacked run function, which installs the trace.""" sys.settrace(self.globaltrace) self.__run_backup() self.run = self.__run_backup def globaltrace(self, frame, why, arg): if why == 'call': return self.localtrace else: return None def localtrace(self, frame, why, arg): if self.killed: if why == 'line': raise SystemExit() return self.localtrace def kill(self): self.killed = True class WorkerThread(KThread): """Worker Thread Class.""" def __init__(self, target, args, kwargs): """Init Worker Thread Class.""" KThread.__init__(self) self.traceback = None self.target = target self.args = args self.kwargs = kwargs # This starts the thread running on creation, but you could # also make the GUI thread responsible for calling this self.start() def run(self): """Run Worker Thread.""" try: self.target(*self.args, **self.kwargs) except: self.traceback = tb.format_exc() class ThreadedProgressDlg(object): def __init__(self, parent, targets, args=None, kwargs={}, title="Dialog title", messages=None, time_delay=2): """ This class implements a progress dialog that can abort during a function call, as opposed to the stock wx.ProgressDialog. Parameters ---------- parent : wx object The parent of the progress dialog. targets : list of callables The methods that will be called in each step in the progress. args : list The arguments to the targets. Should match length of targets. kwargs : dict or list of dicts Keyword arguments to the targets. If dict, then the same dict is used for all targets. title : str The title of the progress dialog. messages : list of str The message displayed for each target. Should match length of targets. time_delay : float Time after which the dialog should be displayed. The default is 2s, which means that a dialog is only displayed after 2s or earlier, if the overall progress seems to be taking longer than 2s. Arguments --------- aborted : bool Whether the progress was aborted by the user. index_aborted : None or int The index in `targets` at which the progress was aborted. finalize : callable A method that will be called after fitting. Can be overriden by subclasses. Notes ----- The progress dialog is only displayed when `time_delay` is or seems shorter than the total running time of the progress. If the progress is not displayed, then a busy cursor is displayed. """ wx.BeginBusyCursor() if hasattr(targets, "__call__"): targets = [targets] nums = len(targets) if not args: args = [()]*nums elif isinstance(args, list): # convenience-convert args to tuples if not isinstance(args[0], tuple): args = [(t,) for t in args] if isinstance(kwargs, dict): kwargs = [kwargs]*nums if not messages: messages = ["item {} of {}".format(a+1, nums) for a in range(nums)] time1 = time.time() sty = wx.PD_SMOOTH | wx.PD_AUTO_HIDE | wx.PD_CAN_ABORT if len(targets) > 1: sty = sty | wx.PD_REMAINING_TIME dlgargs = [title, "initializing..."] dlgkwargs = {"maximum": nums, "parent": parent, "style": sty} dlg = None self.aborted = False self.index_aborted = None for jj in range(nums): init = True worker = WorkerThread(target=targets[jj], args=args[jj], kwargs=kwargs[jj]) while worker.is_alive() or init: if (time.time()-time1 > time_delay or (time.time()-time1)/(jj+1)*nums > time_delay ) and not dlg: dlg = wx.ProgressDialog(*dlgargs, **dlgkwargs) wx.EndBusyCursor() init = False time.sleep(.01) if dlg: if len(targets) == 1: # no progress bar but pulse # cont = dlg.UpdatePulse(messages[jj])[0] cont = dlg.Update(0, messages[jj])[0] else: # show progress until end cont = dlg.Update(jj+1, messages[jj])[0] if cont == False: dlg.Destroy() worker.kill() self.aborted = True break if self.aborted: self.aborted = True self.index_aborted = jj break if worker.traceback: dlg.Destroy() self.aborted = True self.index_aborted = jj raise Exception(worker.traceback) if dlg: dlg.Hide() dlg.Destroy() wx.EndBusyCursor() wx.BeginBusyCursor() self.finalize() wx.EndBusyCursor() def finalize(self): """ You may override this method in subclasses. """ pass if __name__ == "__main__": # GUI Frame class that spins off the worker thread class MainFrame(wx.Frame): """Class MainFrame.""" def __init__(self, parent, aid): """Create the MainFrame.""" wx.Frame.__init__(self, parent, aid, 'Thread Test') # Dumb sample frame with two buttons but = wx.Button(self, wx.ID_ANY, 'Start Progress', pos=(0, 0)) self.Bind(wx.EVT_BUTTON, self.OnStart, but) def OnStart(self, event): """Start Computation.""" # Trigger the worker thread unless it's already busy arguments = [test_class(a) for a in range(10)] def method(x): x.arg *= 1.1 time.sleep(1) tp = ThreadedProgressDlg(self, [method]*len(arguments), arguments) print(tp.index_aborted) print([a.arg for a in arguments]) class MainApp(wx.App): """Class Main App.""" def OnInit(self): """Init Main App.""" self.frame = MainFrame(None, -1) self.frame.Show(True) self.SetTopWindow(self.frame) return True class test_class(object): def __init__(self, arg): self.arg = arg app = MainApp(0) app.MainLoop() pycorrfit-1.1.7/pycorrfit/gui/misc.py0000664000372000037200000000667713554642611020544 0ustar travistravis00000000000000"""Module misc: Non-science related code. """ import codecs import numpy as np import wx # The icon file was created with # img2py -i -n Main PyCorrFit_icon.png icon.py from . import icon # Contains the program icon def parseString2Pagenum(parent, string, nodialog=False): """ Parse a string with a list of pagenumbers to an integer list with page numbers. e.g. "1-3,5,7" --> [1,2,3,5,7] parent is important """ listFull = string.split(",") PageNumbers = list() try: for item in listFull: pagerange = item.split("-") start = pagerange[0].strip() start = int("".join(filter(type(start).isdigit, start))) end = pagerange[-1].strip() end = int("".join(filter(type(end).isdigit, end))) for i in np.arange(end-start+1)+start: PageNumbers.append(i) PageNumbers.sort() return PageNumbers except: if nodialog is False: errstring = "Invalid syntax in page selection: "+string +\ ". Please use a comma separated list with" +\ " optional dashes, e.g. '1-3,6,8'." try: wx.MessageDialog(parent, errstring, "Error", style=wx.ICON_ERROR | wx.OK | wx.STAY_ON_TOP) except: raise ValueError(errstring) else: raise ValueError(errstring) return None def parsePagenum2String(pagenumlist): """ Make a string with dashes and commas from a list of pagenumbers. e.g. [1,2,3,5,7] --> "1-3,5,7" """ if len(pagenumlist) == 0: return "" # Make sure we have integers newlist = list() for num in pagenumlist: newlist.append(int(num)) newlist.sort() # begin string string = str(newlist[0]) # iteration through list: dash = False for i in np.arange(len(newlist)-1)+1: if dash == True: if newlist[i]-1 == newlist[i-1]: pass else: string += "-"+str(newlist[i-1])+", "+str(newlist[i]) dash = False else: if newlist[i]-1 == newlist[i-1]: if newlist[i]-2 == newlist[i-2]: dash = True elif len(newlist) != i+1 and newlist[i]+1 == newlist[i+1]: dash = True else: string += ", "+str(newlist[i]) dash = False else: dash = False string += ", "+str(newlist[i]) # Put final number if newlist[i] == newlist[-1]: if parseString2Pagenum(None, string)[-1] != newlist[i]: if dash == True: string += "-"+str(newlist[i]) else: string += ", "+str(newlist[i]) return string def removewrongUTF8(name): newname = u"" for char in name: try: codecs.decode(char, "UTF-8") except: pass else: newname += char return newname def getMainIcon(pxlength=32): """ *pxlength* is the side length in pixels of the icon """ # Set window icon iconBMP = icon.getMainBitmap() # scale image = wx.Bitmap.ConvertToImage(iconBMP) image = image.Scale(pxlength, pxlength, wx.IMAGE_QUALITY_HIGH) iconBMP = wx.Bitmap(image) iconICO = wx.Icon(iconBMP) return iconICO pycorrfit-1.1.7/pycorrfit/gui/page.py0000664000372000037200000010747013554642611020516 0ustar travistravis00000000000000"""Module frontend The frontend displays the GUI (Graphic User Interface). All functions and modules are called from here. """ import numpy as np import warnings import wx import wx.lib.plot as plot import wx.lib.scrolledpanel as scrolled from pycorrfit import models as mdls from pycorrfit import fit from pycorrfit import Correlation from . import tools from . import wxutils class FittingPanel(wx.Panel): """ Those are the Panels that show the fitting dialogs with the Plots. """ def __init__(self, parent, counter, modelid, active_parms, tau=None): """ Initialize with given parameters. """ wx.Panel.__init__(self, parent=parent, id=wx.ID_ANY) self.parent = parent self.corr = Correlation(fit_model=modelid) if tau is not None: self.corr.lag_time = tau # active_parameters: # [0] labels # [1] values # [2] bool values to fit self.corr.fit_parameters = active_parms[1] self.corr.fit_parameters_variable = active_parms[2] self._bgselected = None self._bg2selected = None self.FitKnots = 5 # number of knots for spline fit or similiars self.weighted_fittype_id = 0 # integer (drop down item) self.weighted_nuvar = 3 # bins for std-dev. (left and rigth) # The weights that are plotted in the page # This is set by the PlotAll function self.weights_plot_fill_area = None # A list containing page numbers that share parameters with this page. # This parameter is defined by the global fitting tool and is saved in # sessions. self.GlobalParameterShare = [] # Counts number of Pages already created: self.counter = counter # Has inital plot been performed? # Call PlotAll("init") to set this to true. If it is true, then # nothing will be plotted if called with "init" self.InitialPlot = False # Model we are using # Tool statistics uses this list: self.StatisticsCheckboxes = None # Splitter window # Sizes size = parent.notebook.GetSize() tabsize = 33 size[1] = size[1] - tabsize self.sizepanelx = 270 canvasx = size[0]-self.sizepanelx+5 sizepanel = (self.sizepanelx, size[1]) sizecanvas = (canvasx, size[1]) self.sp = wx.SplitterWindow(self, size=size, style=wx.SP_3DSASH) # This is necessary to prevent "Unsplit" of the SplitterWindow: self.sp.SetMinimumPaneSize(1) # Settings Section (left side) #self.panelsettings = wx.Panel(self.sp, size=sizepanel) self.panelsettings = scrolled.ScrolledPanel(self.sp, size=sizepanel) self.panelsettings.SetupScrolling(scroll_x=False) # Setting up Plot (correlation + chi**2) self.spcanvas = wx.SplitterWindow(self.sp, size=sizecanvas, style=wx.SP_3DSASH) # This is necessary to prevent "Unsplit" of the SplitterWindow: self.spcanvas.SetMinimumPaneSize(1) # y difference in pixels between Auocorrelation and Residuals cupsizey = size[1]*4/5 # Calculate initial data self.calculate_corr() # Draw the settings section self.settings() # Load default values self.apply_parameters_reverse() # Upper Plot for plotting of Correlation Function self.canvascorr = plot.PlotCanvas(self.spcanvas) self.canvascorr.logScale = (True, False) self.canvascorr.enableZoom = True self.PlotAll(event="init", trigger="tab_init") self.canvascorr.SetSize((canvasx, cupsizey)) # Lower Plot for plotting of the residuals self.canvaserr = plot.PlotCanvas(self.spcanvas) self.canvaserr.logScale = (True, False) self.canvaserr.enableZoom = True self.canvaserr.SetSize((canvasx, size[1]-cupsizey)) self.spcanvas.SplitHorizontally(self.canvascorr, self.canvaserr, cupsizey) self.sp.SplitVertically(self.panelsettings, self.spcanvas, self.sizepanelx) # Bind resizing to resizing function. wx.EVT_SIZE(self, self.OnSize) @property def active_parms(self): names = self.corr.fit_model.parameters[0] parms = self.corr.fit_parameters bools = self.corr.fit_parameters_variable return [names, parms, bools] @property def IsCrossCorrelation(self): return self.corr.is_cc @property def modelid(self): return self.corr.fit_model.id @property def prevent_batch_modification(self): return self.wxCBPreventBatchParms.GetValue() @prevent_batch_modification.setter def prevent_batch_modification(self, value): self.wxCBPreventBatchParms.SetValue(value) @property def title(self): return self.tabtitle.GetValue() @title.setter def title(self, title): self.tabtitle.SetValue(title.strip()) self.corr.title = title.strip() @property def traceavg(self): warnings.warn("Trace average always set to none!") return None @property def tracecc(self): if self.corr.is_cc and len(self.corr.traces) != 0: return self.corr.traces else: return None @property def bgselected(self): return self._bgselected @bgselected.setter def bgselected(self, value): if value is None: self.corr.backgrounds = [] return # check paren.Background and get id background = self.parent.Background[value] self.corr.background_replace(0, background) self._bgselected = value @property def bg2selected(self): return self._bg2selected @bg2selected.setter def bg2selected(self, value): if value is None: if self.corr.is_cc: self.corr.backgrounds = [] return # check paren.Background and get id background = self.parent.Background[value] self.corr.background_replace(1, background) self._bg2selected = value def apply_parameters(self, event=None): """ Read the values from the GUI form and write it to the pages parameters / correlation class. This function is called when the "Apply" button is hit. """ modelid = self.corr.fit_model.id parameters = list() parameters_variable = list() # Read parameters from form and update self.active_parms[1] for i in np.arange(len(self.spincontrol)): parameters.append(1*self.spincontrol[i].GetValue()) parameters_variable.append(self.checkboxes[i].GetValue()) self.corr.fit_parameters_variable = np.array(parameters_variable, dtype=bool) # As of version 0.7.5: we want the units to be displayed # human readable - the way they are displayed # in the Page info tool. # Here: Convert human readable units to program internal # units parmsconv = mdls.GetInternalFromHumanReadableParm( modelid, np.array(parameters))[1] self.corr.fit_parameters = parmsconv # Fitting parameters self.weighted_nuvar = self.Fitbox[5].GetValue() self.weighted_fittype_id = self.Fitbox[1].GetSelection() fitbox_value = self.Fitbox[1].GetValue() if self.weighted_fittype_id == -1: # User edited knot number Knots = fitbox_value Knots = "".join(filter(lambda x: x.isdigit(), Knots)) if Knots == "": Knots = "5" self.weighted_fittype_id = 1 self.FitKnots = str(Knots) fit_weight_type = "spline{}".format(self.FitKnots) fit_weight_data = self.weighted_nuvar elif self.weighted_fittype_id == 1: Knots = fitbox_value Knots = "".join(filter(lambda x: x.isdigit(), Knots)) self.FitKnots = int(Knots) fit_weight_type = "spline{}".format(self.FitKnots) fit_weight_data = self.weighted_nuvar elif self.weighted_fittype_id == 0: fit_weight_type = "none" fit_weight_data = None elif self.weighted_fittype_id == 2: fit_weight_type = "model function" fit_weight_data = self.weighted_nuvar else: # fitbox_selection > 2: fit_weight_type = fitbox_value self.corr.fit_weight_type = fitbox_value fit_weight_data = self.corr.fit_weight_data # Fitting algorithm keys = fit.GetAlgorithmStringList()[0] idalg = self.AlgorithmDropdown.GetSelection() self.corr.fit_algorithm = keys[idalg] self.corr.fit_weight_type = fit_weight_type self.corr.fit_weight_data = fit_weight_data # If parameters have been changed because of the check_parms # function, write them back. self.apply_parameters_reverse() def apply_parameters_reverse(self, event=None): """ Read the values from the pages parameters and write it to the GUI form. """ modelid = self.corr.fit_model.id # # As of version 0.7.5: we want the units to be displayed # human readable - the way they are displayed # in the Page info tool. # # Here: Convert program internal units to # human readable units parameters = mdls.GetHumanReadableParms(modelid, self.corr.fit_parameters)[1] parameters_variable = self.corr.fit_parameters_variable # Write parameters to the form on the Page for i in np.arange(len(self.active_parms[1])): self.spincontrol[i].SetValue(parameters[i]) self.checkboxes[i].SetValue(parameters_variable[i]) # Fitting parameters self.Fitbox[5].SetValue(self.weighted_nuvar) idf = self.weighted_fittype_id List = self.Fitbox[1].GetItems() List[1] = "spline ("+str(self.FitKnots)+" knots)" self.Fitbox[1].SetItems(List) self.Fitbox[1].SetSelection(idf) # Normalization if self.corr.normparm is None: normsel = 0 else: normsel = self.corr.normparm + 1 self.AmplitudeInfo[2].SetSelection(normsel) # Fitting algorithm keys = fit.GetAlgorithmStringList()[0] idalg = keys.index(self.corr.fit_algorithm) self.AlgorithmDropdown.SetSelection(idalg) self.updateChi2() def calculate_corr(self): """ Calculate model correlation function """ return self.corr.modeled def Fit_enable_fitting(self): """ Enable the fitting button and the weighted fit control""" # self.Fitbox = [ fitbox, weightedfitdrop, fittext, fittext2, # fittextvar, fitspin, buttonfit, textalg, # self.AlgorithmDropdown] self.Fitbox[0].Enable() self.Fitbox[1].Enable() self.Fitbox[6].Enable() self.Fitbox[7].Enable() self.Fitbox[8].Enable() def Fit_function(self, event=None, noplots=False, trigger=None): """ Calls the fit function. `noplots=True` prevents plotting of spline fits `trigger` is passed to page.PlotAll. If trigger is "fit_batch", then `noplots` is set to `True`. """ tools.batchcontrol.FitProgressDlg(self, self) def Fit_finalize(self, trigger): """ Things that need be done after fitting """ # Reset list of globally shared parameters, because we are only # fitting this single page now. # TODO: # - also remove this page from the GlobalParameterShare list of # the other pages self.GlobalParameterShare = [] # Update spin-control values self.apply_parameters_reverse() # Plot everything try: self.PlotAll(trigger=trigger) except OverflowError: # Sometimes parameters are just bad and # we still want the user to use the # program. warnings.warn("Could not plot canvas.") # update displayed chi2 self.updateChi2() def Fit_WeightedFitCheck(self, event=None): """ Enable Or disable variance calculation, dependent on "Weighted Fit" checkbox """ # self.Fitbox=[ fitbox, weightedfitdrop, fittext, fittext2, fittextvar, # fitspin, buttonfit ] weighted = (self.Fitbox[1].GetSelection() != 0) # In the case of "Average" we do not enable the # "Calculation of variance" part. if weighted is True and self.Fitbox[1].GetValue() != "Average": self.Fitbox[2].Enable() self.Fitbox[3].Enable() self.Fitbox[4].Enable() self.Fitbox[5].Enable() else: self.Fitbox[2].Disable() self.Fitbox[3].Disable() self.Fitbox[4].Disable() self.Fitbox[5].Disable() def MakeStaticBoxSizer(self, boxlabel): """ Create a Box with check boxes (fit yes/no) and possibilities to change initial values for fitting. Parameters: *boxlabel*: The name of the box (is being displayed) *self.active_parms[0]*: A list of things to put into the box Returns: *sizer*: The static Box *check*: The (un)set checkboxes *spin*: The spin text fields """ modelid = self.corr.fit_model.id box = wx.StaticBox(self.panelsettings, label=boxlabel) sizer = wx.StaticBoxSizer(box, wx.VERTICAL) check = list() spin = list() # # As of version 0.7.5: we want the units to be displayed # human readable - the way they are displayed # in the Page info tool. # labels = mdls.GetHumanReadableParms(modelid, self.corr.fit_parameters)[0] sizerh = wx.GridSizer(len(labels), 2, 2, 2) for label in labels: checkbox = wx.CheckBox(self.panelsettings, label=label) # We needed to "from wx.lib.agw import floatspin" to get this: spinctrl = wxutils.PCFFloatTextCtrl(self.panelsettings) sizerh.Add(spinctrl) sizerh.Add(checkbox) # Put everything into lists to be able to refer to it later check.append(checkbox) spin.append(spinctrl) sizer.Add(sizerh) return sizer, check, spin def OnAmplitudeCheck(self, event=None): """ Enable/Disable BG rate text line. New feature introduced in 0.7.8 """ modelid = self.corr.fit_model.id # Normalization to a certain parameter in plots # Find all parameters that start with an "N" # ? and "C" ? # Create List normlist = list() normlist.append("None") # Add parameters parameterlist = list() for i in np.arange(len(self.active_parms[0])): label = self.active_parms[0][i] if label[0].lower() == "n": normlist.append("*"+label) parameterlist.append(i) # Add supplementary parameters # Get them from models supplement = mdls.GetMoreInfo(modelid, self) if supplement is not None: for i in np.arange(len(supplement)): label = supplement[i][0] if label[0].lower() == "n": normlist.append("*"+label) # Add the id of the supplement starting at the # number of fitting parameters of current page. parameterlist.append(i+len(self.active_parms[0])) normsel = self.AmplitudeInfo[2].GetSelection() if normsel in [0, -1]: # init or no normalization selected self.corr.normparm = None normsel = 0 else: self.corr.normparm = parameterlist[normsel-1] if len(parameterlist) > 0: self.AmplitudeInfo[2].Enable() self.AmplitudeInfo[3].Enable() else: self.AmplitudeInfo[2].Disable() self.AmplitudeInfo[3].Disable() # Set dropdown values self.AmplitudeInfo[2].SetItems(normlist) self.AmplitudeInfo[2].SetSelection(normsel) # Plot intensities # Quick reminder: # self.AmplitudeInfo = [ [intlabel1, intlabel2], # [bgspin1, bgspin2], # normtoNDropdown, textnor] # Signal self.AmplitudeInfo[0][0].SetValue("{:.4f}".format(0)) self.AmplitudeInfo[0][1].SetValue("{:.4f}".format(0)) for i in range(len(self.corr.traces)): S = self.corr.traces[i].countrate self.AmplitudeInfo[0][i].SetValue("{:.4f}".format(S)) if self.corr.is_cc: self.AmplitudeInfo[0][1].Enable() else: self.AmplitudeInfo[0][1].Disable() # Background # self.parent.Background[self.bgselected][i] # [0] average signal [kHz] # [1] signal name (edited by user) # [2] signal trace (tuple) ([ms], [kHz]) if len(self.corr.backgrounds) >= 1: self.AmplitudeInfo[1][0].SetValue( self.corr.backgrounds[0].countrate) else: self.AmplitudeInfo[1][0].SetValue(0) self.AmplitudeInfo[1][1].SetValue(0) if len(self.corr.backgrounds) == 2: self.AmplitudeInfo[1][1].SetValue( self.corr.backgrounds[1].countrate) else: self.AmplitudeInfo[1][1].SetValue(0) # Disable the second line in amplitude correction, if we have # autocorrelation only. boolval = self.corr.is_cc for item in self.WXAmplitudeCCOnlyStuff: item.Enable(boolval) def OnBGSpinChanged(self, e): """ Calls tools.background.ApplyAutomaticBackground to update background information """ # Quick reminder: # self.AmplitudeInfo = [ [intlabel1, intlabel2], # [bgspin1, bgspin2], # normtoNDropdown, textnor] if self.corr.is_cc: # update both self.bgselected and self.bg2selected bg = [float(self.AmplitudeInfo[1][0].GetValue()), float(self.AmplitudeInfo[1][1].GetValue())] sig = [float(self.AmplitudeInfo[0][0].GetValue()), float(self.AmplitudeInfo[0][1].GetValue())] # Make sure bg < sig for ii in range(len(bg)): if sig[ii] != 0: if bg[ii] > .99*sig[ii]: bg[ii] = .99*sig[ii] self.AmplitudeInfo[1][ii].SetValue(bg[ii]) else: # Only update self.bgselected bg = float(self.AmplitudeInfo[1][0].GetValue()) sig = float(self.AmplitudeInfo[0][0].GetValue()) # Make sure bg < sig if sig != 0: if bg > .99*sig: bg = .99*sig self.AmplitudeInfo[1][0].SetValue(bg) tools.background.ApplyAutomaticBackground(self, bg, self.parent) e.Skip() def OnTitleChanged(self, e): modelid = self.corr.fit_model.id pid = self.parent.notebook.GetPageIndex(self) if self.tabtitle.GetValue() == "": text = self.counter + mdls.modeldict[modelid][1] else: # How many characters of the the page title should be displayed # in the tab? We choose 9: AC1-012 plus 2 whitespaces text = self.counter + self.tabtitle.GetValue()[-9:] self.parent.notebook.SetPageText(pid, text) def OnSetRange(self, e): """ Open a new Frame where the parameter range can be set. Rewrites self.parameter_range Parameter ranges are treated like parameters: They are saved in sessions and applied in batch mode. """ # TODO: # - make range selector work with new class # We write a separate tool for that. # This tool does not show up in the Tools menu. if self.parent.RangeSelector is None: self.parent.RangeSelector = tools.RangeSelector(self) self.parent.RangeSelector.Bind(wx.EVT_CLOSE, self.parent.RangeSelector.OnClose) else: try: self.parent.RangeSelector.OnClose() except: pass self.parent.RangeSelector = None def OnSize(self, event): """ Resize the fitting Panel, when Window is resized. """ size = self.parent.notebook.GetSize() tabsize = 33 size[1] = size[1] - tabsize self.sp.SetSize(size) def PlotAll(self, event=None, trigger=None): """ This function plots the whole correlation and residuals canvas. We do: - Channel selection - Background correction - Apply Parameters (separate function) - Drawing of plots The `event` is usually just an event from buttons or similar wx objects. It can be "init", then some initial plotting is done before the data is handled. The `trigger` is passed to `self.parent.OnFNBPageChanged` so that tools can update their content accordingly. For more information on triggers, have a look at the doctring of the `tools` submodule. """ if event == "init": # We use this to have the page plotted at least once before # readout of parameters (e.g. startcrop, endcrop) # This is a performence tweak. if self.InitialPlot: return else: self.InitialPlot = True # Enable/Disable, set values frontend normalization self.OnAmplitudeCheck() # Apply parameters self.apply_parameters() # Calculate correlation function from parameters # Drawing of correlation plot # Plots corr.correlation_fit and the calcualted correlation function # self.datacorr into the upper canvas. # Create a line @ y=zero: zerostart = self.corr.lag_time_fit[0] zeroend = self.corr.lag_time_fit[-1] datazero = [[zerostart, 0], [zeroend, 0]] # Set plot colors width = 1 colexp = "grey" colfit = "blue" colweight = "cyan" lines = list() linezero = plot.PolyLine(datazero, colour='orange', width=width) lines.append(linezero) if self.corr.correlation is not None: if self.corr.is_weighted_fit and \ self.parent.MenuShowWeights.IsChecked(): try: weights = self.corr.fit_results["fit weights"] except: weights = self.corr.fit_weight_data if isinstance(weights, np.ndarray): # user might have selected a new weight type and # presses apply, do not try to display weights # if weights are from average or other, make sure that the # dimensions are correct if weights.shape[0] == self.corr.correlation.shape[0]: weights = weights[self.corr.fit_ival[0] :self.corr.fit_ival[1]] # perform some checks if np.allclose(weights, np.ones_like(weights)): weights = 0 elif weights.shape[0] != self.corr.modeled_fit.shape[0]: # non-matching weigths warnings.warn( "Unmatching weights found. Probably from previous data set.") weights = 0 # Add the weights to the graph. # This is done by drawing two lines. w = 1*self.corr.modeled_fit w1 = 1*w w2 = 1*w w1[:, 1] = w[:, 1] + weights w2[:, 1] = w[:, 1] - weights # crop w1 and w2 if corr.correlation_fit does not include all # data points. if np.all(w[:, 0] == self.corr.correlation_fit[:, 0]): pass else: raise ValueError( "This should not have happened: size of weights is wrong.") # Normalization with self.normfactor w1[:, 1] *= self.corr.normalize_factor w2[:, 1] *= self.corr.normalize_factor self.weights_plot_fill_area = [w1, w2] lineweight1 = plot.PolyLine(w1, legend='', colour=colweight, width=width) lines.append(lineweight1) lineweight2 = plot.PolyLine(w2, legend='', colour=colweight, width=width) lines.append(lineweight2) else: self.weights_plot_fill_area = None # Plot Correlation curves # Plot both, experimental and calculated data # Normalization with self.normfactor, new feature in 0.7.8 datacorr_norm = self.corr.modeled_plot linecorr = plot.PolyLine(datacorr_norm, legend='', colour=colfit, width=width) dataexp_norm = self.corr.correlation_plot lineexp = plot.PolyLine(dataexp_norm, legend='', colour=colexp, width=width) # Draw linezero first, so it is in the background lines.append(lineexp) lines.append(linecorr) PlotCorr = plot.PlotGraphics(lines, xLabel=u'lag time τ [ms]', yLabel=u'G(τ)') self.canvascorr.Draw(PlotCorr) # Calculate residuals resid_norm = self.corr.residuals_plot lineres = plot.PolyLine(resid_norm, legend='', colour=colfit, width=width) # residuals or weighted residuals? if self.corr.is_weighted_fit: yLabelRes = "weighted \nresiduals" else: yLabelRes = "residuals" PlotRes = plot.PlotGraphics([linezero, lineres], xLabel=u'lag time τ [ms]', yLabel=yLabelRes) self.canvaserr.Draw(PlotRes) else: # Amplitude normalization, new feature in 0.7.8 datacorr_norm = self.corr.modeled_plot linecorr = plot.PolyLine(datacorr_norm, legend='', colour='blue', width=1) PlotCorr = plot.PlotGraphics([linezero, linecorr], xLabel=u'Lag time τ [ms]', yLabel=u'G(τ)') self.canvascorr.Draw(PlotCorr) self.Refresh() self.parent.OnFNBPageChanged(trigger=trigger) def settings(self): """ Here we define, what should be displayed at the left side of the fitting page/tab. Parameters: """ modelid = self.corr.fit_model.id horizontalsize = self.sizepanelx-10 # Title # Create empty tab title mddat = mdls.modeldict[modelid] modelshort = mdls.GetModelType(modelid) titlelabel = u"Data set {} ({} {})".format( self.counter.strip(" :"), modelshort, mddat[1]) boxti = wx.StaticBox(self.panelsettings, label=titlelabel) sizerti = wx.StaticBoxSizer(boxti, wx.VERTICAL) sizerti.SetMinSize((horizontalsize, -1)) self.tabtitle = wx.TextCtrl(self.panelsettings, value="", size=(horizontalsize-20, -1)) self.Bind(wx.EVT_TEXT, self.OnTitleChanged, self.tabtitle) sizerti.Add(self.tabtitle, 0, wx.EXPAND, 0) # Create StaticBoxSizer box1, check, spin = self.MakeStaticBoxSizer("Model parameters") # Make the check boxes and spin-controls available everywhere self.checkboxes = check self.spincontrol = spin # # As of version 0.7.5: we want the units to be displayed # human readable - the way they are displayed # in the Page info tool. # labels, parameters = mdls.GetHumanReadableParms(modelid, self.active_parms[1]) parameterstofit = self.active_parms[2] # Set initial values given by user/programmer for Diffusion Model for i in np.arange(len(labels)): self.checkboxes[i].SetValue(parameterstofit[i]) self.spincontrol[i].SetValue(parameters[i]) # Put everything together self.panelsettings.sizer = wx.BoxSizer(wx.VERTICAL) self.panelsettings.sizer.Add(sizerti, 0, wx.EXPAND, 0) self.panelsettings.sizer.Add(box1, 0, wx.EXPAND, 0) # checkbox "Protect from batch control" self.wxCBPreventBatchParms = wx.CheckBox(self.panelsettings, -1, "prevent batch modification") box1.Add(self.wxCBPreventBatchParms) # buttons "Apply" and "Set range" horzs = wx.BoxSizer(wx.HORIZONTAL) buttonapply = wx.Button(self.panelsettings, label="Apply") self.Bind(wx.EVT_BUTTON, self.PlotAll, buttonapply) horzs.Add(buttonapply) buttonrange = wx.Button(self.panelsettings, label="Set range") self.Bind(wx.EVT_BUTTON, self.OnSetRange, buttonrange) horzs.Add(buttonrange) box1.Add(horzs) # Set horizontal size box1.SetMinSize((horizontalsize, -1)) # More info normbox = wx.StaticBox( self.panelsettings, label="Amplitude corrections") miscsizer = wx.StaticBoxSizer(normbox, wx.VERTICAL) miscsizer.SetMinSize((horizontalsize, -1)) # Intensities and Background sizeint = wx.FlexGridSizer(rows=3, cols=3, vgap=5, hgap=5) sizeint.Add(wx.StaticText(self.panelsettings, label="[kHz]")) sizeint.Add(wx.StaticText(self.panelsettings, label="Intensity")) sizeint.Add(wx.StaticText(self.panelsettings, label="Background")) sizeint.Add(wx.StaticText(self.panelsettings, label="Ch1")) intlabel1 = wx.TextCtrl(self.panelsettings) bgspin1 = wxutils.PCFFloatTextCtrl(self.panelsettings) bgspin1.Bind(wx.EVT_KILL_FOCUS, self.OnBGSpinChanged) bgspin1.Bind(wx.EVT_TEXT_ENTER, self.OnBGSpinChanged) sizeint.Add(intlabel1) intlabel1.SetEditable(False) sizeint.Add(bgspin1) chtext2 = wx.StaticText(self.panelsettings, label="Ch2") sizeint.Add(chtext2) intlabel2 = wx.TextCtrl(self.panelsettings) intlabel2.SetEditable(False) bgspin2 = wxutils.PCFFloatTextCtrl(self.panelsettings) bgspin2.Bind(wx.EVT_KILL_FOCUS, self.OnBGSpinChanged) sizeint.Add(intlabel2) sizeint.Add(bgspin2) miscsizer.Add(sizeint) # Normalize to n? textnor = wx.StaticText(self.panelsettings, label="Plot normalization") miscsizer.Add(textnor) normtoNDropdown = wx.ComboBox(self.panelsettings) self.Bind(wx.EVT_COMBOBOX, self.PlotAll, normtoNDropdown) miscsizer.Add(normtoNDropdown) self.AmplitudeInfo = [[intlabel1, intlabel2], [bgspin1, bgspin2], normtoNDropdown, textnor] self.WXAmplitudeCCOnlyStuff = [chtext2, intlabel2, bgspin2] self.panelsettings.sizer.Add(miscsizer, 0, wx.EXPAND, 0) # Add fitting Box fitbox = wx.StaticBox(self.panelsettings, label="Fitting options") fitsizer = wx.StaticBoxSizer(fitbox, wx.VERTICAL) fitsizer.SetMinSize((horizontalsize, -1)) # Add a checkbox for weighted fitting weightedfitdrop = wx.ComboBox(self.panelsettings) self.weightlist = ["no weights", "spline (5 knots)", "model function"] weightedfitdrop.SetItems(self.weightlist) weightedfitdrop.SetSelection(0) fitsizer.Add(weightedfitdrop) # WeightedFitCheck() Enables or Disables the variance part weightedfitdrop.Bind(wx.EVT_COMBOBOX, self.Fit_WeightedFitCheck) # Add the variance part. # In order to do a weighted fit, we need to calculate the variance # at each point of the experimental data array. # In order to do that, we need to know how many data points from left # and right of the interesting data point we want to include in that # calculation. fittext = wx.StaticText(self.panelsettings, label="Calculation of the variance") fitsizer.Add(fittext) fittext2 = wx.StaticText(self.panelsettings, label="from 2j+1 data points") fitsizer.Add(fittext2) fitsizerspin = wx.BoxSizer(wx.HORIZONTAL) fittextvar = wx.StaticText(self.panelsettings, label="j = ") fitspin = wx.SpinCtrl(self.panelsettings, -1, initial=3, min=1, max=100) fitsizerspin.Add(fittextvar) fitsizerspin.Add(fitspin) fitsizer.Add(fitsizerspin) # Add algorithm selection textalg = wx.StaticText(self.panelsettings, label="Algorithm") fitsizer.Add(textalg) self.AlgorithmDropdown = wx.ComboBox(self.panelsettings) items = fit.GetAlgorithmStringList()[1] self.AlgorithmDropdown.SetItems(items) self.Bind(wx.EVT_COMBOBOX, self.apply_parameters, self.AlgorithmDropdown) fitsizer.Add(self.AlgorithmDropdown) self.AlgorithmDropdown.SetMaxSize(weightedfitdrop.GetSize()) # Add button "Fit" and chi2 display fitbuttonsizer = wx.BoxSizer(wx.HORIZONTAL) buttonfit = wx.Button(self.panelsettings, label="Fit") self.Bind(wx.EVT_BUTTON, self.Fit_function, buttonfit) fitbuttonsizer.Add(buttonfit) # add shortcut acctbl = wx.AcceleratorTable( [(wx.ACCEL_CTRL, ord('F'), buttonfit.GetId())]) self.SetAcceleratorTable(acctbl) ## self.WXTextChi2 = wx.StaticText(self.panelsettings) # this StaticText is updated by `self.updateChi2()` fitbuttonsizer.Add(self.WXTextChi2, flag=wx.ALIGN_CENTER) fitsizer.Add(fitbuttonsizer) self.panelsettings.sizer.Add(fitsizer, 0, wx.EXPAND, 0) # Squeeze everything into the sizer self.panelsettings.SetSizer(self.panelsettings.sizer) # This is also necessary in Windows self.panelsettings.Layout() self.panelsettings.Show() # Make all the stuff available for everyone self.Fitbox = [fitbox, weightedfitdrop, fittext, fittext2, fittextvar, fitspin, buttonfit, textalg, self.AlgorithmDropdown] # Disable Fitting since no data has been loaded yet for element in self.Fitbox: element.Disable() self.panelsettings.sizer.Fit(self.panelsettings) self.parent.Layout() def updateChi2(self): """ updates the self.WXTextChi2 text control """ label = u"" if hasattr(self.corr, "fit_results"): if "chi2" in self.corr.fit_results: chi2 = self.corr.fit_results["chi2"] chi2str = wxutils.float2string_nsf(chi2, n=3) chi2str = wxutils.nice_string(chi2str) label = u" χ²={}".format(chi2str) # This does not work with wxPython 2.8.12: # self.WXTextChi2.SetLabelMarkup(u"{}".format(label)) self.WXTextChi2.SetLabel(u"{}".format(label)) pycorrfit-1.1.7/pycorrfit/gui/plotting.py0000664000372000037200000003512613554642611021440 0ustar travistravis00000000000000"""Module plotting Everything about plotting with matplotlib is located here. Be sure to install texlive-science and texlive-math-extra """ import warnings import matplotlib import numpy as np import re # We do catch warnings about performing this before matplotlib.backends stuff with warnings.catch_warnings(): warnings.simplefilter("ignore") matplotlib.use('WXAgg') # Tells matplotlib to use WxWidgets for dialogs # Text rendering with matplotlib from matplotlib import rcParams import matplotlib.pyplot as plt import matplotlib.gridspec as gridspec # For finding latex tools import unicodedata from .. import models as mdls from ..meta import find_program def greek2tex(char): """ Converts greek UTF-8 letters to latex """ #decchar = codecs.decode(char, "UTF-8") decchar = char repres = unicodedata.name(decchar).split(" ") # GREEK SMALL LETTER ALPHA if repres[0] == "GREEK" and len(repres) == 4: letter = repres[3].lower() if repres[1] != "SMALL": letter = letter[0].capitalize() + letter[1:] return "\\"+letter else: return char def escapechars(string): """ For latex output, some characters have to be escaped with a "\\" """ #string = codecs.decode(string, "UTF-8") escapechars = ["#", "$", "%", "&", "~", "_", "\\", "{", "}"] retstr = r"" for char in string: if char in escapechars: retstr += "\\" retstr += char elif char == "^": # Make a hat in latex without $$? retstr += r"$\widehat{~}$" else: retstr += char return retstr def latexmath(string): """ Format given parameters to nice latex. """ if string == "offset": # prohibit the very often "offset" to be displayed as variables return r"\mathrm{offset}" elif string == "SP": return r"\mathrm{SP}" #string = codecs.decode(string, "UTF-8") unicodechars = dict() #unicodechars["τ"] = r"\tau" #unicodechars["µ"] = r"\mu" unicodechars["²"] = r"^2" unicodechars["³"] = r"^3" unicodechars["₁"] = r"_1" unicodechars["₂"] = r"_2" unicodechars["₃"] = r"_3" unicodechars["₀"] = r"_0" #unicodechars["α"] = r"\alpha" # We need lambda in here, because unicode names it lamda sometimes. unicodechars["λ"] = r"\lambda" #unicodechars["η"] = r'\eta' unitchars = dict() unitchars["µ"] = r"\micro " items = string.split(" ", 1) a = items[0] if len(items) > 1: b = items[1] if b.count(u"µ"): # Use siunitx with the upright µ bnew = r"[\SI{}{" for char in b.strip("[]"): if char in unitchars.keys(): bnew += unitchars[char] else: bnew += char b = bnew+r"}]" else: b = "" anew = r"" for char in a: if char in unicodechars.keys(): anew += unicodechars[char] elif char != greek2tex(char): anew += greek2tex(char) else: anew += char # lower case lcitems = anew.split("_", 1) if len(lcitems) > 1: anew = lcitems[0]+"_{\\text{"+lcitems[1]+"}}" return anew + r" \hspace{0.3em} \mathrm{"+b+r"}" def genLatexText(parm, labels): """Generate the LaTeX text for the plot with handling multiple underscores in the labels. """ text = r'' for i in np.arange(len(parm)): line = r' {} &= {:.3g} \\'.format(latexmath(labels[i]), parm[i]) match = re.search(r'\\text\{(.*?)\}', line) if match: if '_' in match.groups()[0]: tmpstr = match.groups()[0].split('_') if ''.join(tmpstr).isnumeric(): line = line.replace(match.groups()[0], ''.join(tmpstr)) elif len(tmpstr) == 2: tmpstr = '$_'.join(tmpstr) + '$' line = line.replace(match.groups()[0], tmpstr) text += line return text def savePlotCorrelation(parent, dirname, Page, uselatex=False, verbose=False, show_weights=True): """ Save plot from Page into file Parameters: *parent* the parent window *dirname* directory to set on saving *Page* Page containing all variables *uselatex* Whether to use latex for the ploting or not. This function uses a hack in misc.py to change the function for saving the final figure. We wanted save in the same directory as PyCorrFit was working and the filename should be the tabtitle. """ # Close all other plots before commencing try: plt.close() except: pass # get data corr = Page.corr dataexp = corr.correlation_plot resid = corr.residuals_plot fit = corr.modeled_plot weights = Page.weights_plot_fill_area tabtitle = Page.tabtitle.GetValue() # fitlabel = ur"Fit model: "+str(mdls.modeldict[Page.modelid][0]) fitlabel = Page.corr.fit_model.name labelweights = r"Weights of fit" labels, parms = mdls.GetHumanReadableParms(Page.modelid, corr.fit_parameters) if dataexp is None: if tabtitle.strip() == "": fitlabel = Page.corr.fit_model.name else: fitlabel = tabtitle else: if tabtitle.strip() == "": tabtitle = "page"+str(Page.counter).strip().strip(":") if Page.corr.normparm is not None: fitlabel += r", normalized to " + \ Page.corr.fit_model.parameters[0][Page.corr.normparm] # Check if we can use latex for plotting: r1 = find_program("latex")[0] r2 = find_program("dvipng")[0] # Ghostscript r31 = find_program("gs")[0] r32 = find_program("mgs")[0] # from miktex r3 = max(r31, r32) if r1+r2+r3 < 3: uselatex = False if uselatex == True: rcParams['text.usetex'] = True rcParams['text.latex.unicode'] = True rcParams['font.family'] = 'serif' rcParams['text.latex.preamble'] = [r"""\usepackage{amsmath} \usepackage[utf8x]{inputenc} \usepackage{amssymb} \usepackage{siunitx}"""] fitlabel = r"{\normalsize "+escapechars(fitlabel)+r"}" tabtitle = r"{\normalsize "+escapechars(tabtitle)+r"}" labelweights = r"{\normalsize "+escapechars(labelweights)+r"}" else: rcParams['text.usetex'] = False # create plot # plt.plot(x, y, '.', label = 'original data', markersize=5) fig = plt.figure() wtit = "Correlation #{:04d}_{}".format(int(Page.counter.strip(":# ")), Page.title.strip()) fig.canvas.set_window_title(wtit) if resid is not None: gs = gridspec.GridSpec(2, 1, height_ratios=[5, 1]) ax = plt.subplot(gs[0]) else: ax = plt.subplot(111) # ax = plt.axes() ax.semilogx() # plot fit first plt.plot(fit[:, 0], fit[:, 1], '-', label=fitlabel, lw=1.5, color="blue") if dataexp is not None: plt.plot(dataexp[:, 0], dataexp[:, 1], '-', color="black", alpha=.7, label=tabtitle, lw=1) else: plt.xlabel(r'lag time $\tau$ [ms]') if weights is not None and show_weights is True: plt.fill_between(weights[0][:, 0], weights[0][:, 1], weights[1][:, 1], color='cyan') # fake legend: p = plt.Rectangle((0, 0), 0, 0, color='cyan', label=labelweights) ax.add_patch(p) plt.ylabel('correlation') if dataexp is not None: mind = np.min([dataexp[:, 1], fit[:, 1]]) maxd = np.max([dataexp[:, 1], fit[:, 1]]) else: mind = np.min(fit[:, 1]) maxd = np.max(fit[:, 1]) ymin = mind - (maxd - mind)/20. ymax = maxd + (maxd - mind)/20. ax.set_ylim(bottom=ymin, top=ymax) xmin = np.min(fit[:, 0]) xmax = np.max(fit[:, 0]) ax.set_xlim(xmin, xmax) # Add some nice text: if uselatex == True and len(parms) != 0: text = r"" text += r'\[' text += r'\begin{split}' text += genLatexText(parms, labels) # According to issue #54, we remove fitting errors from plots # if errparms is not None: # keys = errparms.keys() # keys.sort() # for key in keys: # text += r' \Delta '+latexmath(key)+r" &= " + str(errparms[key]) +r' \\ ' text += r' \end{split} ' text += r' \] ' else: text = r"" for i in np.arange(len(parms)): text += u"{} = {:.3g}\n".format(labels[i], parms[i]) # According to issue #54, we remove fitting errors from plots # if errparms is not None: # keys = errparms.keys() # keys.sort() # for key in keys: # text += "Err "+key+" = " + str(errparms[key]) +"\n" logmax = np.log10(xmax) logmin = np.log10(xmin) logtext = 0.6*(logmax-logmin)+logmin xt = 10**(logtext) yt = 0.3*ymax plt.text(xt, yt, text, size=12) if resid is not None: ax2 = plt.subplot(gs[1]) #ax2 = plt.axes() ax2.semilogx() if Page.corr.is_weighted_fit: if uselatex == True: lb = r"\newline \indent " else: lb = "\n" yLabelRes = "weighted " + lb + "residuals" else: yLabelRes = "residuals" minx = np.min(resid[:, 0]) maxx = np.max(resid[:, 0]) miny = np.min(resid[:, 1]) maxy = np.max(resid[:, 1]) plt.hlines(0, minx, maxx, colors="orange") plt.plot(resid[:, 0], resid[:, 1], '-', color="black", alpha=.85, label=yLabelRes, lw=1) plt.xlabel(r'lag time $\tau$ [ms]') plt.ylabel(yLabelRes, multialignment='center') ax2.set_xlim(minx, maxx) maxy = max(abs(maxy), abs(miny)) ax2.set_ylim(-maxy, maxy) ticks = ax2.get_yticks() ax2.set_yticks([ticks[0], ticks[-1], 0]) # Hack # We need this for hacking. See edclasses. fig.canvas.HACK_parent = parent fig.canvas.HACK_fig = fig fig.canvas.HACK_Page = Page fig.canvas.HACK_append = ".png" # Legend outside of plot # Decrease size of plot to fit legend box = ax.get_position() ax.set_position([box.x0, box.y0 + box.height * 0.2, box.width, box.height * 0.9]) if resid is not None: box2 = ax2.get_position() ax2.set_position([box2.x0, box2.y0 + box.height * 0.2, box2.width, box2.height]) ax.legend(loc='upper center', bbox_to_anchor=(0.5, -0.55), prop={'size': 9}) if verbose == True: plt.show() else: # If WXAgg is not used for some reason, then our hack does not work # and we must use e.g. TkAgg try: fig.canvas.toolbar.save() except AttributeError: fig.canvas.toolbar.save_figure() # Close all other plots before commencing try: plt.close() except: pass def savePlotTrace(parent, dirname, Page, uselatex=False, verbose=False): """ Save trace plot from Page into file Parameters: *parent* the parent window *dirname* directory to set on saving *Page* Page containing all variables *uselatex* Whether to use latex for the ploting or not. This function uses a hack in misc.py to change the function for saving the final figure. We wanted save in the same directory as PyCorrFit was working and the filename should be the tabtitle. """ # Close all other plots before commencing try: plt.close() except: pass # Trace must be displayed in s timefactor = 1e-3 tabtitle = Page.tabtitle.GetValue() if tabtitle.strip() == "": tabtitle = "page"+str(Page.counter).strip().strip(":") # Intensity trace in kHz may stay the same if len(Page.corr.traces) == 0: return traces = Page.corr.traces labels = list() for ii, tr in enumerate(traces): labels.append("Channel {}: {}".format(ii+1, tr.name)) # Check if we can use latex for plotting: r1 = find_program("latex")[0] r2 = find_program("dvipng")[0] # Ghostscript r31 = find_program("gs")[0] r32 = find_program("mgs")[0] r3 = max(r31, r32) if r1+r2+r3 < 3: uselatex = False if uselatex == True: rcParams['text.usetex'] = True rcParams['text.latex.unicode'] = True rcParams['font.family'] = 'serif' rcParams['text.latex.preamble'] = [r"\usepackage{amsmath}"] for i in np.arange(len(labels)): labels[i] = r"{\normalsize "+escapechars(labels[i])+r"}" else: rcParams['text.usetex'] = False # create plot # plt.plot(x, y, '.', label = 'original data', markersize=5) fig = plt.figure(figsize=(10, 3)) wtit = "Trace #{:04d}_{}".format(int(Page.counter.strip(":# ")), Page.title.strip()) fig.canvas.set_window_title(wtit) ax = plt.subplot(111) for i in np.arange(len(traces)): # Columns time = traces[i][:, 0]*timefactor intensity = traces[i][:, 1] plt.plot(time, intensity, '-', label=labels[i], lw=1) # set plot boundaries maxy = -np.infty miny = np.infty for tr in traces: maxy = max(np.max(tr[:, 1]), maxy) miny = min(np.min(tr[:, 1]), miny) ax.set_ylim(miny, maxy) plt.ylabel('count rate [kHz]') plt.xlabel('time [s]') # Legend outside of plot # Decrease size of plot to fit legend box = ax.get_position() ax.set_position([box.x0, box.y0 + box.height * 0.2, box.width, box.height * 0.9]) plt.legend(loc='upper center', bbox_to_anchor=(0.5, -0.35), prop={'size': 9}, ) # Hack # We need this for hacking. See edclasses. fig.canvas.HACK_parent = parent fig.canvas.HACK_fig = fig fig.canvas.HACK_Page = Page fig.canvas.HACK_append = "_trace.png" plt.tight_layout(rect=(.001, .34, .999, 1.0)) if verbose == True: plt.show() else: # If WXAgg is not used for some reason, then our hack does not work # and we must use e.g. TkAgg try: fig.canvas.toolbar.save() except AttributeError: fig.canvas.toolbar.save_figure() # Close all other plots before commencing try: plt.close() except: pass # set dpi to 300 matplotlib.rcParams['savefig.dpi'] = 300 pycorrfit-1.1.7/pycorrfit/gui/edclasses.py0000664000372000037200000001515313554642611021544 0ustar travistravis00000000000000"""Edited classes Contains classes edited specifically for PyCorrFit. """ import sys import traceback import warnings import matplotlib import wx # We do catch warnings about performing this before matplotlib.backends stuff # matplotlib.use('WXAgg') # Tells matplotlib to use WxWidgets with warnings.catch_warnings(): warnings.simplefilter("ignore") try: # Tells matplotlib to use WxWidgets for dialogs matplotlib.use('WXAgg') except: pass class ChoicesDialog(wx.Dialog): def __init__(self, parent, dropdownlist, title, text): # parent is main frame self.parent = parent # super(ChoicesDialog, self).__init__(parent=parent, # title=title) wx.Dialog.__init__(self, parent, -1, title) # Controls panel = wx.Panel(self) # text1 textopen = wx.StaticText(panel, label=text) btnok = wx.Button(panel, wx.ID_OK) btnabort = wx.Button(panel, wx.ID_CANCEL) # Dropdown self.dropdown = wx.ComboBox(panel, -1, "", (15, 30), wx.DefaultSize, dropdownlist, wx.CB_DROPDOWN | wx.CB_READONLY) self.dropdown.SetSelection(0) # Bindings self.Bind(wx.EVT_BUTTON, self.OnOK, btnok) self.Bind(wx.EVT_BUTTON, self.OnAbort, btnabort) # Sizers topSizer = wx.BoxSizer(wx.VERTICAL) topSizer.Add(textopen) topSizer.Add(self.dropdown) btnSizer = wx.BoxSizer(wx.HORIZONTAL) btnSizer.Add(btnok) btnSizer.Add(btnabort) topSizer.Add(btnSizer) panel.SetSizer(topSizer) topSizer.Fit(self) # self.Show(True) self.SetFocus() def OnOK(self, event=None): self.SelcetedID = self.dropdown.GetSelection() self.EndModal(wx.ID_OK) def OnAbort(self, event=None): self.EndModal(wx.ID_CANCEL) def save_figure(self, evt=None): """ A substitude function for save in: matplotlib.backends.backend_wx.NavigationToolbar2Wx We want to be able to give parameters such as dirname and filename. """ try: parent = self.canvas.HACK_parent fig = self.canvas.HACK_fig Page = self.canvas.HACK_Page add = self.canvas.HACK_append dirname = parent.dirname filename = self.canvas.get_window_title().replace(" ", "_").lower()+add formats = fig.canvas.get_supported_filetypes() except: dirname = "." filename = "" formats = self.canvas.get_supported_filetypes() parent = self fieltypestring = "" keys = formats.keys() keys.sort() for key in keys: fieltypestring += formats[key]+"(*."+key+")|*."+key+"|" # remove last | fieltypestring = fieltypestring[:-1] dlg = wx.FileDialog(parent, "Save figure", dirname, filename, fieltypestring, wx.FD_SAVE | wx.FD_OVERWRITE_PROMPT) # png is default dlg.SetFilterIndex(keys.index("png")) # user cannot do anything until he clicks "OK" if dlg.ShowModal() == wx.ID_OK: wildcard = keys[dlg.GetFilterIndex()] filename = dlg.GetPath() if not filename.endswith(wildcard): filename += "."+wildcard dirname = dlg.GetDirectory() #savename = os.path.join(dirname, filename) savename = filename try: self.canvas.figure.savefig(savename) except: # RuntimeError: # The file does not seem to be what it seems to be. info = sys.exc_info() errstr = "Could not latex output:\n" errstr += str(filename)+"\n\n" errstr += str(info[0])+"\n" errstr += str(info[1])+"\n" for tb_item in traceback.format_tb(info[2]): errstr += tb_item wx.MessageDialog(parent, errstr, "Error", style=wx.ICON_ERROR | wx.OK | wx.STAY_ON_TOP) else: dirname = dlg.GetDirectory() try: parent.dirname = dirname except: pass class MyScrolledDialog(wx.Dialog): def __init__(self, parent, overtext, readtext, title): wx.Dialog.__init__(self, parent, title=title) overtext = wx.StaticText(self, label=overtext) text = wx.TextCtrl(self, -1, readtext, size=(500, 400), style=wx.TE_MULTILINE | wx.TE_READONLY) sizer = wx.BoxSizer(wx.VERTICAL) btnsizer = wx.BoxSizer() btn = wx.Button(self, wx.ID_OK) # , "OK ") btnsizer.Add(btn, 0, wx.ALL, 5) btnsizer.Add((5, -1), 0, wx.ALL, 5) btn = wx.Button(self, wx.ID_CANCEL) # , "Abort ") btnsizer.Add(btn, 0, wx.ALL, 5) sizer.Add(overtext, 0, wx.EXPAND | wx.ALL, 5) sizer.Add(text, 0, wx.EXPAND | wx.ALL, 5) sizer.Add(btnsizer, 0, wx.EXPAND | wx.ALIGN_CENTER_VERTICAL | wx.ALL, 5) self.SetSizerAndFit(sizer) class MyOKAbortDialog(wx.Dialog): def __init__(self, parent, text, title): wx.Dialog.__init__(self, parent, title=title) overtext = wx.StaticText(self, label=text) sizer = wx.BoxSizer(wx.VERTICAL) btnsizer = wx.BoxSizer() btn = wx.Button(self, wx.ID_OK) # , "OK ") btnsizer.Add(btn, 0, wx.ALL, 5) btnsizer.Add((5, -1), 0, wx.ALL, 5) btn = wx.Button(self, wx.ID_CANCEL) # , "Abort ") btnsizer.Add(btn, 0, wx.ALL, 5) sizer.Add(overtext, 0, wx.EXPAND | wx.ALL, 5) sizer.Add(btnsizer, 0, wx.EXPAND | wx.ALIGN_CENTER_VERTICAL | wx.ALL, 5) self.SetSizerAndFit(sizer) class MyYesNoAbortDialog(wx.Dialog): def __init__(self, parent, text, title): wx.Dialog.__init__(self, parent, title=title) overtext = wx.StaticText(self, label=text) sizer = wx.BoxSizer(wx.VERTICAL) btnsizer = wx.BoxSizer() btn1 = wx.Button(self, wx.ID_YES) #btn1.Bind(wx.EVT_BTN, self.YES) btnsizer.Add(btn1, 0, wx.ALL, 5) btnsizer.Add((1, -1), 0, wx.ALL, 5) btn2 = wx.Button(self, wx.ID_NO) btnsizer.Add(btn2, 0, wx.ALL, 5) btnsizer.Add((1, -1), 0, wx.ALL, 5) btn3 = wx.Button(self, wx.ID_CANCEL) btnsizer.Add(btn3, 0, wx.ALL, 5) sizer.Add(overtext, 0, wx.EXPAND | wx.ALL, 5) sizer.Add(btnsizer, 0, wx.EXPAND | wx.ALIGN_CENTER_VERTICAL | wx.ALL, 5) self.SetSizerAndFit(sizer) self.SetFocus() self.Show() def YES(self, e): self.EndModal(wx.ID_YES) try: # Add the save_figure function to the standard class for wx widgets. matplotlib.backends.backend_wx.NavigationToolbar2Wx.save = save_figure except (NameError, AttributeError): pass pycorrfit-1.1.7/pycorrfit/gui/update.py0000664000372000037200000001037513554642611021061 0ustar travistravis00000000000000"""PyCorrFit - update checking""" from distutils.version import LooseVersion # For version checking import os import tempfile import traceback import urllib.request import webbrowser import simplejson import wx.html import wx.lib.delayedresult as delayedresult from .. import _version as pcf_version from . import doc # Documentation/some texts # The icon file was created with # img2py -i -n Main PyCorrFit_icon.png icon.py from . import misc class UpdateDlg(wx.Frame): def __init__(self, parent, valuedict): description = valuedict["Description"] homepage = valuedict["Homepage"] githome = valuedict["Homepage_GIT"] changelog = valuedict["Changelog"] pos = parent.GetPosition() pos = (pos[0]+100, pos[1]+100) wx.Frame.__init__(self, parent, wx.ID_ANY, title="Update", size=(230, 240), pos=pos) self.changelog = changelog # Fill html content html = wxHTML(self) string = '' \ + " PyCorrFit
" \ + "Your version: {}
".format(description[0]) \ + "Latest version: {}
".format(description[1]) \ + "({})

".format(description[2]) if homepage: string = string + 'Homepage
'.format(homepage) if githome: string = string + 'Repository
'.format(githome) if changelog: string = string + \ 'Change Log'.format(changelog) string += '

' html.SetPage(string) self.Bind(wx.EVT_CLOSE, self.Close) # Set window icon ico = misc.getMainIcon() wx.Frame.SetIcon(self, ico) def Close(self, event): if len(self.changelog) != 0: # Cleanup downloaded file, if it was downloaded if self.changelog != doc.StaticChangeLog: os.remove(self.changelog) self.Destroy() class wxHTML(wx.html.HtmlWindow): def OnLinkClicked(self, link): webbrowser.open(link.GetHref()) def get_gh_version(ghrepo="user/repo", timeout=20): """ Check GitHub repository for latest release """ u = "https://api.github.com/repos/{}/releases/latest".format(ghrepo) try: data = urllib.request.urlopen(u, timeout=timeout).read() except: newversion = None try: with open("check_update_error.log", "w") as fe: fe.writelines(str(traceback.format_exc())) except: pass else: j = simplejson.loads(data) newversion = LooseVersion(j["tag_name"]) return newversion def update(parent): """ This is a thread for _Update """ parent.StatusBar.SetStatusText("Connecting to server...") delayedresult.startWorker(_update_consumer, _update_worker, wargs=(parent,), cargs=(parent,)) def _update_consumer(delayedresult, parent): results = delayedresult.get() dlg = UpdateDlg(parent, results) dlg.Show() parent.StatusBar.SetStatusText( "...update status: "+results["Description"][2]) def _update_worker(parent): # Online changelog file cl_file = doc.GitChLog try: responseVer = urllib.request.urlopen(cl_file, timeout=2) except: changelog = "" else: changelog = responseVer.read() ghrepo = "FCS-analysis/PyCorrFit" if hasattr(pcf_version, "repo_tag"): old = LooseVersion(pcf_version.repo_tag) else: old = LooseVersion(pcf_version.version) new = get_gh_version(ghrepo) if new is not None: if new > old: action = "update available" elif new < old: action = "ahead of release" else: action = "state of the art" description = [old, new, action] if changelog: changelogfile = tempfile.mktemp()+"_PyCorrFit_CHANGELOG"+".txt" clfile = open(changelogfile, 'wb') clfile.write(changelog) clfile.close() else: changelogfile = doc.StaticChangeLog results = dict() results["Description"] = description results["Homepage"] = doc.HomePage results["Homepage_GIT"] = doc.GitHome results["Changelog"] = changelogfile return results pycorrfit-1.1.7/pycorrfit/gui/usermodel.py0000664000372000037200000002547213554642611021602 0ustar travistravis00000000000000"""Module: user model When the user wants to use his own functions. We are using sympy as function parser instead of writing our own, which might be safer. We only parse the function with sympy and test it once during import. After that, the function is evaluated using eval()! """ import numpy as np import scipy.special as sps import sys import warnings try: import sympy import sympy.functions from sympy.core import S from sympy.core.function import Function from sympy import sympify except ImportError: warnings.warn("Importing sympy failed." + " Reason: {}.".format(sys.exc_info()[1].message)) # Define Function, so PyCorrFit will start, even if sympy is not there. # wixi needs Function. Function = object import wx from pycorrfit import models as mdls from pycorrfit.models.control import append_model def myDecoding(string): """ Read the string or the byte chain and return a string. It allows to decode "µ" in the user models. """ if isinstance(string, bytes): try: decode = string.decode() except UnicodeError: decode = "".join(chr(b) for b in string) return decode return string class CorrFunc(object): """ Check the input code of a proposed user model function and return a function for fitting via GetFunction. """ def __init__(self, labels, values, substitutes, funcstring): self.values = values # a --> a # b [ms] --> b self.variables = list() for item in labels: self.variables.append(item.split(" ")[0].strip()) self.funcstring = funcstring for key in substitutes.keys(): # Don't forget to insert the "(" and ")"'s self.funcstring = self.funcstring.replace(key, "("+substitutes[key]+")") for otherkey in substitutes.keys(): substitutes[otherkey] = substitutes[otherkey].replace(key, "("+substitutes[key]+")") # Convert the function string to a simpification object self.simpification = sympify(self.funcstring, sympyfuncdict) self.simstring = str(self.simpification) self.vardict = evalfuncdict def GetFunction(self): # Define the function that will be calculated later def G(parms, tau): tau = np.atleast_1d(tau) for i in np.arange(len(parms)): self.vardict[self.variables[i]] = float(parms[i]) self.vardict["tau"] = tau # Function called with array/list # The problem here might be # for key in vardict.keys(): # symstring = symstring.replace(key, str(vardict[key])) # symstring = symstring.replace("####", "tau") g = eval(self.funcstring, self.vardict) # This would be a safer way to do this, but it is too slow! # Once simpy supports arrays, we can use these. # # g = np.zeros(len(tau)) # for i in np.arange(len(tau)): # vardict["tau"] = tau[i] # g[i] = simpification.evalf(subs=vardict) return g return G def TestFunction(self): """ Test the function for parsibility with the given parameters. """ vardict = dict() for i in np.arange(len(self.variables)): vardict[self.variables[i]] = sympify(float(self.values[i])) for tau in np.linspace(0.0001, 10000, 10): vardict["tau"] = tau Number = self.simpification.evalf(subs=vardict) if Number.is_Number is False: raise SyntaxError("Function could not be parsed!") class UserModel(object): """ Class for importing txt files as models into PyCorrFit. """ def __init__(self, parent): " Define all important constants and variables. " # Current ID is the last model ID we gave away. # This will be set using self.SetCurrentID self.CurrentID = None # The file to be opened. This is a full path like # os.path.join(dirname, filename) self.filename = None # Imported models # Modelarray = [model1, model2] self.modelarray = [] # String that contains the executable code self.modelcode = None # Parent is main PyCorrFit program self.parent = parent # The string that identifies the user model menu self.UserStr = "User" def GetCode(self, filename=None): """ Get the executable code from the file. Optional argument filename may be used. If not self.filename will be used. This automatically sets self.filename """ if filename is not None: self.filename = filename openedfile = open(self.filename, 'r') code = openedfile.readlines() # File should start with a comment #. # Remove everything before that comment (BOM). startfile = code[0].find("#") if startfile != -1: code[0] = code[0][startfile:] else: code[0] = "# "+code[0] # Returncode: True if model was imported, False if there was a problem. # See ModelImported in class CorrFunc self.AddModel(code) openedfile.close() def AddModel(self, code): """ *code* is a list with strings each string is one line. """ # a = 1 # b [ms] = 2.5 # gAlt = 1+tau/b # gProd = a*b # G = 1/gA * gB labels = list() values = list() substitutes = dict() for line in code: # We deal with comments and empty lines # We need to check line length first and then we look for # a hash. line = myDecoding(line) line = line.strip() if len(line) != 0 and line[0] != "#": var, val = line.split("=") var = var.strip() if var == "G": # Create a fuction that calculates G funcstring = val.strip() self.FuncClass = CorrFunc(labels, values, substitutes, funcstring) func = self.FuncClass.GetFunction() doc = myDecoding(code[0]).strip() # Add whitespaces in model string (looks nicer) for olin in code[1:]: doc = doc + "\n " + myDecoding(olin).strip() func.__doc__ = doc elif var[0] == "g": substitutes[var] = val.strip() else: # Add value and variable to our lists labels.append(var) values.append(float(val)) # Active Parameters we are using for the fitting # [0] labels # [1] values # [2] bool values to fit bools = list([False]*len(values)) bools[0] = True # Create Modelarray active_parms = [labels, values, bools] self.SetCurrentID() Modelname = myDecoding(code[0][1:]).strip() definitions = [self.CurrentID, Modelname, Modelname, func] model = dict() model["Parameters"] = active_parms model["Definitions"] = definitions self.modelarray.append(model) def ImportModel(self): """ Do everything that is necessarry to import the models into PyCorrFit. """ # Set the model ids of the new model(s) # Normally, there is only one model. for i in np.arange(len(self.modelarray)): self.SetCurrentID() self.modelarray[i]["Definitions"][0] = self.CurrentID # We assume that the models have the correct ID for now append_model(self.modelarray) # Set variables and models # Is this still necessary? - We are doing this for compatibility! self.parent.value_set = mdls.values self.parent.valuedict = mdls.valuedict self.parent.models = mdls.models self.parent.modeldict = mdls.modeldict self.parent.modeltypes = mdls.modeltypes # Get menu menu = self.parent.modelmenudict[self.UserStr] # Add menu entrys for item in self.modelarray: # Get definitions Defs = item["Definitions"] # This is important if we want to save the session with # the imported model. mdls.modeltypes[self.UserStr].append(Defs[0]) menuentry = menu.Append(Defs[0], Defs[1], Defs[2]) self.parent.Bind(wx.EVT_MENU, self.parent.add_fitting_tab, menuentry) def TestFunction(self): """ Convenience function to test self.FuncClass """ self.FuncClass.TestFunction() def SetCurrentID(self): # Check last item or so of modelarray # Imported functions get IDs starting from 7000 theID = 7000 for model in mdls.models: theID = max(theID, model[0]) self.CurrentID = theID + 1 class wixi(Function): """ This is a ghetto solution for using wofz in sympy. It only returns the real part of the function. I am not sure, if the eval's are placed correctly. I only made it work for my needs. This might be wrong! For true use of wofz, I am not using sympy, anyhow. """ nargs = 1 is_real = True @classmethod def eval(self, arg): return None def as_base_exp(self): return self, S.One # @UndefinedVariable def _eval_evalf(self, prec): result = sps.wofz(1j*float(self.args[0])) return sympy.numbers.Number(sympy.functions.re(result)) def evalwixi(x): """ Complex Error Function (Faddeeva/Voigt). w(i*x) = exp(x**2) * ( 1-erf(x) ) This function is called by other functions within this module. We are using the scipy.special.wofz module which calculates w(z) = exp(-z**2) * ( 1-erf(-iz) ) z = i*x """ z = x*1j result = sps.wofz(z) # We should have a real solution. Make sure nobody complains about # some zero-value imaginary numbers. return np.real_if_close(result) sympyfuncdict = dict() sympyfuncdict["wixi"] = wixi evalfuncdict = dict() evalfuncdict["wixi"] = evalwixi evalfuncdict["I"] = 1j scipyfuncs = ['wofz', 'erf', 'erfc'] numpyfuncs = ['abs', 'arccos', 'arcsin', 'arctan', 'arctan2', 'ceil', 'cos', 'cosh', 'degrees', 'e', 'exp', 'fabs', 'floor', 'fmod', 'frexp', 'hypot', 'ldexp', 'log', 'log10', 'modf', 'pi', 'power', 'radians', 'sin', 'sinh', 'sqrt', 'tan', 'tanh'] for func in scipyfuncs: evalfuncdict[func] = eval("sps."+func) for func in numpyfuncs: evalfuncdict[func] = eval("np."+func) pycorrfit-1.1.7/pycorrfit/gui/main.py0000775000372000037200000000655613554642611020534 0ustar travistravis00000000000000"""Main execution script""" from distutils.version import LooseVersion import sys import warnings import yaml import numpy as np import scipy class Fake(object): """ Fake module. """ def __init__(self): self.__version__ = "0.0 unknown" self.version = "0.0 unknown" self.use = lambda x: None # Import matplotlib a little earlier. This way some problems with saving # dialogs that are not made by "WXAgg" are solved. try: import matplotlib except ImportError: matplotlib = Fake() # We do catch warnings about performing this before matplotlib.backends stuff # matplotlib.use('WXAgg') # Tells matplotlib to use WxWidgets with warnings.catch_warnings(): warnings.simplefilter("ignore") matplotlib.use('WXAgg') # Tells matplotlib to use WxWidgets for dialogs # Sympy is optional: try: import sympy except ImportError: print("Importing sympy failed! Checking of external model functions") print("will not work!") # We create a fake module sympy with a __version__ property. # This way users can run PyCorrFit without having installed sympy. sympy = Fake() # We must not import wx here. frontend/gui does that. If we do import wx here, # somehow unicode characters will not be displayed correctly on windows. # import wx # Continue with the import: from . import frontend as gui # noqa: E402 from . import doc # noqa: E402 def CheckVersion(given, required, name): """ For a given set of versions str *required* and str *given*, where version are usually separated by dots, print whether for the module str *name* the required verion is met or not. """ try: req = LooseVersion(required) giv = LooseVersion(given) except: print(" WARNING: Could not verify version of "+name+".") return if req > giv: print(" WARNING: You are using "+name+" v. "+given + " | Required: "+name+" " + required) else: print(" OK: "+name+" v. "+given+" | "+required+" required") # Start gui def Main(): # VERSION version = doc.__version__ __version__ = version print(gui.doc.info(version)) # Check important module versions print("\n\nChecking module versions...") CheckVersion(matplotlib.__version__, "2.2.2", "matplotlib") CheckVersion(np.__version__, "1.14.2", "NumPy") CheckVersion(yaml.__version__, "3.12", "PyYAML") CheckVersion(scipy.__version__, "1.0.1", "SciPy") CheckVersion(sympy.__version__, "1.1.1", "sympy") CheckVersion(gui.wx.__version__, "4.0.1", "wxPython") # Start gui app = gui.MyApp(False) frame = gui.MyFrame(None, -1, version) app.frame = frame # Before starting the main loop, check for possible session files # in the arguments. sysarg = sys.argv for arg in sysarg: if arg.endswith(".pcfs"): print("\nLoading Session "+arg) frame.OnOpenSession(sessionfile=arg) break if arg.endswith(".fcsfit-session.zip"): print("\nLoading Session "+arg) frame.OnOpenSession(sessionfile=arg) break elif arg[:6] == "python": pass elif arg[-12:] == "PyCorrFit.py": pass elif arg[-11:] == "__main__.py": pass else: print("Ignoring command line parameter: "+arg) app.MainLoop() if __name__ == "__main__": Main() pycorrfit-1.1.7/pycorrfit/gui/icon.py0000664000372000037200000004460613554642611020533 0ustar travistravis00000000000000# ---------------------------------------------------------------------- # This file was generated by /usr/bin/img2py # from wx.lib.embeddedimage import PyEmbeddedImage Main = PyEmbeddedImage( "iVBORw0KGgoAAAANSUhEUgAAAQAAAAEACAYAAABccqhmAAAABHNCSVQICAgIfAhkiAAAAAlw" "SFlzAAAMqAAADKgBt04g1gAAABl0RVh0U29mdHdhcmUAd3d3Lmlua3NjYXBlLm9yZ5vuPBoA" "ACAASURBVHic7Z13nFTV2ce/d9r2Xcru0quAFDE2sASUJogGS9TYYosxFozGkMTo6/uuEzXF" "RoxYIomx94YGjBRpKioqGKQICNKWBRbYXdg27b5/nLne2WXuzOwyc++dmfP9fOYzs3PP7jwM" "9/zOc855nucoqqoikUiyE4fVBkgkEuuQAiCRZDFSACSSLEYKgESSxUgBkEiyGCkAEkkWIwVA" "IslipABIJFmMFACJJIuRAiCRZDFSACSSLEYKgESSxUgBkEiyGCkAEkkWIwVAIsliXFYbIJFk" "K4pXOR64GmgGvlQr1BdMt0EWBJFIzEfxKicD7wElEW/fpFaoj5pqhxQAicRcFK/iAbYB5a0u" "qUC5WqFWm2WLXAOQSMznfA7t/AAKJk/LpQBIJOZzg8H7KmI9wDSkAEgkJqJ4laOA0QaXP1Ar" "1P1m2iMFQCIxl+tjXHvcNCvCSAGQSExC8SqFwOUGl3cCs0w0B5ACIJGYyaVAscG1f6gVasBM" "Y0AKgERiJkaLf0FgppmGaEgBkEhMQPEqJwHHGFyerVao28y0R0MKgERiDkajP1iw+KchBUAi" "STGKVykFfmJweRPwvonmtEAKgESSem4Fcg2uPalWWBePLwVAIkkhilfpANxkcNkHPGWiOYcg" "BUAiSS03Y7z196Jaoe4x05jWSAGQSFKE4lWKgFsMLoeAP5loTlSkAEgkqeNGoJPBtVfUCnW9" "mcZEQwqARJICFK+SD/za4LIK3GuiOYZIAZBIUsMviJ7zD/CmWqGuNtMYI2RNQAmIQhRuwBPx" "rL0OIVar/eFn7bXpcevpguJVcoDfxmhyj1m2xEMKQOajIFahOwEdI56L0Dt6e+4DFV0QmoH9" "wL7ws/ba1OIWNuJnQHeDa++qFepKM42JhRSAzKII6Ino5FpH7wg4U/BZCpATfhQBpVHaNNBS" "GHYBlYjkl4xE8Spu4LYYTe42y5ZEkAKQ3uQDvYDe4ecOcdo7EP/nzvDDgejIkY/W74EY7UPh" "ZzXKzyFEpw6En7XItvzwo0eEDQGECGwDtiJEIdTGf7eduRzoY3DtfbVCXW6mMfGQVYHTixxa" "dvjOBu1c6B3dFfFQDNonG00IAq1eR+vofmA7uiDsNsnGpBOu9rsO6GfQZJRaoX5koklxkR6A" "/XEgbqghwBFEd+ddiLl8Tvg5XkdXEe65P8oj0OpnB2IxMPLhavVzbvhzI+1xhe2JJIC+ZuAL" "2+EO//u0TlMLrA0/TK2PlwRuwbjzL7Rb5wfpAdiZbsBQ4EgOTSRxoi/g5WC8nesD6sKP2vBD" "+znZbncu4pCL4vCjJPwoxFiQtJ2F5vDr1jfjToQQrAOakmxvCxSvogC5aoXa2M7fLwM20PKg" "j0jGqRXqwvbalyqkANiLEsRIP5RD5/MuIA/R0aJ5ASFgD1CFmFfXAu26mZOMA7FI2BnoihC2" "AoO2PoTNTbQUgxCwGViDSJ9t9yKi4lWKgcnA2LAt3cJ2dUEIaj1CeKrCjy3AbGBprJJdild5" "HOOCn0vUCvW09tqcSqQA2IMewIlA31bvO9A7vTvK7+1F3KQ7EXPndNmbL0IXg64c6uGoCBFo" "4tCtxCZgJfAlCXoFilfpDZwdfowh+ncZjxpgDvAuMEetUOsi/v4w4CuiC7MKjFQr1M/b8Zkp" "RwqAtfRFdPzIVXJtey2PQ+fQfsSItB3R8X2pN9EUOiDEoC9Q1upaCOEVNNJS4PyITvcFYtQ+" "BMWr9EVsu11GchdADwD3AdPVCrVe8SrvAxMN2j6rVqhXJvGzk4oUAGsYgOj4XSLecyJc4zxa" "3qwqsAPh+m4jg/fQwxQB/cOPolbXAojOHjlFCABfA8sRHRPFq3QG/geYSsvFyWRTBbyOcb5/" "AzBIrVB3pNCGw0IKgHkowGBgJC2371zoHT+SvcC3wHekeAHMxpQjhKAvLTtyECEEjehCEALW" "dHuw25Cqg1VejBfjzMSrVqh3WW1ELKQAmEMvYDwtU0NdiBXyyPlvAPgGsZpch0TDiYhwHELL" "BJsQQggaGgONyhnPn3Huki1LfmiFgVHYgRj9G6w2JBZSAFJLAXAaYuTX8ITfj5zf+9D3vjNl" "Xp8qugDDiYi131633X36c6dPWVe97ohE/4iiusivHYanqRvu5lLczaU4A0UEPPvx5+zBn7OH" "5vytNBZubK+dV6oV6rPt/WWzkIFAqcGBqAF/Crrr6kTsj0d2/CbE1tY3iEUtSXx2hR+lwPCv" "qr4aOun5ST/ZVb/LqPDG9ziCeRRXn0JJ9akUV5+CM1AY98N8uVXUli2mtmwJBzusBCWh8In9" "wIuJNLQa6QEknx4Id19LjlEQI37k3dYArAbWk/mLeiljRdWK3DFPj3mxrrluYKx2SshN6fbz" "6fLdlbj88dIljGkq/JbKI56grvTDRJr/Ta1QjcqB2QYpAMkjHzgVEcSjkYMY9bX9YR9iD3s9" "mZUAYzq+oE/p8VCPB6obqo223wDouOt0um2ciqepS6xmbaK+ZBU7Bk2noXhtvKbXqhXqP5L2" "wSlACkBy6A2ciRABiO7ub0LsWdshOi/tOXLGkdev37t+qtF1RXXQfcPNlG27KCWfH3L42Db0" "HvZ3mRermQ+YoFaoS1NiRBKQAnB4KMDJiD19LX02n5bx7zXAp4h5qyQJnPPyOaPe+eadxzAI" "7nEGCujz9d0U7z055bZU9XuKqv7/4NA0hu/ZAwxVK9TqlBvTDuQiYPspQIz6vcI/OxERbVqY" "aQARqbYW6e4njZqmGsfcb+f+BoPO7wjmMuDLR8k7cKQp9nTd/DOcwXx2DHzYqEkZ8H+I8wFs" "hywK2j56Iwo/aJ0/B7Hop3X+rcDbiIU+2fmTyBkvnHFOU6DJYLtPofea/zWt82uUbb2YTpU/" "itXkesWrDDDLnrYgBaBtKIitvfMRrr6CCFftGH4dBJYBixAr/ZIksmr3qpwvd35pOO/vuvlq" "OuweZ6ZJ39Prm99RUPMDo8tu4I8mmpMwUgASx4Po+CchOrsTEdmnpbbWItJGN1hiXRZwyeuX" "XOYP+qMu5+cdHEjXzdeYbdL3KCE3fVZXoIQMEw0vVLzKCDNtSgQpAImRjzjeuXf451xauvzf" "Ijp/jfmmZQ8b92883+ha9w1TQbX2dvY0daN0+wWxmlxhli2JIgUgPiXAxegx6EWIxT7N5f8o" "/EiXXPy05HfzfjegOdDcO9q1on0jKdp3otkmRaXrd1fFijA810xbEkEKQGzKgUvQO3wJustf" "A/wbMfpLUsw7698Zb3St66ZrzTQlJk5/cSwvoKfiVU4w0554SAEwpjfC7dcW+zqgp+xWIqrD" "1FpjWvaxvXZ71NU9d3MZBbVHmW1OTDrsHhvrsq28ACkA0RkEnIdeYbcjelTfZuADpMtvGk98" "/kR5vb9+aLRrJdWjzDYnLnkHBsUKPT7bTFviIQXgUI4GzkI/OKMzekbfOmApcm/fVOZvmm9U" "apviPaPNNCVhSvacanRpcLgCsS2QAtCSQYhMPm2brzN6tOQK4DOL7Mpqqg5WGZ2yS35dVMfA" "cvLrhhldchP9GDVLkAKg0xtRLjqy8zsRQd7LgFXWmZbd7G/a37pQKCD23l1+O1T+OhR3c8w+" "bnRwqOlIARCUI+ZmmtvfKfwcQkT1yeAeCznoOxjVA3D7bDOQHoK7OapmaUgBsBEdgB+jL/h1" "Qh/5lyAq8UospMHfELU3xRllLUV6AOlBAS3j+juiz/k/RST1SCQZSzYLQA5i5C9B3+fXVvu/" "QlTtkdiAfHf+nmjv+3NsmWIPxLWt0iw74pGtAqAAP0I/hSayes83CAGQ2IRCT2HUI8P9HjsL" "QFTN0pACYDEnAn3Cr4vQI/y2Irf6bEfH3I5Re5Pq8BNw2zMYU3oA9qUnoowXiKw+LbZ/FyLI" "R9ZIsxldC7tG9QAAGorXmGlKwjQUrza65Ads47pkmwDkIcp4aXv92ibyAWAhskS3LZnQf8Jm" "o2t1Zfast1lbtsTo0jq1wj6FOLNNACajF+zUMvxCwGLkiTy25foTrt9d4C6IOtTXJlaj31Qa" "i9bjyzWsAfuOmbbEI5sEYCTikEkQIqAV81gO7LPCIEni9Czp+UG09/05e6gv+dpsc2JSU74w" "1uW3zbIjEbJFAHoA2qGROejz/i2IVX+JzTl70NkLjK5V9Z9ppikxCbrrqO75utHl7WqF+rmZ" "9sQjGwQgF33e76DlvP9jq4yStI37Tr9vY44rJ2pg1oFOn3Gg06dmmxSVqr5PE3QdNLpsq9Ef" "skMARiG2+kDM+7UY/8XIAznTioGdBr5mdK1y4KOJHtyZMny5O2ON/irwLxPNSYhMF4CuiKOk" "Qbj9WqTfZ8h5f9rx1kVvvehxeqqiXWss3EBVv3+abdL3qA4/W4Z5UR2GY8oraoX6pZk2JUIm" "C4CCntvvQD+ddwcyzDctGdBpgO/4bsfPMLpe1e9f1JRHXStMOduOvI/6DoYBpD7gf0w0J2Ey" "WQB+AGh1mYrQq/jaY7IoaRfzLp/3bp4rb2P0qypbh95NY5G567p7er/Mvu7/NrzucrhmqhXq" "JhNNSphMFYB89FV/D3qo7yrAcIVGYn8KPAWhSQMm3Y9BxGbI2cTG46ZSV/qRCdaoVPWfyY6B" "fzNske/OPzj38rkxjxC2kkwVgNMQ230KItEHoA6w14axpF28ddFbHw/qPOgxo+tBVz2bjv4d" "u/u8kDIbQs5GNh99O1X9nsIoetypOIMPTHzgmbF9x/ZHTzyzFZkoAL2AIeHXBbTM75fFPDOE" "VTes+ntpfulcwwZKiMoBM9g8/HZ8eTuS+tkHOn3O+hE/p7Zsccx2t55867wbTrjhO8RANCGp" "RiQJRbVPWHKyuAJRdNEZflaA7xDVfSQZxIqqFbmjnhr1bIO/YUisdkrITemO8+iy+Wpc/g7t" "/rzGwg1UDniUA53jLyNNHjj5izmXzvkAaEQ/P+I/gK2ylzJNAAYCU8KvtVr+AUQAhjytNwN5" "bPljXabNnfaPpkBT33htHcE8iqtPoaT6VIqrT4l1hNf3+HJ3UVu2mNqyxRzssDKhWIPS/NK5" "227d9mKuK1crDbwPsROwH3gaG2WcZpoA/BRR4NOFXnr5C8AwN1OS/szfNL/wwtcufKCmqeaH" "8VsLFNVFfu0wPM3luJpLcfs64wwUEHDX4M/ZS8Czj+b8bTQWtqkerDqw88Anvr7h68c9To8D" "cbhMPqLza3En/8ZG29CZJAD9EF846KN/E/AGMs0347nwtQtPfH3N609i0bqWQ3E0jek75n8W" "XLEgcl1iMCIJDWAvIvJ0D/Cc2fYZkUmLgNrxsC708l5rkJ0/45m+bHr3t9e9fT8W3c95rryN" "N5xwwxWtOj+IcvKN4dfafKMM6G+acXFwxW+SFvREL7WsZfr5kJl+Gc8rq1/pePsHtz8ZCAU6" "mv3Zbqd793Hdjpux4PIFswo8BdEWB4KIQeh4xKDkQqxJnQjYIjAoUwRAG/2d6EE/a5HJPhnN" "su3L8q+Zdc3jzYHmPkZtclw5W5sDzb0Qu0FJwaE46gd1HvTUqxe++szw8uHNcZp/AxyFnoZe" "C3RDnERledn5TBCArugFPrXRP4AQAEmGsqVmi3vy85MfrvfXGx7CV5RT9OW6qeuueW3Na2VP" "r3x6zOaazWPrmupGqKhtvu+divNA5/zOS4eUDll015i7lo7pOybRiFLtXjwGMTgdRHgGI7GB" "AGTCIuA5wBGI0V+LtvoasF3mlSQ51PvqHX0f7nt/dUP1RKM2Hqdnxws/fuGSC4ZesD/y/UXf" "LSr0LvaO2rR/0zEN/oaypkBTuS/oK/eH/GWqqrodiqPe4/Ts8Tg9u3NduXsKPYU7j+9+/LIZ" "k2d8UV5Q3t71JA/iABo3LeMCXgJ2tvNvJoV0F4AC4BfoIb/5CHV9A7EDIMlAek7veeeOuh0X" "GV13OVz7Kk6ruPzOU+9s0wi7aveqnARc+vZyLHpq+h7Effo1YBzNaALpvgswGNH5FfS5/wZk" "589YBs0YdGOszu9UnAevO/6669va+QFS2Pmh5Y6Udq8OwuJpeLoLgHY4vJb4A2CQKipJd45/" "8viLN+zdcIPRdUVRfD8e+uNbZpw5w47rP83oB83mhp89wABrzBGkswCUos/5NUWtQVb6yUjG" "PTNu0pc7v7w9RpPQuL7jfv/qBa/a+WSnb8PPLvSq1EMN2ppCOguAlgDiQA/8+dagrSSNOefl" "c0Yt2rLoT8S4X0/ofsI986+Yb9u8+zCV6NNTbdDqg757ZTrpKgAKugBo7pQKGJ4gI0lPprw0" "ZfS76999WFVVt1GbI0uPnLH82uWGBUNtROQ9moe+fhUzmzGVpKsA9EIPrdSUdCcy4y+jOOvF" "s06bvWH2X1VV9Ri16VXc68V1U9f93Uy7DhPNS1XQi9RaNg1IVwHQvjAn+lzKFqGVkuQw+YXJ" "Y9/b+N70WJ2/rKDsvY03b/yzmXYlgX2ItSoQ29bQcj3LVNJRAFyIvH/QR/8ANoiqkiSHSc9P" "Gvf+xvcfiuX2l+SWLFt53co7PE5POgayaF5ADnofHGyFIekoAD3QR31t/r8FIQKSNOf0506f" "MO/beQ/GCtctyin68r3L3vtV96Lu6fp/HrlWpS1gG+YzpJJ0zAXoFX52oNu/3SJbMo511ety" "/r3+351X71ldVnmgsnO+O7+pf8f+e0b2GFl93uDzalI54o5/dvzEhZsX3qeiOo3aFOcUfz7/" "ivk3jug+otGoTRrQgJgKdEKsAzQipgC5mBzElo4C0Dv8nBPxnuFZzBJjfEGfctOcm4Yt2Lxg" "fNXBqlHNgeaeQTVoWCdLQQm6nK69RZ6i1YM6D/pg2snTFreOtW8v454ZN2nRd4v+Eqfzf7bo" "qkVTj+16bCZEeu5ECIB2HyuItHZTA9nSLRfAA0xFfFkliDWAGmx25rrdufLtK4/5YPMHZ+2q" "3zXOH/SXt/fvKCihwpzCFX079P3goYkPvTmh/4R2nbkw5ukxk5dsWfJnFdVwSlqSU/LpwqsW" "3pQhnR/EVHZ8+LWWG7ASMPVoo3QTgP7AueHXZYhdgLXAcsssSiN+N+93A/6x4h+/2t+4/7Rk" "/22nw1k7tHTok29e9ObLAzoN8CX6e6P/Nfqsj7Z+9MeYnT+3ZNnSq5f+MsWx+mbjAi5BDGa1" "iGnAXuAZM41INwE4DVFdJTL1dyF6jLUkCo8tf6zL3UvuvqnqYNXZpHjh1+P0VI7oPmLG+z99" "f7ZBlZzvOfrxo69YtXvVb4hRrKNDboePl12z7ObBpYMzqfNrTEbcx03oW4NPYGI8S7oJwOWI" "LywPMQUAeBlR/ksShXHPjpu4+LvF94bUUG781smjyFP05RM/euLWS4dfekhuhi/oUwY9Mug3" "W2q3XBHrb3TI7fDhsmuW/SpDOz/oKcIhYHf4vdmYWMounbYBc9FHfS04ZC+y80cl3MmmLty8" "8AGzOz/AAd+B466edfVL0+ZOGxj5/sZ9Gz09H+p5f7zO3zG349Ll1y6/JYM7P4B21HnkjlZv" "g7YpIZ0EoEfEa23lNOpZ8dnO8srleT0e6vHghn0brieJtfDaii/o6/7XT/76/OQXJo8FeH/j" "+0XHPXnck3sa9kyK9Xsd8zouXnHdilvaspaQpuxGP65Ou6d7mmlAOk0BRgCjEaKlrVzL+X8r" "VlStyB39r9FP1fvqh8dvbRqhYWXDHt60f9OUxkBjzPz30vzSeZ9f+/ltfTr0yZaCrmcg7ufI" "UmF/w6TAtnSKA+gUfo60uSZaw2zFF/QpE5+beK/NOj+AY/We1bfGa9SruNeLa6eu/Uu8xcMM" "Yz/6aVYaHRFbgyknnQUghKiwKgkz/PHh18UqlGlj1GFlw6Z/fePX/7LaEAuoCz9bIgDptAag" "HfygfVEHsNEhi1Yz4dkJp6/fu/5Gq+1oK4qi+Ef3Gf37LO38oAuAgt4fOxm0TTrpIgB56Ik/" "WqhorUHbrOPplU+XLvxu4T1YuODXHpyK8+D5Q86/YclVS+ZYbYuF1EW81gY30045ShcBiPxC" "tC+pLlrDbOTOD+68IaSG8uO3tA8uh2vvL0/85ZWvXfjap1bbYjEH0XcCtMFNegCt0L4QBekB" "tOCuRXf1qTxQeb7VdrSVwpzCtdMnTbfNMdkWoiKmsyA9AEM0AYjMFJMeAPDo8kdvjpVBZ1dq" "GmtGXfn2lcdYbYdNaL0Q6EEveZdS0kUAWi8AgvQAuGnOTUPSdNUfgFnrZt1ktQ02wWgnIOWk" "iwBoaqiNdD5kCDBzNsyZbLUNh0Ndc92IV1a/Yvqx3jZEE4BITy4vWsNkky4CoMX+a6vc2RIl" "FpPKg5XjrLbhcFBRHdM/mT7GajtsQORgpt3jhsVQk4kUgDTl9/N/f0RzoNmSOnLJZMPeDWkt" "Ykki8n6WAhAFrQioFIAws76ZNT5+K/uzv2n/ycsrl5vi7toYKQBxaO0BZP38f0fdjh9abUMy" "UFU1x7vIO8JqOyxGCkAMImvDa/amaznopNEcbO5utQ3JYufBnRnzb2knkQKg3eNSAMJEfhHS" "A0Bk/fmD/lKr7UgWtc21lpyKYyOkBxCDaAKQ1WsAr695vWOsgzPSjXpffbsrE2cIUgBiEDkF" "kAIAfLL9k4zqME2Bpmz3AFREWXCQAnAIkV+EZm9WC8CW2i0Z4/4D+IK+jPr3tBNtXUuuAbQi" "GPFay/9Pu9j3ZFKSU1JvtQ3JxKk4M+rf0060vqjd40Gjhqn4UDsTueCnfTmGp8ZmA0eVH7U7" "fqv0IdeVa0r1G5uj3dNaarApC91SANKQs488u9pqG5JJnjsvowStHUQu6Gr3uBSAMJHzfSkA" "wODSwc1OhzNjsiGLPEUZJWjtIPJ+lgLQCukBRMHj8GSM29wxr2O2ewBSAGIQQl8Q0eZHWS8A" "JbklX1ttQ7IY32/8KqttsBgpAHHQvgzpAYQ5pusxph4jnSpyXbmb7hpz1xar7bCYyPtZLgJG" "obUAmLJHamemT5r+sUNxNFptx+HSo6jHAqttsAHRPABTzkRMFwHQFgKlBxBmcOng5o55HT+y" "2o7DZVy/cRnhyRwmcgoQh6bws7YWkEP62J4yhpQOSevR0+1075px5ozVVtthAwrCz5EH3UgB" "iGB/+DkyXrrIIltsw/PnPf++x+nZYbUd7eXIzkc+53F65OlOUBx+jkxzN2WbN10EYF/4OfIL" "Ko7WMJvo06GPf2SPkY9YbUd78Dg9lW9d9NZLVtthE1oLgIpJB9+miwBoHkAI3U0qscgW2+AL" "+pRAKOBRUEyJG08mI7qPmDGg04CsrusQQWsBqMWkXIB0ySnfH/E6gFg0yWoPYNrcaQNnfjHz" "fw/4DhxrtS1tJd+dv+79n74/22o7bEJO+AF6p99v0DbppIsA1CBGfwe6AGSlB7B069KCy9+6" "/MatNVsvS8cTgRRF8V807KK7CzwFofits4LIgUzzAPZFa5gK0kUAtDlRJ3SVzDoPYNyz4yZ+" "uPXD2/xBf9oWBBnZfeQ9T53z1H+ttsNGRN7H0gOIwT6EAGgqqblOpgRMWMkfFv+hz/RPpt9R" "01RzitW2HA69S3o//8nPP3nTajtshiYAQfT1LekBREFTxcidgBIgYxNJ1lWvy5ny0pRrv93/" "7dWqqiYU/ViUU/TlQd/BoxJtbxYluSXLvrr+q/uttsOGaFPZyPtaegBRaB0LANAZAwH449I/" "9pq/ef6Q/Y37S+ua68oa/A2lTYGm0qAazM915u7Oc+dVF3oK95TkluztU9Jnx4MTH1zZvai7" "bcqNT3lpyuh53867oznY3DOR9h6np3J8v/F/mnPZnEVTXpoy+r0N790XVIOmnDAbjw65HT58" "7cLXftsht4Oc9x+KVg5Nu/eaAdMqJCmqmjZxGKXAFeHXnRD5ANuAhVqDqXOmDpn77dzxO+p2" "jG8MNA5oyx93Ks6DnfM7LxlWPmzBvePu/fDknic3JM3yNvDIZ4909S7y/n5v496ETv5RFMXf" "v0P/p9+6+K2/Dy8f/v106I4Fd/R/6JOHHmkONPdOnbXx6dOhz7Mrr1v5oOz8USkGzg2/3o/o" "/JuBt8wyIJ0EAOAGxKmpheGHb0vtljenvDTl4m+qv/mpL+jrlowPURTFV5ZfNv+XI3/56J2n" "3rk1GX8zHpUHKl0Tn5t4xdrqtdeH1FBCR2UV5xR/esuJt9zzh7F/+C7a9dkbZhdf9uZlD9Q2" "1Z6cVGMTQFEU/0k9TvJ+fM3Hs8z+7DRiEHBS+PUuxBrAYuALswxINwH4EeJL84TUUKe7Ft01" "9IGPHzi+MdDYNRUfpqAEuxd3f/2esfc8cdUxV6Wsas0lb1xywtvr3r6zKdB0RCLtXQ5X9Sm9" "Trl/8VWL58RrW9NU4zjpnyddu2HvhqtDaqggXvtkUOgp/Orioy7+y8wpM7M9zz8epwF9EMlu" "e8PvPY+J61rpJgBHAxP+/vnf+9+z9J4Lt9dtN2U7zKE4Go/oeMQzi65a9PdkrhM899/nOv92" "7m+n7arfNSWR9gpKqFdJr5eeOfeZGWP6jjnYls96cdWLnW6bf9sNO+p2XJiq+IEcV86WUb1G" "PTz/ivnzUvH3M5CLEDtZB8OPJuBxWiYFpZR0E4CO17577cynVjx1XkgNmR7GXJxT/Nlz5z33" "67OPPPuwEjXqffWOU58+9SdfVX11c1ANJpTUVOgp/O+VP7jynhlnzlh7OJ9916K7+jy2/LFf" "VjdWj03WToHH6akcWjb06dmXzn7NTgupNqcjoAn/PkT230bgHTONSBsBULyKC3gYuLEtv+cA" "OigKpQ6FMkUhF9irquxRVapDapuDCHJcOVt/c/Jvbrpn3D2b2/irAJz90tmj5m+aPy3RRUqn" "w1l7bNdj//rRzz56I5mZc8srl+fdseCOH67avWpsdUP1qcFQsEMbfl0tcBes6Vncc+GkAZMW" "PXzGw98ky64sYgignYqszf8XAivMNCItBEDxKh2AN4BxibQvUxRGuZyMdjk5+TyThgAAENxJ" "REFU1uWMWT1ke0hlaSDA4kCQ1cFQQr6XU3EePGvQWb+ddfGsDxOxB8QOxfP/fX5aXXPdiQn+" "itq1sOusRyY/8tAFQy9I6b5wTVONY9rcaUdt3Lex577GfWUHfAfKG/wNZc2B5jKn4mzMc+ft" "LnAX7CnJLdldll+257wh56259rhrMzb+wiTGAr0QI78W+PMM+lqAKdheABSv4gbmAmPitT3a" "6eAXOR5+4HR8f8BaW9irqrzqC/Cazx+3GoOiKP7Lj778Z8+c+8zKWO2mL5ve/c8f/fnm3fW7" "z4TEzMpz5224YMgFdz973rOmjgYS01AQ838P+vy/AXjCdEPSQAAeB66P1aavw8H1OW5GuZKz" "trVbVflns5/3/AFibV67HK69D5/x8EU3jrhxV+trszfMLr5pzk3Xbqndcmmic22H4mgYVjbs" "0flXzH+hvKA87VJ8JQnTE92b3YvYBfgGMD1D0tYCoHiVqcCMWG3Odrv4da4nJSGNK4JB7mz0" "URvjOypwF6xZ9vNlV2hBOBv3bfT8+JUfX7ymes11wVAw4YSl0vzSuXePvfsv159wvXStMx9t" "+y8AaNvLc4B1ZhtiWwFQvMoYYB4G4coO4KYcDz/xpDaauTKkcltjM5tDxr5AeUH5nG23bvv9" "hOcmTP50+6e3+IK+7on+/TxX3sZJAybd/9ZFb32cFIMldscD/ARxC2vuvw/h/pu+g2JLAVC8" "ihNYhVgpPQQHcG9eDqOT5PLHo0FVubWxmdVBYxHIdeVuaQo09Un0b7qd7t3HdTtuxoLLF8yS" "ufFZRWT03x5EbsvXiHUu07FrMtBVGHR+gJtzPKZ1foB8ReFPeTlcU9/EHgPBTLTzOxXnwYGd" "Bz716oWvPhsZuy/JGrRoTx96Ytsai2yxX01AxavkAV6j6+e6XVyQYrc/Gp3CItDeyBkFJdCr" "uNeLL1/w8plrp66dKTt/VlIElIVfa4e61AHbrTHHnh7ALUCPaBcGOBzcmmtdmvtgp4Obcj08" "1NS2Wpal+aVzbznxlofNSiyS2BZt9FfRz7o4rMjOw8VWAqB4FQfwa6PrN+a4sboI3jluF2/4" "AmyJsSioUZRT9OWFQy988J9n/1OWwJIA9A8/N6HH+1vm/oPNBAD4IbqL1ILjnU5GmjjvN8IJ" "3JDj5veNxh58rit305i+Y/763mXvLTRsJMk2eiNS2EF3/3diYvWfaNhNAM41unB9jn2OAxzl" "cjLc6WBVlF0Bh+JoXnHdigsHlw6WNe8lkQwPPwfQj/2y/Fg0uy0CRhWAng6FIU57mTrRHV07" "Q2oo5/YFt59gsjkSe9MdUb4OxL4/iNBfS91/sJEAKF7laPQ5UgtOddnNUYHRLqdhYP/yyuUJ" "JS1JsobI0V9b/PsCCwJ/WmMbAQBGGl0wc88/UUoVhaEGXklNY83wqBck2Ug50CX8Wiv22QR8" "ZY05LbGTAESt55erwDCbuf8axzijC5Mv6Iu6kCnJSo4OPwfRR/8VmHT8dzzs1LOixs+XKYqt" "jIykVIk+CQiEAp1rmmrsarbEPDqh39cHEVt/Pkwu+hELO92kUQWgVLGTiS0pdUQXABXV8cba" "NzpHvSjJJrTRP4Q++n8V8dpy7NS7onsABp3MDhh5AACfV34upwHZTVfE3j+Iub+KWPQzreR3" "IthJAKIWx8w324o2kBdDm/Y37jelBLfEljgArfRbALHlByLD1ZIDZ4ywkwBURXtzrw3TlTX2" "hYxtG9BpQMrOEZDYnqHoZ/4dQJ/7L7fMIgPsJACV0d6strEAxLJtQv8Je0w0RWIfCoAfhF83" "oZ9evQw9CMg22F4A9sQYZa3GyDtxKI7Gth7cIckYRiJSRlTE6A+i8MeXllkUA9sLwD5Vpc6m" "XsB2A3FyO92yrl920hNR6hvEaK8V/JiPiaf9tAU7CcCWaG+GgI8C9iuQGwKWGdiV68zdaa41" "EhvgRI9mjVz4+xqR9WdL7CQACxDlkQ9hiQ0FYHUwxD4Dz6RXSa+EDwyRZAzHoqf71qEX/Vhi" "mUUJYBsBUCvUGmBRtGufBYI02cyBWhpDlC456pIPTDRFYj09ESv/IHL9tTDfJdgo6CcathGA" "MG9He7MZmOW3PHHqew6qKrMN7Mlz5224Y/Qd20w2SWIdBYhCNiBc/7rw60qE+29rbCMAilcp" "AwYaXX/W56feJouBz/oChoeF9CjqIUf/7EEBTkUc8Q1Qg77n/x+rjGoLlifaK17leOBm9LPS" "o1KrqrzoC3CtxZWBdoZUXvNFXaoAUM8bfF5a/MdLksKx6CXs6tDz++cixMD2WCIA4QM/zwd+" "CZyS6O+94vczwe2kn8MaxyUEPNjsi75SCZQXlL933+n3bTTTJoll9ACOCr9uRF/1/wpYb4lF" "7cBUAVC8SjlwHeKwz4SPz9JoUuG2xmZm5udSEiMRJ1X8vdnPJwaLf4qi+KedPO1vJpsksYZ8" "YFT4deS8fw+w2BKL2okpR4MpXuUExGgf081PlGOcDv6an2uqer3vD3B3jPMAepf0fn7Lr7b8" "xUSTJNbgAiYCpeGfqxEi4Aeex+Iqv20lZX0ows2/GTg5mX97ZTDE/zY283+5OTEz8pLFPH+A" "P8fo/E7FefDBiQ8+mXpLJBajIE721Tp/Lfq8fx5p1vkhBR6A4lW6AL+gnW4+Yj71AuK01KfR" "51mH0N/h4C95OXRLUc2AEDCz2c9zxot+AKEzB5558+xLZ6eV6ydpF6PQC9fWo8f6r0IIQNqR" "FAEIn+gzCfg5MAVoz1L9VuAx4B9qhbo3/Hf7AZ9icFgIQImicFuuh1OTXDh0j6pyf5OPj+NE" "IQ4tGzp99Y2rn0rqh0vsyAm0DPapDb+uBF7HBhV+28NhCYDiVXoDPws/esVpbsRi4G/ALLVC" "PaS3KV5lNCKZIuahgD9wOpia4zGs1JsojSo87/Pzit8fN/qwS0GXf1f9pur2w/pASTpwFHBc" "+HUz+n7/XuAVbB7tF4s2C0B4bn82YrSfSPuCiTQ3/xG1Qo17bp7iVa4G/gmGpfi/51SXk9Pd" "Lk5yOtu0PrAhFGJpIMjbvoBhjH8khZ7Cr5Zfu/yawaWD5Sm/mc0A9K1qP7APPdX3ZfRpQFqS" "sAAoXuVI4BrgSkSt8/awBd3N39eWX1S8ynnAs+gJFzFxAyNcTo5zOihVHHRyiCO+c1HYp6rs" "VVX2qSrfhTt+VRvqDpQXlM+Zf8X8/5NHfGc8vRGLfgrCxd+HXuDz5fDPaU1cAVC8ykjgfkTI" "Y3tZhHDz34nm5ieK4lWGA7OAfodhy+GgDi4d/MjaqWtnWvT5EvMYCJyE6PwhhLsfRAjBa9g4" "xbctxBQAxaucCvwbg4KdcTgAvATMUCvUVe0zL6pNnYE3EMpsGk7F2Tih/4Tb/vPT/8gTfzOf" "o4Fjwq9DiJE+EH79DrDJIruSTjwB2EHbt/I+A2YCL6sVakrKYileRQEuBe4F+qTiMzQciiM0" "vt/4/04/Y/qiYWXD3kaMBJLMRAFGAIPDPwcQe/ua1/o+NjjRN5nEE4Aa9OqmsahBREHNTGRR" "L1koXiUHEWF4B9Ax2X//qPKjVj048cH5E4+YqG3xBBDTmajlyyRpjROxz68NKH5E5w+FH/8B" "1lljWuqIJwCVGJzZF+ZDxGj/mlqhNibZtoRRvEpH4CrgHMR/4uEEBWxzKI537x5796Y7Rt+h" "fTkehMBo88GPgM2H8RkSe+EGxiIO84CWW31+hNsftWRduhNPAE5ElOqKPORiL/AMYiV/bWrN" "azuKV+kEnIXYqjwOMYXJNWgeQiRwbEYo/Dtqhaqd26YAYxApnyDCpjuhb3uuQESASdKbQkTn" "1zzIRvSSXo3AWxicWZEJJLILcCIilj8f+BZ4W61Q02r7K+wh9ECIQR5iBbcSqFIr1HgRXCei" "V3xxIm4ULYdiB8ILSqvvQ/I9vRD/t1qQ2UH02v11iMXmtIvvbwumZANmAEcBpyO8AgfQAf2m" "aUBEM8qDQNIHB8I71EJ7VUSH16ax1cCb2PAgj2QjBSBx+gM/Qh/9C9GDklTEwQ8ZtUKcoRQg" "Ylq0/JIAYr6veYI7ELUps8KrkwLQNroh1heKwz97EN6Ati6wHbFAmBU3TxrSA7FIrNWkiJzv" "A/wXscuTlok97UEKQNvJQWQ+Dgj/7EBslWo3VT1iXWCX+aZJDHAiAnuGhX9u7fL7EHX80qaU" "V7KQAtB+jkW4ktqWY+SUAMSC6RekcaZYhtATcWKP9n/T2uXfjYh2TYsinslGCsDh0QWxLqAF" "S3nCrzVR8CG2C9dj07PhMphCRFRfZJp6a5d/BeLwDvsdPWUSUgAOnxzEDsGg8M8KYqEp0hvY" "C3yCDCM2AyfC1R+OLsRa4U6trlszwuXfYLp1NkMKQPL4AWJKoFVDciK8gchCJusRuwXGBQYl" "h0MPhLuvJa+piK28BvRRfycwG72Sb1YjBSC5FCGyFAdFvJeL2DXQdgqagbWIuHIpBMmhDJHB" "1yPivSZEJw+Ff24ElpIGx3WZiRSA1NAHGIceXqogpgSRIdV+4BtgDXKhsL10R7j6XSLeCyBS" "0bWtWBURsv0h8ns+BCkAqcOJKCR5InrwkAshBJG5CUHEXHQ1YgtREp/eiI7fOeK9EOL7i3T3" "dyFyWTI2lv9wkQKQeooRySZHRLznRAhBXsR7IUShia+R89NoKIhKUMNpmaIeRMzzm9A7fhNi" "xF+F3H2JiRQA8+iHSKrqGvGeEzEtyKNlwdMqhBhsAcOjCLOFjgjx7EdLwQygd/zI91Yhdlws" "S09PJ6QAmE9vxLQgcn/agRCCfFoKQRBxXsK3iNXrbPnPykN0+CM4tNCLH9HxI8OtfcBKROCV" "7PhtQAqAdXRDCEH/iPcUxPpAHoeeg9CI8Ao2kZkpqk6EKB6BWNyLFEItN7+JljsnjYht1ZXI" "/It2IQXAekoRQjCIlje9EyEEeRxa4ageMU2oQngGDaQfCuLf3hUhhmUc+u9sRnTyZlp6PweB" "zxHJO1mTuJMKpADYhw6IvezBHHr2gQfdM4h23MkBdDGowp7bXQrCne+G6PRdiH44rR9hfyP6" "Hj4IAdiG2Db9hiwO300mUgDsh4JYJxiKqE3vanXNE37kYHy6cy1imlCL2FHQns0aLfMRK/XF" "Ec+lRD/eTUW49T7ESN/axn2ITr+WND+Fx45IAbA3boQIDEXMj1uP/g50QfAQ/7j3BoQY1CLc" "aB9ixNXOt498BNBHWXf44Yp4HfleHi07eyw7tEKbzRGf35pG9CApuYefQqQApA9FwJGIKMMe" "RO9kkYLgQsypD6dCcoj2nf2ooSKERBMTrcNHu+lqEC7+JkSR1lCUNpIkIwUgPXEi5tG9ENOF" "bhh3dCV8zYUuCtrz4R2lLFARnVU7NivyEasTH0B0+G2IrU7p3luAFIDMwIXYOuuNEIVS9KzE" "eCitHo5WP4Po5FpHj3xuy81zALFIuRXR6TNxKzPtkAKQuRQhVt07Is4z6BR+XRzrlw4T7TSd" "/YjFu8jnbI9otCVSALIPF0IIihBegrZmEO21GzHaa3N3n8HrJsQcXrrxaYYUAIkki0nGIpBE" "IklTpABIJFmMFACJJIuRAiCRZDFSACSSLEYKgESSxUgBkEiyGCkAEkkWIwVAIslipABIJFmM" "FACJJIuRAiCRZDFSACSSLEYKgESSxUgBkEiyGCkAEkkWIwVAIslipABIJFmMFACJJIuRAiCR" "ZDFSACSSLEYKgESSxUgBkEiyGCkAEkkWIwVAIslipABIJFmMFACJJIuRAiCRZDH/D8PLUIX+" "BVXhAAAAAElFTkSuQmCC") getMainData = Main.GetData getMainImage = Main.GetImage getMainBitmap = Main.GetBitmap getMainIcon = Main.GetIcon pycorrfit-1.1.7/pycorrfit/gui/tools/0000775000372000037200000000000013554643106020357 5ustar travistravis00000000000000pycorrfit-1.1.7/pycorrfit/gui/tools/plotexport.py0000664000372000037200000000421313554642611023151 0ustar travistravis00000000000000"""Module tools - plotexport: Let the user create nice plots""" import wx class Tool(wx.Frame): # This tool is derived from a wx.frame. def __init__(self, parent): # parent is the main frame of PyCorrFit self.parent = parent # Get the window positioning correctly pos = self.parent.GetPosition() pos = (pos[0]+100, pos[1]+100) wx.Frame.__init__(self, parent=self.parent, title="Example Tool", pos=pos, style=wx.DEFAULT_FRAME_STYLE | wx.FRAME_FLOAT_ON_PARENT) # MYID # This ID is given by the parent for an instance of this class self.MyID = None # Page - the currently active page of the notebook. self.Page = self.parent.notebook.GetCurrentPage() # Content self.panel = wx.Panel(self) btnexample = wx.Button(self.panel, wx.ID_ANY, 'Example button') # Binds the button to the function - close the tool self.Bind(wx.EVT_BUTTON, self.OnClose, btnexample) self.topSizer = wx.BoxSizer(wx.VERTICAL) self.topSizer.Add(btnexample) self.panel.SetSizer(self.topSizer) self.topSizer.Fit(self) self.SetMinSize(self.topSizer.GetMinSize()) # Icon if parent.MainIcon is not None: wx.Frame.SetIcon(self, parent.MainIcon) self.Show(True) def OnClose(self, event=None): # This is a necessary function for PyCorrFit. # Do not change it. self.parent.toolmenu.Check(self.MyID, False) self.parent.ToolsOpen.__delitem__(self.MyID) self.Destroy() def OnPageChanged(self, page, trigger=None): """ This function is called, when something in the panel changes. The variable `trigger` is used to prevent this function from being executed to save stall time of the user. Forr a list of possible triggers, see the doc string of `tools`. """ # When parent changes # This is a necessary function for PyCorrFit. # This is stuff that should be done when the active page # of the notebook changes. self.Page = page pycorrfit-1.1.7/pycorrfit/gui/tools/parmrange.py0000664000372000037200000001441213554642611022707 0ustar travistravis00000000000000"""Module tools - RangeSelector Select the range in which the parameter should reside for fitting. """ import numpy as np import wx from pycorrfit import models as mdls from .. import wxutils class RangeSelector(wx.Frame): # This tool is derived from a wx.frame. def __init__(self, Page): # parent is the main frame of PyCorrFit self.parent = Page.parent # Get the window positioning correctly pos = self.parent.GetPosition() pos = (pos[0]+100, pos[1]+100) wx.Frame.__init__(self, parent=self.parent, title="Parameter Range", pos=pos, style=wx.DEFAULT_FRAME_STYLE | wx.FRAME_FLOAT_ON_PARENT) # Page - the currently active page of the notebook. self.Page = self.parent.notebook.GetCurrentPage() # Content self.panel = wx.Panel(self) self.topSizer = wx.BoxSizer(wx.VERTICAL) self.WXboxsizerlist = list() self.WXparmlist = list() self.OnPageChanged(self.Page) # Icon if self.parent.MainIcon is not None: wx.Frame.SetIcon(self, self.parent.MainIcon) self.Show(True) def FillPanel(self): """ Fill the panel with parameters from the page """ corr = self.Page.corr self.parameter_range = np.zeros_like(corr.fit_parameters_range) leftbound = [] for item in corr.fit_parameters_range[:, 0]: if item is None: leftbound.append(-np.inf) else: leftbound.append(item) labels, parmleft = mdls.GetHumanReadableParms(corr.fit_model.id, # @UnusedVariable leftbound) rightbound = [] for item in corr.fit_parameters_range[:, 1]: if item is None: rightbound.append(np.inf) else: rightbound.append(item) labels, parmright = mdls.GetHumanReadableParms(corr.fit_model.id, rightbound) self.parameter_range[:, 0] = np.array(parmleft) self.parameter_range[:, 1] = np.array(parmright) # create line self.WXboxsizer = wx.FlexGridSizer( rows=len(labels), cols=5, vgap=5, hgap=5) for i in range(len(labels)): left = wxutils.PCFFloatTextCtrl(self.panel) right = wxutils.PCFFloatTextCtrl(self.panel) left.SetValue(self.parameter_range[i][0]) right.SetValue(self.parameter_range[i][1]) left.Bind(wx.EVT_SPINCTRL, self.OnSetParmRange) right.Bind(wx.EVT_SPINCTRL, self.OnSetParmRange) text = wx.StaticText(self.panel, label=u'< ') text2 = wx.StaticText(self.panel, label=labels[i]) text3 = wx.StaticText(self.panel, label=u' <') self.WXboxsizer.Add(left) self.WXboxsizer.Add(text) self.WXboxsizer.Add(text2, 0, wx.ALIGN_CENTER, 0) self.WXboxsizer.Add(text3) self.WXboxsizer.Add(right) self.WXparmlist.append([left, [text, text2, text3], right]) self.WXboxsizer.Layout() self.topSizer.Add(self.WXboxsizer) self.btnapply = wx.Button(self.panel, wx.ID_ANY, 'Apply') self.Bind(wx.EVT_BUTTON, self.OnSetParmRange, self.btnapply) self.topSizer.Add(self.btnapply) self.topSizer.Layout() self.panel.SetSizer(self.topSizer) self.topSizer.Fit(self.panel) self.SetMinSize((self.topSizer.GetMinSize()[0] + 20, self.topSizer.GetMinSize()[1] + 40)) self.topSizer.Fit(self) def OnClose(self, event=None): # This is a necessary function for PyCorrFit. # Do not change it. self.parent.RangeSelector = None self.Destroy() def OnPageChanged(self, page=None, trigger=None): """ This function is called, when something in the panel changes. The variable `trigger` is used to prevent this function from being executed to save stall time of the user. Forr a list of possible triggers, see the doc string of `tools`. """ # When parent changes # This is a necessary function for PyCorrFit. # This is stuff that should be done when the active page # of the notebook changes. if trigger in ["parm_batch", "fit_batch", "page_add_batch"]: return self.Page = page if self.parent.notebook.GetPageCount() == 0: self.panel.Disable() return self.panel.Enable() try: self.btnapply.Destroy() except: pass for i in np.arange(len(self.WXparmlist)): self.WXparmlist[i][0].Destroy() # start self.WXparmlist[i][1][0].Destroy() # pname self.WXparmlist[i][1][1].Destroy() # pname self.WXparmlist[i][2].Destroy() # end del self.WXparmlist for i in np.arange(len(self.WXboxsizerlist)): self.WXboxsizer.Remove(0) self.WXboxsizerlist = list() self.WXparmlist = list() self.FillPanel() def OnSetParmRange(self, e): """ Called whenever something is edited in this frame. Writes back parameter ranges to the page """ corr = self.Page.corr # Read out parameters from all controls for i in range(len(self.WXparmlist)): self.parameter_range[i][0] = self.WXparmlist[i][0].GetValue() self.parameter_range[i][1] = self.WXparmlist[i][2].GetValue() if self.parameter_range[i][0] > self.parameter_range[i][1]: self.parameter_range[i][1] = 1.01 * \ np.abs(self.parameter_range[i][0]) self.WXparmlist[i][2].SetValue(self.parameter_range[i][1]) # Set parameters parm0 = mdls.GetInternalFromHumanReadableParm(corr.fit_model.id, self.parameter_range[:, 0])[1] parm1 = mdls.GetInternalFromHumanReadableParm(corr.fit_model.id, self.parameter_range[:, 1])[1] corr.fit_parameters_range = np.dstack((parm0, parm1))[0] if self.parent.MenuAutocloseTools.IsChecked(): # Autoclose self.OnClose() pycorrfit-1.1.7/pycorrfit/gui/tools/statistics.py0000664000372000037200000006733513554642611023141 0ustar travistravis00000000000000"""Provide the user with tab-separated statistics of their curves. Values are sorted according to the page number. """ import codecs import re import numpy as np import wx import wx.lib.plot as plot # Plotting in wxPython import wx.lib.scrolledpanel as scrolled from pycorrfit import models as mdls from .info import InfoClass from .. import misc # Menu entry name MENUINFO = ["&Statistics view", "Show some session statistics."] def run_once(f): def wrapper(*args, **kwargs): if not wrapper.has_run: wrapper.has_run = True return f(*args, **kwargs) wrapper.has_run = False return wrapper class Stat(wx.Frame): # This tool is derived from a wx.frame. def __init__(self, parent): self.MyName = "STATISTICS" # parent is the main frame of PyCorrFit self.boxsizerlist = list() self.parent = parent # Get the window positioning correctly pos = self.parent.GetPosition() pos = (pos[0]+100, pos[1]+100) wx.Frame.__init__(self, parent=self.parent, title="Statistics", pos=pos, size=(700, 600), style=wx.DEFAULT_FRAME_STYLE | wx.FRAME_FLOAT_ON_PARENT) # MYID # This ID is given by the parent for an instance of this class self.MyID = None self.MyName = "STATISTICS" # List of parameters that are plotted or not self.PlotParms = list(["None", 0]) # Page - the currently active page of the notebook. self.Page = self.parent.notebook.GetCurrentPage() # Pagenumbers self.PageNumbers = range(1, 1+self.parent.notebook.GetPageCount()) # Splitter window. left side: checkboxes # right side: plot with parameters self.sp = wx.SplitterWindow(self, style=wx.SP_3DSASH) # This is necessary to prevent "Unsplit" of the SplitterWindow: self.sp.SetMinimumPaneSize(1) # Content # We will display a dialog that conains all the settings # - Which model we want statistics on # - What kind of parameters should be printed # (We will get the parameters from the current page) # If on another page, the parameter is not available, # do not make a mess out of it. # Then the user presses a button and sees/saves the table # with all the info. self.panel = scrolled.ScrolledPanel(self.sp) self.panel.SetupScrolling(scroll_y=True) # Parameter settings. if self.parent.notebook.GetPageCount() != 0: self.InfoClass = InfoClass(CurPage=self.Page) else: self.panel.Disable() # A dropdown menu for the source Page: text = wx.StaticText(self.panel, label="Create a table with all the selected\n" + "variables below from pages with the\n" + "same model as the current page.") # Page selection as in average tool Pagetext = wx.StaticText(self.panel, label="Curves ") Psize = text.GetSize()[0]/2 self.WXTextPages = wx.TextCtrl(self.panel, value="", size=(Psize, -1)) # Set number of pages pagenumlist = list() for i in np.arange(self.parent.notebook.GetPageCount()): Page = self.parent.notebook.GetPage(i) pagenumlist.append(int("".join(filter(lambda x: x.isdigit(), Page.counter)))) valstring = misc.parsePagenum2String(pagenumlist) self.WXTextPages.SetValue(valstring) # Plot parameter dropdown box self.PlotParms = self.GetListOfPlottableParms() Parmlist = self.PlotParms DDtext = wx.StaticText(self.panel, label="Plot parameter ") self.WXDropdown = wx.ComboBox(self.panel, -1, "", size=(Psize, -1), choices=Parmlist, style=wx.CB_DROPDOWN | wx.CB_READONLY) self.Bind(wx.EVT_COMBOBOX, self.OnDropDown, self.WXDropdown) self.Bind(wx.EVT_TEXT, self.OnDropDown, self.WXTextPages) self.WXDropdown.SetSelection(0) # Show Average and SD textavg = wx.StaticText(self.panel, label="Average ") textsd = wx.StaticText(self.panel, label="Standard deviation ") self.WXavg = wx.TextCtrl(self.panel, size=(Psize, -1)) self.WXsd = wx.TextCtrl(self.panel, size=(Psize, -1)) self.WXavg.SetEditable(False) self.WXsd.SetEditable(False) # Create space for parameters self.box = wx.StaticBox(self.panel, label="Export parameters") self.masterboxsizer = wx.StaticBoxSizer(self.box, wx.VERTICAL) self.masterboxsizer.Add(text) self.boxsizer = wx.BoxSizer(wx.HORIZONTAL) self.masterboxsizer.Add(self.boxsizer) self.Checkboxes = list() self.Checklabels = list() if self.parent.notebook.GetPageCount() != 0: self.OnChooseValues() self.btnSave = wx.Button(self.panel, wx.ID_ANY, 'Save') self.Bind(wx.EVT_BUTTON, self.OnSaveTable, self.btnSave) # Add elements to sizer self.topSizer = wx.BoxSizer(wx.VERTICAL) GridAll = wx.FlexGridSizer(rows=4, cols=2, vgap=5, hgap=5) GridAll.Add(Pagetext) GridAll.Add(self.WXTextPages) GridAll.Add(DDtext) GridAll.Add(self.WXDropdown) GridAll.Add(textavg) GridAll.Add(self.WXavg) GridAll.Add(textsd) GridAll.Add(self.WXsd) #Psizer = wx.BoxSizer(wx.HORIZONTAL) # Psizer.Add(Pagetext) # Psizer.Add(self.WXTextPages) #DDsizer = wx.BoxSizer(wx.HORIZONTAL) # DDsizer.Add(DDtext) # DDsizer.Add(self.WXDropdown) # self.topSizer.Add(Psizer) # self.topSizer.Add(DDsizer) self.topSizer.Add(GridAll) self.topSizer.Add(self.masterboxsizer) self.topSizer.Add(self.btnSave) # Set size of window self.panel.SetSizer(self.topSizer) self.topSizer.Fit(self.panel) px = self.topSizer.GetMinSize()[0] # Plotting panel self.canvas = plot.PlotCanvas(self.sp) self.canvas.enableZoom = True self.sp.SplitVertically(self.panel, self.canvas, px+5) # Icon if parent.MainIcon is not None: wx.Frame.SetIcon(self, parent.MainIcon) self.Show(True) self.OnDropDown() def GetListOfAllParameters(self, e=None, return_std_checked=False, page=None): """ Returns sorted list of parameters. If return_std_checked is True, then a second list with standart checked parameters is returned. """ if page is None: page = self.Page self.InfoClass.CurPage = page # Now that we know our Page, we may change the available # parameter options. Infodict = self.InfoClass.GetCurInfo() # We want to sort the information and have some prechecked # values in the statistics window afterwards. # new iteration keys = Infodict.keys() parms = list() errparms = list() for key in keys: for item in Infodict[key]: if item is not None: if key == "fitting" and item[0].startswith("Err "): errparms.append(item) elif len(item) == 2: parms.append(item) # Separate checkbox for fit errors if len(errparms) > 0: parms.append(("Fit errors", errparms)) Info = Stat.SortParameters(parms) # List of default checked parameters: checked = np.zeros(len(Info), dtype=np.bool) # Fit parameters pbool = page.corr.fit_parameters_variable model = mdls.modeldict[page.corr.fit_model.id] pname = mdls.GetHumanReadableParms(model.id, model.parameters[1])[0] checkadd = np.array(pname)[pbool] for ii, p in enumerate(Info): if p[0] in checkadd: checked[ii] = True # A list with additional strings that should be default checked # if found somewhere in the data. checklist = ["cpp", "duration", "bg rate", "avg.", "Model name", "filename/title"] for i in range(len(Info)): item = Info[i] for checkitem in checklist: if item[0].count(checkitem): checked[i] = True # A list with strings that should not be checked: nochecklist = [] for i in range(len(Info)): item = Info[i] for checkitem in nochecklist: if item[0].count(checkitem): checked[i] = False if return_std_checked: return Info, checked else: return Info def GetListOfPlottableParms(self, e=None, return_values=False, page=None): """ Returns list of parameters that can be plotted. (This means that the values are convertible to floats) If return_values is True, then a second list with the corresponding values is returned. """ if page is None: page = self.Page if self.parent.notebook.GetPageCount() != 0: Info = self.GetListOfAllParameters(page=page) parmlist = list() parmvals = list() for item in Info: if item is not None and len(item) == 2: try: val = float(item[1]) except: pass else: # save the key so we can find the parm later parmlist.append(item[0]) parmvals.append(val) else: parmlist = [""] parmvals = [0] if return_values: return parmlist, parmvals else: return parmlist def GetWantedParameters(self): """ Updates self.SaveInfo with all the information that will be saved to the table. """ strFull = self.WXTextPages.GetValue() PageNumbers = misc.parseString2Pagenum(self, strFull) # Get the wanted parameters from the selection. checked = list() for i in np.arange(len(self.Checkboxes)): if self.Checkboxes[i].IsChecked() == True: checked.append(self.Checklabels[i]) # Collect all the relevant pages pages = list() for i in np.arange(self.parent.notebook.GetPageCount()): Page = self.parent.notebook.GetPage(i) if Page.modelid == self.Page.corr.fit_model.id: # Only pages with same modelid if int(Page.counter.strip("#: ")) in PageNumbers: # Only pages selected in self.WXTextPages pages.append(Page) self.InfoClass.Pagelist = pages AllInfo = self.InfoClass.GetAllInfo() self.SaveInfo = list() # Some nasty iteration through the dictionaries. # Collect all checked variables. pagekeys = list(AllInfo.keys()) # If pagenumber is larger than 10, # pagekeys.sort will not work, because we have strings # Define new compare function def cmp_func(a): return int(a.strip().strip("#")) pagekeys.sort(key=cmp_func) # for Info in pagekeys: # pageinfo = list() # for item in AllInfo[Info]: # for subitem in AllInfo[Info][item]: # if len(subitem) == 2: # for label in checked: # if label == subitem[0]: # pageinfo.append(subitem) # # We want to replace the above iteration with an iteration that # covers missing values. This means checking for # "label == subitem[0]" # and iteration over AllInfo with that consition. for ii in pagekeys: pageinfo = list() for label in checked: label_in_there = False for item in AllInfo[ii]: for subitem in AllInfo[ii][item]: if subitem is not None and len(subitem) == 2: if label == subitem[0]: label_in_there = True pageinfo.append(subitem) elif label == "Fit errors" and subitem[0].startswith("Err "): label_in_there = True pageinfo.append(subitem) if label_in_there == False: # No data available pageinfo.append([label, "NaN"]) self.SaveInfo.append(pageinfo) def OnCheckboxChecked(self, e="restore"): """ Write boolean data of checked checkboxes to Page variable *StatisticsCheckboxes*. If e=="restore", then we will attempt to get the info back from the page. """ # What happens if a checkbox has been checked? # We write the data to the Page # (it will not be saved in the session). if e == "restore": checklist = self.Page.StatisticsCheckboxes if checklist is not None: if len(checklist) <= len(self.Checkboxes): for i in np.arange(len(checklist)): self.Checkboxes[i].SetValue(checklist[i]) else: checklist = list() for cb in self.Checkboxes: checklist.append(cb.GetValue()) self.Page.StatisticsCheckboxes = checklist def OnChooseValues(self, event=None): """ Plot the values for the parameter selected by the user. """ Info, checked = self.GetListOfAllParameters( return_std_checked=True) #headcounter = 0 #headlen = len(head) # We will sort the checkboxes in more than one column if there # are more than *maxitemsincolumn* maxitemsincolumn = np.float(19) Sizernumber = int(np.ceil(len(Info)/maxitemsincolumn)) self.boxsizerlist = list() for i in np.arange(Sizernumber): self.boxsizerlist.append(wx.BoxSizer(wx.VERTICAL)) # Start at -1 so the indexes will start at 0 (see below). #itemcount = -1 for i in range(len(Info)): #itemcount += 1 #headcounter += 1 checkbox = wx.CheckBox(self.panel, label=Info[i][0]) # if headcounter <= headlen: # checkbox.SetValue(True) # Additionally default checked items # for checkitem in checklist: # if item[0].count(checkitem): # checkbox.SetValue(True) checkbox.SetValue(checked[i]) # Add checkbox to column sizers sizern = int(np.floor(i/maxitemsincolumn)) self.boxsizerlist[sizern].Add(checkbox) self.Checkboxes.append(checkbox) self.Checklabels.append(Info[i][0]) self.Bind(wx.EVT_CHECKBOX, self.OnCheckboxChecked, checkbox) # Add sizers to boxsizer for sizer in self.boxsizerlist: self.boxsizer.Add(sizer) self.OnCheckboxChecked("restore") self.AllPlotParms = Info def OnClose(self, event=None): # This is a necessary function for PyCorrFit. # Do not change it. self.parent.toolmenu.Check(self.MyID, False) self.parent.ToolsOpen.__delitem__(self.MyID) self.Destroy() def OnDropDown(self, e=None): """ Plot the parameter selected in WXDropdown Uses info stored in self.PlotParms and self.InfoClass """ if self.parent.notebook.GetPageCount() == 0 or self.Page is None: self.canvas.Clear() return # Get valid pages strFull = self.WXTextPages.GetValue() try: PageNumbers = misc.parseString2Pagenum(self, strFull, nodialog=True) except: PageNumbers = self.PageNumbers else: self.PageNumbers = PageNumbers # Get plot parameters DDselid = self.WXDropdown.GetSelection() #[label, key] = self.PlotParms[DDselid] label = self.PlotParms[DDselid] # Get potential pages pages = list() for i in np.arange(self.parent.notebook.GetPageCount()): Page = self.parent.notebook.GetPage(i) if Page.corr.fit_model.id == self.Page.corr.fit_model.id: # Only pages with same modelid if int(Page.counter.strip("#: ")) in PageNumbers: # Only pages selected in self.WXTextPages pages.append(Page) plotcurve = list() for page in pages: pllabel, pldata = self.GetListOfPlottableParms(page=page, return_values=True) # Get the labels and make a plot of the parameters if len(pllabel)-1 >= DDselid and pllabel[DDselid] == label: x = int(page.counter.strip("#: ")) y = pldata[DDselid] plotcurve.append([x, y]) else: # try to get the label by searching for the first # instance for k in range(len(pllabel)): if pllabel[k] == label: x = int(page.counter.strip("#: ")) y = pldata[k] plotcurve.append([x, y]) # Prepare plotting self.canvas.Clear() linesig = plot.PolyMarker(plotcurve, size=1.5, marker='circle') plotlist = [linesig] # average line try: avg = np.average(np.array(plotcurve)[:, 1]) maxpage = int(np.max(np.array(plotcurve)[:, 0]) + 1) minpage = int(np.min(np.array(plotcurve)[:, 0]) - 1) except: minpage = 0 maxpage = 0 self.WXavg.SetValue("-") self.WXsd.SetValue("-") else: # Plot data plotavg = [[0.5, avg], [maxpage+.5, avg]] #lineclear = plot.PolyLine(plotavg, colour="black") lineclear = plot.PolyMarker(plotavg, colour="black") plotlist.append(lineclear) # Update Text control self.WXavg.SetValue(str(avg)) self.WXsd.SetValue(str(np.std(np.array(plotcurve)[:, 1]))) # Draw # This causes a memory leak after this function has been # called several times with the same large data set. # This goes away if only linesig OR lineclear are plotted. # # graphics = plot.PlotGraphics(plotlist, # xLabel='page number', # yLabel=label) graphics = plot.PlotGraphics([linesig], xLabel='page number', yLabel=label) # Correctly set x-axis minticks = 2 self.canvas.SetXSpec(max(maxpage-minpage, minticks)) self.canvas.Draw(graphics, xAxis=(minpage, maxpage)) def OnPageChanged(self, page, trigger=None): """ This function is called, when something in the panel changes. The variable `trigger` is used to prevent this function from being executed to save stall time of the user. Forr a list of possible triggers, see the doc string of `tools`. """ # When parent changes # This is a necessary function for PyCorrFit. # This is stuff that should be done when the active page # of the notebook changes. # filter unwanted triggers to improve speed if trigger in ["parm_batch", "fit_batch", "page_add_batch"]: return elif trigger in ["tab_init"] and page is not None: # Check if we have to replot for a new model if self.Page.corr.fit_model.id == page.corr.fit_model.id: return if (trigger in ["page_add_finalize"] and self.WXTextPages.GetValue() == "1"): # We probably imported data with statistics window open self.PageNumbers = range(1, 1+self.parent.notebook.GetPageCount()) setstring = misc.parsePagenum2String(self.PageNumbers) self.WXTextPages.SetValue(setstring) # # Prevent this function to be run twice at once: # oldsize = self.GetSizeTuple() if self.WXTextPages.GetValue() == "": # Set number of pages pagenumlist = list() for i in np.arange(self.parent.notebook.GetPageCount()): Page = self.parent.notebook.GetPage(i) pagenumlist.append(int("".join(filter(lambda x: x.isdigit(), Page.counter)))) valstring = misc.parsePagenum2String(pagenumlist) self.WXTextPages.SetValue(valstring) DDselection = self.WXDropdown.GetValue() self.Page = page self.InfoClass = InfoClass(CurPage=self.Page) self.PlotParms = self.GetListOfPlottableParms() # Make sure the selection stays the same DDselid = 0 for i in range(len(self.PlotParms)): if DDselection == self.PlotParms[i]: DDselid = i Parmlist = self.PlotParms self.WXDropdown.SetItems(Parmlist) self.WXDropdown.SetSelection(DDselid) self.panel.Enable() for i in np.arange(len(self.Checkboxes)): self.Checkboxes[i].Destroy() del self.Checkboxes # self.Checklabels[i].Destroy() # those cannot be destroyed. for i in np.arange(len(self.boxsizerlist)): self.boxsizer.Remove(0) self.boxsizer.Layout() self.boxsizerlist = list() self.Checkboxes = list() self.Checklabels = list() # Disable if there are no pages left if self.parent.notebook.GetPageCount() == 0: self.panel.Disable() self.canvas.Clear() return self.OnChooseValues() self.boxsizer.Layout() self.topSizer.Fit(self) (ax, ay) = self.GetSizeTuple() (px, py) = self.topSizer.GetMinSize() self.sp.SetSashPosition(px+5) self.SetMinSize((px+400, py)) self.SetSize((np.max([px+400, ax, oldsize[0]]), np.max([py, ay, oldsize[1]]))) # Replot self.OnDropDown() def OnSaveTable(self, event=None): dirname = self.parent.dirname dlg = wx.FileDialog(self.parent, "Choose file to save", dirname, "", "Text file (*.txt)|*.txt;*.TXT", wx.FD_SAVE | wx.FD_OVERWRITE_PROMPT) # user cannot do anything until he clicks "OK" if dlg.ShowModal() == wx.ID_OK: filename = dlg.GetPath() if filename.lower().endswith(".txt") is not True: filename = filename+".txt" dirname = dlg.GetDirectory() openedfile = codecs.open(filename, 'w', encoding="utf-8") # Get Parameterlist of all Pages with same model id as # Self.Page # This creates self.SaveInfo: self.GetWantedParameters() # Write header linestring = u"" for atuple in self.SaveInfo[0]: linestring += u"{}\t".format(atuple[0]) # remove trailing "\t" openedfile.write(u"{}\r\n".format(linestring.strip())) # Write data for item in self.SaveInfo: linestring = u"" for btuple in item: linestring += u"{}\t".format(btuple[1]) openedfile.write(linestring.strip()+u"\r\n") openedfile.close() # Close the stat: self.OnClose() else: dirname = dlg.GetDirectory() dlg.Destroy() # Give parent the current dirname self.parent.dirname = dirname def SetPageNumbers(self, pagestring): self.WXTextPages.SetValue(pagestring) @staticmethod def SortParameters(parms): u""" Sort a list of tuples according to the first item. The sorting convention was met in issue #113: - at the beginning: avg. countrates and particle numbers - fast components go before slow components: e.g. [τ_trip, τ_diff] - model parameters are sorted logically according to their origin: e.g. [T1, τ_trip1], or [F1, τ_diff1], or [T2, τ_trip2] - if the parameter ends with a number, then we sort it to the logical blocks - includes n1, n2, etc. - at end: other fitting parameters and mode information fitting parameters intensities n tautrip1 T1 tautrip2 T2 tau1 F1 tau2 F2 tau3 F3 alpha SP non-fitting parameter model name chisquare weighted fit interval measurement time ... model id """ startswith_sort = [ u"avg. signal", u"n", u"T", u"τ_trip", u"F", u"C", u"D", u"τ", u"τ_diff", u"alpha", u"SP", ] otherparms = list() # append to this list all parameters that might be in another model. for m in mdls.models: for p in mdls.GetHumanReadableParms(m.id, m.parameters[1])[0]: exists = False for sw in startswith_sort+otherparms: if p.startswith(sw): exists = True if not exists: otherparms.append(p) # sort the other parameters by name otherparms.sort() # special offsets to distinguish "T" and "Type": special_off_start = ["Type", "Fit"] def rate_tuple(item): x = item[0] return rate(x) def rate(x): """ rate a parameter for sorting. lower values are at the beginning of the list. """ x = x.split("[")[0] # start at the top r = 0 # BLOCK OFFSET try: intx = int(x[-1]) except: pass else: # penalty: belongs to block r += 3 + intx # STARTSWITH PENALTY for p in startswith_sort: if x.startswith(p): r += 1 + 3*(startswith_sort.index(p)) break # Block offset integer non_decimal = re.compile(r'[^\d]+') pnum = non_decimal.sub("", x) if len(pnum) > 0: r += int(pnum) if x.count("3D"): r -= 3 if x.count("2D"): r -= 0 # Other Parameters for p in otherparms: if p.startswith(x): r += (otherparms.index(p)) + 3*len(startswith_sort) # Special offsets for p in special_off_start: if x.startswith(p): r += 300 if r == 0: r = 10000 return r def compare(x, y): """ rates x and y. returns -1, 0, 1 required for common list sort """ rx = rate_tuple(x) ry = rate_tuple(y) return rx-ry return sorted(parms, key=rate_tuple) pycorrfit-1.1.7/pycorrfit/gui/tools/datarange.py0000664000372000037200000002207413554642611022664 0ustar travistravis00000000000000"""Module tools - datarange: Let the user choose the disaplyed time range""" import numpy as np import wx # Menu entry name MENUINFO = ["&Data range", "Select an interval of lag times to be used for fitting."] class SelectChannels(wx.Frame): def __init__(self, parent): # parent is main frame self.parent = parent # Get the window positioning correctly pos = self.parent.GetPosition() pos = (pos[0]+100, pos[1]+100) wx.Frame.__init__(self, parent=self.parent, title="Data range selection", pos=pos, style=wx.DEFAULT_FRAME_STYLE | wx.FRAME_FLOAT_ON_PARENT) # MYID # This ID is given by the parent for an instance of this class self.MyID = None # Start drawing panel = wx.Panel(self) self.panel = panel # Page self.Page = self.parent.notebook.GetCurrentPage() self.Calc_init(self.Page) text1 = wx.StaticText(panel, label=u"The lag times τ are stored as an " + u"array of length ") self.textend = wx.StaticText(panel, label="%d." % self.lentau) text2 = wx.StaticText(panel, label=u"You may wish to confine this array. " + u"This can be done here.") # Spincontrols: FlexSpinSizer = wx.FlexGridSizer(rows=2, cols=4, vgap=5, hgap=5) FlexSpinSizer.Add(wx.StaticText(panel, label="Channels:")) self.spinstart = wx.SpinCtrl(panel, -1, initial=self.left, min=self.start0, max=self.end0-1) FlexSpinSizer.Add(self.spinstart) FlexSpinSizer.Add(wx.StaticText(panel, label=" - ")) self.spinend = wx.SpinCtrl(panel, -1, initial=self.right, min=self.start0+1, max=self.end0) FlexSpinSizer.Add(self.spinend) FlexSpinSizer.Add(wx.StaticText(panel, label="Times [ms]:")) self.TextTimesStart = wx.StaticText(panel, label="None") FlexSpinSizer.Add(self.TextTimesStart) FlexSpinSizer.Add(wx.StaticText(panel, label=" - ")) self.TextTimesEnd = wx.StaticText(panel, label="None") FlexSpinSizer.Add(self.TextTimesEnd) # Buttons btnapply = wx.Button(panel, wx.ID_ANY, 'Apply') btnapplyall = wx.Button(panel, wx.ID_ANY, 'Apply to all pages') self.ButtonApply = btnapply self.ButtonApplyAll = btnapplyall self.Bind(wx.EVT_BUTTON, self.OnApply, btnapply) self.Bind(wx.EVT_BUTTON, self.OnApplyAll, btnapplyall) self.Bind(wx.EVT_SPINCTRL, self.OnChangeChannels, self.spinend) self.Bind(wx.EVT_SPINCTRL, self.OnChangeChannels, self.spinstart) # Checkbox self.fixcheck = wx.CheckBox(panel, -1, label="Fix current channel selection for all pages.") self.Bind(wx.EVT_CHECKBOX, self.OnCheckbox, self.fixcheck) # Text channelsel = "Leave this window open for a fixed selection." text3 = wx.StaticText(panel, label=channelsel) # Sizer topSizer = wx.BoxSizer(wx.VERTICAL) buttonsizer = wx.BoxSizer(wx.HORIZONTAL) buttonsizer.Add(btnapply, 1) buttonsizer.Add(btnapplyall, 1) text1sizer = wx.BoxSizer(wx.HORIZONTAL) text1sizer.Add(text1) text1sizer.Add(self.textend) topSizer.Add(text1sizer) topSizer.Add(text2) topSizer.AddSpacer(5) topSizer.Add(FlexSpinSizer) topSizer.Add(self.fixcheck) topSizer.Add(text3) topSizer.AddSpacer(5) topSizer.Add(buttonsizer) panel.SetSizer(topSizer) topSizer.Fit(self) self.SetMinSize(topSizer.GetMinSize()) # Get times. self.OnChangeChannels() # Icon if parent.MainIcon is not None: wx.Frame.SetIcon(self, parent.MainIcon) # Show window self.Show(True) self.OnPageChanged(self.Page) def Calc_init(self, parent): # Variables # Parent should be the fitting panel - # The tab, where the fitting is done. self.Page = parent if self.Page == None: # dummy info taufull = np.arange(100) self.left = self.right = None self.panel.Disable() else: self.left = self.Page.corr.fit_ival[0] # starting position self.right = self.Page.corr.fit_ival[1] # ending position taufull = self.Page.corr.lag_time self.lentau = len(taufull) self.start0 = 0 # left border of interval # The interval starts at 0! self.end0 = self.lentau - 1 # right border of interval if self.left is None or self.left > self.end0: # This means, that either left = right = None # or the correlation-array is too small self.left = self.start0 if self.right is None: # set the maximum possible value self.right = self.end0 else: self.right -= 1 def OnApply(self, event=None): self.SetValues() self.Page.PlotAll() def OnApplyAll(self, event=None): N = self.parent.notebook.GetPageCount() for i in np.arange(N): # Set Page Page = self.parent.notebook.GetPage(i) # Find out maximal length self.SetValues(page=Page) Page.PlotAll() # Page.PlorAll() calls this function. This results in the wrong data # being displayed in an open "View Info" Window. We call it again. self.parent.OnFNBPageChanged() if self.parent.MenuAutocloseTools.IsChecked(): # Autoclose self.OnClose() def OnChangeTimes(self, e=None): """ Called, whenever data range in seconds is changed. This updates the data range in channels in the window. This function might be used in later versions of PyCorrFit. """ pass def OnChangeChannels(self, e=None): """ Called, whenever data range in channels is changed. This updates the data range in seconds in the window. """ if self.Page == None: return N = len(self.Page.corr.lag_time) start = self.spinstart.Value end = self.spinend.Value # If the initial boundaries are outside of the experimental # data array of length N, change the start and end variables. start = start*(start < N-2) end = min(end, N-1) t1 = 1.*self.Page.corr.lag_time[start] t2 = 1.*self.Page.corr.lag_time[end] self.TextTimesStart.SetLabel("%.4e" % t1) self.TextTimesEnd.SetLabel("%.4e" % t2) self.OnCheckbox() def OnCheckbox(self, event=None): """ Set the correct value in the spincontrol, if the checkbox is not checked. """ state = self.fixcheck.GetValue() if state == True: self.OnApplyAll() self.ButtonApply.Disable() self.ButtonApplyAll.Disable() else: self.ButtonApply.Enable() self.ButtonApplyAll.Enable() # self.OnPageChanged(self.Page) def OnClose(self, event=None): self.parent.toolmenu.Check(self.MyID, False) self.parent.ToolsOpen.__delitem__(self.MyID) self.Destroy() def OnPageChanged(self, page, trigger=None): # We do not need the *Range* Commands here yet. # We open and close the SelectChannelsFrame every time we # import some data. # if trigger in ["parm_batch", "fit_batch", "page_add_batch"]: return # Check if we have a fixed channel selection if self.parent.notebook.GetPageCount() == 0: self.panel.Disable() else: self.panel.Enable() # There is a page. We may continue. state = self.fixcheck.GetValue() if state: # We do not need to run Calc_init self.SetValues(page=page) page.PlotAll(event="init") else: # We will run it self.Calc_init(page) self.spinstart.SetRange(self.start0, self.end0-1) self.spinstart.SetValue(self.left) self.spinend.SetRange(self.start0+1, self.end0) self.spinend.SetValue(self.right) self.textend.SetLabel("%d." % self.lentau) self.OnChangeChannels() def SetValues(self, page=None): if page is None: page = self.Page # Get interval start = self.spinstart.GetValue() end = self.spinend.GetValue() + 1 # +1, [sic] if start > end: # swap the variables, we are not angry at the user start, end = end, start # Find out maximal length maxlen = len(page.corr.lag_time) # Use the smaller one of both, so we do not get an # index out of bounds error page.corr.fit_ival = [start*(start < maxlen - 1), min(end, maxlen) ] pycorrfit-1.1.7/pycorrfit/gui/tools/simulation.py0000664000372000037200000004226513554642611023126 0ustar travistravis00000000000000"""Module tools - simulation Enables the user to change plotting parameters and replotting fast. Might be useful for better understanding model functions. """ import numpy as np import wx from wx.lib.agw import floatspin from pycorrfit import models as mdls # Menu entry name MENUINFO = ["S&lider simulation", "Fast plotting for different parameters."] class Slide(wx.Frame): # This tool is derived from a wx.frame. def __init__(self, parent): # parent is the main frame of PyCorrFit self.parent = parent # Get the window positioning correctly pos = self.parent.GetPosition() pos = (pos[0]+100, pos[1]+100) wx.Frame.__init__(self, parent=self.parent, title="Simulation", pos=pos, style=wx.DEFAULT_FRAME_STYLE | wx.FRAME_FLOAT_ON_PARENT) # Starting positions/factors for spinctrls and sliders self.slidemax = 1000 self.slidestart = 500 self.spinstartfactor = 0.1 self.spinendfactor = 1.9 # MYID # This ID is given by the parent for an instance of this class self.MyID = None # Page - the currently active page of the notebook. self.Page = self.parent.notebook.GetCurrentPage() # Content self.panel = wx.Panel(self) self.rbtnB = wx.RadioButton(self.panel, -1, 'Vary A and B', style=wx.RB_GROUP) self.rbtnOp = wx.RadioButton(self.panel, -1, 'Fix relation') self.btnreset = wx.Button(self.panel, wx.ID_ANY, 'Reset') # Set starting variables self.SetStart() # Populate panel dropsizer = wx.FlexGridSizer(rows=2, cols=3, vgap=5, hgap=5) dropsizer.Add(wx.StaticText(self.panel, label="Parameter A")) dropsizer.Add(wx.StaticText(self.panel, label="Operator")) dropsizer.Add(wx.StaticText(self.panel, label="Parameter B")) self.droppA = wx.ComboBox(self.panel, -1, self.labelA, (15, 20), wx.DefaultSize, self.parmAlist, wx.CB_DROPDOWN | wx.CB_READONLY) self.droppA.SetSelection(0) self.Bind(wx.EVT_COMBOBOX, self.Ondrop, self.droppA) self.dropop = wx.ComboBox(self.panel, -1, "", (10, 20), wx.DefaultSize, self.oplist, wx.CB_DROPDOWN | wx.CB_READONLY) self.dropop.SetSelection(0) self.opfunc = self.opdict[list(self.opdict.keys())[0]] self.Bind(wx.EVT_COMBOBOX, self.Ondrop, self.dropop) self.droppB = wx.ComboBox(self.panel, -1, self.labelB, (15, 30), wx.DefaultSize, self.parmBlist, wx.CB_DROPDOWN | wx.CB_READONLY) self.Bind(wx.EVT_COMBOBOX, self.Ondrop, self.droppB) self.droppB.SetSelection(1) dropsizer.Add(self.droppA) dropsizer.Add(self.dropop) dropsizer.Add(self.droppB) textfix = wx.StaticText(self.panel, label="\nEdit intervals and drag the slider.\n") # Parameter A slidesizer = wx.FlexGridSizer(rows=3, cols=5, vgap=5, hgap=5) self.textstartA = wx.StaticText(self.panel, label=self.labelA) slidesizer.Add(self.textstartA) self.startspinA = floatspin.FloatSpin(self.panel, digits=7) slidesizer.Add(self.startspinA) self.sliderA = wx.Slider(self.panel, -1, self.slidestart, 0, self.slidemax, wx.DefaultPosition, (250, -1), wx.SL_HORIZONTAL) slidesizer.Add(self.sliderA) self.endspinA = floatspin.FloatSpin(self.panel, digits=7) slidesizer.Add(self.endspinA) self.textvalueA = wx.StaticText(self.panel, label="%.5e" % self.valueA) slidesizer.Add(self.textvalueA) # Parameter B self.textstartB = wx.StaticText(self.panel, label=self.labelB) slidesizer.Add(self.textstartB) self.startspinB = floatspin.FloatSpin(self.panel, digits=7) slidesizer.Add(self.startspinB) self.sliderB = wx.Slider(self.panel, -1, self.slidestart, 0, self.slidemax, wx.DefaultPosition, (250, -1), wx.SL_HORIZONTAL) slidesizer.Add(self.sliderB) self.endspinB = floatspin.FloatSpin(self.panel, digits=7) slidesizer.Add(self.endspinB) self.textvalueB = wx.StaticText(self.panel, label="%.5e" % self.valueB) slidesizer.Add(self.textvalueB) # Result of operation self.textstartOp = wx.StaticText(self.panel, label=self.labelOp) slidesizer.Add(self.textstartOp) self.startspinOp = floatspin.FloatSpin(self.panel, digits=7) slidesizer.Add(self.startspinOp) self.sliderOp = wx.Slider(self.panel, -1, self.slidestart, 0, self.slidemax, wx.DefaultPosition, (250, -1), wx.SL_HORIZONTAL) slidesizer.Add(self.sliderOp) self.endspinOp = floatspin.FloatSpin(self.panel, digits=7) slidesizer.Add(self.endspinOp) self.textvalueOp = wx.StaticText(self.panel, label="%.5e" % self.valueOp) slidesizer.Add(self.textvalueOp) # Bindings for slider self.Bind(wx.EVT_SLIDER, self.OnSlider, self.sliderA) self.Bind(wx.EVT_SLIDER, self.OnSlider, self.sliderB) self.Bind(wx.EVT_SLIDER, self.OnSlider, self.sliderOp) # Bindings for radiobuttons self.Bind(wx.EVT_RADIOBUTTON, self.OnRadio, self.rbtnB) self.Bind(wx.EVT_RADIOBUTTON, self.OnRadio, self.rbtnOp) self.Bind(wx.EVT_BUTTON, self.OnReset, self.btnreset) # Bindings for spin controls # Our self-made spin controls alread have wx_EVT_SPINCTRL bound to # the increment function. We will call that function manually here. self.startspinA.Unbind(wx.EVT_SPINCTRL) self.startspinB.Unbind(wx.EVT_SPINCTRL) self.startspinOp.Unbind(wx.EVT_SPINCTRL) self.endspinA.Unbind(wx.EVT_SPINCTRL) self.endspinB.Unbind(wx.EVT_SPINCTRL) self.endspinOp.Unbind(wx.EVT_SPINCTRL) self.Bind(wx.EVT_SPINCTRL, self.OnSlider, self.startspinA) self.Bind(wx.EVT_SPINCTRL, self.OnSlider, self.startspinB) self.Bind(wx.EVT_SPINCTRL, self.OnSlider, self.startspinOp) self.Bind(wx.EVT_SPINCTRL, self.OnSlider, self.endspinA) self.Bind(wx.EVT_SPINCTRL, self.OnSlider, self.endspinB) self.Bind(wx.EVT_SPINCTRL, self.OnSlider, self.endspinOp) # Set values self.SetValues() # Sizers self.topSizer = wx.BoxSizer(wx.VERTICAL) self.topSizer.Add(dropsizer) self.topSizer.Add(self.rbtnB) self.topSizer.Add(self.rbtnOp) self.topSizer.Add(self.btnreset) self.topSizer.Add(textfix) self.topSizer.Add(slidesizer) self.panel.SetSizer(self.topSizer) self.topSizer.Fit(self) self.OnRadio() self.OnPageChanged(self.Page, init=True) # Icon if parent.MainIcon is not None: wx.Frame.SetIcon(self, parent.MainIcon) self.Show(True) def CalcFct(self, A, B, C): if self.rbtnB.Value == True: func = self.opfunc[0] try: C = func(A, B) except ZeroDivisionError: pass else: return B, C else: func = self.opfunc[1] try: B = func(A, C) except ZeroDivisionError: pass else: return B, C def FillOpDict(self): # Dictionaries: [Calculate C, Calculate B) self.opdict["A/B"] = [lambda A, B: A/B, lambda A, C: A/C] self.opdict["B/A"] = [lambda A, B: B/A, lambda A, C: C*A] self.opdict["A*B"] = [lambda A, B: A*B, lambda A, C: C/A] self.opdict["A+B"] = [lambda A, B: A+B, lambda A, C: C-A] self.opdict["A-B"] = [lambda A, B: A-B, lambda A, C: A-C] self.opdict["A*exp(B)"] = [lambda A, B: A*np.exp(B), lambda A, C: np.log(C/A)] self.opdict["B*exp(A)"] = [lambda A, B: B*np.exp(A), lambda A, C: C/np.exp(A)] def OnClose(self, event=None): # This is a necessary function for PyCorrFit. # Do not change it. self.parent.toolmenu.Check(self.MyID, False) self.parent.ToolsOpen.__delitem__(self.MyID) self.Destroy() def Ondrop(self, event=None): self.labelOp = self.oplist[self.dropop.GetSelection()] self.labelA = self.parmAlist[self.droppA.GetSelection()] self.labelB = self.parmBlist[self.droppB.GetSelection()] self.textstartOp.SetLabel(self.labelOp) self.textstartA.SetLabel(label=self.labelA) self.textstartB.SetLabel(self.labelB) self.sliderB.SetValue(self.slidestart) self.sliderOp.SetValue(self.slidestart) self.sliderA.SetValue(self.slidestart) self.SetValues() self.OnSize() def OnPageChanged(self, page=None, trigger=None, init=False): """ This function is called, when something in the panel changes. The variable `trigger` is used to prevent this function from being executed to save stall time of the user. Forr a list of possible triggers, see the doc string of `tools`. 'init' is used by this tool only. """ # if init: # # Get the parameters of the current page. # self.SavedParms = self.parent.PackParameters(self.Page) # When parent changes # This is a necessary function for PyCorrFit. # This is stuff that should be done when the active page # of the notebook changes. if trigger in ["parm_batch", "fit_batch", "page_add_batch"]: return if self.parent.notebook.GetPageCount() == 0: self.panel.Disable() return try: # wx._core.PyDeadObjectError: The C++ part of the FittingPanel # object has been deleted, attribute access no longer allowed. oldcounter = self.Page.counter except: oldcounter = -1 if page is not None: if page.counter != oldcounter: self.Page = page self.SetStart() self.droppA.SetItems(self.parmAlist) self.droppB.SetItems(self.parmBlist) self.droppA.SetSelection(0) self.droppB.SetSelection(1) self.dropop.SetSelection(0) # Set labels self.Ondrop() else: self.Page = page self.panel.Enable() def OnRadio(self, event=None): if self.rbtnB.Value == True: # Parameter B is vaiable self.sliderOp.Enable(False) self.startspinOp.Enable(False) self.endspinOp.Enable(False) self.sliderB.Enable(True) self.startspinB.Enable(True) self.endspinB.Enable(True) else: # Operation result is vaiable self.sliderOp.Enable(True) self.startspinOp.Enable(True) self.endspinOp.Enable(True) self.sliderB.Enable(False) self.startspinB.Enable(False) self.endspinB.Enable(False) self.Ondrop() def OnReset(self, e=None): self.parent.UnpackParameters(self.SavedParms, self.Page) self.Page.apply_parameters_reverse() # self.OnPageChanged(self.Page) self.SetStart() self.Ondrop() def OnSize(self, event=None): # We need this funciton, because contents of the flexgridsizer # may change in size. self.panel.SetSizer(self.topSizer) self.topSizer.Fit(self) self.panel.SetSize(self.GetSize()) def OnSlider(self, event=None): # Set the slider vlaues idmax = self.sliderA.GetMax() slideA = self.sliderA.GetValue() startA = self.startspinA.GetValue() endA = self.endspinA.GetValue() self.valueA = startA + (endA-startA)*slideA/idmax self.textvalueA.SetLabel("%.5e" % self.valueA) if self.rbtnB.Value == True: slideB = self.sliderB.GetValue() startB = self.startspinB.GetValue() endB = self.endspinB.GetValue() self.valueB = startB + (endB-startB)*slideB/idmax else: # Same thing slideOp = self.sliderOp.GetValue() startOp = self.startspinOp.GetValue() endOp = self.endspinOp.GetValue() self.valueOp = startOp + (endOp-startOp)*slideOp/idmax self.valueB, self.valueOp = self.CalcFct(self.valueA, self.valueB, self.valueOp) self.textvalueB.SetLabel("%.5e" % self.valueB) self.textvalueOp.SetLabel("%.5e" % self.valueOp) self.SetResult() self.OnSize() def SetResult(self, event=None): if self.parent.notebook.GetPageCount() == 0: # Nothing to do return # And Plot idA = self.droppA.GetSelection() idB = self.droppB.GetSelection() # As of version 0.7.5: we want the units to be displayed # human readable - the way they are displayed # in the Page info tool. # Convert from human readable to internal units # The easiest way is to make a copy of all parameters and # only write back those that have been changed: # parms_0 = 1.*np.array(mdls.valuedict[self.modelid][1]) parms_0[idA] = self.valueA # human readable units parms_0[idB] = self.valueB # human readable units parms_i =\ mdls.GetInternalFromHumanReadableParm(self.modelid, parms_0)[1] self.Page.active_parms[1][idA] = parms_i[idA] self.Page.active_parms[1][idB] = parms_i[idB] self.Page.apply_parameters_reverse() self.Page.PlotAll() def SetStart(self): # Sets first and second variable of a page to # Parameters A and B respectively. if self.parent.notebook.GetPageCount() == 0: self.modelid = 6000 ParmLabels, ParmValues = \ mdls.GetHumanReadableParms(self.modelid, mdls.valuedict[6000][1]) else: self.SavedParms = self.parent.PackParameters(self.Page) self.modelid = self.Page.modelid ParmLabels, ParmValues = \ mdls.GetHumanReadableParms(self.modelid, self.Page.active_parms[1]) self.parmAlist = ParmLabels self.parmBlist = ParmLabels # Operators # Calculation of variable A with fixed B self.opdict = dict() self.FillOpDict() self.oplist = list(self.opdict.keys()) self.oplist.sort() self.labelA = self.parmAlist[0] self.labelB = self.parmBlist[1] self.labelOp = self.oplist[0] self.opfunc = self.opdict[self.labelOp] self.valueA = ParmValues[0] self.valueB = ParmValues[1] self.valueB, self.valueOp = self.CalcFct(self.valueA, self.valueB, 0) def SetValues(self, event=None): # Set the values for spin and slider # As of version 0.7.5: we want the units to be displayed # human readable - the way they are displayed # in the Page info tool. # # Parameter A idA = self.droppA.GetSelection() # Parameter B idB = self.droppB.GetSelection() # self.valueB = self.Page.active_parms[1][idB] # self.valueA = self.Page.active_parms[1][idA] if self.parent.notebook.GetPageCount() == 0: self.modelid = 6000 ParmValues = \ mdls.GetHumanReadableParms(self.modelid, mdls.valuedict[6000][1])[1] else: self.modelid = self.Page.modelid ParmValues = \ mdls.GetHumanReadableParms(self.modelid, self.Page.active_parms[1])[1] self.valueA = ParmValues[idA] self.valueB = ParmValues[idB] # Operator idop = self.dropop.GetSelection() #keys = self.opdict.keys() opkey = self.oplist[idop] self.opfunc = self.opdict[opkey] # Parameter A startA = self.valueA*self.spinstartfactor endA = self.valueA*self.spinendfactor self.startspinA.SetValue(startA) self.endspinA.SetValue(endA) # Parameter B startB = self.valueB*self.spinstartfactor endB = self.valueB*self.spinendfactor self.startspinB.SetValue(startB) self.endspinB.SetValue(endB) # Operation result self.valueOp = self.opfunc[0](self.valueA, self.valueB) startOp = self.valueOp*self.spinstartfactor endOp = self.valueOp*self.spinendfactor self.startspinOp.SetValue(startOp) self.endspinOp.SetValue(endOp) # Set text self.textvalueA.SetLabel("%.5e" % self.valueA) self.textvalueB.SetLabel("%.5e" % self.valueB) self.textvalueOp.SetLabel("%.5e" % self.valueOp) self.SetResult() pycorrfit-1.1.7/pycorrfit/gui/tools/example.py0000664000372000037200000000474513554642611022376 0ustar travistravis00000000000000"""Module tools - example This is an example tool. You will need to edit __init__.py inside this folder to activate it. Add the filename (*example*) and class (*Tool*) to either of the lists *ImpA* or *ImpB* in __init__.py. """ import wx class Tool(wx.Frame): # This tool is derived from a wx.frame. def __init__(self, parent): # parent is the main frame of PyCorrFit self.parent = parent # Get the window positioning correctly pos = self.parent.GetPosition() pos = (pos[0]+100, pos[1]+100) wx.Frame.__init__(self, parent=self.parent, title="Example tool", pos=pos, style=wx.DEFAULT_FRAME_STYLE | wx.FRAME_FLOAT_ON_PARENT) # MYID # This ID is given by the parent for an instance of this class self.MyID = None # Page - the currently active page of the notebook. self.Page = self.parent.notebook.GetCurrentPage() # Content self.panel = wx.Panel(self) btncopy = wx.Button(self.panel, wx.ID_ANY, 'Example button') # Binds the button to the function - close the tool self.Bind(wx.EVT_BUTTON, self.OnClose, btncopy) self.topSizer = wx.BoxSizer(wx.VERTICAL) self.topSizer.Add(btncopy) self.panel.SetSizer(self.topSizer) self.topSizer.Fit(self) self.SetMinSize(self.topSizer.GetMinSize()) # Icon if parent.MainIcon is not None: wx.Frame.SetIcon(self, parent.MainIcon) self.Show(True) def OnClose(self, event=None): # This is a necessary function for PyCorrFit. # Do not change it. self.parent.toolmenu.Check(self.MyID, False) self.parent.ToolsOpen.__delitem__(self.MyID) self.Destroy() def OnPageChanged(self, page, trigger=None): """ This function is called, when something in the panel changes. The variable `trigger` is used to prevent this function from being executed to save stall time of the user. Forr a list of possible triggers, see the doc string of `tools`. """ # When parent changes # This is a necessary function for PyCorrFit. # This is stuff that should be done when the active page # of the notebook changes. if self.parent.notebook.GetPageCount() == 0: # Do something when there are no pages left. self.panel.Disable() return self.panel.Enable() self.Page = page pycorrfit-1.1.7/pycorrfit/gui/tools/background.py0000664000372000037200000006120513554642611023054 0ustar travistravis00000000000000"""Module tools - background: perform background correction""" import os import sys import traceback import numpy as np import wx from wx.lib.agw import floatspin import wx.lib.plot as plot from pycorrfit import readfiles from pycorrfit import Trace from .. import misc # Menu entry name MENUINFO = ["&Background correction", "Open a file for background correction."] class BackgroundCorrection(wx.Frame): def __init__(self, parent): self.MyName = "BACKGROUND" # Parent is main frame self.parent = parent # Get the window positioning correctly pos = self.parent.GetPosition() pos = (pos[0]+100, pos[1]+100) wx.Frame.__init__(self, parent=parent, title="Background correction", pos=pos, style=wx.DEFAULT_FRAME_STYLE | wx.FRAME_FLOAT_ON_PARENT) # MYID # This ID is given by the parent for an instance of this class self.MyID = None # Current trace we are looking at self.activetrace = None # Importet trace self.trace = None # Importet trace after user decides to cange radio buttons self.oldtrace = None self.oldfilename = None self.average = None # Start drawing # Splitter Window self.sp = wx.SplitterWindow(self, style=wx.SP_NOBORDER) # Controls panel = wx.Panel(self.sp) # text1 backgroundinit = ( "Correct the amplitude for non-correlated background.\n" + "The background intensity can be either imported\n" + "from a blank measurement or set manually.") textinit = wx.StaticText(panel, label=backgroundinit) # Radio buttons self.rbtnfile = wx.RadioButton(panel, -1, 'Blank measurement: ', style=wx.RB_GROUP) self.rbtnfile.SetValue(True) self.btnbrowse = wx.Button(panel, wx.ID_ANY, 'Browse ...') self.rbtnhand = wx.RadioButton(panel, -1, 'Manual, [kHz]: ') # Spincontrol self.spinctrl = floatspin.FloatSpin(panel, digits=4, min_val=0, increment=.01) self.spinctrl.Enable(False) # Verbose text self.textfile = wx.StaticText(panel, label="No blank measurement file selected.") textmeanavg = wx.StaticText(panel, label="Average background signal [kHz]: ") self.textmean = wx.StaticText(panel, label="") # name textname = wx.StaticText(panel, label="User defined background name: ") sizeTextn = textname.GetSize()[0] self.bgname = wx.TextCtrl(panel, value="", size=(sizeTextn, -1)) self.bgname.Enable(False) self.btnimport = wx.Button(panel, wx.ID_ANY, 'Import into session') self.btnimport.Enable(False) # Dropdown self.BGlist = ["File/User"] # updated by self.UpdateDropdown() textdropdown = wx.StaticText(panel, label="Show background: ") self.dropdown = wx.ComboBox(panel, -1, "File/User", (15, -1), wx.DefaultSize, self.BGlist, wx.CB_DROPDOWN | wx.CB_READONLY) self.UpdateDropdown() # Radio buttons Channel1 and 2 self.rbtnCh1 = wx.RadioButton(panel, -1, 'Ch1 ', style=wx.RB_GROUP) self.rbtnCh1.SetValue(True) self.rbtnCh2 = wx.RadioButton(panel, -1, 'Ch2') # Apply buttons self.btnapply = wx.Button(panel, wx.ID_ANY, 'Apply') textor = wx.StaticText(panel, label=" or ") self.btnrem = wx.Button(panel, wx.ID_ANY, 'Dismiss') textpages = wx.StaticText(panel, label=" correction for pages: ") self.WXTextPages = wx.TextCtrl(panel, value="") # Initial value for WXTextPages pagenumlist = list() for i in np.arange(self.parent.notebook.GetPageCount()): Page = self.parent.notebook.GetPage(i) pagenumlist.append( int("".join(filter(lambda x: x.isdigit(), Page.counter)))) valstring = misc.parsePagenum2String(pagenumlist) self.WXTextPages.SetValue(valstring) textyma = wx.StaticText(panel, label="Shortcut - ") self.btnapplyall = wx.Button(panel, wx.ID_ANY, 'Apply to all pages') textor2 = wx.StaticText(panel, label=" or ") self.btnremyall = wx.Button(panel, wx.ID_ANY, 'Dismiss from all pages') # Bindings self.Bind(wx.EVT_BUTTON, self.OnBrowse, self.btnbrowse) self.Bind(wx.EVT_RADIOBUTTON, self.OnRadioFile, self.rbtnfile) self.Bind(wx.EVT_RADIOBUTTON, self.OnRadioHand, self.rbtnhand) self.Bind(wx.EVT_SPINCTRL, self.SpinCtrlChange, self.spinctrl) self.Bind(wx.EVT_BUTTON, self.OnImport, self.btnimport) self.Bind(wx.EVT_COMBOBOX, self.OnDraw, self.dropdown) self.Bind(wx.EVT_BUTTON, self.OnApply, self.btnapply) self.Bind(wx.EVT_BUTTON, self.OnApplyAll, self.btnapplyall) self.Bind(wx.EVT_BUTTON, self.OnRemove, self.btnrem) self.Bind(wx.EVT_BUTTON, self.OnRemoveAll, self.btnremyall) # Sizers topSizer = wx.BoxSizer(wx.VERTICAL) text1sizer = wx.BoxSizer(wx.HORIZONTAL) text1sizer.Add(self.rbtnfile) text1sizer.Add(self.btnbrowse) text2sizer = wx.BoxSizer(wx.HORIZONTAL) text2sizer.Add(self.rbtnhand) text2sizer.Add(self.spinctrl) textmeansizer = wx.BoxSizer(wx.HORIZONTAL) textmeansizer.Add(textmeanavg) textmeansizer.Add(self.textmean) dropsizer = wx.BoxSizer(wx.HORIZONTAL) dropsizer.Add(textdropdown) droprightsizer = wx.BoxSizer(wx.VERTICAL) dropsizer.Add(droprightsizer) droprightsizer.Add(self.dropdown) # droprightsizer.Add(self.textafterdropdown) applysizer = wx.BoxSizer(wx.HORIZONTAL) applysizer.Add(self.btnapply) applysizer.Add(textor) applysizer.Add(self.btnrem) applysizer.Add(textpages) applysizer.Add(self.WXTextPages) applysizer.Add(self.rbtnCh1) applysizer.Add(self.rbtnCh2) allsizer = wx.BoxSizer(wx.HORIZONTAL) allsizer.Add(textyma) allsizer.Add(self.btnapplyall) allsizer.Add(textor2) allsizer.Add(self.btnremyall) topSizer.Add(textinit) topSizer.Add(text1sizer) topSizer.Add(text2sizer) topSizer.Add(self.textfile) topSizer.Add(textmeansizer) topSizer.Add(textname) topSizer.Add(self.bgname) topSizer.Add(self.btnimport) topSizer.Add(dropsizer) topSizer.Add(applysizer) topSizer.Add(allsizer) panel.SetSizer(topSizer) topSizer.Fit(self) self.SetMinSize(topSizer.GetMinSize()) self.Show(True) # Canvas self.canvas = plot.PlotCanvas(self.sp) # Sizes psize = panel.GetBestSize() initial_size = (psize[0], psize[1]+200) self.SetSize(initial_size) sashsize = psize[1]+3 # This is also necessary to prevent unsplitting self.sp.SetMinimumPaneSize(sashsize) self.sp.SplitHorizontally(panel, self.canvas, sashsize) # If there is no page, disable ourselves: self.OnPageChanged(self.parent.notebook.GetCurrentPage()) # Icon if parent.MainIcon is not None: wx.Frame.SetIcon(self, parent.MainIcon) def Apply(self, Page, backgroundid): if self.rbtnCh1.GetValue() == True: Page.bgselected = backgroundid else: Page.bg2selected = backgroundid if Page.IsCrossCorrelation is False: # Autocorrelation only has one background! Page.bg2selected = None def OnApply(self, event): strFull = self.WXTextPages.GetValue() PageNumbers = misc.parseString2Pagenum(self, strFull) if PageNumbers is None: # Something went wrong and parseString2Pagenum already displayed # an error message. return # BG number item = self.dropdown.GetSelection() # Apply to corresponding pages for i in np.arange(self.parent.notebook.GetPageCount()): Page = self.parent.notebook.GetPage(i) j = "".join(filter(lambda x: x.isdigit(), Page.counter)) if int(j) in PageNumbers: self.Apply(Page, item) Page.OnAmplitudeCheck("init") Page.PlotAll() # Clean up unused backgrounds CleanupAutomaticBackground(self.parent) def OnApplyAll(self, event): self.btnrem.Enable(True) self.btnremyall.Enable(True) N = self.parent.notebook.GetPageCount() item = self.dropdown.GetSelection() for i in np.arange(N): # Set Page Page = self.parent.notebook.GetPage(i) try: self.Apply(Page, item) Page.OnAmplitudeCheck("init") Page.PlotAll() except OverflowError: errstr = "Could not apply background to Page "+Page.counter +\ ". \n Check the value of the trace average and the background." dlg = wx.MessageDialog(self, errstr, "Error", style=wx.ICON_ERROR | wx.OK | wx.STAY_ON_TOP) dlg.ShowModal() Page.bgselected = None Page.bg2selected = None # Clean up unused backgrounds CleanupAutomaticBackground(self.parent) def OnClose(self, event=None): self.parent.toolmenu.Check(self.MyID, False) self.parent.ToolsOpen.__delitem__(self.MyID) self.Destroy() def OnBrowse(self, event): # filetypes_bg_dict is a dictionary with filetypes that have some # trace signal information. SupFiletypes = list(readfiles.filetypes_bg_dict.keys()) SupFiletypes.sort() filters = "" for i in np.arange(len(SupFiletypes)): # Add to the filetype filter filters = filters+SupFiletypes[i] if i+1 != len(SupFiletypes): # Add a separator filters = filters+"|" dlg = wx.FileDialog(self, "Choose a data file", self.parent.dirname, "", filters, wx.FD_OPEN) if dlg.ShowModal() == wx.ID_OK: # Workaround since 0.7.5 (dirname, filename) = os.path.split(dlg.GetPath()) #filename = dlg.GetFilename() #dirname = dlg.GetDirectory() # Set parent dirname for user comfort self.parent.dirname = dirname try: # [data, trace, curvelist] stuff = readfiles.open_any_bg(dirname, filename) except: # The file does not seem to be what it seems to be. info = sys.exc_info() errstr = "Unknown file format:\n" errstr += str(filename)+"\n\n" errstr += str(info[0])+"\n" errstr += str(info[1])+"\n" for tb_item in traceback.format_tb(info[2]): errstr += tb_item wx.MessageDialog(self, errstr, "Error", style=wx.ICON_ERROR | wx.OK | wx.STAY_ON_TOP) return # Usually we will get a bunch of traces. Let the user select which # one to take. if len(stuff["Filename"]) > 1: choices = list() for i2 in np.arange(len(stuff["Filename"])): choices.append(str(i2)+". " + stuff["Filename"][i2] + " " + stuff["Type"][i2]) dlg = wx.SingleChoiceDialog(self, "Choose a curve", "Curve selection", choices=choices) if dlg.ShowModal() == wx.ID_OK: selindex = dlg.GetSelection() else: return else: selindex = 0 # If we accidentally recorded a cross correlation curve # as the background, let the user choose which trace he wants: channelindex = None if (len(stuff["Type"][selindex]) >= 2 and stuff["Type"][selindex][0:2] == "CC"): choices = ["Channel 1", "Channel 2"] label = "From which channel do you want to use the trace?" dlg = wx.SingleChoiceDialog(self, label, "Curve selection", choices=choices) if dlg.ShowModal() == wx.ID_OK: channelindex = dlg.GetSelection() trace = stuff["Trace"][selindex][channelindex] else: return else: trace = stuff["Trace"][selindex] if trace is None: print("WARNING: I did not find any trace data.") return # Display filename and some of the directory self.textfile.SetLabel("File: ..."+dirname[-10:]+"/"+filename) name = str(selindex)+". "+stuff["Filename"][selindex]+" " +\ stuff["Type"][selindex] if channelindex is not None: name += " "+str(channelindex+1) self.bgname.SetValue(name) self.trace = trace # Calculate average self.average = self.trace[:, 1].mean() # Display average self.textmean.SetLabel(str(self.average)+" kHz") self.spinctrl.SetValue(self.average) # Let the user see the opened file self.dropdown.SetSelection(0) # show trace self.OnDraw() # Enable button and editable name self.bgname.Enable(True) self.btnimport.Enable(True) else: # User pressed "Abort" - do nothing. self.parent.dirname = dlg.GetDirectory() dlg.Destroy() return def OnDraw(self, event=None): item = self.dropdown.GetSelection() if item < 0: # Disable Apply Buttons self.btnapply.Enable(False) self.btnapplyall.Enable(False) # Draw the trace that was just imported if self.trace and self.trace.all(): # It is enougth to check that you have an array # Calculate average self.average = self.trace[:, 1].mean() self.activetrace = self.trace # self.textafterdropdown.SetLabel(" Avg: "+str(self.average)+ # " kHz") self.textmean.SetLabel(str(self.average)) self.spinctrl.SetValue(self.average) else: # Clear the canvas. Looks better. self.canvas.Clear() # Don't show the average # self.textafterdropdown.SetLabel("") self.textmean.SetLabel("") return else: # Enable Apply Buttons self.btnapply.Enable(True) self.btnapplyall.Enable(True) # Draw a trace from the list self.activetrace = self.parent.Background[item-1].trace # We want to have the trace in [s] here. trace = 1.*self.activetrace trace[:, 0] = trace[:, 0]/1000 linesig = plot.PolyLine(trace, legend='', colour='blue', width=1) self.canvas.Draw(plot.PlotGraphics([linesig], xLabel='time [s]', yLabel='background signal [kHz]')) def OnImport(self, event): self.parent.Background.append( Trace(trace=self.trace, name=self.bgname.GetValue())) # Next two lines are taken care of by UpdateDropdown #name = "{} ({:.2f} kHz)".format(self.bgname.GetValue(), self.average) # self.BGlist.append(name) self.UpdateDropdown() self.btnremyall.Enable(True) self.btnrem.Enable(True) self.btnapplyall.Enable(True) self.btnapply.Enable(True) self.OnDraw() # Update BG dropdown of each page for i in np.arange(self.parent.notebook.GetPageCount()): self.parent.notebook.GetPage(i).OnAmplitudeCheck() def OnPageChanged(self, page=None, trigger=None): """ This function is called, when something in the panel changes. The variable `trigger` is used to prevent this function from being executed to save stall time of the user. Forr a list of possible triggers, see the doc string of `tools`. """ # We do not need the *Range* Commands here yet. # We open and close the SelectChannelsFrame every time we # import some data. if trigger in ["parm_batch", "fit_batch", "page_add_batch"]: return if len(self.parent.Background) == 0: self.BGlist = list() self.UpdateDropdown() self.dropdown.SetValue("File/User") if self.parent.notebook.GetPageCount() == 0: self.sp.Disable() return self.sp.Enable() if len(self.BGlist) <= 0: self.btnrem.Enable(False) self.btnremyall.Enable(False) self.btnapply.Enable(False) self.btnapplyall.Enable(False) else: self.btnrem.Enable(True) self.btnremyall.Enable(True) self.btnapply.Enable(True) self.btnapplyall.Enable(True) if (self.WXTextPages.GetValue() == "" and self.parent.notebook.GetPageCount() != 0): # Initial value for WXTextPages pagenumlist = list() for i in np.arange(self.parent.notebook.GetPageCount()): Page = self.parent.notebook.GetPage(i) pagenumlist.append( int("".join(filter(lambda x: x.isdigit(), Page.counter)))) valstring = misc.parsePagenum2String(pagenumlist) self.WXTextPages.SetValue(valstring) def OnRadioFile(self, event): # Do not let the user change the spinctrl # setting. self.spinctrl.Enable(False) self.btnbrowse.Enable(True) # Restor the old trace self.trace = self.oldtrace if self.oldfilename is not None: self.textfile.SetLabel(self.oldfilename) if self.trace is None: # Disable button and editable name self.bgname.Enable(False) self.btnimport.Enable(False) # Let us draw self.dropdown.SetSelection(0) self.OnDraw() def OnRadioHand(self, event): # Let user enter a signal. self.spinctrl.Enable(True) self.btnbrowse.Enable(False) # save the old trace. We might want to switch back to it. if self.trace is not None: self.oldtrace = 1.*self.trace self.oldfilename = self.textfile.GetLabel() self.SpinCtrlChange() # Do not show the filename self.textfile.SetLabel("No file selected.") # Enable button and editable name self.bgname.Enable(True) self.btnimport.Enable(True) if len(self.bgname.GetValue()) == 0: # Enter something as name self.bgname.SetValue("User") def OnRemove(self, event): strFull = self.WXTextPages.GetValue() PageNumbers = misc.parseString2Pagenum(self, strFull) if PageNumbers is None: # Something went wrong and parseString2Pagenum already displayed # an error message. return # BG number #item = self.dropdown.GetSelection() # Apply to corresponding pages for i in np.arange(self.parent.notebook.GetPageCount()): Page = self.parent.notebook.GetPage(i) j = "".join(filter(lambda x: x.isdigit(), Page.counter)) if int(j) in PageNumbers: if self.rbtnCh1.GetValue() == True: Page.bgselected = None else: Page.bg2selected = None Page.bgselected = None Page.OnAmplitudeCheck("init") Page.PlotAll() # Clean up unused backgrounds CleanupAutomaticBackground(self.parent) def OnRemoveAll(self, event): N = self.parent.notebook.GetPageCount() for i in np.arange(N): Page = self.parent.notebook.GetPage(i) Page.bgselected = None Page.bg2selected = None Page.OnAmplitudeCheck("init") Page.PlotAll() # Clean up unused backgrounds CleanupAutomaticBackground(self.parent) def SetPageNumbers(self, pagestring): self.WXTextPages.SetValue(pagestring) def SpinCtrlChange(self, event=None): # Let user see the continuous trace we will generate self.average = self.spinctrl.GetValue() self.trace = np.array([[0, self.average], [1, self.average]]) self.textmean.SetLabel(str(self.average)) self.OnDraw() def UpdateDropdown(self, e=None): self.BGlist = list() # self.BGlist.append("File/User") for item in self.parent.Background: self.BGlist.append(item.name) self.dropdown.SetItems(self.BGlist) # Show the last item self.dropdown.SetSelection(len(self.BGlist)-1) def ApplyAutomaticBackground(page, bg, parent): """ Creates an "automatic" background with countrate in kHz *bg* and applies it to the given *page* object. If an automatic background with the same countrate exists, uses it. Input: *page* - page to which the background should be applied *bg* - background that should be applied to that page float or list of 1 or two elements -> if the page is cross-correlation, the second background will be applied as well. *parent* - parent containing *Background* list """ bglist = 1*np.atleast_1d(bg) # minus 1 to identify non-set background id bgid = np.zeros(bglist.shape, dtype=int) - 1 for b in range(len(bglist)): bgname = "AUTO: {:e} kHz \t".format(bglist[b]) # Check if exists: for i in range(len(parent.Background)): if (parent.Background[i].countrate == bglist[b] and parent.Background[i].name == bgname): bgid[b] = i if bgid[b] == -1: # Add new background parent.Background.append( Trace(countrate=bglist[b], name=bgname, duration=1)) bgid[b] = len(parent.Background) - 1 # Apply background to page # Last item is id of background page.bgselected = bgid[0] if len(bgid) == 2: page.bg2selected = bgid[1] else: page.bg2selected = None CleanupAutomaticBackground(parent) page.OnAmplitudeCheck("init") page.PlotAll() def CleanupAutomaticBackground(parent): """ Goes through the pagelist *parent.notebook.GetPageCount()* and checks *parent.Background* for unnused automatic backgrounds. Removes these and updates the references to all backgrounds within the pages. """ # Create a dictionary with keys: indices of old background list - # and elements: list of pages having this background BGdict = dict() for i in range(len(parent.Background)): BGdict[i] = list() # Append pages to the lists inside the dictionary for i in range(parent.notebook.GetPageCount()): Page = parent.notebook.GetPage(i) if Page.bgselected is not None: if Page.bgselected not in BGdict: BGdict[Page.bgselected] = list() BGdict[Page.bgselected].append([Page, 1]) if Page.bg2selected is not None: if Page.bg2selected not in BGdict: BGdict[Page.bg2selected] = list() BGdict[Page.bg2selected].append([Page, 2]) oldBackground = parent.Background parent.Background = list() bgcounter = 0 for key in BGdict.keys(): if len(BGdict[key]) != 0 or not oldBackground[key].name.endswith("\t"): parent.Background.append(oldBackground[key]) for Page, bgid in BGdict[key]: if bgid == 1: Page.bgselected = bgcounter else: Page.bg2selected = bgcounter bgcounter += 1 # If the background correction tool is open, update the list # of backgrounds. # (self.MyName="BACKGROUND") toolkeys = parent.ToolsOpen.keys() if len(toolkeys) == 0: pass else: for key in toolkeys: tool = parent.ToolsOpen[key] try: if tool.MyName == "BACKGROUND": tool.UpdateDropdown() tool.OnPageChanged() except: pass pycorrfit-1.1.7/pycorrfit/gui/tools/batchcontrol.py0000664000372000037200000003227213554642611023421 0ustar travistravis00000000000000"""Module tools - batch: batch processing""" import numpy as np import os import wx from pycorrfit import openfile as opf from pycorrfit import models as mdls from pycorrfit import Fit from pycorrfit.gui.threaded_progress import ThreadedProgressDlg # Menu entry name MENUINFO = ["B&atch control", "Batch fitting."] class BatchCtrl(wx.Frame): def __init__(self, parent): # Parent is main frame self.parent = parent # Get the window positioning correctly pos = self.parent.GetPosition() pos = (pos[0]+100, pos[1]+100) wx.Frame.__init__(self, parent=parent, title="Batch control", pos=pos, style=wx.DEFAULT_FRAME_STYLE | wx.FRAME_FLOAT_ON_PARENT) # MYID # This ID is given by the parent for an instance of this class self.MyID = None # Misc try: self.curpage = self.parent.notebook.GetCurrentPage() except: self.curpage = None # Controls panel = wx.Panel(self) self.panel = panel # Icon self.Redraw() if parent.MainIcon is not None: wx.Frame.SetIcon(self, parent.MainIcon) self.Show(True) def GetParameters(self): """ The parameters """ # Get the item from the dropdown list item = self.dropdown.GetSelection() if self.rbtnhere.Value == True: # Get parameters from this session if item <= 0: Page = self.parent.notebook.GetCurrentPage() else: Page = self.parent.notebook.GetPage(item-1) # First apply the parameters of the page Page.apply_parameters() # Get all parameters Parms = self.parent.PackParameters(Page) else: # Get Parameters from different session Parms = self.YamlParms[item] return Parms def GetProtectedParameterIDs(self): """ The model parameters that are protected from batch control """ pbool = [not cb.GetValue() for cb in self.wxParameterCheckBoxes] return np.array(pbool, dtype=bool) def OnApply(self, event): wx.BeginBusyCursor() Parms = self.GetParameters() modelid = Parms[1] # Set all parameters for all pages for i in np.arange(self.parent.notebook.GetPageCount()): OtherPage = self.parent.notebook.GetPage(i) if (OtherPage.corr.fit_model.id == modelid and OtherPage.corr.correlation is not None): # create a copy of the fitting parameters in # case we want to protect them proparms = OtherPage.corr.fit_parameters self.parent.UnpackParameters(Parms, OtherPage) if OtherPage.prevent_batch_modification: # write back protected parameters OtherPage.corr.fit_parameters = proparms else: # write back only selected parameters pbool = self.GetProtectedParameterIDs() OtherPage.corr.fit_parameters[pbool] = proparms[pbool] OtherPage.apply_parameters_reverse() OtherPage.PlotAll(trigger="parm_batch") # Update all other tools fit the finalize trigger. self.parent.OnFNBPageChanged(trigger="parm_finalize") wx.EndBusyCursor() def OnClose(self, event=None): self.parent.toolmenu.Check(self.MyID, False) self.parent.ToolsOpen.__delitem__(self.MyID) self.Destroy() def OnFit(self, event): item = self.dropdown.GetSelection() if self.rbtnhere.Value == True: if item <= 0: page = self.parent.notebook.GetCurrentPage() else: page = self.parent.notebook.GetPage(item-1) # Get internal ID modelid = page.corr.fit_model.id else: # Get external ID modelid = self.YamlParms[item][1] # Get all pages with right modelid fit_page_list = [] for ii in np.arange(self.parent.notebook.GetPageCount()): pageii = self.parent.notebook.GetPage(ii) if (pageii.corr.fit_model.id == modelid and pageii.corr.correlation is not None): fit_page_list.append(pageii) FitProgressDlg(self, fit_page_list, trigger="fit_batch") if self.parent.MenuAutocloseTools.IsChecked(): # Autoclose self.OnClose() def OnPageChanged(self, Page=None, trigger=None): """ This function is called, when something in the panel changes. The variable `trigger` is used to prevent this function from being executed to save stall time of the user. Forr a list of possible triggers, see the doc string of `tools`. """ if self.parent.notebook.GetPageCount() == 0: self.panel.Disable() # nothing to do return else: self.panel.Enable() # Filter triggers if trigger in ["fit_batch", "fit_finalize", "init", "parm_batch", "parm_finalize"]: return oldpage = self.curpage self.curpage = self.parent.notebook.GetCurrentPage() # redraw this tool if necessary if oldpage is not None and oldpage: oldmodelid = oldpage.modelid else: oldmodelid = 0 newmodelid = self.curpage.modelid if oldmodelid != newmodelid: self.RedrawParameterBox() # We need to update the list of Pages in self.dropdown if self.rbtnhere.Value == True: DDlist = list() DDlist.append("Current page") for i in np.arange(self.parent.notebook.GetPageCount()): aPage = self.parent.notebook.GetPage(i) DDlist.append(aPage.counter+aPage.tabtitle.GetValue()) self.dropdown.SetItems(DDlist) self.dropdown.SetSelection(0) def OnRadioHere(self, event=None): self.OnPageChanged(trigger="view") self.RedrawParameterBox() def OnRadioThere(self, event=None): # If user clicks on pages in main program, we do not want the list # to be changed. wc = opf.session_wildcards wcstring = "PyCorrFit session (*.pcfs)|*{};*{}".format( wc[0], wc[1]) dlg = wx.FileDialog(self.parent, "Open session file", self.parent.dirname, "", wcstring, wx.FD_OPEN) # user cannot do anything until he clicks "OK" if dlg.ShowModal() == wx.ID_OK: sessionfile = dlg.GetPath() self.dirname = os.path.split(sessionfile)[0] else: self.parent.dirname = dlg.GetDirectory() self.rbtnhere.SetValue(True) return Infodict = opf.LoadSessionData(sessionfile, parameters_only=True) self.YamlParms = Infodict["Parameters"] DDlist = list() for i in np.arange(len(self.YamlParms)): # Rebuild the list modelid = self.YamlParms[i][1] modelname = mdls.modeldict[modelid][1] DDlist.append(self.YamlParms[i][0]+modelname) self.dropdown.SetItems(DDlist) # Set selection text to first item self.dropdown.SetSelection(0) self.RedrawParameterBox() def Redraw(self, e=None): panel = self.panel for child in panel.GetChildren(): panel.RemoveChild(child) child.Destroy() # Parameter source selection boxleft = wx.StaticBox(panel, label="Parameter source") self.rbtnhere = wx.RadioButton(panel, -1, 'This session', style=wx.RB_GROUP) self.rbtnhere.SetValue(True) self.rbtnthere = wx.RadioButton(panel, -1, 'Other session') self.dropdown = wx.ComboBox(panel, -1, "Current page", (15, 30), wx.DefaultSize, [], wx.CB_DROPDOWN | wx.CB_READONLY) # Create the dropdownlist text2 = wx.StaticText(panel, label="""Only data sets that have the same model as the parameter source will be affected by batch modification, which includes parameter values as well as settings for fitting and background correction. To prevent batch modification of parameter values for an individual page, check its "prevent batch modification" check box.""") self.Bind(wx.EVT_RADIOBUTTON, self.OnRadioHere, self.rbtnhere) self.Bind(wx.EVT_RADIOBUTTON, self.OnRadioThere, self.rbtnthere) self.Bind(wx.EVT_COMBOBOX, self.RedrawParameterBox, self.dropdown) leftSizer = wx.StaticBoxSizer(boxleft, wx.VERTICAL) leftSizer.Add(self.rbtnhere) leftSizer.Add(self.rbtnthere) leftSizer.AddSpacer(5) leftSizer.Add(self.dropdown) leftSizer.AddSpacer(5) leftSizer.Add(text2) leftSizer.AddSpacer(5) # Parameter selection boxright = wx.StaticBox(panel, label="Selected parameters") rightSizer = wx.StaticBoxSizer(boxright, wx.VERTICAL) self.parameter_sizer = rightSizer self.RedrawParameterBox() # Buttons btnapply = wx.Button(panel, wx.ID_ANY, 'Apply to applicable pages') btnfit = wx.Button(panel, wx.ID_ANY, 'Fit applicable pages') # Bindings self.Bind(wx.EVT_BUTTON, self.OnApply, btnapply) self.Bind(wx.EVT_BUTTON, self.OnFit, btnfit) # Sizers sizer_bag = wx.GridBagSizer(hgap=5, vgap=5) sizer_bag.Add(leftSizer, (0, 0)) sizer_bag.Add(rightSizer, (0, 1)) horsizer = wx.BoxSizer(wx.HORIZONTAL) horsizer.Add(btnapply) horsizer.Add(btnfit) sizer_bag.Add(horsizer, (1, 0), span=wx.GBSpan(1, 2)) panel.SetSizer(sizer_bag) sizer_bag.Fit(panel) self.SetMinSize(sizer_bag.GetMinSize()) # Check if we even have pages. self.OnPageChanged() panel.Layout() sizer_bag.Fit(self) self.mastersizer = sizer_bag self.mastersizer.Fit(self) def RedrawParameterBox(self, e=None): sizer = self.parameter_sizer panel = self.panel for child in sizer.GetChildren(): window = child.GetWindow() panel.RemoveChild(window) sizer.RemoveWindow(window) window.Destroy() text = wx.StaticText(panel, label="""If desired, (de)select individual parameters for batch modification.""") sizer.Add(text) if self.parent.notebook.GetPageCount(): # Get parameters of current page parms = self.GetParameters() modelid = parms[1] ptext, _pval = mdls.GetHumanReadableParms(modelid, parms[2]) ptext = [p.split()[0] for p in ptext] self.wxParameterCheckBoxes = [] for p in ptext: cb = wx.CheckBox(panel, label=p) cb.SetValue(True) self.wxParameterCheckBoxes.append(cb) sizer.Add(cb) # Try to set sizes correctly box = sizer.GetStaticBox() boxs = box.GetBestSize() sizs = sizer.GetMinSize() thesize = (max(boxs[0], sizs[0]), sizs[1]) sizer.SetMinSize(thesize) box.SetSize(thesize) try: self.mastersizer.Fit(panel) panel.Layout() self.SetSize(panel.GetSize()) self.mastersizer.Fit(self) except: pass class FitProgressDlg(ThreadedProgressDlg): def __init__(self, parent, pages, trigger=None): """ A progress dialog for fitting in PyCorrFit This is a convenience class that wraps around `ThreadedProgressDlg` and performs all necessary steps for fitting single pages in PyCorrFit. Parameters ---------- parent : wx object The parent of the progress dialog. pages : list of instances of `pycorrfit.gui.page.FittingPanel` The pages with the model and correlation for fitting. trigger : str PyCorrFit internal trigger string. """ if not isinstance(pages, list): pages = [pages] self.pages = pages self.trigger = trigger title = "Fitting data" messages = ["Fitting page #{}.".format( pi.counter.strip("# :")) for pi in pages] targets = [Fit]*len(pages) args = [pi.corr for pi in pages] # write parameters from page instance to correlation [pi.apply_parameters() for pi in self.pages] super(FitProgressDlg, self).__init__(parent, targets, args, title=title, messages=messages) def finalize(self): """ Do everything that is required after fitting, including cleanup of non-fitted pages. """ if self.aborted: # we need to cleanup fin_index = max(0, self.index_aborted-1) pab = self.pages[self.index_aborted] pab.fit_results = None pab.apply_parameters() else: fin_index = len(self.pages) # finalize fitting [pi.Fit_finalize(trigger=self.trigger) for pi in self.pages[:fin_index]] pycorrfit-1.1.7/pycorrfit/gui/tools/comment.py0000775000372000037200000000515313554642611022402 0ustar travistravis00000000000000"""Module tools - comment: Edit the session comment""" import wx class EditComment(wx.Frame): """ Little Dialog to edit the comment on the session. """ def __init__(self, parent): # Variables # parent is main frame self.parent = parent # Get the window positioning correctly pos = self.parent.GetPosition() pos = (pos[0]+100, pos[1]+100) wx.Frame.__init__(self, parent=parent, title="Session comment", pos=pos, style=wx.DEFAULT_FRAME_STYLE | wx.FRAME_FLOAT_ON_PARENT) initial_size = (400, 300) initial_sizec = (initial_size[0], initial_size[1]-50) self.SetSize(initial_size) self.SetMinSize((400, 300)) # Content self.panel = wx.Panel(self) self.control = wx.TextCtrl(self.panel, style=wx.TE_MULTILINE, size=initial_sizec, value=self.parent.SessionComment) self.Bind(wx.EVT_TEXT, self.OnTextChanged, self.control) text = wx.StaticText(self.panel, label="Session comments will be saved in the session file.") # buttons btnsave = wx.Button(self.panel, wx.ID_SAVE, 'Save Comment') self.Bind(wx.EVT_BUTTON, self.OnSave, btnsave) # sizers self.topSizer = wx.BoxSizer(wx.VERTICAL) self.topSizer.Add(text) self.topSizer.Add(self.control) self.topSizer.Add(btnsave, 1, wx.RIGHT | wx.EXPAND) self.panel.SetSizer(self.topSizer) self.topSizer.Fit(self) # Icon if parent.MainIcon is not None: wx.Frame.SetIcon(self, parent.MainIcon) self.Show(True) wx.EVT_SIZE(self, self.OnSize) self.text_changed = False def OnSize(self, event): size = event.GetSize() sizec = (size[0], size[1]-50) self.panel.SetSize(size) self.control.SetSize(sizec) def OnClose(self, e=None): self.parent.filemenu.Check(self.parent.menuComm.GetId(), False) if self.text_changed: # ask the user to save or discard. dlg = wx.MessageDialog(self, "Save comment?", "Do you want to save the current changes?", style=wx.YES_NO) if dlg.ShowModal() == wx.ID_YES: self.OnSave() self.Destroy() def OnTextChanged(self, e=None): """ When the user changes the text """ self.text_changed = True def OnSave(self, e=None): self.parent.SessionComment = self.control.GetValue() self.text_changed = False self.OnClose() pycorrfit-1.1.7/pycorrfit/gui/tools/chooseimport.py0000664000372000037200000002273413554642611023454 0ustar travistravis00000000000000"""Module tools - chooseimport Displays a window that lets the user choose what type of data (AC1, AC2, CC12, CC21) he wants to import. """ import numpy as np import wx from pycorrfit import models as mdls from . import overlaycurves class ChooseImportTypes(wx.Dialog): """ This class is used for importing single files from the "Current" menu. The model function is defined by the model that is in use. """ # This tool is derived from a wx.Dialog. def __init__(self, parent, curvedict): # parent is the main frame of PyCorrFit self.parent = parent # init # super(ChooseImportTypes, self).__init__(parent=parent, # title="Choose types", size=(250, 200)) wx.Dialog.__init__(self, parent, -1, "Choose models") self.keys = list() # Content self.panel = wx.Panel(self) self.sizer = wx.BoxSizer(wx.VERTICAL) self.boxes = dict() # For the selection of types to import when doing import Data chooseimport = ("Several types of data were found in\n" + "the chosen file. Please select what\n" + "type(s) you would like to import.") textinit = wx.StaticText(self.panel, label=chooseimport) self.sizer.Add(textinit) thekeys = list(curvedict.keys()) thekeys.sort() for key in thekeys: label = key + " (" + str(len(curvedict[key])) + " curves)" check = wx.CheckBox(self.panel, label=label) self.boxes[key] = check self.sizer.Add(check) self.Bind(wx.EVT_CHECKBOX, self.OnSetkeys, check) btnok = wx.Button(self.panel, wx.ID_OK, 'OK') # Binds the button to the function - close the tool self.Bind(wx.EVT_BUTTON, self.OnClose, btnok) self.sizer.Add(btnok) self.panel.SetSizer(self.sizer) self.sizer.Fit(self) # Icon if parent.MainIcon is not None: wx.Dialog.SetIcon(self, parent.MainIcon) # self.Show(True) self.SetFocus() def OnClose(self, event=None): # This is a necessary function for PyCorrFit. # Do not change it. self.EndModal(wx.ID_OK) # self.Destroy() def OnSetkeys(self, event=None): self.keys = list() for key in self.boxes.keys(): if self.boxes[key].Value == True: self.keys.append(key) class ChooseImportTypesModel(wx.Dialog): """ This class shows a dialog displaying options to choose model function on import of data """ # This tool is derived from a wx.frame. def __init__(self, parent, curvedict, correlations, labels=None): """ curvedict - dictionary, contains indexes to correlations and labels. The keys are different types of curves correlations - list of correlations labels - list of labels for the correlations (e.g. filename+run) if none, index numbers will be used for labels """ # parent is the main frame of PyCorrFit self.parent = parent # init # super(ChooseImportTypesModel, self).__init__(parent=parent, # title="Choose types", size=(250, 200)) wx.Dialog.__init__(self, parent, -1, "Choose models") self.curvedict = curvedict self.kept_curvedict = curvedict.copy() # Can be edited by user self.correlations = correlations self.labels = labels # List of keys that will be imported by our *parent* self.typekeys = list() # Dictionary of modelids corresponding to indices in curvedict self.modelids = dict() # Content self.panel = wx.Panel(self) self.sizer = wx.BoxSizer(wx.VERTICAL) self.boxes = dict() labelim = "Select a fitting model for each correlation channel (AC,CC)." textinit = wx.StaticText(self.panel, label=labelim) self.sizer.Add(textinit) curvekeys = list(curvedict.keys()) curvekeys.sort() self.curvekeys = curvekeys # Dropdown model selections: # Contains string in model dropdown DropdownList = ["No model selected"] self.DropdownIndex = [None] # Contains corresponsing model modelkeys = list(mdls.modeltypes.keys()) modelkeys.sort() for modeltype in modelkeys: for modelid in mdls.modeltypes[modeltype]: DropdownList.append(modeltype+": "+mdls.modeldict[modelid][1]) self.DropdownIndex.append(modelid) self.ModelDropdown = dict() dropsizer = wx.FlexGridSizer( rows=len(modelkeys), cols=3, vgap=5, hgap=5) self.Buttons = list() i = 8000 for key in curvekeys: # Text with keys and numer of curves dropsizer.Add(wx.StaticText(self.panel, label=str(key))) label = " ("+str(len(curvedict[key]))+" curves)" button = wx.Button(self.panel, i, label) i += 1 self.Bind(wx.EVT_BUTTON, self.OnSelectCurves, button) self.Buttons.append(button) dropsizer.Add(button) # Model selection dropdown dropdown = wx.ComboBox(self.panel, -1, DropdownList[0], (15, 30), wx.DefaultSize, DropdownList, wx.CB_DROPDOWN | wx.CB_READONLY) dropsizer.Add(dropdown) self.ModelDropdown[key] = dropdown self.Bind(wx.EVT_COMBOBOX, self.OnSetkeys, dropdown) self.sizer.Add(dropsizer) btnok = wx.Button(self.panel, wx.ID_OK, 'OK') # Binds the button to the function - close the tool self.Bind(wx.EVT_BUTTON, self.OnClose, btnok) self.sizer.Add(btnok) self.panel.SetSizer(self.sizer) self.sizer.Fit(self) # self.Show(True) self.SetFocus() if parent.MainIcon is not None: wx.Dialog.SetIcon(self, parent.MainIcon) def OnClose(self, event=None): # This is a necessary function for PyCorrFit. # Do not change it. self.keepcurvesindex = list() for key in self.kept_curvedict.keys(): self.keepcurvesindex += self.kept_curvedict[key] for i in np.arange(len(self.keepcurvesindex)): self.keepcurvesindex[i] = int(self.keepcurvesindex[i]) self.EndModal(wx.ID_OK) # self.Show # self.Destroy() def OnSelectCurves(self, buttonevent): # Get the type of curves we want to look at index = buttonevent.GetId() - 8000 self.buttonindex = index key = self.curvekeys[index] # Get correlation curves for corresponding type corrcurves = dict() if self.labels is None: labeldict = None else: labeldict = dict() for i in self.curvedict[key]: corrcurves[str(i)] = self.correlations[int(i)] if self.labels is not None: labeldict[str(i)] = self.labels[int(i)] prev_selected = list() for item in self.kept_curvedict.keys(): prev_selected += self.kept_curvedict[item] overlaycurves.Wrapper_OnImport(self.parent, corrcurves, self.OnSelected, prev_selected, labels=labeldict) def OnSelected(self, keep, remove): # Set new button label for i in np.arange(len(keep)): keep[i] = int(keep[i]) #button = self.Buttons[self.buttonindex] label = " ("+str(len(keep))+" curves)" # button.SetLabel(label) # Add new content to selected key SelectedKey = self.curvekeys[self.buttonindex] #self.kept_curvedict[SelectedKey] = keep # If there are keys with the same amount of correlations, # these are assumed to be AC2, CC12, CC21 etc., so we will remove # items from them accordingly. diff = set(keep).intersection(set(self.curvedict[SelectedKey])) indexes = list() for i in np.arange(len(self.curvedict[SelectedKey])): for number in diff: if number == self.curvedict[SelectedKey][i]: indexes.append(i) for j in np.arange(len(self.curvekeys)): key = self.curvekeys[j] if len(self.curvedict[key]) == len(self.curvedict[SelectedKey]): newlist = list() for index in indexes: newlist.append(self.curvedict[key][index]) self.kept_curvedict[key] = newlist # Also update buttons button = self.Buttons[j] button.SetLabel(label) def OnSetkeys(self, event=None): # initiate objects self.typekeys = list() self.modelids = dict() # iterate through all given keys (AC1, AC2, CC12, etc.) for key in self.curvedict.keys(): # get the dropdown selection for a given key modelindex = self.ModelDropdown[key].GetSelection() # modelindex is -1 or 0, if no model has been chosen if modelindex > 0: # Append the key to a list of to be imported types self.typekeys.append(key) # Append the modelid to a dictionary that has indexes # belonging to the imported curves in *parent* modelid = self.DropdownIndex[modelindex] for index in self.curvedict[key]: # Set different model id for the curves self.modelids[index] = modelid self.typekeys.sort() pycorrfit-1.1.7/pycorrfit/gui/tools/globalfit.py0000664000372000037200000001271013554642611022675 0ustar travistravis00000000000000"""Module tools - globalfit Perform global fitting on pages which share parameters """ import numpy as np import wx from pycorrfit import Fit from .. import misc # Menu entry name MENUINFO = ["&Global fitting", "Interconnect parameters from different measurements."] class GlobalFit(wx.Frame): # This tool is derived from a wx.frame. def __init__(self, parent): # Define a unique name that identifies this tool # Do not change this value. It is important for the Overlay tool # (selectcurves.py, *Wrapper_Tools*). self.MyName = "GLOBALFIT" # parent is the main frame of PyCorrFit self.parent = parent # Get the window positioning correctly pos = self.parent.GetPosition() pos = (pos[0]+100, pos[1]+100) wx.Frame.__init__(self, parent=self.parent, title="Gobal fitting", pos=pos, style=wx.DEFAULT_FRAME_STYLE | wx.FRAME_FLOAT_ON_PARENT) # MYID # This ID is given by the parent for an instance of this class self.MyID = None # Page - the currently active page of the notebook. self.Page = self.parent.notebook.GetCurrentPage() # Content self.panel = wx.Panel(self) self.topSizer = wx.BoxSizer(wx.VERTICAL) textinit = """Fitting of multiple data sets with different models. Parameter names have to match. Select pages (e.g. 1,3-5,7), check parameters on each page and start 'Global fit'. """ self.topSizer.Add(wx.StaticText(self.panel, label=textinit)) # Page selection self.WXTextPages = wx.TextCtrl(self.panel, value="", size=(330, -1)) # Set initial value in text control pagenumlist = list() for i in np.arange(self.parent.notebook.GetPageCount()): Page = self.parent.notebook.GetPage(i) pagenumlist.append( int("".join(filter(lambda x: x.isdigit(), Page.counter)))) valstring = misc.parsePagenum2String(pagenumlist) self.WXTextPages.SetValue(valstring) self.topSizer.Add(self.WXTextPages) # Button btnfit = wx.Button(self.panel, wx.ID_ANY, 'Global fit') # Binds the button to the function - close the tool self.Bind(wx.EVT_BUTTON, self.OnFit, btnfit) self.topSizer.Add(btnfit) self.panel.SetSizer(self.topSizer) self.topSizer.Fit(self) self.SetMinSize(self.topSizer.GetMinSize()) self.OnPageChanged(self.Page) # Icon if parent.MainIcon is not None: wx.Frame.SetIcon(self, parent.MainIcon) self.Show(True) def OnClose(self, event=None): # This is a necessary function for PyCorrFit. # Do not change it. self.parent.toolmenu.Check(self.MyID, False) self.parent.ToolsOpen.__delitem__(self.MyID) self.Destroy() def OnFit(self, e=None): # process a string like this: "1,2,4-9,10" strFull = self.WXTextPages.GetValue() PageNumbers = misc.parseString2Pagenum(self, strFull) global_pages = list() if PageNumbers is None: # Something went wrong and parseString2Pagenum already displayed # an error message. return # Get the correlations corrs = list() for i in np.arange(self.parent.notebook.GetPageCount()): Page = self.parent.notebook.GetPage(i) corr = Page.corr j = "".join(filter(lambda x: x.isdigit(), Page.counter)) if int(j) in PageNumbers: if corr.correlation is not None: Page.apply_parameters() corrs.append(corr) global_pages.append(int(j)) else: print("No experimental data in page #"+j+"!") if len(corrs) == 0: return # Perform fit fitter = Fit(corrs, global_fit=True) fit_parm_names = [f.split()[0] for f in fitter.fit_parm_names] # update fit results for corr in corrs: corr.fit_results["global parms"] = u", ".join(fit_parm_names) corr.fit_results["global pages"] = u", ".join( [str(g) for g in global_pages]) # Plot resutls for i in np.arange(self.parent.notebook.GetPageCount()): Page = self.parent.notebook.GetPage(i) j = "".join(filter(lambda x: x.isdigit(), Page.counter)) if int(j) in global_pages: Page.apply_parameters_reverse() Page.PlotAll() if self.parent.MenuAutocloseTools.IsChecked(): # Autoclose self.OnClose() def OnPageChanged(self, page, trigger=None): """ This function is called, when something in the panel changes. The variable `trigger` is used to prevent this function from being executed to save stall time of the user. Forr a list of possible triggers, see the doc string of `tools`. """ # When parent changes # This is a necessary function for PyCorrFit. # This is stuff that should be done when the active page # of the notebook changes. if trigger in ["parm_batch", "fit_batch", "page_add_batch"]: return if self.parent.notebook.GetPageCount() == 0: self.panel.Disable() return self.panel.Enable() self.Page = page def SetPageNumbers(self, pagestring): self.WXTextPages.SetValue(pagestring) pycorrfit-1.1.7/pycorrfit/gui/tools/__init__.py0000664000372000037200000000562313554642611022476 0ustar travistravis00000000000000"""PyCorrFit - module "tools" This file contains tools, such as dialog boxes and other stuff, that we need in PyCorrFit. The tools work with triggers on page updates. Every tool has a function `OnPageChanged(self, page, trigger=None)` which is called when something in the frontend chages. In order to minimize user stall time, these functions are not executed for a certain list of triggers that is defined in that function. This e.g. dramatically speeds up tools like "Statistics view" when batch fitting. Recognized triggers: tab_init : initial stuff that is done for a new page tab_browse : the tab has change and a new page is visible fit_batch : the page is batch-fitted right now fit_finalize : a (batch) fitting process is finished parm_batch : parameters are changed in a batch process parm_finalize : finished (batch) changing of page parameters page_add_batch : when many pages are added at the same time page_add_finalize : finished (batch) adding of pages """ import sys import numpy as np from .parmrange import RangeSelector from .comment import EditComment from .chooseimport import ChooseImportTypesModel from .chooseimport import ChooseImportTypes from . import datarange from . import background from . import overlaycurves from . import batchcontrol from . import globalfit from . import average from . import simulation from . import info from . import statistics from . import trace # Load all of the classes # This also defines the order of the tools in the menu ImpA = [ ["datarange", "SelectChannels"], ["overlaycurves", "Wrapper_Tools"], ["batchcontrol", "BatchCtrl"], ["globalfit", "GlobalFit"], ["average", "Average"], ["background", "BackgroundCorrection"] ] ImpB = [ ["trace", "ShowTrace"], ["statistics", "Stat"], ["info", "ShowInfo"], ["simulation", "Slide"] ] ModuleActive = list() ToolsActive = list() for i in np.arange(len(ImpA)): # We have to add "tools." because this is a relative import ModuleActive.append(__import__( ImpA[i][0], globals(), locals(), [ImpA[i][1]], 1)) ToolsActive.append(getattr(ModuleActive[i], ImpA[i][1])) ModulePassive = list() ToolsPassive = list() for i in np.arange(len(ImpB)): ModulePassive.append(__import__( ImpB[i][0], globals(), locals(), [ImpB[i][1]], 1)) ToolsPassive.append(getattr(ModulePassive[i], ImpB[i][1])) # ModulePassive.append(importlib.import_module("tools."+ImpB[i][0])) #ToolsPassive.append(getattr(ModulePassive[i], ImpB[i][1])) ToolDict = dict() ToolDict["A"] = ToolsActive ToolDict["P"] = ToolsPassive # Make the same for Menu Names in Tools NameActive = list() for i in np.arange(len(ImpA)): NameActive.append(ModuleActive[i].MENUINFO) NamePassive = list() for i in np.arange(len(ImpB)): NamePassive.append(ModulePassive[i].MENUINFO) ToolName = dict() ToolName["A"] = NameActive ToolName["P"] = NamePassive pycorrfit-1.1.7/pycorrfit/gui/tools/average.py0000664000372000037200000004041513554642611022347 0ustar travistravis00000000000000"""Module tools - average: Creates an average of curves""" import numpy as np import wx from pycorrfit import models as mdls from .. import misc # Menu entry name MENUINFO = ["&Average data", "Create an average curve from whole session."] class Average(wx.Frame): # This tool is derived from a wx.frame. def __init__(self, parent): # Define a unique name that identifies this tool # Do not change this value. It is important for the Overlay tool # (selectcurves.py, *Wrapper_Tools*). self.MyName = "AVERAGE" # parent is the main frame of PyCorrFit self.parent = parent # Get the window positioning correctly pos = self.parent.GetPosition() pos = (pos[0]+100, pos[1]+100) wx.Frame.__init__(self, parent=self.parent, title="Average curves", pos=pos, style=wx.DEFAULT_FRAME_STYLE | wx.FRAME_FLOAT_ON_PARENT) # MYID # This ID is given by the parent for an instance of this class self.MyID = None # Page - the currently active page of the notebook. self.Page = self.parent.notebook.GetCurrentPage() # Content self.panel = wx.Panel(self) self.topSizer = wx.BoxSizer(wx.VERTICAL) textinit = wx.StaticText(self.panel, label="Create an average from the following pages:") self.topSizer.Add(textinit) # Page selection self.WXTextPages = wx.TextCtrl(self.panel, value="", size=(textinit.GetSize()[0], -1)) self.topSizer.Add(self.WXTextPages) # Chechbox asking for Mono-Model self.WXCheckMono = wx.CheckBox(self.panel, label="Only use pages with the same model as the first page.") self.WXCheckMono.SetValue(True) self.topSizer.Add(self.WXCheckMono) # Model selection Dropdown textinit2 = wx.StaticText(self.panel, label="Select a model for the average:") self.topSizer.Add(textinit2) self.WXDropSelMod = wx.ComboBox(self.panel, -1, "", (15, 30), wx.DefaultSize, [], wx.CB_DROPDOWN | wx.CB_READONLY) self.topSizer.Add(self.WXDropSelMod) textinit3 = wx.StaticText(self.panel, label="This tool averages only over pages with the same type" + "\n(auto- or cross-correlation). Intensity data are" + "\nappended sequentially.") self.topSizer.Add(textinit3) # Set all values of Text and Strin self.SetValues() btnavg = wx.Button(self.panel, wx.ID_CLOSE, 'Create average') # Binds the button to the function - close the tool self.Bind(wx.EVT_BUTTON, self.OnAverage, btnavg) self.topSizer.Add(btnavg) self.panel.SetSizer(self.topSizer) self.topSizer.Fit(self) self.SetMinSize(self.topSizer.GetMinSize()) # Icon if parent.MainIcon is not None: wx.Frame.SetIcon(self, parent.MainIcon) self.Show(True) self.OnPageChanged(self.Page) def OnClose(self, event=None): # This is a necessary function for PyCorrFit. # Do not change it. self.parent.toolmenu.Check(self.MyID, False) self.parent.ToolsOpen.__delitem__(self.MyID) self.Destroy() def OnPageChanged(self, page, trigger=None): """ This function is called, when something in the panel changes. The variable `trigger` is used to prevent this function from being executed to save stall time of the user. Forr a list of possible triggers, see the doc string of `tools`. """ # When parent changes # This is a necessary function for PyCorrFit. # This is stuff that should be done when the active page # of the notebook changes. if trigger in ["parm_batch", "fit_batch", "page_add_batch", "parm_finalize", "fit_finalize"]: return if self.parent.notebook.GetPageCount() == 0: self.panel.Disable() return #idsel = self.WXDropSelMod.GetSelection() self.SetValues() # Set back user selection: # self.WXDropSelMod.SetSelection(idsel) self.panel.Enable() self.Page = page def OnAverage(self, evt=None): strFull = self.WXTextPages.GetValue() PageNumbers = misc.parseString2Pagenum(self, strFull) if PageNumbers is None: # Something went wrong and parseString2Pagenum already displayed # an error message. return pages = list() UsedPagenumbers = list() # Reference page is the first page of the selection! #referencePage = self.parent.notebook.GetCurrentPage() referencePage = None for i in np.arange(self.parent.notebook.GetPageCount()): Page = self.parent.notebook.GetPage(i) if Page.counter.strip(" :#") == str(PageNumbers[0]): referencePage = Page break if referencePage is None: # If that did not work, we have to raise an error. raise IndexError("PyCorrFit could not find the first" + " page for averaging.") return for i in np.arange(self.parent.notebook.GetPageCount()): Page = self.parent.notebook.GetPage(i) corr = Page.corr model = Page.corr.fit_model j = "".join(filter(lambda x: x.isdigit(), Page.counter)) if int(j) in PageNumbers: # Get all pages with the same model? if self.WXCheckMono.GetValue() == True: if (model.id == referencePage.corr.fit_model.id and corr.is_cc == referencePage.corr.is_cc): # Check if the page has experimental data: # If there is an empty page somewhere, don't bother if corr.correlation is not None: pages.append(Page) UsedPagenumbers.append(int(j)) else: if corr.is_cc == referencePage.corr.is_cc: # If there is an empty page somewhere, don't bother if corr.correlation is not None: pages.append(Page) UsedPagenumbers.append(int(j)) # If there are no pages in the list, exit gracefully if len(pages) <= 0: texterr_a = "At least one page with experimental data is\n" +\ "required for averaging. Please check the pages\n" +\ "that you selected for averaging." if self.WXCheckMono.GetValue() == True: texterr_a += " Note: You selected\n" +\ "to only use pages with same model as the first page." wx.MessageDialog(self, texterr_a, "Error", style=wx.ICON_ERROR | wx.OK | wx.STAY_ON_TOP) return # Now get all the experimental data explist = list() # Two components in case of Cross correlation tracetime = [np.array([]), np.array([])] tracerate = [np.array([]), np.array([])] TraceNumber = 0 TraceAvailable = False # turns True, if pages contain traces for page in pages: corr = page.corr # experimental correlation curve # (at least 1d, because it might be None) explist.append(np.atleast_1d(1*corr.correlation)) # trace # We will put together a trace from all possible traces # Stitch together all the traces. trace = corr.traces TraceNumber = len(trace) if TraceNumber > 0: TraceAvailable = True # Works with one or two traces. j = 0 or 1. for j in np.arange(TraceNumber): if len(tracetime[j]) != 0: # append to the trace oldend = tracetime[j][-1] # we assume that the first two points in a # trace are equidistant and we will use their # difference as an offset offset = trace[j][:, 0][1] - 2*trace[j][:, 0][0] newtracetime = 1.*trace[j][:, 0] + offset newtracetime = newtracetime + oldend tracetime[j] = np.append(tracetime[j], newtracetime) del newtracetime tracerate[j] = np.append(tracerate[j], trace[j][:, 1]) else: # Initiate the trace tracetime[j] = 1.*trace[j][:, 0] tracerate[j] = 1.*trace[j][:, 1] # Now check if the length of the correlation arrays are the same: len0 = len(explist[0]) for item in explist[1:]: if len(item) != len0: # print an error message wx.MessageDialog(self, "Averaging over curves with different lengths is not" + "\nsupported. When measuring, please make sure that" + "\nthe measurement time for all curves is the same.", "Error", style=wx.ICON_ERROR | wx.OK | wx.STAY_ON_TOP) return # Now shorten the trace, because we want as little memory usage as # possible. I used this algorithm in read_FCS_Confocor3.py as well. newtraces = list() if TraceAvailable: for j in np.arange(TraceNumber): tracej = np.zeros((len(tracetime[j]), 2)) tracej[:, 0] = tracetime[j] tracej[:, 1] = tracerate[j] if len(tracej) >= 500: # We want about 500 bins # We need to sum over intervals of length *teiler* teiler = int(len(tracej)/500) newlength = len(tracej) // teiler newsignal = np.zeros(newlength) # Simultaneously sum over all intervals for k in np.arange(teiler): newsignal = \ newsignal+tracej[k:newlength*teiler:teiler][:, 1] newsignal = 1. * newsignal / teiler newtimes = tracej[teiler-1:newlength*teiler:teiler][:, 0] if len(tracej) % teiler != 0: # We have a rest signal # We average it and add it to the trace rest = tracej[newlength*teiler:][:, 1] lrest = len(rest) rest = np.array([sum(rest)/lrest]) newsignal = np.concatenate((newsignal, rest), axis=0) timerest = np.array([tracej[-1][0]]) newtimes = np.concatenate((newtimes, timerest), axis=0) newtrace = np.zeros((len(newtimes), 2)) newtrace[:, 0] = newtimes newtrace[:, 1] = newsignal else: # Declare newtrace - # otherwise we have a problem down three lines ;) newtrace = tracej newtraces.append(newtrace) else: newtraces = [None, None] # Everything is cleared for averaging exparray = np.array(explist) averagedata = exparray.sum(axis=0)[:, 1]/len(exparray) # Create a copy from the first page average = 1*exparray[0] # Set average data average[:, 1] = averagedata # create new page self.IsCrossCorrelation = self.Page.corr.is_cc interval = self.Page.corr.fit_ival # Obtain the model ID from the dropdown selection. idsel = self.WXDropSelMod.GetSelection() modelid = self.DropdownIndex[idsel] self.AvgPage = self.parent.add_fitting_tab(modelid=modelid, select=True) self.AvgPage.corr.fit_ival = interval self.AvgPage.corr.correlation = average if self.IsCrossCorrelation is False: self.AvgPage.corr.corr_type = "AC average" newtrace = newtraces[0] if newtrace is not None and len(newtrace) != 0: self.AvgPage.corr.traces = [newtrace] else: self.AvgPage.corr.traces = [] else: self.AvgPage.corr.corr_type = "CC average" if newtraces[0] is not None and len(newtraces[0][0]) != 0: self.AvgPage.corr.traces = newtraces else: self.AvgPage.corr.traces = [] self.AvgPage.Fit_enable_fitting() if len(pages) == 1: # Use the same title as the first page newtabti = referencePage.tabtitle.GetValue() else: # Create a new tab title newtabti = "Average [" + \ misc.parsePagenum2String(UsedPagenumbers)+"]" self.AvgPage.tabtitle.SetValue(newtabti) # Set the addition information about the variance from averaging Listname = "Average" listname = Listname.lower() standarddev = exparray.std(axis=0)[:, 1] if np.sum(np.abs(standarddev)) == 0: # The average sd is zero. We probably made an average # from only one page. In this case we do not enable # average weighted fitting pass else: # TODO: # kind of hackish to repeat this three times: # self.AvgPage.corr.set_weights(Listname, standarddev) self.AvgPage.corr.set_weights(listname, standarddev) WeightKinds = self.AvgPage.Fitbox[1].GetItems() # Attention! Average weights and other external weights should # be sorted (for session saving). extTypes = list(self.AvgPage.corr._fit_weight_memory.keys()) # TODO: # find a cleaner solution extTypes.remove("none") extTypes.sort() # sorting for key in extTypes: try: WeightKinds.remove(key) except: pass LenInternal = len(WeightKinds) IndexAverag = extTypes.index(listname) IndexInList = LenInternal + IndexAverag for key in extTypes: WeightKinds += [key] self.AvgPage.Fitbox[1].SetItems(WeightKinds) self.AvgPage.Fitbox[1].SetSelection(IndexInList) self.AvgPage.corr.set_weights(listname, standarddev) self.AvgPage.apply_parameters() self.AvgPage.corr.set_weights(listname, standarddev) self.AvgPage.PlotAll() if self.parent.MenuAutocloseTools.IsChecked(): # Autoclose self.OnClose() def SetPageNumbers(self, pagestring): self.WXTextPages.SetValue(pagestring) def SetValues(self, e=None): # Text input pagenumlist = list() for i in np.arange(self.parent.notebook.GetPageCount()): Page = self.parent.notebook.GetPage(i) pagenumlist.append( int("".join(filter(lambda x: x.isdigit(), Page.counter)))) valstring = misc.parsePagenum2String(pagenumlist) self.WXTextPages.SetValue(valstring) # Dropdown modelkeys = list(mdls.modeltypes.keys()) modelkeys.sort() try: current_model = self.parent.notebook.GetCurrentPage().corr.fit_model.id except: current_model = -1 i = 0 DropdownList = list() self.DropdownIndex = list() # Contains model ids with same index current_index = 0 for modeltype in modelkeys: for modelid in mdls.modeltypes[modeltype]: DropdownList.append(modeltype+": "+mdls.modeldict[modelid][1]) self.DropdownIndex.append(str(modelid)) if str(current_model) == str(modelid): current_index = i i += 1 self.WXDropSelMod.SetItems(DropdownList) self.WXDropSelMod.SetSelection(current_index) pycorrfit-1.1.7/pycorrfit/gui/tools/trace.py0000664000372000037200000001043013554642611022025 0ustar travistravis00000000000000"""Module tools - trace: Show the trace of a measurement""" import numpy as np import wx import wx.lib.plot as plot # Menu entry name MENUINFO = ["&Trace view", "Show the trace of an opened file."] class ShowTrace(wx.Frame): def __init__(self, parent): # parent is main frame self.parent = parent # Get the window positioning correctly pos = self.parent.GetPosition() pos = (pos[0]+100, pos[1]+100) wx.Frame.__init__(self, parent=self.parent, title="Trace view", pos=pos, style=wx.DEFAULT_FRAME_STYLE | wx.FRAME_FLOAT_ON_PARENT) # MYID # This ID is given by the parent for an instance of this class self.MyID = None # Page self.Page = self.parent.notebook.GetCurrentPage() # Canvas self.canvas = plot.PlotCanvas(self) self.canvas.enableZoom = True if self.parent.notebook.GetPageCount() == 0: # We do not need to disable anything here. user input. pass else: self.OnDraw() initial_size = (780, 250) self.SetSize(initial_size) self.SetMinSize(initial_size) # Icon if parent.MainIcon is not None: wx.Frame.SetIcon(self, parent.MainIcon) self.Show(True) def OnClose(self, event=None): self.parent.toolmenu.Check(self.MyID, False) self.parent.ToolsOpen.__delitem__(self.MyID) self.Destroy() def OnDraw(self): traces = self.Page.corr.traces self.canvas.enableLegend = True if len(traces) == 1: self.trace = 1*traces[0].trace # We want to have the trace in [s] here. self.trace[:, 0] = self.trace[:, 0]/1000 line = plot.PolyLine(self.trace, legend='{:.2f}kHz'.format( traces[0].countrate), colour='blue', width=1) lines = [line] xmax = np.max(self.trace[:, 0]) xmin = np.min(self.trace[:, 0]) ymax = np.max(self.trace[:, 1]) ymin = np.min(self.trace[:, 1]) elif len(traces) == 2: # This means that we have two (CC) traces to plot self.tracea = 1*traces[0].trace self.tracea[:, 0] = self.tracea[:, 0]/1000 self.traceb = 1*traces[1].trace self.traceb[:, 0] = self.traceb[:, 0]/1000 linea = plot.PolyLine(self.tracea, legend='channel 1 {:.2f}kHz'.format( traces[0].countrate), colour='blue', width=1) lineb = plot.PolyLine(self.traceb, legend='channel 2 {:.2f}kHz'.format( traces[1].countrate), colour='red', width=1) lines = [linea, lineb] xmax = max(np.max(self.tracea[:, 0]), np.max(self.traceb[:, 0])) xmin = min(np.min(self.tracea[:, 0]), np.min(self.traceb[:, 0])) ymax = max(np.max(self.tracea[:, 1]), np.max(self.traceb[:, 1])) ymin = min(np.min(self.tracea[:, 1]), np.min(self.traceb[:, 1])) else: self.canvas.Clear() return # Plot lines self.canvas.Draw(plot.PlotGraphics(lines, xLabel='time [s]', yLabel='count rate [kHz]'), xAxis=(xmin, xmax), yAxis=(ymin, ymax)) def OnPageChanged(self, page=None, trigger=None): """ This function is called, when something in the panel changes. The variable `trigger` is used to prevent this function from being executed to save stall time of the user. Forr a list of possible triggers, see the doc string of `tools`. """ if trigger in ["parm_batch", "fit_batch", "page_add_batch"]: return self.Page = page # When parent changes if self.parent.notebook.GetPageCount() == 0: # Nothing to do try: self.canvas.Clear() except: pass return self.OnDraw() pycorrfit-1.1.7/pycorrfit/gui/tools/info.py0000664000372000037200000003170413554642611021671 0ustar travistravis00000000000000"""Module tools - info: Open a text window with lots of information""" import wx import numpy as np from pycorrfit import fit from pycorrfit import models as mdls # Menu entry name MENUINFO = ["Page &info", "Display some information on the current page."] class InfoClass(object): """ This class get's all the Info possible from a Page and makes it available through a dictionary with headings as keys. """ def __init__(self, CurPage=None, Pagelist=None): # A list of all Pages currently available: self.Pagelist = Pagelist # The current page we are looking at: self.CurPage = CurPage def GetAllInfo(self): """ Get a dictionary with page titles and an InfoDict as value. """ MultiInfo = dict() for Page in self.Pagelist: # Page counter includes a whitespace and a ":" which we do not want. MultiInfo[Page.counter[:-2]] = self.GetPageInfo(Page) return MultiInfo def GetCurInfo(self): """ Get all the information about the current Page. Added for convenience. You may use GetPageInfo. """ return self.GetPageInfo(self.CurPage) def GetCurFancyInfo(self): """ For convenience. """ return self.GetFancyInfo(self.CurPage) def GetFancyInfo(self, Page): """ Get a nice string representation of the Info """ InfoDict = self.GetPageInfo(Page) # Version Version = u"PyCorrFit v."+InfoDict["version"][0]+"\n" # Title Title = u"\n" for item in InfoDict["title"]: Title = Title + item[0]+"\t" + item[1]+"\n" # Parameters Parameters = u"\nParameters:\n" for item in InfoDict["parameters"]: Parameters = Parameters + u" "+item[0]+"\t" + str(item[1])+"\n" # Supplementary parameters Supplement = u"\nSupplementary parameters:\n" try: for item in InfoDict["supplement"]: Supplement = Supplement + " "+item[0]+"\t" + str(item[1])+"\n" except KeyError: Supplement = "" # Fitting Fitting = u"\nFitting:\n" try: for item in InfoDict["fitting"]: Fitting = Fitting + " "+item[0]+"\t"+str(item[1])+"\n" except KeyError: Fitting = "" # Background Background = u"\nBackground:\n" try: for item in InfoDict["background"]: Background = Background + " "+item[0]+"\t"+str(item[1])+"\n" except KeyError: Background = "" # Function doc string ModelDoc = u"\n\nModel doc string:\n " + InfoDict["modeldoc"][0] # Supplementary variables try: SupDoc = u"\n"+8*" "+InfoDict["modelsupdoc"][0] except: SupDoc = u"" PageInfo = Version+Title+Parameters+Supplement+Fitting+Background +\ ModelDoc+SupDoc return PageInfo def GetPageInfo(self, Page): """ Needs a Page and gets all information from it """ Page.PlotAll("init") # A dictionary with headings as keys and lists of singletts/tuples as # values. If it is a tuple, it might me interesting for a table. InfoDict = dict() # Get Correlation corr = Page.corr # Get model information model = corr.fit_model parms = corr.fit_parameters fct = corr.fit_model.function.__name__ InfoDict["version"] = [Page.parent.version] Title = list() # The tool statistics relys on the string "filename/title". # Do not change it! if len(model[1]) == 0: # Prevent saving no title model[1] = "NoName" Title.append(["filename/title", Page.title]) Title.append(["Model species", model.components]) Title.append(["Model name", model.name]) Title.append(["Model ID", str(model.id)]) Title.append(["Model function", fct]) Title.append(["Page number", Page.counter[1:-2]]) # Parameters Parameters = list() # Use this function to determine human readable parameters, if possible Units, Newparameters = mdls.GetHumanReadableParms(model.id, parms) # Add Parameters for i in np.arange(len(parms)): Parameters.append([Units[i], Newparameters[i]]) InfoDict["parameters"] = Parameters # Add some more information if available # Info is a dictionary or None MoreInfo = mdls.GetMoreInfo(model.id, Page) if MoreInfo is not None: InfoDict["supplement"] = MoreInfo # Try to get the dictionary entry of a model try: # This function should return all important information # that can be calculated from the given parameters. func_info = mdls.supplement[model.id] except KeyError: # No information available pass else: InfoDict["modelsupdoc"] = [func_info.__doc__] # Fitting if hasattr(corr, "fit_results"): Fitting = list() weightedfit = corr.fit_results["weighted fit"] if corr.correlation is not None: # Mode AC vs CC if corr.is_cc: Title.append(["Type AC/CC", "Cross-correlation"]) else: Title.append(["Type AC/CC", "Autocorrelation"]) if "chi2" in corr.fit_results: Fitting.append([u"χ²", corr.fit_results["chi2"]]) if weightedfit: try: Fitting.append( ["Weighted fit", corr.fit_results["weighted fit type"]]) except KeyError: Fitting.append( ["Weighted fit", u""+Page.Fitbox[1].GetValue()]) if "chi2 type" in corr.fit_results: ChiSqType = corr.fit_results["chi2 type"] else: ChiSqType = "unknown" Fitting.append([u"χ²-type", ChiSqType]) Fitting.append( ["Algorithm", fit.Algorithms[corr.fit_algorithm][1]]) if len(Page.GlobalParameterShare) != 0: shared = str(Page.GlobalParameterShare[0]) for item in Page.GlobalParameterShare[1:]: shared += ", "+str(item) Fitting.append(["Shared parameters with Pages", shared]) if "weighted fit bins" in corr.fit_results: Fitting.append( ["Std. channels", 2*corr.fit_results["weighted fit bins"]+1]) # Fitting range: t1 = 1.*corr.lag_time[corr.fit_ival[0]] t2 = 1.*corr.lag_time[corr.fit_ival[1]-1] Fitting.append(["Ival start [ms]", "%.4e" % t1]) Fitting.append(["Ival end [ms]", "%.4e" % t2]) # Fittet parameters try: fitparmsid = corr.fit_results["fit parameters"] except: fitparmsid = corr.fit_parameters_variable fitparms = np.array(corr.fit_model.parameters[0])[fitparmsid] fitparms_short = [f.split()[0] for f in fitparms] fitparms_short = u", ".join(fitparms_short) Fitting.append(["Fit parm.", fitparms_short]) # global fitting for key in corr.fit_results.keys(): if key.startswith("global"): Fitting.append( [key.capitalize(), corr.fit_results[key]]) # Fit errors if "fit error estimation" in corr.fit_results: errors = corr.fit_results["fit error estimation"] for err, par in zip(errors, fitparms): nam, val = mdls.GetHumanReadableParameterDict( model.id, [par], [err]) Fitting.append(["Err "+nam[0], val[0]]) InfoDict["fitting"] = Fitting # Normalization parameter id to name if corr.normparm is None: normparmtext = "None" elif corr.normparm < len(corr.fit_parameters): normparmtext = corr.fit_model.parameters[0][corr.normparm] else: # supplementary parameters supnum = corr.normparm - len(corr.fit_parameters) normparmtext = MoreInfo[supnum][0] Title.append(["Normalization", normparmtext]) # Background Background = list() if corr.is_cc: if len(corr.backgrounds) == 2: # Channel 1 Background.append(["bg name Ch1", corr.backgrounds[0].name]) Background.append(["bg rate Ch1 [kHz]", corr.backgrounds[0].countrate]) # Channel 2 Background.append(["bg name Ch2", corr.backgrounds[1].name]) Background.append(["bg rate Ch2 [kHz]", corr.backgrounds[1].countrate]) InfoDict["background"] = Background else: if len(corr.backgrounds) == 1: Background.append(["bg name", corr.backgrounds[0].name]) Background.append(["bg rate [kHz]", corr.backgrounds[0].countrate]) InfoDict["background"] = Background # Function doc string InfoDict["modeldoc"] = [corr.fit_model.description_long] InfoDict["title"] = Title return InfoDict class ShowInfo(wx.Frame): def __init__(self, parent): # parent is main frame self.parent = parent # Get the window positioning correctly pos = self.parent.GetPosition() pos = (pos[0]+100, pos[1]+100) wx.Frame.__init__(self, parent=self.parent, title="Info", pos=pos, style=wx.DEFAULT_FRAME_STYLE | wx.FRAME_FLOAT_ON_PARENT) # MYID # This ID is given by the parent for an instance of this class self.MyID = None # Page self.Page = self.parent.notebook.GetCurrentPage() # Size initial_size = wx.Size(650, 700) initial_sizec = (initial_size[0]-6, initial_size[1]-30) self.SetMinSize(wx.Size(200, 200)) self.SetSize(initial_size) # Content self.panel = wx.Panel(self) self.control = wx.TextCtrl(self.panel, style=wx.TE_MULTILINE, size=initial_sizec) self.control.SetEditable(False) font1 = wx.Font(10, wx.MODERN, wx.NORMAL, wx.NORMAL, False, u'Monospace') self.control.SetFont(font1) btncopy = wx.Button(self.panel, wx.ID_CLOSE, 'Copy to clipboard') self.Bind(wx.EVT_BUTTON, self.OnCopy, btncopy) self.topSizer = wx.BoxSizer(wx.VERTICAL) self.topSizer.Add(btncopy) self.topSizer.Add(self.control) self.panel.SetSizer(self.topSizer) self.topSizer.Fit(self) # Icon if parent.MainIcon is not None: wx.Frame.SetIcon(self, parent.MainIcon) self.Show(True) wx.EVT_SIZE(self, self.OnSize) self.Content() def Content(self): # Fill self.control with content. # Parameters and models if self.parent.notebook.GetPageCount() == 0: self.control.SetValue("") self.panel.Disable() return self.panel.Enable() Page = self.Page InfoMan = InfoClass(CurPage=Page) PageInfo = InfoMan.GetCurFancyInfo() self.control.SetValue(PageInfo) def OnClose(self, event=None): self.parent.toolmenu.Check(self.MyID, False) self.parent.ToolsOpen.__delitem__(self.MyID) self.Destroy() def OnCopy(self, event): if not wx.TheClipboard.IsOpened(): clipdata = wx.TextDataObject() clipdata.SetText(self.control.GetValue()) wx.TheClipboard.Open() wx.TheClipboard.SetData(clipdata) wx.TheClipboard.Close() else: print("Other application has lock on clipboard.") def OnPageChanged(self, page=None, trigger=None): """ This function is called, when something in the panel changes. The variable `trigger` is used to prevent this function from being executed to save stall time of the user. Forr a list of possible triggers, see the doc string of `tools`. """ if trigger in ["parm_batch", "fit_batch", "page_add_batch"]: return # When parent changes self.Page = page self.Content() def OnSize(self, event): size = event.GetSize() sizec = wx.Size(size[0]-5, size[1]-30) self.panel.SetSize(size) self.control.SetSize(sizec) pycorrfit-1.1.7/pycorrfit/gui/tools/overlaycurves.py0000664000372000037200000003710113554642611023644 0ustar travistravis00000000000000"""Module tools - overlaycurves Let the user choose which correlation curves to use. Contains wrappers for file import and tools. """ import platform from matplotlib import cm import numpy as np import wx import wx.lib.plot as plot from .. import edclasses from .. import misc # Menu entry name MENUINFO = ["&Overlay curves", "Select experimental curves."] class Wrapper_OnImport(object): """ Wrapper for import function. parent: wx.Frame curvedict: dictionary with curves onselected: external function that is called with two arguments: *kept keys* and *unwanted keys* as lists referring to curvedict. selkeys: preselected values for curves in curvedict """ def __init__(self, parent, curvedict, onselected, selkeys=None, labels=None): self.onselected = onselected self.parent = parent self.Selector = UserSelectCurves(parent, curvedict, wrapper=self, selkeys=selkeys, labels=labels) self.Selector.Show() self.parent.Disable() self.Selector.Bind(wx.EVT_CLOSE, self.OnClose) def OnClose(self, event=None): self.parent.Enable() self.Selector.Destroy() def OnResults(self, keyskeep, keysrem): """ Here we will close (or disable?) pages that are not wanted by the user. It is important that we do not close pages that do not contain any experimental data (Page.dataeyp is None), because we ignored those pages during import. """ self.OnClose() self.onselected(keyskeep, keysrem) class Wrapper_Tools(object): def __init__(self, parent): """ Wrapper for tools menu. Gets curvedict from parent and starts curve selection. See *UserSelectCurves* class. """ # parent is the main frame of PyCorrFit self.parent = parent # MYID # This ID is given by the parent for an instance of this class self.MyID = None # Wrapping curvedict, labels = self.GetCurvedict() self.labels = labels self.Selector = UserSelectCurves(parent, curvedict, wrapper=self, labels=labels) # This is necessary for parent to deselect and select the tool # in the tools menu. self.Bind = self.Selector.Bind if self.parent.notebook.GetPageCount() == 0: self.Selector.sp.Disable() def Disable(self): self.Selector.Disable() def Enable(self, par=True): self.Selector.Enable(par) def GetCurvedict(self, e=None): curvedict = dict() labels = dict() N = self.parent.notebook.GetPageCount() for i in np.arange(N): Page = self.parent.notebook.GetPage(i) key = Page.counter if Page.corr.correlation is not None: curve = 1*Page.corr.correlation_plot curvedict[key] = curve labels[key] = Page.tabtitle.GetValue() return curvedict, labels def OnClose(self, event=None): # This is a necessary function for PyCorrFit. # Do not change it. self.parent.toolmenu.Check(self.MyID, False) self.parent.ToolsOpen.__delitem__(self.MyID) self.Selector.Destroy() def OnPageChanged(self, page=None, trigger=None): """ This function is called, when something in the panel changes. The variable `trigger` is used to prevent this function from being executed to save stall time of the user. Forr a list of possible triggers, see the doc string of `tools`. """ if trigger in ["parm_batch", "fit_batch", "page_add_batch", "tab_init", "tab_browse"]: return # When parent changes # This is a necessary function for PyCorrFit. # This is stuff that should be done when the active page # of the notebook changes. if self.parent.notebook.GetPageCount() == 0: self.Selector.SelectBox.SetItems([]) self.Selector.sp.Disable() else: oldlabels = self.Selector.curvelabels self.Selector.sp.Enable() # Sticky behavior cleaned up in 0.7.8 curvedict, labels = self.GetCurvedict() self.Selector.curvedict = curvedict self.Selector.labels = labels self.Selector.ProcessDict() self.labels = labels if oldlabels != self.Selector.curvelabels: self.Selector.SelectBox.SetItems( self.Selector.curvelabels) for i in np.arange(len(self.Selector.curvekeys)): self.Selector.SelectBox.SetSelection(i) self.Selector.OnUpdatePlot(trigger=trigger) def OnResults(self, keyskeep, keysrem): """ Here we will close (or disable?) pages that are not wanted by the user. It is important that we do not close pages that do not contain any experimental data (Page.dataeyp is None), because we ignored those pages during import. """ if len(keysrem) == 0: self.OnClose() return # warn the user! # First make a list of all pages that need to be removed and then # delete those pages. overtext = "Keep only pages in this list?" textlist = "" for key in keyskeep: textlist += "- "+key+" "+self.labels[key]+"\n" dlg = edclasses.MyScrolledDialog(self.parent, overtext, textlist, "Warning") if dlg.ShowModal() == wx.ID_OK: N = self.parent.notebook.GetPageCount() pagerem = list() for i in np.arange(N): Page = self.parent.notebook.GetPage(i) key = Page.counter if keysrem.count(key) == 1: pagerem.append(Page) for Page in pagerem: j = self.parent.notebook.GetPageIndex(Page) self.parent.notebook.DeletePage(j) self.OnPageChanged() dlg.Destroy() def OnSelectionChanged(self, keylist, trigger=None): if len(keylist) == 0: return # integer type list with page number pagelist = list() N = self.parent.notebook.GetPageCount() for i in np.arange(N): Page = self.parent.notebook.GetPage(i) key = Page.counter if keylist.count(key) == 1: pagelist.append(int(key.strip("#: "))) # Get open tools toolkeys = self.parent.ToolsOpen.keys() if len(toolkeys) == 0: return # Fill string = misc.parsePagenum2String(pagelist) for key in toolkeys: tool = self.parent.ToolsOpen[key] try: tool.SetPageNumbers(string) except: # tool does not have this function and hence does not # need numbers. pass class UserSelectCurves(wx.Frame): # This tool is derived from a wx.frame. def __init__(self, parent, curvedict, wrapper=None, selkeys=None, labels=None): """ *curvedict* is a dictionary that contains the curves. Keys serve as identifiers in the curve selection. e.g. curvelist["#1:"] = np.array[ np.array[0.0,1], np.array[0.0,.971] ...] *parent* is the main frame *wrapper* is the object to which the chosen keys are given back. If it is not None, it must provide a function *OnResults*, accepting a list of keys as an argument. *selkeys* items in the list *curvedict* that are preelected. *labels* dictionary with same keys as *curvelist* - labels of the entries in the list. If none, the keys of *curvedict* will be used. """ super(UserSelectCurves, self).__init__() # parent is the main frame of PyCorrFit self.parent = parent self.wrapper = wrapper self.curvedict = curvedict self.selkeys = selkeys self.labels = labels # can be None self.curvelabels = None # filled by self.ProcessDict() if self.selkeys is not None: newselkeys = list() for item in self.selkeys: newselkeys.append(str(item)) self.selkeys = newselkeys # Get the window positioning correctly pos = self.parent.GetPosition() pos = (pos[0]+100, pos[1]+100) wx.Frame.__init__(self, parent=self.parent, title="Overlay curves", pos=pos, style=wx.DEFAULT_FRAME_STYLE | wx.FRAME_FLOAT_ON_PARENT, size=(800, 500)) # Pre-process self.ProcessDict() # Content self.sp = wx.SplitterWindow( self, size=(500, 500), style=wx.SP_NOBORDER) self.sp.SetMinimumPaneSize(1) # Top panel panel_top = wx.Panel(self.sp, size=(500, 200)) self.upperSizer = wx.BoxSizer(wx.VERTICAL) if platform.system().lower() == 'darwin': ctrl = "Apple" else: ctrl = "Ctrl" text = "By holding down the '"+ctrl+"' key, single curves can be \n" +\ "selected or deselected. The 'Shift' key can be used \n" +\ "to select groups." self.upperSizer.Add(wx.StaticText(panel_top, label=text)) # Bottom Panel self.bottom_sp = wx.SplitterWindow( self.sp, size=(500, 300), style=wx.SP_NOBORDER) self.bottom_sp.SetMinimumPaneSize(1) sizepanelx = 250 panel_bottom = wx.Panel(self.bottom_sp, size=(sizepanelx, 300)) self.boxSizer = wx.BoxSizer(wx.VERTICAL) # Box selection style = wx.LB_EXTENDED self.SelectBox = wx.ListBox(panel_bottom, size=(sizepanelx, 300), style=style, choices=self.curvelabels) for i in np.arange(len(self.curvekeys)): self.SelectBox.SetSelection(i) # Deselect keys that are not in self.selkeys if self.selkeys is not None: for i in np.arange(len(self.curvekeys)): if self.selkeys.count(self.curvekeys[i]) == 0: self.SelectBox.Deselect(i) self.Bind(wx.EVT_LISTBOX, self.OnUpdatePlot, self.SelectBox) self.boxSizer.Add(self.SelectBox, wx.EXPAND) minsx = self.boxSizer.GetMinSize()[0] # Button REMOVE btnrem = wx.Button(panel_bottom, wx.ID_ANY, 'Remove selected') self.Bind(wx.EVT_BUTTON, self.OnPushResultsRemove, btnrem) btnrem.SetMinSize((minsx, -1)) self.boxSizer.Add(btnrem) # Button KEEP btnkep = wx.Button(panel_bottom, wx.ID_ANY, 'Keep selected') self.Bind(wx.EVT_BUTTON, self.OnPushResultsKeep, btnkep) self.boxSizer.Add(btnkep) btnkep.SetMinSize((minsx, -1)) # Button CANCEL btncancel = wx.Button(panel_bottom, wx.ID_ANY, 'Cancel') self.Bind(wx.EVT_BUTTON, self.OnCancel, btncancel) self.boxSizer.Add(btncancel) btncancel.SetMinSize((minsx, -1)) # Finish off sizers panel_top.SetSizer(self.upperSizer) panel_bottom.SetSizer(self.boxSizer) self.upperSizer.Fit(panel_top) self.boxSizer.Fit(panel_bottom) minsize = (np.array(self.boxSizer.GetMinSize()) + np.array(self.upperSizer.GetMinSize()) + np.array((300, 30))) self.SetMinSize(minsize) # self.SetSize(minsize) #self.SetMaxSize((9999, self.boxSizer.GetMinSize()[1])) # Canvas self.canvas = plot.PlotCanvas(self.bottom_sp) self.canvas.logScale = (True, False) self.canvas.enableZoom = True # Splitter window self.bottom_sp.SplitVertically(panel_bottom, self.canvas, sizepanelx) sizetoppanel = self.upperSizer.GetMinSize()[1] self.sp.SplitHorizontally(panel_top, self.bottom_sp, sizetoppanel) self.OnUpdatePlot() # Icon if parent.MainIcon is not None: wx.Frame.SetIcon(self, parent.MainIcon) self.Show(True) def GetSelection(self): keyssel = list() for i in self.SelectBox.GetSelections(): keyssel.append(self.curvekeys[i]) keysnosel = list() for key in self.curvekeys: if keyssel.count(key) == 0: keysnosel.append(key) return keyssel, keysnosel def ProcessDict(self, e=None): # Define the order of keys used. # We want to sort the keys, such that #10: is not before #1: self.curvekeys = list(self.curvedict.keys()) # Sorting key function applied to each key before sorting: def page_num(counter): return int( counter.strip().strip(":").strip("#")) try: for item in self.curvekeys: page_num(item) except: def fstr(x): return x else: fstr = page_num self.curvekeys.sort(key=fstr) if self.labels is None: self.curvelabels = self.curvekeys else: # Use given labels instead of curvekeys. self.curvelabels = list() for key in self.curvekeys: self.curvelabels.append( "#"+str(key).strip(":# ")+" "+self.labels[key]) def OnCancel(self, e=None): """ Close the tool """ self.wrapper.OnClose() def OnPushResultsRemove(self, e=None): # Get keys from selection keysrem, keyskeep = self.GetSelection() self.wrapper.OnResults(keyskeep, keysrem) def OnPushResultsKeep(self, e=None): # Get keys from selection keyskeep, keysrem = self.GetSelection() self.wrapper.OnResults(keyskeep, keysrem) def OnUpdatePlot(self, e=None, trigger=None): """ What should happen when the selection in *self.SelectBox* is changed? This function will alsy try to call the function *self.parent.OnSelectionChanged* and hand over the list of currently selected curves. This is an addon for 0.7.8 where we will control the page selection in the average tool. If `trigger` is something that occurs during loading of data, then we will not replot everything. """ # if e is not None and e.GetEventType() == 10007: # return # Get selected curves curves = list() legends = list() selection = self.SelectBox.GetSelections() for i in selection: curves.append(self.curvedict[self.curvekeys[i]]) legends.append(self.curvekeys[i]) # Set color map cmap = cm.get_cmap("gist_rainbow") # Clear Plot self.canvas.Clear() # Draw Plot lines = list() for i in np.arange(len(curves)): color = cmap(1.*i/(len(curves)), bytes=True) color = wx.Colour(color[0], color[1], color[2]) line = plot.PolyLine(curves[i], legend=legends[i], colour=color, width=1) lines.append(line) self.canvas.enableLegend = True if len(curves) != 0: self.canvas.Draw(plot.PlotGraphics(lines, xLabel=u'lag time τ [ms]', yLabel=u'G(τ)')) # This is an addon for 0.7.8 keyskeep = list() for i in self.SelectBox.GetSelections(): keyskeep.append(self.curvekeys[i]) try: self.wrapper.OnSelectionChanged(keyskeep, trigger=trigger) except: pass pycorrfit-1.1.7/pycorrfit/gui/__init__.py0000664000372000037200000000000013554642611021316 0ustar travistravis00000000000000pycorrfit-1.1.7/pycorrfit/gui/frontend.py0000664000372000037200000024017313554642611021417 0ustar travistravis00000000000000"""PyCorrFit - Module "frontend" The frontend displays the GUI (Graphic User Interface). All necessary functions and modules are called from here. """ from distutils.version import LooseVersion # For version checking import os import pathlib import platform import sys import traceback import warnings import webbrowser import numpy as np import wx.adv import wx.lib.agw.flatnotebook as fnb import wx.py.shell from pycorrfit import models as mdls from pycorrfit import openfile as opf from pycorrfit import readfiles from pycorrfit import meta # PyCorrFit modules from . import contribute from . import doc from . import edclasses from . import misc from . import page from . import plotting from . import tools from . import update from . import usermodel ######################################################################## class ExceptionDialog(wx.MessageDialog): """""" def __init__(self, msg): """Constructor""" wx.MessageDialog.__init__(self, None, msg, "Error", wx.OK | wx.ICON_ERROR) ######################################################################## class FlatNotebook(fnb.FlatNotebook): """ Flatnotebook class """ def __init__(self, parent): """Constructor""" style = fnb.FNB_SMART_TABS | fnb.FNB_NO_NAV_BUTTONS |\ fnb.FNB_DROPDOWN_TABS_LIST | fnb.FNB_NODRAG |\ fnb.FNB_TABS_BORDER_SIMPLE |\ fnb.FNB_X_ON_TAB | fnb.FNB_NO_X_BUTTON # Bugfix for Mac if platform.system().lower() in ["windows", "linux"]: style = style | fnb.FNB_HIDE_ON_SINGLE_TAB self.fnb = fnb.FlatNotebook.__init__(self, parent, wx.ID_ANY, agwStyle=style) class MyApp(wx.App): def __init__(self, args): wx.App.__init__(self, args) # Suppress WXDEBUG assertions, as happens by default with wx2.8. # http://anonscm.debian.org/cgit/collab-maint/wx-migration-tools.git/tree/README#n30 self.SetAssertMode(wx.APP_ASSERT_SUPPRESS) def MacOpenFile(self, filename): """ """ if filename.endswith(".pcfs"): stri = self.frame.OnClearSession() if stri == "clear": self.frame.OnOpenSession(sessionfile=filename) elif filename.endswith(".txt"): self.frame.OnAddModel(modfile=filename) else: self.frame.OnLoadBatch(dataname=filename) ########################################################### class MyFrame(wx.Frame): def __init__(self, parent, anid, version): sys.excepthook = MyExceptionHook # Set initial variables that make sense self.version = version # Init of the superClass super(MyFrame, self).__init__( parent, anid, "PyCorrFit " + self.version) self.CreateStatusBar() # A Statusbar in the bottom of the window self.StatusBar.SetStatusText("Find help and updates online:" + " 'Help > Update'") # Properties of the Frame initial_size = (768, 700) self.SetSize(initial_size) self.SetMinSize(initial_size) # Set this, so we know in which directory we are working in. # This will change, when we load a session or data file. self.dirname = os.curdir self.filename = None # Session Comment - may be edited and saved later self.SessionComment = "This is a session comment. It will be saved" +\ " as the session is saved." # Set variables # The model module that can be changed by importing user defined # functions. # These are only for compatibility. # value_set and valuedict only for compatibility! # I should use mdls for anything, since it's globally imported # and modified by this program (e.g. adding new function) self.value_set = mdls.values self.valuedict = mdls.valuedict # Tab Counter self.tabcounter = 1 # Background Correction List # Contains instances of `Trace` self.Background = list() # A dictionary for all the opened tool windows self.ToolsOpen = dict() # A dictionary for all the tools self.Tools = dict() # Range selector (None if inactive) # Fitting parameter range selection # New as of 0.7.9 self.RangeSelector = None # Setting up the menus. # models, modeldict, modeltypes only for compatibility! # I should use mdls for anything, since it's globally imported # and modified by this program (e.g. adding new function) self.models = mdls.models self.modeldict = mdls.modeldict self.modeltypes = mdls.modeltypes self.modelmenudict = dict() self.MakeMenu() # Create the Flatnotebook (Tabs Tabs Tabs!) panel = wx.Panel(self) self.panel = panel self.notebook = FlatNotebook(panel) self.notebook.SetRightClickMenu(self.curmenu) # self.notebook.SetAGWWindowStyleFlag(FNB_X_ON_TAB) sizer = wx.BoxSizer(wx.VERTICAL) sizer.Add(self.notebook, 1, wx.ALL | wx.EXPAND, 5) panel.SetSizer(sizer) self.Layout() self.Centre() self.Show() # Notebook Handler self.notebook.Bind(fnb.EVT_FLATNOTEBOOK_PAGE_CLOSED, self.OnFNBClosedPage) self.notebook.Bind(fnb.EVT_FLATNOTEBOOK_PAGE_CHANGED, self.OnFNBPageChanged) # This is a hack since version 0.7.7: # When the "X"-button on a page is pressed, ask the user # if he really wants to close that page. self.notebook._pages.Unbind(wx.EVT_LEFT_UP) self.notebook._pages.Bind(wx.EVT_LEFT_UP, self.OnMyLeftUp) # If user hits the "x", ask if he wants to save the session self.Bind(wx.EVT_CLOSE, self.OnExit) # Set window icon try: self.MainIcon = misc.getMainIcon() wx.Frame.SetIcon(self, self.MainIcon) except: self.MainIcon = None # Display support dialog if hasattr(sys, "frozen"): self.OnContribute() def add_fitting_tab(self, event=None, modelid=None, counter=None, select=False): """ This function creates a new page inside the notebook. If the function is called from a menu, the modelid is known by the event. If not, the modelid should be specified by *modelid*. *counter* specifies which page number we should use for our new page. If it is None, we will simply use *self.tabcounter*. *event* - An event that has event.GetId() equal to a modelid *modelid* - optional, directly set the modelid *counter* - optional, set the "#" value of the page """ if modelid is None: # Get the model id from the menu modelid = event.GetId() # Give the new page focus select = True if counter is not None: # Set the tabcounter right, so the tabs are counted continuously. counterint = int(counter.strip().strip(":").strip("#")) self.tabcounter = max(counterint, self.tabcounter) modelid = int(modelid) counter = "#"+str(self.tabcounter)+": " # Get the model for the page together valuepack = mdls.valuedict[modelid] active_labels = valuepack[0] active_values = 1*valuepack[1] active_fitting = 1*valuepack[2] active_parms = [active_labels, active_values, active_fitting] model = mdls.modeldict[modelid][1] # Create New Tab Newtab = page.FittingPanel(self, counter, modelid, active_parms) # self.Freeze() self.notebook.AddPage(Newtab, counter+model, select=select) if select: # A hack to have the last page displayed in the tab menu: Npag = self.notebook.GetPageCount() self.notebook.SetSelection(Npag-1) # self.Thaw() self.tabcounter = self.tabcounter + 1 # Enable the "Current" Menu self.EnableToolCurrent(True) # ####### # # This is a work-around to prevent a weird bug in version 0.7.8: # The statistics OnPageChanged function is called but the parameters # are displayed double if a new page is created and the statistics # window is open. # Find Tool Statistics # Get open tools #toolkeys = self.ToolsOpen.keys() # for key in toolkeys: # tool = self.ToolsOpen[key] # try: # if tool.MyName=="STATISTICS": # # Call the function properly. # tool.OnPageChanged(Newtab) # except: # pass return Newtab def EnableToolCurrent(self, enabled): """ Independent on order of menus, enable or disable tools and current menu. """ # Tools menu is now always enabled # tid = self.menuBar.FindMenu("&Tools") # self.menuBar.EnableTop(tid, enabled) cid = self.menuBar.FindMenu("Current &Page") self.menuBar.EnableTop(cid, enabled) def MakeMenu(self): self.filemenu = wx.Menu() # toolmenu and curmenu are public, because they need to be enabled/ # disabled when there are tabs/notabs. self.toolmenu = wx.Menu() # curmenu needs to be public, because we want to call it from the right # click menu of a Page in fnb self.curmenu = wx.Menu() modelmenu = wx.Menu() prefmenu = wx.Menu() helpmenu = wx.Menu() # wx.ID_ABOUT and wx.ID_EXIT are standard IDs provided by wxWidgets. # self.filemenu menuAddModel = self.filemenu.Append(wx.ID_ANY, "&Import model", "Add a user defined model.") menuLoadBatch = self.filemenu.Append(wx.ID_ANY, "&Load data\tCtrl+O", "Loads one or multiple data files") menuOpen = self.filemenu.Append(wx.ID_OPEN, "&Open session\tCtrl+Shift+O", "Restore a previously saved session") self.filemenu.AppendSeparator() self.menuComm = self.filemenu.Append(wx.ID_ANY, "Co&mment session", "Add a comment to this session", kind=wx.ITEM_CHECK) self.filemenu.Check(self.menuComm.GetId(), False) menuClear = self.filemenu.Append(wx.ID_ANY, "&Clear session\tAlt+C", "Remove all pages but keep imported model functions.") menuSave = self.filemenu.Append(wx.ID_SAVE, "&Save session\tCtrl+S", "Save entire Session") self.filemenu.AppendSeparator() menuExit = self.filemenu.Append(wx.ID_EXIT, "E&xit\tCtrl+Q", "Terminate the program") # prefmenu self.MenuUseLatex = prefmenu.Append(wx.ID_ANY, "Use Latex", "Enables/Disables usage of Latex for image saving.", kind=wx.ITEM_CHECK) self.MenuVerbose = prefmenu.Append(wx.ID_ANY, "Verbose mode", "Enables/Disables output of additional information.", kind=wx.ITEM_CHECK) self.MenuShowWeights = prefmenu.Append(wx.ID_ANY, "Show weights", "Enables/Disables displaying weights of fit.", kind=wx.ITEM_CHECK) self.MenuShowWeights.Check() self.MenuAutocloseTools = prefmenu.Append(wx.ID_ANY, "Autoclose tools", "Automatically close tools after usage.", kind=wx.ITEM_CHECK) # toolmenu toolkeys = list(tools.ToolDict.keys()) toolkeys.sort() for ttype in toolkeys: for tool in np.arange(len(tools.ToolDict[ttype])): menu = self.toolmenu.Append(wx.ID_ANY, tools.ToolName[ttype][tool][0], tools.ToolName[ttype][tool][1], kind=wx.ITEM_CHECK) self.toolmenu.Check(menu.GetId(), False) # Append tool to list of tools with menu ID self.Tools[menu.GetId()] = tools.ToolDict[ttype][tool] # Bindings # On tool only needs the Id of the wx.EVT_MENU self.Bind(wx.EVT_MENU, self.OnTool, menu) if ttype != toolkeys[-1]: self.toolmenu.AppendSeparator() # curmenu menuImportData = self.curmenu.Append(wx.ID_ANY, "&Import Data", "Import experimental FCS curve") menuSaveData = self.curmenu.Append(wx.ID_ANY, "&Save data (*.csv)", "Save data (comma separated values)") menuSavePlotCorr = self.curmenu.Append(wx.ID_ANY, "&Save correlation as image", "Export current plot as image.") menuSavePlotTrace = self.curmenu.Append(wx.ID_ANY, "&Save trace as image", "Export current trace as image.") self.curmenu.AppendSeparator() menuClPa = self.curmenu.Append(wx.ID_ANY, "&Close Page", "Close Current Page") # model menu # Integrate models into menu keys = mdls.modeltypes.keys() keys = sorted(list(keys)) for modeltype in keys: # Now we have selected a type of model # Create a submenu submenu = wx.Menu() modelmenu.Append(wx.ID_ANY, modeltype, submenu) # Append to menulist self.modelmenudict[modeltype] = submenu for modelid in mdls.modeltypes[modeltype]: # Now we add every model that belongs to that type model = mdls.modeldict[modelid] if platform.system().lower() == "darwin" and hasattr(sys, 'frozen'): ### # Work-around for freezed mac version ### # (strange UTF-8 decoding error, # would work with misc.removewrongUTF8) b = model[1].split("(")[0].strip() c = misc.removewrongUTF8(model[2]) menuentry = submenu.Append(model[0], b, c) else: menuentry = submenu.Append(model[0], model[1], model[2]) self.Bind(wx.EVT_MENU, self.add_fitting_tab, menuentry) # help menu menuContribute = helpmenu.Append(wx.ID_ANY, "&Contribute", "How to contribute to PyCorrFit") menuDocu = helpmenu.Append(wx.ID_ANY, "&Documentation", "PyCorrFit documentation") menuWiki = helpmenu.Append(wx.ID_ANY, "&Wiki", "PyCorrFit wiki pages by users for users (online)") menuUpdate = helpmenu.Append(wx.ID_ANY, "&Update", "Check for new version" + " (Web access required)") helpmenu.AppendSeparator() menuShell = helpmenu.Append(wx.ID_ANY, "S&hell", "A Python shell") helpmenu.AppendSeparator() menuSoftw = helpmenu.Append(wx.ID_ANY, "&Software", "Information about the software used") menuAbout = helpmenu.Append(wx.ID_ABOUT, "&About", "Information about this program") # Create the menubar. self.menuBar = wx.MenuBar() # Adding all the menus to the MenuBar self.menuBar.Append(self.filemenu, "&File") self.menuBar.Append(self.toolmenu, "&Tools") self.menuBar.Append(self.curmenu, "Current &Page") self.menuBar.Append(modelmenu, "&Model") self.menuBar.Append(prefmenu, "&Preferences") self.menuBar.Append(helpmenu, "&Help") # Adding the MenuBar to the Frame content. self.SetMenuBar(self.menuBar) self.EnableToolCurrent(False) # Set events # File #self.Bind(wx.EVT_MENU, self.OnLoadSingle, menuLoadSingle) self.Bind(wx.EVT_MENU, self.OnLoadBatch, menuLoadBatch) self.Bind(wx.EVT_MENU, self.OnAddModel, menuAddModel) self.Bind(wx.EVT_MENU, self.OnCommSession, self.menuComm) self.Bind(wx.EVT_MENU, self.OnClearSession, menuClear) self.Bind(wx.EVT_MENU, self.OnOpenSession, menuOpen) self.Bind(wx.EVT_MENU, self.OnSaveSession, menuSave) self.Bind(wx.EVT_MENU, self.OnExit, menuExit) # Current self.Bind(wx.EVT_MENU, self.OnImportData, menuImportData) self.Bind(wx.EVT_MENU, self.OnSaveData, menuSaveData) self.Bind(wx.EVT_MENU, self.OnSavePlotCorr, menuSavePlotCorr) self.Bind(wx.EVT_MENU, self.OnSavePlotTrace, menuSavePlotTrace) self.Bind(wx.EVT_MENU, self.OnDeletePage, menuClPa) # Preferences self.Bind(wx.EVT_MENU, self.OnLatexCheck, self.MenuUseLatex) # Help self.Bind(wx.EVT_MENU, self.OnSoftware, menuSoftw) self.Bind(wx.EVT_MENU, self.OnContribute, menuContribute) self.Bind(wx.EVT_MENU, self.OnAbout, menuAbout) self.Bind(wx.EVT_MENU, self.OnUpdate, menuUpdate) self.Bind(wx.EVT_MENU, self.OnDocumentation, menuDocu) self.Bind(wx.EVT_MENU, self.OnWiki, menuWiki) self.Bind(wx.EVT_MENU, self.OnShell, menuShell) # Add the shortcuts # # For Tools first. toolsitems = self.toolmenu.GetMenuItems() avg = filter(lambda x: x.GetItemLabelText() == 'Average data', toolsitems).__next__() avg.SetItemLabel("{}\tAlt+A".format(avg.GetItemLabel())) bat = filter(lambda x: x.GetItemLabelText() == 'Batch control', toolsitems).__next__() bat.SetItemLabel("{}\tAlt+B".format(bat.GetItemLabel())) stat = filter(lambda x: x.GetItemLabelText() == 'Statistics view', toolsitems).__next__() stat.SetItemLabel("{}\tAlt+S".format(stat.GetItemLabel())) view = filter(lambda x: x.GetItemLabelText() == 'Trace view', toolsitems).__next__() view.SetItemLabel("{}\tAlt+V".format(view.GetItemLabel())) self.accel_tbl = wx.AcceleratorTable([ (wx.ACCEL_ALT, ord('A'), avg.GetId()), (wx.ACCEL_ALT, ord( 'B'), bat.GetId()), (wx.ACCEL_ALT, ord( 'V'), view.GetId()), (wx.ACCEL_ALT, ord( 'S'), stat.GetId()), (wx.ACCEL_CTRL, ord('O'), menuLoadBatch.GetId()), (wx.ACCEL_CTRL | wx.ACCEL_SHIFT, ord( 'O'), menuOpen.GetId()), (wx.ACCEL_CTRL, ord('S'), menuSave.GetId()), (wx.ACCEL_ALT, ord('C'), menuClear.GetId()), (wx.ACCEL_CTRL, ord( 'Q'), menuExit.GetId()) ]) self.SetAcceleratorTable(self.accel_tbl) def OnAbout(self, event=None): # Show About Information description = ("PyCorrFit is a data displaying, fitting " + "and evaluation tool \nfor fluorescence correlation " + "spectroscopy. \nPyCorrFit is written in Python.") licence = doc.licence() info = wx.adv.AboutDialogInfo() # info.SetIcon(wx.Icon('hunter.png', wx.BITMAP_TYPE_PNG)) info.SetName('PyCorrFit') info.SetVersion(self.version) info.SetDescription(description) info.SetCopyright(u'(C) 2011 - 2012 Paul Müller') info.SetWebSite(doc.HomePage) info.SetLicence(licence) info.SetIcon(misc.getMainIcon(pxlength=64)) info.AddDeveloper(u'Paul Müller') info.AddDocWriter(u'Thomas Weidemann, Paul Müller') wx.adv.AboutBox(info) def OnAddModel(self, event=None, modfile=None): """ Import a model from an external .txt file. See example model functions available on the web. """ # Add a model using the dialog. filters = "text file (*.txt)|*.txt" if modfile is None: dlg = wx.FileDialog(self, "Open model file", self.dirname, "", filters, wx.FD_OPEN) if dlg.ShowModal() == wx.ID_OK: # Workaround since 0.7.5 (dirname, filename) = os.path.split(dlg.GetPath()) #filename = dlg.GetFilename() #dirname = dlg.GetDirectory() self.dirname = dirname # Try to import a selected .txt file else: self.dirname = dlg.GetDirectory() dlg.Destroy() return else: dirname, filename = os.path.split(modfile) self.dirname = dirname NewModel = usermodel.UserModel(self) try: NewModel.GetCode(os.path.join(dirname, filename)) except NameError: # sympy is probably not installed # Warn the user text = ("SymPy not found.\n" + "In order to import user defined model\n" + "functions, please install Sympy\n" + "version 0.7.2 or higher.\nhttp://sympy.org/") if platform.system().lower() == 'linux': text += ("\nSymPy is included in the package:\n" + " 'python-sympy'") dlg = wx.MessageDialog(None, text, 'SymPy not found', wx.OK | wx.ICON_EXCLAMATION) dlg.ShowModal() return except: # The file does not seem to be what it seems to be. info = sys.exc_info() errstr = "Unknown file format:\n" errstr += str(filename)+"\n\n" errstr += str(info[0])+"\n" errstr += str(info[1])+"\n" for tb_item in traceback.format_tb(info[2]): errstr += tb_item dlg = wx.MessageDialog(self, errstr, "Error", style=wx.ICON_ERROR | wx.OK | wx.STAY_ON_TOP) dlg.ShowModal() return # Test the code for sympy compatibility. # If you write your own parser, this might be easier. try: NewModel.TestFunction() except: # This means that the imported model file could be # contaminated. Ask the user how to proceed. text = "The model parsing check raised an Error.\n" +\ "This could be the result of a wrong Syntax\n" +\ "or an error of the parser.\n" +\ "This might be dangerous. Procceed\n" +\ "only, if you trust the source of the file.\n" +\ "Try and import offensive file: "+filename+"?" dlg2 = wx.MessageDialog(self, text, "Unsafe Operation", style=wx.ICON_EXCLAMATION | wx.YES_NO | wx.STAY_ON_TOP) if dlg2.ShowModal() == wx.ID_YES: NewModel.ImportModel() else: return else: # The model was loaded correctly NewModel.ImportModel() self.dirname = dirname def OnClearSession(self, e=None, clearmodels=False): """ Clear the entire session Returns: "abort" if the user did not want to clear the session "clear" if the session has been cleared and the user did or did not save the session """ numtabs = self.notebook.GetPageCount() # Ask, if user wants to save current session. if numtabs > 0: dial = wx.MessageDialog(self, 'Do you wish to save this session first?', 'Save current session?', wx.ICON_QUESTION | wx.CANCEL | wx.YES_NO | wx.NO_DEFAULT) # dial = edclasses.MyYesNoAbortDialog(self, # 'Do you wish to save this session first?', # 'Save current session?') result = dial.ShowModal() dial.Destroy() if result == wx.ID_CANCEL: return "abort" # stop this function - do nothing. elif result == wx.ID_YES: filename = self.OnSaveSession() if filename is None: return "abort" elif result == wx.ID_NO: pass # Delete all the pages self.notebook.DeleteAllPages() # Disable all the dialogs and menus self.EnableToolCurrent(False) self.OnFNBPageChanged() self.tabcounter = 1 self.filename = None self.SetTitleFCS(None) self.SessionComment = "You may enter some information here." self.Background = list() # Do we want to keep user defined models after session clearing? if clearmodels == True: # Also reset user defined models for modelid in mdls.modeltypes["User"]: mdls.values.remove(mdls.valuedict[modelid]) del mdls.valuedict[modelid] mdls.models.remove(mdls.modeldict[modelid]) del mdls.modeldict[modelid] mdls.modeltypes["User"] = list() # Model Menu menu = self.modelmenudict["User"] for item in menu.GetMenuItems(): menu.RemoveItem(item) return "clear" def OnCommSession(self, e=None): """ Dialog for commenting on session. """ try: self.EditCommentDlg.IsEnabled() except AttributeError: # Dialog is not opened self.EditCommentDlg = tools.EditComment(self) self.EditCommentDlg.Bind(wx.EVT_CLOSE, self.EditCommentDlg.OnClose) self.filemenu.Check(self.menuComm.GetId(), True) except RuntimeError: del self.EditCommentDlg self.OnCommSession() else: # Close Dialog self.EditCommentDlg.OnClose() def OnContribute(self, event=None): # Show contribute dialog dlg = contribute.ContributeDialog(self) dlg.ShowModal() def OnDeletePage(self, event=None): """ This method is based on the flatnotebook demo It removes a page from the notebook """ # Ask the user if he really wants to delete the page. title = self.notebook.GetCurrentPage().tabtitle.GetValue() numb = self.notebook.GetCurrentPage().counter.strip().strip(":") text = "This will close page "+numb+"?\n"+title dlg = edclasses.MyOKAbortDialog(self, text, "Warning") if dlg.ShowModal() == wx.ID_OK: self.notebook.DeletePage(self.notebook.GetSelection()) self.OnFNBClosedPage() if self.notebook.GetPageCount() == 0: self.OnFNBPageChanged() def OnDocumentation(self, e=None): """ Get the documentation and view it with browser""" path = doc.GetLocationOfDocumentation() if path is None: # Now we have to tell the user that there is no documentation self.StatusBar.SetStatusText("...documentation not found.") else: path = pathlib.Path(path) self.StatusBar.SetStatusText("...documentation: {}".format(path)) if platform.system().lower() == 'windows': cmd = 'start /d "{}" {}'.format(path.parent, path.name) elif platform.system().lower() == 'linux': cmd = 'xdg-open "{}" &'.format(path) elif platform.system().lower() == 'darwin': cmd = 'open "{}" &'.format(path) else: # defaults to linux style: cmd = 'xdg-open "{}" &'.format(path) os.system(cmd) def OnExit(self, e=None): """ Kindly asks the user if he wants to save the session and then exit the program. """ numtabs = self.notebook.GetPageCount() # Ask, if user wants to save current session. if numtabs > 0: dial = wx.MessageDialog(self, 'Do you wish to save this session first?', 'Save current session?', wx.ICON_QUESTION | wx.CANCEL | wx.YES_NO | wx.NO_DEFAULT) result = dial.ShowModal() dial.Destroy() if result == wx.ID_CANCEL: return # stop this function - do nothing. elif result == wx.ID_YES: filename = self.OnSaveSession() if filename is None: # Do not exit. The user pressed abort in the session # saving dialog. return # Exit the Program self.Destroy() def OnFNBClosedPage(self, e=None): """ Called, when a page has been closed """ if self.notebook.GetPageCount() == 0: # Grey out tools self.EnableToolCurrent(False) def OnFNBPageChanged(self, e=None, Page=None, trigger=None): """ Called, when - Page focus switches to another Page - Page with focus changes significantly: - experimental data is loaded - weighted fit was done - trigger is a string. For more information read the doc strings of the `tools` submodule. """ if (e is not None and e.GetEventType() == fnb.EVT_FLATNOTEBOOK_PAGE_CHANGED.typeId and trigger is None): trigger = "tab_browse" # Get the Page if Page is None: Page = self.notebook.GetCurrentPage() keys = list(self.ToolsOpen.keys()) for key in keys: # Update the information self.ToolsOpen[key].OnPageChanged(Page, trigger=trigger) # parameter range selection tool for page. if self.RangeSelector is not None: try: self.RangeSelector.OnPageChanged(Page, trigger=trigger) except: pass # Bugfix-workaround for mac: # non-existing tabs are still displayed upon clearing session if platform.system().lower() == "darwin": if self.notebook.GetPageCount() == 0: self.notebook.Hide() else: self.notebook.Show() def OnImportData(self, e=None): """Import experimental data from all filetypes specified in *readfiles.filetypes_dict*. Is called by the curmenu and applies to currently opened model. Calls self.ImportData. """ # Open a data file # Get Data SupFiletypes = list(readfiles.filetypes_dict.keys()) SupFiletypes.sort() filters = "" for i in np.arange(len(SupFiletypes)): # Add to the filetype filter filters = filters+SupFiletypes[i] if i+1 != len(SupFiletypes): # Add a separator, but not behind the last entry # This is wx widgets stuff. filters = filters+"|" dlg = wx.FileDialog(self, "Open data file", self.dirname, "", filters, wx.FD_OPEN) if dlg.ShowModal() == wx.ID_OK: # The filename the page will get path = dlg.GetPath() # Workaround since 0.7.5 (self.dirname, self.filename) = os.path.split(path) #self.filename = dlg.GetFilename() #self.dirname = dlg.GetDirectory() try: Stuff = readfiles.open_any(self.dirname, self.filename) except: # The file format is not supported. info = sys.exc_info() errstr = "Unknown file format:\n" errstr += str(self.filename)+"\n\n" errstr += str(info[0])+"\n" errstr += str(info[1])+"\n" for tb_item in traceback.format_tb(info[2]): errstr += tb_item wx.MessageDialog(self, errstr, "Error", style=wx.ICON_ERROR | wx.OK | wx.STAY_ON_TOP) return else: dataexp = Stuff["Correlation"] trace = Stuff["Trace"] curvelist = Stuff["Type"] filename = Stuff["Filename"] if "Weight" in Stuff: Weight = Stuff["Weight"] WeightName = Stuff["Weight Name"] else: Weight = [None] * len(Stuff["Type"]) WeightName = [None] * len(Stuff["Type"]) # If curvelist is a list with more than one item, we are # importing more than one curve per file. Therefore, we # need to create more pages for this file. # # We want to give the user the possibility to choose from # several types of input functions. If curvelist contains # more than one type of data, like "AC1", "AC2", "CC1", ... # then the user may wish to only import "AC1" or "AC2" # functions. curvetypes = dict() for i in np.arange(len(curvelist)): try: curvetypes[curvelist[i]].append(i) except KeyError: curvetypes[curvelist[i]] = [i] # Now we have a dictionary curvetypes with keys that name # items in curvelist and which point to indices in curvelist. # We will display a dialog that let's the user choose what # to import. keys = curvetypes.keys() if len(keys) > 1: Chosen = tools.ChooseImportTypes(self, curvetypes) newcurvelist = list() newfilename = list() newdataexp = list() newtrace = list() newWeight = list() newWeightName = list() if Chosen.ShowModal() == wx.ID_OK: keys = Chosen.keys if len(keys) == 0: # do not do anything return for key in keys: # create a new curvelist with the selected curves for index in curvetypes[key]: newcurvelist.append(curvelist[index]) newfilename.append(filename[index]) newdataexp.append(dataexp[index]) newtrace.append(trace[index]) newWeight.append(Weight[index]) newWeightName.append(WeightName[index]) curvelist = newcurvelist filename = newfilename dataexp = newdataexp trace = newtrace Weight = newWeight WeightName = newWeightName else: return Chosen.Destroy() # curvelist is a list of numbers or labels that correspond # to each item in dataexp or trace. Each curvelist/filename # item will be converted to a string and then added to the # pages title. num = len(curvelist) # Show a nice progress dialog: style = wx.PD_REMAINING_TIME | wx.PD_SMOOTH | wx.PD_AUTO_HIDE |\ wx.PD_CAN_ABORT dlg = wx.ProgressDialog("Import", "Loading pages...", maximum=num, parent=self, style=style) # Get current page and populate CurPage = self.notebook.GetCurrentPage() for i in np.arange(num): # Fill Page with data self.ImportData(CurPage, dataexp[i], trace[i], curvetype=curvelist[i], filename=filename[i], weights=Weight[i], weight_type=WeightName[i], curveid=i) # Let the user abort, if he wants to: # We want to do this here before an empty page is added # to the notebok. if dlg.Update(i+1, "Loading pages...")[0] == False: dlg.Destroy() return if i+1 != num: # Create new page. # (Add n-1 pages while importing.) CurPage = self.add_fitting_tab(event=None, modelid=CurPage.corr.fit_model.id, counter=None) # We are finished here: dlg.Destroy() return else: # User pressed "Abort" - do nothing. self.dirname = dlg.GetDirectory() dlg.Destroy() return def OnMyLeftUp(self, event): """ Wrapper for LeftUp: We want to have a wrapper for the page closing event. The code was copied from "flatnotebook.py" Handles the ``wx.EVT_LEFT_UP`` event for L{PageContainer}. :param `event`: a `wx.MouseEvent` event to be processed. """ # Get the page container pc = self.notebook._pages # forget the zone that was initially clicked self._nLeftClickZone = fnb.FNB_NOWHERE where = pc.HitTest(event.GetPosition())[0] FNB_X = 2 FNB_TAB_X = 3 if not pc.HasAGWFlag(fnb.FNB_NO_TAB_FOCUS): # Make sure selected tab has focus self.SetFocus() if where == FNB_X: # Make sure that the button was pressed before if pc._nXButtonStatus != fnb.FNB_BTN_PRESSED: return pc._nXButtonStatus = fnb.FNB_BTN_HOVER self.OnDeletePage(self.notebook.GetCurrentPage()) elif where == FNB_TAB_X: # Make sure that the button was pressed before if pc._nTabXButtonStatus != fnb.FNB_BTN_PRESSED: return pc._nTabXButtonStatus = fnb.FNB_BTN_HOVER self.OnDeletePage(self.notebook.GetCurrentPage()) else: # Call what should have been called. pc.OnLeftUp(event) def ImportData(self, Page, dataexp, trace, curvetype="", filename="", curveid="", run="0", weights=None, weight_type=None, trigger=None): """ Import data into the current page. `trigger` is passed to PlotAll. For more info see the submodule `tools`. """ CurPage = Page # Set name of correlation CurPage.corr.filename = filename # Import traces. Traces are usually put into a list, even if there # is only one trace. The reason is, that for cross correlation, we # have two traces and thus we have to import both. if trace is not None: CurPage.corr.traces = trace # Import correlation function CurPage.corr.correlation = dataexp CurPage.corr.corr_type = curvetype CurPage.OnAmplitudeCheck() # It might be possible, that we want the channels to be # fixed to some interval. This is the case if the # checkbox on the "Channel selection" dialog is checked. # self.OnFNBPageChanged() # Enable Fitting Button CurPage.Fit_enable_fitting() # Set new tabtitle value and strip leading or trailing # white spaces. if run != "": title = "{} r{:03d}-{}".format(filename, int(run), curvetype) else: title = "{} id{:03d}-{}".format(filename, int(curveid), curvetype) CurPage.title = title # set weights if weights is not None: CurPage.corr.set_weights(weight_type, weights) List = CurPage.Fitbox[1].GetItems() if not weight_type in List: List.append(weight_type) CurPage.Fitbox[1].SetItems(List) CurPage.Fitbox[1].SetSelection(len(List)-1) else: listid = List.index(weight_type) CurPage.Fitbox[1].SetSelection(listid) # Plot everything CurPage.OnAmplitudeCheck() CurPage.PlotAll(trigger=trigger) # Call this function to allow the "Channel Selection" window that # might be open to update itself. # We are aware of the fact, that we just did that # self.OnFNBPageChanged() def OnLatexCheck(self, e): """ Checks if we can use latex. If not, create a pop-up window stating so. """ uselatex = self.MenuUseLatex.IsChecked() if uselatex == False: # do nothing return # Check if we can use latex for plotting: r1 = meta.find_program("latex")[0] r2 = meta.find_program("dvipng")[0] # Ghostscript r31 = meta.find_program("gs")[0] r32 = meta.find_program("mgs")[0] # from miktex r3 = max(r31, r32) if r1+r2+r3 < 3: # Warn the user if platform.system().lower() == 'windows': text = ("Latex plotting features will not work.\n" + "Please install MiKTeX.\n" + "http://miktex.org/") elif platform.system().lower() == 'darwin': text = ("Latex plotting features will not work.\n" + "Please install MacTeX.\n" + "http://tug.org/mactex/") else: text = ("Latex plotting features will not work.\n" + "Make sure you have these packages installed:\n" + " - dvipng\n" + " - ghostscript\n" + " - texlive-latex-base\n" + " - texlive-science\n") dlg = wx.MessageDialog(None, text, 'Latex not found', wx.OK | wx.ICON_EXCLAMATION) dlg.ShowModal() def OnLoadBatch(self, e=None, dataname=None): """ Open multiple data files and apply a single model to them We will create a new window where the user may decide which model to use. """ if dataname is None: # Browse the file system SupFiletypes = list(readfiles.filetypes_dict.keys()) # Sort them so we have "All suported filetypes" up front SupFiletypes.sort() filters = "" for i in np.arange(len(SupFiletypes)): # Add to the filetype filter filters = filters+SupFiletypes[i] if i+1 != len(SupFiletypes): # Add a separator if item is not last item filters = filters+"|" dlg = wx.FileDialog(self, "Open data files", self.dirname, "", filters, wx.FD_OPEN | wx.FD_MULTIPLE) if dlg.ShowModal() == wx.ID_OK: Datafiles = dlg.GetFilenames() # We rely on sorted filenames Datafiles.sort() # Workaround since 0.7.5 paths = dlg.GetPaths() if len(paths) != 0: self.dirname = os.path.split(paths[0])[0] else: self.dirname = dlg.GetDirectory() dlg.Destroy() else: dlg.Destroy() return else: Datafiles = list() if isinstance(dataname, list): for item in dataname: Datafiles.append(os.path.split(item)[1]) self.dirname = os.path.split(Datafiles[0])[0] else: Datafiles.append(os.path.split(dataname)[1]) self.dirname = os.path.split(dataname)[0] Datafiles.sort() # Get information from the data files and let the user choose # which type of curves to load and the corresponding model. # List of filenames that could not be opened BadFiles = list() Exceptions = list() # Lists for correlation, trace, type and names Correlation = list() Trace = list() Type = list() Filename = list() # there might be zipfiles with additional name info # Run = list() # Run number connecting AC1 AC2 CC12 CC21 Curveid = list() # Curve ID of each curve in a file Weight = list() WeightName = list() # Display a progress dialog for file import N = len(Datafiles) style = wx.PD_REMAINING_TIME | wx.PD_SMOOTH | wx.PD_AUTO_HIDE |\ wx.PD_CAN_ABORT dlgi = wx.ProgressDialog("Import", "Loading data...", maximum=N, parent=self, style=style) for j in np.arange(N): afile = Datafiles[j] # Let the user abort, if he wants to: if dlgi.Update(j, "Loading data: "+afile)[0] == False: dlgi.Destroy() return #Stuff = readfiles.openAny(self.dirname, afile) try: Stuff = readfiles.open_any(self.dirname, afile) except Exception as excpt: # The file does not seem to be what it seems to be. BadFiles.append(afile) Exceptions.append(excpt) # Print exception trb = traceback.format_exc(excpt) trb = "..." + trb.replace("\n", "\n...") warnings.warn("Problem processing a file." + " Reason:\n{}".format(trb)) else: for i in np.arange(len(Stuff["Type"])): Correlation.append(Stuff["Correlation"][i]) Trace.append(Stuff["Trace"][i]) Type.append(Stuff["Type"][i]) Filename.append(Stuff["Filename"][i]) if "Weight" in Stuff: Weight.append(Stuff["Weight"][i]) WeightName.append(Stuff["Weight Name"][i]) else: Weight.append(None) WeightName.append(None) # Curveid.append(str(i+1)) dlgi.Destroy() # Add number of the curve within a file. nameold = None counter = 1 for name in Filename: if name == nameold: Curveid.append(counter) counter += 1 else: counter = 1 nameold = name Curveid.append(counter) counter += 1 # If there are any BadFiles, we will let the user know. if len(BadFiles) > 0: # The file does not seem to be what it seems to be. errstr = "The following files could not be processed:\n" for item, excpt in zip(BadFiles, Exceptions): trb = traceback.format_exc(excpt) trb = " " + trb.replace("\n", "\n ") errstr += " " + item + "\n" + trb dlg = wx.MessageDialog(self, errstr, "Error", style=wx.ICON_WARNING | wx.OK | wx.CANCEL | wx.STAY_ON_TOP) if dlg.ShowModal() == wx.ID_CANCEL: return # Abort, if there are no curves left if len(Type) == 0: return # We want to give the user the possibility to choose from # several types of input functions. If curvelist contains # more than one type of data, like "AC1", "AC2", "CC1", ... # then the user may wish to only import "AC1" or "AC2" # functions. curvetypes = dict() for i in np.arange(len(Type)): try: curvetypes[Type[i]].append(i) except KeyError: curvetypes[Type[i]] = [i] # Fill in the Run information keys = list(curvetypes.keys()) # This part is a little tricky. We assume at some point, that different # types of curves (AC1, AC2) belong to the same run. The only possible # chek/assumtion that we can make is: # If all curvetypes have the same amount of curves, then the curves # from different curvetypes belong together. # Unfortunately, we do not know how the curves are ordered. It could # be like this: # AC1-r1, AC2-r1, CC12-r1, CC21-r1, AC1-r2, AC1-r2, ... # or otherwise interlaced like this: # AC1-r1, AC2-r1, AC1-r2, AC1-r2, ... , CC12-r1, CC21-r1, ... # What we do know is that the first occurence of AC1 matches up with # the first occurences of AC2, CC12, etc. # We create the list/array *Run* whose elements are the run-number # at the position of the curves in *Types*. # Check if the type of curves have equal length lentypes = np.zeros(len(keys), dtype=int) for i in range(len(keys)): lentypes[i] = len(curvetypes[keys[i]]) if len(np.unique(np.array(lentypes))) == 1 and lentypes[0] != 0: # Made sure that AC1 AC2 CC12 CC21 have same length # Create Runs such that they are matched. # We assume that the curves are somehow interlaced and that # the Nth occurence of the keys in Types correspond to the # matching curves. # Also make sure that number starts at one for each selected file. coords = np.zeros(len(keys), dtype=np.int) Run = np.zeros(len(Curveid), dtype=np.int) WorkType = 1*Type for fname in np.unique(Filename): # unique returns sorted file names. for i in range(Filename.count(fname) // len(keys)): for k in range(len(keys)): coords[k] = WorkType.index(keys[k]) WorkType[coords[k]] = None Run[coords] = i + 1 #del WorkType else: Run = [""] * len(Curveid) # Now we have a dictionary curvetypes with keys that name # items in *Type* and which point to indices in *Type*. # We will display a dialog that lets the user choose what # to import. keys = curvetypes.keys() # Start the dialog for choosing types and model functions labels = list() for i in np.arange(len(Filename)): if Run[i] != "": labels.append("{} r{:03d}-{}".format(Filename[i], Run[i], Type[i])) else: labels.append("{} id{:03d}-{}".format(Filename[i], Curveid[i], Type[i])) Chosen = tools.ChooseImportTypesModel(self, curvetypes, Correlation, labels=labels) newCorrelation = list() newTrace = list() newType = list() newFilename = list() modelList = list() newCurveid = list() newRun = list() newWeight = list() newWeightName = list() if Chosen.ShowModal() == wx.ID_OK: keys = Chosen.typekeys # Keepdict is a list of indices pointing to Type or Correlation # of curves that are supposed to be kept. keepcurvesindex = Chosen.keepcurvesindex # modelids is a dictionary with chosen modelids. # The keys of modelids are indices in the *Type* etc. lists. modelids = Chosen.modelids if len(keys) == 0: # do not do anything return for key in keys: # create a new curvelist with the selected curves for index in curvetypes[key]: if keepcurvesindex.count(index) == 1: newCorrelation.append(Correlation[index]) newTrace.append(Trace[index]) newType.append(Type[index]) newFilename.append(Filename[index]) modelList.append(modelids[index]) newCurveid.append(Curveid[index]) newRun.append(Run[index]) newWeight.append(Weight[index]) newWeightName.append(WeightName[index]) Correlation = newCorrelation Trace = newTrace Type = newType Filename = newFilename Curveid = newCurveid Run = newRun Weight = newWeight WeightName = newWeightName else: return Chosen.Destroy() # Import the data into new pages # curvelist is a list of numbers or labels that correspond # to each item in dataexp or trace. Each curvelist/filename # item will be converted to a string and then added to the # pages title. num = len(Type) # Show a nice progress dialog: style = wx.PD_REMAINING_TIME | wx.PD_SMOOTH | wx.PD_AUTO_HIDE |\ wx.PD_CAN_ABORT dlg = wx.ProgressDialog( "Import", "Loading pages...", maximum=num, parent=self, style=style) for i in np.arange(num): # create a new page CurPage = self.add_fitting_tab(event=None, modelid=modelList[i], counter=None) # Fill Page with data self.ImportData(CurPage, Correlation[i], Trace[i], curvetype=Type[i], filename=Filename[i], curveid=str(Curveid[i]), run=str(Run[i]), weights=Weight[i], weight_type=WeightName[i], trigger="page_add_batch") # Let the user abort, if he wants to: # We want to do this here before an empty page is added # to the notebok. if dlg.Update(i+1, "Loading pages...")[0] == False: break self.OnFNBPageChanged(trigger="page_add_finalize") # If the user did not select curves but chose a model, destroy # the dialog. dlg.Destroy() def OnOpenSession(self, e=None, sessionfile=None): """ Displays a dialog for opening PyCorrFit sessions Optional parameter sessionfile defines the file that shall be automatically loaded (without a dialog). See Also -------- `pycorrfit.openfile.LoadSessionData` """ # We need to clear the session before opening one. # This will also ask, if user wants to save the current session. clear = self.OnClearSession(clearmodels=True) if clear == "abort": # User pressed abort when he was asked if he wants to save # the session. Therefore, we cannot open a new session. return "abort" # Create user dialog wc = ["*{}".format(w) for w in opf.session_wildcards] wcstring = "PyCorrFit session (*.pcfs)|{}".format(";".join(wc)) if sessionfile is None: dlg = wx.FileDialog(self, "Open session file", self.dirname, "", wcstring, wx.FD_OPEN) # user cannot do anything until he clicks "OK" if dlg.ShowModal() == wx.ID_OK: sessionfile = dlg.GetPath() (self.dirname, self.filename) = os.path.split( sessionfile) else: # User did not press OK # stop this function self.dirname = dlg.GetDirectory() return "abort" dlg.Destroy() Infodict = opf.LoadSessionData(sessionfile) # Check for correct version try: arcv = LooseVersion(Infodict["Version"]) thisv = LooseVersion(self.version.strip()) if arcv > thisv: errstring = "Your version of Pycorrfit ("+str(thisv) +\ ") is too old to open this session (" +\ str(arcv).strip()+").\n" +\ "Please download the lates version of " +\ " PyCorrFit from \n"+doc.HomePage+".\n" +\ "Continue opening this session?" dlg = edclasses.MyOKAbortDialog(self, errstring, "Warning") returns = dlg.ShowModal() if returns == wx.ID_OK: dlg.Destroy() else: dlg.Destroy() return "abort" except: pass self.SetTitleFCS(self.filename) # Background traces try: self.Background = Infodict["Backgrounds"] except: pass # Preferences # if Preferences is Not None: # add them! # External functions for key in Infodict["External Functions"].keys(): NewModel = usermodel.UserModel(self) # NewModel.AddModel(self, code) # code is a list with strings # each string is one line NewModel.AddModel( Infodict["External Functions"][key].splitlines()) NewModel.ImportModel() # Internal functions: N = len(Infodict["Parameters"]) # Reset tabcounter self.tabcounter = 1 # Show a nice progress dialog: style = wx.PD_REMAINING_TIME | wx.PD_SMOOTH | wx.PD_AUTO_HIDE |\ wx.PD_CAN_ABORT dlg = wx.ProgressDialog("Import", "Loading pages...", maximum=N, parent=self, style=style) for i in np.arange(N): # Let the user abort, if he wants to: if dlg.Update(i+1, "Loading pages...")[0] == False: dlg.Destroy() return # Add a new page to the notebook. This page is created with # variables from models.py. We will write our data to # the page later. counter = Infodict["Parameters"][i][0] modelid = Infodict["Parameters"][i][1] Newtab = self.add_fitting_tab(modelid=modelid, counter=counter) # Add experimental Data # Import dataexp: number = counter.strip(":# ") pageid = int(number) dataexp = Infodict["Correlations"][pageid][1] if Infodict["Parameters"][0][7]: curvetype = "cc" else: curvetype = "ac" # As of 0.7.3: Add external weights to page try: for key in Infodict["External Weights"][pageid].keys(): Newtab.corr.set_weights( key, Infodict["External Weights"][pageid][key]) except KeyError: pass if len(Infodict["Parameters"][i]) >= 6: if Infodict["Parameters"][i][5][0] >= 3: # we have a weighted fit with external weights # these are usually averages. keys = Infodict["External Weights"][pageid].keys() keys = list(keys) keys.sort() key = keys[Infodict["Parameters"][i][5][0]-3] weights = Infodict["External Weights"][pageid][key] weight_type = key else: weight_type = None weights = None self.ImportData(Newtab, dataexp, trace=Infodict["Traces"][pageid], curvetype=curvetype, weights=weights, weight_type=weight_type) # Set Title of the Page try: Newtab.tabtitle.SetValue(Infodict["Comments"][pageid]) except: pass # no page title # Parameters self.UnpackParameters(Infodict["Parameters"][i], Newtab, init=True) # Supplementary data fit_results = dict() fit_results["weighted fit"] = Infodict["Parameters"][i][5][0] > 0 try: Sups = Infodict["Supplements"][pageid] except KeyError: pass else: if "FitErr" in Sups: ervals = list() for errInfo in Sups["FitErr"]: ervals.append(float(errInfo[1])) fit_results["fit error estimation"] = ervals try: if len(Sups["Global Share"]) > 0: fit_results["global pages"] = Sups["Global Share"] except KeyError: pass try: fit_results["chi2"] = Sups["Chi sq"] except: pass # also set fit parameters fit_results["fit parameters"] = np.where( Infodict["Parameters"][i][3])[0] # set fit weights for plotting if fit_results["weighted fit"]: # these were already imported: try: weights = Infodict["External Weights"][pageid] for w in weights.keys(): fit_results["weighted fit type"] = w fit_results["fit weights"] = weights[w] except KeyError: pass Newtab.corr.fit_results = fit_results # Plot everything Newtab.PlotAll(trigger="page_add_batch") # Set Session Comment dlg.Destroy() try: self.SessionComment = Infodict["Comments"]["Session"] except: pass try: prefdict = Infodict["Preferences"] except: pass else: for key in prefdict: if key == "Pages prevent batch modification": for pid in prefdict[key]: for i in np.arange(N): page = self.notebook.GetPage(i) if page.counter.strip("#: ") == pid: page.prevent_batch_modification = True if self.notebook.GetPageCount() > 0: # Enable the "Current" Menu self.EnableToolCurrent(True) self.OnFNBPageChanged(trigger="page_add_finalize") else: # There are no pages in the session. # Disable some menus and close some dialogs self.EnableToolCurrent(False) def OnSaveData(self, e=None): """ Opens a dialog for saving correlation data of a Page Also saves the parameters that are accessible in the Info dialog and the trace(s). """ # What Data do we wish to save? Page = self.notebook.GetCurrentPage() # Export CSV data filename = "#{:04d}_{}".format(int(Page.counter.strip(":# ")), Page.title.strip()) dlg = wx.FileDialog(self, "Save curve", self.dirname, filename, "Correlation with trace (*.csv)|*.csv;*.*" + "|Correlation only (*.csv)|*.csv;*.*", wx.FD_SAVE | wx.FD_OVERWRITE_PROMPT) # user cannot do anything until he clicks "OK" if dlg.ShowModal() == wx.ID_OK: path = dlg.GetPath() # Workaround since 0.7.5 if not path.lower().endswith(".csv"): path += ".csv" (self.dirname, self.filename) = os.path.split(path) if dlg.GetFilterIndex() == 0: savetrace = True else: savetrace = False # Collect info on page InfoMan = tools.info.InfoClass(CurPage=Page) PageInfo = InfoMan.GetCurFancyInfo() opf.ExportCorrelation(path, Page.corr, PageInfo, savetrace=savetrace) dlg.Destroy() def OnSavePlotCorr(self, e=None): """ make some output """ # Saving dialog box. uselatex = self.MenuUseLatex.IsChecked() verbose = self.MenuVerbose.IsChecked() show_weights = self.MenuShowWeights.IsChecked() Page = self.notebook.GetCurrentPage() try: plotting.savePlotCorrelation(self, self.dirname, Page, uselatex, verbose, show_weights) except NameError as excpt: trb = traceback.format_exc(excpt) trb = " " + trb.replace("\n", "\n ") raise NameError("Please make sure matplotlib is installed:\n"+trb) def OnSavePlotTrace(self, e=None): """ make some output """ # Saving dialog box. uselatex = 1*self.MenuUseLatex.IsChecked() verbose = 1*self.MenuVerbose.IsChecked() Page = self.notebook.GetCurrentPage() try: plotting.savePlotTrace(self, self.dirname, Page, uselatex, verbose) except NameError as excpt: trb = traceback.format_exc(excpt) trb = " " + trb.replace("\n", "\n ") raise NameError("Please make sure matplotlib is installed:\n"+trb) def OnSaveSession(self, e=None): """ Displays a dialog for saving PyCorrFit sessions Returns ------- - the filename of the session if it was saved - None, if the user canceled the action See Also -------- `pycorrfit.openfile.SaveSessionData` """ # Parameters are all in one dictionary: Infodict = dict() Infodict["Backgrounds"] = self.Background # Background list # Session comment "Session" and Pages int Infodict["Comments"] = dict() Infodict["Correlations"] = dict() # all correlation curves Infodict["External Functions"] = dict() # external model functions # additional weights for the pages Infodict["External Weights"] = dict() Infodict["Parameters"] = dict() # all parameters of all pages Infodict["Preferences"] = dict() # not used Infodict["Supplements"] = dict() # error estimates for fitting Infodict["Traces"] = dict() # all traces # Save each Page N = self.notebook.GetPageCount() # External functions for usermodelid in mdls.modeltypes["User"]: # Those models belong to external user functions. doc = mdls.modeldict[usermodelid][-1].__doc__ doc = doc.splitlines() docnew = "" for line in doc: docnew = docnew+line.strip()+"\r\n" Infodict["External Functions"][usermodelid] = docnew for i in np.arange(N): # Set Page Page = self.notebook.GetPage(i) counter = int(Page.counter.strip(":# ")) # Apply currently set parameters Page.apply_parameters() # Set parameters Infodict["Parameters"][counter] = self.PackParameters(Page) corr = Page.corr # Set supplementary information, such as errors of fit if hasattr(corr, "fit_results"): Infodict["Supplements"][counter] = dict() if "chi2" in corr.fit_results: Infodict["Supplements"][counter]["Chi sq"] = float( corr.fit_results["chi2"]) else: Infodict["Supplements"][counter]["Chi sq"] = 0 PageList = list() for pagei in Page.GlobalParameterShare: PageList.append(int(pagei)) Infodict["Supplements"][counter]["Global Share"] = PageList # optimization error Alist = list() if ( # there is an error key "fit error estimation" in corr.fit_results and # the errors were computed len(corr.fit_results["fit error estimation"]) != 0 and # len(errors) matches len(fit parameters) len(corr.fit_results["fit error estimation"]) == len( corr.fit_results["fit parameters"]) ): for ii, fitpid in enumerate(corr.fit_results["fit parameters"]): Alist.append([int(fitpid), float( corr.fit_results["fit error estimation"][ii]) ]) Infodict["Supplements"][counter]["FitErr"] = Alist # Set exp data Infodict["Correlations"][counter] = [ corr.lag_time, corr.correlation] # Also save the trace Infodict["Traces"][counter] = corr.traces # Append title to Comments # #Comments.append(Page.tabtitle.GetValue()) Infodict["Comments"][counter] = Page.tabtitle.GetValue() # Add additional weights to Info["External Weights"] external_weights = dict() for key in corr._fit_weight_memory.keys(): if isinstance(corr._fit_weight_memory[key], np.ndarray): external_weights[key] = corr._fit_weight_memory[key] # also save current weights if hasattr(corr, "fit_results"): if "weighted fit type" in corr.fit_results: fittype = corr.fit_results["weighted fit type"] fitweight = corr.fit_results["fit weights"] external_weights[fittype] = fitweight Infodict["External Weights"][counter] = external_weights # Append Session Comment: Infodict["Comments"]["Session"] = self.SessionComment # Protected pages: protpage = [] for i in np.arange(N): # Set Page Page = self.notebook.GetPage(i) if Page.prevent_batch_modification: protpage.append(Page.counter.strip("#: ")) Infodict["Preferences"]["Pages prevent batch modification"] = protpage # File dialog dlg = wx.FileDialog(self, "Save session file", self.dirname, "", "PyCorrFit session (*.pcfs)|*.pcfs|All files (*.*)|*.*", wx.FD_SAVE | wx.FD_OVERWRITE_PROMPT) if dlg.ShowModal() == wx.ID_OK: # Save everything path = dlg.GetPath() # Workaround since 0.7.5 (self.dirname, self.filename) = os.path.split(path) opf.SaveSessionData(path, Infodict) else: self.dirname = dlg.GetDirectory() self.filename = None # Set title of our window if (self.filename is not None and not self.filename.endswith(".pcfs")): self.filename += ".pcfs" dlg.Destroy() self.SetTitleFCS(self.filename) return self.filename def OnShell(self, e=None): Shell = wx.py.shell.ShellFrame(self, title="PyCorrFit Shell", style=wx.DEFAULT_FRAME_STYLE | wx.FRAME_FLOAT_ON_PARENT, locals=locals()) # Set window icon if self.MainIcon is not None: wx.Frame.SetIcon(Shell, self.MainIcon) Shell.Show(True) def OnSoftware(self, event=None): # Show About Information text = doc.SoftwareUsed() wx.MessageBox(text, 'Software', wx.OK | wx.ICON_INFORMATION) def OnTool(self, event): eid = event.GetId() try: # Check if a tool is open self.ToolsOpen[eid] except KeyError: # eid is not on self.ToolOpen # So we open the dialog and add it to the list self.ToolsOpen[eid] = self.Tools[eid](self) self.ToolsOpen[eid].MyID = eid self.ToolsOpen[eid].Bind(wx.EVT_CLOSE, self.ToolsOpen[eid].OnClose) self.toolmenu.Check(eid, True) else: # We close it then self.ToolsOpen[eid].OnClose() def OnUpdate(self, event): update.update(self) def OnWiki(self, e=None): """ Go to the GitHub Wiki page""" webbrowser.open(doc.GitWiki) def PackParameters(self, Page): """ Gets all parameters from a page and returns a list object, that can be used to save as e.g. a safe YAML file """ Page.apply_parameters() # Get Model ID modelid = Page.corr.fit_model.id # Get Page number counter = Page.counter active_numbers = Page.corr.fit_parameters # Array, Parameters active_fitting = Page.corr.fit_parameters_variable crop = Page.corr.fit_ival Parms = [counter, modelid, active_numbers, active_fitting, crop] # Weighting: # Additional parameters as of v.0.2.0 # Splines and model function: # Additional parameters as of v.6.4.0 # self.Fitbox=[ fitbox, weightedfitdrop, fittext, fittext2, # fittextvar, fitspin, buttonfit ] # Some fits like Spline have a number of knots of the spline # that is important for fitting. If there is a number in the # Dropdown, save it. # knots = str(Page.FitKnots) knots = "".join(filter(lambda x: x.isdigit(), knots)) if len(knots) == 0: knots = None else: knots = int(knots) weighted = Page.weighted_fittype_id weights = Page.weighted_nuvar algorithm = Page.corr.fit_algorithm Parms.append([weighted, weights, knots, algorithm]) # Additional parameters as of v.0.2.9 # Which Background signal is selected? # The Background information is in the list *self.Background*. Parms.append([Page.bgselected, Page.bg2selected]) # Additional parameter as of v.0.5.8 # Is the Experimental data (if it exists) AC or CC? Parms.append(Page.IsCrossCorrelation) # Additional parameter as of v.0.7.8 # The selection of a normalization parameter (None or integer) Parms.append(Page.corr.normparm) # Parameter ranges Parms.append(Page.corr.fit_parameters_range) return Parms def UnpackParameters(self, Parms, Page, init=False): """ Apply the given parameters to the Page in question. This function contains several *len(Parms) >= X* statements. These are used for opening sessions that were saved using earlier versions of PyCorrFit. The `init` variable can be set if fundamental changes are made (loading data). This e.g. might change the type (Autocorrelation/Cross-Correlation) of the page. """ modelid = Parms[1] if Page.corr.fit_model.id != modelid: print("Wrong model: "+str(Page.corr.fit_model.id)+" vs. "+str(modelid)) return active_values = Parms[2] active_fitting = Parms[3] # As of version 0.7.0: square pinhole TIR-FCS models # use sigma instead of lambda, NA and sigma_0. This # is for backwards compatibility: changeTIRF = False if modelid in [6000, 6010]: if len(Parms[2]) > len(mdls.valuedict[modelid][0]): lindex = 1 changeTIRF = True elif (modelid == 6022 and len(Parms[2]) == len(mdls.valuedict[modelid][0]) + 1): # Change in verson 0.8.7: TIRF_2D2D model remove d_eva active_values = np.delete(active_values, 4) active_fitting = np.delete(active_fitting, 4) elif modelid in [6020, 6021, 6022, 6023]: if len(Parms[2]) > len(mdls.valuedict[modelid][0]): lindex = 2 changeTIRF = True if changeTIRF: lamb = active_values[lindex] NA = active_values[lindex+1] sigma = 0.21*lamb/NA active_values[lindex] = sigma active_values = np.delete(active_values, lindex+1) active_fitting = np.delete(active_fitting, lindex+1) # Cropping: What part of the correlation should be displayed. Page.corr.fit_ival = Parms[4] # Add parameters and fitting to the created page. # We need to run Newtab.apply_parameters_reverse() in order # for the data to be displayed in the user interface. Page.corr.fit_parameters = active_values Page.corr.fit_parameters_variable = active_fitting # Weighted fitting if len(Parms) >= 6: if len(Parms[5]) == 2: [weighted, weights] = Parms[5] knots = None elif len(Parms[5]) == 3: # We have knots as of v. 0.6.5 [weighted, weights, knots] = Parms[5] else: # We have different fitting algorithms as of v. 0.8.3 [weighted, weights, knots, algorithm] = Parms[5] Page.corr.fit_algorithm = algorithm if knots is not None: # This is done with apply_paramters_reverse: # text = Page.Fitbox[1].GetValue() # text = filter(lambda x: x.isalpha(), text) # Page.Fitbox[1].SetValue(text+str(knots)) Page.FitKnots = int(knots) if weighted is False: weighted = 0 elif weighted is True: weighted = 1 elif len(Page.Fitbox[1].GetItems())-1 < weighted: # Is the case, e.g. when we have an average std, # but this page is not an average. weighted = 0 Page.weighted_fittype_id = weighted Page.weighted_nuvar = weights Page.apply_parameters_reverse() if Page.corr.correlation is not None: Page.Fit_enable_fitting() Page.Fit_WeightedFitCheck() # Set which background correction the Page uses: if len(Parms) >= 7: # causality check: if Parms[6][0] is None or len(self.Background) > Parms[6][0]: Page.bgselected = Parms[6][0] if len(Parms[6]) == 2: # New in 0.8.1: CC background correction Page.bg2selected = Parms[6][1] # New feature since 0.7.8: BG selection on Page panel Page.OnAmplitudeCheck("init") # Set if Newtab is of type cross-correlation: if len(Parms) >= 8: if Parms[7]: Page.corr.corr_type = "cc" else: Page.corr.corr_type = "ac" Page.OnAmplitudeCheck() if len(Parms) >= 9: # New feature in 0.7.8 includes normalization to a fitting # parameter. Page.corr.normparm = Parms[8] Page.apply_parameters_reverse() Page.OnAmplitudeCheck("init") if len(Parms) >= 10: Page.corr.fit_parameters_range = np.array(Parms[9]) # If we want to add more stuff, we should do something like: # if len(Parms) >= 11: ## nextvalue = Parms[10] # Such that we are compatible to earlier versions of # PyCorrFit sessions. def SetTitleFCS(self, title): if title is not None and len(title) != 0: title = " {"+title+"}" self.SetTitle('PyCorrFit ' + self.version + title) else: self.SetTitle('PyCorrFit ' + self.version) def MyExceptionHook(etype, value, trace): """ Handler for all unhandled exceptions. :param `etype`: the exception type (`SyntaxError`, `ZeroDivisionError`, etc...); :type `etype`: `Exception` :param string `value`: the exception error message; :param string `trace`: the traceback header, if any (otherwise, it prints the standard Python header: ``Traceback (most recent call last)``. """ wx.GetApp().GetTopWindow() tmp = traceback.format_exception(etype, value, trace) exception = "".join(tmp) dlg = ExceptionDialog(exception) dlg.ShowModal() dlg.Destroy() wx.EndBusyCursor() pycorrfit-1.1.7/pycorrfit/meta.py0000664000372000037200000000316213554642611017735 0ustar travistravis00000000000000"""meta data and methods for PyCorrFit""" import os import sys def find_program(program): """ Uses the systems PATH variable find executables""" path = os.environ['PATH'] paths = path.split(os.pathsep) for d in paths: if os.path.isdir(d): fullpath = os.path.join(d, program) if sys.platform[:3] == 'win': for ext in '.exe', '.bat': program_path = fullpath + ext if os.path.isfile(fullpath + ext): return (1, program_path) else: if os.path.isfile(fullpath): return (1, fullpath) return (0, None) def get_file_location(filename): """ Locate non-Python files that are part of PyCorrFit. """ dirname = os.path.dirname(os.path.abspath(__file__)) locations = ["/./", "/pycorrfit_doc/", "/doc/"] locations += ["/.."+l for l in locations] locations = [os.path.realpath(dirname+l) for l in locations] for i in range(len(locations)): # check /usr/lib64/32 -> /usr/lib for larch in ["lib32", "lib64"]: if dirname.count(larch): locations.append(locations[i].replace(larch, "lib", 1)) # freezed binaries: if hasattr(sys, 'frozen'): try: adir = sys._MEIPASS + "/doc/" # @UndefinedVariable except: adir = "./" locations.append(os.path.realpath(adir)) for loc in locations: thechl = os.path.join(loc, filename) if os.path.exists(thechl): return thechl break # if this does not work: return None pycorrfit-1.1.7/pycorrfit/__init__.py0000664000372000037200000000056313554642611020550 0ustar travistravis00000000000000""" PyCorrFit is a tool to fit fluorescence correlation spectroscopy data on a logarithmic scale. """ from . import meta from . import models from . import openfile from . import readfiles from .correlation import Correlation from .fit import Fit from .trace import Trace from ._version import version as __version__ __author__ = u"Paul Müller" __license__ = "GPL v2" pycorrfit-1.1.7/pycorrfit/_version_save.py0000664000372000037200000000012213554642637021652 0ustar travistravis00000000000000#!/usr/bin/env python # This file was created automatically longversion = '1.1.7' pycorrfit-1.1.7/pycorrfit/trace.py0000664000372000037200000000673213554642611020113 0ustar travistravis00000000000000"""PyCorrFit data set: Classes for FCS data evaluation""" import hashlib import numpy as np import scipy.integrate as spintg class Trace(object): """ unifies trace handling """ def __init__(self, trace=None, countrate=None, duration=None, name=None): """ Load trace data Parameters ---------- trace : ndarray of shape (N, 2) The array contains time [ms] and count rate [kHz]. coutrate : float Average count rate [kHz]. Mandatory if `trace` is None. duration : float Duration of measurement in milliseconds. Mandatory if `trace` is None. name : str The name of the trace. """ self._countrate = None self._duration = None self._trace = None self._uid = None if trace is None: self.countrate = countrate self.duration = duration else: self.trace = trace if name is None: name = "{:.2f}kHz, {:.0f}s".format(self.countrate, self.duration/1000) self.name = name def __getitem__(self, idx): return self.trace[idx] def __repr__(self): text = "Trace of length {:.3f}s and countrate {:.3f}kHz".format( self.duration/1000, self.countrate) return text @property def countrate(self): if self._countrate is None: #self._countrate = np.average(self._trace[:,1]) # Take into account traces that have arbitrary sampling self._countrate = spintg.simps( self._trace[:, 1], self._trace[:, 0]) / self.duration return self._countrate @countrate.setter def countrate(self, value): if value is None: raise ValueError("Setting value to `None` not allowed!") if self._trace is not None: raise ValueError("Cannot set countrate; `self.trace` is set.") self._countrate = value @property def duration(self): if not hasattr(self, "_duration") or self._duration is None: self._duration = self._trace[-1, 0] - self._trace[0, 0] return self._duration @duration.setter def duration(self, value): if value is None: raise ValueError("Setting value to `None` not allowed!") if self._trace is not None: raise ValueError("Cannot set duration; `self.trace` is set.") self._duration = value @property def uid(self): if self._uid is None: hasher = hashlib.sha256() hasher.update(str(np.random.random())) hasher.update(str(self.trace)) hasher.update(self.name) self._uid = hasher.hexdigest() return self._uid @property def trace(self): if self._trace is None: self._trace = np.array([[0, self.countrate], [self.duration, self.countrate] ]) return self._trace @trace.setter def trace(self, value): if value is None: raise ValueError("Setting value to `None` not allowed!") if not isinstance(value, np.ndarray): raise ValueError("Trace data must be np.ndarray!") if value.shape[1] != 2: raise ValueError("Shape of array must be (N,2)!") self._trace = value # self.countrate is set automagically pycorrfit-1.1.7/pycorrfit/readfiles/0000775000372000037200000000000013554643106020371 5ustar travistravis00000000000000pycorrfit-1.1.7/pycorrfit/readfiles/read_pt3_scripts/0000775000372000037200000000000013554643106023641 5ustar travistravis00000000000000pycorrfit-1.1.7/pycorrfit/readfiles/read_pt3_scripts/correlation_methods.py0000664000372000037200000001117113554642611030260 0ustar travistravis00000000000000import numpy as np from .fib4 import dividAndConquer """FCS Bulk Correlation Software Copyright (C) 2015 Dominic Waithe This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. """ def tttr2xfcs(y, num, NcascStart, NcascEnd, Nsub): """autocorr, autotime = tttr2xfcs(y,num,10,20) Translation into python of: Fast calculation of fluorescence correlation data with asynchronous time-correlated single-photon counting. Michael Wahl, Ingo Gregor, Matthias Patting, Jorg Enderlein """ dt = np.max(y)-np.min(y) y = np.round(y[:], 0) numshape = num.shape[0] autotime = np.zeros(((NcascEnd+1)*(Nsub+1), 1)) auto = np.zeros( ((NcascEnd+1)*(Nsub+1), num.shape[1], num.shape[1])).astype(np.float64) shift = float(0) delta = float(1) for j in range(0, NcascEnd): # Finds the unique photon times and their indices. The division of 'y' by '2' each cycle makes this more likely. y, k1 = np.unique(y, 1) k1shape = k1.shape[0] # Sums up the photon times in each bin. cs = np.cumsum(num, 0).T # Prepares difference array so starts with zero. diffArr1 = np.zeros((k1shape+1)) diffArr2 = np.zeros((k1shape+1)) # Takes the cumulative sum of the unique photon arrivals diffArr1[1:] = cs[0, k1].reshape(-1) diffArr2[1:] = cs[1, k1].reshape(-1) #del k1 #del cs num = np.zeros((k1shape, 2)) # Finds the total photons in each bin. and represents as count. # This is achieved because we have the indices of each unique time photon and cumulative total at each point. num[:, 0] = np.diff(diffArr1) num[:, 1] = np.diff(diffArr2) #diffArr1 = []; #diffArr2 = []; for k in range(0, Nsub): shift = shift + delta lag = np.round(shift/delta, 0) # Allows the script to be sped up. if j >= NcascStart: # Old method #i1= np.in1d(y,y+lag,assume_unique=True) #i2= np.in1d(y+lag,y,assume_unique=True) # New method, cython i1, i2 = dividAndConquer(y, y+lag, y.shape[0]) # If the weights (num) are one as in the first Ncasc round, then the correlation is equal to np.sum(i1) i1 = np.where(i1.astype(np.bool))[0] i2 = np.where(i2.astype(np.bool))[0] # Now we want to weight each photon corectly. # Faster dot product method, faster than converting to matrix. if i1.size and i2.size: auto[(k+(j)*Nsub), :, :] = np.dot((num[i1, :]).T, num[i2, :])/delta autotime[k+(j)*Nsub] = shift # Equivalent to matlab round when numbers are %.5 y = np.ceil(np.array(0.5*y)) delta = 2*delta for j in range(0, auto.shape[0]): auto[j, :, :] = auto[j, :, :]*dt/(dt-autotime[j]) autotime = autotime/1000000 # Removes the trailing zeros. idauto = np.where(autotime != 0)[0] autotime = autotime[idauto] auto = auto[idauto, :, :] return auto, autotime def delayTime2bin(dTimeArr, chanArr, chanNum, winInt): decayTime = np.array(dTimeArr) # This is the point and which each channel is identified. decayTimeCh = decayTime[chanArr == chanNum] # Find the first and last entry firstDecayTime = 0 # np.min(decayTimeCh).astype(np.int32) tempLastDecayTime = np.max(decayTimeCh).astype(np.int32) # We floor this as the last bin is always incomplete and so we discard photons. numBins = np.floor((tempLastDecayTime-firstDecayTime)/winInt) lastDecayTime = numBins*winInt bins = np.linspace(firstDecayTime, lastDecayTime, int(numBins)+1) photonsInBin, jnk = np.histogram(decayTimeCh, bins) # bins are valued as half their span. decayScale = bins[:-1]+(winInt/2) #decayScale = np.arange(0,decayTimeCh.shape[0]) return list(photonsInBin), list(decayScale) pycorrfit-1.1.7/pycorrfit/readfiles/read_pt3_scripts/import_methods.py0000664000372000037200000002001213554642611027243 0ustar travistravis00000000000000import struct import numpy as np import csv """FCS Bulk Correlation Software Copyright (C) 2015 Dominic Waithe This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. """ def csvimport(filepath): r_obj = csv.reader(open(filepath, 'rb')) line_one = r_obj.next() if line_one.__len__() > 1: if float(line_one[1]) == 2: version = 2 else: print('version not known:', line_one[1]) if version == 2: type = str(r_obj.next()[1]) if type == "pt uncorrelated": Resolution = float(r_obj.next()[1]) chanArr = [] trueTimeArr = [] dTimeArr = [] line = r_obj.next() while line[0] != 'end': chanArr.append(int(line[0])) trueTimeArr.append(float(line[1])) dTimeArr.append(int(line[2])) line = r_obj.next() return np.array(chanArr), np.array(trueTimeArr), np.array(dTimeArr), Resolution else: print('type not recognised') return None, None, None, None def pt3import(filepath): """The file import for the .pt3 file""" f = open(filepath, 'rb') Ident = f.read(16) FormatVersion = f.read(6) CreatorName = f.read(18) CreatorVersion = f.read(12) FileTime = f.read(18) CRLF = f.read(2) CommentField = f.read(256) Curves = struct.unpack('i', f.read(4))[0] BitsPerRecord = struct.unpack('i', f.read(4))[0] RoutingChannels = struct.unpack('i', f.read(4))[0] NumberOfBoards = struct.unpack('i', f.read(4))[0] ActiveCurve = struct.unpack('i', f.read(4))[0] MeasurementMode = struct.unpack('i', f.read(4))[0] SubMode = struct.unpack('i', f.read(4))[0] RangeNo = struct.unpack('i', f.read(4))[0] Offset = struct.unpack('i', f.read(4))[0] AcquisitionTime = struct.unpack('i', f.read(4))[0] StopAt = struct.unpack('i', f.read(4))[0] StopOnOvfl = struct.unpack('i', f.read(4))[0] Restart = struct.unpack('i', f.read(4))[0] DispLinLog = struct.unpack('i', f.read(4))[0] DispTimeFrom = struct.unpack('i', f.read(4))[0] DispTimeTo = struct.unpack('i', f.read(4))[0] DispCountFrom = struct.unpack('i', f.read(4))[0] DispCountTo = struct.unpack('i', f.read(4))[0] DispCurveMapTo = [] DispCurveShow = [] for i in range(0, 8): DispCurveMapTo.append(struct.unpack('i', f.read(4))[0]) DispCurveShow.append(struct.unpack('i', f.read(4))[0]) ParamStart = [] ParamStep = [] ParamEnd = [] for i in range(0, 3): ParamStart.append(struct.unpack('i', f.read(4))[0]) ParamStep.append(struct.unpack('i', f.read(4))[0]) ParamEnd.append(struct.unpack('i', f.read(4))[0]) RepeatMode = struct.unpack('i', f.read(4))[0] RepeatsPerCurve = struct.unpack('i', f.read(4))[0] RepeatTime = struct.unpack('i', f.read(4))[0] RepeatWait = struct.unpack('i', f.read(4))[0] ScriptName = f.read(20) # The next is a board specific header HardwareIdent = f.read(16) HardwareVersion = f.read(8) HardwareSerial = struct.unpack('i', f.read(4))[0] SyncDivider = struct.unpack('i', f.read(4))[0] CFDZeroCross0 = struct.unpack('i', f.read(4))[0] CFDLevel0 = struct.unpack('i', f.read(4))[0] CFDZeroCross1 = struct.unpack('i', f.read(4))[0] CFDLevel1 = struct.unpack('i', f.read(4))[0] Resolution = struct.unpack('f', f.read(4))[0] # below is new in format version 2.0 RouterModelCode = struct.unpack('i', f.read(4))[0] RouterEnabled = struct.unpack('i', f.read(4))[0] # Router Ch1 RtChan1_InputType = struct.unpack('i', f.read(4))[0] RtChan1_InputLevel = struct.unpack('i', f.read(4))[0] RtChan1_InputEdge = struct.unpack('i', f.read(4))[0] RtChan1_CFDPresent = struct.unpack('i', f.read(4))[0] RtChan1_CFDLevel = struct.unpack('i', f.read(4))[0] RtChan1_CFDZeroCross = struct.unpack('i', f.read(4))[0] # Router Ch2 RtChan2_InputType = struct.unpack('i', f.read(4))[0] RtChan2_InputLevel = struct.unpack('i', f.read(4))[0] RtChan2_InputEdge = struct.unpack('i', f.read(4))[0] RtChan2_CFDPresent = struct.unpack('i', f.read(4))[0] RtChan2_CFDLevel = struct.unpack('i', f.read(4))[0] RtChan2_CFDZeroCross = struct.unpack('i', f.read(4))[0] # Router Ch3 RtChan3_InputType = struct.unpack('i', f.read(4))[0] RtChan3_InputLevel = struct.unpack('i', f.read(4))[0] RtChan3_InputEdge = struct.unpack('i', f.read(4))[0] RtChan3_CFDPresent = struct.unpack('i', f.read(4))[0] RtChan3_CFDLevel = struct.unpack('i', f.read(4))[0] RtChan3_CFDZeroCross = struct.unpack('i', f.read(4))[0] # Router Ch4 RtChan4_InputType = struct.unpack('i', f.read(4))[0] RtChan4_InputLevel = struct.unpack('i', f.read(4))[0] RtChan4_InputEdge = struct.unpack('i', f.read(4))[0] RtChan4_CFDPresent = struct.unpack('i', f.read(4))[0] RtChan4_CFDLevel = struct.unpack('i', f.read(4))[0] RtChan4_CFDZeroCross = struct.unpack('i', f.read(4))[0] # The next is a T3 mode specific header. ExtDevices = struct.unpack('i', f.read(4))[0] Reserved1 = struct.unpack('i', f.read(4))[0] Reserved2 = struct.unpack('i', f.read(4))[0] CntRate0 = struct.unpack('i', f.read(4))[0] CntRate1 = struct.unpack('i', f.read(4))[0] StopAfter = struct.unpack('i', f.read(4))[0] StopReason = struct.unpack('i', f.read(4))[0] Records = struct.unpack('i', f.read(4))[0] ImgHdrSize = struct.unpack('i', f.read(4))[0] # Special Header for imaging. if ImgHdrSize > 0: ImgHdr = struct.unpack('i', f.read(ImgHdrSize))[0] ofltime = 0 cnt_1 = 0 cnt_2 = 0 cnt_3 = 0 cnt_4 = 0 cnt_Ofl = 0 cnt_M = 0 cnt_Err = 0 # just counters WRAPAROUND = 65536 # Put file Save info here. syncperiod = 1e9/CntRate0 # outfile stuff here. # fpout. #T3RecordArr = []; chanArr = [0]*Records trueTimeArr = [0]*Records dTimeArr = [0]*Records #f1=open('./testfile', 'w+') for b in range(0, Records): T3Record = struct.unpack('I', f.read(4))[0] # T3RecordArr.append(T3Record) nsync = T3Record & 65535 chan = ((T3Record >> 28) & 15) chanArr[b] = chan #f1.write(str(i)+" "+str(T3Record)+" "+str(nsync)+" "+str(chan)+" ") dtime = 0 if chan == 1: cnt_1 = cnt_1+1 dtime = ((T3Record >> 16) & 4095) # f1.write(str(dtime)+" ") elif chan == 2: cnt_2 = cnt_2+1 dtime = ((T3Record >> 16) & 4095) # f1.write(str(dtime)+" ") elif chan == 3: cnt_3 = cnt_3+1 dtime = ((T3Record >> 16) & 4095) # f1.write(str(dtime)+" ") elif chan == 4: cnt_4 = cnt_4+1 dtime = ((T3Record >> 16) & 4095) # f1.write(str(dtime)+" ") elif chan == 15: markers = ((T3Record >> 16) & 15) if markers == 0: ofltime = ofltime + WRAPAROUND cnt_Ofl = cnt_Ofl+1 #f1.write("Ofl "+" ") else: cnt_M = cnt_M+1 #f1.write("MA:%1u "+markers+" ") truensync = ofltime + nsync truetime = (truensync * syncperiod) + (dtime*Resolution) trueTimeArr[b] = truetime dTimeArr[b] = dtime #f1.write(str(truensync)+" "+str(truetime)+"\n") f.close() # f1.close(); return np.array(chanArr), np.array(trueTimeArr), np.array(dTimeArr), Resolution pycorrfit-1.1.7/pycorrfit/readfiles/read_pt3_scripts/correlation_objects.py0000664000372000037200000010470713554642611030256 0ustar travistravis00000000000000import numpy as np import os import sys from .correlation_methods import * from .import_methods import * import time from .fitting_methods import equation_ # from lmfit import minimize, Parameters,report_fit,report_errors, fit_report import csv import copy """FCS Bulk Correlation Software Copyright (C) 2015 Dominic Waithe This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. """ class picoObject(): # This is the class which holds the .pt3 data and parameters def __init__(self, filepath, par_obj, fit_obj): # parameter object and fit object. If self.par_obj = par_obj self.fit_obj = fit_obj self.type = 'mainObject' #self.PIE = 0 self.filepath = str(filepath) self.nameAndExt = os.path.basename(self.filepath).split('.') self.name = self.nameAndExt[0] self.ext = self.nameAndExt[-1] self.par_obj.data.append(filepath) self.par_obj.objectRef.append(self) # Imports pt3 file format to object. self.unqID = self.par_obj.numOfLoaded # For fitting. self.objId1 = None self.objId2 = None self.objId3 = None self.objId4 = None self.processData() self.plotOn = True def processData(self): self.NcascStart = self.par_obj.NcascStart self.NcascEnd = self.par_obj.NcascEnd self.Nsub = self.par_obj.Nsub self.winInt = self.par_obj.winInt self.photonCountBin = self.par_obj.photonCountBin # File import if self.ext == 'pt3': self.subChanArr, self.trueTimeArr, self.dTimeArr, self.resolution = pt3import( self.filepath) if self.ext == 'csv': self.subChanArr, self.trueTimeArr, self.dTimeArr, self.resolution = csvimport( self.filepath) # If the file is empty. if self.subChanArr == None: # Undoes any preparation of resource. self.par_obj.data.pop(-1) self.par_obj.objectRef.pop(-1) self.exit = True self.par_obj.image_status_text.showMessage( "Your sample is not in the correct format.") self.par_obj.fit_obj.app.processEvents() return # Colour assigned to file. self.color = self.par_obj.colors[self.unqID % len(self.par_obj.colors)] # How many channels there are in the files. # Minus 1 because not interested in channel 15. self.numOfCH = np.unique(np.array(self.subChanArr)).__len__()-1 # Finds the numbers which address the channels. self.ch_present = np.unique(np.array(self.subChanArr[0:100])) # Calculates decay function for both channels. self.photonDecayCh1, self.decayScale1 = delayTime2bin(np.array( self.dTimeArr), np.array(self.subChanArr), self.ch_present[0], self.winInt) if self.numOfCH == 2: self.photonDecayCh2, self.decayScale2 = delayTime2bin(np.array( self.dTimeArr), np.array(self.subChanArr), self.ch_present[1], self.winInt) # Time series of photon counts. For visualisation. self.timeSeries1, self.timeSeriesScale1 = delayTime2bin(np.array( self.trueTimeArr)/1000000, np.array(self.subChanArr), self.ch_present[0], self.photonCountBin) unit = self.timeSeriesScale1[-1]/self.timeSeriesScale1.__len__() # Converts to counts per self.kcount_CH1 = np.average(self.timeSeries1) # This is the unnormalised intensity count for int_time duration (the first moment) raw_count = np.average(self.timeSeries1) var_count = np.var(self.timeSeries1) self.brightnessNandBCH0 = ( ((var_count - raw_count)/(raw_count))/(float(unit))) if (var_count-raw_count) == 0: self.numberNandBCH0 = 0 else: self.numberNandBCH0 = (raw_count**2/(var_count-raw_count)) if self.numOfCH == 2: self.timeSeries2, self.timeSeriesScale2 = delayTime2bin(np.array( self.trueTimeArr)/1000000, np.array(self.subChanArr), self.ch_present[1], self.photonCountBin) unit = self.timeSeriesScale2[-1]/self.timeSeriesScale2.__len__() self.kcount_CH2 = np.average(self.timeSeries2) # This is the unnormalised intensity count for int_time duration (the first moment) raw_count = np.average(self.timeSeries2) var_count = np.var(self.timeSeries2) self.brightnessNandBCH1 = ( ((var_count - raw_count)/(raw_count))/(float(unit))) if (var_count-raw_count) == 0: self.numberNandBCH1 = 0 else: self.numberNandBCH1 = (raw_count**2/(var_count-raw_count)) # Calculates the Auto and Cross-correlation functions. self.crossAndAuto(np.array(self.trueTimeArr), np.array(self.subChanArr)) if self.fit_obj != None: # If fit object provided then creates fit objects. if self.objId1 == None: corrObj = corrObject(self.filepath, self.fit_obj) self.objId1 = corrObj.objId self.fit_obj.objIdArr.append(corrObj.objId) self.objId1.param = copy.deepcopy(self.fit_obj.def_param) self.objId1.name = self.name+'_CH0_Auto_Corr' self.objId1.ch_type = 0 # channel 0 Auto self.objId1.siblings = None self.objId1.prepare_for_fit() self.objId1.kcount = self.kcount_CH1 self.objId1.autoNorm = np.array(self.autoNorm[:, 0, 0]).reshape(-1) self.objId1.autotime = np.array(self.autotime).reshape(-1) self.objId1.param = copy.deepcopy(self.fit_obj.def_param) if self.numOfCH == 2: if self.objId3 == None: corrObj = corrObject(self.filepath, self.fit_obj) self.objId3 = corrObj.objId self.objId3.param = copy.deepcopy(self.fit_obj.def_param) self.fit_obj.objIdArr.append(corrObj.objId) self.objId3.name = self.name+'_CH1_Auto_Corr' self.objId3.ch_type = 1 # channel 1 Auto self.objId3.siblings = None self.objId3.prepare_for_fit() self.objId3.kcount = self.kcount_CH2 self.objId3.autoNorm = np.array( self.autoNorm[:, 1, 1]).reshape(-1) self.objId3.autotime = np.array(self.autotime).reshape(-1) self.objId3.param = copy.deepcopy(self.fit_obj.def_param) if self.objId2 == None: corrObj = corrObject(self.filepath, self.fit_obj) self.objId2 = corrObj.objId self.objId2.param = copy.deepcopy(self.fit_obj.def_param) self.fit_obj.objIdArr.append(corrObj.objId) self.objId2.name = self.name+'_CH01_Cross_Corr' self.objId2.ch_type = 2 # 01cross self.objId2.siblings = None self.objId2.prepare_for_fit() self.objId2.autoNorm = np.array( self.autoNorm[:, 0, 1]).reshape(-1) self.objId2.autotime = np.array(self.autotime).reshape(-1) self.objId2.param = copy.deepcopy(self.fit_obj.def_param) if self.objId4 == None: corrObj = corrObject(self.filepath, self.fit_obj) self.objId4 = corrObj.objId self.objId4.param = copy.deepcopy(self.fit_obj.def_param) self.fit_obj.objIdArr.append(corrObj.objId) self.objId4.name = self.name+'_CH10_Cross_Corr' self.objId4.ch_type = 3 # 10cross self.objId4.siblings = None self.objId4.prepare_for_fit() self.objId4.autoNorm = np.array( self.autoNorm[:, 1, 0]).reshape(-1) self.objId4.autotime = np.array(self.autotime).reshape(-1) self.objId4.param = copy.deepcopy(self.fit_obj.def_param) self.fit_obj.fill_series_list() self.dTimeMin = 0 self.dTimeMax = np.max(self.dTimeArr) self.subDTimeMin = self.dTimeMin self.subDTimeMax = self.dTimeMax self.exit = False del self.subChanArr del self.trueTimeArr del self.dTimeArr def crossAndAuto(self, trueTimeArr, subChanArr): # For each channel we loop through and find only those in the correct time gate. # We only want photons in channel 1 or two. y = trueTimeArr[subChanArr < 3] validPhotons = subChanArr[subChanArr < 3] # Creates boolean for photon events in either channel. num = np.zeros((validPhotons.shape[0], 2)) num[:, 0] = (np.array([np.array(validPhotons) == self.ch_present[0]])).astype(np.int32) if self.numOfCH == 2: num[:, 1] = (np.array([np.array(validPhotons) == self.ch_present[1]])).astype(np.int32) self.count0 = np.sum(num[:, 0]) self.count1 = np.sum(num[:, 1]) t1 = time.time() auto, self.autotime = tttr2xfcs( y, num, self.NcascStart, self.NcascEnd, self.Nsub) t2 = time.time() # Normalisation of the TCSPC data: maxY = np.ceil(max(self.trueTimeArr)) self.autoNorm = np.zeros((auto.shape)) self.autoNorm[:, 0, 0] = ( (auto[:, 0, 0]*maxY)/(self.count0*self.count0))-1 if self.numOfCH == 2: self.autoNorm[:, 1, 1] = ( (auto[:, 1, 1]*maxY)/(self.count1*self.count1))-1 self.autoNorm[:, 1, 0] = ( (auto[:, 1, 0]*maxY)/(self.count1*self.count0))-1 self.autoNorm[:, 0, 1] = ( (auto[:, 0, 1]*maxY)/(self.count0*self.count1))-1 # Normalisaation of the decay functions. self.photonDecayCh1Min = self.photonDecayCh1 - \ np.min(self.photonDecayCh1) self.photonDecayCh1Norm = self.photonDecayCh1Min / \ np.max(self.photonDecayCh1Min) if self.numOfCH == 2: self.photonDecayCh2Min = self.photonDecayCh2 - \ np.min(self.photonDecayCh2) self.photonDecayCh2Norm = self.photonDecayCh2Min / \ np.max(self.photonDecayCh2Min) return class subPicoObject(): def __init__(self, parentId, xmin, xmax, TGid, par_obj): # Binning window for decay function self.TGid = TGid # Parameters for auto-correlation and cross-correlation. self.parentId = parentId self.par_obj = par_obj self.NcascStart = self.parentId.NcascStart self.NcascEnd = self.parentId.NcascEnd self.Nsub = self.parentId.Nsub self.fit_obj = self.parentId.fit_obj self.ext = self.parentId.ext self.type = 'subObject' # Appends the object to the subObject register. self.par_obj.subObjectRef.append(self) self.unqID = self.par_obj.subNum self.parentUnqID = self.parentId.unqID #self.chanArr = parentObj.chanArr #self.trueTimeArr = self.parentId.trueTimeArr #self.dTimeArr = self.parentId.dTimeArr self.color = self.parentId.color self.numOfCH = self.parentId.numOfCH self.ch_present = self.parentId.ch_present self.photonCountBin = self.par_obj.photonCountBin self.filepath = str(self.parentId.filepath) self.xmin = xmin self.xmax = xmax self.nameAndExt = os.path.basename(self.filepath).split('.') self.name = 'TG-'+str(self.unqID)+'-xmin_'+str(round(xmin, 0)) + \ '-xmax_'+str(round(xmax, 0))+'-'+self.nameAndExt[0] self.objId1 = None self.objId2 = None self.objId3 = None self.objId4 = None self.processData() self.plotOn = True def processData(self): self.NcascStart = self.par_obj.NcascStart self.NcascEnd = self.par_obj.NcascEnd self.Nsub = self.par_obj.Nsub self.winInt = self.par_obj.winInt #self.subChanArr, self.trueTimeArr, self.dTimeArr,self.resolution = pt3import(self.filepath) if self.ext == 'pt3': self.subChanArr, self.trueTimeArr, self.dTimeArr, self.resolution = pt3import( self.filepath) if self.ext == 'csv': self.subChanArr, self.trueTimeArr, self.dTimeArr, self.resolution = csvimport( self.filepath) # If the file is empty. # if self.subChanArr == None: # Undoes any preparation of resource. # self.par_obj.subObjectRef.pop(-1) #self.exit = True # return self.subArrayGeneration(self.xmin, self.xmax) self.dTimeMin = self.parentId.dTimeMin self.dTimeMax = self.parentId.dTimeMax self.subDTimeMin = self.dTimeMin self.subDTimeMax = self.dTimeMax # Time series of photon counts. For visualisation. self.timeSeries1, self.timeSeriesScale1 = delayTime2bin(np.array( self.trueTimeArr)/1000000, np.array(self.subChanArr), self.ch_present[0], self.photonCountBin) unit = self.timeSeriesScale1[-1]/self.timeSeriesScale1.__len__() self.kcount_CH1 = np.average(self.timeSeries1) if self.numOfCH == 2: self.timeSeries2, self.timeSeriesScale2 = delayTime2bin(np.array( self.trueTimeArr)/1000000, np.array(self.subChanArr), self.ch_present[1], self.photonCountBin) unit = self.timeSeriesScale2[-1]/self.timeSeriesScale2.__len__() self.kcount_CH2 = np.average(self.timeSeries2) # Adds names to the fit function for later fitting. if self.objId1 == None: corrObj = corrObject(self.filepath, self.fit_obj) self.objId1 = corrObj.objId self.fit_obj.objIdArr.append(corrObj.objId) self.objId1.param = copy.deepcopy(self.fit_obj.def_param) self.objId1.name = self.name+'_CH0_Auto_Corr' self.objId1.ch_type = 0 # channel 0 Auto self.objId1.siblings = None self.objId1.prepare_for_fit() self.objId1.kcount = self.kcount_CH1 self.objId1.autoNorm = np.array(self.autoNorm[:, 0, 0]).reshape(-1) self.objId1.autotime = np.array(self.autotime).reshape(-1) self.objId1.param = copy.deepcopy(self.fit_obj.def_param) if self.numOfCH == 2: if self.objId3 == None: corrObj = corrObject(self.filepath, self.fit_obj) self.objId3 = corrObj.objId self.fit_obj.objIdArr.append(corrObj.objId) self.objId3.param = copy.deepcopy(self.fit_obj.def_param) self.objId3.name = self.name+'_CH1_Auto_Corr' self.objId3.ch_type = 1 # channel 1 Auto self.objId3.siblings = None self.objId3.prepare_for_fit() self.objId3.kcount = self.kcount_CH2 self.objId3.autoNorm = np.array(self.autoNorm[:, 1, 1]).reshape(-1) self.objId3.autotime = np.array(self.autotime).reshape(-1) self.objId3.param = copy.deepcopy(self.fit_obj.def_param) if self.objId2 == None: corrObj = corrObject(self.filepath, self.fit_obj) self.objId2 = corrObj.objId self.objId2.param = copy.deepcopy(self.fit_obj.def_param) self.fit_obj.objIdArr.append(corrObj.objId) self.objId2.name = self.name+'_CH01_Cross_Corr' self.objId2.ch_type = 2 # channel 01 Cross self.objId2.siblings = None self.objId2.prepare_for_fit() self.objId2.autoNorm = np.array(self.autoNorm[:, 0, 1]).reshape(-1) self.objId2.autotime = np.array(self.autotime).reshape(-1) self.objId2.param = copy.deepcopy(self.fit_obj.def_param) if self.objId4 == None: corrObj = corrObject(self.filepath, self.fit_obj) self.objId4 = corrObj.objId self.objId4.param = copy.deepcopy(self.fit_obj.def_param) self.fit_obj.objIdArr.append(corrObj.objId) self.objId4.name = self.name+'_CH10_Cross_Corr' self.objId4.ch_type = 3 # channel 10 Cross self.objId4.siblings = None self.objId4.prepare_for_fit() self.objId4.autoNorm = np.array(self.autoNorm[:, 1, 0]).reshape(-1) self.objId4.autotime = np.array(self.autotime).reshape(-1) self.fit_obj.fill_series_list() del self.subChanArr del self.trueTimeArr del self.dTimeArr def subArrayGeneration(self, xmin, xmax): if(xmax < xmin): xmin1 = xmin xmin = xmax xmax = xmin1 #self.subChanArr = np.array(self.chanArr) # Finds those photons which arrive above certain time or below certain time. photonInd = np.logical_and( self.dTimeArr >= xmin, self.dTimeArr <= xmax).astype(np.bool) self.subChanArr[np.invert(photonInd).astype(np.bool)] = 16 self.crossAndAuto() return def crossAndAuto(self): # We only want photons in channel 1 or two. validPhotons = self.subChanArr[self.subChanArr < 3] y = self.trueTimeArr[self.subChanArr < 3] # Creates boolean for photon events in either channel. num = np.zeros((validPhotons.shape[0], 2)) num[:, 0] = (np.array([np.array(validPhotons) == self.ch_present[0]])).astype(np.int) if self.numOfCH == 2: num[:, 1] = (np.array([np.array(validPhotons) == self.ch_present[1]])).astype(np.int) self.count0 = np.sum(num[:, 0]) self.count1 = np.sum(num[:, 1]) # Function which calculates auto-correlation and cross-correlation. auto, self.autotime = tttr2xfcs( y, num, self.NcascStart, self.NcascEnd, self.Nsub) maxY = np.ceil(max(self.trueTimeArr)) self.autoNorm = np.zeros((auto.shape)) self.autoNorm[:, 0, 0] = ( (auto[:, 0, 0]*maxY)/(self.count0*self.count0))-1 if self.numOfCH == 2: self.autoNorm[:, 1, 1] = ( (auto[:, 1, 1]*maxY)/(self.count1*self.count1))-1 self.autoNorm[:, 1, 0] = ( (auto[:, 1, 0]*maxY)/(self.count1*self.count0))-1 self.autoNorm[:, 0, 1] = ( (auto[:, 0, 1]*maxY)/(self.count0*self.count1))-1 return class corrObject(): def __init__(self, filepath, parentFn): # the container for the object. self.parentFn = parentFn self.type = 'corrObject' self.filepath = str(filepath) self.nameAndExt = os.path.basename(self.filepath).split('.') self.name = self.nameAndExt[0] self.ext = self.nameAndExt[-1] self.autoNorm = [] self.autotime = [] self.model_autoNorm = [] self.model_autotime = [] self.datalen = [] self.objId = self self.param = [] self.goodFit = True self.fitted = False self.checked = False self.clicked = False self.toFit = False self.kcount = None self.filter = False def prepare_for_fit(self): if self.parentFn.ch_check_ch0.isChecked() == True and self.ch_type == 0: self.toFit = True if self.parentFn.ch_check_ch1.isChecked() == True and self.ch_type == 1: self.toFit = True if self.parentFn.ch_check_ch01.isChecked() == True and self.ch_type == 2: self.toFit = True if self.parentFn.ch_check_ch10.isChecked() == True and self.ch_type == 3: self.toFit = True # self.parentFn.modelFitSel.clear() # for objId in self.parentFn.objIdArr: # if objId.toFit == True: # self.parentFn.modelFitSel.addItem(objId.name) self.parentFn.updateFitList() def residual(self, param, x, data, options): A = equation_(param, x, options) residuals = data-A return residuals def fitToParameters(self): # self.parentFn.updateParamFirst() # self.parentFn.updateTableFirst() # self.parentFn.updateParamFirst() # Populate param for lmfit. param = Parameters() #self.def_param.add('A1', value=1.0, min=0,max=1.0, vary=False) for art in self.param: if self.param[art]['to_show'] == True: param.add(art, value=float(self.param[art]['value']), min=float( self.param[art]['minv']), max=float(self.param[art]['maxv']), vary=self.param[art]['vary']) # Find the index of the nearest point in the scale. data = np.array(self.autoNorm).astype(np.float64).reshape(-1) scale = np.array(self.autotime).astype(np.float64).reshape(-1) self.indx_L = int(np.argmin(np.abs(scale - self.parentFn.dr.xpos))) self.indx_R = int(np.argmin(np.abs(scale - self.parentFn.dr1.xpos))) # Run the fitting. res = minimize(self.residual, param, args=( scale[self.indx_L:self.indx_R+1], data[self.indx_L:self.indx_R+1], self.parentFn.def_options)) # Repopulate the parameter object. for art in self.param: if self.param[art]['to_show'] == True and self.param[art]['calc'] == False: self.param[art]['value'] = param[art].value self.param[art]['stderr'] = float(param[art].stderr) # Extra parameters, which are not fit or inherited. #self.param['N_FCS']['value'] = np.round(1/self.param['GN0']['value'],4) self.residualVar = res.residual output = fit_report(param) print('residual', res.chisqr) if(res.chisqr > 0.05): print('CAUTION DATA DID NOT FIT WELL CHI^2 >0.05', res.chisqr) self.goodFit = False else: self.goodFit = True self.fitted = True self.chisqr = res.chisqr self.localTime = time.asctime(time.localtime(time.time())) # self.parentFn.updateTableFirst(); self.model_autoNorm = equation_( param, scale[self.indx_L:self.indx_R+1], self.parentFn.def_options) self.model_autotime = scale[self.indx_L:self.indx_R+1] # self.parentFn.on_show() #self.parentFn.axes.plot(model_autotime,model_autoNorm, 'o-') # self.parentFn.canvas.draw(); def load_from_file(self, channel): tscale = [] tdata = [] int_tscale = [] int_tdata = [] if self.ext == 'fcs': corrObj = self text = [0] r_obj = csv.reader(open(corrObj.filepath, 'rb'), delimiter='\t') title = r_obj.next() line = r_obj.next() line = r_obj.next() name = line[1].split(' = ')[1] read = True while read == True: corrObj = corrObject(self.filepath, self.parentFn) self.parentFn.objIdArr.append(corrObj) corrObj.name = name line = r_obj.next() text = [] for part in line: if part != '': text.append(part) # Reads to first correlation array text. while text[0].split(' = ')[0] != 'CorrelationArray' or text[0].split(' = ')[1] == int(0): line = r_obj.next() text = [] for part in line: if part != '': text.append(part) line = r_obj.next() tdata = [] tscale = [] while text[0].split(' = ')[0] != 'PhotonCountHistogramArraySize': try: line = r_obj.next() except: read = False break text = [] for part in line: if part != '': text.append(part) if text.__len__() > 1: tscale.append(float(text[0])) tdata.append(float(text[1])) if tdata.__len__() == 0: corrObj = [] self.parentFn.objIdArr.pop(-1) break channel = 0 corrObj.siblings = None corrObj.autoNorm = np.array( tdata).astype(np.float64).reshape(-1) corrObj.autotime = np.array(tscale).astype( np.float64).reshape(-1)*1000 corrObj.name = corrObj.name+'-CH'+str(channel) corrObj.ch_type = channel corrObj.param = copy.deepcopy(self.parentFn.def_param) self.parentFn.fill_series_list() if self.ext == 'SIN': self.parentFn.objIdArr.append(self.objId) proceed = False for line in csv.reader(open(self.filepath, 'rb'), delimiter='\t'): if proceed == 'correlated': if line == []: proceed = False else: tscale.append(float(line[0])) tdata.append(float(line[channel+1])) if proceed == 'intensity': if line == []: proceed = False elif line.__len__() > 1: int_tscale.append(float(line[0])) int_tdata.append(float(line[channel+1])) if (str(line) == "[\'[CorrelationFunction]\']"): proceed = 'correlated' elif (str(line) == "[\'[IntensityHistory]\']"): proceed = 'intensity' self.siblings = None self.autoNorm = np.array(tdata).astype(np.float64).reshape(-1) self.autotime = np.array(tscale).astype( np.float64).reshape(-1)*1000 self.name = self.name+'-CH'+str(channel) self.ch_type = channel # self.prepare_for_fit() # Average counts per bin. For it to be seconds (Hz), must divide by duration. unit = int_tscale[-1]/(int_tscale.__len__()-1) # And to be in kHz we divide by 1000. self.kcount = np.average(np.array(int_tdata)/unit)/1000 self.param = copy.deepcopy(self.parentFn.def_param) self.parentFn.fill_series_list() # Where we add the names. if self.ext == 'csv' or self.ext == 'CSV': r_obj = csv.reader(open(self.filepath, 'rb')) line_one = r_obj.next() if line_one.__len__() > 1: if float(line_one[1]) == 2: version = 2 else: print('version not known:', line_one[1]) else: version = 1 if version == 1: self.parentFn.objIdArr.append(self) c = 0 for line in csv.reader(open(self.filepath, 'rb')): if (c > 0): tscale.append(line[0]) tdata.append(line[1]) c += 1 self.autoNorm = np.array(tdata).astype(np.float64).reshape(-1) self.autotime = np.array(tscale).astype(np.float64).reshape(-1) self.name = self.name+'-CH'+str(0) self.ch_type = 0 self.datalen = len(tdata) self.param = copy.deepcopy(self.parentFn.def_param) self.parentFn.fill_series_list() if version >= 2: numOfCH = float(r_obj.next()[1]) if numOfCH == 1: self.parentFn.objIdArr.append(self) self.type = str(r_obj.next()[1]) self.ch_type = int(r_obj.next()[1]) self.name = self.name+'-CH'+str(self.ch_type) line = r_obj.next() while line[0] != 'Time (ns)': if line[0] == 'kcount': self.kcount = float(line[1]) if line[0] == 'numberNandB': self.numberNandB = float(line[1]) if line[0] == 'brightnessNandB': self.brightnessNandB = float(line[1]) if line[0] == 'CV': self.CV = float(line[1]) if line[0] == 'carpet pos': carpet = int(line[1]) if line[0] == 'pc': pc_text = int(line[1]) if line[0] == 'pbc_f0': self.pbc_f0 = float(line[1]) if line[0] == 'pbc_tb': self.pbc_tb = float(line[1]) line = r_obj.next() if pc_text != False: self.name = self.name + '_pc_m'+str(pc_text) tscale = [] tdata = [] null = r_obj.next() line = r_obj.next() while line[0] != 'end': tscale.append(line[0]) tdata.append(line[1]) line = r_obj.next() self.autoNorm = np.array(tdata).astype( np.float64).reshape(-1) self.autotime = np.array(tscale).astype( np.float64).reshape(-1) self.siblings = None self.param = copy.deepcopy(self.parentFn.def_param) self.parentFn.fill_series_list() if numOfCH == 2: corrObj2 = corrObject(self.filepath, self.parentFn) corrObj3 = corrObject(self.filepath, self.parentFn) self.parentFn.objIdArr.append(self) self.parentFn.objIdArr.append(corrObj2) self.parentFn.objIdArr.append(corrObj3) line_type = r_obj.next() self.type = str(line_type[1]) corrObj2.type = str(line_type[1]) corrObj3.type = str(line_type[1]) line_ch = r_obj.next() self.ch_type = int(line_ch[1]) corrObj2.ch_type = int(line_ch[2]) corrObj3.ch_type = int(line_ch[3]) self.name = self.name+'-CH'+str(self.ch_type) corrObj2.name = corrObj2.name+'-CH'+str(corrObj2.ch_type) corrObj3.name = corrObj3.name+'-CH'+str(corrObj3.ch_type) line = r_obj.next() while line[0] != 'Time (ns)': if line[0] == 'kcount': self.kcount = float(line[1]) corrObj2.kcount = float(line[2]) if line[0] == 'numberNandB': self.numberNandB = float(line[1]) corrObj2.numberNandB = float(line[2]) if line[0] == 'brightnessNandB': self.brightnessNandB = float(line[1]) corrObj2.brightnessNandB = float(line[2]) if line[0] == 'CV': self.CV = float(line[1]) corrObj2.CV = float(line[2]) corrObj3.CV = float(line[3]) if line[0] == 'carpet pos': self.carpet_position = int(line[1]) if line[0] == 'pc': pc_text = int(line[1]) if line[0] == 'pbc_f0': self.pbc_f0 = float(line[1]) corrObj2.pbc_f0 = float(line[2]) if line[0] == 'pbc_tb': self.pbc_tb = float(line[1]) corrObj2.pbc_tb = float(line[2]) line = r_obj.next() if pc_text != False: self.name = self.name + '_pc_m'+str(pc_text) corrObj2.name = corrObj2.name + '_pc_m'+str(pc_text) corrObj3.name = corrObj3.name + '_pc_m'+str(pc_text) null = r_obj.next() line = r_obj.next() tscale = [] tdata0 = [] tdata1 = [] tdata2 = [] while line[0] != 'end': tscale.append(line[0]) tdata0.append(line[1]) tdata1.append(line[2]) tdata2.append(line[3]) line = r_obj.next() self.autotime = np.array(tscale).astype( np.float64).reshape(-1) corrObj2.autotime = np.array( tscale).astype(np.float64).reshape(-1) corrObj3.autotime = np.array( tscale).astype(np.float64).reshape(-1) self.autoNorm = np.array(tdata0).astype( np.float64).reshape(-1) corrObj2.autoNorm = np.array( tdata1).astype(np.float64).reshape(-1) corrObj3.autoNorm = np.array( tdata2).astype(np.float64).reshape(-1) self.siblings = [corrObj2, corrObj3] corrObj2.siblings = [self, corrObj3] corrObj3.siblings = [self, corrObj2] self.param = copy.deepcopy(self.parentFn.def_param) corrObj2.param = copy.deepcopy(self.parentFn.def_param) corrObj3.param = copy.deepcopy(self.parentFn.def_param) self.parentFn.fill_series_list() pycorrfit-1.1.7/pycorrfit/readfiles/read_pt3_scripts/fib4.pyx0000664000372000037200000000274413554642611025236 0ustar travistravis00000000000000# -*- coding: utf-8 -*- """ PicoQuant functionalities from FCS_viewer This file contains a fast implementation of an algorithm that is very important (yes I have no clue about the structure of pt3 files) for importing *.pt3 files: `dividAndConquer`. The code was written by Dr. Dominic Waithe Wolfson Imaging Centre. Weatherall Institute of Molecular Medicine. University of Oxford https://github.com/dwaithe/FCS_viewer See Also: The wrapper: `read_pt3_PicoQuant.py` The wrapped file: `read_pt3_PicoQuant_original_FCSViewer.py`. """ import cython cimport cython import numpy as np cimport numpy as np DTYPE = np.float64 ctypedef np.float64_t DTYPE_t @cython.boundscheck(False) @cython.wraparound(False) @cython.nonecheck(False) def dividAndConquer(arr1b,arr2b,arrLength): """divide and conquer fast intersection algorithm. Waithe D 2014""" cdef np.ndarray[DTYPE_t, ndim=1] arr1bool = np.zeros((arrLength-1)) cdef np.ndarray[DTYPE_t, ndim=1] arr2bool = np.zeros((arrLength-1)) cdef np.ndarray[DTYPE_t, ndim=1] arr1 = arr1b cdef np.ndarray[DTYPE_t, ndim=1] arr2 = arr2b cdef int arrLen arrLen = arrLength; cdef int i i = 0; cdef int j j = 0; while(i = bestlength: # We want about 500 bins # We need to sum over intervals of length *teiler* teiler = int(np.floor(len(trace)/bestlength)) newlength = int(np.floor(len(trace)/teiler)) newsignal = np.zeros(newlength) # Simultaneously sum over all intervals for j in np.arange(teiler): newsignal = \ newsignal+trace[j:newlength*teiler:teiler][:, 1] newsignal = 1. * newsignal / teiler newtimes = trace[teiler-1:newlength*teiler:teiler][:, 0] if len(trace) % teiler != 0: # We have a rest signal # We average it and add it to the trace rest = trace[newlength*teiler:][:, 1] lrest = len(rest) rest = np.array([sum(rest)/lrest]) newsignal = np.concatenate((newsignal, rest), axis=0) timerest = np.array([trace[-1][0]]) newtimes = np.concatenate((newtimes, timerest), axis=0) newtrace = np.zeros((len(newtimes), 2)) newtrace[:, 0] = newtimes newtrace[:, 1] = newsignal else: # Declare newtrace - # otherwise we have a problem down three lines ;) newtrace = trace return newtrace pycorrfit-1.1.7/pycorrfit/readfiles/read_mat_ries.py0000664000372000037200000001643613554642611023553 0ustar travistravis00000000000000""".mat files (Ries)""" import pathlib import warnings import numpy as np # On the windows machine the matlab binary import raised a warning. # We want to catch that warning, since importing ries's files works. with warnings.catch_warnings(): warnings.simplefilter("ignore") try: # scipy.io might not work on OSX (wrong architecture) from scipy.io.matlab.mio5_params import mat_struct from scipy.io import loadmat except: print(" Error: import error in scipys 'matlab' submodule.") print(" Try upgrading python-scipy or ignore this") print(" error if you are not using .mat files that") print(" were generated by programs by Jonas Ries.") def openMAT(path, filename=None): """ Read mat files that Jonas Ries used in his programs. For opening .mat files, this helped a lot: http://stackoverflow.com/questions/7008608/ scipy-io-loadmat-nested-structures-i-e-dictionaries The structure has been derived from "corrSFCS.m" from the SFCS.m program from Jonas Ries. """ path = pathlib.Path(path) if filename is not None: warnings.warn("Using `filename` is deprecated.", DeprecationWarning) path = path / filename filename = path.name # initiate lists correlations = list() traces = list() curvelist = list() # Import everything inside the mat file as big iterated dictionary alldata = loadmat(path) # Correlation functions are stored in "g" g = alldata["g"] # Get all Autocorrelation functions try: # ac for autocorrelation ac = g["ac"] except KeyError: pass else: # Workaround for single ACs, they are not stored in a separate list, # but directly inserted into g["ac"]. We put it in a list. # This is not the case for the trace averages. # There are a maximum of 4 autocorrelation functions in one file, # as far as I know. if len(ac) > 4: ac = [ac] g["act"] = [g["act"]] for i in np.arange(len(ac)): corr = ac[i] try: times = g["act"][i] except KeyError: pass else: # Another workaround # Sometimes, there's just one curve, which # means that corr[0] has no length. if len(np.atleast_1d(corr[0])) == 1: final = np.zeros((len(corr), 2)) final[:, 0] = times final[:, 1] = corr correlations.append(final) curvelist.append("AC"+str(i+1)) try: # only trace averages are saved traceavg = g["trace"][i] except: # No trace traces.append(None) else: trace = np.zeros((2, 2)) trace[1, 0] = 1.0 trace[:, 1] = traceavg traces.append(trace) elif len(corr) == len(times): for j in np.arange(len(corr[0])): final = np.zeros((len(corr), 2)) final[:, 0] = times final[:, 1] = corr[:, j] correlations.append(final) curvelist.append("AC"+str(i+1)) try: # only trace averages are saved traceavg = g["trace"][i][j] except: # No trace traces.append(None) else: trace = np.zeros((2, 2)) trace[1, 0] = 1.0 trace[:, 1] = traceavg traces.append(trace) # Get dc "dual color" functions try: dc = g["dc"] except KeyError: pass else: for i in np.arange(len(dc)): corr = dc[i] try: times = g["dct"][i] except KeyError: pass else: if len(corr) == len(times): for j in np.arange(len(corr[0])): final = np.zeros((len(corr), 2)) final[:, 0] = times final[:, 1] = corr[:, j] correlations.append(final) curvelist.append("CC dual color "+str(i+1)) traces.append(None) # Get twof "two focus" functions try: twof = g["twof"] except KeyError: pass else: for i in np.arange(len(dc)): corr = twof[i] try: times = g["twoft"][i] except KeyError: pass else: if len(corr) == len(times): for j in np.arange(len(corr[0])): final = np.zeros((len(corr), 2)) final[:, 0] = times final[:, 1] = corr[:, j] correlations.append(final) curvelist.append("CC two foci "+str(i+1)) traces.append(None) # Get dc2f "dual color two focus" functions try: g["dc2f"] except KeyError: pass else: for i in np.arange(len(dc)): corr = twof[i] try: times = g["dc2ft"][i] except KeyError: pass else: if len(corr) == len(times): for j in np.arange(len(corr[0])): final = np.zeros((len(corr), 2)) final[:, 0] = times final[:, 1] = corr[:, j] correlations.append(final) curvelist.append("CC dual color two foci "+str(i+1)) traces.append(None) dictionary = dict() dictionary["Correlation"] = correlations dictionary["Trace"] = traces dictionary["Type"] = curvelist filelist = list() for i in curvelist: filelist.append(filename) dictionary["Filename"] = filelist return dictionary def loadmat(filename): ''' this function should be called instead of direct scipy.io.loadmat as it cures the problem of not properly recovering python dictionaries from mat files. It calls the function check keys to cure all entries which are still mat-objects ''' data = loadmat(filename, struct_as_record=False, squeeze_me=True) return _check_keys(data) def _check_keys(adict): ''' checks if entries in dictionary are mat-objects. If yes todict is called to change them to nested dictionaries ''' for key in adict: if isinstance(adict[key], mat_struct): adict[key] = _todict(adict[key]) return adict def _todict(matobj): ''' A recursive function which constructs from matobjects nested dictionaries ''' adict = {} for strg in matobj._fieldnames: elem = matobj.__dict__[strg] if isinstance(elem, mat_struct): adict[strg] = _todict(elem) else: adict[strg] = elem return adict pycorrfit-1.1.7/pycorrfit/readfiles/read_pt3_PicoQuant.py0000664000372000037200000000761713554642611024442 0ustar travistravis00000000000000"""Wrapper for Loading PicoQuant .pt3 data files Wraps around FCS_point_correlator by Dominic Waithe https://github.com/dwaithe/FCS_point_correlator """ import pathlib import warnings import numpy as np from .read_pt3_scripts.correlation_objects import picoObject from . import util class ParameterClass(): """Stores parameters for correlation """ def __init__(self): # Where the data is stored. self.data = [] self.objectRef = [] self.subObjectRef = [] self.colors = ['blue', 'green', 'red', 'cyan', 'magenta', 'yellow', 'black'] self.numOfLoaded = 0 self.NcascStart = 0 self.NcascEnd = 25 self.Nsub = 6 self.winInt = 10 self.photonCountBin = 25 def getTrace(picoObject, number): """ Extracts trace `number` from a `picoObject`. Parameters ---------- picoObject: instance of picoObject The data retreived from a pt3 file number: The id of the trace, can be 1 or 2. """ attrint = "timeSeries{}".format(number) attrtime = "timeSeriesScale{}".format(number) # binned photon counts intensity = np.array(getattr(picoObject, attrint)) # Time in ms for each bin time = np.array(getattr(picoObject, attrtime)) # time delta deltat = np.abs(time[2]-time[1]) trace = np.zeros((intensity.shape[0], 2)) trace[:, 0] = time # ms trace[:, 1] = intensity / deltat # kHz # If the trace is too big. Wee need to bin it. newtrace = util.downsample_trace(trace) return newtrace def openPT3(path, filename=None): """ Retreive correlation curves from PicoQuant data files This function is a wrapper around the PicoQuant capability of FCS_Viewer by Dominic Waithe. """ path = pathlib.Path(path) if filename is not None: warnings.warn("Using `filename` is deprecated.", DeprecationWarning) path = path / filename filename = path.name par_obj = ParameterClass() pt3file = picoObject(str(path), par_obj, None) po = pt3file auto = po.autoNorm # lag time [ms] autotime = po.autotime.reshape(-1) corrlist = list() typelist = list() tracelist = list() # Some data points are zero for some reason id1 = np.where(autotime != 0) # AC0 - autocorrelation CH0 corrac0 = auto[:, 0, 0] if np.sum(np.abs(corrac0[id1])) != 0: typelist.append("AC0") # autotime,auto[:,0,0] corrlist.append(np.hstack((autotime[id1].reshape(-1, 1), corrac0[id1].reshape(-1, 1)))) tracelist.append([getTrace(po, 1)]) # AC1 - autocorrelation CH1 corrac1 = auto[:, 1, 1] if np.sum(np.abs(corrac1[id1])) != 0: typelist.append("AC1") # autotime,auto[:,1,1] corrlist.append(np.hstack((autotime[id1].reshape(-1, 1), corrac1[id1].reshape(-1, 1)))) tracelist.append([getTrace(po, 2)]) # CC01 - Cross-Correlation CH0-CH1 corrcc01 = auto[:, 0, 1] if np.sum(np.abs(corrcc01[id1])) != 0: typelist.append("CC01") # autotime,auto[:,0,1] corrlist.append(np.hstack((autotime[id1].reshape(-1, 1), corrcc01[id1].reshape(-1, 1)))) tracelist.append([getTrace(po, 1), getTrace(po, 2)]) # CC10 - Cross-Correlation CH1-CH0 corrcc10 = auto[:, 1, 0] if np.sum(np.abs(corrcc10[id1])) != 0: typelist.append("CC10") # autotime,auto[:,1,0] corrlist.append(np.hstack((autotime[id1].reshape(-1, 1), corrcc10[id1].reshape(-1, 1)))) tracelist.append([getTrace(po, 1), getTrace(po, 2)]) filelist = [filename] * len(typelist) dictionary = dict() dictionary["Correlation"] = corrlist dictionary["Trace"] = tracelist dictionary["Type"] = typelist dictionary["Filename"] = filelist return dictionary pycorrfit-1.1.7/pycorrfit/readfiles/read_CSV_PyCorrFit.py0000664000372000037200000001552513554642611024342 0ustar travistravis00000000000000"""CSV files""" import csv import pathlib import warnings import numpy as np def openCSV(path, filename=None): """ Read relevant data from a file looking like this: [...] # Comment # Data type: Autocorrelation [...] 1.000000e-006 3.052373e-001 1.020961e-006 3.052288e-001 1.042361e-006 3.052201e-001 1.064209e-006 3.052113e-001 1.086516e-006 3.052023e-001 1.109290e-006 3.051931e-001 [...] # BEGIN TRACE [...] 10.852761 31.41818 12.058624 31.1271 13.264486 31.27305 14.470348 31.33442 15.676211 31.15861 16.882074 31.08564 18.087936 31.21335 [...] The correlation part could also look like this: # Channel (tau [s]) Experimental correlation Fitted correlation Residuals Weights [model function] 2.0000000000e-07 1.5649271000e-01 1.5380094370e-01 2.6917663029e-03 7.3158300646e-03 4.0000000000e-07 1.4751239000e-01 1.5257959602e-01 -5.0672060199e-03 5.8123579098e-03 6.0000000000e-07 1.5145113000e-01 1.5137624642e-01 7.4883584881e-05 8.5622019656e-03 8.0000000000e-07 1.5661088000e-01 1.5019053433e-01 6.4203456659e-03 6.8098486549e-03 1.0000000000e-06 1.5456273000e-01 1.4902210818e-01 5.5406218229e-03 7.2476381023e-03 1.2000000000e-06 1.3293905000e-01 1.4787062503e-01 -1.4931575028e-02 6.9861494246e-03 1.4000000000e-06 1.4715790000e-01 1.4673575040e-01 4.2214960494e-04 6.9810206017e-03 1.6000000000e-06 1.5247520000e-01 1.4561715797e-01 6.8580420325e-03 6.6680066656e-03 1.8000000000e-06 1.4703974000e-01 1.4451452937e-01 2.5252106284e-03 6.3299717550e-03 In that case we are also importing the weights. Data type: If Data type is "Cross-correlation", we will try to import two traces after "# BEGIN SECOND TRACE" 1st section: First column denotes tau in seconds and the second row the correlation signal. 2nd section: First column denotes tau in seconds and the second row the intensity trace in kHz. Returns: 1. A list with tuples containing two elements: 1st: tau in ms 2nd: corresponding correlation signal 2. None - usually is the trace, but the trace is not saved in the PyCorrFit .csv format. 3. A list with one element, indicating, that we are opening only one correlation curve. """ path = pathlib.Path(path) if filename is not None: warnings.warn("Using `filename` is deprecated.", DeprecationWarning) path = path / filename filename = path.name # Check if the file is correlation data with path.open("r", encoding='utf-8') as fd: firstline = fd.readline() if firstline.lower().count("this is not correlation data") > 0: return None # Define what will happen to the file timefactor = 1000 # because we want ms instead of s csvfile = path.open('r', encoding='utf-8') readdata = csv.reader(csvfile, delimiter=',') data = list() weights = list() weightname = "external" trace = None traceA = None DataType = "AC" # May be changed numtraces = 0 prev_row = None for row in readdata: if len(row) == 0 or len(str(row[0]).strip()) == 0: # Do nothing with empty/whitespace lines pass # Beware that the len(row) statement has to be called first # (before the len(str(row[0]).strip()) ). Otherwise some # error would be raised. elif str(row[0])[:12].lower() == "# Type AC/CC".lower(): corrtype = str(row[0])[12:].strip().strip(":").strip() if corrtype[:17].lower() == "cross-correlation": # We will later try to import a second trace DataType = "CC" DataType += corrtype[17:].strip() elif corrtype[0:15].lower() == "autocorrelation": DataType = "AC" DataType += corrtype[15:].strip() elif str(row[0])[0:13].upper() == '# BEGIN TRACE': # Correlation is over. We have a trace corr = np.array(data) data = list() numtraces = 1 elif str(row[0])[0:20].upper() == '# BEGIN SECOND TRACE': # First trace is over. We have a second trace traceA = np.array(data) data = list() numtraces = 2 # Exclude commentaries elif str(row[0])[0:1] != '#': # Read the 1st section # On Windows we had problems importing nan values that # had some white-spaces around them. Therefore: strip() # As of version 0.7.8 we are supporting white space # separated values as well if len(row) == 1: row = row[0].split() data.append((np.float(row[0].strip())*timefactor, np.float(row[1].strip()))) if len(row) == 5: # this has to be correlation with weights weights.append(np.float(row[4].strip())) if weightname == "external": try: weightname = "ext. " + \ prev_row[0].split("Weights")[1].split( "[")[1].split("]")[0] except: pass prev_row = row # Collect the rest of the trace, if there is any: rest = np.array(data) if numtraces == 0: corr = rest elif numtraces >= 1: trace = rest del data # Remove any NaN numbers from thearray # Explanation: # np.isnan(data) # finds the position of NaNs in the array (True positions); 2D array, bool # any(1) # finds the rows that have True in them; 1D array, bool # ~ # negates them and is given as an argument (array type bool) to # select which items we want. corr = corr[~np.isnan(corr).any(1)] # Also check for infinities. corr = corr[~np.isinf(corr).any(1)] csvfile.close() Traces = list() # Set correct trace data for import if numtraces == 1 and DataType[:2] == "AC": Traces.append(trace) elif numtraces == 2 and DataType[:2] == "CC": Traces.append([traceA, trace]) elif numtraces == 1 and DataType[:2] == "CC": # Should not happen, but for convenience: Traces.append([trace, trace]) else: Traces.append(None) dictionary = dict() dictionary["Correlation"] = [corr] dictionary["Trace"] = Traces dictionary["Type"] = [DataType] dictionary["Filename"] = [filename] if len(weights) != 0: dictionary["Weight"] = [np.array(weights)] dictionary["Weight Name"] = [weightname] return dictionary pycorrfit-1.1.7/pycorrfit/readfiles/read_FCS_Confocor3.py0000664000372000037200000004030513554642611024266 0ustar travistravis00000000000000"""Confocor .fcs files""" import csv import pathlib import warnings import numpy as np from . import util def openFCS(path, filename=None): """ Load data from Zeiss Confocor3 Data is imported sequenially from the file. PyCorrFit will give each curve an id which corresponds to the position of the curve in the .fcs file. The AIM software can save data as multiple or single data files. The type is identified by the first line of the .fcs file. This works with files from the Confocor2, Confocor3 (AIM) and files created from the newer ZEN Software. This function is a wrapper combining *openFCS_Single* and *openFCS_Multiple* """ path = pathlib.Path(path) if filename is not None: warnings.warn("Using `filename` is deprecated.", DeprecationWarning) path = path / filename with path.open('r') as fd: identitystring = fd.readline().strip()[:20] if identitystring == "Carl Zeiss ConfoCor3": return openFCS_Multiple(path) else: return openFCS_Single(path) def openFCS_Multiple(path): """ Load data from Zeiss Confocor3 Data is imported sequenially from the file. PyCorrFit will give each curve an id which corresponds to the position of the curve in the .fcs file. This works with files from the Confocor2, Confocor3 (AIM) and files created from the newer ZEN Software. """ filename = path.name # TODO: # match curves with their timestamp # (actimelist and cctimelist) # with path.open("r", encoding="iso8859_15") as fd: Alldata = fd.readlines() # Start progressing through the file. i is the line index. # We are searching for "FcsDataSet" sections that contain # all the information we want. # index i for linenumber i = 0 # A parameter to check whether we are in a "FcsDataSet" section # and should import something fcsset = False # The names of the traces aclist = [] # All autocorrelation functions cclist = [] # All cross-correlation functions # The intensity traces traces = [] # we use "AcquisitionTime" to match up curves thistime = None actimelist = [] cctimelist = [] # The correlation curves ac_correlations = [] cc_correlations = [] # The names of the correlation channels channels = {} ac_count = 0 while i <= len(Alldata)-1: if Alldata[i].count("FcsDataSet") == 1: # We are in a "FcsDataSet" section fcsset = True gottrace = False if fcsset == True: # Check key-value if Alldata[i].count("="): current_key = Alldata[i].split("=")[0].strip() current_value = Alldata[i].split("=")[1].strip() else: i = i + 1 continue # Extract data if current_key == "AcquisitionTime": thistime = current_value elif current_key == "Channel": # Find out what type of correlation curve we have. # Might be interesting to the user. FCStype = current_value FoundType = False idauto = "Auto-correlation detector" idcross = "Cross-correlation detector" if FCStype.startswith(idauto): chid = FCStype.rsplit(" ", 1)[1] if chid not in channels: ac_count += 1 channels[chid] = ac_count FoundType = "AC"+str(channels[chid]) aclist.append(FoundType) actimelist.append(thistime) elif FCStype.startswith(idcross): chid1, chid2 = FCStype[len(idcross):].strip().split( " versus detector ") if chid1 in channels: chnum1 = channels[chid1] else: raise NotImplementedError("AC must come before CC!") if chid2 in channels: chnum2 = channels[chid2] else: raise NotImplementedError("AC must come before CC!") FoundType = "CC"+str(chnum1)+str(chnum2) cclist.append(FoundType) cctimelist.append(thistime) else: # Jump out of this set. We will continue at # the next "FcsDataSet"-section. warnings.warn("Unknown channel configuration " "'{}' in '{}'!".format(FCStype, path)) fcsset = False elif current_key == "CountRateArray": # Start importing the trace. This is a little difficult, since # traces in those files are usually very large. We will bin # the trace and import a lighter version of it. tracelength = int(current_value.split()[0]) if tracelength != 0: tracedata = Alldata[i+1: i+tracelength+1] # Jump foward in the index i = i + tracelength readtrace = csv.reader(tracedata, delimiter='\t') trace = [] for row in readtrace: # tau in ms, trace in kHz # So we need to put some factors here trace.append((np.float(row[3])*1000, np.float(row[4])/1000)) trace = np.array(trace) # If the trace is too big. Wee need to bin it. newtrace = util.downsample_trace(trace) # Finally add the trace to the list traces.append(newtrace) if FoundType[:2] != "AC": # For every trace there is an entry in aclist print("Trace data saved in CC section." + "I cannot handle that.") gottrace = True elif current_key == "CorrelationArraySize": # Get the correlation information corrlength = int(current_value) if corrlength != 0: # For cross correlation or something sometimes # there is no trace information. if gottrace == False and FoundType[:2] == "AC": # We think we know that there is no trace in CC curves traces.append(None) corrdata = Alldata[i + 2: i + corrlength + 2] # Jump foward i = i + corrlength readcorr = csv.reader(corrdata, delimiter='\t') corr = [] for row in readcorr: # tau in ms, corr-function corr.append((np.float(row[3])*1000, np.float(row[4])-1)) if FoundType[:2] == "AC": ac_correlations.append(np.array(corr)) elif FoundType[:2] == "CC": cc_correlations.append(np.array(corr)) else: # There is no correlation data in the file # Fill in some dummy data. These will be removed. if FoundType[:2] == "AC": # append a dummy correlation curve ac_correlations.append(None) if gottrace == False: # append a dummy trace traces.append(None) elif FoundType[:2] == "CC": # append a dummy correlation curve # cc_correlations do not have traces cc_correlations.append(None) # We reached the end of this "FcsDataSet" section. fcsset = False i = i + 1 # We now have: # aclist: a list of AC curve names mentioned in the file. # cclist: a list of CC curve names mentioned in the file. # traces: All traces corresponding to non-"None"-type entries in # ac_correlations. Not in cc_correlations, # because cross-correlations are not saved with traces.? # # Identifiers: # actimelist: aquisition times according to aclist # cctimelist: aquisition times according to cclist # # Correlations: # ac_correlations: AC-correlation data in list. # cc_correlations: CC-correlation data in list. # # ac_correlations or cc_correlations can have items that are "None". # These item come from averaging inside the Confocor software and # do not contain any data. # These "None" type items should be at the end of these lists. # If the user created .fcs files with averages between the curves, # the *traces* contains *None* values at those positions. # We now create: # curvelist: All actually used data # tracelist: Traces brought into right form (also for CCs) # corrlist: Correlation curves # Index in curvelist defines index in trace and correlation. curvelist = [] tracelist = [] corrlist = [] # match up curves with their timestamps # (actimelist and cctimelist) knowntimes = [] for tid in actimelist: if tid not in knowntimes: knowntimes.append(tid) #n = actimelist.count(tid) actids = np.where(np.array(actimelist) == tid)[0] cctids = np.where(np.array(cctimelist) == tid)[0] if len(actids) == 0: warnings.warn("File {} timepoint {} has no AC data.". format(filename, tid)) elif len(actids) == 1: # single AC curve if ac_correlations[actids[0]] is not None: curvelist.append(aclist[actids[0]]) tracelist.append(1*traces[actids[0]]) corrlist.append(ac_correlations[actids[0]]) else: if traces[actids[0]] is not None: warnings.warn( "File {} curve {} does not contain AC data.".format(filename, tid)) elif len(actids) == 2: # Get AC data if aclist[actids[0]] == "AC1": acdat1 = ac_correlations[actids[0]] trace1 = traces[actids[0]] acdat2 = ac_correlations[actids[1]] trace2 = traces[actids[1]] elif aclist[actids[0]] == "AC2": acdat1 = ac_correlations[actids[1]] trace1 = traces[actids[1]] acdat2 = ac_correlations[actids[0]] trace2 = traces[actids[0]] else: warnings.warn( "File {} curve {}: unknown AC data.".format(filename, tid)) continue if acdat1 is not None: # AC1 curvelist.append("AC1") tracelist.append(trace1) corrlist.append(acdat1) if acdat2 is not None: # AC2 curvelist.append("AC2") tracelist.append(trace2) corrlist.append(acdat2) if len(cctids) == 2: # Get CC data if cclist[cctids[0]] == "CC12": ccdat12 = cc_correlations[cctids[0]] ccdat21 = cc_correlations[cctids[1]] elif cclist[cctids[0]] == "CC21": ccdat12 = cc_correlations[cctids[1]] ccdat21 = cc_correlations[cctids[0]] else: warnings.warn( "File {} curve {}: unknown CC data.".format(filename, tid)) continue tracecc = [trace1, trace2] if ccdat12 is not None: # CC12 curvelist.append("CC12") tracelist.append(tracecc) corrlist.append(ccdat12) if ccdat21 is not None: # CC21 curvelist.append("CC21") tracelist.append(tracecc) corrlist.append(ccdat21) dictionary = dict() dictionary["Correlation"] = corrlist dictionary["Trace"] = tracelist dictionary["Type"] = curvelist filelist = [] for i in curvelist: filelist.append(filename) dictionary["Filename"] = filelist return dictionary def openFCS_Single(path): """ Load data from Zeiss Confocor3 files containing only one curve. This works with files from the Confocor2, Confocor3 (AIM) and files created from the newer ZEN Software. """ filename = path.name with path.open("r", encoding="iso8859_15") as fd: Alldata = fd.readlines() # Start progressing through the file. i is the line index. # We are searching for "FcsDataSet" sections that contain # all the information we want. # index i for linenumber i = 0 # Indicates if trace or FCS curve should be imported in loop fcscurve = False tracecurve = False while i <= len(Alldata)-1: if Alldata[i].partition("=")[0].strip() == "##DATA TYPE": # Find out what type of correlation curve we have. # Might be interesting to the user. Type = Alldata[i].partition("=")[2].strip() if Type == "FCS Correlogram": fcscurve = True tracecurve = False elif Type == "FCS Count Rates": tracecurve = True fcscurve = False else: raise SyntaxError("Unknown file syntax: "+Type) i = i + 1 if tracecurve == True: if Alldata[i].partition("=")[0].strip() == "##NPOINTS": # Start importing the trace. This is a little difficult, since # traces in those files are usually very large. We will bin # the trace and import a lighter version of it. tracelength = int(Alldata[i].partition("=")[2].strip()) # Trace starts 3 lines after this. i = i + 3 if tracelength != 0: tracedata = Alldata.__getslice__(i, i+tracelength) # Jump foward in the index i = i + tracelength readtrace = csv.reader(tracedata, delimiter=',') trace = [] for row in readtrace: # tau in ms, trace in kHz # So we need to put some factors here trace.append((np.float(row[0])*1000, np.float(row[1]))) trace = np.array(trace) # If the trace is too big. Wee need to bin it. newtrace = util.downsample_trace(trace) tracecurve = False if fcscurve == True: if Alldata[i].partition("=")[0].strip() == "##NPOINTS": # Get the correlation information corrlength = int(Alldata[i].partition("=")[2].strip()) i = i + 2 if corrlength != 0: corrdata = Alldata.__getslice__(i, i+corrlength) # Jump foward i = i + corrlength readcorr = csv.reader(corrdata, delimiter=',') corr = [] for row in readcorr: # tau in ms, corr-function corr.append((np.float(row[0]), np.float(row[1])-1)) corr = np.array(corr) fcscurve = False # Check for correlation at lag-time zero, which lead to a bug (#64) # on mac OSx and potentially affects fitting. if corr[0][0] == 0: corr = corr[1:] dictionary = {} dictionary["Correlation"] = [corr] dictionary["Trace"] = [newtrace] dictionary["Type"] = [""] dictionary["Filename"] = [filename] return dictionary pycorrfit-1.1.7/pycorrfit/readfiles/__init__.py0000664000372000037200000002342113554642611022504 0ustar travistravis00000000000000"""Module readfiles: Import correlation data from data files""" import csv import io import pathlib import shutil import sys import tempfile import warnings import zipfile import numpy as np import yaml # To add a filetype add it here and in the # dictionaries at the end of this file. from .read_ASC_ALV import openASC from .read_CSV_PyCorrFit import openCSV from .read_SIN_correlator_com import openSIN from .read_FCS_Confocor3 import openFCS from .read_mat_ries import openMAT from .read_pt3_PicoQuant import openPT3 def add_all_supported_filetype_entry(adict): wildcard = "" keys = adict.keys() N = len(keys) i = 0 for key in keys: newwc = key.split("|")[1] wildcard = wildcard + newwc i = i + 1 if i != N: wildcard = wildcard + ";" adict[ALL_SUP_STRING+"|"+wildcard] = open_any def get_supported_extensions(): """ Returns list of extensions of currently supported file types. """ extlist = [] for kf in list(filetypes_dict.keys()): ext = kf.split("|")[-1] ext = ext.split(";") ext = [e.lower().strip("*. ") for e in ext] ext = list(np.unique(ext)) extlist += ext extlist = list(np.unique(extlist)) extlist.sort() return extlist # To increase user comfort, we will now create a file opener thingy that # knows how to open all files we know. def open_any(path, filename=None): """Open a supported data file Parameters ---------- path : str Full path to file or directory containing `filename` filename : str The name of the file if not given in path (optional). """ path = pathlib.Path(path) if filename is not None: warnings.warn("Using `filename` is deprecated.", DeprecationWarning) path = path / filename wildcard = path.suffix for key in filetypes_dict.keys(): # Recurse into the wildcards wildcardstring = key.split("|") # We do not want to recurse if wildcardstring[0] != ALL_SUP_STRING: otherwcs = wildcardstring[1].split(";") for wc in otherwcs: if wc.strip("*") == wildcard: return filetypes_dict[key](path) else: # If we could not find the correct function in filetypes_dict, # try again in filetypes_bg_dict: return open_any_bg(path) def open_any_bg(path, filename=None): path = pathlib.Path(path) if filename is not None: warnings.warn("Using `filename` is deprecated.", DeprecationWarning) path = path / filename wildcard = path.suffix for key in filetypes_bg_dict.keys(): # Recurse into the wildcards wildcardstring = key.split("|") # We do not want to recurse if wildcardstring[0] != ALL_SUP_STRING: otherwcs = wildcardstring[1].split(";") for wc in otherwcs: if wc.strip("*") == wildcard: return filetypes_bg_dict[key](path) else: # For convenience in openZIP return None def openZIP(path, filename=None): """Load everything inside a .zip file that could be an FCS curve. Will use any wildcard in `filetypes_dict`. """ # It's a rather lengthy import of the session file. The code is copied # from openfile.OpenSession. The usual zip file packed curves are # imported on the few code lines after the else statement. path = pathlib.Path(path) if filename is not None: warnings.warn("Using `filename` is deprecated.", DeprecationWarning) path = path / filename filename = path.name # Open the archive: Arc = zipfile.ZipFile(path, mode='r') Correlations = [] # Correlation information Curvelist = [] # Type information Filelist = [] # List of filenames corresponding to *Curvelist* Trace = [] # Corresponding traces # First test, if we are opening a session file sessionwc = [".fcsfit-session.zip", ".pcfs"] if filename.endswith(sessionwc[0]) or filename.endswith(sessionwc[1]): # Get the yaml parms dump: yamlfile = Arc.open("Parameters.yaml") # Parms: Fitting and drawing parameters of the correlation curve # The *yamlfile* is responsible for the order of the Pages #i. # The parameters are actually useless to us right now. Parms = yaml.safe_load(yamlfile) yamlfile.close() # Get the correlation arrays ImportedNum = list() for i in np.arange(len(Parms)): # The *number* is used to identify the correct file number = str(Parms[i][0]) expfilename = "data"+number[1:len(number)-2]+".csv" expfile = Arc.open(expfilename, 'r') readdata = csv.reader(io.StringIO( expfile.read().decode()), delimiter=',') dataexp = list() if str(readdata.__next__()[0]) == "# tau only": # We do not have a curve here pass else: Filelist.append(filename+"/#"+number[1:len(number)-2]) for row in readdata: # Exclude commentaries if (str(row[0])[0:1] != '#'): dataexp.append((float(row[0]), float(row[1]))) dataexp = np.array(dataexp) Correlations.append(dataexp) ImportedNum.append(i) del readdata expfile.close() # Get the Traces for i in ImportedNum: # Make sure we only import those traces that had a corresponding # correlation curve. (ImportedNum) # # The *number* is used to identify the correct file number = str(Parms[i][0]) # Find out, if we have a cross correlation data type IsCross = False try: IsCross = Parms[i][7] except IndexError: # No Cross correlation pass if IsCross is False: tracefilenames = ["trace"+number[1:len(number)-2]+".csv"] Curvelist.append("AC") else: # Cross correlation uses two traces tracefilenames = ["trace"+number[1:len(number)-2]+"A.csv", "trace"+number[1:len(number)-2]+"B.csv"] Curvelist.append("CC") thistrace = list() for tracefilename in tracefilenames: try: Arc.getinfo(tracefilename) except KeyError: # No correlation curve, but add a None pass else: tracefile = Arc.open(tracefilename, 'r') traceread = csv.reader(io.StringIO( tracefile.read().decode()), delimiter=',') singletrace = list() for row in traceread: # Exclude commentaries if (str(row[0])[0:1] != '#'): singletrace.append((float(row[0]), float(row[1]))) singletrace = np.array(singletrace) thistrace.append(singletrace) del traceread del singletrace tracefile.close() if len(thistrace) == 1: Trace.append(thistrace[0]) elif len(thistrace) == 2: Trace.append(thistrace) else: Trace.append(None) else: # We are not importing from a session but from a zip file with # probably a mix of all filetypes we know. This works # recursively (e.g. a zip file in a zipfile). allfiles = Arc.namelist() # Extract data to temporary folder tempdir = pathlib.Path(tempfile.mkdtemp()) for afile in allfiles: Arc.extract(afile, path=tempdir) data = open_any(tempdir / afile) if data is not None: Correlations += data["Correlation"] Trace += data["Trace"] Curvelist += data["Type"] fnames = data["Filename"] Filelist += [filename+"/"+fs for fs in fnames] shutil.rmtree(path=tempdir, ignore_errors=True) Arc.close() dictionary = {} dictionary["Correlation"] = Correlations dictionary["Trace"] = Trace dictionary["Type"] = Curvelist dictionary["Filename"] = Filelist return dictionary # The string that is shown when opening all supported files # We add an empty space so it is listed first in the dialogs. ALL_SUP_STRING = " All supported files" # Dictionary with filetypes that we can open # The wildcards point to the appropriate functions. filetypes_dict = {"Correlator.com (*.SIN)|*.SIN;*.sin": openSIN, "ALV (*.ASC)|*.ASC;*.asc": openASC, "PyCorrFit (*.csv)|*.csv": openCSV, "Matlab 'Ries (*.mat)|*.mat": openMAT, "PicoQuant (*.pt3)|*.pt3": openPT3, "Zeiss ConfoCor3 (*.fcs)|*.fcs": openFCS, "Zip file (*.zip)|*.zip": openZIP, "PyCorrFit session (*.pcfs)|*.pcfs": openZIP } # For user comfort, add "All supported files" wildcard: add_all_supported_filetype_entry(filetypes_dict) # Dictionary with filetypes we can open that have intensity traces in them. filetypes_bg_dict = {"Correlator.com (*.SIN)|*.SIN;*.sin": openSIN, "ALV (*.ASC)|*.ASC": openASC, "PyCorrFit (*.csv)|*.csv": openCSV, "PicoQuant (*.pt3)|*.pt3": openPT3, "Zeiss ConfoCor3 (*.fcs)|*.fcs": openFCS, "Zip file (*.zip)|*.zip": openZIP, "PyCorrFit session (*.pcfs)|*.pcfs": openZIP } add_all_supported_filetype_entry(filetypes_bg_dict) pycorrfit-1.1.7/pycorrfit/readfiles/read_ASC_ALV.py0000664000372000037200000005153113554642611023053 0ustar travistravis00000000000000"""ALV .ASC files""" import csv import pathlib import warnings import numpy as np class LoadALVError(BaseException): pass def openASC(path, filename=None): """ Read data from a ALV .ASC files. """ path = pathlib.Path(path) if filename is not None: warnings.warn("Using `filename` is deprecated.", DeprecationWarning) path = path / filename with path.open("r", encoding="iso8859_15") as openfile: first = openfile.readline() # Open special format? filetype = first.strip() if filetype.count("ALV-7004"): return openASC_ALV_7004(path) else: # last resort return openASC_old(path) def openASC_old(path): """ Read data from a .ASC file, created by some ALV-6000 correlator. ALV-6000/E-WIN Data Date : "2/20/2012" ... "Correlation" 1.25000E-004 3.00195E-001 2.50000E-004 1.13065E-001 3.75000E-004 7.60367E-002 5.00000E-004 6.29926E-002 6.25000E-004 5.34678E-002 7.50000E-004 4.11506E-002 8.75000E-004 4.36752E-002 1.00000E-003 4.63146E-002 1.12500E-003 3.78226E-002 ... 3.35544E+004 -2.05799E-006 3.77487E+004 4.09032E-006 4.19430E+004 4.26295E-006 4.61373E+004 1.40265E-005 5.03316E+004 1.61766E-005 5.45259E+004 2.19541E-005 5.87202E+004 3.26527E-005 6.29145E+004 2.72920E-005 "Count Rate" 1.17188 26.77194 2.34375 26.85045 3.51563 27.06382 4.68750 26.97932 5.85938 26.73694 7.03125 27.11332 8.20313 26.81376 9.37500 26.82741 10.54688 26.88801 11.71875 27.09710 12.89063 27.13209 14.06250 27.02200 15.23438 26.95287 16.40625 26.75657 17.57813 26.43056 ... 294.14063 27.22597 295.31250 26.40581 296.48438 26.33497 297.65625 25.96457 298.82813 26.71902 1. We are interested in the "Correlation" section, where the first column denotes tau in ms and the second row the correlation signal. Values are separated by a tabulator "\t" (some " "). 2. We are also interested in the "Count Rate" section. Here the times are saved as seconds and not ms like above. 3. There is some kind of mode where the ALV exports five runs at a time and averages them. The sole correlation data is stored in the file, but the trace is only stored as average or something. So I would not recommend this. However, I added support for this. PyCorrFit then only imports the average data. ~ Paul, 2012-02-20 Correlation data starts at "Correlation (Multi, Averaged)". Returns: [0]: An array with tuples containing two elements: 1st: tau in ms 2nd: corresponding correlation signal [1]: Intensity trace: 1st: time in ms 2nd: Trace in kHz [2]: An array with N elements, indicating, how many curves we are opening from the file. Elements can be names and must be convertible to strings. """ filename = path.name with path.open("r", encoding="iso8859_15") as openfile: Alldata = openfile.readlines() # End of trace EndT = Alldata.__len__() # Correlation function # Find out where the correlation function is for i in np.arange(len(Alldata)): if Alldata[i].startswith('Mode'): mode = Alldata[i][5:].strip(' ":').strip().strip('"') single_strings = ["a-ch0", "a-ch1", "auto ch0", "auto ch1", "fast auto ch0", "fast auto ch1", ] if (mode.lower().count('single') or mode.lower().strip() in single_strings): single = True channel = mode.split(" ")[-1] else: # dual single = False # accc ? if mode.lower().count("cross") == 1: accc = "CC" else: accc = "AC" if Alldata[i].startswith('"Correlation'): # This tells us if there is only one curve or if there are # multiple curves with an average. if (Alldata[i].strip().lower() == '"correlation (multi, averaged)"'): multidata = True else: multidata = False if Alldata[i].startswith('"Correlation"'): # Start of correlation function StartC = i+1 if Alldata[i].startswith('"Correlation (Multi, Averaged)"'): # Start of AVERAGED correlation function !!! # There are several curves now. StartC = i+2 if Alldata[i].replace(" ", "").lower().strip() == '"countrate"': # takes care of "Count Rate" and "Countrate" # End of correlation function EndC = i-1 # Start of trace (goes until end of file) StartT = i+1 if Alldata[i].startswith('Monitor Diode'): EndT = i-1 # Get the header Namedata = Alldata[StartC-1: StartC] # Define *curvelist* curvelist = csv.reader(Namedata, delimiter='\t').__next__() if len(curvelist) <= 2: # Then we have just one single correlation curve curvelist = [""] else: # We have a number of correlation curves. We need to specify # names for them. We take these names from the headings. # Lag times not in the list curvelist.remove(curvelist[0]) # Last column is empty curvelist.remove(curvelist[-1]) # Correlation function Truedata = Alldata[StartC: EndC] readdata = csv.reader(Truedata, delimiter='\t') # Add lists to *data* according to the length of *curvelist* data = [[]]*len(curvelist) # Work through the rows in the read data for row in readdata: for i in np.arange(len(curvelist)): if len(row) > 0: data[i].append((np.float(row[0]), np.float(row[i+1]))) # Trace # Trace is stored in two columns # 1st column: time [s] # 2nd column: trace [kHz] # Get the trace Tracedata = Alldata[StartT: EndT] timefactor = 1000 # because we want ms instead of s readtrace = csv.reader(Tracedata, delimiter='\t') trace = list() trace2 = list() # Work through the rows for row in readtrace: # time in ms, countrate trace.append(list()) trace[0].append((np.float(row[0])*timefactor, np.float(row[1]))) # Only trace[0] contains the trace! for i in np.arange(len(curvelist)-1): trace.append(list()) trace[i+1].append((np.float(row[0])*timefactor, 0)) if not single: k = len(curvelist)/2 if int(k) != k: print("Problem with ALV data. Single mode not recognized.") # presumably dual mode. There is a second trace # time in ms, countrate trace2.append(list()) trace2[0].append((np.float(row[0])*timefactor, np.float(row[2]))) # Only trace2[0] contains the trace! for i in np.arange(len(curvelist)-1): trace2.append(list()) trace2[i+1].append((np.float(row[0])*timefactor, 0)) # group the resulting curves corrlist = list() tracelist = list() typelist = list() if single: # We only have several runs and one average # split the trace into len(curvelist)-1 equal parts if multidata: nav = 1 else: nav = 0 splittrace = mysplit(trace[0], len(curvelist)-nav) i = 0 for t in range(len(curvelist)): typ = curvelist[t] if typ.lower()[:7] == "average": typelist.append("{} average".format(channel)) corrlist.append(np.array(data[t])) tracelist.append(np.array(trace[0])) else: typelist.append("{} {}".format(accc, channel)) corrlist.append(np.array(data[t])) tracelist.append(splittrace[i]) i += 1 elif accc == "AC": # Dual mode, autocorrelation # We now have two averages and two different channels. # We now have two traces. # The data is assembled in blocks. That means the first block # contains an average and the data of channel 0 and the second # block contains data and average of channel 1. We can thus # handle the data from 0 to len(curvelist)/2 and from # len(curvelist)/2 to len(curvelist) as two separate data sets. # CHANNEL 0 if multidata: nav = 1 else: nav = 0 channel = "CH0" splittrace = mysplit(trace[0], len(curvelist)/2-nav) i = 0 for t in range(int(len(curvelist)/2)): typ = curvelist[t] if typ.lower()[:7] == "average": typelist.append("{} average".format(channel)) corrlist.append(np.array(data[t])) tracelist.append(np.array(trace[0])) else: typelist.append("{} {}".format(accc, channel)) corrlist.append(np.array(data[t])) tracelist.append(splittrace[i]) i += 1 # CHANNEL 1 channel = "CH1" splittrace2 = mysplit(trace2[0], len(curvelist)/2-nav) i = 0 for t in range(int(len(curvelist)/2), int(len(curvelist))): typ = curvelist[t] if typ.lower()[:7] == "average": typelist.append("{} average".format(channel)) corrlist.append(np.array(data[t])) tracelist.append(np.array(trace2[0])) else: typelist.append("{} {}".format(accc, channel)) corrlist.append(np.array(data[t])) tracelist.append(splittrace2[i]) i += 1 elif accc == "CC": if multidata: nav = 1 else: nav = 0 # Dual mode, cross-correlation channel = "CC01" splittrace = mysplit(trace[0], len(curvelist)/2-nav) splittrace2 = mysplit(trace2[0], len(curvelist)/2-nav) i = 0 for t in range(int(len(curvelist)/2)): typ = curvelist[t] if typ.lower()[:7] == "average": typelist.append("{} average".format(channel)) corrlist.append(np.array(data[t])) tracelist.append([np.array(trace[0]), np.array(trace2[0])]) else: typelist.append("{} {}".format(accc, channel)) corrlist.append(np.array(data[t])) tracelist.append([splittrace[i], splittrace2[i]]) i += 1 # CHANNEL 1 channel = "CC10" i = 0 for t in range(int(len(curvelist)/2), int(len(curvelist))): typ = curvelist[t] if typ.lower()[:7] == "average": typelist.append("{} average".format(channel)) corrlist.append(np.array(data[t])) # order must be the same as above tracelist.append([np.array(trace[0]), np.array(trace2[0])]) else: typelist.append("{} {}".format(accc, channel)) corrlist.append(np.array(data[t])) # order must be the same as above tracelist.append([splittrace[i], splittrace2[i]]) i += 1 else: print("Could not detect data file format for: {}".format(filename)) corrlist = np.array(data) tracelist = np.array(trace) typelist = curvelist dictionary = dict() dictionary["Correlation"] = corrlist dictionary["Trace"] = tracelist dictionary["Type"] = typelist filelist = list() for i in curvelist: filelist.append(filename) dictionary["Filename"] = filelist return dictionary def openASC_ALV_7004(path): """ Opens ALV file format with header information "ALV-7004/USB" This is a single-run file format. - data is identified by 4*"\t" - count rate is identified by string (also "countrate") - allzero-correlations are removed "Correlation" 2.50000E-005 -9.45478E-001 -1.00000E+000 5.22761E-002 3.05477E-002 5.00000E-005 6.73734E-001 -2.59938E-001 3.17894E-002 4.24466E-002 7.50000E-005 5.30716E-001 3.21605E-001 5.91051E-002 2.93061E-002 1.00000E-004 3.33292E-001 1.97860E-001 3.24102E-002 3.32379E-002 1.25000E-004 2.42538E-001 1.19988E-001 4.37917E-002 3.05477E-002 1.50000E-004 1.86396E-001 1.23318E-001 5.66218E-002 2.25806E-002 1.75000E-004 1.73836E-001 8.53991E-002 4.64819E-002 3.46865E-002 2.00000E-004 1.48080E-001 9.35377E-002 4.37917E-002 4.17223E-002 [...] 1.00663E+004 2.80967E-005 -2.23975E-005 -7.08272E-005 5.70470E-005 1.09052E+004 9.40185E-005 2.76261E-004 1.29745E-004 2.39958E-004 1.17441E+004 -2.82103E-004 -1.97386E-004 -2.88753E-004 -2.60987E-004 1.25829E+004 1.42069E-004 3.82018E-004 6.03932E-005 5.40363E-004 "Count Rate" 0.11719 141.83165 81.54211 141.83165 81.54211 0.23438 133.70215 77.90344 133.70215 77.90344 0.35156 129.67148 74.58858 129.67148 74.58858 0.46875 134.57133 79.53957 134.57133 79.53957 [...] 29.29688 143.78307 79.06236 143.78307 79.06236 29.41406 154.80135 82.87147 154.80135 82.87147 29.53125 187.43013 89.61197 187.43013 89.61197 29.64844 137.82655 77.71597 137.82655 77.71597 [...] """ filename = path.name with path.open("r", encoding="iso8859_15") as openfile: Alldata = openfile.readlines() # Find the different arrays # correlation array: " " # trace array: " " allcorr = [] alltrac = [] i = 0 intrace = False mode = False for item in Alldata: if item.lower().strip().strip('"') == "count rate": intrace = True continue elif item.count("Mode"): mode = item.split(":")[1].strip().strip('" ').lower() i += 1 if item.count("\t") == 4: if intrace: it = item.split("\t") it = [float(t.strip()) for t in it] alltrac.append(it) else: ic = item.split("\t") ic = [float(c.strip()) for c in ic] allcorr.append(ic) allcorr = np.array(allcorr) alltrac = np.array(alltrac) tau = allcorr[:, 0] time = alltrac[:, 0] * 1000 lenc = allcorr.shape[0] lent = alltrac.shape[0] # Traces trace1 = np.zeros((lent, 2), dtype=np.float_) trace1[:, 0] = time trace1[:, 1] = alltrac[:, 1] trace2 = trace1.copy() trace2[:, 1] = alltrac[:, 2] trace3 = trace1.copy() trace3[:, 1] = alltrac[:, 3] trace4 = trace1.copy() trace4[:, 1] = alltrac[:, 4] # Correlations corr1 = np.zeros((lenc, 2), dtype=np.float_) corr1[:, 0] = tau corr1[:, 1] = allcorr[:, 1] corr2 = corr1.copy() corr2[:, 1] = allcorr[:, 2] corr3 = corr1.copy() corr3[:, 1] = allcorr[:, 3] corr4 = corr1.copy() corr4[:, 1] = allcorr[:, 4] typelist = [] corrlist = [] tracelist = [] filelist = [] if mode == False: raise LoadALVError("Undetermined ALV file mode: {}".format(path)) # Go through all modes if mode == "a-ch0+1 c-ch0/1+1/0": # For some reason, the traces columns show the values # of channel 1 and 2 in channels 3 and 4. if not (np.allclose(trace1, trace3, rtol=.01) and np.allclose(trace2, trace4, rtol=.01)): raise LoadALVError("Unexpected data format: {}".format(path)) if not np.allclose(corr1[:, 1], 0): corrlist.append(corr1) filelist.append(filename) tracelist.append(trace1) typelist.append("AC1") if not np.allclose(corr2[:, 1], 0): corrlist.append(corr2) filelist.append(filename) tracelist.append(trace2) typelist.append("AC2") if not np.allclose(corr3[:, 1], 0): corrlist.append(corr3) filelist.append(filename) tracelist.append([trace1, trace2]) typelist.append("CC12") if not np.allclose(corr4[:, 1], 0): corrlist.append(corr4) filelist.append(filename) tracelist.append([trace1, trace2]) typelist.append("CC21") elif mode in ["a-ch0", "a-ch0 a-"]: if not (np.allclose(trace2[:, 1], 0) and np.allclose(trace3[:, 1], 0) and np.allclose(trace4[:, 1], 0) and np.allclose(corr2[:, 1], 0) and np.allclose(corr3[:, 1], 0) and np.allclose(corr4[:, 1], 0)): raise LoadALVError("Unexpected data format: {}".format(path)) corrlist.append(corr1) filelist.append(filename) tracelist.append(trace1) typelist.append("AC") elif mode in ["a-ch1", "a-ch1 a-"]: if not (np.allclose(trace1[:, 1], 0) and np.allclose(trace3[:, 1], 0) and np.allclose(trace4[:, 1], 0) and np.allclose(corr1[:, 1], 0) and np.allclose(corr3[:, 1], 0) and np.allclose(corr4[:, 1], 0)): raise LoadALVError("Unexpected data format: {}".format(path)) corrlist.append(corr2) filelist.append(filename) tracelist.append(trace2) typelist.append("AC") elif mode in ["a-ch2", "a- a-ch2"]: if not (np.allclose(trace1[:, 1], 0) and np.allclose(trace2[:, 1], 0) and np.allclose(trace4[:, 1], 0) and np.allclose(corr1[:, 1], 0) and np.allclose(corr2[:, 1], 0) and np.allclose(corr4[:, 1], 0)): raise LoadALVError("Unexpected data format: {}".format(path)) corrlist.append(corr3) filelist.append(filename) tracelist.append(trace3) typelist.append("AC") elif mode in ["a-ch3", "a- a-ch3"]: if not (np.allclose(trace1[:, 1], 0) and np.allclose(trace2[:, 1], 0) and np.allclose(trace3[:, 1], 0) and np.allclose(corr1[:, 1], 0) and np.allclose(corr2[:, 1], 0) and np.allclose(corr3[:, 1], 0)): raise LoadALVError("Unexpected data format: {}".format(path)) corrlist.append(corr4) filelist.append(filename) tracelist.append(trace4) typelist.append("AC") else: msg = "ALV mode '{}' not implemented yet.".format(mode) raise NotImplementedError(msg) dictionary = dict() dictionary["Correlation"] = corrlist dictionary["Trace"] = tracelist dictionary["Type"] = typelist dictionary["Filename"] = filelist return dictionary def mysplit(a, n): """ Split a trace into n equal parts by interpolation. The signal average is preserved, but the signal variance will decrease. """ if n <= 1: return [np.array(a)] a = np.array(a) N = len(a) lensplit = np.int(np.ceil(N/n)) # xp is actually rounded -> recalculate xp = np.linspace(a[:, 0][0], a[:, 0][-1], N, endpoint=True) # let xp start at zero xp -= a[:, 0][0] yp = a[:, 1] # time frame for each new curve #dx = xp[-1]/n # perform interpolation of new trace x, newstep = np.linspace(0, xp[-1], lensplit*n, endpoint=True, retstep=True) # interpolating reduces the variance and possibly changes the avg y = np.interp(x, xp, yp) data = np.zeros((lensplit*n, 2)) data[:, 0] = x + newstep # make sure that the average stays the same: data[:, 1] = y - np.average(y) + np.average(yp) return np.split(data, n) pycorrfit-1.1.7/pycorrfit/readfiles/read_SIN_correlator_com.py0000664000372000037200000003301513554642611025463 0ustar travistravis00000000000000"""correlator.com .sin files""" import csv import pathlib import warnings import numpy as np class OpenSINError(BaseException): pass def openSIN(path, filename=None): """Parse .sin files (correlator.com)""" path = pathlib.Path(path) if filename is not None: warnings.warn("Using `filename` is deprecated.", DeprecationWarning) path = path / filename filename = path.name with path.open() as fd: data = fd.readlines() for line in data: line = line.strip() if line.lower().startswith("mode"): mode = line.split("=")[1].strip().split() # Find out what kind of mode it is # The rationale is that when the mode # consists of single characters separated # by empty spaces, then we have integer mode. if len(mode) - np.sum([len(m) for m in mode]) == 0: return openSIN_integer_mode(path) else: return openSIN_old(path) def openSIN_integer_mode(path): """Integer mode file format of e.g. flex03lq-1 correlator (correlator.com) This is a file format where the type (AC/CC) of the curve is determined using integers in the "Mode=" line, e.g. Mode= 2 3 3 2 0 1 1 0 which means the first correlation is CC23, the second CC32, the third CC01, and the fourth CC10. Similarly, Mode= 1 1 2 2 0 4 4 4 would translate to AC11, AC22, CC04, and AC44. """ with path.open() as fd: data = fd.readlines() # get mode (curve indices) for line in data: line = line.strip() if line.lower().startswith("mode"): mode = line.split("=")[1].strip().split() mode = [int(m) for m in mode] if len(mode) % 2 != 0: msg = "mode must be multiples of two: {}".format(path) raise OpenSINError(msg) # build up the lists corr_func = [] intensity = [] section = "" # loop through lines for line in data: line = line.strip().lower() if line.startswith("["): section = line continue elif (len(line) == 0 or line.count("=")): continue if section.count("[correlationfunction]"): corr_func.append(line.split()) elif section.count("[intensityhistory]"): intensity.append(line.split()) # corr_func now contains lag time, and correlations according # to the mode parameters. corr_func = np.array(corr_func, dtype=float) intensity = np.array(intensity, dtype=float) timefactor = 1000 # because we want ms instead of s timedivfac = 1000 # because we want kHz instead of Hz intensity[:, 0] *= timefactor corr_func[:, 0] *= timefactor intensity[:, 1:] /= timedivfac # correlator.com correlation is normalized to 1, not to 0 corr_func[:, 1:] -= 1 # Now sort the information for pycorrfit correlations = [] traces = [] curvelist = [] for ii in range(len(mode)//2): modea = mode[2*ii] modeb = mode[2*ii+1] if modea == modeb: # curve type AC curvelist.append("AC{}".format(modea)) # trace atrace = np.zeros((intensity.shape[0], 2), dtype=float) atrace[:, 0] = intensity[:, 0] atrace[:, 1] = intensity[:, modea+1] traces.append(atrace) else: # curve type CC curvelist.append("CC{}{}".format(modea, modeb)) # trace modmin = min(modea, modeb) modmax = max(modea, modeb) tracea = np.zeros((intensity.shape[0], 2), dtype=float) tracea[:, 0] = intensity[:, 0] tracea[:, 1] = intensity[:, modmin+1] traceb = np.zeros((intensity.shape[0], 2), dtype=float) traceb[:, 0] = intensity[:, 0] traceb[:, 1] = intensity[:, modmax+1] traces.append([tracea, traceb]) # correlation corr = np.zeros((corr_func.shape[0], 2), dtype=float) corr[:, 0] = corr_func[:, 0] corr[:, 1] = corr_func[:, ii+1] correlations.append(corr) dictionary = {} dictionary["Correlation"] = correlations dictionary["Trace"] = traces dictionary["Type"] = curvelist filelist = [] for _i in curvelist: filelist.append(path.name) dictionary["Filename"] = filelist return dictionary def openSIN_old(path): """Parses the simple sin file format (correlator.com) Read data from a .SIN file, usually created by the software using correlators from correlator.com. FLXA Version= 1d [Parameters] ... Mode= Single Auto ... [CorrelationFunction] 1.562500e-09 0.000000e+00 3.125000e-09 0.000000e+00 4.687500e-09 0.000000e+00 ... 1.887435e+01 1.000030e+00 1.929378e+01 1.000141e+00 1.971321e+01 9.999908e-01 2.013264e+01 9.996810e-01 2.055207e+01 1.000047e+00 2.097150e+01 9.999675e-01 2.139093e+01 9.999591e-01 2.181036e+01 1.000414e+00 2.222979e+01 1.000129e+00 2.264922e+01 9.999285e-01 2.306865e+01 1.000077e+00 ... 3.959419e+02 0.000000e+00 4.026528e+02 0.000000e+00 4.093637e+02 0.000000e+00 4.160746e+02 0.000000e+00 4.227854e+02 0.000000e+00 4.294963e+02 0.000000e+00 [RawCorrelationFunction] ... [IntensityHistory] TraceNumber= 458 0.000000 9.628296e+03 9.670258e+03 0.262144 1.001358e+04 9.971619e+03 0.524288 9.540558e+03 9.548188e+03 0.786432 9.048462e+03 9.010315e+03 1.048576 8.815766e+03 8.819580e+03 1.310720 8.827210e+03 8.861542e+03 1.572864 9.201050e+03 9.185791e+03 1.835008 9.124756e+03 9.124756e+03 2.097152 9.059906e+03 9.029389e+03 ... 1. We are interested in the "[CorrelationFunction]" section, where the first column denotes tau in seconds and the second row the correlation signal. Values are separated by a tabulator "\t". We do not import anything from the "[Parameters]" section. We have to subtract "1" from the correlation function, since it is a correlation function that converges to "1" and not to "0". 2. We are also interested in the "[IntensityHistory]" section. If we are only interested in autocorrelation functions: An email from Jixiang Zhu - Correlator.com (2012-01-22) said, that "For autocorrelation mode, the 2nd and 3 column represent the same intensity series with slight delay. Therefore, they are statistically the same but numerically different." It is therefore perfectly fine to just use the 2nd column. Different acquisition modes: Mode [CorrelationFunction] [IntensityHistory] Single Auto 2 Colums (tau,AC) 1 significant Single Cross 2 Colums (tau,CC) 2 Dual Auto 3 Colums (tau,AC1,AC2) 2 Dual Cross 3 Colums (tau,CC12,CC21) 2 Quad 5 Colums (tau,AC1,AC2,CC12,CC21) 2 Returns: [0]: N arrays with tuples containing two elements: 1st: tau in ms 2nd: corresponding correlation signal [1]: N Intensity traces: 1st: time in ms 2nd: Trace in kHz [2]: A list with N elements, indicating, how many correlation curves we are importing. """ with path.open() as fd: Alldata = fd.readlines() # Find out where the correlation function and trace are for i in np.arange(len(Alldata)): if Alldata[i][0:4] == "Mode": Mode = Alldata[i].split("=")[1].strip() if Alldata[i][0:21] == "[CorrelationFunction]": StartC = i+1 if Alldata[i][0:24] == "[RawCorrelationFunction]": EndC = i-2 if Alldata[i][0:18] == "[IntensityHistory]": # plus 2, because theres a line with the trace length StartT = i+2 if Alldata[i][0:11] == "[Histogram]": EndT = i-2 curvelist = [] correlations = [] traces = [] # Get the correlation function Truedata = Alldata[StartC:EndC] timefactor = 1000 # because we want ms instead of s readcorr = csv.reader(Truedata, delimiter='\t') # Trace # Trace is stored in three columns # 1st column: time [s] # 2nd column: trace [Hz] # 3rd column: trace [Hz] - Single Auto: equivalent to 2nd # Get the trace Tracedata = Alldata[StartT:EndT] # timefactor = 1000 # because we want ms instead of s timedivfac = 1000 # because we want kHz instead of Hz readtrace = csv.reader(Tracedata, delimiter='\t') # Process all Data: if Mode == "Single Auto": curvelist.append("AC") corrdata = [] for row in readcorr: # tau in ms, corr-function minus "1" corrdata.append((np.float(row[0])*timefactor, np.float(row[1])-1)) correlations.append(np.array(corrdata)) trace = [] for row in readtrace: # tau in ms, corr-function minus "1" trace.append((np.float(row[0])*timefactor, np.float(row[1])/timedivfac)) traces.append(np.array(trace)) elif Mode == "Single Cross": curvelist.append("CC") corrdata = [] for row in readcorr: # tau in ms, corr-function minus "1" corrdata.append((np.float(row[0])*timefactor, np.float(row[1])-1)) correlations.append(np.array(corrdata)) trace1 = [] trace2 = [] for row in readtrace: # tau in ms, corr-function minus "1" trace1.append((np.float(row[0])*timefactor, np.float(row[1])/timedivfac)) trace2.append((np.float(row[0])*timefactor, np.float(row[2])/timedivfac)) traces.append([np.array(trace1), np.array(trace2)]) elif Mode == "Dual Auto": curvelist.append("AC1") curvelist.append("AC2") corrdata1 = [] corrdata2 = [] for row in readcorr: # tau in ms, corr-function minus "1" corrdata1.append((np.float(row[0])*timefactor, np.float(row[1])-1)) corrdata2.append((np.float(row[0])*timefactor, np.float(row[2])-1)) correlations.append(np.array(corrdata1)) correlations.append(np.array(corrdata2)) trace1 = [] trace2 = [] for row in readtrace: # tau in ms, corr-function minus "1" trace1.append((np.float(row[0])*timefactor, np.float(row[1])/timedivfac)) trace2.append((np.float(row[0])*timefactor, np.float(row[2])/timedivfac)) traces.append(np.array(trace1)) traces.append(np.array(trace2)) elif Mode == "Dual Cross": curvelist.append("CC12") curvelist.append("CC21") corrdata1 = [] corrdata2 = [] for row in readcorr: # tau in ms, corr-function minus "1" corrdata1.append((np.float(row[0])*timefactor, np.float(row[1])-1)) corrdata2.append((np.float(row[0])*timefactor, np.float(row[2])-1)) correlations.append(np.array(corrdata1)) correlations.append(np.array(corrdata2)) trace1 = [] trace2 = [] for row in readtrace: # tau in ms, corr-function minus "1" trace1.append((np.float(row[0])*timefactor, np.float(row[1])/timedivfac)) trace2.append((np.float(row[0])*timefactor, np.float(row[2])/timedivfac)) traces.append([np.array(trace1), np.array(trace2)]) traces.append([np.array(trace1), np.array(trace2)]) elif Mode == "Quad": curvelist.append("AC1") curvelist.append("AC2") curvelist.append("CC12") curvelist.append("CC21") corrdata1 = [] corrdata2 = [] corrdata12 = [] corrdata21 = [] for row in readcorr: # tau in ms, corr-function minus "1" corrdata1.append((np.float(row[0])*timefactor, np.float(row[1])-1)) corrdata2.append((np.float(row[0])*timefactor, np.float(row[2])-1)) corrdata12.append( (np.float(row[0])*timefactor, np.float(row[3])-1)) corrdata21.append( (np.float(row[0])*timefactor, np.float(row[4])-1)) correlations.append(np.array(corrdata1)) correlations.append(np.array(corrdata2)) correlations.append(np.array(corrdata12)) correlations.append(np.array(corrdata21)) trace1 = [] trace2 = [] for row in readtrace: # tau in ms, corr-function minus "1" trace1.append((np.float(row[0])*timefactor, np.float(row[1])/timedivfac)) trace2.append((np.float(row[0])*timefactor, np.float(row[2])/timedivfac)) traces.append(np.array(trace1)) traces.append(np.array(trace2)) traces.append([np.array(trace1), np.array(trace2)]) traces.append([np.array(trace1), np.array(trace2)]) else: raise NotImplemented( "'Mode' type '{}' in {} not supported by this method!". Mode, format(path)) dictionary = {} dictionary["Correlation"] = correlations dictionary["Trace"] = traces dictionary["Type"] = curvelist filelist = [] for i in curvelist: filelist.append(path.name) dictionary["Filename"] = filelist return dictionary pycorrfit-1.1.7/PKG-INFO0000664000372000037200000000412213554643106015506 0ustar travistravis00000000000000Metadata-Version: 2.1 Name: pycorrfit Version: 1.1.7 Summary: Scientific tool for fitting correlation curves on a logarithmic plot. Home-page: https://github.com/FCS-analysis/PyCorrFit Author: Paul Müller Author-email: dev@craban.de License: GPL v2 Description: |PyCorrFit| =========== |PyPI Version| |Build Status Win| |Build Status Travis| |Coverage Status| |Docs Status| Documentation ------------- The documentation of PyCorrFit is available at https://pycorrfit.readthedocs.org. Problems -------- If you find a bug or need help with a specific topic, do not hesitate to ask a question at the `issues page `__. .. |PyCorrFit| image:: https://raw.github.com/FCS-analysis/PyCorrFit/master/doc/Images/PyCorrFit_logo_dark.png .. |PyPI Version| image:: https://img.shields.io/pypi/v/PyCorrFit.svg :target: https://pypi.python.org/pypi/pycorrfit .. |Build Status Win| image:: https://img.shields.io/appveyor/ci/paulmueller/PyCorrFit/master.svg?label=win :target: https://ci.appveyor.com/project/paulmueller/pycorrfit .. |Build Status Travis| image:: https://img.shields.io/travis/FCS-analysis/PyCorrFit/master.svg?label=linux_osx :target: https://travis-ci.org/FCS-analysis/PyCorrFit .. |Coverage Status| image:: https://img.shields.io/codecov/c/github/FCS-analysis/PyCorrFit/master.svg :target: https://codecov.io/gh/FCS-analysis/PyCorrFit .. |Docs Status| image:: https://readthedocs.org/projects/pycorrfit/badge/?version=latest :target: https://readthedocs.org/projects/pycorrfit/builds/ Keywords: fluorescence correlation spectroscopy Platform: ALL Classifier: Operating System :: OS Independent Classifier: Programming Language :: Python :: 3 Classifier: Topic :: Scientific/Engineering :: Visualization Classifier: Intended Audience :: Science/Research Requires-Python: >=3.6, <4 Provides-Extra: GUI pycorrfit-1.1.7/doc/0000775000372000037200000000000013554643106015157 5ustar travistravis00000000000000pycorrfit-1.1.7/doc/README.md0000664000372000037200000000126113554642611016436 0ustar travistravis00000000000000This folder contains the TeX-source of the [PyCorrFit documentation](https://github.com/FCS-analysis/PyCorrFit/wiki/PyCorrFit_doc.pdf). If, for some reason, you wish to compile it yourself, you will need a working LaTeX distribution. If you are running a Linux system, make sure that the following packages are installed: ghostscript texlive texlive-base texlive-bibtex-extra texlive-latex-extra texlive-math-extra texlive-science Apply these commands repeatedly (3 times to be on the safe side) to build the documentation: pdflatex -synctex=1 -interaction=nonstopmode PyCorrFit_doc.tex bibtex PyCorrFit_doc.aux pycorrfit-1.1.7/doc/PyCorrFit_doc.pdf0000664000372000037200000216206013554642637020377 0ustar travistravis00000000000000%PDF-1.5 % 2 0 obj << /Type /ObjStm /N 100 /First 804 /Length 1213 /Filter /FlateDecode >> stream xڝV]o6}oK?D@QK4KED,z4~ʖq kK J(%!AK% `֝`)fm]]cQEdfXsQEd.9` `U$PZ66307oNtԺ2Vy-y_no?+o%UQ eSo[&Vm]4rT]H_ÐA_۪wz}MU5m[j  ztwyh-]%|d*!C"srKZU#kum{h=B5/!H+Kf(nK{'2ׂu;3C9@'P4nXo]{*vz;2VJϽcYVUUs= ŵYp io+w7U͐UsjVm]L6!sxSӫ:YQW}@/N毯ɾ:LVz|EL'a8Tȋ'Ok:MNcM=՛ =T ;;qhm:v-Ae~a] }Dz8}p\!^ok^N@c9kl5G~Ez kڒny&8Q{J6 n7ܔqQKnU閏" ~߶63%ݲ/mڻ .JWٲhvhsq_1]$S9i8|#* }S3^j% endstream endobj 285 0 obj << /Length 1397 /Filter /FlateDecode >> stream xOs8a?:n&۝4qv8L1Bͩ_}$N3$=~z"ћWuބ %#]i(s_ˬ&Έ" :Lz>7-3ܠfפ߮0y۬/+.Yʴg/5sJx;v!VK}Ә [[Ȣ߈1Y&Q*:(Ftk]bG̏lJn%G(Gm#hm4OE՟*>MXʶA}%7:GEM%mܥݥʒ}?6A p7N*1)=P"[&eF:t חzK !`+rE2|ŠBEE;L彨 xoD#&>X$[u%M[*Tr w)UC-`oṛzMr^&|fe-Y0,:)t(nAKf3*a%Y[1b{(k,"WLDt_|82kfRiVK~"6렷"n Ķ'vυ6ǰ[Qn%[(.6E'5!pv}Wz| F#iz&Fn#DlEb.aKub4z{ʽ|a 06!oJ@h>|jr>8J endstream endobj 249 0 obj << /Type /XObject /Subtype /Form /FormType 1 /PTEX.FileName (./Images/PyCorrFit_logo_dark.pdf) /PTEX.PageNumber 1 /PTEX.InfoDict 295 0 R /BBox [0 0 629.979187 184.27446] /Group 290 0 R /Resources << /ExtGState << /a0 << /CA 0.172107 /ca 0.172107 >> /a1 << /CA 0.445104 /ca 0.445104 >> /a2 << /CA 1 /ca 1 >> /a3 << /CA 0.804734 /ca 0.804734 >> >>/Font << /f-0-0 296 0 R>> >> /Length 659 /Filter /FlateDecode >> stream xUMk1 WDo(9,B{ 9)P!kk&M)eO'o't w@=}w> 9ʃN R %F!^J2k#6BjfFWRuܣWpSیޓw*Jw]8N^ZO~3[U<Ы9Lo`N!N JrIS/0 5`ai 9j;\rP9gD=w/%+ %DSY3A^ c`!hm9-aofi/Hwm?Yߋ];.f-]-J)=)$C’6&1 %I-PfІ-XteWnOQ;!(f(&*^څX5/T3@%{weP~Ej1 mP<'"p u4te,~; ߱]tWpB?GWG> stream x]j >$n!Cд 1}PA|83[{+: gx0 Z)1:Z,k18I7/k3U8kb޽Oc+c\m!s*IXތ,jW+%{yTg9k.DD剨LT)3) х{+)TV6{b>vfZݚ\igs` endstream endobj 301 0 obj << /Length 302 0 R /Filter /FlateDecode /Length1 9652 >> stream x: xՕ;yfɼ< 3!!dL$"AB&@paM D, >W |k"2IMl-Z݅Xjn g@:w9{sϽ7 >`[ @Cn-|>%Tvx~nk{n?eѶ#+iIgcys;7n=Xh򓼼mռ 'mlK뺭#N| L8[4 *U#$0r0*y1Ҩ8%N;_珇o2~oC$<0oಏU% ( 7 Ep!\[m+Ѡ\0"}5nv}E ԅG]{0BecFMbQ((Q;̼<5+h2r!sAE \ШՓ $U\D<&sw8FHh5i?k1(nڕQCnTgF5D:C$ +5B11^8|kaj iJMLR[U)Lb*b0yi*E?I$wwsL~9M3%L2Zi&p-vGb*]%o^}'dPJ^F0;A^A*L Q%˟:2/I:M[n<`y 0jHL\j6ZBԣz)5Ox x<; )%SȔbJLZZ4T(pY9d/{nZ.Kia.n,(( r@ĀU& f R0= *ZrJQVN  3] q7)Ǎ KqVqAT8 ?8Rpnt1X$(,#dءTm{L+ZsWg+G謈ZD*y4Ǭu٬&{#`ǣfhFl27LaLPQV2jC ώ' ?#ݿȡc t1%/8}g8g DwefO(gQexly!z1:B_jG5qnʝ4)/7eZ3tZZ X3ܼ**_PeUUnAuAEU"r b'D ?$-Sw-WC?ob1ۏ6)m-L9[6a7uYӦ#5\o;(3:-LX(u=.S-pY*j>,+4N_WoZ`ldTT,ʮpȾo}I#upЊwBM-N&[_pw?A& - 8KQ"j&wtzc+34 E Ö|<++^#䱗 -I+l4SVbu,ƃQG3u*ku%VXӋ̓!}.z.Ʌtetk u=1 +'Dv/j}O{Nؒy?|]wZkR J F&d0V3cy&jT檪eߩ>wPH_lc/+h"{~2\s|ts%cm;.Vsiugx0>{/t@K$Gkϲ+*K 9B~!ۻrsק[FݏsrMv e>doŜ?\ku DP#r.% 1HJ&v|(͢>ņSΤ=XqIc%iH}`z\K5p 6+# F4C_RK80:$nxPFAa>yvl×O>vx {,X94UJ?c ixH@}ҀW4Uڈa$Y Ω o *8fX߁'a~ oBL"}=rJǓǥvV 6`<+y%n;a7S}>$j7̂Ў|3DML%m+ka؋:}qx>D2$A$w!y70 l6 wt |~*@=ވ\g=%P jQkeO%C7F0'T#c[ O;;`'ƀ'1}5oؿ(Y^X a:ZɋvJhѿw)tZ[q(ggZh49rzEyYԒ)E`Iybzr.Î d4dJ%D~gr>oPE_E@>iKsyƋO_'Da!wU-X-+B[FzC&m[lf1B;} Se Z%hR]<.'oY uկhwLSݑo\,6WOqɄRj]:637M稛ɵ+ BsFBïRv5#*5z!1>7cZol4"]dM43UpB>m\_;#PkH}(#>~Ţ#ґwag Ifʦ|~ŭEF-1[鋳5ҩQA5D#|StJzW'a0#@x'ec_xP}G&o&]J]Cx*݆N F(s/tZZA2S&Ù}2ҔEj1B8{^-g/Cs9Gs9Ga#@ؿr8agn >ܼlUnH.60T5hL"#<` El1l!'&VyH`ӛ 2F;6< orҹXtkta8PsC%qngdN+^,IXZL5[D(D(jV5Pv( k TG s̓7U # (A"4!Oۅp^xDޠx V4y y =GB裇fCl1V^!!hF ݓRMKpjncQMiSSKR"Pt8TF*X d),wAB`DDڋP|M/; 94 LBI=-x}n麀9Wg[8" ,0$6<0Dfj `%݅܅z=M\5nC 6i2L0010nLaڅi'PhVZ[CG+T/6LqjfXi6]5F|Ȭ=6GelVϭbWWoZoX/^!Q{PaP'_O +~rP_c'rOe<[ƥ28,҃r3^' y󏨑lKnN*|[_ 8)=RZY}uoQs"ˁˁ:98Iyd9YA,YfDue22,%FFu_ux*s6;fy`VˆNGZr:ȏ䴭c;bop[ʫu~IhGмz.6<2`a_鬍wVǚsTsXXXssdGT^ie:LuZtm3vU\w>"E=*)UxU&HTGȏUFd__ߞtolZճS۳i37@&45hcN9FئO=o:ی=kzpB $LmzVb7'= endstream endobj 332 0 obj << /Length 1192 /Filter /FlateDecode >> stream xYr8+؍X@Xf2,]5,h,T0dt~$NTK%qt9^"oEI_'z/N~];ԃ0)Eb D=qYz (+dk{ s~@^H?s\FH@gl9/cN9y ! BrA cp7>@_*?lǪjv1 B$*\o1-qf)R:+Ib3fO5 ز3[=+y҂gLIJMN(8r j}no 0 ׿8*xf! b# :K hUcʦXN4r!;G#HeY.6gfB`S:^?}JC3WYnW~5*\9MR*Ve͉^N,N o:V [ uv^@Li";8y@юKzy0Jetʥ SR.@׭G!o˕ su**F8r>eR]$&٭7aO Fj#2h,GjaF[z8TJ>E=,u.>O19 jc > stream xڽXYs8~#URx-#ǙL%޵fyHHb׀o7DEq͖, / Ow(vXsGI?K'MCGܹ.[xqo:%2JU/w?~Z3(xgx<^ ogC1?Y$7O^Z5i C8IE+VTpٷ4jW4؁U͔-&@[Tv@?QI+kB} b`l6߹j7a8}~'C]ЀS<ϕu+WiZ*O;.'I?]4v-F6 )hZUU-d  t[:3>hAfcܚ5U%1%iΙK^mNpˈ6L{*7l {0 ѡ/͋J @#;*.(`8>NKwtF((/96U;-rj`/V I AIt (d^~,xV Al]m_TY xK62Sp۞x9Ϟa<[\q 5 /pHCҧmx -o<! er3U`OML֠׿gs扉~o}NOc!~+ aptPM/,R:hnT.uA4=(#Txޚ 6жȠ1z':W*(i}M8:=r8!f(І>CnvO a} dfדذʮCyU FbV)\E~9Gߣ5 y^R^nuO65'K9yυYM\u(u*' lq%?i\h L5IPw&n jo \_M [}ɘJdz35>AJƊ-H7=6^"!+=g:<ٴsz筮€(D<󫽻\u#>v\V/dgqW<̳ >( +cU鋏LHSK?7ګ.}fAa?=hpl)8Ps{ozOrB.38ϸ_nK5`eDO1Nr|||u_D֯RtKY-r'a-nw3-XwZ׫[bhz\g׶^n r;4N yОC`^>1뿙NU endstream endobj 203 0 obj << /Type /ObjStm /N 100 /First 881 /Length 2762 /Filter /FlateDecode >> stream xڵZ[S~ׯGH_R°Tːx/BC$a/>jL jKϭ9=FQAX& F+hCvlЁzkcqid&4M*a6u=ft4l^“3g?2Ί~.@X.gQ 4њfrPJ2. .'qXgNX7\4gN}&$y$D~i2cO-Y k@|!*bLӐ yY œQ r+ {B0 Ȩ\ipJEL>Yp#BK ̦A=+= A3^ $ m(\MVDm*\ EJŐ*>LA,u` &ZmZ[)@ ,3EXvEȊQd'l!O f(~Ke RWF%:> pB Cej#tZPS8>я=͉h^thò\:w?vt~K^\sPz8]^Ʒkħ]l׃gbޠݯ_]ګvNWy;o# [A7M3qtpaD]۽G;?7G!>cMf{5Pƫvzn>w?7E;WʡR]t7fNUn_Y{~QD*V4?_"KuƱϞV4oXV$1j޶f1i?GyxORN)9-S>$WjXе.y MPt6666PJ/Tz ^B*PJ/Vzҋ^b+XJ/VzK^Қ٨ `юWBLEi%erKӔt]p{ѝLoAê5˖oDwoPiq>Ρ9>۽]Nx"\8>bPg5O88>3=r^ñR}^Lŭ?>׋vA;ww9ueBPe,הÏ_@wvIi|>v8_Mg@_ ܣb)ܺbbvll,;}ϦŬź}eh%Pqe)#طW/U3`O߯t1/ &OOI)WQg9x :Y!K dd탌DF2^1?"E*xzoef&}Ft8^fe7C1ۃѼJ/k^Řc! wwO*8Maę?Vl-(Z`kB endstream endobj 367 0 obj << /Length 3219 /Filter /FlateDecode >> stream xڥZYܶ~ׯK oQJx]ǛH0c{k4!w)i`l4#o[y7ϼ.m^p%\]^cߕ(#ݳ/2~zR勳/#|M>ӗ)/M+p/NWAL7gkT޷M] LVk< b~lVUYߴ&CwgK#r- ^ ýɞfiLk9:ǎQʮ{7=dZGgto^3sȾ־]Y҅(a9u Fzpn_bfu'˄qܜJmTk )+!<7LQs:'S.{^:5L\3KMlfɶZ0thжQ0?H=9׺{,s.iGI"aPY{OZ*5ːhWS7qV< w1_d8$ܜU -I#mIhn V5lAooe q(eK5x,C"uL:7YLКЏzYИ^՝ʇT.Y `-9:7ӷ|nKnQ ~ؼT᳤a!D c hiiJTRY~ڕ11m5J71 YU:UIܔЎB^7] w:t:uL.(Mş|BO;y2q&cQf E:c~[~q:r C<3ǣ4:ܠ *̝92g5gVs .;<&?𫦵%~k/a0&h.݄Np@?瘮<Vec]-7E"I&PBX<er˯VDo E満К_F;:}<ݧpP q(\>g endstream endobj 387 0 obj << /Length 3413 /Filter /FlateDecode >> stream xڭZIW%QSܗId;p jȚd>o+.3I [P&H~8ª(n2 Fڛ_FmCRǁSKwmRW##v>?+$ը+T0gme[iVW>Ld Z"7ga 4󰸌O?4T(N'g-kuߢFۙa80/6wFgULut6k~=duQrN;\8X^=T+G9 b'Wg5;IoP3@1FT+WJ7cT]Ĺbl'VDrgykqE_~6Y݋RjmD G\tn'-KAb:F ʖ ĹU'>xI ~Q IVk6GtFqU9]iX4gEWLpƕ1j'yʗdy6ȓMZ}oF7K6X00 |2zk$<ӭk_(LǿnO;!|J:8 4+3~Վ9 {=LrqQDnAeTlYi[R@& ,Vwz' ZVGgNת殎ϫjeak^O{qd}p P;tYYUy0*ӣYQ{<ʏʛ2KSɒm.Bmy\^PqOp)h)mjyH8dq4'4i|H Z9q55hS~r\@ %LNh@5_k`lޕDl"D. x{*8PӊKH=`,2g=5? h"p#7ʻvI@^\8 [p '?YTdiA firN@y INc@V?Ⳅ4+̲xI46.G_ a"SH>"0+j1X 9h 9!E@6%G6ͅ(4tI h?N>R1+l$C{J4Q1VdviQؕucf=.,"R& wNf6<7g<-* aE"i5ƩNo6K\xd#p2B" oPV (tԝe| K7[4(ILaVQԛDD(pz:HH(XB0Ferz2\%ekԇ.,ʙaZ^?L0jZ[9d`3\3vP,O!Pm IX!Dw(`Tstw XԘ\QRA1^+{"/pGwXz-IZ߁Ʉh\R@dzVj34Bkk(sJVs*Wi8tU ~W!$) sDwkU $L+Kl8Vq$]qn.kqн'T$"a2NpE+Ď") $Xۗ%9%( $T%AUyq Ʊ3h= 2֢-}D;W(a;3?\/WB^ɓ[p_/rQ8._5jGLo!PVݤ[}" :T!NYգnrNȬ qYX/Hf`?Xu9 w. {THEI_?!ǞܳU , &a\" rbcADbԳn? RLhm1s2 wJ{@Ga D~c].fLxͮӭ/)/JZTaT[i9"UNwռ{$74\Ȫfvطh=HxMwJUn\E0ά(9 >BQ2J$ iaF@26T$cC3q `yŒפ$YƁyH;ȱ5sj:H钳efRg34c!LI-0-AFLCykdeϑe_@*pmn~ F-ao U"؂QD8!_b"8y c=oRW ׅI;`ek{\7ÌP?cNܭdQܜwF)"x2FJȆ!go1; ~:VY!  R˃4K*}##ԌXʤN"2H~%^]B9gF㎿`f.׈8{}w'$v_'慷(O|;tqk*UU cpf,+X$%11V?43:0"!{;3~hZNoDJQكek *SEʚZ;\-eIgje>of! m VB?&﹗Bf)8fٌ@J{qb7xʞK}Y}%ђ Fr]B2Ҍ$)kWrE*7vq|=6߿q!yOܔh_[>Kh!OMTVY[uBK;O,k$4 i,kzg endstream endobj 402 0 obj << /Length 2266 /Filter /FlateDecode >> stream xڭXY~_a,@a2EtMDWȯO]es`a"EXUq~ QXd.)0]|RİjҐzYP}j^ V,ߩ(,RYEw >iCtn`v`F;M%Lq0|tb0h}w]iöD\Y:L׷}c&oIA"'{w0My~ do]?I8yAgܾw45uh< '+(Jڶ1,Tc`xH5źGR&b=3 q[l:/$;j;J$ ǟLy*1aJwL} ݬiq#L 88,+֡EẠp$yT ~G'B {lم( 8rS)qGZT3S)t$PitGZA%ʓ Jutlt9a6 󈸒0E*kwxK,Ti[ct$>o530b!rA[yDaq,IbO>:£s08uayg8Z28=NF Fnxܝ;挤gfѦEy0I26yU- jrkdSOkd52N _d{W=Cq ҞifLU|C" :3pP3d4}q5-D'Ik0'14j3fMd*S0ø*Q'Yfa%N$0>J*dr^ _j{xvs?7J}~^U tDS DzgGK =5ȜpRp9y(j ܠδ-*h J4!($ydnJ`]H.)*'B2C{A- UO&T-X٫|ٺ ˪?lI\ٚ7]f_RTY%uBPOzRebc Cq#+#9S 6*RlF[`(?\L2 bش ={@b` 'hSa 5#J+< <7|xs8@M>t{PK PaV.*0]/OMb3W. ɜc]ҋ>gD 翀?uO0>#i|}(*Թё* Bʖq.E&MoB TRQ0qJxkmK5IM,19JuNlAD@G_҈8r%ўq2a!T pU#9-XXӂ_H&+|;m kdp.&\m^&%^|>aPp#s⸁ۣ{hDQj7q0BO3&Cm3;K앰Q:%\mud@T0 hI󤈲5l-/ţ#֐q6īLU4@L,߈!|d2* #;33.2@H1-ߚYҟLXȗI|৒9hbUۚ8Ip>Ƙ$Aaߒ~NmYY2o/G ->Lt*#ſ8d25eNf)ѫYjIvLf7ئ%dƱ1L{4;EQ:jjIs&sz噰W`81t~2 "7;N,=;}(O2JYNkecʘ/c[qnm̗pC\$PRl W Ő#gr3VnyȤ#m8S'd')nC>LYvU-RW?R,[#42> stream xWSsOyx8DPl5*ۺ@@AT,Fn,Eņ"U:HfBzH3 ܀x6#a$vrx7@jȾ|8ė&^,Ӓ"6~tnyrbD}z!,<LOq̭ϝYJzN PV`*=;G`m.EbJ{atY0S=GhKϷ`u9m'Q4Zu37&z,zYCПGӱOKTg^ U=miwjٓ~xgS>fݾpx[ߑunMT!g4l |my/MCkz@dbj9U̴lmys-Г) 29{R+x=C_ȑ+ ĽCnM/Kic7cϧW LTWpdt._ԍ>k}uљ;{k*‘}zQ=q];Hi暵8dӗ1u $ i{ `mnHj-\񥱡(l0TpY^EuEIާ/֏`Q\@=O:͡J=Kti&--7k+LktxJ{{4`hd.TZ>==]kX8_w >cadh:&mr5#?[sb4k&Zf-۬BDÅg[PySp^mdz$a ]ǃD@=F=VtfkUC)J=w8 Nua5{tЕvC/hFA>cc= N`p vWgj*ʆzXxRu~taY3̉zcxOaZk롋1aC=W=ֺ.R(z<.*+0kN=@BZ8z\VqEeqNnL{^+ihl)LK Ux ̪4$sNz݀7TSӥmAIiOn-!~eQuaq&+{Clc!BfK*+>~X}cL\v1˫ _}wf9?dw1ÕNMC*@=Q=֭YXƬQRE_)d|~#-Ņ5Ӂ!QgNpg_gN3sO˯ꙓ>=$DA#> ,-dYO_p-y}t>CcNi-zP伸Sگ6[N_ur{V,D  !*LPAQ 7_Սg?"n׳Ct-<<5֙5]: zޞxJO$g}Z4sSVwuM=Dn}:NSBzതR6"-J;0,*ڸCŃW7=ہbV>)&YܰK"vǎc߱K Ϭ=7lsVs9(9@,`2<.chهq{wqұq;v|o߷zPP@(Wϟ=B P|VqWA=f^}ܒEx_|zlcq:H8!&@,oyH$=1B*|kz}3w6 ',]z >e2Ʌk|%ww `1= 8q`"H޼z.C=,_L5G="W?4,{?c iydNz@=wP !d~z@= zcE#,$(b0cQX@\c Z}[?W Z2PaAQsS5zئդ.eKjfZܞ@g4v[B(/sXm}3+E'Ԟ dn~-c&kFX2lK25GcُFjk߬07۾/v1{|,V1Tx;z;c%ur5o8q^59{V)dۙF9Y4륯*}aBzB ˇ}K]=" z(m(ϸy%G20.lϹew-­[G}g3r\a %VC/]rt~s47~4n6/SwTf[3ǽhܒ,ܼCTsɇlgx׹y2$ƞjleqwsާĒw3=a?nUN.ŤnS=T?_kx+#O=>RwܞWq9-]AJ5]LQ^>UWvzSԷ})~d.Gߛ-+3ݔSHbߨqI_B_SU8,fGuq,+=+|XYWR>F9EIo|h{LkOJ'I, Ebkքh/3qWJCPw#_Uv/[>yÊDϏ\M?Gj̲ ^Ԋiak8o^֣vv͚{ =ygCYaP`QGS'z(;{9lk4>\'fsШez Noq9靄nU%ѫ\b_/"nCHomPl%NG4QZwnN _mz5YPo͢Zywu l74Φ$?gj]l| 70tE3vQ#dé uvs Z2=W3!8btC=Be# P`gOC+ 8NVO.Cf]f͟q.KZߙcV( jFRًr>X,Xn 몫| ~q{'ZZ~||n{-hT$ n[ig[vtN)u5[C+Xe >[i㲚 C=V5NL;#\7ie[fr 7Wv~p'2&>@=zq3 sf>9V\z|Y:kiUȁ:ӑQԝw\L7^XB x3Px˪zhj<6g11d%{Z ֥lsؐX=0SqyՊށAt(_FBř˾kEqMɈ\| _`v[1/o ſEB=@=z#¹9NOfUƸ6cZ5o^ΖoRG "WS;ӆL[mCy W3n75m-Y+ז5î+=NW +p(o+vIU#Y5M--yw)+i[4ʉ]OQNngKy[T6޲;Ù ǀGUSJQYKe2P+vןf )DIEJ-PGCe>N"^]L]ZkDA0PGϼ b`D=\)*9^of>[:<ηSԮi]3=oaWarzXkK؎e8x|sFF']qO_KVӜ>!/eaw$Wu6BE+gaa.A&}Kdq+)=rpΧW*SK39sw}W7jrрzȞb~ ~@=%_NzzF=?O1ncǍKɷ] tŔj@=ʅbIu>Y[OX8uݛWzXr>T)?XBܵɸNX q;7 ,o c.D'h} RۆzG/l8t^.z,ȅ臕3WdXvzz,zrɟl=PWR)qRD*~14/A=ÅhǞ)zPO|owOWRIP`FǏTetw6#?U * R9WmhDŽPPţrLRPpE6df2c#3E@ PFzz@=c>?z*(\񓫇FԘ`068xpD9#C){V7z'u.[11HYsϡP=uwBv[Zm|AT*O!KIEۨE8c>gp V>Sq؛wCa#x.oc{|WPa* fẓzX|YJ O9Och:U7!\ eu PC=`?c"Xo~;['l\=ԣj/=anPPŧWiBVIkxJ@,ZQqJއ~}*;C=:f{ԋ!a>L=)at:\ԍvCJ8D\dM H;Ӕ:\Ȼ@~dzz>?#z/y/ȕȚz $-#_{ehj+lDԒ丒Ԥ#o'WpPfg?8w$TgWQI5LfX`z9U̴9s-oŅ6)900>"JT+ ~-vjE䶃Ir,zzu{x!yoREa4IwX9a@dzص[t7b=E}h:e+It>?,opˣdy66xzX͜ItkMC%#b,aצ3kF:;EHWF󦾑Ep!w~YpAC+ To |О,X>\͵߫XP]6,>B? o{M!NU3]rڗ>iP7GnLiFUNB(M=mjW  ?6Žޚ#MMN< d%7q cͲή^\;m1˫Œ΀}z,zX|@oR)L!i+_?D:_?!C*pAa2B_瞼+(//xq.Nn(^su- EXU;3 C;U\QY[?,9FLowE5 M5i S]Y !Jz,c}@=,kTJ5&M*9KZ(՗ұYu˟;49nFuł+qayqC<0@^oGBh> 覱?p7]=NݯKT=&Lۅzz,z_t-]RO]kPWeIAz,{n# "`ٟD6;k|| DӣDY6$4TP\z'uA=@==%ꡙ bՕe$s ήsn $4PT;XzsCQPPY)ll=qIɩzSRGĬ/PǒS{wP=ԓ1&_z4BјkxoGgOm)Cl&?YMsPUo^#1S= )k (7XM^ߤކ{I$nU~&ޑv0 {cDŽ{xmc=쫱kWaeLpت.i}~jG2/gWy~WzfzT̜#:&? *۹R>}:d Z,vC@_U7eII{H!=A~UCd +p߭ ?APŧC΍`f@=@=E=!7G]J)QhW{bQs~!-qIs{çAԯq"-qǴ坐U\4֍r9ox%VLVllm'1s9FP͆z ~AQ0kc=$mykWd +.gԃEӒndx'`:,/+mi ` Gţ*kq;RGy#h5rf/jEH1Qew5;wPXP+#sys9FP(䶃䘪GoW{ȯ&[ILTA,ޛcm8`ZL!]=4l_ ʛ,ܐ srQEwӥ>.8ăwQgܻa/S#\.4tʶ+:ٳzdRۡVLգJ{}D,YKHIz@=P1zhy93sj!Ay^1C9j5nZFPpǀWej%(!99 P!<~Y}0cBђpYnٖcTdvTJSnkIϱNs5Dž-donc'@H2XB^cǷtR\H,0gfRAyԊuq\=nn'6L3ɠi*ˈ_bwO'ץn̰ Aker1kT(Lգ?Ňy$ߞgCBfiXEHIz@=F=L#3-u3\z(Ci-MSBP돗 5KslT2Ҫ̋A.^z+MM[GKE+(k!PGCe>NcJ~rΝ6]1Pˋ4>׃B ˧GsC !I#M06B:PC=Pas=[[t[Nx)Ui.z}߱\Xkzޞ߷YK\(O:|zRkaCޔFkV&ٵzy\-Mǎ%''?kC!2z <y4pأ(P%d!PƟgwز%}j C~[ !JMՃsxF -O3&#VOA=,1{M5T2f.D?zcV}z̋z4*R?5eɘz@=fz,uA=FmͪkizBՃdZ;PP{C$|㲗y,``2ꉀTJPV9mmX,lok.!2>)R] 0MP`-|nkscSc9DJ%:c+P(2t\ZEFtwuC z@==A l*j|\$lTiPoyWdL, 9_!e2agmczX7;PP{)Pȉt .{\{[cF ZaU}m\z@=Q=ұZG#6`/oBD&M/: PC=b ߿Z祯(&$5PsB=@=HASYԃ  )HY6$4TP\'Wi Q]YF2':6oѰII M%P3aQ=gL=8,fѭ~=LONXכz<"fz@=zLzz@=,z2kWCh5S` yM6-8e䧂<[9w|zbv2,z@=zyG,(?z\P= )k (7XM^ߤކ{I$nU~&ޑv̳[WZR$?-Ɠ}4Z(ڽǜC.i5EQl_XXbvvJFOk~Vo̾i{JV8A5d9w=N.ZFԞ G7̖b*U__ӧC "i7z y]u#\KqNDcWi>;.nI^0_z@=Cuo;Ey=+x?r=96[G)xŘV q:QrJp6Ui;!i%sz_xȅry\f3k\H;^IU.FP͆z ~AQ0kc=$mykWdDŽc>ZjCRL/.ŴXюƧ*~QquSwC+KZ?kq~U])J!GXLT!tan jgȸؑ31>r i&&4A+_}|6bۮ](+Ƴ&|r,XkPr Lj0%Xw E.;FPt|q2H<ݿE"z v7A(I#PЈ o(gd9%wOFr>Q+$͕4m NxRzf"f]P!KXG_j!_K~o3aG/3qWJMRbz[n)ELJZmDșe!k~R+<wq0Dٝs͎]d+_Cc]!֗tlݿ&#t3 S!qxoFPgp V>Sq؛wCa#x.o!uFƇҺ3\Czx1XXʻ3sL(`agRTPd熬%QYiP _IVxN^7(wê}.^LC&Ze=mO7SrC|~O dAZ@H2a/4ƶ-rކvNj1W~czLC+.?B=~@=@=;TŏV)؁aFVޓ;mڙɜ\!Ay^PXiqL˛jFFPpǀWh%(|lbTdvTJSnkIϱNs5Dž-donc'@H2a'ZQqKއ~}??zDZ1_P93?ߨ3V\z0s 9ڱ$ ':+Z.lVEe83˗rzXk7-_FBka& $Oc̾)?c"SPGgk}9I=Ͼ; <$4 zȋ`W+4w]\hQ=k\whjŅ'W !cuQɌ7je@n(E%w9Rj#g|8^𨪵m^lwR롴)kKu1k$C o8QoԊ :b]Z{ƍrܕ"dJ(EFFA^~!Pգ9}`Gu$ގ&A^kPY!^}](?R=^^Qr"A=pG z(ysԽ*ô9饌<,H35D.E,S.)=rpΧW*v}tv)w| $ZKN>O~׆FCe2S/y?e#^h(-GQC=;\i/b {*. Ohp`| O3wز%}j C~[ !JMՃsxF  aצ3w9񎗇sߗ(. =!IpHR-1`5Rɘz  Orrr5G~\WٝyK;Q#lM}-NfxΨi4= PbbQ7Qi1(svHP{`CI/#G^zfڠXI4ckk 3<PC=w7RG*cdxvpجb5`J_] /ev >} >4o#Xz" *%:cIP?,!m9^'E*~&) mmnlj#HTDz@=p1PkB.I_>O'5PUD(+lDwW9>PPcgQٞA l*j|\$lTiP|?aɘXr\C$dH4?Pȉt .{\{[cF ZaU}m\z@=]zC*~,q9bcҍ/D@d2>; %:>p6 as=߿Z祯(&$5P/P쑂ogQH$4{PYQ&9l1أ#$zB?z O3297߻}3ƕde.JUqvCG!\>ruw oPP=gL=8,櫬&nu JzB]=&prWY)3z2kWCh5S`1o\NRߑdUNy3S[6rO%֝ӚGuo;Ey=+x?r=9^FPLxp=ǟYhjj(!2u#;/ՓQ<"~*Nskσ>}ȸINԃx{73Ǫ7-lzyT J;R QquS5}5Қk I 2} ~EۿRi;4f[fÕ'k#5WM]v #yV٫DoL:\A=PdBrxӻ\znUH&]Eڙ1"_FP x=ǟ!bU~M8"-ォC=`a7>@= qvܕҩ0{C^rԪ0&\SPm1SG)GD=Ϧ>C?o{[ wjB@=.zhݙ9x&geL&jL=2i'iVC8.qd.0vܨ~zSsfpf@hI=&v6wB b=] jKkQuwdN ?;P;2F䋻ݨɘPfꑕ؞OҬOrwG䃼/k Emdl>[\!~v3Η=Ђz0[bf|\Ү)w}LKmyνSPoWXMÆwLb}Q6{c"SPgifˡ[] WM\k綽<`w<;Í㦟76zجs[{I87÷[7n R=,XX ?zg+UMQh'm)J-e׽m}2 5ƍZ1^F yyz׵_H!rzGG >p9lsaL*UL1^ǵ-Pz7lT!_>Oq  |@}hFFMD@T*%Jt@=C,5J{D.sdH(>"R F==M \&tw|L?0dY;jEue LJ PƟG 6 J5>^aT4?!ź!5D"L&1쬍4X`Lz@= HP¼Y˵;kĠ6z[  OeP@=~RX_gi͍Cn 7!"Imy͍]Y(ѡPcgP뾞e^zKɾkmBjXLL zVOHY㳨A$=R>clI io<caxf6}TWC$:kb4lo@BASIGR ߾Vm =QߺΗ]qK,Xꑙaačg&Y'zMMJ=HTKi^W_w+cn0k7m`!쾭t eԣ,ak/m,7o31qkP:"@g cyz2kWCh5S` yM6-8e䧂<[9w|z m~r$(a=WއR>VW whGͷ%Ues&3"re"@2Sb8q Zmfz06M}?V~r줗"7i!dr[Uoow9z'󿪇Jw)kHD a#}y^W%Rd&=!чUFZEgH˘uk%$m㶠ƻwv1]zm$p:X^tw?Nouקmq!-ʑ[7<ؔa{>t}w.q/:P\Y=]Sg㼒#SCjXъOzz^L)*zNQkWA+xysԥ/-=vBt66LQ6r TMhUSiE1d9^W; O\ wK)^|zfL={?x]u(Vabյc]IH{_¶5yIhjj(!2uce.,P Wӏf݌aU=6'%Kkju|CAσrKJ?`9]][UHngԽUu5or[ظ 1H=5'Rd^Tz؅rТ9q QquSˑ%y۟5`q~U/foh~+-Wf>ˎXU=wZWP*mf|T={x]41'.@ץjˢ#n$ +i<_Ϯv ɖkWAO>G\f}+{|ʷ0\=&_C^ eY:\M DZ^ uq/W0Epz=sXf깻r>T!酹+#F`OHn)$G@yg/+V:Z+֋K#(uOWqI_B_Lxjt!YmV;=ߨ}]/|>ΟXȞ]$T$&(%i${ԃ[b G#C8!{7E©b-NH/cK{jP?}zu)ů%:9g])6Iyjqanus{sX71(Twp器(ň}7VczCc]!֗tlݿ&#t3 S!qxoFK=j.P11_4'zr§2԰PA#jL qF;jNݓGv~걤7>@=ʻ3sL(`BM YizDvŃr=S'^IF:j 2ul1cC&Ze=mO7SrC|~O dAZ@HAGlwxwp#]crG*.ncx' 8C[ൂ  stìw~|걤hPX=TwSìÕ'cwbX!iy93sj!AyNP$o6k[' >q[aiC"*T=ZR7s~ogqa*ٛ[X?‰$4lve;#A^yn5hc롛/KJu=w}LKmyν8P@=v6uR]McB=zLLMucrF}=:Bv&aÉ-6R" YYCU[ԺZل5=u@m]=׊zZd* SlmO0G^0ɷwG!8G#VF-VЛ{9t+k}} ~ߨ} ~!OcՃS}3|aލ[T0cЊc܏UJ_jPemcTvFQ'3* RcܨQ]rc+6rƇ3Z&͢vR롴)kKoа_ 3XM"kISfrj՘zX]e yyz׵_H!rthNߵ#Q|h!${I=TVW_W"+Hū#/]}^yu˅&1gC=&prV 5Z*6Gݫ$Ϻ #)? SC@[[t \Nx)ySv侊WzexJNPn6kI=<.c⒓ϓ䵡rT=FG rluوW<J 8QJYP{Cm}oa B=\C E߾.k0PPQKhx9{W-[ҧƠ\M111䧱2T=H0ښ[jmIFc/B38^217C|S}@= tRQ?2wZ>5`5Rɘz Y,aצ0s럅zíXv}_wCGD)bAEA,XvEeUPQDEEzNhNH}i ]CsqL&dd3PP  FiGljT z|ϋ>(㤝n@'L|i$ㄊ՚wy'OX\|QTzFlX$ll,kz )|bb~j7!)Ѳ_g./chP4-{T"redx(?}xhFF" ]tǺz~13Q~ׯ`]RRMzҨU >o4Vȭ\&ik6sHzP Lv$i5UOwwߚ*;d3=ݝ$?PPM`09v0Av5aP_sPFJsG i4*6)rK5>;WED:F|LwVuB! 9#ÃSZ v8`Z=:mj[ W٘qcxhXGDQ۞M/I[],tѡPoRBcoÿ.}=e%6!8Bׅz@=`%-Uϙ㋨sTeP$ # H#H5 PPMf5 Q[]AjC:<ûGܸ`cU~=_Y'> =MIE~,4־-RA?G=_,%WKc^IiIk\'t@=P0Rz$V7ZSeYӺܢv2:x6u)'Rͨ]|π/* ] z'Iy5onM!kSm:NeΧT%G;~ Irz(‚H˺d3jgH+ϖE?4@Vcz'>8zSbv״Vc'~J˽4cĘA+yKO}E=dR ֖37o 6SȄVrdxh0KVf,ˤJ%:Pzz,w iv/Tn藆QXD3MZJШ3ՃjTj xD;T'rzc4z|K ͼɄZ5:G=P|{'>8z& aCQ%PQQTzFlX$ll,kz@=lfXtIAZ*922<4`#~Ks#A.:`VT*:[IOہ_J獐f $m-fnI^CP_+, :cc"6 P_z,:׃tI 1;Hخf0LWz@=c WFSjT4N9wԠiFbkR)Tz@=k:Hϙ:ڊN(1ZacUc=gdxxVz@=`C^jzlPG{޿Mmk <S7n  p눀h4j۳2i .:_=f`nzاz(/{u)+)$m  .걉>>zhZ1gIA\& E?`K|t΍+ {7I^=Dc&z@=G=cziJ󼜬BZn0^v; +wWt1xU,}o AWeAi3_@=R=pPRU*Ǔ&&'&f?%$NNzh EyxS^[]^0(p) ˪k* H7QE/*8Mz@=6zL]A=`P*͐LSG,eVL%S>7k$W~l`%j걉P^%/[=D"bXRlb#Yc`t3d[IӤꑘjVCWtqI%v >MYz"L V_?Иv)|i<{Nb=I}qڬ"F`PWw'XgR r"fm y3<68uE= 1_==~j BPĥ}I##wܧfns3sH,3{TF m8[**2Wu˟\A=V=,Wمzc7k*ssR+ 7>(hܢ)ϟ,%ڂ!EY^I|^z(ĬAٷ~z@=AiچXnw̧9 $gVGaf鴶ԙ/.%ګiVGGwRKcHTb^g=~zLNNvtt t:15cyAPWFm; sRʀ(Wj3ˀ@5:{)f/W!R:Q$sknP%---Νruuݾ}Ν;===ɶ_BB#b'h !ƋX/΄pg9/~0c3N|cCi9?g:Z.J5j; Hj6/䖗18B^\=dݹVp+Sd#e})䣌²y|"2c܌5X[;GTTT@@'OrV4P(t钏ϫWQNsڴ^TPRniu&g<(y%OR/ƠP)Q7ZS/^Y~4uܢv2:x6u)'լ_\)^.kkyma_7dם.-||:lisa?L_ 5/:|QPP@tڽRrƆR)Vۯ6TG8)lNVA=6}@=xAZ_|ZIUQr3뗐!ҹѵqcmVۨcPeu=v}})gBTW-l'hkqFF~N3:Z}4- NAԙT5sQ669r~ǶqǦU_CH @=6zLM K},FjhWfLX}[C&hmi=s l3Lh5!J sdeۈ2L:Tciȑ#Ke;ܳUgrqY:gvL_9Q==iDPf{L~iEL4s䯥 Z=S=HFZfрwlr P]]=_RZTrqs}mg_QWgz@=Ĺ_Wo없Qo7PFD(--՘QՕZ +\c;>3@=?H=u& aCQ%2puu23`ߟ=^:Ҽ#Nkf( xҭz zlPU*F=x#CC,66p5E@=z|LSS=L&\8AqP'vu=g"f[3ˈpof5 ;Ju5k؉}-P{SZ*922<4`#~Ks#A.:cs=gnJ=irZ o*8uhF1IB=ӯAĀMS8.?~s=JEG{KQ^6i;KV23LZ!rm )uC= @=loNhT=߽f*;d3=ݝ$?PP(->vM`09v0Av5aDhmmQAvwwC=&WJx2ȞU==={IHHzolP)KTfn]Mb~45  ik4Syyyaaa;w  8y$˝<Dz؏z(Җn#LJъ&i=x'OJRׅzͥ}='F:H32XJ&i$j'᧙@=@=lGMUV]f7&I+$mIs` gQ&>} znx;NԣK^1#y1$ l,\!i+NэNfohh&wA=fPQZg3f`0ڑeht393 N|aYIslhoogu:VP`11W(.ȝщ;6ӵ4.̆?]G83{'@5U v(i޽_gzz0Q3=6޲ r])PfS>3Ǫ``7O&ZuG<)#a ֟v*Uv]]+OO~An”zz0Q܉#6=3HXn(T]σ(og7KnWz6*c+f%cq*v^I**++3Q]1GfA^﨨('''O& fdd$. D=`XX|]ӽ?ڛ~gBYWzYnG׽=qP3ߥ9t/iu"ݢw6^VqΓړԥbvJC@W-,^4*f27U >9yoRm|o5NcΟx|N (55_3oS.'eV꒶V3'ՙ @=@=GLMaF 1iIhfc}WI wvQ{)FI~MELDk.%. gRGSGbf,CHHV4.|*MyLwu=cn<=P\Q՜ͽY sWϟV/׊ O)>$Q&dV͠ lxS㇫ǗOǙl6[BQ*wšh})y'mhHY'3K%.q|bޔͻMz$4Rgsq>yX7Kْ4ܒf՚$M-t71䩹8\BR37E$ \)'w/ټR/y1eUÙ(bOfuIz@=U>,kKYRsfL؟'jtTZqH87wnP* U[.r52wt|À-Cg7>6fC*?UZbTI6JAɅmTƆF&(a6?m2e-ς(wIz7HQviSg.f䨩u1ݯ&)%M?]W|WPZD)KxQFaY׼ZBTgsO%畖f|-{3n\"i|@9zWZSɽ`Tgz^s7ˉ"'A=njoLJwicM:JLL(2LJ$d#]ɬrI RۥG)g0]"fj6K wZd*v=Lje+|3vS=j%%M݈ \ 8t)_:΂GCws#SDC~/ŖR[l>bNfn{?kͫ)DXqW!nB<(דE#6jzxo@=#M`0]htg2چcV.%|rkteӝjb$?$K$W?>i¦Glf z;Q)Rwi/ zjtt?zl Dte A/nW u%Ý5u5>VL2i (LAe$KQB{$W?>ik})-/zs+wjdz@=~zMM1.g^!3vΤ,# &,.Kk_哱ܔ3,#mEF\azfUtfdݒzV*Y/pd sD,/z4ԗ33? zACDr8iP˜+zLO3p==amȐTB"Tl'n iLMe$BL0'y$WHڊz@=zd}*UZdKK4N@aiiҙ2ߜ.!!j i+N6zH%Oq+GGiV;3tϋ9u6ti~4A=ݫŧy5U^x`!iO6z *@=@ ;qOg;)@=D=\CAi[1 Pp=]´4yzz;zFzzB. ]m-Ħ>N뗫- l$FV1 cl2tZ Θ@=߶z,zz@=w#66oz8zz)[MT?z@=ڼ[z8zHώ,5n7>=|GD޺v6<>Ȏz!]͜E=\Rp]fc3dO_}jsOw[=7sfPzt!i bG$5K=l7z P`g`@=zd\+),+?~0m$("If[Ǯqjŏ1ՂnXYWO.=1ђ΂ĸ#=||d ~}0;p^f.`͛[| FdU7bjPnHR|ԑPӓ ӯ_b53`35'og4GQZ??sxvHH  \9_#nk^OkMmMq5C~QXY]x2}QhfʪO.z1ZċѧemBUPT?8^TUUZ۬ A]vX\J![_vy;n%_>c">zmAUmUYA~ݐL6=?fQRS]q/Eӷ\|]\]˩Z; YA?^&P,F"YQz(/.}Rsƀ+z޽SU )̎=:)ϛEi@.xz|ҟdqBH¹N?=a1.ʉqw_1[vҋ J-?6oKZ[ >PS`Owg[["2t%I5x/ GL$/# EM=Ch%n7wvs@(f&mYcrf1RpxL\8y$V( o+KbUӷ } Km5"͝>ak$cP ¥:x<_=ztzjT#}--ͭLp_(V(+E<#ϹsIhO=U.Tp5\xy/KLc"_4[P-p1h+99/u.x9cR,"O|Zґ\^ӘG)ay[~LD0s/$~xՍS̕nnR_F邷wKV~b%m*͗]z8Bǚy]3ŅL<}C#Wm-`c_<흮<(<~z~!XK[܂sŽcL!?{ yzIu&_8{4I܃P -)@@=B=}P9I;|\}Oȫqu&;}w>9s=PtS`㢀~j7ԡ1u+ d݌q"wx~V-> &W @@=6z3C=UW})Ǿ K{oDMiY)q]'^떣~![l][OPzzŠmNۺ nnd;x9N}W AWeAi3o/!+v^!\C|Xl=B\}c'EzI=d6ef ~Qvrs}s sn*.zQmkw=2Mz+.xR3_py!rpN$w5ô ,D捘dw~BL;ťrF5AoL4=LFlNw}!Kb=ݏ%7?My_zD %(gjft>#pAABeOcüqyaKfjQӧU~$VĄy=}ܴے'My|:_:SB(˕B&AQӓwzM>|'/I10rSz{SRZz3̕+%;1u}4#)[*ro.ȭA}'4p ?嵊 njCHsuK'qrɳujf;˒<]+¼:NE)y&= s?|X5 MW=kaUD]Z^Q_'Q*{(jO\JNל#^/p镝S_JKbvQPzzXW}Kg`ƀ+.!rxtQC{*Pc>1˻R(R=+/{Q~wAJpPFqw:lz"Z~\IٔZIhWjC9'ܨ F3hC_n,p)9ÊEc~GdblXds7rJS<D NLg3C/Oy+_\]Y?Uțέ]~ĬAC|$_n:SNzY6Ֆ~z~D^+iqW΃S\)'w/w~R+;0t`!0vnTb^Lc:_<\sDԃepw>'j~v Ll4s=L\c QNߕr~f/_2I-1[Jգv*VÜi9A=c '>w9gS%Wqc|lW&[>o?u6څf ^m_+B4CzqS.'R{/Mڗs}0I35lZ=H#gmd5RTXJlWYQgWIktuIdS|Œԃ:J z|㯛z.\XLQĊEC(B)n2M67NF=Y\)SykiHu_=(k^Fjz-dݹVp+Sij+?VM(T`Rx=/XTYQ0'#~Ү6Ճsy":eIPبoe<Ǎ"!j{4g/Cjꫫ_tyIwa;\L׌vSĵ }LOu /kMW3Kz!hag {%]b 9cNB,1~})| siS=>b.>[n%#I4` [d1u@ON1yq]r[>8ʤp{@VsL`ׯz@=RCkMUا؈O~^H٬`Y1Wo5TZGD$¡k>NL=ڦ7q47n[)mVAYUuE(;"x|/K\czOħ:!mSq?{:|'G;Fm9{.Pa@@=k|z Pp°+7@@=PǢ!"?!҅/HTJB:jдLQ1QM[hT[e|oDr"}=]%>{kZđܡ>L::Hϙ|c޽& yVVWXz Vc'16P7&5e!|OVlwHݝyD,uM ٍWG{33Ѩ .ncwWؘNXP=,kݔ:tD^n~?k59W(d_>:| wݤMHP'WP`zW9;!viqπgQU6z+ # c*է! g293}|| дb+߆z0Cm=RLٹ9Sۧ򑆬~&Q z,ifV&D=&GmuWP`11Woը_uÇBpBm;\a8y}Q3l#H޿3wc1vp1o;|@\  J wJyrȌ",z@=c1Tޏ'-%LLNL؏z`]Lc!k{d,\I)UoPt7^llOؓz`P7dT>z12QQRqimʅ0YW2{4Ip(?R3l*W91>3):wl!wlqutf}c2nf|Lן[~{g^1&*}9XRlfZVCK!J&L@=`Uz ƕR.ZT=>zL(j/In,g|ۯKJe'`9Gt4'3Ftk;ubQ}wq&,7)o֖7csdZzH=p]\1TxvEc& +4OnOȯ,χ]DbqZȃ4j+yaJÉ2tMgX79!-(9ڙKHёtwX[c6YB.+,(AH9:>p1!-w_L/w1*51P?@=Л>Q1c!@=@ Ỳ+YY5_Pz̆B~zRL}fZ?F:Q5F@=lRx@ ;8i/I=y5_8n 0 @=XB$bH 8d|Tb W>ƕ5hZѨڨT-6ӺR{ |`^.P˽M٨Hll,zu=tt~!(.ˤ AtDHD">6h Oj5K=໿kWdu|TNm-\gc Cƺ^Ӵbjjwvv Bimm%mh4m'vm`zl@&'>`|/`C}['YHҢ|F3 h``#Ȼn&BSG(Iz@=DaU=hasvB~?7:=}R+ qW3a;]cwÓ'䏐F+~PT>j竇433iPyy㋨ 0 ?D=`b'&x>yŗf~v֟_,(V!A -%1>j+HM8z"cu=>çpEmJ;싚aS\"5W.qc_ >8zθSΗ,p¤>忽Jyŕ[%J \}YǢ16JxRI=`钵=KQ2zM34SW3^p+\ǢT*/!c3z@=CGQ=ԃj{wq-A޴N|7ՄUҥfȶI9PpHzfQG b\)+EcaxI=Qۜ\]scBQW}Ivc<;<=~]Ri )l䜦X,3)eIfVtk;ubݤ Y?X)e-M L9=]zoZzS8PCgQ.ZKV▿X|Մ(3 ͓{qs.=+aav}5,kMT'`jՑmU5ϿYJ7e;yna KhZzcb|r ͗⼏ݸ!δNe L4 k㪁`o/7{XȖd}zJ5Xi }rCem }o4Л_)U|Vfjר||CPp\}cz@ǂ]LK {zzĚzOSozLJlbxsNmLغeӮkbiYn_nש*ˢN<ϫmh)ڲ/tOM;R=Ll[(\MXB3kT~=鴶ԁz@=[=zLx]͜7ׂ[ڞ7G#H4yk}r(3W0oJԸҎ?|={8.8"kXhԶclzQHsOg7?{].-84H=p4ޘ4; xßmeߩRmKsTU]OqWV0fjר|\T`C=P;Uv%՘\s\jߘ7}5u姧MZoYd>5OdtJ;aMݪNleZş1|~̵*_gX-PVW3д&$,s3wO{ D Fް򢦭}>nrBZxKJ>}Y,i%UEά_BL`{b9Hlm﬿ϖgoX)W_qfm}*ΟX^;5A~Ǎ5ZmΚz(‚H˺d3jPXo;/IxVUk6y D=b/2155&,Xs ɘƅ ѥU>|c̳7_%j{i \TTB96͛7_2hPBLH~=btCgQXD3MZJШPplzaĹ_Wo 0!-187&j([=pX=^A=6z& =z#C5z@=PuBRR=6|jF=x#CC,66pJ=DRȱ#C_AӲ,T|>ׯ]7<4`#~Ks#AzǶPb(/t5͠V23TѹP*;I`08Z Urk!IZuPzpaK B.lo}6xA6<دѨ7_j4LDžxؘEmu$Lb(PplbccQ%VZZZxn`s`jt5l/iA*@8v@=0'>` c '>a iwod@@=.0AbBUYP̣9ڻl7d~W+Y=p6z(Om̸ R'Yz*.zQzZaf+$-/O~(YNqŻB@=̰8z,0;->z'E\.;z8켍@=QH6Oa^/7<.+yr:xrd{餒~ٌnȼo;|~8p'г/ji{= 0=d{i'n#RS JnycVșI:RO3r8O:nnW\fj՝o]l P`Cg7Egv='.%kNJ#t46s{o;7==))-MJyŕfz{dRviyEq~ydz'ǟ啕~t^M*Zq+ԉy@Z.Tq*rN-m3>a.uݟm5g zͩ?b~_G)CivzK+Ejv@Dΰ`٧$ )UB[{s6aN$ReB]T`RCKٔZuK\#^6v\X2?N=^,I=fO6.EͱEw.uxAg yz(x9)*"kt@ܘ@6-yp*w+읉[XjoRAq-"ZEijV[W+а/ȾN k@ $$wf&I aI~='d8dfL΋awENC6U0;aaBcC=@=OʦڞQA^,;rcRQ3kcÄe; ^zUKGWKCC1Q?ʌ)0^TPVB=zX S>ԧ1vw>ElM3kժdiR=賰1Yw;V) Yq\=X^?wJaIsbQm 3:i 5[>)~]qUUQ{UZw<=azUSkGKG`~M/'[I6OIm 3w~:P걌Ek#5y ꡐyn_MTY2\3^cI5?IAK4q^iUYTGNcX1]bXMےn!+zr]NzUPzoPz PPzlzH"pzfjcˀzQ\Fcdx0/';=K԰loB=c!xy33S "#:|RIOWwQ+!' 7\ˎz]t-B~|RXQŹyp6}| `Dp2\8=YZ?:27'i[qlkKMXpǘ;v}4!b=@=ecfj";=Y]ف+4VG>oˈzXIB.kmjzx Js w#7ӐTҲsPl5VMtZW)))"XD=VF?hg-q# jI͔8 Z#p\Y=CCO zl)Ыx񡡗Ry*TK B=|2zvɸa|ȃYh͔ĐE Eb*~数')[FP?Q30l4,2 'C]LC#jX𙿻f[FP?H3(0jcjdVAL#)sVpkau<%3Zi4?ez201z,c9lypzY,*vNiz%H傶 Smj̶ )q@'):9C=5.)ΎVO[zzX)@=ҢN4a0L+ޡV/`W<%nB"B=FĀo}J{L=c}Iē.U+X־?e(>WVt/'vR`}+ Cۉfm^tuCA= 6 ;ԋ$a2´HPM[Slhխ@ebzbXPxk+qCϿfySZPW/ʶ{_>?]dBPT˨=,1} O>RH]?22Ę /'J;M+oЏT+ʹA 1cy)MW4|vyfܸ:\peR86OprҵKKT%ʹ9ܬ)f$FeEyLL w$1q<k!B"B=p#`+eo_RZ=(Ȏ̤aa?&76w/PDz~N0'yNUcsCy'S\m9^`$f$ I(LI6 )hT%AmV^E5|q Uqr]{AiEݟRy>}iS4J;( o1LJeRL*!ۖ!c:d_'EEw-2S9hJLvHr[ QRcCJ"2!̂N ɤbqB.0s܈Zl6zlz8f.NHx|޽a4)L#MOWnۆʹ܎nDl<!NuwVh4wG^/.}PkSPjaι9EiQo'PMQ?!n^vzvz2"+OEeYox@=6E=H*Z lqp o!x~zlzLON LMh4mPc+opzjBN/Q@O(7PUfhjaA,g,Th1걉@ },c@ P6> zGz0.uC /Zd[>3Z?0u[`P[> L~iō1?wz%$3e\+Rz@=@=6S=pj[[Z[h;ۯܳzTd)~I?qwZ=L\5ecyzH[gO 5܈=X6<7/F T.cBfS=.]?2#<=}? U9Sh:V74TĕzD6`8}IζڒjonD}#Ü majtyyP1Qy7:2./pR=wG~/5}rRn]欨ȓjN2M)nyrn3zFP= qz q-ܓߌS;-|ٗF!|u>_VQ狳n P۞W`\/t|qXr~P>.~=D:rT^\R ]z(y엚iI)z?}I0'k>Og##)˗/c?LCV+s. 3u.BBǹjWqA=c~ip[Xt;GUp% :~ÃqL 0=n+6ԣznD>z*np%+32>wL(VqA=fտ2[' 3U S0{/)<Ive˔fn̕)WϯlntЭ onD}`s~=fnVz(§WicfQ-{\Q=ӝoYk/pddoVsSpSeF]9jbޓ|`8 c!gvƛ#p#?/ce?M i?( a=z@={zJjZjr_߈aP .ޤ2AN1K׷CbW2A=P* h߱z\RpzH"pzfjcezfT:cseF`CMl C;i!EB;y: 鳬 [BPAV~Y! Q0+:}֬ ::]ƾYXhq+>fR#= 6L yI062ǡPi4YpwGzrjԃLf%=zAiQNc0fiPI0+Bci7!Eˈ WK#8gDzMXiVm+}EE& kWT/]ITaTNٟzn: ?y00nP/.`WynB"B=<3TPy8 lZP^VV d^&Y~돽,e e׉?Y0; 8SVTʪ> e>T3xȵYGӦmb}~YakO{٫X_\U-C8sU~MScUOckLN<˫jh̸nVϨGê!oR >yUyG_o{~ r0h---1V)A zxO+~Kʵ0jzݒ4{z(M,77dcN?Uvzl;[<ض|䒹z綳uhxY;bdʈd}WE\B;rt'tb`V_jI$ډì5(C/+reDg˟U+;\n~ ~Ю!`Fb:YQ *j뿮]FI|EEVLHQ@'):9Ce=5. Ύꁟ'8>ʿ뒲X%0\?T²]v8ùzPz~;'HkM-;lKă/o-$|q;hFFlzbхX^3ӊŞ{X8R$8kyr+zNb펺C?ǀfiZs0yD|7o>yիqaׅySJfH)Qgg1nDzfpKXuoxJK9N")\+T]wX{,:V듘*aZS,֧߼L D:( 61$A?R1S*.\BCL׏Ivb(R x\=S[%LngEmTfYcWw}U/%ס?L+NjֳTiq(PˉS}4~0k׍HfkJ"ssʹYS*Hʊ򘘘A_Ib&$y$BE zuPJ>c>3qy؏I卍a T*JXz4@\`i>>#6 f=ϩjln(dS\kNZqekۡ;iU--YX=t1Y_^_yC}YN^\=YQVNӋ9^`$f$ I(LI6 )Ѝ۬&׽AdŁWu"[(SQ,VA SD!+U4SU/8w֓?&WC;Y;z;cg'v-6^OÅγS4J;( o1LJeRL*!ۖ!c:d_'EU^[1.h4D;wPAydC* 6 )խlN &x^*7;Ň\/nm$jLrTXOI-kmSt%>gV3{D{ӫWܢ5sPE=R^XR~Vȃ.FS4j55C;9cbXD- zC1dLzLJ>*ss(R -zճ7VJz{[_LJ8s_ҏR k{:87Φ/Ûuu| 7\kWk8/^M?ZQGO]bxzA'5*x#kۗ,[:VG'=C0X, ID,bGDdL_j5)?XޢSկF}M+R |3sa/mds_p2 ڙaw?^7K>邫2joA=W;zqjz[ 1c$Ɛ|t:NEH zE<zIq>!~JŎ*'<ٴ~$,>(FNܧιUh4 z7oϪKv^{ܽ~_|ݡKKP$'%`!56dM$b!R,bL*ǡ+䲙ɜ 3wKJ.հƓO\,o̦C֨گѶ'G>_5/s_<ȴN.`C/ʧKk驼tD:Oϻ7z&2iD zT97[v^3mQYg fFw~ؤϹSLJ^J}I=_upz^{|'F0ɼY=3 ;K429zqQE샆ZӜz4FT +w)J v=AfݢU=zP00Ьl!{ߪ^ϼ>/ fo/sWN7UW{|'>W^Ilhk .eg'#|,޼TTw@=6z̭Z׫N'YWÂ啓M%{0'Ľ{oWqqɹ->*ԃܬb^IUgYzx:$op}o*=5av西+ǧ'{_<^W5ob~UsW_W}0v_f -)¼vr]t~W^{38p@ [maj uj֞obIl'z, *_O y4"s-算ǭ54o>y:E'Vt(ࡸ^'Oܫk}ӽ~[zW{\aTȅ#rTȡPuUhSVBnl=2XQX9I5-hHl:ȧyEY$L),\/ʷ#U 2%!bPj% ,NRdaAcf'?<83* L HHd!kHdԴy}d;Xe52tͮ_tZ$jXy<:M/$KOKo }289=2[^-۹d5qy:8e"z0v}Lɔ%(g{eD/KGl|P3p6Р-% D}e) V wI!-^N`jhI(%r\V+ )=e)Ie}+n/Sؖ.MN,f._bQuXyH~P;v*t RzE4I7a5J==X0-趩遺ܴ̔TN'!zXfԆQɬapGU}?d&%-f\j]V9e+ҮuK=L/DkE=3/|f(1[$Kyɹ32zج&z6X=6`cV'?:61[i[=r,Lu}6=!E@=p6@=L/D_ NUB̼CFr~_(_j.c~\8`z??Z9 Nޑx=2$?,oq}f. 8A87H A?̾^6vmD=UjT=khbUe/TL@=\|uXP%?SθþzȅrNZr^ׄ*d ?/ =>U 'f`+Z=Vf-}x;KdFfzֿHU<zIq>!~J$hn$T%] TEm7K,[ZlWNg :kaߤH4k/˯^zp8LOuFBZJvMst7U5Nu,Sv37!-vhBPFj8i-ݼ!@7w%dgu[,Qr{gEIiA>D=̼d0Tlp#DRT4uQ͗ʭ dKHK/01]dGj.c6rzXl͋A5I{vUGaGV&f,]_L?t2KJ.F166,' X,U_hyޟl-pGC.+˃z\)(g^M$/)΄63h8#)0y\ښYKOafM/ZU3+\.M3 6&-*0N>d1ACp9e89b.sR)zr۹NzkYM8S3嗘NR{UAךO(t:sfrcƘzyE\7ˋFLuúՠW3g:n:qaܞW\: y޳aD/+{C@ PrȾ_*KzXOlWz0̃N= ׼`k\ÏybbVu_=aC/]?>=1SqAOfsq@2{D=ԼI%m"do'9NYPPzZx3{={7%{i'M05nWB<],HJMu]jwwԾRƞobIljm~%F6XzhE C\z\!/)-/q0ӮzM@=Ȭ]z̊zJ8 Ճqwj(5X<"@=|W=\pzHչii:\$5GC2yF2-T즴w X]`BblZ=VcϫI^gKeYۀZ=ԋs +ARB=k@+,xIpsսE,Mz6#?Fz8)ssoYnEjYq8VCf,=gSY㬡Sz2rhJ01:ok$z *\ X5zEw7cQF4R%(pjxbOf>M7 NL{z,,(dRWzzOOO3ۭwp8"D=VJ=z@=@=6L=ҒJ"|}ԣ; !I] ?ɓ'$͎?pǏư#ܞnohldh{;\-,ˤ&z{LzqHWAtHHJJp^ qWPSPosuC%5ƼW052Q&?Pŋ9Dd_`fH!mn=~z'ļrN*Ǹ@{TPU_)эQm߾}| > -)ŒyqwKf,딓_y^?8C=6^=p@=CEV!Jعs=4y^x:E'Vt(ߡT]4s5l uz@=@=<z8U9HJP\Wժ+d[lզ G>1+͸2x?Zzz,9*P>SAkfjcnN!8 D,LWA]o3p:AXbq@=V"`hfcV4f񱑱z[zwWPQu[C!eޮ_ .ĘjaA97ZߎX+D-'mw)ݼ#4zzxzU+*Ղ'H.ZJlkkk5ZTKc?HMpuݴ.@[S=#G:}mW;y: Qq (\*(LrK;z{x>Mzz@=oUJJ駟RRR6[=,P>9>rP'A=@=֢k\RP2I7'Ruަ8Ƙ fN]" Gu>,_bR@ȭy yZb~.TNgd{u3d[>=X_Z=0U(buަAAA### *rxۼKi\gC=@=6F=ΐd=Cܓtwo;u?eG3kRz V=/QA6Μ97X+,xiWoIƎzͥPyqwKfٯO㯜殾^fG1͖_9z_c dyf<R!!oݺuȑ˗/):9C=d'T]piWdj{ȑ 2[z+1אrnzNJ%߹ǦqbC|QHnO޳ȩv!!W?DvEr`R~+',>'3 Z=LO0 # MTw,)^l`:ឃr8}JU䕿&d#кz^Vt:_1lMpOC=tljC:{9$)D3)!i}v>_+j2f|rV>6 k "pYˌ#$ںZxvz7-5}$A?UMʘ_]~S\S[+aKM̯.|x2^ ƈUvJ<P`R=$I'~B@;+C!(>"g|ĎGb4;BdeÕav݆!7jf,UB^`R,m3nݺf^Gc-b PWw>?w8#^Z=-d$Tq  '&Zz(Fsα&OƲ2;aC[AՖ_yJ=oHpc5_ߪPXG}b6N=y˥1]z)}2u$;cFfǖϻ[L=V'sUe(-[Y| eꡘ5;Ey{{[eϣFxLkeMZwʏbcp;;\F?C=>;ȲK :]jp+뇰Fpq z,DX _ aJEbYI 6;qIB>V^vuueχX 39feFpKAk27+U- y_d逈G:Q}U.Q_TlrW.:zZgw,|B=!gPj@:x>̕)cta5|=#GNtb>\3^uB.Ϻst ČO{˭uS=2x|-:z-zf ݺ80bXCHC<tZkcc@u7^=ܲ ֢'.i̕U/j P?71-4`?"Z=9Í/03$yҸ~u*Jnͣz`[kDZic+qn\q"o[g:@=fec~~Ri-ׯo\BޝoVXx%$ ߒr-KuR8hqwj[:[ՊV]90NAq%洹aeWZR+ά9{Y\xK{ Z%HsM=bH8=352lX0B|Ν;}uNέPzSӊI5Wu?,9H~g et]cկ8<.clq qZ\9Na:}u:.ߕ>K}Ub{;p=+ʗȭW^(OW}bkN?/Oc^Gȃ"PeD:Fj* rӓ}]M kq({z}DDwd`kMbqfs;9̵trzR6}< \Y:og6->>(+,~V״dZznA3O VDNRAP47y%Jz:P{ S^O7L=][LAj+&*NO揎 Ikt:Gs$ ZRGk`laTWW׿}6,,,))KWWX?6A+SJ#\* JeXz,;k"z2cfj";=Y!s<|\FJR7rYkS&TTT?~?͹sZc ޯ<~3[{ޢ wD\ b6k5VMtZW))rT4XC}?H2?pnZ\jWqӞ]k&jIu8 Z#p\Y=CCO zCMe,7k=V3W-k&J9%=_<&Xr~W0ޫ^z2T4}hK @Bk$,L.)0ɓo޼a6lݙj8Y@A*W76܇cy͋yjh8[ 걎d)O^OjqVTuHNF=DðAXbYnW@9߿ooog6lѫ1)F>,_qR@ȭݯV[]=<")+VkX_4CϿf=]ħ;l־teR7/h)'(-u 6ٰv)B"B=֋%'}I/T;vmޥ4][*XI2HL*ſIUY|yJB'] c[XO{`뒢Տ/f%Mo8FУoV* nŕHt:`֮]50T4ɱJTh˻`R8sB[aKă/n-U^5RpW=ۃB=L*(gPy <" f{e0&(R &jOl$%%]pAlVW^bK_tG|K^v+秞:ZNVrn;H$=Y/+9}۷ybzF1vƼ)o+Kvnְ|YzqMMȓ=wZv_Xԙ --3eBpƪ̟Y*-OcDI!5syg>Yx}׶$ykۻ{ZK_rI^y12j_vÄz\N^;IgY>"&(*ehjyP؂[\2)X=.^pȆH$";}_%~R-.|Y@zI l~@qɇ6Nwj۶:jێeLi$XG0˟U4Xϕv_we-,vg9FV[XɊN_OG70j󽟄+73-kx/XѓxQg)L2S u(ثjU>_0uZ\T1EJ_׮]#$^|q墢BdW(R &kAAэQm߾}pZ46jPuȲ&HS(9زvgm;_#T?fѿbQ ӥZPόT'8(Pn:.ŎoKo#>c(pQP8pmڵvv@^uM=\.*^ϢPj]<%۷o޼ɓWDž_M)!!(R &cccΝ;z(AxŃ~wcAUw}_ȔRK<ڮRv[֮OblO_BWZ}wX{+FP(xM4\jE&_Gl up;f~Y7u1bk[ĝ`w`'aT X8 /'J;M+oЏT+ʹA 1cy)}M+33> }ޭ4gM\r1Ί5aЉOmc}t` PzEo;wJ;/Q&vQ=Tb}GVw(5XCI ^1DRc#@//>vjnyY^z: CR %'*V(3jgF[x d_=檝}$rҵKKT%ʹ9ܬ)f$FeEyLL w$1q<k!B"B=lBMUZzǏ3{]eIApUꆙG<+7+,gX{T567p2PvlTl{γo[My#V\vNZUKKu'vT+;YzrE9 em짬\T~˪.jl'o%7Tf?8|Y}b\}[TX~#V7 wb>˩jji(3î| Z7ԗu3L%o%h 1 !We$ODaJL_HQ@MRSSݛlZN#66v2t@#G@'?aʝ4V4oǺ]X_$fEݟS>t=,TC|'C;Sˉ;n&te8v@O'B?'=zh'kGz(ncڪ/49@%zeo>eN_}KφeEoCctʻg;]~s5ŝ—] 4\V.b^=/I|{zS4J;( o1LJeRL*!ۖ!c:d_'E6 'LOu޳g,skSx:@ېaüO 3hvP|͂<2!I AkIH OE z؄̌z;\W =:\A=)HJ/jjhJwl+AS$ZM P(DΘX*QA ސ} *^!)ITTTqqI=z})7-fH!mn=~z@=t_w}qES4N"zp\!1Ba X"0XlӏȘ J=jR &ա޽#AdK+,xiWoIƎzM2q-:HSN{y]_;Hzz@=IZ8K5yP A bcH>:Ad'^\$B=lBVmmm/_ؿ\\yA+:9C{Z168pv48}4s5l 133Izz@=IN|K|8Cjl:YIB&Y "T,7CWe3S9fn;U*^gntrbz^'#u=SyoOϻ7z&2iD zT97[Ihh{ J<~. ^"t_wgUy7֚SzqQE샆ZӜz4FT +w)J v=AWŋ_'/_}@=@=E=Ky7<e~ɓ'aaN:cWPaYżZ{ϲ_`f"ǖϻzreU_]ozPVs@ kWK 걟M:pnZ\#C}]M :.E걮h'3OD<5 [Nꡙ(夗4v4}u;|` @=6G=2IOwGOWصT*!ׇ;Ћ8Ã~hX;f}UC#/P)1qzzN3d0az3N",-i:Zc#C=<^%1L)4peU{wĵq E*ֵ޶{[k]{-mU(D,₸,"@İd%33$.$Q&=sB0KR59BTw/6\ZknX̦z*)BO!ov}jB\hu?'z;`u#n]h +E8#f|dM;3uɇV%\>Svv;O{G)g癟~C=#~Ϣ^Ee\1j̨5qRз ny3s"LZI/G*xA=laS2}zxLnH!9;gYiIR?f{ͨ2fԌ:ǭYMLAu6,bN #!B4mUW 7MI4y+TB!Qwz\Y)هXmy˭z y({||aWbXS=!Bª+53WPy(s V}&G=;8}dHsg:m_lt1W,©B!$\(zmzRyxg_TFpATun=xܬjq;zNW$({@c\zB!U+_3t(;;Tn+x;ʜJȿft~ivmG0댱aB>ts"AS mY&]2]{sҗWcU+5Ζ,uqїuQOZZtzw^.~}d#V>vbG7vrn.eqRZ=Sqr1P)+#8eM6q[nvtt?79$3'bXS=چ\_{2T=UT[A磞xxh5[֗|BJluQOϱO{Kw_'~}d#V>vbG77Z7{×kx_EU*)8uǪV֩T6Q>Xo%fIpy ;~(oBnluu_s꣤Nxi~kÞTis͘?AFi%9ʣNu2|q-DxRkƯQyRknɨCv?FO3TuC)rp8v6&$w?eyAzgGUvR:bQ`1b3sZ`L6UoIKw\~%f# )&b&fIpYTpgeKyޯf {S̿d-2gk 1>Q=up5ߐF2j>#|5^بm ritrmaգ(Cjp4n71a|Z+xG̖V4;Q|1I3m1acWA!bfYs"A6|r&?7LoO|sgǛZXHU{1̏fH'u\VWZ<5^ب'C}t\O6lb gK<\&ePaF-πYa1Yrlb\z9;3oU}2WJXeQ+ضvN%9;qf$9+>=F=iyT/'M9& _R𭇬G6QkYc-v6e_[=v/m,VZtkv}.|K`߈{fᎵ_co϶Yg4:ʱPWMWT7~W͡-+寘lו![=&`O¢zB".{go9u\0&ǮJKDӡa-KE3szfUIXʖoI˷l\VKNtſpVZڶA}Ù]WBʇH}qNW\/n4i S=^}CVatԠoV}gK $q`ZK !${㾥kNv]>K^/YRVW Ve)5mЭ?+7^ٞt_`gw[kVߤ<vDkԼK#_AHbVpEe4?*ݿice=gFI!P=P=!BՃB!P=!B]TTTzzz@@ʨ@hp9P=P=P=TTTzzzP=P=P=TTzzQTtkjjjn.P=P=P=TTzx{^*z+Z?Ί8 \8 ;8Ίp8Ί^}!B!D;ĈZ= A endstream endobj 408 0 obj << /Type /XObject /Subtype /Image /Width 1065 /Height 678 /BitsPerComponent 8 /ColorSpace /DeviceGray /Length 2673 /Filter /FlateDecode >> stream xaggJ(+{eB)QI"DVd")Qddt^]}Ϗk(p/$0 (%0 ^ 0 @S 0 FPA 0 @3 0 CFPQFa 0 #FPQFQFQF{T(QFQF{\(QFQFQF{R(N(^(u0 (<%`QF(`Q0 (t0 (t0 (z]$0 (]%0 ($0 ^w 0 @ 0 3FPQFQFY 0 @o QFQFQF'`QF/`QF `Q0 ( 0 ( 0 (z%0 (C$0 (/H`@%0 (C%0 ($0 p 0  0 (FJ`Q^0 (0 (0 (zc$0 (c%0 ($0 x 0  0 D 7I(0Y(QF{E(0E(0U(*`QFxM(0M(M0 (̐0 (̔0 (̒0 (zK`Qސ0 (̖0 (zs$0 (s%0 (oJ`@yF`@F`@FP- 0 F`@FP 0 F`@EFPoQF= 0  QF`QF`QF`QF[!`QF@(R(ꭒ0 (|(`QFX-`QH`Q>0 (|,`Q>0 (0 (|*`QI`Q>0 (0 (zK`Q6H`Q6J`@/$0 ($0 (%0 ([$0 ޗF`@F`@mFP+ 0 v 0  S(K([(}-`QF#`QF+`QI`Q0 (|+`Q0 (0 (|/`QF8 `QJ`QI`QK`@#F`@$0 (G%0 1 0 q 0 FPQFQFQF;-`QFI(QF;#`QF8+`QFE(ꝓ0 (*`QF8/`QFM(.`QF `QFC(]0 (\0 ()`Q0 (\0 (\0 (zK`QJ`QI`@F`@F`@$0 M 0 - 0 m F[A endstream endobj 421 0 obj << /Length 3141 /Filter /FlateDecode >> stream xڝYnF}# 95/X`dOyHЖZ7%|}N]"e:, շSf|x]}}x>n8Inv7efI~Sidlo~ ~MLg|Ed08.5QНƺk>60+?5Sۤ\?wt[%ksDbmgCs]qu07[O4QXhGZ:x2]IZVMHi˛E''Ksu}$:+# `Gimc;[m(gFZҶ͹' V)VZX|gari( ntQtЎX.)-p lon;,x-~ܿ63̠Vhi|Ii<}053{5}c͹ds2 #)[m˰II+ƶ3vgydn q$l]@JT6<?0;?ނX@SݩwwkzX(GWC.>\?ںVtRsTlLGd!iMz+QUB71IGxie?IQQ5H]VzKۍHez:rQo%ƽF^*d;ME,NM:i톌uOR-}YH Ϟ'YLz ᙞnFe#ۇ[(ȑσcy8!MT8Ξ%YPbAZPvlh޶I<:ݍz1<-J+fyQ9t|5`mn[K($hlNy۱h׸8O&'1S>_͟a\#ͬzHB᪓cM99|a]0#uSmuk)N0*rs(]d wTd5T;&X'^jGTfaf׏h~ݓI&%Ce/j #<ۺOzYH/~+~!XԒ+ tf.ۯչ[umYU5~24,Gf?:F3y'Xo$/QyGN2*tǣ$ȸZNS~5h5$baN? |L=vnwl4o88_h'{Û_xn̰0} }FDVo*Rn-k}ޞfUG(VZxL5T:@t54aGU@*x ?%O-7cMg*o]#"oVDky0 TEFFD@MJGDE&!,fTbIKXN##\qzi7J k{t5?%&; )I1N2F'2mtE`TUydFDLOp{#0d-bݙE~u}G nZL8˺A嗧pgԃzg 3?1Ql3`"o/7 tHCX pK E BuJ.qYX]53>rי!n9 8\ cί\BA.V170o{v Y!7e$)(<tȓ_"K8?6ʷij~~7?p}C]qTuLħ[}/¬L Py5(XVI\zyd "Px7,ŽrZrlۦ&o ;!Ê.p.%ξdUO,?cgט&oSf 8̡]@͘Gd1􄑆d+k%店Tp^5Z:i{S.0;"m 5<,d܊@x("*GeM(3xƆ't5K`^I"t[v?kYȵG2 &ǧiPJq{׽[&j^Ȍ/),=y9M6ov̔肧qd .p G$ihVTj5=ʄq1ӏQ4ڊ $Ŕ%l+fইz/DTN65'ՃyXdƅ3quZEP-I0 ZU*rfz; ?3L e_?8W~`gs*r%l%12u M У''0?Ah.ꭘՊ~"3F9 밀uJbY ,p8bs\x{HS޷ц̙QZl!JRR;hf$^}6bZ<".?*zPR䴅4Rx>e WƩ03$ev\z"ZcP+ݤ>;LIE:y>֭>"; q dS 㚠ٵbf/uYSoILs݌w{Y~*C˿NoU( >vP 9a\^e\zwJM??J&@d kG9io1ij$VO dp^ӦMݫhBժaZaĻjG> stream xڵYYoH~ϯ#69\{&;s&[w)Ra%}jr`a죺ZEpE Ww/^QR~" ,I.Ta#oA,GE \/M0vtK^2' o<ݢ.Ի3T Ok/Ǿ3Mf:[ Vz@iߜlCe5I?2ґ8خ15 mK`]ښǦ@^Db(JXec߳W`z{~!N=g_#-|I+_1).qK$ukJ(`rO*I8pԮOUi]oؠ$AFu{> YTvGvBI8C#HvV$ :x"AJ}h'O+`N-0vv.QYם;v?8Zm5N4v4[;v2upJ),; +"KJi;VҁdO!TPxWu.d"J ۵ Ni3 d?8ubDSLzצPצy%EE4g9VmcIsG& u~;wia"M:StCUYany"YUd|`Xͺ]tGxuI0@3NzH_jr B{}g Rk ru3%Op~/GqdXaxm'l_=K<nv/rE*˗x#/DgT휲$MY!DT%"RDyFt$2T~%tβsC6LCX,' )E3)*C ajnms57+oץs{Fήm'1&Zdww}87}\]򅱾] PϝH!-űw)?- 6@%ڢk?՞x_>ԉcͽ | qdRB`ij C,{3_jbT8  l70;rRmNf`@k?\\])`0[(pܱG9:rJ85ơ Sןx  i0䁆<3\A~|3l&A0-ر5HXiK6FN<$C@^6ǻ Հ[ˎ@y 2MTbt7R306'ž̅٘ؤi﫺&p]#n;@,8  WW'j9 h O{NE" FHQQ; Yq5E䩇wF5Ǟx@p%ug`aC62H~tT [;P"10N*UgtnA'ɬn+ PV$|?v@IbzL"g'Yw׷cDb@g)CStPnkaR DϪ*Qp@[+ʭb̊)}HXwQ:6^1vxa:Bw`p&) MeikBܷ͖KR\QT^(RV* Oc yiΦr`]vqHjf*BIJhkzā_7#r 3drև1UĐ3 7ʉCu%qUT*Pf==%.҄!ZyЏ컳~ۜT#O+c(ImK+5 1L<̞2.qx,-nwv0-c@(yPTʻ|%';%/0OnKAKFc"u@ 1-cp5`aR>C֧#z[;Edn# , yOPy2 A Tû ٷ'Jq 9C&`F`E #MwP.h'AA-$FL|ga~" g_AP^u)+g 8a^O bQ*'9qVvN XcOP̴KNJTbb?L=P1D!P`vI景r$PFܜbvZ#MH=ER $ |$(NR sz։d#` QQO܈A<H$?B":ϺG:c-͡)16ĔONi[ogS_nBq(q.:Vt3De qaz8ixs¬<Eem M~-%0ᚄ2 L)GޖLl 3!\ԥߘ0?ԁbdEIw/_[ endstream endobj 439 0 obj << /Length 2808 /Filter /FlateDecode >> stream xڝ˶۶qВʉh|/'nӜ4wt"Yg^H70`0Q9n߾~4qXe<6Ey)$T)@ͿaeYOk&Q0v4hvx.o fd^0c|m"76$ /q{}4cݥI ^F3#ݹМ:z *rbp0'eG8`+~VZX%k;ᷴCch&h5Ӊ/=; k>.}wK#$&YF1\vbԥXǥCEeXҸ cUnvqiΟi?@exA=/JzwyΡ7`p+oj`! َq#ƫM7PH#6S[tbƧ"85զA$Q2LU%jIܓ9Z()sïk=i^f"g{pf7LY57F"ipqg'Np"\B ܖ\ 19r ѤGp,~CvS.m-Ķ9`z[ u DҼzBό|f65~,bt.Y 8 .XͫQ s8P BJh帩A( ,l{+)U\3iOڌp2\xC͡9MȮ X|L==r2B ~ywwڴR >AH+ph#0clT{`,"ĵr,t2-8Y;`xGAOPM6܏ @t,{@^F+Qe߱zDzh=J/**9Y*#2c)Zp/sG'ud'4 ɗ oٴsγưl@/,*ϐiJ`1 Mr&hH-MV(8Q*U]aEkzvm[ssCj C965Vu\R1Qz2@s@07O)H*"o@ Rcd6 2X/)xx膳F|uURE…WI2+2qtA\]38M*ZmJysDefDR)?wYp %*8>fs*zR/BXx 5ea^YiSbU=m+@%fdmO^N[Am#`|VTc\e&D𮸖0ޢy;4`t#5(ة 7>ukAOR~A8^K%,T ((8kV]Ҕ2Eo8  8g*3c\]ćd#{p\ZF; =XCS |Gi>$kćbSObm<Fj|Ƶk1Ư?pՠ[N_/`Ug;UdҴRQ+.\ϊ'F0UibD[:5e_Kr|,q@֭gr{Q^F@_DY ,IHز>?`Eм.J8^5I&kq qcIj&9&—|\wgjҌ$;"&)N71Km]ϲZ`Ia۳ N77ÁJ;B'oЖ4fbƶnT0(fˎ `*iF)NP>>>ܔ+9,{Nz 깭\g:wDFݒG" ڐpZ(|r16Z%%_(Upu(dIR]EH<)lPǾF @zڟJna$[TMCRKc Ŵ \O  @܀HCwldAZ.BU< Ô4Źgq:ߋOtFWCԈ܌ W8g7D?u͍xc;.n8_Ͻ (Ԉ78]u_+\-`oӲƯlhJZvE+, X~n õpvsMPCdG̕<(,hgVX}_Qtj6(X({J}l@J,4.\*\8wM7@*pspڵ ^lT+$R5blW;g+YII-.cc`KK P/U MJF:*O endstream endobj 356 0 obj << /Type /ObjStm /N 100 /First 883 /Length 2469 /Filter /FlateDecode >> stream xZ]oH|ׯ݇ gzg8^du%AU˦mlɈdsX3S]ݤsQ\V%Q< *6brXš,S6M6"0 ?b\r&f\r"z o"raO+%..$;%zBqqg znFfp{ѳŐ.R0k8Myq6^ ʛ@4Qy`SXlC>a .R1c?eF2ƉF3ۤrx&s *\ , U D"? ӏBk__$ύa /1]x.ݝ.UHI"f;^KǫYדt9.Ɠot2ؗs[dA-'NcImBڦm6ʝlV$dkIeõ\7AXi47L'fc|7nï,:;y{8QcuL{9W:OGG5YUoTs>oI=1z\Povnb>hN2J\'stqyx8!QK5mXJjuܞm]mV~iCnA3p@޳xGp|Rl:փR) }*(D!HڒBl| jQ7t L@zEqbO8l+gpqrm\,ó!{"'R0xbܚa?\L&ɫUz_#03J$pv-P=tEݶ["+uʲK9m2jKVNkSFm%O?ᔿN)VĶmTjTnS;у$pSjYK;- ;ñy16k &{2 2Z~[ymBfk?}dѦ Ļ7YwIMrda1n>5\ۤ1Ici+-SK˩R6tWE8N3B]jz[w&$;m^frwi ,;ܶ˪c$gHַ6mj`ǪmK{][fsOӕYyN"5}m v<&ln4|3q=>WXBҹs2&i7uzbu@H[!-%,]܇Ez}/- t(YQ({>t/K.c3bmqe Fd7Drԭ,#d륣[.d2l1...*߽iD] endstream endobj 445 0 obj << /Length 3464 /Filter /FlateDecode >> stream xڕZKs6WqfKdnwVe]"! rί~$ǔT<@ 4~|P{܅BOtEAaAI+Tw'{8&iƓ|9z }7 l?tgbG隁Urlpq[r |PwLp6A%ˍ( t>ilߘȹ;8TvHJeQNx$iP~&ur{7p0Ǻ}Z&SADӛٳ`{hy7 Myc&{羽"|Xc]Sm_XA~43||j{óCן9k&b7DIP |MBUx{ ϧD=qtݳ2Sx[r,2#8t1GOcoItuw#b i Q5w ?!nIgg{xBUt#I *]U^Ѿ< r a/ t}%Jƚg.ӈMMM50|UmFKNOV hYS,.}^βk&M7n@w~GtY7m&lM m\-Ԭiwao9;u#[_8V:z\D{[tLHĊPLqW[-X&#J( nFNodO˱f3=)]"mQGɈKJz VU'~f%-Qث2b2TxP+ a k()TkH%ؤ7*gQ*h}@ vz?`U=/G I=<#jyi<9cRאkLb743F 80[!a7m[rL7WP직v:;FL0a覾&x  w=)bV?8U"(H?PP2E6\ *泫%m+t Ք1*5gzQT0b{W պDi y+썯~+]B_}7s"LX3NVߴO[=vr>@l'Ɉj\jSŕy0u ') Xdk}0GF_0nYQ x$MÜ/C$C`( ~jD>Ix> O䏹 ^VG(F({UF+~ȞLQd(8gCpS̞rJG 4_U\#Le;f: u@CDLF.l6Yj,n. nHũ6L%l1b%z1*}?Յ0ֹ!Gd. `Сfe !`-<ȲWC<e<`i.HuA䑃gya/u%QiWAtNP S)G KH.6㗸&5*,IRmO*O;skcK":жbCIPr `\2bl'$qz&fRгw_-]oܻ$)7(YW\Ul\믉А"q"1ІjTS3v XArb)cwlX_szY FRY sXU6/P- .7YJ#-J{8OnK\%$`)Q=R/dxH= 11Q~2B1tjL}y.넋<R*Nj*0O-9k2abq.[2DŖ n1 p9D!-nYwߦvX.1yȇA!G>RmN n;71$si\ZwT endstream endobj 453 0 obj << /Length 2672 /Filter /FlateDecode >> stream xڝYɒ8+t&,7OGWOa90EjT=X@";ݯ><ݪ,+UH{uꗢ:szumuzؓ[am5[@L-0ɜ-r]4}b# '^x3>5m!{yܪTu_qjZaǞM޲ک~-ye fx5 F{N]x 0B~U;w.1-L&B0%]M׫G/ :]7y:#ur7Hbng|V-QwevRsݦ{𬾩V9ȧjgCν*<xST7}~)$.ؾp#Qm[4sC?y[=1 BK=\8ێGoBŨn7#, Ov:sQ#زrvvOjzv2Xّd Sɔ.,k 9iMre鶠p판B٤T f(T;g΅t^)\ |l=pq:HNБ8\å|}2yu>+\?I?QkZ(f AXvlW`HvA{BE<_ݻ^_T [@-P2aw0IT7,:Yļ m!T=Sck!`1IaĘ0̸gavifSp02sNN{ϊ8ʘnVeDpZ%O/ @0|h^wܺnwNо [AXpJcaASw3>hNKi+eF6z.{mgutqG{-@IȮp&g-0? !Pϗp π,.rS=\niXt0(u;;L rԴɠiCaEIYyhs$m 8aO!6ʕt*Hy ZGXݥB8fzpS˶Et9s0"5wq=hX& D }$<N10G21K,;umaT{ES+c ^Ym -lu+{YY4$\!(?aJ44fI*V6dzgl%QYną=KlYU7UkMI^$2\tC,h"ÉMuL^\èMffYQqw/(M'IUTt:U``j*ecߏ5Z j*,V1@\ʝb7 f^)qIdɱ6I$ AҽT^´.OL O*[Nr1j\*:qsQ3$oS@XE J$e# l!s{ѕmLא ]!q9xN@bΗ ~-$8| 8qQEAA^)/)!z5ֳ~!H W f@<} DBmlΞްK~( uַ.d!gtBtD;Hs tȳ?{Vf8W&|h ;4dnv_^w:d;YuӅ(eg‰R kM8+@9|4a*p k)%ǝ 8rȫ=3R:RrFV,B뗞[K[tOxOFoƼ0O=6 l~`b݌.:nZi7ȃ'C^-9> u=$> stream xڵ۲۶=_qE xĝtO|}HK E$u. E8v'3s,](;%w.Y|}ߥi\繺?Uy]YfUwluD8vF6Seƌv"rft]ˀ'yi[0`Wa-KGz19N8grU:I(Vn{=w0=NW :8|?H(U`tڈ ﶩەqV|cI? e*rIZ!*"idŃoҤ5{sd[n٧ QG&'`1yd'(G8=^-i]vl=~Y 4ݵ7xV.|^k|4؛QvF~OޥFk7ڷ$inNURObfÝ7i5Ȋ eq[h|nwK T3ED&Jl7\M~~%6bv#YP^w8pxg| ukmTR1|n Fu zl#+`x2~[Q}meZ҉t o*Aɜ/ C W*!u u<6L0p\vSu4``k~&; h.M -|O"Ӟ&}fioO,AҞS2];s ttL[OD+y̧$!ehDQFo[m=$@H a$tn?}3 [ƖAF\.L%lvn+^mQdu萌bQWKV6)LLa[)@IKǓ4x`\ip$c'tIDu3M,bu H{!@UL=N 鴚6,A}{!G\J* KBv*Ub͈@̓TZej[xkkY3k]4}Q(.g,(sdet _Xq_$|2(-g=  Mvup=3sgHm(׾]!1vEwH!ƗKɲ&L\|2,|{uBP[ըz8j"uh2HU بDÈ.'0B,00`IUE11qe3NGm0K*+ 6h(rgvI~y։>s1nlN ?G2 *Jћhpj؟(T!hP\wֽ-Ĥǔt) 60~DALt4:g`0PM*.!l]f,ЬGK.B&ڟZjxk +c]8>OWsì'2v/>$#븼!oAƭ}(*lv]_YFkmX>ƍIzW ȹlEpY+c.-ҌBXeB*z T*TL_L锷Wok_)Y\Nn7pocb r)cwnǏҀXne Dt :P.Iu,ZEC>bXɵ$fT;{7bЧ,hh-\-03c0qaObBIN2~]sQ^6spn|lЁ5M^|!7dtZ\B8"5 _cO ",uYUq5=E4hj?+vח%.l ?>Q hdU&Rahq.Ţ?}6g+4 W5M9tY: e4WnF-5늧jug endstream endobj 487 0 obj << /Length 2648 /Filter /FlateDecode >> stream xYKsܸWr$ T^q*ڬ}f0%9_nt0mˉR4Aя?`.J/~~{sqy%u$(NMTB42F4:znaTaW]o=0UK2Mf*⇁z݆[;l2v0qӸhAGL| ke>tPߢ%$9<3e9mlmGXfRG9Nkt$=~\^eVRQLgP!SizxKOmoSnܤE W-m=N,_$+zϮ&-IdntխC{y܂;:jap͒Fd95m]9S*ɡ|#U֠XjG~pIxڕB%e/_YPa;;"W^}ۚ+v} д*~{ ٞ(V=~Vo*NM^: FȂ$][`54d&0᾿@]!?kYa{/A~k~Gg8ӣ\q\N 4dCay4@%&088-(&#RZ,5D=LB?. /읣%>޳`-`ϼ$% xV ~{<#M gǙB 7E[z=|Gb jlo8q'mыM۾ZQwLzZVأFt!z¢s1K .߲qw5tF=%S*29޺eSi,^G߹D$zB0 r(<‚3z3fJlE L30"$_ZE0Pa9+tp4&:Q(׆/'o8&4Np ǭkx>wmeqX 4g.8>alM-Dv @ˬ e @f4(3!aP}gKDfS#Քa i{Z#1ޡ͵[|rW>L4E_- { } 7ol''mr:et g1IL&=.sϸJ<,qxEqi‹qx{i~ ;g¨0dr)GȾGVIsܐ9 'aElz`c v@xhg!P}tFl787&HCDTYw T)5@{XU^lt@J?ɟa9蔈7zׄTidC&< 90|IS3vHaS/xt`]M}v?V$๔q-h@>e53D\Z,ގD,UnҚ &p5b5˞^+UAe]Aޯ& ܝ~hХ F3'U>Tny*3LN'6EG&$/J/ǥ;*<6ӯ|X)_vXQ*`W%6-I{1^mi?,TZq׫\o}P*`&!ībL?ⵑI|Q86> stream xڭYY۸~_K &^'gnʉql?P"fTr_n4@ZjN8}|}(^ݭطzq͓H]e1te 3cYo*Lo&IuIPE}vY VwԫFW4<,ٚƷC[, fc4ܗ8xXO['nzwq<қ_R)=^ 5/S~oPH4M]nt<ѯ^0YJLH:)p}ZvJYKO2Ey:7iy4ȟ8SGiv&l6kICX6N`6P='rqHAF Qp Q zAjڬS)tuar1j|*Dm b8|4ex&P&^f)FJ)Ϻ>/OsP;cOi2 |:i{0͠/ǥmBjYб_e~}[=tM6i;]5D]sofl!# >eRPBoso谨#|Te$OÒצy.R>^=`/o}KNA֨;1$OΤtYF- >eQ@Eu qš_%|,X?D;wH+1u$'p=ysL-餧 ↀLo6ĵ%TvO&صAAgs CMMCqvΜyABmAK Sp~GkۗX.r{ yE .?%֠;f)D[:ui_\ƒ9=#AL|}ZoG6zCDVbO;9*@[lﺹ/œRzRYRS {}p5g>N+ ZA嫟<ŗ܃E2/SAs9sZ,1vqRhRF^ BpԨ@/% P2wdbT*W.r*dzEd "Yu@a U endstream endobj 522 0 obj << /Length 3118 /Filter /FlateDecode >> stream xڽZo8_aVƌHIý\z,[[ʒW|QI>$&!9oFw —߿\\]hJ,nW8 adxeUhbo_/WKov+(ZvKγێ;%߃Pֲ4L>|槫03e%^>n|*m#GJG\Ł{}?p΄[:1XZ+0>V*|/W{[n?v&PIvD:tM~ a2^xaxYˬaZ8UUTw$ދwy *H Р.ο__~&Vs#M9Qtk䏒ҢfNI' 4@a0Uඨ2VRyW_ǶBcS47LTQ;q ?Qz?,w5rHƋwӳ;nc!*1;dkpc+m]:|7]F ml  O{umÉ*D3?f)5ĀsHy w\ endstream endobj 530 0 obj << /Length 2403 /Filter /FlateDecode >> stream xڽ[]o8}ϯ/i:V-i>6l㷴LDeɕx~/EʶZElQ}彇* V >ݠ7>QA0I <}EBӾ G=os:0c<4 Hĉ3qHcz `#lxBƄMÄs̗ABSEtwSnɃhn}A}|obYfi7 .TLMC̸b Ed c$*)[,- U$ෛr!suE֨ }|'ӅZ.waz+3%YEX.fREJ_Yȕ1r- Y4Ho ]( !"!|ELtr T\mZiFVхP@@faiϣf_4kyħUYXX[Ku\uVnNr 7Oru^ЛC!B8BBQ㰐raY!M뿖xL:6׻B5zW ,_Q{2 $rFI4\=7ْ.L5Һ홚Z5 i&)gO>LlG*Ý/_ћF ʀqhd`bޣE?g]E!-e˼LCbܖV6ϲ m=^zy~JHp]4NJ$ 9e,W,9,teں.OP.d+28\*qUbkO@@q48O*FR۴d\.hR'3sy wG [D<?DOi3P'P:m/LC1vژ As!;@ԅ,RN~6 #JI1! XD{-@9q\NVˬ,2o]$O4N4ʹd,d>͑K3&tTBOų*՛TYoqB6EI@8vf7]xppqQp@܊}3}9*;uȓ2DZI+uh|9q2>%礇Wiv\L6N,d1/o;К?ˏS0yb D8ɍ 0S\N . i#)х@Jj77B!k |h|-r1A#1&f?px[A8aHܪn܄d5 @: 4|VTF qE8aƐ ^HC@! a* b~yb~hJC6B7N;գ+=o:6o\l`fuuresu 6Ń!1vUm1:3dPK$BJ `},Ug߽׶] nTk6dդʪ-(Lʪyis5EFGHu|]GHq?UF@Z|7)g,jYm{aPVq+N/Wr7ǩIg%1:RĐYæsm%O[]Mlyyf9&R1.ӝLUzgKL>wBz7n]XV]٬xE&-['kTs.eٽ9pYd $u1:3((1N#HWl^da#o>۷_D\)${12H& ՌYW>8ewV#mY#-))|y&ǻ\xq@ 0Ffa| z.8\THe[.+ۮ3lA78;<aRd-@u%a*vJ^Pp]b{dJ 9HsջTE'!rUN63qr_կ'UuL>M3` #^&e|ƹ07Jf\1Fg:R(§^<8M@[%|w|-ʌ'^s'J.O7I>淏`Ćz{NaՎ fZjPT - aV7e{FJnۗa W̼§bL5 iE;nE`}qh|W{i&A6 ~+vO1 M'fRj̈́/ypxXߛ VJӐui)]VЏ[EViIӨOH2{*( p:&V+(Xֹ,})UpVGܨ7#Oq/ o~ib. ג~X\aXQ endstream endobj 537 0 obj << /Length 3027 /Filter /FlateDecode >> stream xڭZKܶW*ʄOU mrRJb+jaCj?~$˵@uލz׏bOFBG^,I4BE )_}(87`wNRocw]8䥄 &@)!u?IP}ة7.S o-fQw`Rε95 v]{Spc{h6gLC`{plz+8(JxWje5p J*!UߦAGD۪3Y<Ddwel. td);6%XX@߭}MDe1WEH.3}O+c:s%tRZKg-AC[-:Xuﳋ:aeSai7)DsT zv1(-J-H/`D%.~ɀ$sݟn,0. (F\ױyć~Zt%~isA _TсBDCX@4~\`!:-z>*0U9Ð"soO[0Q¡eY_\QAz "?`o:;qG7f<Z6T=έz۽GxY=An.2YpB]pp%EN=-- /HW=BSD jJ!o ! YU{35& `|1"kDC+&w~'G J8bVNx83%"ӹesESr %@l\?ۦo`,11Gn3틏{s'x/m?^붿{߃cJ3JQm0,p#*}aCG 029rҜ=ȉŒQ٥BOf̅T>A4@d-g7L/}gTXZ5 e6H܍|uPZUx>e]b#;VR 9hIf`"zxOth%y)2x@uʲzy izT~ˈv̮ `}[wI= I.fh[D ǎ2u +ʹ|M0wQڡy̾iOD^XsvLimM6uq(pB0ajOh~}Ȣ ]2ʧ}{؜Sa6ϛ*,znjZMm4g\l4+'M&e:gVL3כ φ<\)-k;h,4xU I)- >&G,ĘՂ;u" `/^yȿՋ"0䊳E>wA٭yZ'B+ܸy"B뢭әms?r‹e<JH:@yHa~?`aNέ9a >,%aC:񑶜*pGC;b^EVIɁ-|LZ,nId:@Me}_\߸i}X´-!FEdͷFF :h_!~WǢi]6E0Jv3s^!p!F6fnWmMO10 gBFHbЙ/q}wgWOlqpl$XLa(n "/djPax(%C Z F/D}R=p(2J8׼pv؂۾qns# cb˪?籨d:AhOtg:DB&٥wgW! ,ҹ1 " #:_Rӥ~g|yBOފÙ+z1"e/@3f試/E $)\RNpI=l G/WlW[Ⱦ(D[~wYEG,_d*"gI_/u@h5}OQҕ8)tC+ 7ǗG"cP̰?!CpI⣔@~קWЁ>u0}pɃK bGr00]w,} 7^i}9s\E<7Rǀ//WTKs\-ǎ()y> stream xZ[o~ׯc%g8C(4iS HRFP8u$#7U+"6~ɥfǹqSJ.l-9ҊV] et]ajv%;aJhed2,IeRJસbr#.bNCO,!DhD;FJ.֜aG`NrDxJ*RUG,iJG'c5C̎CCcHф[R6{E@梎3'bFSƈF.dW,7`$(fq *n{1 <^Lex2B%9a[)]YD!x{R::FQ mh595Y"J@"tI3l ))N9g3$yɔ1 6N^5񖂫F2`d L!  feS@?4/Z.B_P&\D M@'Lbh6 \JĤl @SiP'ZhgSQѬ{u/Ng7z~^ւcrֽ|5a/g$`@xn}of φE'áp}3 YzxY_2_37ݷw7'qx07lQ&oKi(Ȟ#׽r׽랻VWoWxw_9&%rD7v6Ch74F^>m9BCF>;VG4#mS}jq2s!Kp M@3[I[iMꝲ|k߯1넙&o [lUG )`8<+D=YO/៬H4CeJ X)Ƀ.)UTqH CkIɶd\Ͱ{:Lݽ~x}~O}8]_[/uOivF*Q&ZAs)ͅhP#tge3c#پ<&hYW-UˁYfqVݴ<_)OdAj%onVYR)m_;5F FRkሀ{Iq MTjBV!'Qe"L%YZۍ.6 6ZVamULs_+F"F ئ2"ZiEBWSmjmC舀PG{]A|O1gcT79ݺ -ma>"0O$\̧\|<]ʔ"_@͛'uj9ghH 87{3 V}M<|f=[b|k挅o5 H-CǏ~}eXCw=7oOW+~ɃW`^JZHB`&ݧ~qS&Vc֌N%}\%^B%v}Qd)o!R`<\}*k_p GRLu,؇OȑʳrM(U 8,g HckA'5p?t։훞tҦQF 8\ ڷwvdF1ahC\Gfp_D7 endstream endobj 559 0 obj << /Length 3438 /Filter /FlateDecode >> stream xZK`n* |HlW%qM\@6k@`*dWb==氉6_Dyyg2alRo(teq ԛ84$?۷8͂#eZ4wqr;UX$ 6[CfL`s94onu6ۭ6qpc1ZV_g^PNsdHt_n_ЗSs>@pz[x0Z9!i0 ‹4&e}fKy$:,-4YEpn@)E`kظ*,]O"RFmID,N\tOѦo6Q--=m0-p[sI&xOq1gS'&Lai`n3ͳ",g<+, +9ɱyњ.2*>/w M% ]S+8BWjPXXPPbfWc҂\"p$N@@çؖ=BNȥl !|7d؟?/;)AFkGMxb#?\0diң|o_t!:rZThq^t1BS)M3%)cddt ߒ?;\ߏ-O5<`sX?0tAHo/1'I'[gM/}sK+kPb%y`K~C $%"!@RB_V ҜەxV6:K%2y̌{%kB~ q2&nmO Nh }h+ṉA{,#.`^9K}ąk` 'PE9"މ$6lra:9בV&s03"  N{%H5>! p(K CK9`m; z5gAc%V}-$3ay? k m\(؊% ^e~‹ %6j[1sP+ s%wky.I6 Ll(E!rttܦqq0.l1LX?ܞ*t݃Q=)U2XAhCO.(JD+G'yZy% %B=IL9f2PZ b0km3ƿnyx6P&*$¸xq_p@vQy'楳kTI_@t(JT6c3 a;ו;r5LcFWEs*0#QePSКrrGY<9@|4PX6 0>a֜0QF 2Ϗr;@^`0$|2) Rz!1ٷD0,-,3`'ύ]*2bDjw[.s8m-XȿFЂO>5Oj _%1EVĥBs'{Nޓ/Yl&rW6jЇc;,V\_+X0 $ .n7K)`yA`k\UM7'ӥ)R|*!ӯ\ڄ]CQ 6̷N.z0BBϰ&a0K`u#'88s\BY6>Q*( ckLEb)iZUIhlFZ:x),XJVb[( rϊs]D(iՀ>c~ ~:LgI$K$ uE:"T]x?L4# vCc<]JL)n0/\K֫/ A/T.GPjK4)b9r8008> Hs|)n=uHl(|Vhf-pfį6UdN+ y٪G. 5/fyT)"ԙA-|dQc?x)2YTcPQaTXT+)ġRI2_;8I`ypfWؒ&q%?u{!Lx!/N~m|.9) 4%ГG? -@Rp?iW-a (GpVIT~`C4$QLrT\ q|O4 W]x̆mjiPbQL͂&̘2ϧʐOŮ<`G%&)xnnonx"cT;Aw\s>BL4cZK+|?xn{ދЬߦ|J[d` 'YYt@;,Te˽-(\o'f* c(ώۈA! ]LOӟOk<;f Z`[yR| gQI^,7|?X0f 7Ϥ"_d endstream endobj 588 0 obj << /Length 4074 /Filter /FlateDecode >> stream x\[۶~Уvj!_C!ƞtiw$)κsͱ4A\?.\O?4|ɟ^=9SƈSO^]M %V1p -ɏӯ^mU/~ź&?S~[KVޔo., ߰)8PfSYz^>QQuZonU/nL _/.4ydzYwˢ Mu}MTџ~o~v[4j,=E&3&1…$RH)0蒜N  W6ulTblTr\bǻp}x E 4˅D,_X8L]M:"oMZM3:& bjw $8CvrSD2svq0J> ݸ@hBs9ⴘ̒nQmL` ̴ɍs:^9cYQԶ}n~kuؚ5~nVe5ek4U%sN6=MS.j .\nEYͪXVм^(Ca [0n% Ar9Q^-)t27&Ɓ VKrFSKᅛZv27!֩憑ss_D©;Ѭdd} `l{!kZ hvʶcKSyի'0*ل9Ngm͘,7Ijb _N^>KpcPQ9aģL@ RЉ Xh55q񠾁:Wh04&X4Y;xA}AdNAV{UP0ƃ ?*(p.4kR.lY 8KNR AՕHD\Cbc喊%VTNf DYw)f;njcض\I-c8 F jMnC~bƌV@ژdxV΄{T+CU':$ƺg\ڼj0M>.!ʇvBA]V"BU zC%LF|؋^uLo qxuS 9πfp q:4.0/kTn}چDP s=vq员weS?K7}{6B̂I<Èv^D N5cb|¦A@r-\(v`;9W*5R*·N}^=_픓MA? N_]X6カγ89(lپmMLM?V),Hm5/˻t~D#. `Dzbr/{ѩAzהfN6N ؠ@XLkU Pr]62[dgBHE5U@cMY7V7̋22o+JDXlOX8q p\wH/hT z(8ʍ)7DlXyS] Ab#B0Wz~$;IQO'rh1qu)ɦk_As 2"<ꦾCӼ^ϑ`br"])YlnHH(šELσV"r3pIE}"D(͂Y˚%7z5͈@\ iΟI90bB} 8&CzS=evax~ 7AVdgX;JaP9,;Jsw`+Fx:NN"{K "_֠k=םLtܯBjMŨ ` O;q^,`0)(?I[WaN&A ;bدPM 7[PyoX,@9s(">ltL4ڥaSu۫j?mdPF&!\,.%ĐLIN.حcPsBxWC [aP`-CF#I?>^eϐAc * Oy(<,퀁MDH;%@pq 9c>K"@3&4RXw_*-9mG /v uVkǵ9Mpl=vWoN"wP+R#f<`6pB7Dwԧ<[ ?i1bgN!fHY;3}#7" l6ܢ9I=f:̖e@f`ETgp)n$9Eoui.X:Tqxޔma|fK7M.cj+4;`HiGd;0jf[ RtA!o\ r)CmZL+ۢa<epQUsr\GZU?➙Z}J2v싿.}t{=mt,14J s K3{LvH4L t< 4Ck-0ض2j}/hF|T1w[i#̎P:aHRD %'A-CC[1VR̷q֟3H+l[ڭKCq,9P6ļ!ŀfƟrO7yH1)OI^0~Ê u~=ZFΟ1CԣqS,i":@@m|vPO#dv#ŞR^`k\frͪ R$LJ]_|^7|4T֗blC<:)3pJ` נC\ [7 zvd0$*VGc/$cq8^?Rq7fW1PD3TW݃ CfQCf+He Xj2,Vv1~5\v.>KKz3Av>@c^ 'qf9IAŌ3}1Y9>6儁:rH8* GYAc>M-[diH'ϱ2Rm!RSZ:h $S1IB3/ "u#Yv/s택gl0#1x= qg3\f!;-9 >qCPl@fP!s,N"UT2(nu1H':)O鱹d33t<*TfigD1#dz_x^p! xnaq c:rF!Rrc~TVܧ, DO*invpR:`xвKgs>qWS+7u2,87j!P>Zt8"Kk n2c ɻ B$=ĕ*b>fSNN!`m?˿AaFC?`f35VwܣV> A0q0PlD@0ז"ӺFS 2_c)%]GKbif@fV??&=CE^ŒDK-w54y2b;,jv(z(IWPASft }@'<>I<ų3Cv [6tfHXt{F8kΏLH#N!`sTK?{\'~yH[L>s zr*K*|m`b\7*o΂Π3ȫkqް endstream endobj 607 0 obj << /Length 4567 /Filter /FlateDecode >> stream x\YF~ׯG6F؁Xkx- *v75Ur[o߈dUzd2Gd\_DvEVo^w/*WVJzw$7J9в]}[eU/ÿ\ jp7/V?9Sٟ?:ԗ֙<ӭ_kϿ Bgrmҏ2Wkfr'307"Ij8ЉH*NFWkM*5 ,X291ƿ߽Md+-t^m/~/@uݯ(p[}ʥ~ژhsBŊ؊SCY>Og*Ns%Κh0UTn5NȀjMsmMMsja3MkEι]`Fz}r|Nfd.-(3n&H-hr؄YrR© V),#Tn <A~wJ γд?qWLq]7X.۴:gf8ޔĕ)=ܕysj[6hR@߅ŵharSoy;F(/Z RiNOX>ݡ-(7E[WkIm+thC-vmS7xt9}o-r ף}xcVkh"_rѷ+ah8U7m~pl'YAZ>z؉@\MK-nMu5.[Qp@X7eFS%ş(_ zqYZV8(ߖ-Ͽ+.)|\Cb02%"" & =H2)n>c#վ{.cfMql۪Mu( uŢi] 5C|.] ?VKהĎ)ǧUv ,XwʮY-#(z.x}E>M& V{tc{NI3Lr%J,_=[E\ nenfe𻤹:٥HS7XS±x&⣙U{ Yk>IW#@DXL״fQhe0/b4Ή]١!|\+wt@Ζ;R6x@<w,}J¹jd J<' 21ADtpp]_%F`I ' w6Nn8ǂs.^z5ve);ߴ),5WyYǁDԥ*7ȑA72CmS|&&،v!r[ xWi0"bz?-I)P|${c̛ aR2j9 ]6UӪqRT&CQDyg8 _OdEL`Lb=HZ)02%E8 G }$5^$(@h"w^qܓj_&]zj`nfIQN~g˔d䔳3"NxFCܦcs#ъ:t@Q1Qes ٕ֙W |@?s\oRb&Qbx@eݧM94XA1+ƣ ǁI`kP:`{9`S6$Od0 ízb' S=k|)`);͗t"FEn@..\i4 <-$'-p~Fo  \L\nK5e.3s ȔtcOIn w? <eL,=1dE2,2a\ d tOo_kqt!ކsif"xe_0atT)U41}"f7MouST@"a"@>lvT@g m} _Cxrӣ&)1mZSJ<{aS18 t ]u :,.rv)6@.9B3#yX<&5D5M75Gjz`W=+ΘOOá)@ oX! o\/~s#ծX`r m{d1g۪vCW5k `ݎ?" ( /Ɖ~&xĐs2l@F-3\]L#O&r :|f \8$ƒ4W0GKv;я4w}[{a9p M5 X]3\>>wg]0˻ ޑ؁N0 %Jh_ Wea|} {|ʽ]jG={Wt6|)9h#:4XfEʾa_7: >f{Pdk$* )í/9G`#fWM_~ ;W&^+P0C8tg!N|! :<.yW0hpW8Ssݻ9V}Vfz^Ǜ*Tp!TZpEf Ue@U=n!Z!B.)*R}B>|W6=$ e'R"J(ﺸ̙93sY{K/_aeo_p(}t c ;"f^ry.6[/Ot{J[UJiFXğS)'rRXXJg{?.S}ާ1_ãX ;+4E?jV ysc|>3 |sgt^3/yk\O~C`2"Cz\ׅR:n1>4鐸"HiEy ̿.bӿ.2t[u"),4pI>;B8o2(,7-@do 0e endstream endobj 633 0 obj << /Length 4078 /Filter /FlateDecode >> stream x[Yܸ~ׯGvXE =X5?x+mFh]lU~ giPP#/I_X߿zyk-VV1+ke*ZOߚCyHŁ8BӶqy쪦 OmT_A?6Ky1#03e-!ʬRTOal3\yfVJUY77&?TRn_-IW՛rɽOܗȒi߮هO OCٕZ0*#*-䩨ZEZ ٖO iocefZQ#khҷt6mӴm+Tެwmj쏺t*>0cX9kLʌJyFrHḇ=ӱetl=_n4FzF}FC;)]lBTj~DpDmT iSz<T1j'4 *-?þMDw7_D6yzՒq_WOnܟ?\7 zzbTj25b{ʰ? y8zxBpoeݕ{}+@.-v.$\[:B.?λ|,<-d/O-@ uvClPx7a?,WuWzS9y~ ;0_Tz앲U~~ w֣M6G15pq::$6yǨDM.Q1GJ1X5At\vbSk  sd`Lu8nfTT`aSH5sa3g.Mx| `,,ZTs1_e4SF8bEݱ<%[0VAJ~P YTg!Ҝc#1r(0= l2Һ8UμcTRh2?S+!(Cn;W+_ #*e`fOlE',\F?{Z{FJ[}x%aAOKAt0uf VjRc,&5۔d=DfpfZ.jl^8挕2Ke&aSbq)g'Bifȏer8 )1?Gb8EǬ57?ä .ӽ  $ExC@"V<@H NaN'Ƽc8 yt*=SDp)K1%diOQBLe=iG#.0%`(2^ ^s9ZD q%[g/-) HU2R'1eI,?+= CXI-Bg}3uyL^ŲKf0ˊ)Ei5וHEf‡WeE|4OIx+6z! >Mp,}֚%ɓrؤnƷӝ<$0ݷmux-1_=)7n%8_w^7rmL j4{i%1u95%qƧ1OLGk2tr aѻpSp3xCglI%E`4'G;y{#2L:rxUzJE0t/uSu1Vlimj퀡{@5E˶ܴe9Ԝga</$`s8 ^6{ٱ;!Jclzy[m[G{ XrYkrb __њk}Rl?!Q %BRȁFil/^곡dS|˧ǿPJEip:˕ĸ+*ΕJx9OiRee!:E];@v-wKW[оbEN?݅P f!^e@8 b;iy"C/}n%f:i3&W\kiT_OJjRv4}̅[T,P wds 1f% Q R[Lr3Y~mh\"`U^,b t~eH3L3;?8E8uG,R䥔1u~;ð8DwWȓTcEz3>2Pz^$7s~q{b3p6E2\x6ێK'˾$F,)_FeS﷐XTqQc<#TN@w=[ԀQ<Qn 7_Rߦm(^ =rJ L=^ ]qǩ (XV=-;42eU̯gώHMXϲ@LD/yEJ`AwƄ2޲Y˂7z]n 6b *Wj*T1CHƊ 6]fgK ?kzb-QcS>7e8gF$=Q[scO22g!"̰z 3zb_ 3C|3Х)b}EnN)7HDN]5y@'0?B U/w}HQ33bN=Ę]2}OmUo]W5)-U)џW ]yY葸P.Œ2bBg7n{pLqPH!2DT O>'`rte endstream endobj 662 0 obj << /Length 4001 /Filter /FlateDecode >> stream x\ݏ/AH"MR}ir $)uHr Iɒe%yYK5 ͌.6  _~ŷL-#V)xf)1iZ.e1y҈MUﲲhvh'M+ʬm^qˬ-˻<ߡiCrUbUo&bfO`ǒ [{7d~K&碭U[ &?b%rX<{ }ŗ M"ş )M(,R0"};rjʰ%\V^?QjaQВ: 챓bS:&F iXu&Fnb9*6/&t$р0Ħ#t?qKT`M0^@ y Â-87j;݋5<F6na*7pY.~x/o'%)aL/4.fCt2kYz%*'4֜gOf1׫ɂSRƂj^EKh`g"vζ"VNx;VD^"Xp@dEEϴ"G{Ϸ۞ˌ\#3+qoɯ8]-AFuBY,%MIp 9/ZR$jN,:RpLW4 zpzuڼ xCSL;r#ljb_7^.b`GTUa }&AZBH7> AkƇnyWY%}UgM>&nCCD|'fPhd.&&Vb&/kd?b_헁?ҨvU`s[665KT;'Ȭ@\}STHdm ۮwLhHd 48wbנPUvhBk8V(m]`X ^4pPwhXd=^aM:Uߐs ^:) w2oK|h" LK>emگQi1 2[; uNY-(G\|7C26^JS׍>6k't92/XRM  ikЏ`ºm@u :n3Of]<%/| SH:hNJUTs^T<߂ +Hͯ}/0s.;M}=ǭ?|Q=Ϧp`'l.8l;tĒs (@xGS =W}䊶1:%~a&B0t!TT5FImĐx jØ8Ӌ%l7N @=%`Ă=c%[NRx)5ezbCDsQ:@T e-1,ZHZ!"vW, E5.j'j0ZW 4Yv ]^d f<8 (b<#}BQ"}ȹ\zئinAh|YLGq1W"}O5Ղ9i$Yp!c.J­`Sdqw]:o|ϢuC5vvr d#m `&c/Ov7"8<r*2RsGC#<ɀ8ԬXk"Թ($qw&JUw9_ ˞I݁^;=R(9S/Kp+xc$qhi"x 8wRjw>_u=#ʜdRTv\ܗy^ۺ:lɹi}8k$ qWu D^CwiĄ$a$8vVe$NTpyGwp:޳j.ϚwmiF/,Qg \B-I(JJs0> ,. px훎Vڽs ByEuh1l} I$S ^'I9xEIR>jO*h͕:Ok&U~:(f$v?&,J_bC14?ߠ)O^ ߔ4;DP䧪qȁw& P5 /,lWy0K3!> z?2pDB9E\TJСFhq.~;\cݳQMpCu? ~s1HGMdى$x^KEǛ:ݷٶYv>((2 hU^H ߡsZ/1 o`o˛Pij9O O r8Oe G62~qtpmqH(Y޴Bf-bD)\=Z<:"Au3s9'B%a$"*;EےTyӷ `%4.x7$W !QURktYcdlq~iW k"rX01(aBgO9=JN;9(t( ܜ/sէe:='E'CT#ұtc|).;yyr89h61 8>-fx'RuLoӓYtan)ӢhfƮWG&L-\m}d&i3/^!LO) -RVZRǦ sZXKu@g8nN3;Wq_%,l̙(\޸kth?;+6Y6E !bg_X t/wHwSt?b(oh_&EW endstream endobj 541 0 obj << /Type /ObjStm /N 100 /First 893 /Length 2503 /Filter /FlateDecode >> stream xZMoϯ1pXEXֻQlXaH{yFyŦY3=YJ9 !ŪW$) rv)Su9M$)Tr .qOʨT^]PV2AX;d*N*"WkGj㲎u-Mj5%J[I: !rCT !$& 4*& LfO"dZc2 _jV)pn\]1.L(^+(Y0epvaA0V y!hS /*P`Z#3Um(j_^C1Ӌ^m*jlx ^1UcpPS+.fρL9fЁab0X`!=16^|j޽7޸_7WCuo}PV!kIr2 =٬oܫWny˰1>@"f1 S`3,| l˟6go-- ?߸{s9aC7X9 bf^ cW?כ{Î& M`}Qz7iEeeR{9*,w]>Lscc{F_,=ibzsu>\?,h ʢ}Ś7 o| oluomL7gz}ߚg!{[XzI,?~<Eg3\=(2^HiXD9S_ZgDSg awh*{i4o>{uq1 +qCh:gF! \QC-#t'![I|-rMV׀g\Q1dH7Z 䡵X^NEfĒ%9P~X} H}Ґa6"Q$X}jiF4zE\z)4_K83lԞtA.4z4Fs~9R{R mNGdA}9FQ1<,PX.VAaR08:'!r ?,gkInd;Nu.w;+weqyv4DdY3`+!=$ݞK|8x-L%ebTpqH8><ố#k5 ^D!؇I}n# ob۾8KISs 1o-Kv_COVcrM4,pˆ 68E l CX @0oTv[A{򆆄0ٮ 74_D% DՇH-T&$ŧ0{=;w72oiNRW9B!vmocYz졙{h{j14=.sp;,)4b#/ی@fn"Pv}sMkGpHVbbC p3?to'vR cyi UQ5[ lyXi P& aK!r,( :.>Җ8݃Q|\yH-o'PVXLs'c{)4͸`ۖ9`ˈ*b{l4&{ĶӲ8~8' G1݌@0ە) oV5xV Xlj wh*#}yiy,V*\AavpnL4lXEpXR˜Dٛ$v;찬mQB"0v-E{Ww(>"C^)+aJN3voH"HM7]t~Eɋɝ#nSAbKXḑXQ tY4qF{8~ i8{Sc1v6}^]9DZY<!xPIU8b#?;dQ Vۧ!XOYuaj;8#<Rm endstream endobj 692 0 obj << /Length 3051 /Filter /FlateDecode >> stream xڝZKsFϯ1|Llm*VI4ْXHguww}?}ɏ|=qppK=7K4 "JyS9Mc>W/v=џG]ӷ ~IC0p>UM^ϻ,pރk gF\Ӛm7T-M]92Cf>nL## q]^K)o(mcZbvsޏ(J3%v9EhԹ,ӡ-3J#g0~ҷrUA0z~hhXƲ #Gz^]sgBoCR\C&AwƩ̬qmBpNi]p֯RvtU> qTSj; RD\R׼|`he;>~yjA{ BE˗' :D%let0rė#Qsc4'sxjgΐ7 ͘ORY?|pY.:D\\m'O ~;jU = qZV%&k]ݒw;H|W 3c$HV QI_S;ӛ]V-d<`eM^&D9 E.i0|o;Uso 3 21Fl7*Q,Q:P!8@AY'`,]`9u9Nǚv)H^<$Jƕ`CO߅TY>Q>;/hYǶ[AǬAMb,*?A/+43qi䮟/&M#,kN!YQ/Uo Bx;q/WD^bs:AB>CP"h~WW7Б\~i]s4KdϿQc 5̩ot^xn0WܠEfc{$I2l/_G E{ј}]^dWi7T!ĩhGs6U-5<哱W ˤЇyW9+z/8'as߇{/S 2>-fqY/8*I9uo.s&%! :drv<cJ_w"MɖR4uʦGXI2hHdI)7=834O0Fn3\8xhrzB4 Q^r,iط͔hl6aEj2y{R)iuzNN A鵧L3[xl!V,: h%_ha CN=Ѵ<'9(;U, [{Mg5K*~{_xw(PfA2 x$^'L,rݱgdedޏMU-zdaʃ)(qqlaҐ,[v~Bc/# \.$鮷/` #ʸccWho$,Z$+QJ}|)]a; )L Hp *ñKbvGx2 66^B ]K]ՕS @.d''طp.͇c }GLӳi&/Š2xdG]fkCEI{?J~0Ѭ'AMx>RFVrp~>W-2 {7)@긲*iqQ`wҝ45"J9Tgsy?B)Qo  p{S~i|Uþ zҞLcZ7a#eΜ}VFpS!ߔ|7oTP[:K\l{*8y_LTqȯRd@[7TĹJ@0t)|qwyY3WJfD2ԪD(F)y<sL?1.$rY&ȧ*mT+0e,[Ue}{n5lNJVXW2Fx$̈́Z_=օxIS ?L7ާ[%B &N(ano/±(plPaG:V iIc{"BLh)iįSFY(dH:kơ<=X)8DaV31=pR^(0/ޢaUq2#,5@}{iԾ^QM e^Hѓ&+50K^0: j0I;g[lH jB$OR=)+B7eG.7ku+u[t4M'7i/.v># !G \75uaB $%Ko)t (أ艢(do endstream endobj 702 0 obj << /Length 1835 /Filter /FlateDecode >> stream xڭXKo6W"ߤ䰛a;cl39(ݲ-[rZyW$ȞYl`"bTꫪCƳ7)g"syk3] O70eQx!aW_tRُͿwHڋݼ.dL9wmp9׻fWK_J̇|_fx^1NݮNKvϯnK愂۝oS|9IqͿмB]}OV⌬B1˨YVzߵ >ĥcԤoͱ">?xZZzy Myz0; >S~`CMOFG:_t[:yH'+iaXGmzq%F鷔ɜt{.ˌ:Ea3Jǃ@xJ\-7hnd!G))4S\-UoMJVz 8fm_2'n̨8ܸϿ =P?:WL n_@$,@Ve~uE4 jQvEF4c1ͺs M#'A >i9M*3!'o^d\☯C/Y&W  e$]%)rL;%()cե- t(~PXl/zm2CIBo*Luq>쒢BhMdw (i`:t-6v=`&EMGb"5{$lZ?01.\Y|VH4Oy?Ir,|2hif&S3A2c5+]VVp88K_4UЋI Žĥ0W(E^.TLZ-%:jFvukAn930ꩀ .~ M#"9KrL.,-MZ[WѧAcg;G/JśQoz&OIW2::ww?#5 yMN[^bQhpФ10Nl(ߍl3U:Fs;gU~H\,( MӯfbU`*f0Ƴ42A*:XhW Y=24(eF֦iZIlz68$tf?:Y#-{IعyB*35Y++{u JY:fɍ@K-y FL-' E 1m?5s*kPNn: Om2t_Sbg>e}`gd8"mUbk]DyB-:CQMdqmJ%%eO?Q:Rm'Sˋ# VǞpNv8tT)d'_ J~P(&`2+5jWQ ؤa0)TCb=d>Lh`6bB}h "e̻>/D endstream endobj 684 0 obj << /Type /XObject /Subtype /Image /Width 1280 /Height 781 /BitsPerComponent 8 /ColorSpace /DeviceRGB /SMask 710 0 R /Length 95761 /Filter /FlateDecode >> stream xWS[{1sϳ}oJQu `G` MPQ*WB $$~g 1_32>kB9לԿT"@ R_@ @@ "@ @ E 'b'_*M 5y^t|/XR6*fc~AÓ@nVďz`/Eyc+{ŝy_s˛9)Fa~qOiĬT؞rVoy]ʭ ⯆xՁ#'%1!/}_R]æ5?+Aߩ%^ ;N6{z0*֗~rN>0f."ЩYhbǚʒ svAx-(sA-O{S񬀨G 3_}P4 ̻Ÿz$}74drQ:$;JFo\xW\z5a|nUpYӾ_P10Smjci|?T]UUYUU\~[^{iHh[>o_|;U"~OBH*teDMuFƕ >sE{G[DduJSWRv3#~SUQov*(f< Xa'5-HZ{o,tq<_Z1+Mk/&0U8׾]PC;C7m޻!x}G͂o~-7]+.{}B/'W / wژup[K+>66D0L~zQIK[k[[[W$iYu>4oل”HoF9GSakb y._yeW<.gOܕ0w{yLްHĊ ! ՚ ~xbvm j@Nm5~;[>oR$an(,|q3a CZ,c Wʫܮ6Wؖ~X/wyC&ڇT;N2{~9;aٞ0j娵Zd~]NQ8{ĵȚRߑu+؝aB.fTtqG}ĢY- +{?* d_\5QH̷&&j\\ c3+ZZ[_cS_=8K=<k9MmS{+L?ƸGpͺ26 S+A?a s$;0S G8 ?ob.X\_sXRUy1(|jdyMy5uݪ=|'&&d9.x1"N93(6ScZA]a& ٤3簹f fucf*o?vѪk^otԭAv;Rv7~U% a/6ߩV̭yx/h02VYo;[>o ???O|Ogσ5+XvVd|||! EUUeE9/\ `78b.aU54Zv ~ʢaLSaošƦL9,?]|UZUUU^7Ѭͺ)zIBĝŹ$ޯ,tVW²9}#VҲ~6&ݼ:4,0ԛMww w:]ߋAظ/ Ԣ~4Kq-7W^VW:נ_x/Q`\`9￯76PRI%}L7czZɓ-z~ݰ7V ;>6LǟočW.|ot UzShczI ®&`VW7`CPYmMo^3{ tAN˞3n,.y9Šξsv~KǼOd6XNK& 3 j߹LJ^- E,B "M~wt[n+ aTq$ß%Eɉvq޼W70W,牯w,?ݲqVyW$y\07 ߅  |_:ŊJX11¼o^=OG P|TaW,꿋SѾl̜7m?K sނ9>_f|9Ǘ9_ emVW$wuhmnF P 7|,_^99s?]=Z_}=]Xv( -UXcrv;\x +KLUR zy0_!c/s`?_A//`y} GZyE(mA"8Yav#n@iRXpo5:'  $[:yv4L:N5w1ߜC&_/ ߉][!6p#3uvi%[֬_WVҊw!|cbɖΉ+ˍrU-Q#h"C ? ;לtA{_O9\m7XJ8b'9yo_\~Yy]?cz4S{7:۩eSWXk8%[:(K[Uڸ$Z.V *bڠa[+;rSel1'mH!;,Q+]eZ!Aa!+XXl/Ԟݐ=w?hW*[Y_U-ԺCsJ+Js_&6}ߥVx݅\"**·Pf:P>̯om+޻:)/ͬЋ'[:(r-xbLm2~_V]QU+ëήաU+}kyc'*`_/J؈!#ʺ(_uG{~E俛Lu+;Qk$OQ6Q>OU7Qyb'e)+u{3,o})Ef9-kRKԃ W:\=\Ԏyt[+<_jVs&mۣr{o _ Kь띯6G /uloO|R6dQU6^ʮx(jY-"5Φԋ |7;y;n%W][݅z(/M?FC\uj+Wzh>*֯p1:جQx; / 鈨s0YXU /l jw'Í#2FC/54)ێV"?H4l ǟ-ԈB-Q_u/h;wV~c֯>zZۣVRpF=/%s/]8 k' SBMqzh9ESK -F:#֏>hiSZieu5YJw%m3O%[8(sD;VN6[6EKI6l^Am>i%~[(%uG5ͳG [Z3>//Gz)r()q!DRgm)wsJK](jӅDM|_Y95Un^H~rz~MSKKmun'EKݸe9W~~g`ɖjZ$ w=wR+2x0Jٱ?|[TYU&K>ǡ.RɻT崛'4Y)jE%w.R_/WZEr^[5(J8kmb{LTk^0Ruˌ)xm^noFSXz+ #]%ձoxl*%AU3eT?=.ڛ2+UCF`Xx&gl3-0lVۣX-|w1wF움ߛ}3_4&tizw F&lQ+]<Վo9;Vewn^}[Z26.m~ Dw>+:}: 1n[Z;)K닒NIJN/Xf{7ûjxs M;VM~+12wiz0Ƀy0i$֨:s>i{v|/Xfnwg/rlIVRbQ9l!Y͎cy`o샻Oc[_($+.C)K'G^i1gvuҶh{ikχeOc<{c憺,j,E2YfƋ~lg_;-9([% >{؊JX1+v|<)XrA>C} v]~㟵[[VgoD/1D.) b\.[.UU+1[>j4?mS_yrܼ@T!;G :{kR¶NU4ѝÝzs…v9Ri{@<\'D;:2 B T俟>i$ I=i޼+v~|eeW#iPm1G3l@mN/X?;G9ìXBTA>=] |q2KgˊQNOg{Gk3S`BeǤkJPT*5?J+ "/,'MMJ"_/~T?M)`z ]!X!_/~L.Q=8[s[A&R󱤢mT+ ]-rg*.w }-J5cb _Y_R_Q !YlQ E+ >O͏#ץ.`^#;țZɝi"rΕ}B/'W dP"ȍ9I?q:= }nf4p$WWla]- p2ǡ]Q^-q%"eqÌ|.c-f?}rw32_ ;S|)5T3lM+X̜$vMtIW!_|ecG/Ɍ~nmcyOϨw7NJ76< b/ * _ dۇ~̔cgjk S"+lM d"S +* <>GL;4*涖W|ll)La4+ ?١3J9oNy,H AnG<,ΏτM궦3Q#,+>7 6,=c2ۓP'ץQvzmIAoؘ)zBRӧq+`6xg$!; ,/~LhmDXNU׽C}? _29yb}[⏐cZi,9I0伦!C #twq9#_/X?tu"ĸZlD)w>jX*{xߨX_ݦ19Wr:ۦ+uw}Em۱ - y-n̩A S-6xZʜKvzpø'6YV ~VL>C_/IwĄL6fՠV~< 3{d W79´mCyg-Cg߀'hi0B> cUC1ӒZnp]a& Tn1ʐlv6ƟYpcRIOgσRd||Wy݋ }e4;jX:ZΜ0~*WGxƩeCQYUUE)_?꥾|_ǟ7NxUDX?<:Z_ &%_ZdW߅+ __Y_/+-k'M-eYV\C")2ZIJŦ4D/*?&>OJbCo3IiVWYښ"J r_|QΈ/oH,XTVNX$lnlWU_/ û}=]vcZ$4pG9Ĕ_/,# +^H.H  [_Wc~ BJ_)_|,#S$q:.lr:=g4?$IYqSI5nENZcл!vh"96.W8k}v&;N_ΐ1\NG~W<L8w<;sdCoŸ!ko-v>Z;{Iq+M'rGf+9nko; $GcRi[TbFc2P?9Syn>•1dv.Țs {u${qr-} [dž!mW1y7ۓv)Mp|~4 (mҝS4>A<\y.yM4lBVI¯`{pʅ=} F, ࿋ռ~[R붠jf=͙*^IW3'jdioZE6w?2fVZԘq+Uw=U= &k*pQf8\kYUNAK%Rn;2ЗŽ)gO:!p$/'^IAڦdkށ+hD;I%=N>y"-JZ7tBQ˗ %> sy "~wɟ7M;&% ;} }T6, ̯z8C`,u9V>7: !뿩I ߉][!6p#3uH=c?sh姝]K*,|(ԁ8Y1%**8I(inoȹ뽎=T+#w:נIY[zZkmd۳>U}OvrMOGi&U }Ճ{~N:#!;D^&mz%66=dh;C熬H7[aΝ/lahǓEYл@;R؊79}]RZ(t+MJ,({qˎ~k؀mLc#n˙B]=hUu5yo[x࿋56]lPTRz|=pe|n,h'UZc(`5|S铢&b=3aР7(k|YE &l_;Bf-V{kgFIs~ubdVnm7$a$緳 6[|ʉY\bΕ/JU7n_JU+6 ڞ#JvbY'w2W4VЙE)vBQ`3 ]"yJ;V!3Բ>P{RF&bKΧɑ}j,F t'+OTCX' J;65{OՊ5jt*+Y;Uy1NL=19:_:DwDuI$ަx~D^~I=<;I# _E{˯yl9'ڴ~|5ޓv9[kX=h9G NA ϓ㾷jݩG6p:ϛ߶~ǩljٛ9eQw,lߕ~^قw ")́v<蚘Yn_Bn=jt%n줣ާ$+?HI#6/5=؉v8W(JkZa+Ni _魑eT\I-__UcI㟭h=)ׯp&҈ը3hfyRԞTreC$M9Kk8 Ak)ׇ=ٛ +YjZ6s&j%ާQ&m=Q(- XMHIex,:bftR_uyқ%>/..z}}_&ר2;CrڇY1G |/ ]k{rR翚#G;VsqŦSyiuw 1Ai|-8lP=ru3&00=$'ݍZN W+)oL՚w^ә7֦XJmb/< OB! $$Q'۟u܅qW9ܙE]ežG8/r )~~vN;==ok*WiXDiv3Ϫ% .rխ=\NVDžS+=St7]wօVʭL2jmk3_H^{Zʛ\u9>CQvǒ[xTwg-jҪk)jgLjfY(/7700д/y^[b\{v`ٕ$Sa~*׹1v4xwJ _/7-1~铚[}y EQ\b\ecMsʏcmWy{u~׳yFȜ})Zߒw%uX}{ēJzvEB榈+W.GPhBa#CY6_VAdž|>ooZK_0OEknZ}]wmB#iL>&y;#6%OnO|" [|gvv;voz_0BƖ523DSccHHy4B!/ P[S}KC |5Ob nw\ͧ46k^=)]h؎qs#9*= W*X<}fIaB.[vvOǶ^.xDRoܑJڛ>DnN__1Ƥ[ȣZ;7p%q gmZiW٭\ɔ*vf??}nY?K_)'l?و=yw:ꖻq-*#屡{HvU nۓv)!b9)}ouC࿋N%g{@QӒ-]1()~pǖOl_ +lzдwr47I%9̀_)+2(1 x?+zhG[_&KJniQ y/z/Ur ;Z#j#n˙B]=hUu5yo[x࿋O&璢ՔNէOP.6(9)-vo)gq߉ 롙OcrI+rF?g[6{?npL_pwݪz/VzZ|;Id?Qտ}3L:g/L ǰHz׍ _cɑ}j,擪+X(l/ԞItK9Qn߉ OrIYnsJ^\?{eGӴ_~^~=kd08yצ=㫅Q6:Cc2kD_/w/wݪ:uʺ(Sʺ3mrER>JArfoR#[?I[LY^Kr}?0^T;,hGJ;dG~ݯ_As,]3gj}A/5[m/Hrd;qd5< jݩG6p:ϛK PU37S[?IN׽BݵeWw+kZa+Ni >rFqg4Rqm'r࿋ʞWDL\ˍ)l jr2٘P&fozr-矤YKK?~3Mԯ$uWRW[o~x[ȃE&g?_H/4-aF~qq{7FL]>|-ϊ9Hn_wߴĄ5VsqŦSyM_/ WVs1V4y\){_B.Ὲe_j75)/A,kdQΰI_/ ]e2 Z>[_WP?B_/࿋%˴d/`~{rCV;in'RMol_Jl{[s~vѽe|u2Yf 09L +bqx" ,}oǕ eZ2\AȯZ"V[]Yf%:! .%GBRMNw䗄4ʘ.{5 L6&Z! `IbSos0 lw|\Iwx mFrL15CDJ Fke_/_ W.~1dr ].k/__R4r \mfޏnK$M+Q8 uw '_s5sI+e-EQn igUΟ?;y.HH!fo[fFq{|E# r}Pd۳(߉][!6p#3uHM[?IBXظFAs"_/ x87#`לtA{_O9\m_wbzh&'g{fuo_~jt(Y;zRUȌl/Ԟ.7|L߄'m9$͚x\RSQuM= w_ʺ(g E^>JArfoR#[?I[_IcO7>z`d5#I;ܜ;cOە_/'_Bn=j M}ԖO/b>+:}: 1ncz$'[y|fKϋZZ*s L,.E]_[71~\Rv(we1RM[?I`V{v~kT|A9wvٙVV}ash@c+ Z_RõZ&t&gI~ZIQCDԽ'nvq}_eӕmRqǁSqU5 ۮ;PB+M]1;)tJaE~\=3ePMˊEyڏJFGs|?W\a߅29n͖~amW əZ6?P7s/;rnl~֫1fZ5(ֹT '?-r-MW\&乡JB`އwVD,IK_qvC9]: wA;0BƖ523DSccHHy4B!/ ٘TT_M?+j9l;5 /kYYZҾFq95ill"i*zV_ ]}_ݯif_#rlL nt ֤mZh;)<0*;i%F6ْwz+{z80Qf;g} / $ ;2OjmnXj`cGb 4_b0XYV뾞ezK ȱkBjKӒ_!Ie2یy u5VIf"_Z @Ҡ_//L_ ȑQ2i3I,ם9%bCouV睻O]1()M$̞yB^“5v$y4YqZ03_~uo+}vB@;o|4KPws7UN+=O;Fp-:L6_/`'F򊐵F\r>Ve'>Oeg&2z&eŷ5z1\+XZ6jOȤzi߉}]/s"̻p$oSˤ&^AHI}F&u$B5su0ഋW_/wfjӥf\LjB Gv?Aiyzi_lzTc'6WK.>2)mkoBiQj@H ?p5c}͇{uyқLU߿Y6UXخ5gXRTHfߕԟqj濺ܨ7n_ͪǼt&)cE~؋n=O2#>*$kHI#srlDV@rQ%J=ywѭhS2 V n_/Ӓ⿎]wօVʧ4z?g<˪cӳKsSy1;eC yiwBwVS+תhI:,'o/tB[g+f'EN)ȏ Vz -3eŢ@ymGRnqr6']Jzl!dW OLt_羮/22  ] | _$f76]/5w%uXw{ēJVVzd bt9rt¨a;6w>Tj铚[}ynkPLpZ=i_P@)""ʕ<7BPP}~mMxuб4bMkٻﯦFsy9k78xff40cQcyݱRl RD@:@:B %bH)H罾lِT^ByP qD0*=¿/B!IR*GC5;"ңINN&mKVK S DaE6_hjK^0^i;_E!;U֝9ݠ ׈P0d}wR:C¿/B! I&)_#ڿCC9_B!C=RI?8€_E!%s2#°y% s 1 PKsQ^_ٿ;e/B!;70p/_R:/ ¿/B!P4f呡FCxd0Oǧ/u_!BOU*Gsa%j~  m ¿_G/B!_j|y}J^ |ڶ ?75 55 :|U_!BoRq?3*toTOVO%[8M]p" Mÿ/B!_zdxܞs73F&|j G/ ¿!B_1 IR)rF%岦ƺIWII%#"P02 __BtG)SՕd&Jή6?) dB&m./0_B!¿_+ }>w1fs=޳޴siv'& ΌYs̿B!tѿ1Pܸ~|-d`WG5gVj~_9ec%ϯ}Ƚ ~?>0_39'6Sj߭R9Q{6$7 N)=>0oE! Nݿt D.d/Cj8ɥPאqzf9]¿gDnQSu0ZڡrޭG3.u_[RSo`pr2?w!Bפlصh\^`JOfD=Q}F(ؖŤXw=eCI|Ы_~(P?Q\~bswc_L^<_:ujDB.'_~?([~fWX:rWVƮˋI&Г]VPcE,tm26P+jh ^\zQ]G\^q4j~7 uB B(kϿ+ڪ55+>~937Rg?V5juNΧ^e%k`VyYݑ퇅^ȯoi$.?dE6d5w}黊7dq;oSCw<;u=;z0 Xr7c1]цodz}Yیe|,3"/K>ZG"֧qq_Erg<B!޿O3|o_kEy|/ۨ7NT^vܭI̵xYِ{kV6aGn@Oܙ⊋_>0o=Pjvſ:0~ÓG6cUH2 r, L]hveg}n]& `7_|N9P';^&d;{xL#=WAE/á0+a}wnن/B!М/[jA$W}0Plb{oԁG--OR"[EuYem*5|6jA=;?57wr޾ vTOēidgEE+߉i8y|?ܮ7"uP/B!YYӝy'Krֵ]aGiT3cM¼ulqq%۾f~]wc_Il+>~!BKq|j0h{_c1 omVe_&O&id2 }nmՠiManD/"m9\,u/B!w+`5kzv,D[m(-fqF6{u5NSmc,v=I3 ~^1NoR9}7 2jCƀaKs43_D_B|ٿ_W:z'.Jd=os.J4{aJʏǎq5q ckۿcxB6?[3n(X[[._IˤeRIqQQRRvCC=YNF3ٿ-?۱uP^Eug?ma/mqܮ _¿! -p88J}+MHvZi8g#ȌZ?K2+HZՏkO>T-w5+Hko=Q|֮/Ghmi>zŋGr^HZma>$wo1*JDa=¿!пS$w)A $''%PT%cha " ¿.n ¿!Ͽٙ_e  Ck*娃1_wvk).B!Io ׈ P*_=n=!B/Jڿ! |e/B!PXW.< + ϝ70C-MDzNY_;O7B!}_BRROƯJ|l?H\&u7-fN#D:N_wv|/!Bj5jyNSɢrgGk0jk+FwW>/ ¿/B!P8( ^o4{%y^?> _Y/O!BWU(΅C.J2LS(i/ ¿s'Bѿ?> ׃ɳm~nj,k4jCѿ,E!ƿ*hӌ-!>ùBw8MDj4_RN, '¿/ "Be27/=]aq{nfLX'_צ/˕Cwuy!E!\LˤaWyFrYScݤ$!Р! dh ¿/B!wV"ϔ)BJ2sb^?9ܸrqE2A0<4 ή}ÓEDX9]Rw"<~>aB!)Cf)r._V)o%[Է}Ȃ2`w[{+"d¿L¿!)0>Fׯ%0L ޺zы/OKQ]A/Rj 2ܝϟW*hz$ל/"s YSC˧Zb$ _ÿ/B!_wKP(NODn29΍+ެLh\Wړ+Em[8?jR4?rQ\mWZ' ²;|KkZi|E3z.{ԩm ,߾'ӘŶᲓԒiOuņc WPqz)RȺ~oKWDnKߜߵnE6''s׮._rk6{6A_G!C޿&Y͝zMjr4w&IEOnoY#1&빏-ZGH;Xu?͎=^T65G{˛Oy_HՕTTJ2TeG+n9V6@9MR%Sftsg*vÂeeo3.nO z,%8l.mV5Ke B!\az6{Sa"Z,;r؎^mo68<@Uw{scw򊃋ї]ſ:03o?ɿRXǮh*k+xfxQ4Y3KU,:wݍVQV =9Z.>vx.E¿/B!4kT}v?>=S&wlǰ~ {7RdcTդ|ֿ;e:ӗ=RwͲ­_Wg53_NMtϛO1׮돥Wd`ʹy; vQ#nV^0/%ܿ.-B!7tkog}.w:fU{_l=rt W]hzZ¯:?nͲkO2N''d3|¿Zy97۟LcGMS1s27;qINREE+rpjNj>GZRR __BcIl+>~!B%/Jᓻ'7G= 򾪌Clv.]WV1Az4/+ܖ2su>Px_9/i¿/B!_c4NZ*-.5+Ʊf>^^}z^nJVrQ'Ӽ:LPk-%UǮs֜|ϣ?Qwڮj9 _fNѿ,W_/B!75Iw^'}eՁ&l~~s6fZՅ_,g-yp`Tڮ.]ꑉ}Aٍq >lq+|葋/6޿Roy cT"U(߈m9A<(8"uK] }'B_w.r4_#ڿ#"!=9dѶj޿d(G~;~y/yXT{ eA_'"B1*NFǜnPkDW(^P)Gf_!B/C&)_#ڿCC99__!Bߩ9R)C$45# w./B!_7%s2#°y% s 1 PKsQ^_ףO=E: B!Ŀ 0p/_R:B_VV+ڟdғrgGk0jk+FwW>/ ΀6ɪ/B!_<2zhA iz4]*2˂@1B!Qb\X:rZ,4Bj¿/ru"Baj"A^߇ҢWyz3yv-M u~eF ¿3_ybc ~|ndq䊭'׉i_BRq?3*toTOVO%[8M]p";S5 Rb"7(i:OOz9w֣יGWty[Y-MeDElfٿWB}:BovfJ޾y ]Z}.b}WfJG8 NYB!3%5y̜}w;7\8rܺvL n1Aٕox [S$ZwtIalkDµ։#M;"_j2fEÍ#~լ2[fƿ!<e/"B)Cf)r._V)]I>Tqb2+IWVȓ}o%[Է}F8wV^"d淧WDlɄ͚qq{5f/B!7k ōGzM&{ o]Ɨըծ$AAEmuEEcH;O؝=Rmu -O{$ {%F¿sgY؝}V12g뎮_¿!Sx9_:Bqz"r`ɿwn\fdFʿi5¿ĹnL>Uʧ9%}~pi8 K/B_%߲-ߛZ+19~Wzg"5Mcc`\.ԩS&"r9YNg޿}ݛOU?hx_9"U[j?Y1[uhĵtGξXLJ~wggkԚەm]B٢<O̪kfW B!z_]D6;HzQԤ7f&Ьx<6fOT>)¿oy Be5{~{Dhd|lWV1Azɴ|nTLRF+/D-=_?ÿoy_BS:񋪘ecr>٬ڱh^|: 6*C9773ݛOȿdTPs{EmS/yȧ;(jgjnn z_wgfϿ /S@!;?gfEa;3ooI.vXnGZ>'_rWh3E$dgf@¿|]˚%V6SB!7;v͠lvU%iɑ7f]{Ev%0zlgq0{W7Um^_TR\Td/POCW8/y?"2o>"J&79a,/nw- "B(ke7-` _#pFLVqgxr%2Z[=rܦCVW* }:/a|,Jģ ؖF\B _-eyfѿޓvf bBy]eoѐ~hhpGzU*{Uh4jww/y ,_gϿ B!78;sAگ_`6zCu/ 2L?ѿ>:H_fϿ g]B!:gқB5;<4< ҿYaMY_6_]!gſJ`"Waa_Y!_gB!x+\<'S<" h7Pn[8C|sC qꉂjiq >XNlmoo(..jAW N"BW&}/}1yfȄOu!\տF~Zt+۷o|3za ¿!`L/J|k4z/I.55MJRɈP  a>@sٿ_z{RFjjZ TUU:u*!!ɓ'/*u -B oΓ4yĿ&OVW/ιƕ g\[. !r.gϕX,#;u e!rH0wnVYiB.gXyaj g7 ue*B!7$k ōGzM&{ o]ƗըίBfUpݻ_%˰_B_:Bqz"r`ɿwn\fdF¿ˋ3c٥~lCտEUWWOP߿%_l_B9AXvsolM|5=cעyks& _sSNmVd9={uo?ҿCP{n9xIT?oH Z_u{u_Bf'ٱGҋߦ&Ȟ1gpBFY{]|rrûd (..n>ʿ,7eM O:i!ο߳cW\ĎԪu}R^`_w _1?Sfi/K });YS/kF5W!k[^xJI5 ~^7:|%iWyUU_XnSF'?'_W5_Ba_MW>O4oӺz Y>_V<fO/Zw5eض3_wbBZbُfi/K%Yo,d/B!ܿcU]yU?jcͿJ(uLfi\okkhdhi/ә1Z_w뼃ե=E!PwQ8+%]ʵ>68Q::ϟfx4w.wÆ ;wKzJݝf͚۷oÿ3_CԿ (F!VdzW%Gޘ5`XmW SU uXnL*).*JJJ]nh'h+旼 7@CiZ.y 7obŊ7nݺuqqqp8& ޿@+xuyT˂_B s_ (=n^lxN l7Ӻr%2Z[=rܦCVW* }:/a|,Jģ t9KݻU[[+q/ uô/ E!ߙL_R9үNrr2h[BJe_2 Fqoh5L4V k׿6Wb)_BS֝9ݠ ׈P0d}wR:gLһz/_|i7nt%no%eMf5e84B_Io ׈ P*_?fee ׃ɳm~nj,k4jÿ4o5rj ϡ_ "BoWv?3"Jsֱ1ٿS/޿ mYa_!;X ޯ[_pFؽz_x֖G\x|$ШGx}o߼e5˥BZؖr#n_W鹄/kY/B!Nݿ27]JhHFGDBz4s8m =*P*4,ſΗ<j/4poc]moOy_oy-/BA~ĿdtUhF!뵿Kra6_Z_!74K2MdQ gƿfƿ@<՘/~Bo꩔Jڿ! |}(¿tsǿG=ο.L5 !ΤrL0_A@ɻy} C0;/~b/~ܿ*hӌ-!>ùBw8MDj4_RN, '¿6%rxaӅ{ٿ2ܞs73F&|j G/zFsϓ_34`}/ Eh saO+B_1 IR)rF%岦ƺIWII%#"P02 ޿.y5kee3[|w1fs=޳޴siv'>J>TqbӲBL*j9c y6pǢfȅ_׍(]8KZ|/~׏_4 ':}"¿A_U(n\N>ږl2W0ȫãÚ3+5/ל鱌|Ԓ>މr+a埨 *j+*#k]_WT~am i%@. ¿^Q /kuɁrщow m&w~RS4ǗiO.UD+ܷ7A,&JX3b{Ezs~b#;gտ8z&6<3_ߖ4 :vtf Ce7w.fkZWr7&e{ƮE Ɨŵ'6Gfn:׭63,=J`;Dv=ckNݗ0y?~^rO:m"r[!p/[]s?kΫN,UQZh {q+cW$8ǎi^p{8QG8wl"HeAS[[Y}YNYGF.r2nP>_[I.;wk /M-dgѿO3|~"{$mjҏyne7vE[ź_οo=}WQU&5|6zrN4Qݫod ݮGߗv;dnMkKr/ yީуd(Œ;W6|K&|[n5dzADn l]veC31 7fr5Tok몊_ +W]ZEue}UA%TA)W=,uםg(i;$mpC ->xkZ,6+E ^|zY^qp;Rvҗq7c}R^`_L¼lB٫^ol}6dt}EGOWc&޿/caxr:Ʊ#w,<*?IA~ӛk.p5&9jl߮xbo:*@O6ߕWm?[wR0~īUCe/Pڙ9Z6_w_wNZ0!rIbӿcPWso{*ڌpWY|{fax4OUUkT}v?Yn,bl2G91u2N/y:B]2X~z'Vva2Lv~F.DtNh;U "U8%Qn|Yc۞s/ ο=ʏc_ugw_ _+'̨i<=fMwsd¿fqy㢩|cW}0P̼8[^(Mf_V<{vgGNnڹ?sۗA.}x2LO>gE-zN(^L{eb g˿/B_ó_ PrZ gp/ߠۿ\W}nB)"Z ;Lzsw5 ,_gʲWV1Aoߞ'Q<KaUEidF&:(ꗇ㟛SQQl`~TJL}'vQͥjխ  :| ^z6uY!*t'> $?w0fٻثv3GU\+1h\z1|6po҈2Q֚F/F~~]gm|J2L#ݟjo+_Y'=W63me]}{bU7> ϯ<+./P,#vhZ}7Zv(5<9 -ٿ, Z|9Z6qy׿](g|c߀wV8V]ݞy2?Éŝ]G7_iܜ_?Jv/;ׇ~!Ceb|2L#\!GN?b:U]+oZA޼|g/|ezu3_&*19}b3F$d8Ewa&YS橭 c?q1y/;˴/ÿ7 &w EX,p5VdzW%Gޘ5@(m^Rbv(X[[._I/F/| {Iv;ߤu|.ι͗eRIqQQRRvCC=YNF3ٿ-?۱uP^Eug?ma/mqܮ= %&rӍڬs,7KˎRg_4qGQi/kktr]/Jb," Y[mOgv~gFo-l6{Ჽk)rrW5+Hkso=QfbXnM|.yŎ++,`[vj%2Z[=rܦCVwk*ݤaķp}0Zy>(x3rFWs-u]đʪCQ_,(3_wO~ /Bob#E(|*8ߥTkDwD$G3L>ږCRA^[sSA&/OfyYrdQNE8GfeĘ_5I f%FwkZ_Gfݿ{%6;/-a/ߙdtUhF!뵿Krd϶F$\Y$z#bfs }d#i'_&Z&-Ylu#/;XLO7QtOsa!c:ڮ]nM"Nfߵl7տ.QN˕L <+ufaۯ,v^Cȿ 0 %#C4~`׏Ol5r_w ؽa˟a uPףSse/\5.10_u 2[5YyW/9گ=ɵL=]95USc=o-  ]:+ NѿN(KBͿKbտip@(I o)%mgƿߛ07fgM.Vh~ooc]mom=aU/Ͽ,%_ִ=̀VmXYcq. :6Ė1mFCſ uP/ˉ'.l_4kq_֌-vCϮY/9_߷~\O;-__PKlY/K-yW`'9=}{t~pArE婧*VտSg|"jM'm=gu5V͇U4 E٥'\ʹ>hoUl%([_iZk :+Eɿ/ߢo&$gߝGGNK_Ch=xY$:G>UUw>+1i#XwkK7v*3Kˋ_=u%`Ng۫';C ?-\ѿp=.ҿΪ X:ϥX2E/ zX_sNzjr/{0jm:I{?{X_re/ }щ;U/zԨտ|V$QkR; k)~ömard[MqY9bg{/!7růrd5zl+ЖDzOWspUuA|R3.Yޘ7hэDP_">xϿKouLmao8Y{HÿK{z_K ſoۿɫr6_*m|eo׶΀gƿo߼)GȖn"VRg$чK"? `KN/,~~sVOܴwTۿ5wB *)~7ard[Mqz3PTk%sJ%SKOUgb7>v:/niEUuue|Z+J?s8iQ1K`|cO/X$(jbN+YOQ+>ʯwa`73¿_7ΰYs@kq_S7^c_P?\kwri_[ܥV!CQ7ʞ-W(mY㕟5>+O<~Wj* [ɧW##vc;_XoOmoۑd+m5Սu8bpzvEDCOUeob#FՎ[*M먄+N3[E-; F^N[>F__4P磈ez˚-N,l/g>ܛFs}{}UkBhK]:,,[8ݥ:!t iJ qޱYͲ,۲=H0R\Wg`K7Z._v5¿ 7 ~dzzڃ87\v󦱱Wq7\&.Vxc t5s5̖,W]Kd9O{hϫ]gdVq-IVN^RKTQkGnYJZqԽ47<^we_He^a tܢşZy9G{_n 'Y}fM;\:/aRq`/KiT _ e/1J""I({[˿G=g/ a]}, 8\$iěFGG'ses Vɗ.^q4vO;h#QR3~H-R҆M5777,֊3/|U8:QK/;*7\]K%N˯m|szxs:nwVZ<w=u7wPtg47M;ؐ#A]?T9!/%>¿/RGlzӋ'SS.ȹ7A7 9\MC?}I']ɩK½ozaKݕoLJd:G{2_mfgkuwr}t#4;w|ՙ+u+R}~7q7pˈEW_WTѐ?{zjkI@/9j;oV=m Mods/wt+3U jͿ +ӿLKINB_9W_9 Ur+s giɒqWؑ7px286аdXDoHnn'q۟[̵eʃy}ط4TadxxwM'lw=u\'|@^o E#..%RWAVڱ.=x t֔f7 r.eHK~\o;^J6yi[VYa_bp&5]5r:_oޕyd1wYm[D/Otg4_*de2_=_>_ _}1Rj¿ZԔ) 9!j>Nr wH͐!D*JڳkEr;2w.S}z36~kaʽwLU埖X{+޴3O ~fwWfq_߶]}N'ؙ`* L2"U_FNѿLKQE)5rfG Կ "bֿQ=6! A`ד&;N\ q$Y-frY`@/Sᩯ?9%s#%CjU+-di沁^~?Dÿ6IJ& k?R2R+H~w&p/n4m+/[r"_ՠah/_sNN ;͈nlhj&ɤft!^&SOsiQEiVU ^,Sr0UvwrجFI-|+yz^AɧKW) U3+ӿTT/)SeB_FIVКDž1߀P?2a ZEU)ÿGw'''=vqxf1]C*/;K_v1μjrgGJ]%ϡy}ɚH=燱"3G Yt_?_Ɵ(/ZFQ ; - y BS> FoGcσw.=.Y؃6?[BTTue9*Mq/2 )Eex3BKA2B# e jӿՃ/*a/M/ ;&i||8y:qJIͼ{*-J.rK/%- _,/Կo i2C`Zv_D4㕆=СyFKqԔ{wn^ELY+ M2 п o?C%KEmWR__D7/RL%WHїK_u ¿ ϿPWp0 "_WEu=/)&߅8[c4o(˨_&IpN+u)Fd8x Fr_&]owBqt¿/,:*ĿοL_veriÿ7u#_9 2kYA!Jlom<9916: */k.4jٿ|Lo7r޿Lp{/_*X̿?+W/B/ Ēűƴ)7kYZ?#!Tl zȊ7J9MZ`Mǿ9ܪ_Fy9R)Fd "_7Kn7tW_4<s:SDcWpH+7+3˯C1RWpX-_ eW7KѿLp5ÿMEBB#_JHG_ C_<e$0 .GƿR_ ՠHѰyaAh/^ׂ T.ǿ|ÿ7@gW~zȿ:k=ƙ jh*y.;Pƶ)9_Fܿ2}ez/S+fNRcпr` ¿$sUgq=+ir#yll/np\`Օ-=Q&a+ ƌO4jŜ;;֑qqߚ/+qWӸFӕ hؿ7&?׿*_\;3~M2?k̿Ƨ'8T~na?Lv% s1mL߿(~즮2; O>ʫjh)y"k_ ڿ^+$]̐2ՃnCVq.J_s=Mm׫4 cĿH/?<ͳӃ//9^5*+ّ߾byF#+ZKE׍˄˿LX07O "bĿ8{MR2K jÿ󛓎,v8q݉>fێGwoYgpEcW=y$l)l9Wɖ=vwߨz~mi Η007vl8s ˿wgk6<ۡUָVɧormzSl7>qpzNm-_"Qɮ=q_݁Ӫ\[48hku;OiuYGKF;oKᔑgRl_%lW}hoxhRݿˈ jٿ23Rᭂ`5"=FrW:`?!Ћ6|g]}iYiN ´5{/ev`aJ#;[NQqq?|כ ;\bX7wNl3>PqmañY嵕)n4lƺX{gˬkn2vM--M;wӁANIYyN nJ'y[LՃk ج"VTdo6A:t_ڦg{<Ϙ^mLݸ<#";i~N+ki˼鳸ڝ=9zT/%ed }2/_V`tE ;"` jڿҿ^F޿T[@"ѢSɁ7k,Ͱݩ>描֝)5_,caC9m[; Oקcxs?;zv/]W{۳dJ>kO2,>,loAǐt<9 nKOK=.U7ŕk)u/l^}eRҿ8d)kͿ Чcn1yf$_*{/0k^JE/9Fh/of:99155REōiU15346l*ߪK oߊ {RC?ׅĭתMuxuMsn;_ nK5;jXkgNEO#g->"OWFǿ:;U娬O;xtRrDcNo[G^"%)ߏ,*R/_ZпL!"4"A_W+_9< 絆Om>l oؚodXKqsW,1Z/pOWܔqmk]n/͞i=-3?]{57 Ɉgvː9 wnw:Tt侍s:$ǿ0yw~[~iu?R?ԓwuC_JfDZ^/ ¿|DT 4@1_Jܿfq.|jY|6p;| F6"U\)5LE_cտrl(߿Lh+1߿ rFoWΉ/tkҿ_$$W}2}_+1/FB]R2_WfOi #_F@Kſ K#p /#B_JY{"_&\НRP[_=/a/ȿT EB_33DeK5 +r/.'7_eŀݱhKۛS{ܭ'ܷqRnMhB*[Gޝ7;loўD +ŜÔ&)y2o47_e#q_/P75_2n-vsoV$Teq_@Kѻ>g1iXd`6) E2ZF'ި¿_Fҿo9+C_9Gʿl zYNM¿ ryD/ .wKb;fsiz6D+ٕ*?/nPP{7Um&Ӿ+E\|x#|to_߿ oP) r/B7 {0R;U%O"JyznIIq~.饿_WB_՝C~+3 "_ՎRǿ_ t_G_ĸE;?T4;d00Z[_ܾf94n#wJ{8Ό^N~̼k_m<\)V:M_oF^PZcF&>rٳK+1ƿ;3e쉧Rgr͵ /Z»vw7,uN²EOݴ,~yjLC 7]=OZmbs䮇%I?\ s|wעA&`jxOXq8e #CZ_K̿_ FпڿWtÿL<>|9҃;LF||_C]m]]mmhpvn-uo]7s&ސ@/>G qk]׵X}gV_|WwI/)&Vd񵷥UY/2CBemzgƔNmϐj޿vSs=__Tm6+#0ƾ%]nV\\Cӫ2 ݋ŝS6yמz||پ>OE;+(,h7}K>ohzA)/{Oo5yi7:6o˰FӱeCEmKV7ߊx_; $KĿޤ]xfklH'l1fv5W]E/=^#-qYx ֏o-,,zsE5&M}N*^seJO~q.3U_XMo8WZQ]sua%M/7v+`a8WW?B_*4e$K>6tjn&rnۡvsֻ'IX1=W~}n-TviJX{eɒ877R{-(ޙʅ5nM>h{nqҚ}u G2R Uo[(j> li˹z3bG꟟ں"SKVo1$n(}컯=+,ʰLF#0cٿ/#_ K=_د}JƂ)yބ_KCd,9=Ѹdj; Ք٣ ¿1_/սFĿjDy)S=9PΏ9'Eme5ZjS*WK =}BzeFմ ׶o_{8ߵ|+v¿u_Kٿɷw=j~(kZ_o#ʳufڼKw'k{hr/iY6H-||r "ܿڿdׇ*Y7-EB_ +G}9ZD=Je/#_&ɗ_r+*  ¿HH_!I ¿2W)(XXM/  ӿ<¿A#+;  Bo_@/ rǶ ޿K "!oe'&7 9WEe"$8ɅcǿT¿/¿/k|odi3>6BW/ ¿j6LƁ~Ex FFvEᰎxV"А͛$_MWgwH}@npc۲BT]YE<>>¿/}PѿeX~@otWѧ_J0AUt?_5[Ck1}ln$HP_;G8Y[]A p]?::Ѫgب9)qڬ-MSSN_  ,_ E_ __soVCc@_gm ޚ9>69|ssU7+$CMU@?2 ¿!{HW{Xcͥ߭\H[\}f_`_fWd^oM ##/kga*C|h/Rؤ{+߿2aW,` mJd཰_[p.wվ?o?}6yGNm[Ҿn-o9Xg!pYI! ƔaRWAW6c~߈WjcοSiz~Pg_%-]Ni;w˒JOۿw_ž5_i_C_A3"s+60 Ge_Ua*j&C/؅]wؔ@/>gs`aߩ4<4t9׻ēfgf} ¿o(˨_ 9ӱG탟-i:a9}^wƜsڞacͿ_ C_ UvP/-uYĥg]lxձYU??*)Ǯ߉qDĪů?Vr`1xyBfuMGٸ?_W嫖I#7F;38ƄWl9 F0߱Q45QNu''+5{:r[UH;5 '{Eȿ\2ȿ'V_oHIi5_5_&eݿwſ+Nsg홛`HqG~ߑatNƦgy{.?%p3)T+wLE7 qM?{Wl .;m^- ¿?f;7(өZw3P@yՒ ¿|_/׻{b׿ 35w~`/uKˢ=ΣWdkDZn֍j1P_wӧOg'+lCck "t_uu+5jꛦ_&N*+[JWMiPXl/ 5TW[w^׻ēFGczcXU#|ft}>7u O3{"?ĤK~6Ǧ4|ÿQ_ _-wEr$5{SWg;' GrS.h5㙃yĖ"?ÿ7߫AWl8hWl8Kiu,eB3!L *.*tdZ_W%1A JX/9͎I_4r煜ISK乻/_N< %  ˾< M7xR9X_e1:2QD}d*EF]'_DT#eEŴ+ߵo ¿Q_j>4߇_@GQ)=޾>I}@vf:z;%@}]5Q9 ¿ox3(1!/\hJҿ ¿o+iR_r^s1׿&@}MUv~z922̭!{Sc[}0vXYkj999¿/ ݲ6z/K* (o`b+Rs7UID=vɣdO?m%rh=]ccßi$^U~De%c32 ߰__uY~](/%0cĿ¿OCё1c'#tNOO;yKt6s.WJ V_EAW&ߘ/Fܴb9bͿ 3EBH_{RC_]!QK]b=/ҿ¿HHozBĦ)IUc 6u_&4 : (/g7<<_7"A `HpW:Cɞ_ /2wA0#0 e>_=C9'B7 tEWEBw5 "׿L $0S ձ~ HcC`E7/#cJĐWl\h˟#IWVde2,f]v^aD!7_^ȾWVߘzWɿ Fo3/o_OWsc}]ueUyRMeq@ojb1uuu61ZZZlfm>6Vi`y|| /=!_$yf~1U;ߨ/ ΍;; *!jz; ;>vuz?6rl$Bl3z1B=x !,:U/o 7|_F#Aں+y^a A} ݝf㠎Sg{kKcvk{{ȈUGww7&'%DgGf%R#߈ _ˈ_6_E¿/3G>_$3=_άlp:\2%dNy[m)0`oUyq_뭫433oU:==51>&Ƈ?ՙc`@eauuA'M*/_ _ɖ]T2?Yܿ斂'*2_$yb9X/ rĿcc҉\m]9_R_I6 ` KIG"vпVv??zi )ӗ5&6o5ޣE6߱Q45_EڿL %H&GW~4=ÿo΅97NZPۿiE--%=zn挑W&; 'r8K nq7_.#nVB*`OG]qInf~NViymW'cI)y>=*ߎ"v#ǿ"y*- FiOX)_8 ¿/""F~z(|冫Z CSKοIoaI'RK/ ¿ Wպm El&n5 ¿D>R_16F t L țo@;.Yڿ{ j=樖bD׶GɷneY&cο&t_Qr0)_i\k|Q>HӀlAQo,B0W[U/¿o@_op:Mftv__ofX;5pUmjǿ'T37֠|]*37PJZ;SBTWoPȷPlojᰶs[ÝHMAdڭ9 ¿/f22o@}k< *>Q!#g_9U52UowFʿ.ef恴Oo`dhPW&` 822̭!8ե&`644DTK@, 9l=ݝ✜cͿ*nE 4_ Kf7Fÿns0Z/ ÿZ//{/f6KoOMeYUy.94ipllcc#VIAwjITI" +0_B & ȿb_JĿUv1Rȿ~{E %sWu_S*̧o!t:<%I:9 ex[c9sS+^`Fȿ#<(1ۿTp!c A'U_F!RLүJ@p߀KHH_aT fD⌈EA{+{+6_/LThK45ZER^ U)f~ZU/MGpމ݉Ke+LÿR~bf$[+sow;&OTWq_ kÿ>`j(ticEg7uXL'^oGdXﻎ\LI/Kk:nKj@ T/ȿ҃B WpPh΍2Y:=_g_u?*V65*n+U -;d8^u,>R.ϡM찏Ta᪓?FE!NNWk@w%ǫFe味.3;Ttx-< "˰geW>Wo{(V9vpA%K'\-Rب:;\BǢeI{No7_cϋZm`e{oU-By1psg5q;2LXmړp0@ /e*/%_viR_U:5];NUT\&e48iܿ}i▟mJ-rh(;-M{nzH56\ںH|ܼɧ7$Ћ٤Jsh7y9Bv/x%đk}>+8z@mVFnQEya}K2u>vU4m8[LvL1pjYϳi|·Yާ:@ /; ܿ\&kL]7׿>,RZd6Nx@+ wLd27VKݨ o1 b;!ˈP/_NCjD+iWD=Ҭ~SRA7*W^=_*d"_ |\(/+Vᅭn׿{h,;϶{J_{_v݄j^񻛨SKW|@-ZVd_[U斈"_9.4w\2?dk3%,wa1@JyS'Zl @ÿH[?U|WzT/o?(ҷ]SZ4lcp +rWz9 @ ׁ7&ٿ疖VTնtڅFRǿ/q߁w;mA7Z$qI'Sʫk_Z߿'yi-VZƌ&h;/MמZJ'%wOI^>kIsb)@WF]aFx#1ڿ? nT(hb9տͿl* +sm" #_2w)|jDȿ9= ڛ.u {߸J,W] HDR)o_ڻڜ׿i]nX/w> jׅ;_+1A7$_{(Yss)/Ͽs5 U%\̹g< t0|ҿ-Ȕ?_NQ f[+Fkie6;x2|mAg'ÅAPWogM{XL)j:~ÙԼҊ+(؍5ԸGrjo-_?Ք"8HWg)?l2" K_̌ШV2gL_R%JcM7aW¿bS2)/g./#4 2e JͯVſ.Wye)/gI4_׿TT¿lP;NZ&q`Ig ԅot@hٿJWb@-AQ|HN/aكzE9 [SAˈ]tɃܨDiAk*D9=IT/'_J̣$6SAٖY|lumDms_>{IOC]mEiq𩦪_ިDIxS^3BM+/%VdSd nKί)yO8/#V$Z9#SV`x0O>%+,/%ۿb %a !< +- (SkK˧M "b$BU.-R_[¿/(ex͝&!AW9JV"Q+:( x[?~+ U60>ORTrz/{bFƄMJh-FܿX ( Jޥh״.(7fff!<(_@_KGWb03:+}*0 ERSc0.bŔucu0-R}9y)At$f>iTH,z+qw@袕tҿOS8A=5,[lr~*J_E A(%IcdYXP7rWx?e_3 =_UH]Tź{l+|*hAF^OlӥYmJg歴pW3c/%aF(4=uiv_>ӱ))_"bf>Ftt<"*ӵ[Nm Ϟ=5&NU+xbb Vg-J~~y$=#r^mܫ(1 "4Y[w=0b XiU1,X\̧rG w]mӆ&I<Yk>Y@V_oN6Ca/#9ߝג`}PMnW`)VR_EeDK*Pr2"mbRCĕLj7aS;a4#9 û1V  "oPe~s;kK_6獌גUkO?/KH;:BJ###[wرW?e%H/>_R8,_&@ɕ2 v/#>lRw D\/"2tmm1_^hUx$uzjՆ;7 9H"rs7o~d&@&+Ѥ(R ,@hĿ/|2׽V) ]$/1%u w~%GȅU7JĿPo ,lgy%eϒ0tYCr<%u!IvFJNd5:A4)# E /‚B_F\ڿA&Q}XWg!#1Z2Tu{=W ?{V_]WfݟOSSN}]%'af%mVj!}bݫ8c(R ,@ɿ MCI9gsrjL¿y~L_"a$%u[#_Ly &_-V̿|#/#Z¿/ۍYf awZd 2RS9'']:f_ᭅ$lr%-Oɕ͓aKetxE "(/7Q(m*I*<_c-%%hukwlΙl$)l6t:qm!~N/_>,b:z<. "Dl ICNNWk@w%ǫFe t9@ ߀ȩg "!1psg5q;2L "o~uUw_$$w/mS gL}un\Fÿ$s]I;.c`[E~q* ;v B !&:2ppz5XHS}[l޵KÜ43Ho">O.((=f?/` 3Vy߱*Ge t9@ Щ~rˇ; ozs^v~[G{sUuNuyhKMp_k[;Oz}STkȹrlVOI;ZcgWtg$O@Ë6q-^V)ݹ.4w\2?Wdk3%\͏,>`E7ՠ >:u\Œq:LJy\ߟzV͓lF~CƎAv~<-..{vZæ!a۩%/0$/`[sklZniInٝÞ;5FaNQqq?_o=-vrj* 27?{g5Gi:a 9}㵧ISy_\9s9 ƲٱFYjݥJ7xa:r{þuf_k:_fC>;!S'~vF~4DWBw($$$$$$$$$$)zC>,u]M:3~4$V0`ͰJ\s*߿ ۮx[M5<:3`3C/ G?ȪK )@ @Ds/of:9915iyr;jXks*ٰOOOEOb0$_cۧ1/@ vNNN \umaǝO `T?gWW9GlK;#Lݏ9leồKdT:KdwW^旗utW䖕zƪ}/ӰWEEigImW'4X>[^S[W|l\/kE @ _9<%BIۏ?qr/y~ȵjxpJnoo2D*2b_ex\_SX?8#m7èlO듕1=JWi/X|pڹ^ՕpEJdqgKJD]YFG#)j+\ҴRIksSG{W}Z/ի=xg;g 9.`s'3)h*<8򵻵"~rVkvVv;]NI@OcAXxp\3x xy]{'CWL|_W7eu>CZ޴_I_e,q2A^/X՟gbC+mV+:u3d "Nɐu n N__0b8Zzg2X6_=) Xv`e[wk3YHc ,BkųY\R) ZA+LKN)iY _;r>rJ  f.3,Œ&_+/R ?]jwN&<5<?D撻u]Jݷ#lJol%zcר&eWMlXa!L"R߉Ҹy?*攭|E8x/j"'.cS]g%u4U'^X-ٚqs<ݿL._Z$m+ArRjMd_W&ck:-tT7Zs-_]Nc:s_?UI0 BcA&E鶫޿~_BDgث~~;\eZ~fA]Rͽ_䙵;_pɝSk~|B2忚/6K3in2hG/毣~sDӏ#W8z-{rfbǙ\5bymz_W==M^}L2)b<YrAM2 !W[S^^tYR$>_c;Y}dwQ/ .~| mZw5;JW^d@/dZQUUQ bɭStjyY>i%/WlcݼƢ{DB⿿x5 m/rݞDžUE/~\KS-<^@㼊ڪo6H\+L;_*ͻ7Wm_#uuu{3/I_]~ ͖W4^\#]߫X64=O::^.6\>i;Uw F-BMV'߷cz!H79;"L-?$y׮ݝ+f>_m2v(+Y[aykN9s*0^XJVl7ٯG5OO1ƮiJ5DZc_?tлwy󒙇CH_+/~FR:ڨagֈvIwZ~m@mO]n6,_:|^t._8{;oJUiY֫xx}RZ eLNNJ$gϞ>}G%f4dn&EW(ᅳ=5+}:ySډ}bC9ӄL֜PfB(-F v OG[N=<̺3 ;|WzãQeW˂Djsש_]?ݟw=.Xh̹W/5FˣgI<:Wt[y?b_)瑥#{;rU5//9k*w_ÛDL"|KvRIkIR9#>>>o&Iv(R ;w˴OC#yף"~2~0ą|w-22a\0ɜWݽ-o·Ra_SMTP 7#vo|xP<ǀgdՄx >Ը]Y2يuH>AoyDƢ/i zetL^ahcxkN57 IϴgqNކ;󧐦Ƨivt=;\tku\r?V>CmXkڙcdʉqsL+H%{h|$&L2DDH_9(koߩ&It/DRAoХzY'+gS e b^tzIT3bKiT SVȚ}uucV|[w&i//ߎ? tkr_+k\Y)RZ r9k3Ռ|N[|4{}v~q=+Y;Yg4嗼rF{2)+JeR ٞ2Ƌ5(HIR) }W {R/W !ֻwg؛=~;v8@7?$jowkc޻NQo֛V[x//_zK2+I +A0IƘ2O$E p<$(ӎd;sϣY-Q;:q^e=ϸQ\r뤀ͩW}yqr8jƪ<'emV+Z=jxVu5W蕟(ȃ&߅'}]<+U7v?jvŌBM^`$;h4) `ٙK.jT7 .Ҟi5+5uc%1&E "T,7MF^, z7_%/%T%{3=ytčV"[^Hw  8ʐHĨ=nj}6y+ÅCx^Λ:/e߉ UFJhQ)uEc#Z>h4Tg(0jZ25JTS wNL( y: _{_}/22!,>~x_j /qŤRRM!/V_@ R|y{䅿?I,GtHa_wVN#-Cojontbԝv T=?(}<%V%_h{'wGkD=[,;Vq>N#=ɝ9V}#xQ^ymuyycx__˓n:r35?~?u]Cw B)5 Z#/_pՕ0yX7].<ɟwL]㶅2%cN=F/a&Qt.<jCx` K@Ğ}8Sk3 ׃NΈA/dsFT·#x6y?"__6\"ё h~bGnOKK ߼p_y#o64tH|6y Em9W!/mwFWw sC2;{`|b.<{ ߗ}cCU6QSQ_rhxdyj/` |_m* )oK+J {s`eMv@ ƺj @xM_)jOfͧ-l®֊baQakpƊ\rQ7lM,n T5m77&3/>(|øS?Y:z_iX*:zXxHv.s%X}0@ _@xFWQgޝc1?П.VF*ZظkpVߤ_no/7V{9< ~D6Q>EC!U;]|V7 3|-YvS.s%X}0@ _@x*& c JZDQuMu9W"oհ9R8c j.ZkpW@v?T~5]NijaxC~{K _lr5M@ /@ VL;|w_yɆ)j>Pip\M$NTl2?RO ˭/v5¨Mw?$ c[RQoa^ |!R{l[+ Xx4`};69t'y-ݍHdI> چU\**P2P}35vU<|0*ؗ/tM~$>iJ_|s_`5z\IW;u[ϼ6/\@E:M4'//k "J]Q5sy~~ȵ#iXW?p{3ݖI/X}習>W}CzUWFb@]*/Xa =?,+jontbԕeV_ JYG:+ K[pKv06pWd& V_HyW^[]^R3ʵU7$mM?{I~!Ѧoٝ-_US?I-P\Np_ݞ;UäCi/ߗGv?jᅭOTHb¦[œǧ6QT=βw ?[kcν#ƞjj_.:FwJ:z 2mHT0eIc):;/P[U|34oRG׵w2tDyqEОlmQS|'igzzGP.cс){gMڕ{9nk7ݡѧe&[_-M|ۅGyNarIj3ۊ cRE콐^HlU ҕ-)si͠ȇ+̉1&:GcHS2S4DjAUzyq53Cves18kqIi7'|e?5j'2U{Gv뫯V&[5.ڪ]ؤW{1 R{RޖW#[(j˹yi0o[7jξo%~)-CQ>zzTwQoW>.=qU^ ~}[>{I)FWU=:D7+g&tHm[cJ޿{~+(k1^=Aޗ^)ϕw5uUEoeG)k +=HJL RQ~G{ɲQ:Hj[ǢO>nY? ˄ US1OMm Z+;2MD+s%om/U.<Q@*f0fĂz*/o^#W\wm#zqr(I x(/ SZERkY,m}t)GEŵD[y؞kSd_Qgޝc1?П.V6gb0qsE-AjX4d`DO(*d4c3 ߿iK#Ծ}S:,<5X۳ -wKB6zo dSHavvlJZDQuMu9W"l͔7⿷߁8pzg|䯫B)w׃19Th<9jcޖm㟹l&~w=26tjs!i! ʋ&4YZeluGQ X\+ʖ.8 N쥇M:m=i띺Qqv<O-klhbckH%Yw{+EYTV^.%Mw")2Jk݈4Y7砀 >_Z.u͔͠f̎8v%%Oϑ R1^Qw|y,]E]}C?S_ka#__c/gwc̲>#%(–<fqq{M~˼'ͳO}~y1>1AV픖u_l*Q\ET-]p0C™gkb",M\Kz/wEj(~U!UG玥]Xݢ%5쩧|_ǃ o) Q_Xoa~vQTE_T,]!IĐD/wyw^^/%ˈv){_*+WFkKSmuhˤPEңfY'֢Fi+~-^vt5m^[ 5!tJ%쬌m bmԼT*iin655 U\{nW;Y}yګFmk8w [۞h"_WT{sߦPa^N_ow9@jccrb 'NPD#Ga/^( h3h*vtaSx35^|Em͏__qkOitkV3 V55[\oӨTC5۷oҲd)**"CCCD7nܘHSՎ%){~,J0 Kx"R%3CԶ9K"nL࿞ad]ƍ>||IPPPzz:K~-778o__"2\,>ykRquN`|| m G;߿?f߾}EEEmd'6ot߿U-cu*Dm-SȎj%igw#[sUהKWa_cl+g_3ĄJb7o{Z+JR^kU1^6v`3Yo+3*.ARD{GGG-wppp)3rTWa?̽q_. U_QPjf?):[_sRHV33I.ARQQQf=eϥR:;;-WJB/%P}`*qك%y6 jWj4jrK_RSS>q٨bLR0w܇K D|.]|ۘڿ&>X'$%Khhh8tPXXq؄=_T*Ts==Y\յRU&#u%eUNjK}]\Y[[Qj̏]eeJ%I(*,$:c^Vz$%b=OOO3jZf-3̲G$͛m]]yKk?+w W')/ٯ~ =/lVj٩)Dl֖'O\z%ۖO_© W`D'XWZH-$.|Sy4{)Sbјe47>|fap-blq…{nZMh{dMh ߅-̻Xk4'sw@VTNϋ;IJjX|,-.h4^UevC&|lnjg͛7ӧoυ{ Z_rC'VuaD2ɗYYϼ5rO)8T-^^Vd[fjpv}%mV`<YVcb\1TTS^\OTx"wÆ ]]]n@=v(s?Vif(g?ZCzUWFb@]*/wu_[w/m͵&eÚE<1՗Xߤ 6WFE DFFƥKJv.,y%..(jcMϓm^ 4jּzUOfRO?)V[n_i_GjQ֡V_“w]vQD6v[6;w677;̀0 tjG]jv^"/~<6_'DւM._n,#+j3KPL 9ۛ'/I4!+9Rbn߯KS1SXaѬHT0eIccf@*N~Z:H^]' T]+]>˖n_QjӝFYYX20?+n~ Y/p!VܹlNJ)> DP%HS2S4 $=/w5X'M*Rѩ__&{֭W^X4{ IIId;**<Io6W[]>Ɓǟ|ѷ x<:и;(xYI9KfiI/!ZwEuIه.m|GjPԖsiyevnܟ럐򶴼G<#o64tHg{]T!e>J "rRZNɇk1|*r( !KdvttryHv2E-_XXh? kXzI tOPuz:u~0pXqQ7p)ۮJowq;W; Y|Ntvk W1wď<5,Cb z*1aNF3v"/ϼY?7QF~?Y6Jg)=@mKiŔ_"%%%{޿G6~jB!7n|𡲲ɓ'AAA_핹j}#_Ud]D%k08p,kZ=xmrjǁKH%E7Rw̕*_{GmbӍ[5cW\w= R36>uĘ7^38V{9jh{I,(/ #lI_-ߴ4__{Ӑ @Sԛ7oث^Oy4+**Zrdus)mסM]"D,w1ҷnUd88s_MV;Ye:aYV9DžA(d/f:w\fw;ZCtu>Δ|U+Dkoԉ(;RxO{'c&NJ]"<ݻYA22T*'&&X2h3Yܰ_.K-|nE_ڭLvy-/>}5`pYBbI[]{J\ZbY7~d2Z3ݮزNekǁ{ycǹ4̥.`,\u`wAOq`oB_,`#_p TƆ&&EQ~s&M6'޽3tDysK6JpϢkaԦMb/j̒ }33tl\$;q_W2eV \̘v["];{ݭOOR]N\\OX{da~utٝ>Ml95vbUmMJq`pw?tX~߰Qh׼CYvr<5.{|W6ԟ)X$FSsN.hV NR>1W^VTW[ϧ+/cznTޜ*Xj~i5 /b2cf*>)x~˝d ~E۳X'2Z~lΝCg"E!2PJkǡCklo6j\ڶv$I~-f.pOjh#`Sfk6is[ýέ_N 1/NC"t8HߗG;"cwGKKc? \rcli/b2Ff*Ӈ mdsUbznevGJn^ Ljz ؉>D[9.| Vqo0ftoy.R.g! iqlv{9wtdsWb܅׹1,9g>T.G.ØGs.\Vdi=K.) ^׉ɲXev)rhozzrHuo,_Ys2A"k,>sw(.'Ȣv'͖rìݻh ųlٽ o#*7taKs[b&{]]U3-wսم7;}\؉Aѫ>I;=~Tč1k^fŋBpzzס/~̱:!ǎnZRu :e_a03,%\W,/~$>]^`@ ck>K+C9Nq3KхA/ʳVWXP8UWW|eϦ >ۓs?0#w.ؾ};'++%KLLLQQ"7nܘ̒elS{4ڡ][m&e։rC,JWH 8KedkQQӁC^{ Q._[.}b ˉe.Cl` Pاƥˏ >~O3pTQ~g|m\F\!a0RE/ x/={Fqqq,YHJbd#===>>^׷/^QzdriMϓm^eiԬy̤~R]k0 `Xڵ jk'rq&eg۵;Na=M,?pQ.]^tp_'8wvB.#}Q^goO-5U}_.WVV2V dC$sbf@Mnc:m}.5LrJc;/ߗGv?j[9:?KE78' (sk75_âWnbkco 79r쯷f4nPJw|YeT"lh6:: F𢨭pIӒJ%Mm_oAvrssI>!$$đH;sc **8Y%vӸYt}%_n_ *;T,Z’lk3M5_vKѣG,Y۷yfpW:fg 齪+Tp`%zyq53`$h,zYLjy7p.5VU5_E:.vT 1eDˎxp|z["*`gdYޚ癲B.ÇTDO8A6siijZѬKתp_)˰Kcp_2ḺwՖ-OهtZЛر!)kC&$$靘2ڛ s\uIqcv"wrQiGuM}Q.,/)/-6jI.w/yi1yГ\ u5==RHMĔ_2-U5\˥Eׅ}ۼ[t׾ZѷjjgL@X‹g0(R 7oVTTjY2s.z_GXoK쿮28_WW'k|~~͈2-9Lu$Siq;AeƂYQФ@/;'O۲e˅  Y81e+-K+_[kK_ ۇףWj4 /$6@ciEڒwzԱkT^2&A6l0&Ev!KҥKą5wba_Ho |jOFKЄ%kXr=q/꿺[yrwDiO皣A>02wO~[\gåK믚m%H.SJi,R .jzddڵk[n2ܔsqL֞&^9Z=_vpN[3uc{~ {yrS[.W1L/ӟ$L )~ e… y677rFV!::e\CkS_/].#5l>!l&*m9{n8c _ޓVɿ#h~HzQ3Hs=`uIQ61EwwKRfjq ؜d~'tUy_xP љ易xW1E5}M.`Or6^.6ԾaGUԙpN7L9T^՛{%vȞ;fbm5ٳ;ٛ~_4 f{aX3=Ȥ(R >͕+W% 2R2C.z)9J"!v.|Vz ~WXpO(Wd"we{׮ٙ-YMgEoeϭ*Sbe9W >_QYޜ~B}7Wm_#uuu{3/I_v?x`޽111׮]+++c@zguYeT={U)'i&ftw1K^w7owh|~oQImg&bl῜ \ËH3 rvjWDZͣU̘gcׯq*%cǎ$<Сw%3&E\Jg&NfI|I˙ xr_U>Dtᅬ5 Ok2Gt6U<8{;o eVxߍEm/Ye㿣U2]**0ZJ?QuQ/%eGάLߓ|g (LNZޚFJo濜 o6~un,yI4Rx-D">{ӧo߾}Qwjj٩IsJ6aR)N{{{FFÇCBBwqMfy_[A mw1z.r_t$s7zZgxf+NcOsO/nO5=3g5ע#^1٢ӘJӓ'LZ\RoЍfntx!5a3+[;:[/_늪j<_^pJf:Z䚅WDZݗ(H/ rKgG||| aOMDIQ@/;6lسg JKڀ%DyhCaG'֢b+)Y?eGA$yqߕ⿎XĮmӾp~dÑl{'8RA+Vٚ%x"}n/.Yg4嗼rF{2)+JeR ٞ2Ƌ5(HIR)h}bt =?DEC] F,il+/Pe7v>+JRv_o5s[|vfrgBh'&*K-˘T"&A6$c$?hR)g(D$)R6+Ttc4맺r'?̽qYwH"2_^7m77=#4xW<3kj㭾 W*3z+!!EƠ-XBoȾ1^!291A"ŪUl$~W%>_72c+KDc$f-X,bF1]F%VI_WA/4VO;ghbFuIM!&tS/0 Hzz_|OY";.]y̳D<ƄT":X$ rЫ08]vʐHxR3k橮n۳y9o࿞'?S=ax+ۓo+Ec#Z>h4G)*4i*+RM-91(|eչ^_‹R] L%_:>ggede1k R:Q`Ej\w5驦O}WT*<\v0ˌ F3 G=/V= ^תgpq^ڤĀ/T%;HmZݾL. EP[d[a_3J$:a&_RW0A/jWgVh_ 㟁׵j\W6i'3)hHm+bv汾hg&U$T)f15jwZUim'i]J7phRK`_ r-B^/=c*q[J>|nȺէԪg?S1SX?^pxU 9H}^/S{Wo^jZ|i5 9&''[BٟvV456_699rFZs!ڑ9cX7Ws3%RW0A/KDYܟ.7`?Ć w!Vu ;+UgnAD!5V3&',-868ٯ21_@B911tO$ntKZ(#ے_ [(-O|&I͐!D &,;/ ̌~dLP91nq/d&7_`3~ABŵ¨Ȼ%.usgFL# ,%{TƁJ%m+̳ ]Z!osimyoA|,5]ALԤR+5~3KL L*J_K߸AcM7Soԉcmb)֟O[Dn:Sϝ !Cj \|!h4F5ʯv^F{KB* Ze}5q>p dQԪU묳nX[2]pTe VDKS}_k.pG%ih_B!B,7~xt_t@Y-fOY$ŽQlVfۤYO%ih_B!B,׳2<6Wk5~FTjv%oT]]_4K\鿄BdY98+'/wOHi~͐p;GRm|,!fuyÇ^}Z=z6݂͞ž!͏4K\鿄Bfkuky貛q\}6747S}l(Kwof?_{~n{ZVc]]!S0W5CF˽it=H#MTC] MK!߁CK5/]s(phܚ"(&x޽41CM ;&kf¬틥4 e{d7PĬXJCJj}H#)!w;? !bν}IE_CxMz̬6i6PY !|{O;G&|j-[CO65MuӤ+DOַ5҉ukެin0^ĝ|@&8aj2~"jFz։לe꾳Zl66w !߀XUYv#ꕋg!0H% yW#jWCrWğrTw< "jߍI'7iI>Zreοkr}ݮ]ueH\3 {>yG%迄2Kg?{V9U2i6]fm5ouygSJҫ9Bk{d@/ZRCEH]ǯC_B  !d!gَmʥ'/Z;Cwp]R]pVjOSN\{kY hmBUƷK!>I+rzާ;+'Jgӝ~B¶#k"6 !_n_B]%6zOq!\WeMTϿW-<|s^xu}ձxBы-Oθ> [C2*X{?Hhq꣺WfYIYH !_[M]-})Y_B>T; `վۻtzcp- C])|g&4/ J؏=X$0eOfZ6\\' RyPb7? mº#}K*_I_?BB%Y%wM/!p3!̭ {[fz]rv64-{R=q?v;ݗ !_/@%5NKwʈ.W[?1)B@%B! K!BB!BB!B+@ ̺A@_@ /_/_@ @ /_ /[̼?/ e" s7d.\9~qT_K"heGC|.wjtt((i2:gpݾg=_D&֟e='O[3UkRT۞n~rP'_xTqB,2+YeW e.5.&S{dO^u ?9:wZV{#Grs^lsƋ)|@k_?yvA/"6^ЗU?)+Oh8Or|C`_GOk}Pj0fm[XQޭpw6گ=gŠJC :!bwڿ\Y,ԦpŠaO;,z?w ̖.6Z_^*bUYfj?xLl-6',FkAQIۮTu:'j޽<}6a뭉o#ݯG׏NmZ. 8zά5>>)9.v߬ZU؄' Zc'9Dcɶ0Fgel-ח vb>+B6wʭN9<ږr,~FٚHIԅz8+d@Kt]^f$"TK j\8ƳBVZ}対/|,ſ h< eK/n7~[អE:ώw,Huͥg.[[}ڤ6<1eO4[u=l˥̾'?v+ͧȲ,:4ajSN]}FD,1ǿ 6^(֝{XU_SYGyH ΄ MMMMݶ^p_G^ymy"עkM;Q~c{wca~qEmMHsʦcKw5"9C^zݮPu8WRasdǼyCJns}ϭe"l|CY "Ke:$t 3? }Qq>XZ"z}W_¿؜@7jgnk_1;<ǺRg^Z6юꌅB,ܙ73wJڢ5 /.)yu(ydWYښjO."a{%e%9V؉)syV#v˹"Ci}"dcɏ+?Fg!-} f/?NR/U\'BzNeSFH*i4r[=ϵJ_J?kx̦s"PEߑ: Zs!XUkBպgNljIZ:{%6Gv|̛:gomᴼDZAW%;ŢwUɓS-d8n}R7'g?8#ζ te|s{ȀЍNTb~s,Hmc7S[ OF jwtMͷ=ϊwEHYޣ|p?95ζAc1oԧjj5{[-XhtA׌͙~fU#NiD鑿ps^F,8z/qEB5oS ]eAjkޖZnٯW*}t&%E!}̖D'mɾj{r]oڨw27K'r ^4n60@N:9Wj<+|{3shMa׷ߪͼ%#5a`_|~_=bk+s|F'oRtv组"ϭUg##]e();$ (4aOaBsQ't#["w+Χ*,?amρ ߩ70sMR7禆o/6r۞b.yy=Pڑ"j4q?bo|pe> stream x@aOHdee [hl*"{DHL){τ}p~Ai pd ??$b??aud ??C%bFH@aud??Q1%  ?:R??9Z??c% 8 ?#sd??$ D ?:I fd?? 5QN??9E&I@֩ud.sd)Y% , ??%  ?:WΓ??)1K@ud]$YS%bI@ud]*YIL??%cWH@ ??5KfK@֕1WI@ud]+YI\/Y7H@ ?ȺQn??YJ@< ?Ț/Y $bn??[% 6 ?Z(Y$bK@ud-??Y*YwJ@]ud#sd'YK@@2 ?Z.Y+$ ! ?zX fd="YJ@* ?zL q ?zB??$b??g$ Y ?zN??yA^??d??Y+Y/K@+d&d??7$ kd)dm??%  ??w% kd'Y$ } ??E>??% # ??%  ?TJ@g1$ kd}.Y;$ k@d}%Y_K|#YJ@wd d$Y?K@/@od!YJ@. ??$ kd-YH@ֿ1{$ ? ?_J@~lb\X endstream endobj 717 0 obj << /Length 3139 /Filter /FlateDecode >> stream xZs6~_Gz&B߀=y5$DٜHKRIݿH,Mv21I]0_.ŷ_rABB(К&ex /R٫c|X)bJ~_Ku*jQٺ|S14W% #xDnu3Uܰ\橁\S1Ih4B%+K񒙬,kfhYYu"7zWM%ar+%eDqyEN>j),ʉ>V]<~XrÈbB7esfW=l%*\fr"L:"[#V;IEmHSc)AaQ+hax:+K$$E픅X *~m]eV%!s m)eJ3 L`0B4D<dT"MXÁp3Q{Έ&kZa7)@Ȓ#Y¸xv"T13*5'؈5a̓w}F5,,cuU0;hnPDlӄR<ۖE/+]߹ .U?*@FÐo\7>?>xaOzAɊ1֏3 EAcS[@@닭J.ȊLOisa~a!Ђ5%vZz0dœx_lg٦W#s]eSDۓ(,ʄ˵of}sS&U9%C& 3Fpc\,!ng VG^^.B;#x HaV~EPNs=u< Tb;]_]]]OC<bD'"WseȨ0@)ƿٯdVpϾ,@e*C|6BeƭG\Qp2ȲOF6 56vCG ]w}+f W FΏW_0EU]ӗ/YbbU_J6ؘW^X^z]  ptQDL :fN=3[|-.0;s}u-ۈmM&s՜3v6'vT~C7Wj{D |^v H5'VM}E;\X16/R"íDyAE~ҀC〤ZB}濗xmu{ףsų+:p5ʶwPteFodV9\6wׁ}|g c5uT|#W} -iǂ (4GzR"ıx H!'8`WŚAA"u!4=0B> -|V%n-̕y\_}&]H~r _vi\cz,l֛B><y=҆%1 {(OLSil\ Geҳku]姱k v A Ok]?]5By]| |js!2}jw]XK8Dh eԡ6]po|sPr09G =*7Q@Pv_]Ff`X1nfZmS?VÞ/RsL¸G9Yl}ʮvxa=T"Z _quWTe~)|S\Ja;W@\{`CW1Zg@8 i8 qhv} bBj( ̔}:gdE"5B<νWR{[-gX34'o::rF: ^C8#,?"Av1=ۋ)CyOtXx#B)]||& ad:8Brx?aAz[ơ[ģLC0z}-da/<`M!83"iW.'fؙ۰diOnvA-ge!1Q) ՒH, hJRsܜrDZTN(cGpP¼Xy+UY?*Uy*3/UMn,ֵ#՝<}8ǧhngGbUYiTU,*!w)% tr:T(Zǧ:||iBdc|DOWÖxydfbX!j柡ƕjxf$Q0אl1Hէ2{S> stream xZ[s~_ &CI2-[JԒ:~HLҎg,\sEx*>u,1jq{V$*]d*zS^_uWט,B3 x[r}ԙ6usWVQw`s>hkif[fb$V*}0&*cI$;ƈc 6_p2eTm;/0Ԗb9H[5ñw&-V8RFgOܖ Cp"jWe_ ޥ's'D"Iq;8QbqfԼ AЏ|{M>Mp3ʮ,`{A- ]Uˇ :[mM.I/.{g%BE{wI \:Ş6YtC] wBydy;ݡoh|8x82)۞AU/%7h9ˤzFd]W!}=Lƪ<4C&Ϭ@cSQ/s׈ /jos̊IQ(;kǬ{Р̡w@fCђ7^DaFgfK"]IB"@ C'cԋxg,~@:4!x'Mm5*Mq G#R˵=* ֖j\v*Qі-[Fa@|̽IW;PӢ˜4L9ʩA*?q:zTc2rR|;Ot+)E kr&gԌ 2KA黱ϡj{br"(;uї|8ڶ wӐ}2Ȥ+\-0V/ 72mϽGXְeR$2/u6Km}CC7P?sg*mt_yss" Qb,J s="Jmxd0<,kٻ9!Ɛs}{_D|?WdpW/]ReOﭝ;Ni5Ռwu-5Y]^`\UM'뿏?duZx5λ ?nu.29TȖ%+z!qFR#A8S%s:4=ljmI4oܤuVlT#eT{Að%ˡtQD7P9 OB| !×o)o׵9HTRgfOyw8Lfp)i6=uLfiu A^/8'A8Z4K ѣ`9\8´B;"LӗJ* =;j9whrhJP{e t]ٗ8~h(W D;M]s^d&$6n\w tH:~:s 9iz454/.%R}*|~:[= [ Z 5Lb_r2D$eϧرq#$w Fz5=~wg]YL52.G"ϏˌHr%;/~߾~4 `(xۖ繁PǵcQ>EGy F)k^;Q ;y`{_/H~S=(IX;ƅ+l'8X9y$ UGB壷&fE~XO]qLh7bx7ʸ_bAOyU:73P:XI1O = fra3s~C{M1#1tbv=&1sy#1TϞIglS!ǯ9XysrP>G0gðU"280j,S'ZOi1|$`' H"YЛ^єlyyL^Lܜv'ɯQb.pT9~NTMPNa' -Ef/znpF>fW'>A?}1H"fB43s\×#t ipX/ {[B_< n" ~r-r:Qi/I7cX\*쟃KK 0x_t:P endstream endobj 765 0 obj << /Length 2116 /Filter /FlateDecode >> stream xZK@%lIq!,U|p7I$VqI b:*yTZl4~LSF77t?f7{lqJl1c-2cVee. ԙ!P,IV0bTR]vx!{ľ<`YH ߚnuxTݮwdS!jZږ+/|Rw0*|7_jɃlrZEՇ]y3=쎋q,J2/bQk\o7vGo-ϽzkYL(V՗uyCOl7쩎[eQ6?H 0%yB<6+ X^ b \H+(`IjKU߯ZKx:{!^_ꋊe:NfRϾVp+ۻ/ڮHݕe6{@6 x@X…G9ÃFS&}mX˗`>.WO<_0 z%(kmSDsj-f\,_UuOQaRָKEst)zpGazRY兯WuzX.0L=R&ލ͍3 cG;rk#4&SL##k0z-3`zBAV4,a$9Lo\LnX"kLE-H%.J 6Depu%٭+LHn΅-$ Ɇ)vxpv!,0q_oX=]]E·)]suTxW]OF]]R ?9zl^: e_N~@?N ~hBhXvӒ>URәpЉh9(xYSη N/&{"wRk(ݝ#jBfQ+<5CupsqY (3e/?L"޳9tl&M(ť/i~uRQܬklQoc ,7QI=؆ICMBɔ#V7j^]ɞyzPL=Ї&!oa2e 9jH'A >{ CmA4c~Qk144FbK}nyWοLӾi]ڌ"frm}$%2Wh~OAR8,-ŇoחO8#@G, p$ǝyJ<ez<@@N<OtѭD`EE黈 to> stream xZKsW U6&~ʇZv>$$‡$u~}1CHȶ\D~~4hofϦwHc2cVey~_@Rg8C Z"YΈqK qN$YOm 9άe%R֩u "?lW}fR^kx>Wuk`s?--^IgݦyaV,. /*Ҝ GBI%VUjx5qFvg "hxj;I vbi4|\ CP^>+ׄQ€oMZށW!6:>G+!o/'哺 Xml+sc6 Gơ< P)Ggu΄HY j1$.lݷ岈Vjէ_ݺY:` f u8\Z]ZVUK"$d.سUUj-xJq,jb\Mp,s[21Ux%-:ë.Z=}&] 1^xp7hZ]B*8Z#Ɔ((ƒ."'saobڻ]go^gl.⛌.n9mdPSJLm gimLa1T<$65i7(=) 8g[J*(jd+ O@ޡ?ēL]h`z1:N|9f61H "K8MLM2#Ԧ_~ g wϕ7OughS/{Ӧٞĩ3 cDm9xLُ$xW>k$$m__)7 Wҥ8^.m@h 5Ml/>/FQ-]G6q wo؞Oi p`dڤa#RMixuh\6qT[yf'݇J\]AI2,ug<|?/vmH0Ih/BКt $ }֏j)X[aׅRϲe`\U0n0ֲE{zmt-p >6)`yY~V~P P@ PzS½ {4~xۜ/bKUctKxj-wZ0򹖸xL3ڙ1_ti,hmW}P& 3h1 qbth @F9Le}7 4>k<eZ _%8?xd|s$f3 L:Jft +Df"va1#vX  sp1@g9KBo ~ӭ% 3HRs2H!+)SA9 zGR: G:dRG =MuPJaT#,/9w Zκ)\<+Cm>X[a׾E?-Зu+Z#䍷n~Gtu6m=hh^JbN4q&@U=Ie]ƭ=J9 cF!>:8zYnMCG9gp7ػp"EK\'%Ҭ`I6:%X P gz4s!. 6;Br_}]]F2 1D!+e1_}Zw;n o5H)vjo0 ^/զI"=kZ#yORl@ܩFwS-S!H՟-uZΚX{vۏ ?٤I !@?]~w+?J-:,W|YW4Oaco1V}./) &INwϥtl/ a %/0'o~)E&R?v endstream endobj 674 0 obj << /Type /ObjStm /N 100 /First 889 /Length 2607 /Filter /FlateDecode >> stream xZms_vMf=Nz2YTIM(2dH`/Sv夎bB[+st ZqmrRmvIT2ZD.ke%lx'Wr{"#Nv1bE׊Q…GP1^a1X#rPxb(A d}ࢢWs^.4@"E!]LPb"H#.ЏjA0 )PbŝRvBI]@`*N 8qTfc "aNdИrt`EAK$bJXHϙVJgÃ7@jkl^VJca鱪]x :jP"B{ǦbnULa2f ;15*+l2I\j?%WEv%wRm`5`ZmjB+9R̭f ܵ[ {5(콪8 Za(4[\ tnɨyUŨy~z8u|?ig/3j4O/5ڳ{ g NSW35]57Njҋ>WxW#. d_1_a%x8a u$˜g-{sWū]/ +\7CN~Y0}D j0Uo6XBZ,xNyQ=!pE&K#En0" ^/*nnQ M mNgӳ-5E½4y;j]{cY;Gd7f3`LtJ6Fѣd#4-8Y쭀9Im )#YXw ~M]Pz2Pz2PM40(T1Tq[YQ.ùieN_̀(G0i$5ފt;l zކ*G A_,eiGate>NJQ(8e:&0>0e*uL*v* 2b ;A$/k]ڷo e>>^뮊n WbbfMmW>PҶ''̓?ߟ=o-Wj7ӳM~ٹ6]3k߶YLAOw5ylR+*,B{WdLɛQi1#Dbmf_ҞL7a] phhtVQO{S!:z$P#l FZdr tM5X;S܀a;`~M-G\(;F HXWtZ!bf*$&nЎy G8{v6s,Nr.Z˷``!%ɛxҐ[t\b ,,,l]v&P~d;Vn|V%nJ@)(r;wP ?#O>.2? U.۞٤٤Է=cJ=SJ=Sꏻ}{53#uO\v:]l|>A?{[0(2ż2djbP0V {KeKl`FݿeS% (`|LĹ* =.m2OBA-vvR':ʳLZz x/"a0+C#'F;_\ϐލWyYgY}DL pԳg[,kxz(U'=XecHԺ슔 F;:Vim:Mc6)&s-Ҙ4F{uW7uW7uPZ"}ivP;tXsgPVP>H=eVrd&Is;S:ɽoL endstream endobj 799 0 obj << /Length 2559 /Filter /FlateDecode >> stream xZr8+8=Db&S=553.B-3dɑ<~"(G*] : >f4{}F;yyB1R(ųj#s) Joo`sp^/bEgUx^b~Uy(v}ޮn5R=`Hf9g)$"y{pY(E=ņ0Sh^M3IaՔsF(׻̯V7fxjwTzGQ|&g[U,lծ+q뻫0+;vKV-7(3qwr8}Ћv&dp9eJ-S+|j:Ij Ij"Yњ9 o!.2ZF0WgΘk2& b̴*0Ng˛iv1p/~[jq{I\[Tk  {BC,l Qq4:RN~F+BU,SYlI5R"kHZ/nUAA&HD%iM h{QΘO]F9^/*u B($ P: Tu0~JqC4pdהt1@dBdkI+(GrN5##F941 *BpGXqwq4X]ӂS5x'|J≖U)+hT$}AS>uVV'%T^ShdCa͍ia˯~: 9N?+X8z>HrUK8ЊsgE3S\n+ٷ]ӂ9n⩻F@>~#Rr"ʶ_^/j!D(?ƒu vlPD \`{OOQ JQ_8`$Ӈ1j<"֨P#ܥ=T{lhz2Q12UݺI며ѐPQ ^V7dzYn'Ls3FNyvDT1O 6b5\i[~kLkPCD q齍n' 6&NxȌX9io|."jĈp$#kٝ:x%wg@NDMp]{F(ɅXaT 8]->0NTY:!nG_J}=MFEԝʨpR U;(=IY8nPxpWDD=ˎu#`Qe p=Ï!0sc.MDb8ND[v =fŏ9~ߠuNcc -0bCq3FG$hIА$lx?bقwҘpwXȱAPڎE,ith `"a @領GgB3֨~k`ce}Ε"t~@o"\VUsx)MF?RNKV*?%8T>RH@v> stream xZK@ X2F~TJDuٕCޛLbp5ɕt @ 8] ߠů-/Wo*#N)^F+ea"ԪvUT~h?FZ)(U߿_T¨_Ta]o_H[T~ůt*R$R@j*la'Q゛rW-w /vmyص<⏸pW^}TC=wz_  >i $x!Uj oo> 7[RzFF{Vzߺ?Z3&,7O+$q~4@eD ǫFǔ#fȁMy99`'qViiV1]u.fW#Ti EGip*.9qAT8i We9ϟJmw*tCx B3ABMT_g`c|ӳS(Xn 厅gߜ#FVs(5A )زDQ7 Z9 2 b2ELTjfꙈ'+I!^q*Ր`/~ch0rDYQd< 3P.AGL &dr4a v`KRq3}Z /߼1EE u`P&b'Ox9RF 1Dl⾆1vUoMp4`2c\c|wľHũ݂ F震+c%,yk.}Vh~VM  =)K` SHS!Ɨ!!/j!R1˲"%ho_2}s,!x7C\K4 L30P9*ᨁ '\K~!> K8!Ld{+9M9)m˵s1I<=IC¬ |;b{+AR1DH$\}^AJ`(!f/.0b8w8[?rb0I [ą&ץ/_f7.|?zio/wۻ`; p˓m"鱂+Ir)]-SITtR`1U}A{fTbc<% w@3N,#A^)6 n뺜Pa^FE|/ 2 VFg:p}V[ck*5l7 }ݶPvf>|6l9LQc5g?ψF0 .ͰژoG̱L7c zebi'WVb\>|/r`\q0`sXj$0z[F"♾qiĤ”~z9J] R_٘t -O ΞM+)3&P& dmڨ6]?OvTu:CI:.̮[=l]%.{1͙0[?WEn29> stream xYߏ5~b/9m/pmUyawI_7w8-*k1^UzrTBYͮ*g׺r0M5[T/&?77ُk9Պ9))P͞>;*g&H.ݐ\i&/9W%5uO>R(x]٨"ǤG0%Χx.|SV6~zq-1e /|α;n7oΧ\s';hmM.׫:v_7K.- ƎwjgٴS*ɳ}ĥ&(>nv&d.r85k~[^rEحf>_m}m$.H~dIdvIˎvv}6U[`EiV۾*,%h '75!u{P}`7Q/D8 r2_o6-No? '&maO j%(q m[%W;5^9^hBH'^ZNoDKL[.xR Lٓ3Lu(o4P/LLIn*do98@bPJJO 1GGMATQjAD{ ԈJ-rl(FL1Ss?w/->wAm81`" jEc}R>+drl $XٌY]!g(C*=;ͨw]du&wPsm4:P@ CR7g/^jA+WM/ue"$Tꇄ%:LØAguBJL%2ɶdCvN@8cFd'wxҎ,37bcYڨc &3=(Ί/vt1ڭ}s52n^(4L=gsB->& =ԴPLbLJmn!tl,!1i ̙,r2W_)l[gH,[OEZP! )8(e&c &y@1hp "O_IUES-G&P:` wraEnGS"ɵ6WEŠqdznW8E~(*H$|F+wN=hzǜot55%vܼH*f|:WcpBNj>Ҧ>DyXmȩ8wYT3Z/o7r~z5OMPUAu!/ŘdpGD/€o*2qBȏH}$kN|h&P0m]"+CˢΗb-$a ̴|de}dqXm%ĉTD|`i,X5CKߔH]BABN4YBur ph䝅Kχ9VboVq"Q6w{p\N$XCYYEE%E7xg|,W S~1ӿtJU uXD@zH#pp X@ 도h;Q'HJC:=_qE@zQk#0uuM_$ wމ%!2dhE{#50Q'ևZ=_hJ3ge%eW@.ꉎpS(D~3U/b>wdw8; ::ÌK^ʹ R?^N9 endstream endobj 842 0 obj << /Length 1211 /Filter /FlateDecode >> stream xڽVKs8 WiŇ(q6u=lRߒ9-G:_A%Q^$Ai, ߏbɳsfLDukV(y"VutYmrX*VI U2yߤYJ"m.i1ߤ<0aBI,5ō<&gBh ,̳4{q9O9SZB14yzT%p:%"y{HDp! 9IC yйPH\E$8~bѹlۖ;]= _} nH !MYU@R[KR$q,nwפ_\qNH#z(^uZԶ*ʯ#ոaj ;ݙ.wUYB# 1|t{Mm0ؗuXa(B:%oݏJgqgy[?!9! DG =&O㇖v Xisj n1dO t޳ ^0`㙢qcXedd`$Fm3UqJr7^L* BL1~xСXx΋c t=|p6۱a*kvH\G5L> wgXvIr01XwDXicbrDwq#Ť,/B(2S&1 ,ZZˍHSe2(S={f@LO$-II|3LI#2LH9ݎ.w]C@,%tl4>ֶNj0^)+bȢj?MT,7Gg.>[i( " dQDfoBu Zh*Rdvc>Y,?}7OL[ Kj> stream xڽYYs8~_G*q@ޜ8xln24KL(RKRI9~hPM9Ԗ]"4Z8,&l6xƂk5,6I2IS %&4RSɣo)|᰽vZ4j-P3D Iә"Yռ4ĄDD0Ѳ.iv;] t5B;$r[7(+^M=`}KE[6ouv{x[ic+ݺU;츄U!4Q^0(vу⎉|8G/sl'|&OaaU)/+WƓ TZ5E90rF:8"ؠ" *B2xgSe+ D2x, @3 c;ksMQ/ Nn:[/]U\1v+kfZ?޶k6yĎbNZg7r::?ym>Vwcȟz)GhJ̈́'a5Zn}㴱1 ~0#Xfnb&6`SbWx=s Gi"NpsbFWV)ܯ"ok vQ s/7 BYAZjEMoa=@BF-U5wM 25[tiIn&=GZq,Ш4rH wIHx6XwopLa)irw XܴI@e-ȊV!g3/0t WJҟe5̏y%/(_%F_5RU¢w1=_GqAl!qnxst@!zP|<Njkб6Ghs~DMP9cUf/ Xe]ޭ\3Xf@GHCta6UOmL 7 4πGQGDX($O>3b{?d nܼ/"qY2<߁#7aU}\y#0'eTdǣm4P>*NNT4c8|-ӣ>r85fQ^6E; @Yw-?fk)]FYaRv4Fi4<τ?𪳮 o{`qQAzx$y2~@HrgP9=<s՞X{;\/^߀e`ͩ&ԓ/{IXC(M>4>on|a 'tk t;;ᜫxиUj_ǢK{4ςJ'bX<Պ@d]e-B";=Cbt@ݸ/%j $3Coѭ m~Q-U} 68S yd5qX[W]J5!$ɱ#=a4I|H+F JiH dGsq>>~')G#5 Tћ0GT}$x`V=RLVA^ q^ Ql%X(m"] $C[uZ/EH3C#⌀ @QzwM]{$w䟼 %# ٻhL{+{ق!owpKO_t 4bH1899u>j3H o}b?8<λZmӘX)J9*ނ endstream endobj 879 0 obj << /Length 2889 /Filter /FlateDecode >> stream xڵY[s6~ϯ#=[i$McݤCK̈́"UJa~e](gE>|lr5&d;'NϘ0:dzbHd>|`/^M>L˒T(ܴU>;&~H9?3ӨyO ?z3i&yS>bY2#NuޥNL-x]Ƈam,O%f'd lu_㳤J^_=jj k^Z̛Y:@YSM]ITOA:+>/niUEm.bmIU]ǒ|a~!6TuYuq_`\VeAy y֍AOD5LK-[ G5c9 kFGw5' tYM!uش?v/ݳ*ЎƫU+;;=;IJ$\ezpVu ,`iLҷyݭ6:%4U(k 4?Gיghk6,ÓDZ,vt4Of]#x9ٰGL=aB E L@ 1u2&=&Uw{**>@Cz"[.$X/_"-pQо#*gBouűr謧 OȫZ!%D* Jkx}'n5ȣQEʄDUy5E"GX&GrhX &vE;&yUt( ]W@PTJ-[&1vw**b@ܙ hc{\ޭbθ<:A6rɋ1(ͿzG^ R0}zeҜ&B: g2E"nc-jVBWp^W>G`.*:h@z51ݘ~U,Jഏ̤NxVs DlJT_OΦVC8FҖjwSInXMty]TsƨɹLނˢULУ+˪+lE$Q7iUB]YD":xx,+qXcibpbboٝ85uujRxoEL6!X8ۉx6 *YgrݭJd d5exFTh|Xw%w{PˉI$?` 2rXEtb`Y9*!7i/%xhЅ0Vh!UU7<(.ϒn1ȃ!"I[$`|#+ lajP̋pۤwy.R˨"mO|;ڟS62#5Wߠ6As<Ƙ]flPz1|e^-|ee^\hbmѴ7Ap,eN|!eBy7}o8pqlAa%])papb3,Q: \DԜ))9,%;eq!_G@4ά@0t~ <Țك)na(C1O/)F[( 'G3 _h2\,( Q$,}J@$eVn0[>FBwqsdZl2UpgA t34'ChobVvELGAHIK:N@C=+c.VCuz7Rl<8Ư;9̯_,K'O&eRڇo!1\{$>@vtςkkwtEx}U/xm<<~+*x1¿+Xpj4(@X.w{!DV}_}tܖ%d*)S7+*CqC^QWOuP9בR(MmfȐsn^!֞?rY u::cFRcBdNNO.ޗS"Xz: ڳ S>:Fi޽ v>*V_<˝P(/7{: vcMz݃3HiΙ(a^S|qٖ";ע>;#M|(dztYpMJzo3O?vbRM{Ě:5LCLߕ~/ 1EbSii3Ipl92+p읁?9 Z&nCL8!^n$cÄ3CJa2ey ШT _)2da3ߙSQxنaα3DԀ]F3D$,_c`f)c¤5~ȱ52 9j ) endstream endobj 790 0 obj << /Type /ObjStm /N 100 /First 900 /Length 2759 /Filter /FlateDecode >> stream xZmo_/$poq/Z4@IP8+q$Wɿ3+JR4A fɇÙgfL)gR*8)M3g1>qm1-)?f;KLkiOOO$;l(bs?iA8  ̐ JRaJ2y@9!B X YoVKZ&Z/[6Զ82MF`(ap{LA\#)&i;lsnhHkN8ڠf r 18 Hٕ=5!_gÍ,>sץjSjqgDz0]#ØJ1Qr^)RbHbv"+DˍgΤHsxpvqߣby?^]Oѓ|((Ks ֝n9p,j",BhۋaP-E6idd+{+Lm>,=M {j8ʢR{%('}>nX3C!9EFDA=^b"#ע"ޫEHǺr'd@򾾟VNEYmaLclR@:A_LCj,~\L11m&w Ϊb{+FgQЮQZuL9F).{E=xU wq&Y yRVBwu$чqBkؼeZf}[oNgf2]RFb1!Qv+VcsNyGzkbvChn&r endstream endobj 901 0 obj << /Length 2918 /Filter /FlateDecode >> stream xڭmoFS% T}^p@&-8b]D۬)RGRM]W$c;\o3 ?;'}~__uV!7F\ߞ8RkOSLj(Y$/\_l;i=O.8|,әU""NK~RjmDK ugFF3&mZɫhwY3/UߑUT&j/?̡X+WT=&ϫ-s"5V̄fZ[m-hS7wng.̺nvWMݶy4yuE]Qq:{6H7jw֕Py:03~(?x1,/I)#qJy"z)̨|:x2r&@@gP#lh@0E 1fhP`N’&g4ԀNϗE5;d+ƽKs`Iz Lyd*=)/nUʟZʂ)1DƉT(=ˑL {#.ݣHI>2\c ܕyCB<3$`ndRSc|25+mh& wM^ȥ(!AE0e 3630VgSIIx5$td]NW=.ds͇[COj%F[S& jԺi-߬rq!v7 8 ؽc\j(`y.΄讇]q[ ~:wgr.RQ0&eC!b0VXE`Z\K\uف$)EpwxzKqWTX⮎9]DDgyu @x8;w!(8p.A(XxLF""63n=8/:箣Fŝ SʣR( _ bࣽ\Iτ?4I?ON75NpZ+NHbeVtE4$UO >EO.ƆLQQiYFa\l6o(LC\vCQ<eCBPu*Uߵf]x$|R.N{bI_J#5{4TWžEiof9n V}!s;>Lo=f_e- ^HC^˼woYKVcJ/Y,_.Bl j_`Z5msu෭A e|f* sWɼ|QBPIZ5EM+$Zz1.КJhQЪ'iFZ_sg7˚. Hx8f@D93-VXud$3or`#}(b|؁BCI3B&Ԕ p^7ilm( 4DJn3d sFH 2UJ 5"0[/FVYfBj,P Y7ɠ}d$.J@з Eh.>qyHD*<XE!U,!Vz_ve80ڂy( 8Ia#'2I .K㳣w(woq9^8<=VMݱЦ_ h9|빰ul$@,.enct9mر1֨ UuESQ6 A׫#4tb̩~ A3c7s~}pEС"tLs@'Q \twH%.*(垔Ka]ɇBkS8oP89Ge˻<W}-]{߉ᱢbz8%b!9(k)t{g|!f"N"˪?6l߮`de8o!]PH8 'I2ͼ ń.9O[`Oi{JVf2MBz<2 {?AI0̪> QD;@zŔ`(!Opk x'7yb ^ZRX9,T'#h Du'{2n.v)x)mVmS>oW,86M>\ ΔݿOmXUf-I{fAEE&YHuKK5Xy^M7Mqw!Zka:T<.u"Mw&1"=1hx,* Xn$ڻv I*M!U TLB/!A1s j zCeɥ u"2%=REۣXA^j.ˣMV5,-(jsQ*vp*CTo0 I|(< ({q_idLA9;>L@_Nf|Vu.X<+dZ݌yŷ FALE5C~̤\ endstream endobj 912 0 obj << /Length 1666 /Filter /FlateDecode >> stream xڕMo8>@D[bnIInEh{Pd֮,n!%vA{y9b.߰#ΩZ̘QJ*F_WſO6F_eDڔ|g1'e;N8iOƉ⚼挼, = Yaw)5Ԍ2˼6W+Xue0TCYc[᪪͏̈QUē02ձt7)ye^}ՅmoW]X4 ]⊮mWcxN.ܾ^a\bX#!5*uI=?Kf?~Oi68`LYGң6Tn/n/Hn(xlnfڮ\nbSR-^FS{ie@JȴFl 'mx\0L_^Nφϕs\=P]|uڭr?D^#!vaSe(m M\o߁qJa= u16[8ܿFrBuC[@aFnth@Y4'*L pahE}1 = mGEeTq.ɞv8WJ.6.o .MΘ}i&2,\3~i!60ڰbo@O)שǙzbh_H_,}]v Y^}q1Al7#q E.3 3\u 0LxCH"gSǟ >naa}u*~:z5W K'CXIgi2"qS-i3>b©hSIЅweZ7XOuZ m #f|`O6a~j~D@qy~W~u44.x:ĩnnXg4 Cy1nBZx V?Y/A$7Z gc sAR|-B4< ;67ϟ!߯Կl}=_uy7| ZS. x9&Mn}3ѫX 3Rc8L1qe.fS3]aqf)#7s-`ǣKci@-Eaơa&QPc㉱ mr 4p;|UqL*X+ }PZP=E{Pگ+}0ȡb GŤږ =H0,(]᧦*zv@PVwC D+d"d!3h({FN8ZraaBtLcJ?Ԥ9^ endstream endobj 932 0 obj << /Length 161 /Filter /FlateDecode >> stream x337U0P0U0S01CB.c I$r9yr\`W4K)YKE!P E? 00(?;h0a$>z A?$h LF N8\ù\=Y endstream endobj 942 0 obj << /Length1 1781 /Length2 12714 /Length3 0 /Length 13838 /Filter /FlateDecode >> stream xڍP.w  n஁.!HpwwwwMёin5s7T IKsj C)&6-$)yu Yi5bolb:T_v4ҎLT{+5q99Q-w"q "t|sWX pc_'gԞQLcքw|h[m/J &eLH1FD"k|ƟG~Mo;-_V.Uћۂu|,C ,shɫ+)8ˠH#8ZKeOzQ|OvT jxyi"' Ib %9/ĥX'?]x'ww+~~c?83ɭweZgd K)ֈ ~!dqfZP 'Ԅ1׌F^KZ#1?LkZ^L>i$|b<8%4PELy@!lBb3lI{RTS|΄GF'^zeOf#ӕ:kC 88?B Hc~h+bR.S([ǵDI84ZeD悖2r"rmŰĭզ.v+FC@k֋Kc|Z4~$}?eO(kE^6e*Jo7Y( ' .W>AP&ܤX`1 @$XRFFY&!V": u?aoVkKy}Eӻ+{À~F3"@D+<e77ᱝZ10" @Hmm"<4Y 凣m{OŸlĵߨ* NxD"ki[dM,hcN~g2vݛ@;i ,ym,KƈR,tz;]1ac-E;ZTxko>Koc֋ XTJk4x>fwZ~90OoZJi{F/ϜnŸ 7|KVZ 'qDBH$xa x lYȢ7AB6~ZIr+`^_ ~xVG=֊kSK)e dZ@I}-+YmKa I[U}@$e߹t24TJԃ%Rq7_FVcJumĘVڡˁ N5 s>[ڎ-% Oo[VPC!DӤ\lqկrԅ-TsXȍ0~b,UO<>ƴ& cEJ7_?7TirS9-UfZBbAmk2d3֪ihuB@Vj*"&4ZM+G3{*[XDMGاژЏ-AD렵df!-+3ۃjBZfF/yl1=mHuaށ v6Z뇲R@4ENZ^x2W+r?Bعr3o!- ubf&j{ e>uoWň~2e-f9x]'c"CJ X)ouCOǶ=8?BrM@a/e7,Nܶ)mj+q `V:m|ۿcU4|ȜfE图ËR}~ HG.9ArRY]>z Qysͷܦ7&{cg=tRw " 4z+`'y,T=YprRDf68wܩEyFQAdhJdXnX%ꉲjU^ţD& s ='u9챠Mq5߬989ϔӋ?ΕgDf[3twM(N_XCK;E|ƴڭy O!>GOFOx\uWFb,8 HRkb?NoR0ڑ,TyW~_vh N2$^|( v6v#CtJ >g+ٗ-IU'Er*.}g0ΨOP0S0Eisf{Wl{S퀁)͔FmSQv 5Gײ7yUw·X.w#}[A݄+2Q&loH H<5(\@|2CXfy}ڥ\'svf04{ igh,J5ԹF&KBgS@ D"/޻1M5n"B]'HqH)榨|4r?a}:b.9)l"8v[Zڪ?aDGmuM )vrxNLyUi''/S nHBOM 7cnI:}r | xNRL5B/\Qܹ.6DK=J(BElo~ ]ڡߵQGʖ;.*_Ag1~8Ҙ}p=6ыY^7[N4.թLY%RPw$cXs6ouTγ,o խ{\4{#EF9`s"3 jcGP(QQ!Ƙr i9-$2R"%aZ"]`@5w>y(dȵ/MX-ߟI3)o>hϠmQ! w' }'˜Xq_h*C a-TZeՆ3?a6ap)`B|O3CIU h&s\YU^T*&4W㠂PAXr{t,Gx~/] D\tKn;'=]ݏpcmdŵFKKd;Q\IFb'. v%&?|xG᜶椵R!{-|n@աi4V0u6!N|HB ߃T@PYVcXMSQbSRͶb.!+ 3sց'bZ_0pt^ň.m{XߑU0 e5c> r<7j/4߃a5>:]rC#1fBz\{阞CoNR>FJu+ϤCy3$ "~ \jRm[烝P'2Z;L|QbḷKdh5CiM f䂝Eً%m |(@>3e_ˠ,'*!m9ChWneyiZrJ/kI׽ͤӅXZ$Η™E$A~CCGPQ[Ɗ.QLu8i1+遰XtK+\4N z 1C,qKt~7qO攏k!R=6:k䰢ܧ}i9X1t,:~Fݨ.h:R긮~:*Su<5ˍ]=g"^ P%_-4TsA.|ju*x1:Mc]5 ңWNW'Rpw{0Q_b`s \GB:m֠.%=|u=;S{QԘ/4[u dxX ]cXR.[̤A^عMxQsCkpbIbf yT8,|ŏ1z3ߛ;`V&F)'3T],>9}r| w+hfv} 2c6KXJN@^#FҀHz#f[! ̱Nvn!&*[M: w94BO %Mʗ/(|].RGd~;5N_j "5>JzX'MnI*GImr9TE/c٪GȘs31$ Ei5h7yqyBeB:hf BiTX˳22MfWg6t<fMjw<?G\VZXz5/jˆ\U_\lLƢrwQREm.$7:>\+@Ql @IOtIĢI iՠu* b0&cLlhՙ,!%uO( lbhCJk>iK"YʢWs+H¨MWʼnwa˾?}-1mdCcVfA(h'ƫj*ZA̘4Gl~PoޓtES-N sDؚY:6Ƹ15{K4:ro_cT-3Ҵ|Vg;?#rr1"d;GB76:jk<'\d+ Fw%o!YLz3oS`w,zfMٛ[>6aQSƏYWYb%\3}4>77ȶ@Jn!=ůi+GkL끗H_2P3|8~3÷aL" Q0o'fai݆ؕb%n;Hl?1w`*dp }*xy7hfɋ1=?J`MQ\6ɇ*5{wo0`Q'7ΰcΦeIa9Y&yLi LQ(a,/ġ\>7˿ؕbVg,83|çKRt@zXF'aZ;soh`e5춲/џr2 "O-D$p64-Ýb̞ղ"))k/:p8;kSdaQUҘ #xѐph.iݶ,wT䍹 P =8IY})/ Iʬ e!ޏhP8i<$2YagDN$;j\Rrt>۟5 z]>^rm=ROx5O0ɺf0kg|a FLQk[u_E'-0 ݍZٴchVk CvSU6_PwںcC`9#@/,n $wfv hqk }Ywn7H# b>$OXGtFtH("0,Vqe_чx.c;Ao[3<`, {BET;|i,(rQT"v)]- O_/] mo@рABRLBDI_Ѝ^ibGs6ĶRp*υ?t麹^mW-ɞ sۓpЀew6[v,*f pB^hcRX(Տ]VP͏ 3^uJFΉ[AStaǥ9Q5ΜHxPbK8қ 'r:/?n)h\h+ulpyE{y(PH!؂.%2g;rn:{ %rlsW3 ȍCͺuwhUn8)kkZ TOT;ϱn9pDM,sJ9J^0UvUG&s6i*wII\R]q'r|d K_x|=Tᔏpf 3fc I+H%aׁChbOɜ:ME/eCApK#u֦Hݣ ;xPb0qEjIA 9u<셽,1Y8]anJ\t2žJgxrnsq!o\eZJ_xH4 PU\(k"ր8&s*ht ,tݷ={z}vz-hW4Ckt*;e逳.boJl:wU],>0Rd1B}pjbjhG߂jcNM쪸hRԎZBd*53 @Ȏ6͛cJF8+-_gf֏CyK7T5gZKQ 覬fol.}Go^;4P#{ c:ޗ0 5c qQv}Y|/\䅹t +ҿ#z)&Vik\F 3i*E i>T+DuglHn*D}v=w7;߻)|zD+8U(oэ ,b~oMMDDF)ϩζqhOc{gR<\/UJ[NZoW`<SW٫iBI[(Ǐģ@X`}%fU}No(:,O솆oV Z0֤R-mk4 95nG"f()eR+TY,EEiՇaBGTIB8CGn˝\ݼտzŤsRn qAo'_~!Pb,{j j.^!é9%UQbs([-V o=F$8z#w*+-v~J B2Iاr{U65pWǢgGYqԢVeq!_A8(Ŀʑ#l O/Z1ZCQd:q7OT0مT,/okpiXRL6s^v@̲\`VS,.J\1e}5GUzMʸT IXĂ\<>D2pz{j|G(Rfۗ,*qjjɎjn[OE( |OprYVKvsWs/l_7ǒ8ֶ@ ƬVK5F Н'Hb(jk+Ts.+M^5or!Ũ㎬q2U/6 .6<2^%tQڛ#R yr-fTd,MTΰ L«n| O޺7pVǹ-|ujd3~\=..%}ɕ.1#^CQ}Ub7%5U\g'/CPybD3E]3cV)>v!(a*8kP\}]ZAfЩK`)F`\EET$0$n {guSд+}_Gz&Zy\XAV73 7`.7)+׿^v$(&K6 ]v^ZQ r**A."vBnkل6 kW!'KzeE$eOLj,.(?s5u~k krc{ v!^ Wu)QmIaztB+l-`zFo_lps""U)y֑j; gä,A&5Pcrd.%Ec` endstream endobj 944 0 obj << /Length1 2234 /Length2 15750 /Length3 0 /Length 17074 /Filter /FlateDecode >> stream xڌp VcgŶmvVlh&ƶf}>w2ϋ"'VR6s0J8ػ330DEYLL LL,pjV5.V<u~Č]? 2nfV33'?<1cw+3<@G.lea?_TfnnNVycWKGFSc[BPY:02zxx0۹088[P<\-*@; ejV.+T]=) P(:6ۀPcptۛehlonleklaҍblbeGƿ|YLhW}bV@ӏ{1{6>AVf0ssdTrrJCGft311qrpN%_ ԼR2%02~q1v\݀~>T/cfYLVp;[yt>֏lQXZTUJߔqгYؙ̬Nv&Q2wL|ѧP7T]ߖuoEnSm7溹~\-_SMߧ+`kuҮ loa6ZHXy͔\M-^=Pꯧ@tej||Tߔf] ; cX>h\?\pM(o `b7Q (1% V`>A| G>"|J QȧG>?#ЙALl?F_c4d(cr.0`'_V>XYz9ZY‡?GͶ@bt7b?|?~Fm}4탨?H?1<(]>Fb?f'i1z8#?G?~x{~S͇7snq=p+k<5Ө}VܞSk: S 5BG'+w<&w-.:nNO&td#CƅTcPҳ{DA ,K,}z^PcOijn0W~Ӓ09jgj?@GIǘ-syr4GQm@cgd'~x#(䜘< !m-9.Ug2yd!eHYT ju|1A v)36.J,m-}P"j) lV#b;#]tdW>(uoverR i(by>B+~{[>bS N&)ChD]Bh]H}_gy8Lz8rˋ)A0gqUȠ༠TcDA_B_M5 q= Q!O@ d9^% !XnQs\g'Ӟxð5u`qi~n|ӡIN\MulA8/hmWs{(\Tay90BuwOH ߜ`BI"7Q5KFZz"˷Ýf= ;SM@s8rOH]y0kbTQyu$[ /ur7"͚ Y (^U7:N]gDVaAd%j4!+SorAA]t2S5@#4@W:-@dԇtkմ8pL#0`!>=Xr^pM‡>ٝhض~svT#EHʃ1 5|(h̅^R{;͎<"c~,zlfdy׹ᘸ5w&#\С,G~ր. ÐqMryrb1r&ׄaۙL7KxB^5lfwʬ%u4܆A$&Є!|BY?du1 メd0w(7v I`B "-fPc!wݡ-881Onn_ vʺWM6bA]%(.|n>D g\Ô:}f &GNïA  ?V`/Uۦὐ"y3ay Yh?Ў"b5AxK*]TzNZhD51.׳m!/F1˥GHtLG0#_7ʼh2b,.FS§k3|=0ІE&܄oB$wW{'ǶWg>렘N PI3]4gZ^ewϭa~rfwRݥW%dQ430SW'_-p@g{ke"of!ɱ&X[Q-ܢd 9u^h {C˥@gerrM _f 0U+Mj_@$i;zޝ+,[Gv fe=vܼHM~?WyGɛ(wm7FBh G?oͤiH{1z4k/ ''tJصw$u3Ӕ䨮b6)+Y\O:x9sȺ@Fb8%*sM/&:S3%_.gV'&(>k%enF-9'`zK4&μ]쑷u; xNJ{YMmv(?NXsh XncBӗnO2џ#]S @Ll2 tm1Z'0! ִG=mwID" ᩾4\782'ЯiWA_hN@vзˣxl<) eTh˩N-!hk|{Bwԍh =ZLX!tY #v]GL=88L+<\1;A%. ͼ=S2* T"O-ZvIqT@d&j/ԇ'ű~B߈YIteݑ ݣ@ R .]XfPh m>}mKHYV;څH9t6.  PMZ4&䍍 J0hi!u&Q`)^1NJqͰ!.򍴋g/D/ӛ+J: XnP/P1[_!y<޼CA zq+dFHdO/0Ko~6ϔ̀m&I^.>ϞAQ#چ#ȖOK9# 11*B~/J4zrsPpC#t,.]mwoc9Cs`/2ܤrЂQ;L% tؙ?|dKz}ђU)3ûtgȏsj5; " ? t<8O&A%>O*X7 U/sէAfTXKߙ4|qZ1_5!*p/~2q?'So'DLxz\|eҾ:#9'jCrwkl&yrR!,*ȻO;!bYݲ0-Mq'梧~\G 'I.ŕֻ%̎\aBͥy7)Ndq s][:i#h; #5&(.p.[+ƸaFt_YH ,8m 1l )$!A] 2e˕%(F;`%1#N5`Ckb=3x0qu̺ͼw)o߳XQjX kPYo;&8OM DF${-j(ȇ7"ܿ -H%{iE,zIN;paZ6Kw< {3V||-0pA6ZnM jjX!\p0\NE{ ki0협 1?3Mڻ4,53?߬mΛ ,qG=,)0ҫh$OŷRnN[yOSȂ.ؤ> L%8.Ȣ|<:;MY z()#3AqE5k;ޒ{ﮬ==5R\rEcdO0@+|j7SC=ud~r;lڧOɂD|C់|A1!w3L,x nJ>fxLnjda`(6XdJ^r<4ygVisAT@ ]s6{ :P6[im~{pGh]hC1@)vRS[2n^i+81x")ƠܤANP]ӥ7R >,UhUkUM)/K3ҏ2k&ÈuSkcMjdrQkEBiBgE;9*iH(q9 s&%@%'@{T*5svfdpz,/A"- `2Qc+cڄe?-4~D?lUtNB:ojC5PYڻud(-j9Rnܦ]A[y`գ7.8+@كwHXڒlvDtf@j,BX<\xДj'.ldcc_ U45=_ƨ 5v˒O6>jTmiqhF|L "{  _oU%)B9IBhj,|/DOB)^>q[r'sp^yFs:<~hG$, yJy6 RtMg6eL q>23ė/w"GFǁbl_iE3aFzIw^IlPɈ QoSn|:Tͧ~yQ;L_n}#dOu"wwZrA9+/ =퍶pLdZuEėGluX%jpc%< cFo7zT{WɁY^iO=I]ваgFCη{* lʭjwuon(i2 EɅXHޠ86.V7I,c2ůvy}36~.<+ڑڈ }E(!ޙGI/HړMv~3͜+80[ل thA C ?`iV߃!Bw(P{\l;p~ quqkt7[ |XvIҋ]E܍K+%Jjl@&uH$Zƛ5|}>RЙAHj|1BM~ b*)r8^~v5$lX\VL<w֋ =:r5J/FwHU) W;@,;r:(T]t~l?A死vgU0ˆlsKFN{50h:|Ԝ>F["ބ'߉^5 n\*ӭU'3V#,ci/A?%t@}#pCZMօV= p{dyc! 8cA/Y5̩PC3|ZEb+]O>:XS!ԌžwkƓJ(B(+uZ;y%qX1@9 :XrJ~R%;D.TtL3X#(J)5a=0iJ|O!8$S Oɪdϵd촥;":9j*"`jgAp۸!fIAD.?hJ\srݞ`68+8ln`=ISһCi !co8L*1?!ѲPKYAXF|#-]>>y} SUFbF==9@~9o"%#7!(3[&~KQ;iI}I;7Tbwɹ=fyȜOf"UmK]ør7~=7ƤaL0$O|80X/p' 6>1׳Q9*IAT^>hBdY;^3eCe 4 5t 2>ܴMvE"Q8Hw|0(:QLInHt{w\ i}&nxi݄^wllj={EjPj K`[.vRoJC+s1%%h"iTxZ!:a<\6V\%b}S_GGM-fFh! f]Y4n8H?^Cb sV{͊G3y-r1z>+H MIh<L0n Ӯ-Tx+sr 'VFB܊n‰}4hcI ];raBiS=e*lӭymoʤḇopZA$6X^FTl YbJ%Iu/Jxa3YJ=0 II3mYof{'>QܽBFܳ^31ԑX=m7R`$M& уUjT(RMe"sPPk~'2oL P%oRN =UuPDU$: y_8$mF>j:jP)B nl|o0'A%S~'̔uF;K=jX]J949a9S!='\YtS9{f⭑4$oڥb6d"Zd. j(.gm^_lg I![!9fCPϴD9NJ$:1Z[w;I2W:r^[4z^Vu_uNZeO_Îs1rIu*& \LeKU{qkN'$ 7~ZT}WN5Z>ts!*#6ghrrH=7Dܲנi&i ]8HVQDɸ$0%9Fnl)^$)Շ&SWeܵ;udEs9M";%Pƶ 4ZZ7US2Yu8]=Jz'WV[hJar'WnaAJEVS}r @k3H5r4}]p f.|\CڃF!_ނuX\lǀ9SJyYfDlu _*U[qEqe#K|#Wۇ4ޔ<6j.3m;D{#RMms9T‰RP4T&rú//aL^4[P[S)srG\#&}zz]R4Q6 TΌ{$d=RŤO s>fA#ˬv&yd( o6L9> wxPjbb(L "o+E"/+^ -&|ЬC=%X<{J ~TrP=FTwKi;Q_yvܐ#DaBa "Vg>AZSw +H$t`eI(uP j< $Wy=;b%E|C (*1A_3s, kKl >Xʋ/X}%8}8ӢF_p,ژk6GOE*ΌgR3r@"8{NQzKsե*qU1Sݪ }%~ PF~E鰧gdآCIWlPיϮ/GѐD1٨8|tkU7e p$33 ݙO?RbUF,3exTx5輡M:\нQ_ZSW}GUW@9Y:;C3o :t~"RXQ5 KA̭mլiX\xpo:"s 2ssf`m4#uڃr%WӻT'YZXP^Ó*],_U3ڶ(0rA(#xoA"!mzYf+b(]yJg$HETg͌(F@cTRZ[&rKbqYmqkcmOԈJXNhȯ|=ޱs@iyrU^2pP_bnR0Ub&uYK],5yT"a/ZJ-`~똫6 Yo'(^ǽ 9:R%6awzߩџh='WCuaƼri`cÍp-#JAP51dDTm ;59U+B>;*b IPEcq׀4t,ط+3I?w@2q8.x$)b$48+r'CG)-ms0U%! ɘʵ(uvDײ2 w M8ˢ7gbp\ f WZ\>ޟFU6F]nP_o-;>~ot}f$jJS*Nc˔ ls~ur.3pb` ;>tpz0crE[ꓧ*4K0 #l5/F!WZP|?3=al-iO#af <:y g%` "`sڞ*2ihs M_WC..CZx½}j^co"?lI`at {ڀ&ǚC96AnЏG 'N۝kfuckmJ#Y#o/?ˏb ֤]m j~-iK)ӎV;+W|eae<}xktk*U@j U f{-jgWNX{<|D N<"f_%-ȏYØeuf1ؕi2͊e̪ c#|2EJhYE'nV>JzYoN.I;[* y}ޠ亴>#]E\ _q5ՆVH3{#%yl!a ru21*ӌfYRzЩ ,C 7Iݸ[|Fwl‰y xo>%_^x\{ v3$Y?;(*ܨ|IH@eUwZ9k"^c .Ze]/XSF7uQ,D3ژ.iMv]srl,Q*G2Ia.1_ E6׀ 97VWr\nkNtV)0bnob#M©}c8!D0ۮ\- UILah- z#_rZ&yyXr*6Eju|?,U6c/O= e@~ʪy3$vm_OtL:猾d!CU';%Сrp{Fxz}(H endstream endobj 946 0 obj << /Length1 1361 /Length2 6023 /Length3 0 /Length 6963 /Filter /FlateDecode >> stream xڍw4 (A;hѣED7`fF'轋=j%j"J-Z$BRok{gﳟ]k8Y {*{ Ą@ Q'1 kpB(~P0kS8H EA Ȼ@7 j"PSᇄ99k qHKK *C00F;Cݱ':݀F:/M ,p`qί'V_滪TRa/κ65z]~^|%okLLz:!Jܙ6IO8Dzx۶\:ɹQBoW|/cewg[n7z:P(~h{\R9S >qa>M'|aBMvcˉNW 썼/Bnkx\RrTDCm'O`/{VTP u2]»PؔL>1M/an_l|p,CVCU%V5RiU@vdtJU>qz@A69& ==-mbgy= -ۍ5:X jNݺiO(ɋ>2g1s:  1N5Qs^?_/> #{sW>DP*x,XR4rrI< [g>߮egjfoi=!Gi2vHWc nX,ҭW_nTĿKU7I^oj¼r~ ϓ D|?Cb?R$ov ¸ghzv=V(ܚPA {ioHo7 $+[mOg :]" d>SAy|?) s{| !Xwu<͵ʕg;\3'G9 h^#>~kI~HI94Fpdh-78˄վqnih\Aཛb=Jm )fܱFOTp*F`bvr׌5B&Xnhy*^jL&mʆ{(>f"93bEqz~+e2c @w@8{LN!&z]2.3 +@'ڼz9y(i,9cHկu6F|&t8#$. >O&L̘ $mGZZ@sd}_>?mB"!*-8*]bvA,(e\Nw#T"VV^`nZtKTr^A!oCevbA@qOާсwVFĤg%JlWz0MfO[Nf8žT:Önee&H`+WJ`Z ]/ҷRg;UHp[B, H$1}~x0j%Sޗ{og6#×+(%I=Q5loflPRHhRW/w_}*ªJx`F6A筕cUmE^$qig}ezf,q 4o VnmCō5 Lf_%9@ kIW*.^H'Ǽ)R]cʖ@QSqdf]PqԷq֩ 7Iӧrp8]z#m؏ [OND_HZqn#M67X>3o Y&UqݸAAV4} KL?Ժp]|Y/t'~Wη~P 8(>;@?@gwEay`X#Ja_s;&Bu#4?LfQ;`@-մI񣯭JEVMY_VPhz;l祱Wз2M›o`EbHԶHO 'ۗg-uj?dOc¼pK|;U jʳHwX2)cI͉6܃ScEE4%I|G Qì_,<VeͰ/6O6~oև%U 5]p ^nR;}nsZ0il`~cjy_v 㩵?Xe$z o^eU[~yoi*H/"ג?6.d46lh:DMfIZ'5 S?zn"} ڡ.1 MDJi 僜H^Enߺ#Y#jJW//"!씩N%~__toT*DդZ>wyP00NzZ~YMwJSTd Oj;”&CKK5/\(֏^ IڠN}4`^NZu](}IX{OJ)?Dต>>Zrǹ!#V)ҘB]TA|H{@WH=NOYBί5Y'滸a!nKn2i"JBMFF7G0VhVsnds=%N ecO?kNX] ! Z.*5լPmuc6heUdP|).ZBgv:-滑HrV4u0]:=Wsԭ cJJuR}I`{ eF[ZH&O_g#h&$!0piUQi[S([; q&)JTG5C$D9?`: WAXVg~KÙAg;cX Sx)t~A/3YƲ߲}aC&?5tԖ 2ʦz,&:7-1rk#792^ m]Y O*uL}jou790fav6PԸԗ~8~ߧ= &ǢdAp&|Dܛs$dn>&S<[`'X)31wHʖy,K3H(㦜9KFO:̃ Yy"\xf7ܮ?%ĉ qv .>M̜ &Y|@eu |AWV`suzbT&xowee #%'Ӧylbm^1EYi8BsH?I"K I{qpkT'=UZ)no:Љ!=HLE%N/5j;!]v4|B"N 1Ÿ:ūbbޜHT2wq*q!zO0kha}q`/s;8-ǔFP9YkRRR +o:35?qL/i`aʓ XK&Qv˵b'ebٵ.3,V -ilaj =]Y6b&~RS(2f0"~ĩr+K~*s2|sv(["J<ǟM-})v(1jq&$YL;Ui b6kP|Řs*[ u#6"izc zLJ0 g[p`,C"GfT Ǵ+NnQC *;Jiұk2uY~B"3 !i@N/H Sf>UL8#g<;4`yrY9^' l>~+2oD!9w\UL# sN[6!b|1h  મ9qOɆǗ1q OL# fzr tB}K:Q-*oJ* #j^ fm說"{Mx &-5ݒRGNvU<we |Lܟ1Hmt}Ƌ.TzG.hjiΟH~fljtzԴC'i|ehTTu;Ɵf#3QNzɭ}ʽrҟ:66B:jvrLF$)JT cRI5 G~LN)"[ҧkr4 zx}z#櫦.u^yt _,xDzȘ+* 1%-R^?lvqaòՌȓdpEbY=Ւ@V" _%1P6Vٝs`G찮y2ۨ9 N֛D2j{5NbbS)%.hUW<-."ZQb|>w+4QTvIH"t=2Mg8 ,2kY6r@IE(6Rܻ6n$L\#m|C%9OZ:Pr5(8/+Rna^/03w>ht>hv-m endstream endobj 948 0 obj << /Length1 1957 /Length2 8772 /Length3 0 /Length 9919 /Filter /FlateDecode >> stream xڍTMJH 5Hwwww 50 ] (Httw k=Ϛf9{~TZ,V , .R`ssjaiuAPW0I7)({Ia*' +'dg#**EV _%C@/9@[*@-aGK@ b A/l 9 yxx]Y!PQff  +ϒ@GЯXiڶ`׿%Advh)(ԜAN;+ u8VR vK 8:N6k& 1NV?=vZ<8: +>T>WK( vY#0,d%qt9\' ,݋;A<|;YY,͙M R`Bfx8xy Җ^Π OC ~>gC ?5n ?+tـ`Y~(`~7Fq@E[|W7]6ōK=ңGγdq(lf[cWMKNq)0ɻ#_̘a6gtn INPoMgy 5fɡ8r?w,rp@_9q{FT!G]ρS4{8^B؈FzgMS/BAzjK8hD/ ^B-yͮ'!ZS ?JюX˰bW"erHrOC{C̠^QI!I.ԏdvFSS5*vjssCBL:#u4"Hf_b׼5v($De͜ *x}W,-tn+|x]4[N+wLwonH37+(ԻUn/ZxΊ~ wk}Y|Op@d8D6|@&)=xQifDQЧSͽXd,ꏼjHŤEllڦ1䀍_4Q.w?9 ˢǧ^ e cV8N]E*ͦmunH%B<{#iO%e{,7{uׁZr!h*q9Guwz9s;&VC$-AUdxo1- Fb W~i| ,sRzӥD#UDmbvA=ܥPvoR}ȅeo\:>і7n Q#(+{ƥlv;%Rm }9rWVDݥcNup&)Sd&C3(ߏ s12*(EZtm k X\j٥;PW[/Dݫ#y]x Gu:/.{QŒ>[yV3ۭ8/R˷D|#Je|1H/Qtj}=|dR#YJ; _n41TO A{YS#ҋS:uݡZ ;jNJၨM۞aGDTBV_ -{O/pR|bm/EB`SIL'֩ c?+(<275j/t#BSakē?hSH|dHn8daa:͸MXYSgZžJ>R0KXZB0BZ.\QƳ%!(/+QNm9ֈ SHlLE Zg2Wml~RҹtD3g̲\2a˝:i jf3X~w&(!5ZEw]H3a>hRLbͅvDZjv|ܶ̓uClD9 BrRuNgO9DBȇtIk֥6ʢ o+JVz}qu_D|zF /D7kpcRgK82 tW^Իt|IK0/:>qĠ 9uʞM%&ZQSg9n7bKiY;A%DUd=UFևv<IM-z1:nm0&fqe'3ys3ZDŽ`O O:HS 6nC{EiqmԣSh/ jPEjT=pWF~\Dn%ҷ}]?+m:4TĒǺί^8~;cB[M %z"^;^4Qf3[>urҘE FA& ?nYrZ@c;| y~ce{kd@04܉U`cj N3ۛ,+Wbkc 95%[;hZ'̔/d3tijNn ̑Y]yt |{ϐlqVXZ- vMO2"IGWڄj'=ɦk3~X.x!?;1LgCf2On:>C!ZĀ13nqX \YPtL:=elVԶ!dEYrI6y"ap0.94m/CPCKzYz`{ea}&'g^բ褠8 an)$Yr5?Zo2<0nG$( ʂևaȼ !2(B58P91zMͪQ)F<3ɂhrq|˰ "я aV7(V5ŢT?U81˲YmGMS= HDq@Wu5Zu]ڋ B6)N$/gՊt\(Ǜƶ=_M$^w6B#hw^`cTӝZ'\DrFwBa|ޛ4K]bod~տl2[) u`n&iNbu(ɽ5K7R4/Y bj(]!Y_]o[5˫?r|h3{"hougUSe/ s{ߐ( F^U YZ-L/pFtj"꒬ݒ[ {5)]NUF,5 8{D˽8"E>4`"偎n63 3mPǿd=1Q{:3V:΁?渺R%Vo- oLndF'g|jn"/k%{!N"f)Q)3s?5(Gve~.;hq:#'/B57j[wSb:q%o_qy B%8Ho^afIAԶ4G.1ͱRx}iډ舌JT)&>vػFv\UD+[rXn!.xF !A8yҺ 8ճ0 bB K)GjїN2K46b3K쥧<pO|Jdhsp!xaJD Ročks"`*-Y:-Iy_Sޏr4h?}2kc8<= LJ+I|-l8^z?>ab}a}b W<F]r ` QF)7wu]` ޜF,c?mnRV|0SpOf,7A幀݉+ZYmV\ֽȽ8Y\6YM0_9.p行fHYy'}R>:[dy0f4?* 8F<t×.$SN,~DܹF"w,i:5VZj ^[#p#yl m0UTQZHZ0eֈֲk9t?]M"pcѢDN"D4ŶW+)_5TLlȓY97Jq>ӻ9X#řor.KщQh7@H>/48nH䅬vr~렵=g0[*eXxE0!SpE;BɅՈ]rE׀1ip'ڹp'+Xtc/XUZ5)#D`٠i/|eS&)VߍY^H)D3H&P]mL zM_Wouv!Fo[p1}\߯Cw~{R{F/I=ԩxi'-4b;; vCڧ㪘UmWYY;u+UO==ȞT#mH`hIpl@Nx?FF:+χvdHv2f3FY~"8;K|{~ H>Rf+3هuGK[ڽQ%6UtuPߦ+ajacS%.!!kpTn@{oZ iL; *}%N- ן5Hm0g԰[Q m.%/u.:I /i8`[`"Sic fMT벀lf\jKt)$\)#+)(ѷ8#(h֒=F$#[B?E/̟mxWv/fD-g ZmL6i0e)ȏQ ~LtU|(fQ)V2/UB#EF;QQar;_hr?U=dJxfA ZtQ[a 2#0k)d0+դ2ĺDk߮$UQu+~n?_\5}U&LFp[\J J}[&W,w\8*v=Oj"Ǿ'Dy| :ɡN6dc1`T璛gޣt-*`eWxwښL3ō'Rv,5juDoDAzG]m)c?>u㟵Cne/LJ?3ٳAdhO5 _7ʸ Z'nnEZݿpO0, .id.Tk>.b S{?3 YƠn.$i1S ;+e}Τp'aHw.41Ηr8SɡCwެ{ ۗ L$Aۏ̭d[<hɡ*ufb7exC !z֍*0Tp{3bH.mֱm!"^XÄc*ÅapحiHS\ r`gJz-~9G ߚƨP &L 0D£>GhxF8(*4 W4L߉X$3r C^,~') |k|q:T8HaMF2­y~6B =ϔsz'C]l+|hosҗմj̈́Im!\^BOpS+*I_HԈ$ǨqvV3I?K endstream endobj 950 0 obj << /Length1 1521 /Length2 7639 /Length3 0 /Length 8662 /Filter /FlateDecode >> stream xڍT-L"h) ZK%$)nŽCqb""E{Gz[Y+93gs0ҾᐶZNHnN@V]]y9@FF]Q G@N"A{yT:T^;y"܂"@ @.+:'@F0Bap-~fK iG0b r`-A%tbH$LՕm$X-@]Vߔ G_8qğ5K>嵓Є `u8nNd%rr8!`' 9Y| : 3!o\,DO[ޟ;_kuu۲8YYaƥq~ V sgFBB3fi{]w o=oOX?p< 0  [8+%`8[ Ӿ8 `7^aVP'\1"_ @<~ '_xw _}G5~]_b{*)2]Hᵃq?8^c]uFqv47'O?q[ -m͟~q" 1Y@1!~\o|?T݇% @p8-~'ZP7 OsXC8/ZR?L~`1\{Zp!l-qBrse9m83SPK@ji Wa1<X^18̌Y2 v2YǞku4|g4 641Swg&=)Qr dE(/"%lzu|we")QT j.P#l]?!{mjqXFX%Nkr[1j__ؒ41azn9ƶL)3^ >M'N*(TU+WRlIx%?5]&X'z ;}iE\ځ, >3mb +}Y <0huƻ07Ai+#]vi>\l׸kkۀ5qړL%Z7V~V|y3"W.$oYDfJL}'c 7gYpl:P[=cVw @)j~5ȇsC\]>ÀG2q}?%\_n}W2Cws!j'[S`HK enҶ@?޲+hiA~W1wzT7g'͚nn3UVS\Cj-<=@Y)ыYasNޯ˘"a(8J^J[IeC1kM,ů%%/ Q\qLU>\6L~~Sñfbe}_-.ƋXUzÕح{G^_/D4|F84f˄PeAfA)YTC0'0h?o\hn9T85JJ\uWsU kܡn_*&괧78=lWi$dzY%O[+.Y0V;U6j2cts$QQbk4PiYysI 풝ki If3#U{!?TCrCGl?7+oShI?5q)aǐ~+{<%=ѻ쿪e>m_/&v;G1V]9BM٤J[ %Lj:,cnq@y;} LcݚD}1~wU/F; }J.) ڎI^[<@AYbCfjVuE-Gal`!J{pVQ*3Wf`:HB=K/+R^~~75/)፾7ET_j{}n<|9/}#(1s#ݪCCn-Շ s4v\V_K^Շ?':DpQ{wwWz%@aQfc%~6Vlp{E$ ͮ¯o˵)YTMd5~>@lfyw#d^Rrȭb{BWVQtlxusz&[V8\CIg}eR~~ly| c0QX.b#! @F7{T?f}F~w}Vh6}Uz [b@-#pC1TG )N0mJ> /WR'fJ GЦ@A&H]|ΪX?sȏ>[׵ɿV'Cݮ3oO'3d1UEVWQiP`|TUk>2P$JI9bhnB[tnAaa9sӃh"1Gq&g*vkwKvhkhn D-Tx4`K\V?qz,AΥ3%J2%j4x?g BäBT2Vҡ֒hE -+nWXpb5׺h4}88>B!EF0pۧ&C+nK ~ڲV`bY ڤF59qz!GGwtljqك7 ;EL>5¸p{7dKm[R.?Q_ʈ 3>:w4 wLulapclH.)owCrVT8\"^O C<ē/ZHU 7)qF'uW]U3˧]!.󦈦j_v[~C9 0 /BVOոqhCRPiBCFƷ;4Y($"4Qj [{1"fmܰ%Wi K{㮸ⳋE~xmR&V쪐ddu9Q(F'r 3 ,"@wI^,= ԇhTԅHK1mK~ BJrE>uM<>e"elW1;!vAh>OzJR 7 ;kSኟpdR!6la:qR5A=tԟZb.WKKZ5(}p dV79l]*adRov7-H\m.bÖv}sΗXr0a^=3 MyREu-c5`%nGzI.:aOBBԌ; gl>gD,wn7c2 |'C2-D󤑦V庩Ce]ԝ:+2r,H thگouE)K8ϥay@pQ3WwI(WgUF =C WSX1:c._V@Z,fQ !kǪc|n nqn?/^6RU$1 }3=(28 4hc;BOVk5[a/>Rg10 ؖ X)+hP AU}'u;iSGCD(7 V"ڄ,4/eS"\ocK fλ_>x٧0Uu(6/S~ȢBma[ph0K_aڶmz)s`"|ʟ4YKqsx~:@*h("m!mX-J7UyAK"#5fgzF䡃[;81WO[.دu?0Dτl[f90-*,thl1cʉD1>$ ә%_IRv[^LyLݘw[`/X)9]-NLԴ{lMR`NYh[YJr|>?AQcrm' .=$Y%[79 9/x jf^.rtO^ wq>P̓2b9q4﫴{ͤt/oz8.XrL xAK 5m:Y`$ 3~ҥΟ_B/VGSȱr?5$42hga܂͛͆bf&dr6SA[@2b2>ozoG/t<#m? N0rlٙ6)XŰ^pET `T>P*9;jq[Ѐ/izsƮk `T?%kU)1ֺAm3!^9"(Or_Yg@c@-PqP PTKDZwLWnֽz-.eFl/hƥʛo :W4J@1\#8 ;wGQz| P6iWXĎKSYCoShBy7bsqsX߯Nam^? a EIo?o9 k * ΃/S)FJ84F$VKlf74"j&5zeq/I9*$cqlg~T@?y$cTJ3.h#%}B[hU4Кa7A£WUw9S{$ND6sfMya 1O2MZ/zme$O/sQcK-Nh""?חV=cf^CJ '+K^2Z!1&.>9(#>K)aQ+9V&].}8MGoĞC!@KSӓ;F8*p#Sɼ(! ܟt({xqJhʷWV;/=zKkxvfӅ:w:iO?Ɯ[6'čoP cjgI%eF꒚tuw.=LhlXc izjVdBd <ŕM7XwyJ3fRKeg[aNDoQ̆ P0c|d("fSH\2Wv1)w%3G]ǕEV>0 ?5!JYR!{)XauvG>Ey{((5Cg`"k4L$p'r=0qY>PøR"-io9Y+Z[>Њ'l $~j0]- wȐ}YT9F,Fb]z=@ eZ}md\}Ç]]P|ӑ~cY}kJqPG|ҤN=.} XHd+֞kܦpIRL/}r! ]r\.ff*\)B}}t-{^ _#:%n0\q K흊m2hj֭7DŽF5 [Mg@EbӤkPl endstream endobj 952 0 obj << /Length1 2135 /Length2 16945 /Length3 0 /Length 18249 /Filter /FlateDecode >> stream xڌP \wwwnA;ww ܃+9*նݽ 'VT61JX;330D䤘YLL LL, GK@{5?"D큆61C@9k%`abO=@dcHXEmlAf#ʘIw:@ h26:O46nSіŅʁLr4(@_VKc@ P1ut1 c{ ~:@EJ` WnW!Ɇ6Vn k3)PeptuZhh`ol44z!@BX `A  ˿42U&6VV@kG}wc~q2Y%ɖQdw̻ `gd@WcsƿPud`.2Ap0t흀^t/B`fF@35Ÿfۃ\:L `翟'OWYI\]^ߒqxг2YؙL,^[GoLrMm3Q!PT&]y;ߘUu/# 'K˿T Z,>Nk g 7TݕW}G_vhr46ˮY6,&{2ψler__TCca!_;bx0 02X85{Lmhv_! `bELF?('_G*AF? 3SޙiA'hq ޗh_.b/?*Nhπwf./9u?8w_ Yޏjhk@, L᝷՟Cфxg6D/@FC {ßw t㝯?R;IyghOCw.y;#Enp;ۿo߯f=fmy-AkЏ9Olīdf,ʔI,0K[tȋg]ylpc'7#2}qO2$BSl H /hb֫BI{ FtP|{gMI,&>cр56eULVnX| _BK5z2j?igs`GTq|16j式GihF袘*ry MgغϤa m|XٻIB=LV$}ۛB&ׇ-?tPezW\x DXTk3hgs{h\>5I2'f\&uFhm;S74h m|kOVҿ!. Xd="]ߤ-sw-~h:Gw2]Y^ɍ57!^j2!ԖJ_sda 7pɏij&,=a__Ka& ].a7WƯpuF5lS+`}w'\h9Ϻ{k.|FFu*Nj~uDti=۽ c %v7 *4xwn û:>#G$)B8sVAS C5vLՕw ע}^Čߘ~fܨVP~e.Gcu,#ē˲'Jɹ~k~iϪ++ VB.$a%Yj1?ӓ)Yf0=*ul)Zk}y r"5(hk!Ģ;݆nvcå凿8_. wј?oש^~3O7y?k& _ * q9eFljI sE`Qӗ,2^˜١zA^CfvB=FR6t5R[YZB,D~b.!?kn (iVVoWt[G%{`p8J|&q\fpHgFٗ $!nDs]kXnJܢ0w4y,J]3c?luPZ>3* C[UkMx,BK-`JBĐEb_ +zB2MVFWHoē8謖 U:'hu{ʿ_u9Pع r5n7 QTTZnJ3 Uf],OQ|Sef#GQjD=ka =*Ug'9lU-5XAiiqɯ+W^@1]\g}y5>T9'OU{J(." ; W)MBkIN=Т,JfBQG R!}"Ϙ dzdEc )ֱo"c]Uh=xr%}t.Og5ڂ/TS(PJT Q [&ymjο*E$"v4l Jiocwu8:y4Id'&vj]3O8X'tw3;0U|˿?vbX~T@$~ۋ,4i OPs#"Y(TǷAO:M@pR Ip8R υ$GK6?B#8c$NY> ?'^*ˇ;ِkY̆뿬d^2#!IgT(Q~\ F8H5|43-r5@8wdĽ TS ˵R_| %Wח?ٟH9'ղ{sRMxCŵ"Ydr }ɶI;X&+!/F }EC7;2^bΨO68MQH'WwyְdI؅OYy~1_4i!WH.c^x=:`|v8!S0gȲ`kX`=l+C?Aȯ+q=ZР 9"%9(ĩ./Q:*xx+V݀ ڍ~ww{xu>[ @AOHD<-9Y" #1јY' G-uXwRtB%h#+4[oAWR:2D@W u); |mepQu"ns!^ٌʶSxُsԏ3`i-^`.w} 4팟ӍHD{#E̩^H Y`K#Tsɸ+6iffqapN^nz‹(˴y £OyAC o &u]B9j'Lռ7qTk;C}KMF2S8qޕ_XF}[#Z;V % 'xCwtAජ~mI@Ģ.eK6ȇl 5p9k d~gTFRAGHAKvoy1$B3ާ"v$,7D}I?Pwj0+4J|c0Ȗfor珒ݍbB8oa5hpmv;ykb?ׂ{'ip Nx&0Pj8H'6o]gbO<Ȭi'[|w\OJa7)΋ucq` q38jqzC)$WoQkTO}d6?.*ڵ#ڏ/ =cȚLT{2Do=Gޔّx%_~6.$r1"|F ~@t$}/ mފ_|]?Oٕ8UYuטͿWsIOu#=Wh+s+E&]!IrYtX~'&.[vlpf<nSsg/nh*I_LrG Nۇ$+籐.B;M @8G(ӃLI9x*uP)7}xoY?a8U4е6Q+eofq̋r2W>-zp`ƶ>kSBZ)`*Z)cZ8YBBHD4HٯkgmRna]A歌nEDt M቉ BA+w +dB1”J2-G [\j$"x…^"j^ߑ9;}>+C|QXx]<2B8DeI#Y"h61q0FPapw7)BTDC` ˠC$@B3-5x'U6gVoD3JPIP#6g'QAGGpd:/{E=o vYiwʃ7V1A4; 7wO/֎ڼ*GhSȌ]`3u+]!;<=HJ{w+]= þFS|}cѹqa8FbGgݽ.K&;^h\ ?ARaw#ia.;' > DNoW0c")◧{A͏A>jXU9K=Pr0R,*g9nOz#DMo0@`ŌY+[^D}z?NFtA]1:N]¤ YJofXF`hSǕaq "Lί{I4dHu+é%5S3oge54y)M'!oX@Wx ˶~`+K";(q2^!m 6Ot5{ׂc,<7)Et@'(嵂c-nvϷMJ \ϳ D+0)Hp%oy%j-!eo`ܹKMa^=uohB=_B|"d:L@&YSrjA 0zv5[,Dt]?l=I+<`8RRS~^]JO<}F- 7Bb:R:^ Soh7lM2X"ԒWJJ@,'‡r6@R$ xN6񉵷'`iouQ?P6/2V_yLiޕ VP/ě$u>IFS:6^\Wȫ9-n3<4SZuh6[ :-V.ux2ki+k=:[J8O[*Ah޽LW|<⹼ zQ׏;#RHVܸϏ?o$Vxyii(Z 84 R'ީZFC' 0Gr8ڐcP@SVwGR'(1~,)S{~4o*-Aݮ "0YI_i-' h9 Ri8 k ֗27l.ckxlpY QRby(v.:sewSxCgtrҜ$vd_X(lbbx&p~|*8[ǽB5r ߶I0v 1~2־"Iά W8̪\m(Tϵ҇!ƥHLk_Ig*I7<@̨Cźr&k*#ܗT̨aɎ,jK+>K2Dx#¶90sཎ0bWC܄,,G [ۑW_8e dQԪh15($0Ex<3%RNazvjqrbtc̏#f#Z~PCބ7Ѫ5iJ'nciQZʔ}- v[N+ rFg>/\Ge)J fJ'ab"_AHn$xQTt8N nSeL-GMh ػ C^t6RZRnQG zŶ3xݬ՘y#UEmi˧.WzQ:^q6+#~Fu[iDr(4p8_{?܊u Fҝ:!ojf̰+_h^k]_q" ;ԄybIyCܮK4'ȸxJQx֓5HGL6@DMTnԷ3m=b7rrix7sIIILe 5x%ea-E5sT- +c̞}k#2 ~Ɔ 0e$|b0(՗*v']ΜKAq1N E`u+H*4(L³_'-cB)ΊRSZ<8rIjb_N[3Sv ^ܤSKJYZV0qZ];~a41AvӴYMoA!B ⓐ!A)8drHK<f|T|LU5 Jb'4WSSsk9cqulIi&:1$g|L9^/ZKbQ:S#ȓ<ULr/I&i e-}gݩŀ'lIsK"[ u=zW;a䚕77ƞQWy` ׏6>uW鐣'n jRrB; ezxa4"40ͫ7P@ebaKՎGT/ک]x,NcJޯMEеz67[$͓nnLEڨ,3x}x9e>;4DtrzNu]؝3)7fοXS.Bn;w,% ~^f$"+@E]8|;o=W2DI>_kYZVFrEY~-(J/ E.&JyYSTnM!)hwk]\g ,xA :ۗ%`y*djb6l k;J#b4&~7”M~+SoOJ$)`QZ5J;[1::s kW~f!5%bGj}ppܐm<쥈?롈 fPF̢qjvt1r_$J*YBޥܑ%|tH*(!]N7)Uypmс|\80+zZ.O,fR#r )px:{nf^gamq8b$j8Ez M&Xaī3$n!#$p)9atਠrr) i%]!Vy^c~R ;A9HRB(򠰓04OwV.p!H ƃG- #Mp?ߺ?SqS#Ą՞EEJ.3Vv|=Hn|jUXtppX"wKa=r$8*~;% 5P֎ gil2ztk1"L?u&j2eXl NRfQYiMn"J!uûsdy#VF,3V`a2LHٗ䠴Kf;Xo$3 e ذ"õ1Bq]3h|kv3xZKBi]m[FQlKz,}|‡3$SH IZ䲚$UBpr܎ZO}b+ !vGx Rw"WY>ۇM.'ݧ&NߘY)Mc\ks#a,t$spغh]K6eBPMܡgfs"FOJh#eF5oThAz֐.~(,J1ML?3_ɮw[oBUd7?t:)b э,.h< [>4 3:} I[L"U +1@ u ȏs(;@FepI?G(Ǫ> _g7h^5~L8ZH8;w"̲q)`aRȕO#y쉆c#%-)N$R}ȕ@[Pc%Bmn*Ur$5,(I d52–a ~XsaؔNቌC~i˩߿ Lu?m¥ܘUP!&0J0Ž/׸R&.6{u.{V%"fge܄'qi>HM!"by؄V32/N]_~4*\=m]#:D]h2Fl8Bil@t_p~,8QZFdvTS14diרJXDoy66~KLZ~}?NȬF5zT7x5YM riEo[ L9^;kew'(GLIߖLdm7b Uc{EXҺ}10yXdǦdvdR봌$yd:A*?vbpM*[b=&4Up4=D;CR(ӈuX5a&Bl`6[>5(%zq&P,M{;,dErm?@ɋvrY}pzD$IZN*Da)l8PLm0ƳƂ!Þ'5A>Mh9 xse`t~UYitH^EZ$52V퉩kV7(6NAysXJ"B(ኒ0T?%nd-U 7;ق y?Dc8(@>>1Jk鐚f54r}w1PެB|hU ]lᎼNš{0֫E?af31f%ְ] *\֎ !?XL#Q`5bO4b"we5;.)F@LU:4W0Lر8}Cy4B]ED釙Ez;CI8 1u.m+Yq6nhu |h0˭~Co:]-uBn ͨ?f^/Jr앨7Fг*6J?FFaJ-fv 쨊$4aKZHS g_.I 𤨷i~z'P qBYR&Br%K0cӄXNg` bZg~#awWHL)iiUu!yO&R^R_k*5RoB ?`uM¼4h% |U'X+9vXS":^TYMSĻ@ᑆ~*\NFv Ҏ\ )ɇ7S;,ݽ@J9Z ~XnΧ hV/Sr= "X/zj[JDZ CosJvt^ې,=st2(ߊVm|JIg;l,{Z&ڗ!谙j$iʜ7,271td5NPR{(? |{ 5&VNWUȇA4;Z}3+A2CzAT#.;\ߐٸ.*;!8T&9.>dSK#oP5w'HuBCf'Blk~(>TAbL) :I 'P< wgfzHƧYx B͊=$Nqq)cQXR>|t"|&EmnN֮r_vm"z { FF3Y.[\BG [vIе9$0Oy L~nZbd.R IRs/X˜کnFe^,';H,iHBoDyp<[[Y'B6a cJ2v& +}WU^} M_B8˱CWUr6(/-VNK<(?w57"yl4 N#:v[LKפ8NhBs~c ة;Rg/C>5K]0`:&9_j`y=jE|;z阹$l|'?xx4W)8bWt/2D_J[A.),!rۂ`ZL~Q_ rGtQTlCG,s@woa@rL)@5{%tA n򒜃"xYeJ`0T:ӓ0|PwhQï-Ѩ'xpt\tOғy~5'TG*pUߛc3^C&e?f` OYA "=> DG67`>*$u]c\WFH d8A Q4l5! dwO!/4fޭCRI͍ S܈Ǎ\zON$3 Zҏǵ<|h(0(vT(Ԯ£o0Em#ɞF 84&{'A%o\=A'eȉ(#RQ"L(ctw%A!x? 1`Odew٫1v&Ȟ8"qF endstream endobj 886 0 obj << /Type /ObjStm /N 100 /First 905 /Length 5338 /Filter /FlateDecode >> stream x\[s~ׯǤ4߲TNx\6-1YRԉ~,9Ty@ ݍaܠ`RoOCz !!4$ Q1 1gbCJ 0drh3Cр+i(`˨dTD]&iӃd f|]vV?XRrFFnv ?889g`0,]s`Mz\"{̐MFKwDlǻH { `QpbeP / &bhz>P)D 3hS 3Fe4C֢!D0-ق2nQG!AhZ#w2v e8dtC$_4[PT@:$XlqfK-P[0 $hMfX Pt)8m@Qs%5Sz]g ]d(bF8Aa ALR8 Y5Q #&BPR ob!x Xppށϗ|q<]q.)un@Pp2l2FWE5:=jmL|4qῡ"3X_ 6iG3#D{Ph1@w~ ayC1 bXX` vaA$: pPv5&o'X aB܂}2?_ %QO,[_]Qb~j34 zLCL>=_^(<|r 50Zro̓rB gﹽ^AkoWNwr9;¥/# +]Ya0F=^"/XPY_,K,`!ňC kbgMG73bJ]S Cx 9xPR>ԇ2.tuLMk%(\x/`jӧ3sϗ>̎/go|?.id`3@c7k\"|2[>1jz"&3ߣ gK1oң#G7Ն{ރ7ۂick;/FZ b+1\kdL#UI$Yy`qBFF }0dH"\4V)D*:b&6">*5bHwePrmv NQhl3 "8BX ;ʀ5׃$A 9QԒhL>ïX@zsMXd)0k)bY ;j"JF hO(ԅ 'Y /Yxוtx AX!R:6ȽQ>9IY-Ztš5{F.AL/܂OⰍ>k e?)7Z>Y#rQWNC'?<JpQ$0rq =h=<${xYgJuO%>q )?=RM )ğxT U%כ IQGMA4ӕtL)*S'ܔ$˵'GQUvWˎ+8]mdm_ "k^)e~c߄mOƢa+Vwc UKEU.# 7#jҹRyjmWna7^H ~zwܓ-QqWtuO?wQGm]]v% (^/ ԋ[E_s'Ō7/_Wrk9TA%igo''kɔ?-.O'xo͎rh~<;??rKK617jz4bzN~*jbT5iJuYfǾ5$`pr<[*ĴN .PuC>=n[-Y>)kK| SmO򸱸G ҉O|}֖=3 +LuGP(=1ޯoJ&n[ܗ-FR_afc[7T >FX Fk -2 :W0 I܌xNx+%$Y5x:_a1aHb'8E2xY…$YBҦ jr5_f׃tzFfj_o;&qrdjjV"EQ[qN@| QBy_VB#b\:"e-){:y%:`K`XK*%oW9̿ZXWyc)\ex*ES "^['FKxo?D. N* * )Ԗ0D$F`u})kޠ{81T@ hp@e\'u5UW${ZNR$͙۞6a\D)޶J -GV{ mYBKVvl5& ZX[kNշ7'~G+&mRέ*[̖mmT[KR^}Z;zStIn*"G }v{-Yg)vjW6QU}'qM#[-[WKf\8rrVD+/p5_i&U~ "lxy4ZFnXKm坤kڷbB\kM_ײ:,:a`2/ޅdH)+GIF>huv}\p>-raђNqBJQV݃ w'P#. ЫjY1 ٛ;[U 7fݴx?V_q[2I/%U7՞}6c-嫜um\S"5`߼>YIЖGO``Wř^TZ=;S&Ò|8.lLZK~RT.H[ 5sk7"[~~g@(VRvhzy],֠_>z?>8\C)c 9G7@>\[NfGOΦ }i'꥚#u4?NԱ3uNL\|R-t1osu~S-Pl~.ή./g'ݦ*Nm?C_7O&?dխVzwVz^J+ml~/ ,-;ۈ3F,&Gx[VKbbtM.7|9=YLΎggF1v{{3~9u=lۉZLgG3l' چ_)\7mF"^tI!kdo'P3 jv'Wlk]wE-әNmpN7Js6mjsrvq*t6ӳ:Q'd9]V@o:7P!ZW֚Ӭu%H$݌rkWw endstream endobj 954 0 obj << /Length1 1385 /Length2 6093 /Length3 0 /Length 7039 /Filter /FlateDecode >> stream xڍt4oY[EĬY3-1"vR{Q*VRRZjyysok_}}>7 e(P- %D@qJ>>SWMgG{"PH堆C0x:G!:^$-@Qh9:tPH'%pt @2¿*np4 A!'" 0AAp?R(8a0rbb>>>7OQQIP8p7503(% Gor@p^ኀ‘/$ Lw?z;_(A!+p' H/G'  \!xߝC`#? OZVCxUAHR(G#|0C2f@ Q_o > /_PO65x: $;q)iQWHl(3Ph_keb.t B__orp($ *\rBGdeXt7R\d}rLƸbInΌ.< wn7P8a?s-Wxݑ>a}r1IFir%C諯)n{LhtM%ܠщh1h{y#֒1ɾtEꀙ?->l9&* 뺮B)eOՅk!:ޡ+_yaiKU^9/̢ פSc4mLٯVҴlsCfLLZuBJHuJ8qi҅ϮTGrx8?{V$9j%"jQ{M{ :n+ִћaq76F?j|caKU(S?f u;'ֲ2A:O'p^ SADn P>ޘ`䭢ecR 6|mL 1J7pɅK(FnNs.M AMQfRGABvҍ`x[ 쫹" *@Fڅ*D!mD)j0^W;FOpʇO0-"XQ*ވh#^ǓkQdZ)Ie$Tf!سwy<7WҟxZKllsAګ"Iނ)fo@Sh{$X+Փ$*ֻ{^!o[DqɭlKc$wdӵyع&o%Q$>daȕ< >fTWIK0*m`}#+B''/ԽMJ-CП}*ZsPh=!<(>:D)$H^LnxS ~UV  )!g8"n%ByȠ=;(%Ѩ-v,A׈0;cfK/^lP:]D=_y7cΗE""StU[$5' PgPBC+R1$׿D􊝷&HJ l}y/Gh#4[YkjGĴdq_uh,iן '$ٜi%Qx~̮Z鍮X3i+ɱSгNPkeӐۨZkȦ@&tXP,8w[?Pa>@f!#TydO|.ZߺWqeƼN|WO̳J z]LCՌGcԂX]$77 /Ze'uP֚ xoazf+q8́4ORԭcmQaCO V+%w{ҭ3./\YѠh%9I[C~Hǝm˵YĻ Dۣ|ёJqLs&6uPU/),O]t&s%i93qԅ@ϣ )aC*H66+v2ӢzO*_ra^WRy^H<`7eMbܫsZ#|W\ 6_^:K^IJ8%DZ~hV >8\jQ{}j¾G΁~G/ÁYmXzT/7A} ;z}y[Vyϴ0\V)UOxjRjM-찶0j,ku@gCL!\~깾FWm1(t vu̟3(NsS+4H6iJyþ=jQ5c+<6sZMm*F. Y +XeHb =mPCp( ({KEhSڑxX-V gYF GK>]a ݻͦ(: xMu uHp~,͋ n3޽GhlH^o?|ӌ=6prvr,][lM Q8&K/kpO.G24Z=>%NKSQԘ+#2&\EXuFL:?ĿyvBFiOj 8!ՔKx]h{v&Ds.50cBo/+o3GQ;<qjD%JKϗh r21sJ~%qf}aqҖf\RK4ITOD~)% g$龩ܺxoO~\ќJTfosSj 6wdau.>ĭZ`J?(UH膗cWIi=g~vSG}ƾu2eфqqvOe~Xeoq5K~ ] 1ڷh$e!b꿸kdN]YXgib m?٫wwhLӬ6qIR|]ًWYv  gER&%(][ke2os6&sb+ kk]:)u[?{"0_\줹W5yM: {'MMes_&/և6CMMLbT[`dǾ$A$?OΧVW9Z^4\)L8 ۸ϥ}/'\7L~Bi#IՃR[ld&`Z M*ksóZizDn lmFל=R'ts?Rv8|9Oglk5OҋRܿ|bbM9+Tvq9-AI|WsR= jv2k5xBt[F^hhTT)<=Sؘ{i(bT@Z2}Wvhw92W픂w LY>ײ o.a Yy$18&9S;m5NI;Zm^-Bz''ӻET6h.]~ӭ`jC>FlU۳7nt32]VZPRuyVV`` z%O)O2@2\A<#J]z)D:83Qn[+Uӏoo?r+bi;hS%3ʻO $taN'Jƍ{O]P=cs1ulUK~Զ>\S$ZBll dc0N K)MH&iD )w6>- 6DZ,QuF,ZEkQ(靲8Kqzs? jY&:w]@[6͇WVC6޾CPO{k?{bLf\74y,+u|੯~k$>s)+v4:=j|ʒ#s&/_&^#XY#bL.#4H6?<-%SI,m>5um7>޶_BaU 6llXk}Urlj4p32~yx}þܞv>Wt ;F7ֹHd˜$rA ڪN̢WTW fL=NJW@\k 6XYiS$E'J+Ҷ^U!1#t+xٴ 2__{qS ]ª&jܢEsmt a5ﱮL+ GJ9NĚ *5I#9\~qG9DQڸ׿g󽦐5r !T endstream endobj 957 0 obj << /Length1 1623 /Length2 9050 /Length3 0 /Length 10132 /Filter /FlateDecode >> stream xڍP.C>&Kapf58$$-Hp.ݳս5Ut?owO5Q`7Bd`PS(,/fiZ# t'5 */`2A(#4yhfN8;w Г @N28Z?}JvKm𿓩d 2r}N^N?g'Yo*߂dp3M"<ʰ=/5ϭU[;Wa P5y9y[e 毬fVjO]B^ֿ_.Q{X03ۇALۆc>wP389=jqC@v( |hsf> o =o a9@A> Ϳ ?_!r@92r8 ۙEx87zH0q?Y aha 7`~f&d1Z•mkD0Jm -=aX&31iUpZ=ܬy^s\f黙 xKJIτ^QE=zin٭As.ޕD_!Y$CRPǺ:ѴOs!{>&p#R?,Y%FQZ;зEuD^]=TPDC9Z 'xHcfN%/[/#P 9{eȒ_Kہ yՁM;춱& _AqJ3=69.fW3NFYp[VwIJKR@TO)+e F .6Kv{sFڰuH.߫J,mJ⍷gef6ȅgf ,xVt?yyEd#&YnB4}iY$=vΦk%O`4刄4Q!4;$ykQI%eWN]Ҳ&:۸(`#ڊ4m6-FSAHFC~r&\ _"}y>Ynڋ}l͕|64n,ӢǟrOO,B穜( y߻`HH䢍8^tF(ܠˏi $2>RX` EnWjl@X^6>㦿d;s.ߥ=ݣ^L%tyy'Iu$fokZVd[i1vlH@| Ւ_O Uz7姆sQ Kb%m4?$2H-s%{_XuWb_4sLW"`љ3dH205'3 ![s h'P}WQ0"(p`>SwwJ|^P;r(B"yR$,CiNEOy(0]㦞omHѣ/DY;ԯ[F2=ئo(]ʨk[PƟHjmJ.|6$}F Hf]GseCrk\ HeR 9Ӟ֎D 3S%uw|U{g2J|R&A }`^G}G/Jo ҅ϼYAu#Gz;Ӯ%KW3{WHxd4 Ja;9N$Waیf!'" 9\^`/ϵ@[,(Ych8;+PyP)*vtgwgQd+vglږߜ""#׆Wk.£-;0?ޗAOyxb@?ڊ+G|BNQ,.2SvJjw߰b!TS k9:GGDh$PJ ϮwrX|75weB§H݃/U?{n]⩎1z9la2쪇(P#暓{P$p?3xz5yV$JHg_Y[?N+ǧ\'X;"?BPҙGůQmƃbb+&B> })LsRslq7&3fjU0_I(@Ձ!ۨ@vtܙ}-%n-%sS*ȢC*A5WH(b/n}3`UI7gST똉(ẫxVYt'n." -IGH I['/Io9M@HYOB[ ~[3!J#;K(bO֪I-: zOL?S뉼&]v#ZCP*N y0ZO M88nO۽ ' ÏY*Y?p& /JzK#M[a17SɏP}^!\eZh>6tE1y:||vKcNBZϓ0PJ`& >}ε&ټmmᯢ {pu zxn5z3{6T{BËXx`+:D&lŊXd徣lR_$h)<e>7HI稡 qnܭ9a$sWZ4:{ C패1b8PZP.\NA'FmE̦9ҳEA\Ć|컖OQ:y'9Ȉ]t/u77V>QL.>8F8b}np9"@e,N1UA$ڰce183>FSBG9Q`p$5x4SzVd>n׺SI_h-TƢftcn7G\}_j _iu.b9rSӡo.ixFnnB(| aC)^Q3 65`<зi̋T6;#Ve RdkX',J<<T-G@䳲s"ٴx gOr4΋:N'>qٺjd&!9>ƙh&džxMSZwR;rDl'Ú_h0j= z߱b4HI"U]bbGC>ytL]q7_niq˽$e:eYT:Ho /^;}=s'~-T e+.Z0Q7Y?2U{􆌴S*Pt:2{W@- Erf,T:3i:f9<ةCU'm}T8 5X6C) kP۱~S &[5K}"5qeǴx7ftڹɼ߲Cp}Y j6x\f(J?XWMzҜcz!Bn^R X}!ˊsξu`G7&i{2OKjN`w;n-r?@U0OB'gyoOhwT 4n{#Yήnz^+4lէ7ϻKs+4?au裯U}<-..Hw"V3V45i>&:zܢ"7e{;47.t9\ O-hJ֫(NfՁ/]˙8j:< X?y۴X  -pH ~ie.2#M2{*BkK:krׯoDxhsia=K8KWol Fq a7NTzNٳh,Yk7~)>d 9* 2la?omYIШӶNJe+Tft]IkNH35!lX^m]6KM61){%D7KVdgBYDLAVqntW"{eok,wVKSi7^̒?t]PƀB.elv绎2;l.8e#jK71M1y@x :/bftm]o{0y G7DmG@FE]#@l CZHJhT{m*1\ (g>}cNb(wkdDnUgwZ Լ11ȺV/9ɮ%T+ǃw"B-u-iEa/m+6J#rHj S`qs_:=ccغ5W+jX<yiwx+xN )WZ"z/zd(ŅtO֚\=cN<"F}%h |uWez|zaTx7[سc>xp+jtĺ(@,FOb}τ3q .{),l:aYKqޔNTЪ6ߕ'a=X6I{k֣~Õ'ؓKEQɊ _o 4k_rң~])j鲗#LA-+̢ H\r 8P8[KvCl{iZۊ\c,~+Ey?Fӣ(Cc45O9X>6B $ANߜ@* T6qbsB')EJ#Lk*2GwM`*QTjU~SM~||}OtFv,]zS+MwD%^!'9RTGצ*m;=Dl\L?#+ &KHG )v.LW{JhKgF8 d!&+ :KI^^߽6[)k6= G}sU3EL-юs!>i82Sps-7 [#!Xߓ~QW#y@*i96Ї$^=ni58|:X e"qձDŽ7U4*֞bǓ2{aʌ-%pzŮ>6|Ib`H;OXOHNL1L`O hg /M|;Ý#Na8TKy5 W mIr' iSOcޙGDjOjRPkpz͛r>x!fSfPztXF<@n=Us:GxoAb$z|5 *{v#05e~?#$yNtʠg,C1 G@؉L+IE|]\d?}+Yg}T.I\)T[UNYMc5`nڽ? /$,ҷ"n ZZ섇<\_[͠:_X9W<N+Re wI]Uq2Iy.ZRukd8+lh(vU!ۿ`l4e V$;*JM^kPMjJ n̩q/!"KT#U$&a  *mODGJgO+f-5f{>9]>[N2_p RЇ]Gf!nj5]i:bI5n'u%,qE|Pajw[hM_($'tW^m/6}Kt- ADyF9ycXG8V,X({}u k|MU{3w1H;:̴MG󨸌 >2^fs8,蛺Q$DMT@_mq﯄Y"y4Lt"ypbFIٸ`ՌUQ=kVk =4#[9NEtZ"n{?_,~I<w(mSebTfq# ޥڽHs RPj0clT/{޽}(lcB%N2;nmWGss%Z6 [zXܱeEz0i 3騨+l>*o_ԕp"{OR5$Ԋ(wwJDӶ:M UD7Ҷ5\Mlm\fcctvYo0Dzicq[&΅,<ɀ WsE#6/:+c77SE1/x8tޜ @mr?:N4=GML(]qjaS9pNf. ċ=Q=sev&T;vƸM}:.W&HfIjI3&ǿZp $덫h4wgn L}'$̓cCigbl\ $ޯݦ̃ۧQ,b+.7-s@(}~u2FcLX 43 endstream endobj 959 0 obj << /Length1 2166 /Length2 18306 /Length3 0 /Length 19607 /Filter /FlateDecode >> stream xڌP\.wwww84. A;܂\ט2|s~$UQg5@L,|qE5V ; %?bJ-5ȁ/qgLNsXXXXl,,crH[r  8-<hh Df&EW+[D3;:(h\]=< հU}bƷư{FEv2j>;jB6vrAQŸ]8af[uKtf~Pei4.+#'$z+\,=gkll.mxxq чƩ|pf} z< H1UYE:u~woe &؊^RiLUdHj!|naD;0kuXX?x!n`OH[҃NJ/bi/( uM(a+IO>F> {N!lh3ֵ)Y;t.W_IggxVhr$?PL2U`3Ix(mpΝRpJwnVG{1hUh6}S%:dޫ  1۸)+3iݟUxGkp|jU jsQN(ݜ7%?s'A|n+|`(.W;o0'QЉ-Pu] ۏ;0wє;F-uZ/bwk`Û>vE]~ΨHX*UAMiS=գ!(1rvL#@~ uB=L|͆wIL`k2A6]pU p'Y9ex[U3  l`82SѮ6 hI,t"61~$aK搆2}GF[{ECb!.7&Z F ;yXX4Ae$sԬgBǹz8okHU`dw$ qؿV .Ui~.}l%;tb?۷4~یwpv]2˵Jd3zFУ%~"\I_>-dAffT@͓dljT|rtŪ'&(ᘔ%]7}Oq|?vHUkc}eTڹ 4zDu?aEK4+ n[XI!*8p_xP0N~ʔ+=죎ۢzn}RzRF$B*McA[Q)wiOͯ_2dO{0N=ʉ_g̰kTd#US`(>([ͰlC!oӁv#K QšwiliA0f&V_c[^˲ܚf(0gI$Բ{l**ΎIw^փ/m G+/#Bihj~ZR[c9 Z):UCǮ.hi Yڢע5yUk7{y]z}`s|Yqw>eLow Tb$bŎ/b>9e-k}+1]VgE^S>˥CA{ZZs9(Lʙk </N'N?ߛp|n8i \ ms]̣Pc`+vf2 J# rKCܕ$Z#y)d_E, 츼?H)\o +GX˥ڭݞ^je a2[[Ofw#vyLg#x1]e{6l,Uf 92qϱz$Л\KOVC?RH=t*}=ɟv0o$@8tQο^`Rx Wi9B0f7F(F)K[!@t q~ZK%jTHkc-$K 8;{ ,.7+ \&{HD`,B è8\ O5@\6_~7#ηGH\k1O wǽ-+nRqo/'Ip'e߫JFZ^}8)\5 S =~_&|A.v9s.O6Ysǚ8rlƩt[(-$|Y yQkIsU≥ʀ*W3/]GFkI`UIk]޿oq]Х&fH&fbQnNŢ*՝EX GfRㄓC *mҭU բ]!Wfiy]s92wFWGKApȭOJЌ IF+؆Faq5=1ˋ}\9vն/T#%Ru_0I6㾦3?:5a5ߖ+wLϲ!4,#XN%  g388m)t8bg& "ǼYTX;lÈer _h'k(J_ŹĊ Un5~uwv^`jIuݓNMnOfSHV? ð/'ѬPT?(X2n;xpUtKy>@aGp-#ܥ !ftK|}칔!06SU3jT`OXE.b|K;͸N8wS LD]*Q9 ƃ!LҤB˫y|3ӌ4 :1tTe:jb`a&tI"Z.8MM]nWgCYJ^RHeWhH?F.b7sP~,#PÖ.R[ q>5~k?̀O5)dL 0oVq+"_4sY4fp'4n~XN]s=1p䓐 #Y|*8hg4gul iQwxTD|| ̩˟y`:E5vǚO"U9kO$4-T(~&^2F} y{wmPeF}- np=\6${-欌җ ѐgz5ôgw^{&tay;'\x\$5UnFiw5󬅏QS/k68>}gp7zs߱(䵢 z(79U73h,=lڒuv:-f ^Ev+Xh?=@u~O$q}yVr/ꊆtXvѤ4jcWoN}W>/KCDbò?V9Do37$ J&BQlYqEh&-FNwFY]zD }ʏHfS{ N3Mr #F"OIfuxC xX_( |y̋D9ծMv@Re'2zP.1#8U_#䩼81dWKp`S'8gՁb%S0֕L"MfKa/nR):^ct}7:l_8eRsf/R ǜɚǗHqqcBG=)2_2!+D}$> 0>+v^ݻl>\Y䕤Cn>?cH%c:X'm$[TFi[%#܃>YD&ݓC/ .xMC_C"nB+Hɮp(m} . VK!s}Su'T UT7j{rPW$[ zjZ֞✈ bsln:pXKBn7QoH9GOo"9/.Aŏ:lYf|$V#EAp*?@ ,6WjcvHY* ,4zN䬵ԁiYv4 .No7gLU15uIw%v 16a c9S]WDXueMVk30A- RuV1Ls?5 h ա<+Kb_U""~VxwLݜ7O#ιsHy։*-9KQ$%twF良__hN{Fg ]smK*rh>?ZexVK8jfAm%F ߚ yACVS/bAz A$AR;fWu#N6oX_^t:FD_E}sQ(i̙:"[ ^O'tfG O06=i/Xm/xCr $a_V|IGH~JS [Zl# ^X6\Դ$.'LΙ{[%p04?(Q0w{XQaJpEe Ãm\vCtAx*Õ!]]K8?&$OdTquHK*}Ȣ@38gPm߁2\':Kc^1Jf`}@uqR7T1<ThVpT-PR+U?k+s2W&W#V.S. ̈́51Ck#clGdM'YH%KR&ڮAwVs^u2C[ C(VzTY`Y//jANi|rzsx"֏}׺к̅2Sfu}V AP0ȯdDGGm;%sdQatBOVm1@O/tn&4Jb{9(DEP#OPQZk26+|=<>xWG>mᝉ!4v';5ʘuI'1Ax0D\;]ѫZ80xmsN|#d_K;aL}>$;î_չ;>!W)tr'|ef?'"Nv5W[P2W$&`|1 58">.7d,ot{+v?!dVtR8q'2jF 9UCkXd/R!X̾^S&ϭ"P$c2 P#[ah4 r,1nr*i,;eϫS-6,^ӗ9rn-"(Xz3y?5-.+["h:kzyuY_7DN>%'YHI$+YݑEE ^޹W_ֹCu+.I:Ef#9L:,„ A_3'qۢk}ڞlpB)6Orsy!lWؒ?y4GJ;\50'b~ѯ@)e|R&Y;DOdC~w]czᢺ<ĝ^HѹQ(R7rQFܪKyLtJK2uCl>:B\b uu[>hטJ$ޒҁQ/AYnA/t$FyF|S vRwj~4>wBېݭс>&p)z02صu=|+%R/*>;$loh-Y#Mp@zHU=[3lz`eYCRJ;D>I_dg&I%dCVҍϟm̿NHsJCDUqw$R@a<Ժ7Xz AUT&tH 83-J-Qx4#48aP(N~QIBmMsZ S|r 3TRk0A" {zEG9UI.S*G %AHE#%W]@4YpE]m'ƒlTZA#_-+cOp{[>͖(!)+o_,|Nֲ_ ́V,ێi%bp0JqW#* h-Nc [FbtX+7&c&U|*E{T捴9ȮU!}j8Ic3 &R?K~/G*%gߐۻ3\ 7wg zv-:r$ 8/O!!!s]J&$.#[oGG3ڄ1gR]@Q/r_ H a=)(%84HkBmE*5d7P~!`@_Qe5"U>\d}-AWEΩ3#{̵X:NAٵY|O$'m' i'sf`?eQ!J*@i!, 7F20a2ڳ`.FOtUP$ ͍f%⯑.ֻyd}6qS(5.tI(`4"2m&_7 of DP+w-CHvf1Lta(!nD+ ҉_BWz.^,[@ l*t'$T];sC8IJ8cL% ^Ƃ}'MT^U&H8!PAU/^~~L:aP#~\7YN~xC9ܧ௝*GcQIBh{,䕠jkE5*tDKH>v~)% \`8m–G) /`ݳ&[.ra~{%`bܞy[̵w;VWޥ]7S$/ŽptdXE"wD+acrÍ!aj8eRT6D*.Cï}ïr&4))˻..S< JWﮗ)@CVnaɥC/nR֡=HB/>ß!l P}{bpG0x9(`ڲ[$mèqM;>vÑl+j0O 5O B)Kp+$d1L˪[^*9`+K8Z<*[U\v{һR}s0¬.R'G+ٷT@im#/tQcFޠ0$X 77ԍ-u1O:G+`>{t&̃߱HpY.:1; ¥}`B?-xxشO&nЪ5>LDzC⛾mLo:a>bN"AlvY.r4Z[޻f&> ƧtTlHOAHY ']>2 G6k4*MSe@HM踪kr7L 501tiGGo.5Cfj[< d'=Fe-&D i@4*|ntzqr kθ;, yZ:plW'į-z1* 'c#Fy5jaś!>dFɆ*lFYx~Bz!4j̄4Tի"gԭQF/O _cX/=V +=Fe8}i:*ܯK!g>'s_if膠h,8E~z kVϐ'Zda_e_r1'="90oӊ B }V5dYt0NPlQBg{ c% b)2-4;A;[ T0f eM8yIc O*|~OƷ2Aƍ"Iz<f%+6I7+O ;i)g}8Pμ }WNsRX`\2_k9wzk Sn'Clc5Nz>![c?9̀ SSTv:Q1n04eBGS)-dbιd3Wb}prhvlIR :vB?!@J v NQ -&$5讝lz=^h^(hQ}\dڒ+Q0 r7Yg]HM ? ekᓖ2Ԧ;Ua"D:QzL*cB+9u{dZcGxX*1gü(;p-"i8LGv`aH-r>Rҽ"X=fhgn&MЋ؛}d܅֑lR.Gqz#-HV8Zo¥f^G5jd5r.4UփJoܱ>u{bf^G;i8lHu9TFնTL1Y0Fw"AKXO؁1_Pω/ P@f;eMtcm*~׸.BqQޭAK4ѸHn؛H>7V+QɇV!."&w/%Ab |?= !C?e6Wߴ Z^F7z(q}4G?@PY'ϖO;"ohP%'`NiB:xw!)l{4~;i:弦bNٙytZP~£PeQ=:MŹdKĞĥ[␉^_=MnwGF=-ZuKqSv~DRw/cbUjd pvK[{\FН_GZ{e&N7>琭0M ߌ#[@iW-(K:FTڈS),J~M*&ec y>|óN8VޭB(V8Zпw}zP%<t]zo>sh)V\e[qzg$Op@"Tn"6J_D֎Osz]7 Ӥ&},\#ķeU##bla8f} @,};:ɮ ~R+U KCcUYK 6#|(i{ e%c>K)b"Iм5+RJI~F(|d!Kڌ}7ym2#z;r$s1]-: PtD1Q4z! (95&'vx*#bLedmZA9/qS.m HvD}hW[8Vz(`A$gBo+-ATvC.ݥv hgROn:םʵ{дԹS$wOVpg*F6v_K7_6śa?)A{T( A+FzҝԱ~GØ~nkH]ɲk<`<̢ߍy$%) ?^8NQ/38InSEdB,U4cD@=FjZv9B LmRH{ ()|_6&f_u pp.8/=s[+m7;p, DML^R¤Xn^+QmjBבLzeG<Ԭce'`EU$Ig"w=??N.|x"] U)lEOyߑ#TOLǵfOEwK?޳?+@骆=YzE WTyZAݴ m a-5[#W~Ң ٛтb i1OIlJ֮bvcz=pDPLӧHT@M!&bPS*QHW5B G~V )_=+u>8\ڋ3ەC(]woF8#!t7FƢa ~ׯZxI;k ^1Ӻ{EH.ӚL~rؑ"4VP谴&'ܒ[/*- l,FzyK=ߘ~A_ǀ-.ԕ$٢j[@)CXB CLC!KS=An!`qVuMYwwb2ȬL2c0Fׇg'b-kBS zwH $fT;_if\=-_^8h?F~}@|ňZeDIPaxg8k}bEƺgx|g 4n'!|WVC7tK(2_lW5>Ekel.XPW״c愸rSQh N؏Gբ[;=݋s 85s/ $AF (?H!β=6BDquhZX#gr%" 蹴X >x[ُ߸Z[3;Q=j- /׫ KiZQX%ʺM4=FC>3  l4y&YC?:G!/wbZt=+2aԜM@{R$ *+Z~iVBPLsܑQCBnv׽l*D| $k)c@1#iL}"q{3:C)%#!/nX@*L'i)P/ih&շ7-(ya&Ty4G~p^ѶzKmż\v!v> FQѫ2DqNpOmXbyN|Jyi$ٜ֓ x/;F P.1vPHFv܊> waR@p&*"+\˪eD ʠ~kc=,oTӬunFw;ng^o<_wn#M۠Duʆ" [U[4nYjƕ FT4ry+Y2d[Sݷĸ%ϹJz9}/ >w)M.Ri F0냬TMLmf}QN,3 dp  M?򪌰H6 sD!ƭ# sDpNUֵxno.!")"7(s)4>0 bn1aO42*xRQհ|= u,ހ?xqއ/'!bA)Uƒ~VbHOOv(铜 s<gC|[Lu!? z_S+|RBb,UMvR2&U^=(VWqC.b: 8AIi~sې[TM'Lijε\G~8MU saD]?iͦkB3OHl/הZt fhl7bbJ:akN~K|v~dT5a:|-GW8,—a&:0<b~f92G2 ?r;N F`GtňBatF*&w)ȀMl,u_|D|V1Zu TA?|3N!0SqƸ@U-pML.s1bGH*yEm8 Zݝ/T$~nn"+ZԩcR' NW3l6sr n,xД':d~׵hEeq},:.L-Γ/cء:=<f8'!^:|-@!|X3+;1(PBa }`艏~^NJ>!Iu-*1SVbۢ2^vGԫ Є{cxCW C^qVwPjwY0A¬+wόUъǎExk44Pk9Go/4GxÏõ0dnؘdYT:DY D=LT_EO.&O>:c)ZfP+zKD|̍Y ;L^Hv7rjs[V/(+uNBL<c3!ɟ֩6 d~|oÜQZh1 ҘWi%R7NBvtf#T]g;uJE.ԗ~r4\7`~GZk|0}uU"EU;}v~e{ߖDCr͟!k1V)3^mf^)" lL>ѫl@ lTIJM>+$C` Է.rr6̑nn$<"ǛB&1D&?.?RBDOa\g99psf|'-nOfV(%b(ͻ\cB6WH7p.ztE @RSM=cOlhJ!(0GuRޜ^8dpխnVs8n jB(@nMU VIuə9ꏎ9Zu FBNmSf-z? #gOu$B%[Ʉj},s=%az?XaI!xiQ H_ĺ<ؑ<3u Fh*UtSG yչPw[!s n_Ʀ;{%,WbRPz!u\1= 3/p~ӆa}~^/{zS";jQb l»g@I}o@l=B=yg" l⅞iz@KA8)oVp8(V(Õ[߭ȥw F"NNIQDy 9c!?]b4i~F~a%O2?iu,yi˻1ȷ.D#w ]|$Hbyٻ<[O&j)Da00!Cy<(Xf ͑ϹET*Q.rmk5թI - m+竇tP*zU3%y}kI(EG!x<13jFɟr3,_ ?4ZyM%;) "럽rdė1f N6'lڴ*䩣a*?ɜ>dP ;C/c y,OCέ Kcy7U.ֲ !>? k.htۿx_M iQQ,%78đ 1lכJ. Fԋq; {p6GWA@z6Ag_Z9k&VxHȔŝd!Wv۪Z( iJ UG({f-tts!>xWɸ.sS;sDlq:d$`6^%yD?XYVBT^˖o,v!T۷ָF̓&uJNh.p]Q6DE+ tӌ2 VdB\W&$/Ĉiq%wf 84dQW7!"nsXJK1iom4mB1*0tξ;brAG5fc ы*}M=|m_x/˚B4p[V0X@^c|g~VǚgW;^f5B3t(nVLiqz,|}/r'Ϟ*^]K1ƛw]҅S+2K2e7Vq35#ﰵa<V eַTU ״\`Wq D@m(U\lǺQܳ];<;^'.{:CǼ{[Ƽ^Xbx=;jD\ZׇD(;7 7J恋]]rWUD"6\u 2y}<0:|H|it/,LOAĀǮ+O$(K:K!`4b!C1Sv+!rг(H05+5w$^:mh` -`*9@%10G^n# ,7Ϗև? fC)\ ˦i60+ȧP?ح ]vp x\ʌ!(=;Q- =fCLL(~֕:vV`(&QlNwqRLb鐸ߋvW>g0(>b+s4N(lEu#`?yA76ν~pҨtW T /1wH s2(˽+)7 2yq#+h0}؜j(uNS/рV_Ow#!w4,:7< \RZr-CAR 2a :p?UV]xu8Yj04##A6ۑCveVzR ͂~ cU<;`۬pmо*10h:OɨFAwH8Z\)6ƏA a {dz[LRw&_sTJW +i3``~2@ȶy(P w;ǟ ´e|k2E2_+/S^{fIBFx</=d\(| ,-+p1k1+0i~֊7MűC黵ǹvCN$>vg-M6"j*crSy!45ަ-н)9*$XU[*~bTbR:0%WtE:j,(sf/CBQ^Q@167@}8O}XS?Һ7a%@Ro]&{ROgC<{uk{^3qrnMz6Y@u*\◤鯷Vf(|-SM endstream endobj 961 0 obj << /Length1 2828 /Length2 21333 /Length3 0 /Length 22914 /Filter /FlateDecode >> stream xڌT[5L#]n[p.Pxcs՞k-5)PޕM `cdac@1#Rk]@H8M\6IWp=@ ```cpO@dPb;]%AVcO=tdfbP2qځO43h8^)A'h(bbl)LZԁ.@gw9we;ߝ R4@.5,\=LdwgٛrG0 rd.+ do *Ҋ,L{߁&.|w)8/&i15 s1s9l xRvv@{W$A@3ؽXެ?don s7G7 '7?!` fcc@O3+598:8,M@@/Dw `2s-A/f|g'@ =v2%)jI2>qqO3'f l/rɂ???wZ`t/7`f3RM_nf\Wr *Anv+jb" \ͬ7/-U0/fـ.`E/7s0}8y&&^l`!qps|W,=?3pX~F<V `xA|V`X_;UqXe_'UqX_ sQ|A`.J/E\T_ sQA`./ &޼ p ~A`r:/LN_&y/籃#Mk#% \3u61/ ;o)f"03g69Y؂?fnfrh_Vnt/A`/-E Av0uM\^v; ux) X~C5rs/,^swps4p\XV^V@?"?giOWdGk੾Tڃ? ;'; n .~. ',G3a#/e\8ںtz+?ǂKMR9B\**4L@̬)zd+O]ƇZu1$4\6xvXǣP5& |4oΕ'͏9̂g2$FDoqe>zw 59\Io0'fh|ď|*ʶ'2p{&wRmo/hYbb:eɓ<.y;L]TA*MH pu"ԭpҚ#H hP#K,G6 F6>n`KtZ6zoh{_{F1b_8NAQ wH"16呸 %L.lQE}kx>k6rdMճX]M!_.F),|B]TΤ&fĎ|d]xv䇫3f6A*K q4deT,|Dųxn"#A'rNX҈>QՈE)-{n(<k6 ϯjO:ǧaQ ^z70{P _Hك4!FmV\Y --pxN N;@ !& :ԘG 3צּXS$ub& e/t 2Rt/iGejְ;^=sϒtMF&8Qu*R]RFiИOKS\m5FW{;jzO_{ 4<>ƎVhQx?@0$JS]P/HD+f* ggepA g<2F٩ b,JBP鰕\|lϾc<20?NǞEM,7j={8cVe pÏr Jc Y,_r>uer߿n/o*Cg{ [ą3tR2#]69Gƒ{Mb3#cgz:qL=oW/xf+16y;+mUY̓aj|ݒL_6Pzo"VpXb`ijJ .xUekk_y# r_*J q߻ߜA7f[G;) ]iG %3eS-^WT>1!ɵ %In$2uۏ)ovnxv3%c u5ohկcG g! I!STZX'|+?joGD G5YAK dÕSK%| q+x?//+3(WC]OGs׏ҶZNa6 UzaJ?:cۤ<8W_9׍m4[j+ x՗jɓsĊOK&aƤ\gbͦ.XX#".sz#h:G=kNtxgFHT[8i3=8E93d:2+t&r%J,)QrEC$E!É KEiru/Z!,]ό#{uX<4Tr0O(~NC\[׳h!ADTcDx7 8DMSbyF }2Yh>{΍"b5U~FKZam!ZjJ^l4+[ب߽`.ыVε}V\,#$UqDݳpVX|Ux8-<ѣ0(!E?A"{>üiu((;Esq14}mIǬ(%'+ G,Wh{wcIpZ}i4RRPA"fkYƣ+T0u5J['$'Ma4.Ckytȇ,)ͧ"S<0CBW3~ 'U@<ҏEᩔ(ks]Oe.y~<;h=ԶUa"a2,=LޒayUS6i oIqMȎ FeSq-)**f~iQ7q±Y_:Zt.❁ &.򌞙*9v1Vm ԓ'HQ^ W gc uV]YmG$/jM7Yc:>)e&;)6% B{iN Y{G?S__nM,JC+r8E~ijBneMU`xCB<#XKdx"i{u5!s4a\%=|IB>soƲe'>5i̕yhN4i)Y0l O}]5Utge8PB[NJg*~ /+$J /KeDYHd9ߍy1~$g[[C!f.&RFnjMC|dwmNn'rcv2'<K+[r$.yBg.@ *{+چ\,oDl,+$6>bk:&aL@x۪9ZӂxKPpEЫ?[ۅ9P~,`c8qni[%,.F <oXK '\gur5}Juַﲒ9 6Kh WA)ĥ1ʞr0d&.i(p>(nhLdQ:o٦f]n8Tt&UdսͭKzpkqXRI\j=5jmxظ0bmbЌTV^~ݻ0I4Vn74=bTqԿ T)d(6woZwc͢Cxd0RcTiFg%߬ıˬE>ltIdtԈhj,t@ xK4@Gtxtj,2nkR?S,Flrm8rhC7Խg,0o-6Rh=#LUʌf양ɓkX5&tH9w~q0*uAI?cA+<,ID7 'ė \0l 7/9aFF. Mؼidݩ7I0bӢR.&ʙ}+oԵy̩0~V{ :kNYY~\ _DhNg?{Ϭ)7UꡞV=*]fu~8Ͳ> t;["{vjBNC +Yp\x$ [K5=!fgT=tNC_"D'xJNT:~ҔRs Ć֧B X2gfȒﵵrr3m*}:a%Ǯq| kg2Q %HF_{cU"xyE&}_ѷcwB[.|FU5kޠ| i$>t M`Hi izz f{]gaڿ$AA{qVWbBU\huZMU_ՔoDb0~k9ʻMs7s|al<;Y~M˃OoZ+i':'n#Fko+abo!J>;!ZDv&ΎC:rEdn% =7a bf0S@J::J7|X՟Zz D#ؗK";OCJ )$a)O<"R/$sHڎ+}&646]}\a y=?]V&A~Zb}ٮmX8; ATEoҘ$)T M!Z*jt{6e0zB.BQ\˚ .mL"qyLv*yі& LPǎ7ŹZ\Jl$KBɒմ!,Q>~gCLGy;*zR}.,Üsۋ&5WBm?vgp*9V`*gsIy\s(,҉nW~ a#N'vuXDuaP?[僝UӱxKCA-iI;#}R5IfJ7|*D̈́ہ=>g#6WňuvV]iX+Qhb$׫(*o0M?WP/H)=XS /63ֵ eiz7A\ c @8Tv~]dRH:qC4-}K2_> 7sfw7U Eun|Av~nZgkn҇vڝ-.o9+m~/4 SëWڑ2OZ 5q^'Hi/fo~2 3A] <ϾRO1UnY{x-Fm"& ,u?c٫7o-4\v :`ٺNv~D/XІRh~Jo\BC9 qqMdbZL 5}XVd Fnl<.,)r,"no»&T T{J YEՃbdLu ck@c+ø5ur)ۃRF7 grPYsN%#P ]n:p?v,%{,~DX+ʭ ײxJ)P[m{6O@hS:v34;GK‫*<n0he(%4</!'U0I nA@*Mј]f°'A[%E[-M7I Ԗ?<=gHI31Ija"REhXr?(k3G B_ UScneD:N}\0nFߋ@|!sm1 T|+)Oztln g sa>3s X٭=#3MW`vMx֪BP׵QT|_aMmISRDgQE97hLTzV> Ju*1W}Ae tw- nͽfH-; u"&L[ -w)CzID+~!6v,B7e-#tAs .4s{pKbgC,YjLڍ@$nyHJxd{G 䢓IWg bՃsDLnm0<~"+T'dܹD:@ [E!&7;vb5Up)寵H:(*c!jO?nr]`!&V&vcX0|y3ٌ*䮔u5X^MuTegBi2%!!18'~ *ݴiIiqgՒ1ǖ~϶s,w]D_QF @b vՑlڰB{g!!B2 V'z5թO1l`nYmsŮ&<7o":9]XruQߟ,ks _ ܱ_ep<& =,*m"M,./"J"l&ë9V+6;n4{=78fܜhQϟuM-> -gQBQ eu^5t6 =~~+\|F&Np!rrOB8k$9id7֥HwVK`yZ!4W:p~0'{qu:.O6~2E*kx-R ޱ~.jVeW-"`4&ʼ rׅGoJ'xʐiӲJ91w*5 3J *GKvVKk=zCC9 2 {-t4y) },=YRa$ Ȭ4K0ҎA/Z_EXu5| ȭx@ܮe,?scG)-1@/VGw޷\mT!DbSAbvJMRXBz;Ma|j#Nx|'`S*|,_Za֗\rcúؓ .${p$b( ̏l)tc^D>zž̿&R4~ AϫW&Ky l`(̟˃_œV=qbGkICekٳ3uȂ`YFZsӌ {XazWPj'u\7]w Ƣ0x#e݋͂B5~4H|V. {1#?i/0ZSq2VoaޡHy~S;krP)%R{ & q870ծlnWo!S_ik{T?Ŝξswlk\&?+\\}~m!\L -!+6Suoc VY)wUw%hBNMW_'S j uj?ed4/Dc}l-܄)wRa>h`٩ $Hg BF IG_Sv̢xU |feh@j@![)~ps\ɠ47:KDC<[J~/mW<&'db$-3pvFXFiGWDzRй1Wfq8#Bvd}Z07%X)U2(Hbc!hU;k\J(D{"A@Y cSiw: :Dַt瞻8gPhiCC\Y/Ѓw{gԈ+NN}br[1xqt`2}IQ m8} .\pra\rzpg 9H4&$0t0fw=cjTŷNeOl ( W7Oc6HKDXŻ*W-t=N]oZ _p.Q;B|/X;$S!!;:g:?JS7v'uU`G9VՈ0^+em (Ez9Z'ێT=B`^r׮&I, djh;نr ئ:ƣ $h}ʎw , X5UXûqs4@gGsm ISb2)X0~p.eTښ (vDLZmFDŵ8cGnI2k쭩hvK!9⑨W _ʬsg~ FNuP<؁2$o#¤ dӤ kw$a3*})Gܙ LMyٵBv98/pY`]ߘk >)/%ͳdz$Zn "O٨fqK&"8 b]w)Z7+s3</b8a6x%ʘ]e󰝦q{ʝD2j.~iI5^tn@Di6NdwyQ@;|bV5DZla4RO؈^yHӾx$ZdnLB_iG?Y 8f֨V^CeҼrDв[1QEڥIwAqao<*"u$rSD^r|p5_b%9.=w[0BAO zalxv @"\ y' m[Ul}#AɛFhhZ霆[kb{ GY$+ I+$,x0%Ž#y"|~\`k1<⮘l9s >_`.pdul`_ SN˧F?c!BFQÇGC 2bD0;ą.AvOOF5Tl\*_|HR!F .ʣɹ`uGtkiZ#_/u"̄B2.KNl35V m&yt?הY,Tl+4 j5Cty6pS,N8$TɀS  WUWVct90֬u]]"ՊNyyDz*cҢzHuCt%pPWxUՋޥ 2ϰ4481뭬DLV\}}@Tz2soER[Zl*U`̚Tu`9HW/o(@+z_d|!+;Vݓ% }e*eCcjglJ\gOi(G]V^{PIE YߢXm ƊV;x-=}Foe(x'R~݅S"Vq&y+cClM&pՖV_iTQXUB8VNZh{J֍$𲈼{݆>M,!OY> &NC"A[|7(xR\0>VD{E *}I>r?NCIh Sm[Bz2WYy .si &u>Dž&0Q}aws\ &"`75/:fO{FrSf#䶯*"KFH%s-4S#t"tX/xef|%vSP#ؼ{ɩ" ;_vㆌ緵@ S{8?I>=ߨn܋" l~4֭P\1yc2`iSյ.Gӓr#E#P% q@"RT #xUj2V[[9+&^ؚT {_W,nob-O#B1'C;.߇4;huۣ?R@kQ[}ճEĈk)hIR#ak `u73ðC:^3E?0ˊ 0h8̃QR9wZW;m+a[X3amÆġT/kx%&njɥނ,F74TVϐxjO©K L%Յ(4hnp3ޠ.Z wRzwH3dvݷ}=g<.85?;;x9j=))q^)S@MsOP.|%LoRY-o'2!)%CYYv_[S9?9!z yl, r: uI a'/ڄ2H0#yr`i9;(;!$*,խmgB+G7*3:ٽ*ܜӭ]Gz1oޓ94wZP%yoo^(E6kV ›=`pDq:G7Xqjuǒ ז@5eӹXy4 +_kEg"]FeXlD>_(>fyUNƴH MSWC'I2 4ܭxLT!į.y`z+'-9M*=Y*CB`*,aQYRNfU0mٴca[ G '4%ї@I:=qӂ[v%s:*δp Ӆ@CZ^S̽40*y-0_#iq7u8qυq_cFsk:{iBs"מiwŕOЩ[oè`Z,tq!ETT/D$zHF$ WP^=qN!fDϗVkJ3jA^duki2ؒn^H园 h3 D JT;".Z1bms~S= ?`b-=!4nZ"=]ށ9z5"'-ke*`)ؙTW SvQs eN*Lpw(>i[=x\FwD%X7yUoVgdCdyIBQuVqN$'w8^p }nX uB_3 ‰ѽQ)r ?Î~֬03UQVr,(0*6CP #Ԅ$QfpjBLT %_g Oc*vL:g@tJYu;ǙIQ ze潤QZNE. kk&|Ƭ~qHL z ŬYO14Cs H)-~ #'[Ag ddT(2Oyz`xBy;aV)>_Hgƫk~Fx|"ީםersa懗_`T± v8fJ8{X(8h$ Zdd`*UٔŬn\y 8tяhΤ^Uxt^Dta$ .d%`*vQJ1V%1eTo 'z(p璴Gl c{Ճ{G܆:8! 1kY,1t6S!/dyK1N?㕽0'P#=?db?X21ISXdK(/:a'pK&eMB'y 󹟏/ϖ54]1!q 3$#e_nB>.ٹغ?h+x9U} JXSo*<Z}W=YcWdMq|t*#%3rmq[j6ͬՙ_j^P3;GPmP6XȎP&,x)㯄 )Q2z^Դ$): !8xI(>1hλ<ؐ Hp؅]昰n\R.+Jr 2$'j՚?LrSsr\p9)eNSyʹOxEVkxecLcz2wȵ?lWi>O+rc4M&m۱ػ G`PA[H=P7zژӼZ}SbG&3G8N-X/ hM2vJELz5.ˑGB٦ 4炻Sk[\% ]51fk5Κ&W>7QcXt};t-\rF)+6Ü!&Zb e*Vfdo o"4Fs,;/ @KT;V [7[hw}\c i*ƒYC!!U<(\v .e0ռ"W4/S/Aĩ$\|Bǩ0IdNN9qPG}y]՜uQ@8j.$}yP q.a}i%r9˹wjT~Y|uIz?dѩNg buyf"}I_n)oCaɗԧȭkәlG.W*KwiČwiZ&EIiGHrIA-lrEκe ;[~ZچXSQy6D970*'1/R$&=vv9,kɄ @~]bܥQ3p_.롇] U纼PP)D(恀VA n MU cJk^2jѺYuA;{{6 _ƜMA8S^ݜ֒ ipeĹOR8PD6dϑN iJ+1@b{euTc y09 n vROKФ@&E8-X Rk;ZYcjBon-5M40>T9d%ONgP~6LW.~|RFc=>Ƒs6м.-qm v3, ?'X\PY ~JurCNJatjL=)0aOs͋0.D&C2EϯDMeP@v=Z,):>fvΩZߐ;J_I:ս~P ^!H@ULaӒ4MT¤~y/$:c e()9/WO/7g;ۧ7RP0aG& DewNZ?>ճ-x0pOC/Yw0Ŕ7]'yut2(xe}ՃܷT"u[.?{ql7jd2P?ԅv_і~oS())TأN hSZXS3 # VzPI5IG?iζg&"԰V*ф K&к?VM6@ IA-eM6T@peF+b &5p_X8tBGRT"VwR >!0а^DnrAAJҧ5Aoǔh"O 'j߬)d{}wAsB:cIP99;#N$pһm]$*_KDT^qzMf;{@[ȢEӲ=.h;5cT1(?l)0*>0NOWU{_dž|#/U*P[""2-Iv2${%!F_BìB,эHln[BwX+K4ŤJw* { )2ʹJ6"ش, ^/oi-e4lj/d; lNgL+~o]iR;Kxanx',hĊc%m:;FI单5BUضT*pW֚ՁKwGتUWO;hj 82tI,~ɺ~w+iU^q1E$( Uɇ3`OI7!r,;KT{嘷Z=Boz|@`UĊ'wu* 7Uoy*xGuCS~+3@{Hqo9(Y- bt)QZՔ/dk3cʖ&m>D =OQu g |[;ТƏ'џݶ@4':x&KB^8D/\$)ѸObA'H3JR.U@ Y i;#Ӝ>ɋMDOQJo-9ƌqur1%ϖ}Nб{1lwi>{m[lℿ[z_M\QR[#F_Fv (I4luP1/Կ_]K7?D,_͋0]b4vG^M9O4Bx~Fh1݌NSSݦx:Lu/KM Pd9YKkW!f ` G1:[|D(,|S̐^B6:̩TY^ G} R912A;lU Y& , nL*Ym,%|e+!zmV";q~[(L3>`O<͏@@]uE>XqfٲBB'=~ŷ{N*9 D3#&tN;MK{?FY^qar2_W!唆jB[h2漜PG3,\̌~P #/y0KRÁ)eu Mv^yN<~lزN.qy<[t,<_KmN  f3gΣR\RwHޫ{ۀ? wDe7T 4QyJ8lQb_b|s6\#*Cqb=fjN66rQ7 ] endstream endobj 963 0 obj << /Length1 1447 /Length2 6810 /Length3 0 /Length 7794 /Filter /FlateDecode >> stream xڍT6LHHt3tww0#0CwHHw RHH|soZ̻~w<#]n9; XEp Z P'`f6 `;/\!nu ĭ Pp Dā@?(C\x0(؝Y 88"nofs! (@ ph rl!`BI:"⼼^^^< wA A8`'\60f#f[3 uuځۻjW0._/_ [[+ :!`&v A[' 58,Wup+B_anSw_)B`ۮ{s@`kyUs#B@ PDT vz|Է\%!/_w'{}D"6`jvp`%[v0?濇˫X׀wC0$"GAm_gk1 e,-B@?|4_Q/|=l r8{q}-@g]vE:8D2 Ӆ lPׂ9C`];׃*[G!J%-v @p8țvķv @a[my{((}% x% x"`l.oJ#s30[5a-gUrF&L^s[=.p+B?-|Qb;DTٜߥU;c=rhi e0ՙQnDg^*%C3kz%FqESy6Ӕ 8nZ>M:泦<|<*FEɋIܬR(G0{A}v(dΥ q*|h݆fx4@BB7NrM@=YAڽUb-Օgq4ב?qμhFTB7*W``$Q|LS*[bU~ @sբgA*ö͜Dmj ;z8 ](ޠPuC?t+/[w%hz /P V-kXs 5rtQ-6oh?`ƴq_>ᗱ?tT1lKvPCaц3+y_~[ 9.vfJ'")dgrcA/#6@Bl Q6]eS Y2Edo#`c/rJt o"~IT>yne&y@}`~=<|g9770p:7--7)"~/>c"ˌhD .1Ҙĉ#7oX B0o˨OHFVIuoT CI@RdN`,f!鴝3.~E"K9 GJ<4jˆ BD̄-1DK{(^SǬL͘BU~% ߩPb;c=A;ɽOٮ<6a\fCyj"zozPt>_՗@g~JtH"5"{s*I3 fj|{FwXAЂ3boc *1"ew&4Z(Er>B*.|2!Y[nnF9he~%c#j&ɫТRW$U80h<9/3Q92`.^q}3$P;̮7npqac6z[D'S:jy 4.1O/k[dm\:~s\H2Ϊ_&bR4c\bB}ifq/W|׽=K} x"f4Vֺtk&=Lc7y_%9.pG{~),EN=앲.-cbCh\Э*{p #8_Lz%N23]' *WDBq p]*bOQSWSx]Kط]@ӧxK_w}jIp1FOƁO%1jkqC]1o||'X6-Ƥ^ w,UxwHQG5#U$&JD|]}}ø#tIiϹM;ǗwURSSEK3ijC˹ Lz$OMrm*GÁ7ƻ fRej}yN DBdl•̱H}X]Q,hFuLtӽBKY돫IM}LhA7o]|*|RmE*T_jqrhuUR9I]H;|Zڛ ] uO\w|d7S,Q]!L?Χh t[My Q,j;Bt z{\ƻoD_brLnk֭}S;\ٽ![U6eAb>T¶Sa=h爠7U\e/EUbek;9ORI+m!5&X9ŗMʮ]JV^BrjhvVjVp{t>-P݂(q6RMCg8?ҢV/ybygA}ƙwa]**+,/Cd +ʾz g3il)!@כʫhl-7ɵS8ky|H#_K>Aq1Ny^1M~k}T7Xw;="XY M`9ᅂzZ+ѿs]P;;oVNܑT=l4zpz\ FoAgZثP>@g6Rr8WA'#r$o +,`&$kx[ّMXb.JNcә]Q?=m)LmõA7w(\<_^l1" gC1+l7/kAd-Nͫq Ni+RFZPsy+>VDP2\&FdCqSF4*)4F4aI]Zl=d϶9|L75Ho9d}PW{-ՓΩЬ>'Kdlοlir*|^b<˒ԏB?>"5Lh1Y> ##6L/Y4J$dhEnyt7&,!bs!8h|>$pZL(PSaYm\>h}iKTey"fߤbv Cj)9x 9D6m%4k׋~kwkhSzbAHo;QZk)1#ŢP&tGӪ;a pOXwZ{GI%pGZ)JϕaOA2K.c i=Uw qP=87x [.(2 {t|2!PVm4=f{|4; Z9Ζy]&I)gAeUсN^ә=}I&BᯋA7 < P?VGQ3|ъ=9Z0!?nBZ'u-1?ԕ W]i 'fj-T-E>1fM.atX[1̝}=!fMvIdlz0?.eb z?{+ 8wLrRsm̼!.ȼɄMzZ~u}l֝i?|O=͈,z,okaS"]g)Q\f2_m*z|#;9\ӣi7oEN`l[u$F%Rdp)zUuaQ9oz?'M71V(UړSko[k՗ʄr>D7z8-m/. 2ѡ͌/xPx:-9.拔[lk%Vq %KA}mHosrՠй'5jP9/?gH* xD_|ԻKzCNVDSu9r9T(.0EkO!mMN^U}|Hؤ'~ #ut@gkKZjr_-Tin$,ۉ)~Q;Dp Dqg %`<LA? K>6hm=94 %'[ 9~`/]-;N182RwC$BИgBu^UVGJf(p.8"nTlQZ$I1jKEkv rodt9cO$a)ސ}![n* ʊelBwZ4+{'8tgkC:bTP_77/K7DǷ4X߽z)]jpxϘ1C\zJ>M}ݖN8ͫ jn# W2Bt?VDž 73}\(ZO~8u,7#ј"xt G|c|+ꜫYTo.,=O7F֒Z-iC|ii'`Ҁw"xe5Zxdxw-$ H jVڭK RAyXOЗL2go0вY?U@-鉌{('+ӵU-~ '~RGse?X}yAc0{6DJ)<}{BC c y哀[!H"bi-AʓkCKPc7Qqfkb"e N6֋* PÉu{$ 'BE4seBUUQr.U4Sr$IS漑uJ~M]8g&{S(ÓNtqG>5-nr 5@@]PΫZdAI#|RHf;c|Tr"#ܸ.5|@1峹C!x&cU7pᨵY7|y#,2\&Tqr+~zUhqF ˚]K?IsST(3eQ66$Ȥ.ZOmʺ\ǧȸSY#m2ߴ@ԄtBQQ]r^8(rQ| 380TaM{XDSz7)]mdiUIɢ!qIӬ,Y>Njzޣ'I{WX.Pp<$mcqdԽĨ*QEg\y~ȡaGxq,T9>X91YU;@~Mc4畻m-Z!x'`Ktj+e{AV٢lu>`w|^L> )Kk^P;!MyZrHQ Nk̗m\];Dʵ3{M\tqct#/i{1>|ƪsYwDD|O*f1\MHtd{boz@˫8ϓQ ij1ƚIN%`,H)yelP2g*Ovh?nFm}y'V{M{c'/_ "AiwWlw;h_<$Zl8d8B !klldx<n@sU奱ߑ{3ݴj+i>]|k1|oW.2sIDBԅk&_M}8I/9&9F4"km_Sס*gRɴc]5 endstream endobj 965 0 obj << /Length1 1511 /Length2 7476 /Length3 0 /Length 8491 /Filter /FlateDecode >> stream xڍTl7t#2 HAN1 "( )- - "%!HM}y?w}xn ):"jHF$ (I@1a PqAx̠(4 /2 00gD 1HR$%D@"Q27+ B"h2e #IKK v(¡(1.P8F`xȈha$YOøh(U.@ .L`CN0 09x!(n@c@Wk a @p0p8ܡ}5a/FF8287vN PS4qUy`h E~5Yᨌá W~*0뺟ﱺ!>쟳 G/S "sb@ PJz_M<_0.@+s~Ȱh7AyADa  CCȸɣ`@@'wWDE\ETw@HTDERC࿣aeWHIץ$yZ >c!qx!5P}iݿ_yR`8/=^u @9ϺBa^jbPD8DZ u4a .7`0zPB t 4UPJUkD%$` G1N`A5tf0@D\NHٯyD b'8 p KD%ACqo߈8o!>bCQ Bp ~G@PrkգG>BY٭D^fQzF{>.ұ=2G֙\9lHD{JI]vkg_'ߒ!U/}9Jٶw,4}~A{2.;Qں%1BcODk]I?FC_ߎ6ȋEl?Qzt>-c#)&Up̦^C]H bOk""n<:e$s̉@T戆Hb'gӮ[cVzE^h;(w[6PvMV'V I[r3;~NNۣŦ錺S@o!fCMؗ taiB Ts6UjaEħ9GA[ݤv6Y#dlI]Č ~B6 !=J#eHԻQT:|PB4%ԘR]cӪ[MI]E?Mx0wV+Mw}9HB5sj´ \X8L3,JieK r#J;{uKR ?Ibg{RsΞW}u`Ujz4dã,­̩㡀álleYyطSx:QjS s}3dBL+-Zˠ7LN5< Gu d }CkcCk吂92o\fw5J `,+'fZBFtoeoYgU_1:L|cϏ3+dʕKg:}8/y4fZeGԃiUL`M eL["`/Fsc 9f1KKsZ lꡛƩGބjv)jJ~41rcɞkEE<+CcX9/P}A}Er5|JKb :fI|9~WCoC9Ee۔w߶sEV; ՞V RM$->f]0M_ n%8Z  3A+%^$Txɖg`zT|#GOAW))U]m#Z菒s6ʱ’-o`wF|׎J׉ ):c;'e[ω3]3&.bs٩, _-"9}tY0`r<%Wbr 0 lǢLA;eGv7/܆x.d;YglxbǜxA_Č&_H6T`pe.2%V?EEYqwW3mHS](УQ!ZaTũ$T{;&ß$F]CL0Lـ7YgF9ǏkF^llHr$󕺶ZgzI2?Y?krx<ܓܚ=Ul(BFF{ .WVj:z_IES:t5ZI\e98N%NȼvbLgX-'5OJ?.N9K=~v@ n1ӓUBW`q+#iS^}9toYyK[ $rns3|,^pxQ~ؕ;"U򉠁R(׮>*CL ^m[s)+5[460n+\!#y\ed&=E0s=׫^)fH2U0CMEx&". h"<8Qw +{ԁ^8JM]Al<>O^k<܂/'Dk.~~ ^Ԍ5uD-ENRUc$5>C?}/5U><9ŏ ].)'SB$?s(߉1erߤI^Ww۠`QZg'73T>:Ao'ڿd^Z䨫/jx-al^ ̓_)N|wsx.@\KfnHoq@ԥ@VWDK3oSmwڙ :c*7 ł(^Kcp09:omZN۫-[ߜ~8Ɂ%TiK$ݝ[ΓkDrVuY /ˎՉtBڄ<{VA˺#a%"kE5xW;)a+ӢV8َ͒Rjnk"xI- nj5&hzPϐ`%<օ,fwaPemNzn;oوlUVzPFq@x$=8)y5k% l$Wn;[ԖJپd9%_Wz,ݏ},3]Ty4fJѐ$Ɔr//SXIzw[`qVkdʣ OChț7Q{vcQI2>*^a^:}ΐ8]]tM.b_pe}>g*gsO}{uY!^hss2q9cq\ \*\wo;aa4Ž![ObS<ST?w2@lWN5'9LfY!/ۋ$Wj]bߝyґ<x&KK^1Lk<ڠ '|no̯ 6˿.9ȴ8%5Wlo4V$)>Ԇ岃( ܶUO6ncn,\!S޾A"3-)#0o)? b7A_lqKUQ:sugM%FWʁ\Zh(c۸;O ߍjl>r\x7vbI@Ts9[NPY>8O-gh߹&dsK5wKjY8w4W{.ã~h-<Vx}A+ ȹOŨ,G*{BAȭmT͋Ҍ$X7$d7x ڠ4˴Ǔ+:%Axt:Է0CZux܍{9:IW}u2H>Lpz<~6~N KIT!N.s*k~4]}Pȱ?~έjyY;ڟl{"F+G:埥+3Fd}ϥ y+Wuxfhb$=vΖ]KPeSͅ;yXV?[Qxy. ϣ#WQ+Zȅ:.2åPWUj$ ejw>Om@Q3K%c39q{֨bkwSmNbOC)aW /{+\"_36-,e2O982-]=iy4ϒ"E}ԫS"8{ėbrF񾌹il>;)q·֪@Pc•V]vZDTkeC}"qyFh"/ G+N?_*+7n^e탗e~\DSm0v26#<ߎmu C./o9=U ej1dW+'.?5x͆d"_3t46lQp~*v!6Hۈ\'z;K0㠌vI4Ś/ ],CTfNf^~%JB8@@Ga^P49' ]{oxUS=zfݥҎIcr?7uc$b%*9cxiR%yv\ťD3bB Ң7jͱNR|X4Ǻjkvy hBl%wZsMf<=ԧO([J崓|)2%J/O $za:yq;{ 5NTCs,kF^.XhපJSW[}K쉁YV9(y 4#l%qSۢ0n.)Glً$ {PnE!%KA^L9u endstream endobj 967 0 obj << /Length1 1958 /Length2 12756 /Length3 0 /Length 13964 /Filter /FlateDecode >> stream xڍTi.[qwPEܽKqw(Nq/-yy_뜕vk˝\EY$mvd$Uyll,llHVH {s0 7㛙  dr@~66;$ ͍,90Zy||yjYB7CvQg9b߹w/] W-T:1Bǯh:0sΑ}f+\,#I$G{j:C> 5Aܻu9Mo= ~:|Vh8"88̋uW<͆LqVi ɣn57 +VtFm "@YD 'ǡWx)uqIUM8g[IRCU L'k[ihk^#<1q? qAciaiuRܬy 7 K'N ?JvOY@qן"t5ock`L0.r|G,3ae{ ti;X" El>TT2S Ü*b杊+м4G?AT.i.Kj[DDj9ft^b|#1Ԥ,ج9ޏvĶ"UZt,J$S۴121r4}\8Zam57͔~t{V}r\xl Yo=PG75_ZqJ(A  :kZYvÒ[8 w j;'">zκzqiq>ӯMFCnہ=I 2iOf^XnSn 5(z52',a_q枷/9B}:G 6C¼^: 1aSWU@M*SVJ )#:4Lߪ?5 72\*:HF>js9%KEI&( _vI=m8]gA}{kwgu| 捴FX\ua=p;tdp8o IaǶ|/jxQî@Hdpw}JvS4o3Xd{ƓUN"/q>'F[K>LyV Q8#†z}>ܽU=bH6Bnbiiwo b1~R):i甛-33ȯ d@Ohe#m4#혣iϰs)L-ea24^)N-xv92~8k0ZD>Oc0۴E7{Y~j:@@k 93R0f- E*AÍ^@9R2P/9,"mʕ*)Վk\,AOwP[A(Φ7(Q6B}ԍN;Т$Y@+lû;~.En~~?AɄڵΚRx b~-Ov|n1\cȶӠω?XyNkQ,9S|d"/?)$1 }SJhJ#CuFV;;´1lKx"m790tİ<m^j[D);NZtplEL!'y ڟ8鸬9_ګU>hǾ1IlnNz82Ӝ'.E!r .L/{HG;j BYhÎҐ^~giұBHFuga"IE-H2Q&.SfNZOӛ ˷O]%=HJ<cOmc(lu95 DQ. n0^*+'iHܭ(s`plC! zw!Y.H 0|􈭙ƮcENJ /GHm3?J~V"Z9ߵGJ" x >i)wᅣm쫺F q84猎]!=7H 7E{0y3WͲ;| -'8DOT□U|#KElNqK]Cf$\;H|r CͤCs3HlhsLt#7;=>5‚Jߛ$EKW]kd6q#l&wnHUj@R6rzH[C9MZBv䮔*U@ILi]O4g唣ASZޗxC?^7}/y}v]ѕGy&ʭkgydŒASWۓّ"3#JyL*7kVߐMLd_׻Er)Rgg$&kQNV9 =bB`wq\q ؟Πv>|t ¥<5aTe/BH_DBIϦ{$2]hzA-Jtj%K봽8 a-ir)VXF54Nxyl|"̾h5/ ƸGK? ޝU͕_C~zx~{c ff%[xFx`j4-Ksv~+ɭe䗏= ټ_6Q2-"o٣[+N~<ӿt۪{ W2.a{WZ^5j` o OIБ]wᇹ 7+69m%D!U?F*lhSE\hǙ쌟Ο$=j;[BrZʵ^V)N?PX{s`"f2@Æ=ɟ-yu;\$C4P1X>GU<j_4%_0PАCgcEPxV-Ū[o)s"~ ]WoMTͅsHߔRQ$HikPu}үꠡpDtU,A0UddG K1;]xc)tꗳ4:Li$ad pZ,Vf }Q'[G_mYs2A;͙.g7i+F/^`F˾fētI'FGH! cbw5.MY(ǀj*4dx]ǣE`B iѺBDms/FZvQ h&88SH{1B{+u,3 *a޺\ s=nGp-ʅF{0YR [SV|h""36f#t`,%0+֚A v 7lfVLyu]z0\|k W:D(^HfpY0D-h˜gӚ8qWtQyqfaՆ ]hX: +ty!W2l5}Q/]6JǀV {TZ̾߁yz{>ەkgM rU"'m߅M_~s9Nu:o^?([*UaOYXZcx O'Ã3VT{r+)n#]gdצz @O &hDbGQw3X*RrV&[TX0uB\;:? j s_C9)Υ)3*^KW\sW͘ RR0+pqf'=vB DRF%_94E2N_YR m?.~댩Z`V$ g5.9ͼ/nӞ0:騴B:=K UD`BfjDEAZf [(6 kZ`l\G-J^J;;k%Q-A{n!lxE_XJjWɋX!/)lt3ܙ^I M?Yy}jmKGKLx[Mč4o:ߍqzX*9{_)nW |R&yH]=fj٨r:g5i֟1vYp툒fM_"ص~6ľM@#:V(r'=^|tmsT~`9/:L1н]^ם)˪Y/ '\)'ޙ1YIZ2jl ]Ejv@BCp4dc>RZiQܣRu+B/V{=&}bSH3Is,'s 9}l.U^gsQjY l֭߮]ɊE\4*]Iq-` 3ndd0j*]i+vˊ| p&0-n1HFsWzyp61xZ9?7ZlӓH;RwvaSQ4T`"pbG6cyI%ln_NV:Nљ m_ժ4Jhh#(/>NljD\w܃~hb\euBxnF^H2g6F< 6%h &.mJC1lpAȻx}G~!-40u'u}8!HE\U͛eL-).qgЀO"h 맽t>9w*VeC~` ':j;Tg]?m만WjK_f㸿xRYKd%ޚrTa>Vԧ p>BD/Ώ4r FX_~,9nԀʣkeu]_K+#~FӐpz:XPwSy%*Ϻ0"ʴ"`ڍD`嶵:]h~W3671A/4Pd{#Q< tva] m?ɉXMZ}Ԟ{ hXQ>UR4dy53Hk¯k腏3]O|U"kDs-v0j47wCst+1 [$Xm_r7!T5e97̏{:/An x-W L2a,?5qaf,Soؼf@{ υ#k`^E44) i^ 9==LT;]Fݻ::Q!E9_50}|Ɇ\Ar}~0_ֿ.4<K'bz; :__ &?%({6Ϳ۬!vx_.De((PItCȸheRbZH#+Ё;x]ֳim`BKu2,eGq` 9"HpY܂SK0W,wTvبV[OHe "<^Ek"qg\QQ@kV,*mfdY(qYWA YhS'iO[ nLP1FF!eOE1w-C2j .7:p?0y~?j*}OW?,<)7HZy ")*=AoJ>+/2Zxz,j"̒fFG/69X6Ϝ-rf;dY޽^0ʧJC-Zg,, ώzvl]HP=|AE5Yyd??(CXAed)8fSD"cR'B UH)t,!CžG=Y9gns?i$ZcfVGq!BW@LNvf!T YvƢ, CyGCI>cI.CWw`{Ub/>и띬۫ 12n~84|G9&6یXc7`P.6+O{E(8jRD`(A[$pmo=:1_/w/XMĈh%g:nr\MB/|Q\MBݵ4RW^wxoKRUU5Xu}H,)x6-tClV2`̉g_=m{<2k o)e'w>c3kB9Wz4J(ŠES9IJ,@K iA?7պNj's edLE6Y7Q c0)6=o%.*J2fOW.>Ɠ&1֏+c"k?A N1Cɥ}]o4d0t΋}\]Ẃ}h$W #SHIoq^SR UT  ts[e6p~|˥/.wV%/g a1($ҏFE1Ee~Z梦0Уv@%O0sE j-/p5UDN@u(%>Tr[<:Y}m>Zܑ/zxȪp'Y%N\dh*.5zܙX;WVDp'jCVSYrq%?9(݋N=k-3H-)I o̊M !"'ط\kR6 ;u#&]? <{ `ZZS K>Ir'TߝTLYvαV UaLj~T{mxϋ?h.Wv=T d1I,sSUlی13؟Q(r'2TKzf0MX1U$^?e\k=RIՋF,fq^H؉*xCE.1^+ ^>%xT<5.lyI;ZݽٵO*Ex;?8Pͭy+! ;}[<A":L.-T FO!n68O>$_ۇT;Wgb:SYBZz1QYcqŻ%˶:#?:_ Y0&@ד>a4TH&E7ONw6.){s򗵿$f3,5V.7`LК}')T; zlxG CK:vj{P,$D֗X5({fgH/~/,Lܮ5i<ͨ65 L.0r,ޖTgP 'ΡYDy&s R*Z^Ṭ"ƙ= UstD;IMԏ&v:"Xtc#RI9,1cZ)\|By#zg8֍ApǚY`Lχ;.3y! lP.[}*9[jEq'Jq{ҞKJ }OcP4oW\J|,4XbålIb; U-QܓaPFCES$G$!^nJĀio,󏈗/gP:eЮʋ{4?7s׹d+KVpċ1;][-PQv@gƩkft| ZA)Vn0ϋ`ɳw"NpN+*k{wUJXh9Y!\܃ֶͯ.X$^(`d;uGg' &"SB} }ԫՍ jOcwi \t.rb"zLҦ/J X11-qܽN Г/yʤjgaM5slzI`AM/#Xuxh#܆KnV?/y'WL k CTjFw8X\tkjPv>5ÕMoEϴYy\F2F:/UXL endstream endobj 969 0 obj << /Length1 2336 /Length2 14538 /Length3 0 /Length 15895 /Filter /FlateDecode >> stream xڍuXZ;AcPR$SNnx}xW&WQg3!.@6~6 ƎDMb #A99!ґpI*Bo]m@`gcE?@ lPd؃% N`K+H+Όa frM\@v/Ll30\ Z8𳲺9@,`+2.db8$j;+"ur` 6;ڛ/r e +0Xv b`b XmAei&oE[gȋ EMbSřlJn^-eo.ٻ8#O2{'?Wlcq`{sߥ:j؃]Arz9Bsf rpq@ <@ _v8,^J-@/?M@'WMH@ l0Yx9Y/S豽 ! ^boGfUUQVd73xxILe+goK'iL߾ / x}6.6o5 I/ Q4LyaW}PlV[-s1y 1{KZ v{U.fVWlR8?3f 왙S2@/k! `dr/,9YC\^L/, NHo *o!J!>?`C@b!z^)xJ%?2¬*%z8K%z?e&6T^4C/ C_?rSO7sF?lC\E_?ռtro/|)_R|yqXz1_b /K/^>mA ϩ/Wx }uoKG/KJM E]'Kζ&Vr˟/_R;_/>\/=v$x 50suz_oS?G12C Y jU%Fμ5"wKy$ѥGjh=V=#}ADz(mhݦ$+se{Y'53%i5T 1 ZëDqR"W*$E-{ԩ|hvŹ`yܬ^%]OwӾw[\Y?>nD%)R "źW-attxuJ!%G,ɉPUvm*%z`S:٘.̕9b wtܽB.$$Ŋ Y睊DZmѣ3L8pɏ\_\8P_!]6:8}(* dW ng9v* T1٘Cd%;shsґ y\026VmD喣E٤tfªS5./Esֹ:l7~|`Lk etb\5s~ؓgڂĵe ~M]J<__E$ i-Gj.v  `TWޜ~]tQdQݧ!W_e-}]n?+H` e:Ώb z,O1 hv|9}2aG0MyXPL6sbAD_o,_ Fb8B&*K,2khZJ~"(`{r:e=ڗ&j0#"VTWTই!Jx I]ۄ{4Ğɱ;h&T.` V+RP(r亶0og\x:i5IUorVl@`F{̉^ۧډXR@ K2Uk?8pG sY+E6Ư>{ ãS0;lKʤgg+Whk(&zAp̮S~:kyߔDA{ :1TϪܗ(=pQݼB@? Igx"Q~H0 ,D_2EwrB7ƑZYб*Y_GXbHi c|6g&Ke;ɢx9bl\%)b7Y|6Boޭ/mv%az?/Q\ymR?CI le"b66lsedB)?vwU`rb+ZK̚X~LȷN?5if4Ҿń/+y (KjLc-3°kp1X$[l7m=Y!%?f-#doޑۻ3;])-&NgP߄]NbtLoxm.44cV+ha ;F8{Im B:`&S]żԟ0(zϜ[HvyMQdwK =C@Exco/2=fm)ԴІم8dMЇB ,_q_Oa~+m59_'~]Qw^N4ͼ1NH;T.}+))AWj>TN|itG)nP v8A&MAI֋0o(=]Z-QάtsVh;S2Q7bIbǦ{6^Z.ĠW`̾)?q4;õ?@.H=m^ 61dhNV{RzR6ͣ æ] |B;-@F[IwsHτ2-cK ضy ^YZWE;$p!wJHi*ғ'J߁I6OZr/[o(̿jpM)G_wIز5"9`'IL:C ?hYbgl0,0bV'0(1Je\(֒:qS٣L4ieq1ȯ**\D/in,Mciuo|Ns_mL@o[sFWJIs{CHNFZ22UvƂ!QCy_I`xRxR}|"xt ?ǁ̬%Hj%T@R?̎ VuyJj콅őV0}&nV\ DI)5d|WEMmmEg_|ߗ+Nrr܃.yrp:wYk. Rp߆XNܮ=̕a;HmCc˵6~v))-ǭdC̏bo}`'Ūzv\߼{x"%,ef{LYN b3x;E>KU1OU h؇1F^DBQ=KKME`|s9DHǛ͛7:w|_*Vx}>{%gU-&kܟ4辝pl 9[l=XVhp>؈q|Ȯ#ʍK9tS5.&8%n(?bLGx]*`ퟬk@ u^E0 I􂛝dzpJz=%i .]x:+Mν+uZb7aW=nʿ҄5E@3 =]^”5*$mxt4|~j6cz9VT] ߠKL*1J{8C3scړ! uV52?,+lZN26٤!KWh@޲ =r>"q[r;",@P֢>!Wa@j.`G>Oo;t+%:E QE*7V%(-rHxN, S!?;y OCoG!s U7WaͻmH+< zGLo㲵BI}GB'yp$E=FMdKy[5yI9${,mB.TnTٸ8`IS15Gp0 u{-b⮭wAexl:5?IWd4+833/;6 3Fv*|9 L⺾j.i0PUKlyeZv9ؘO{'.qIy9cxcr y͏rN:J'.t!9 Q_| )>Ys!KPE#>pӝ1MJq7Ļ(P̳й\DjVUb J Vy~R*l`}ݖ7i-US;6W_h< :'1kmݓ?,\qPB$`=Џ07UU`ԕC*M;3!wjՇ(#>wڴVVҕHr0r,X*5G=0WLw`d5rN^[w^Oȷ_;7PVT:^F(<w~KYؿ'UF|sˤ>Tܻ<N%\ <au\%WBـ&$Jh<7؎?fZ29mljD+t=O:X&ўFRO-gF'W76ٗu;b`Rp4$ 2=D |и̊ww9=/ОWA,}ÉN#Wzu,wcd ԝϤYVQlHIsq=Bf'Na"efy[&sػ-ޫQ"[%cR$\?ɑUxв?4m=i3uy]ٰX9Unbv;CDV~G "B $U̺.3 aW|-5Н>E(K;)A*B @7ht\^atWFę͔ _8s+ Iߧg6M>G3d&5{@GHeD8"nY-K̎vy6C-FYTYGuu/+P'"IsSGI] zOY]{\GY]18>K54+r|Im" ƻMϝm n `OY{7-}x#Oi})]x>3I_aq@nxWY6vfvZ' hD(Cb09ϯY{a;LP7-x]:_DeDFR"__/q[wnb1S7j_RX/\h T#S+F-s%k{̨,Ԑ\RÍxr<]<)oI"b3\YĤlEc=! l1-LwaE$@`JQOo3һΤ C =$vD2vJ;%ZWEzՋ<L?/|ĒvZ /,H1͓~);?aZcdSK<JUY$WefJ5lG)vKP%.]׍PPK&jƒWktMqTA-TaGיeQZ2l2 s?=2#Qy ~2O& ,qfnDq}U!PSU=x wKC}p%Hu>vX=},eGGKBr(f.*V^ܲL5DzW8 x9V%Șܛڢpf~E\Ltˬft˦ٞ~Noyi{2C߮jVwnKgh3mƯE#yoeMaC_Xg8EHl?/<idFWRNoTU=2. ㌾_Wr -+e4 }kۅy9!S#jt4̹Ӂzv0A#%yw Cdlv8сUJuD}dwk˷eUwFzRHāWTɘJEI{#Ngo{5SG夈%Fg|^)>/kugG5gĽ^Do-ojr21 2;RwIYw^ȴ#K90+I G^ˋJX.8sZY+ۣi@RAr(23rf_BntY`-za/l2s5zڠ'3ep;8x̝AR_4U=ꡤQ:BU&ML.-3.:Ӑq༐v;D>1@cZHYm5EQ{Kn䠉apQ)2>~&2pom}BH4kItU1eьeg' 4Ǡ~uZ\ yL(һUK'߶Փf^Xa|z}2vy?mA`=Ll tke!{؉xmȉ#4۳3$LMLC,,*qӮ4m8 -Ӭ\RNV ꋿhiENlA\l9i|0D"+v߯:UΦSkrBsY)Q=;շ2>-%>my}~]#D)\w$P0%u!ƈwKuT&%sn%w:Y'}N+rk=F-$۽ jW s -\rU'}5Mw j7M:ޡ>-:XmϭݚB%{ia.Ψ1-JTOݖ}Z"q1ׯ_xfpwMیcP,k[z# QWtV!>^EX(-tYw"j6ך*]4 ( s2O( ϛo!q;>(Į ~r,ma\mk^)h׭n'7r)w23ļye(CƒX@VˌF;0kD:Uѡ좋Q-]6.0W`jݦ*?ɶJ{PxC ˳[/KLX$0Ln\`S./acoZ3#QԻ+۟9cwY7yVshǏYw{#uG*aյ'. Q]CsP2{()gs2j~§ ҅S%2Y559XMIczg12}Bm=e{9wB.{$?}l@v]lˤ1LjG ]nThf.LRGl \pKj@{t@nw2OUQBJfe +o.^آL>y`*jDaMizHI >[\_m"qN]H4dUr[nX8WPz@*V<ԥr4:VD(s8N,C'"n~ "Aic.*#~M_MŢ?ₛhWgjݚI-UI:(tܙzjteq28@jiNi旒''L֕U ߤ3ȜԱ_ŵ:ћfd~ "7 &绨Y~OaNBC\c>!zP Ih*7]ꅽCjt2ጟxZ1DըVM&[k-ne"+A{. U^Oa#rH6⦁nX_ GD@pE W^' "}X E:8E.қjGc ~ w 9_c1p_uG OL 2jWmT9QυYB -+mųrHBqvu1-T?Z EIjo`|EE$,6l9E(5I\Ԓ 4 gU9EkywiQ(L[˙l5NHD_LdBſG? z؝\'bf/z?1qغX-jf,L8|ch?5G 96i FWr;((8,$5/Ҙ^YOZ͈󒸹'@NS%|mզ0bf;BPDvʗ]]y\7Emiej-t܊ 506|$E RU$Qb$*EGtEG[pE*;+ a+K%QPCWJǯ #LM;隱Ʉ蕲:O6,dx(wEFNQ8 %/7LT S̄uCyYy) 5oXaX3ﰷoWTwKy`" gA| *ptIh({E-O UmXInðd&5:' $OPX0/hj_ޛ+, IaPkA'QޗU5a= ʞ(CPa+4.ƿJhUҮzŰ'=ߢKhETU mvм֖K4Ue:ּ -l\K 7rٻ*4R tH8[3|%gW-E(>9ł8nG-\1ɉC{0/åW߅*^Z2@;ğ шأQ3\.}eXﵶ"sMTִ,AB26̏]]8#&I{(lYm'- <(INFl)qyO9)?&ʵRɸy o1W#'^XnU0By5E {- 1$پu `Q`R+=ѵq@>]Ks̔u}]9T_@Ff`C&glb o9Ͼvs{{92ODZj[OB\EkN\wAOKRH %2+ޫ(o 5wWierr"+>\c:w|XgW.++TnQwNϽ]\r1TYZvDk/PȊb^MHZzum&%*/!;3]j"`Ťv"S2m"uX*fc{ n>oV|E(/ n 5̣w_?bklYX,絾RT 2 UœƂ+I.u Vy1isI#le_}N'G KLs )ɡ橖(3t"Ϡ1 lz@ʏ_V7cU-IY,A \.c/ρEBu< 9}hmc$ڪ]3y54+KX 4.eWX׭wk0ܰu5j@# ƺTn pih;atcdn~3>ž|nZԦuL!'G1VX"=7CU@QR.=<@9+oYX]7Fd])<NI_\w"{mW9T sҽdytJkB8.xZy: !^*Oto!t1$PEXӧPSd+Hg:Д7Ч瑦T[ pQ^nN·\RfQW¶' d!u{hb{ gR OJg!808ǟ93"x/lA)P$74@c d-=d1>#' <1{$K 볣6Ksf e\;^6-%qAtq\b(b񖧺WuKT'?>  c_zAJs8zM@7z*<1Q{e^+3ʡkr TtLdbDIګ#Qر54wNք3~(\B %!4?n* t@?X|"0Icq8h~=WsE\ΑZUڧͲ V 4*Dba/~+6.=^m>ѮF(|k m?{?:*+۲)ib JU-isĬUR "R8mO,uP}/dZ^`;{r]3"A!zYT-&9Ab}w|ϱӯiJ9(o8SˌWuVFסD2aE٢}ܬg|- m~^Kob 1r !qI*>|w#ގD> < ܱ[ȿ:vO<"DϨ6A2>,բCf-Rzks=t' vrIbvs8uћ(SLf _e<9-l*xEs>LCt;sb +;~1 tS{&fAƋ.e춬)Tץ]Dz &=2m{+yREDIޗ9zDyP(1{Ogְ͹J).[d-9;Xk/*l{M(`KN@x&Gw.LЁa3뺘~wH\O4u3Qm;yĉP_YFW_Za)w뾝1LF%w=uԹ#{$j?l#;-dC$ôXy@HX X:zۊMIӶQwfcNvl'ԅ#7mOoZw+pg%Q O?[C]xyG^.6Ow`Q\ 9vm_j0ډv?h!Zܖ?DO4Vcߵ3Κs2NưşbT"I }F\m Ue4LIHalvj٭"c'sswX9#coOăE[}lIړh+ n&S~"RGe_4kڍzy-IB9І2J%FVKF& *V.?z8ut@g1@Ϻ`obUxv *sFrhطN\vU*9^Q'"ظ>9π4km]ͳ̫yT$JtDZz)ijfMmlK v'K1_=X ќQ0[yQ~h*Ue,D ᗸ`D؂^/p"SB̲6m8z+dX;z i08| hII->E8vj g5A> $P6WǷ܇=El endstream endobj 971 0 obj << /Length1 1638 /Length2 7379 /Length3 0 /Length 8465 /Filter /FlateDecode >> stream xڍTk6t7Hww 3 ]- %4" |sYk3kX y`v #/P64 ⰰr' J'@ʔ6 bA~~a%7 ЀAApE'#ݞ .. m 䆌h a`_.إw >>^$ # 8U2@S/  09"| Rۃp @FktAпR?Xr6܁P?0 *Z_7uaH{7 !~ #p0W| YssAp_)=A{\W( upU;1RWGBDEEE ޙW#?woYCP;,v!?p@o o'98:# X#' 0-7RS?#(DAlա0_"pπpKd.?G ?y3R@~)pG\/r aY)9UG uB2G@_/99/y =k C+r 3P{ïa==~8^#O"T:| !&dAG'ί B< 6tDR7WCa0_{!)g4Cp%Kz) ~osG\@Ȗ!7",%2R7~zН@oi؇o򇇲J4DHH5 SGR5( "LTnQkhbkK_y:(>}2 Y._;3ƇGّݘŬr9AR4NNh}w=2B]mK|.di617E@*Chc}tV*@BB5k֌! z{?|BG6l֓{8xҮ.rȄSYѤH*L6(*D{٫*i!'<Ôd"DPlDdm.)2OOzRM|NttXtƣ'fN)%6m=v:OZٶRM! n;LS0 =[\+RilCRqc_w/Z?7X@b悷lJ'z,f9aUkD?%Sa(Զ%t)~&5ju_t`yO8]|ukCA @L).U*c1p5Lpʋ؆ \%/9d#jQu?@D˧1oV軷|foa}M'OwL=h´;1b̅+ϣ:J̓&JO~nv>stePQu{Q]_ޘ0*Ut&ͣ^|gMʆ0ܑ?"B5pxNRh~:+xm(ώA r>+$jWBߓFC njԛ4'ԑ' |Gޯ-;/wh樅GK=(읲=l\-{|(ι!捿D!NOJ&W*b}*kȱ+ @_ީтW/6i?;B&q? zƚHvI@NΦBbͫB#frVҚblTi JS[4VpЃ)ǯQ|rE}É/fʈqtu>72z5å$#i/ysӚ#hg0wQw2ӼAcT,qeb#/r# M.h >'i)rĊc'8( fvxTh>+6G=p:neB@ NuggBJ҆NyNHx:bOvQ Iv&MӍ0؟hO6X3_aN~-Yl7L/GU-]Ziti Bu,?#qjaU!_UQ%,܇q)sR4q@oS{][ўvhx:p X< ^IX~O6`qK=XhJy4t..OKmB쯘\K2&_!ўHa#׌Ol"iaX5ZF Ɍ =<bXGC}2w.~F&n#6<*'ǨRS>/ʽ'QZ0 IWPvJqx°z|1̥XץcNq$s X.L6j>xsw{G NY2U&y vu9~ *kF;7'cK͢ډ8%X4P~t IjS}yJfe%Bu?R^Ci•* KuwoPᬭڴSwa *}PF4(@sd&wWf;WsrMu2l@<"E@?cfU>iQ LI<*6rAQw:@N;u|gJp_H׌oP2b|18t5; KE ؃ƉC.Z:ХVPHq4L,n̆֬eZ`}>*qLAFW*/י_/h{D#"شzgsz ?,TOߧ@orT\,L#nwkD`+U"iޠΩU{U&srS¤)_;2Rc5x.g#7êUTƌ[˘iDQm[WR~:^xt̃!ފϨ Wyuh k6ÒOw+VD *Nt#)lr(RSCeYWǍw+5\!mRO܎R96ݫ_*tT[>oϖG|bB =2(]Q:ֹcţK 3gaZyĹ;:Z't یFy9UKR켶* I&;txJ6V>BCLfLȿwevue>l C?w(i |n)w6~]2˝,MSN,FgFi*m~.AIbޕi> `Z+u;۲ML~a$abtzYHp=Òt(vl8,{Ⱦ`˲PCCXحO _pU[1꤫I_|BcR쁃=P u& vu~yCFQOFRE*DԄ}1p:DƱ*զ.wzK6 D%M 7ypZ#Fz^)kx.b&zL/5j{Ab$X`\I'WL< _O$mv?`I<aFg'ֺRm$'||6 JL53jY3L٬NR5a# +ŪzOtZ,;lbBNw3nC #5غl|` 2 \ ~\B#X&aJ%IJ͐I}lS~C*vlIކlQY A;jyxY?REZ^Ņxh#d8BzX h=!lmC_\-j륱!^N«糉^L͒7 32{Oo*>iɗ$j/z^S>ilvV0 |8aT8s+7(0q8,BtZb9^UGT^WN,|zowgl0 Ө(AQVicPd9):+51/I՚GYx ;.uޗ<)!} SH7|ބҧ30)~v ԓ,㿒 b 8|\`. Jj"׌qF+% K7K,/X5m5>:RO:g 1Hb];jo{`>.YQVY []eWBah;e?"fYVWL&Ƶuܬ- ]y>Yq%w`.KB29id"2ĹŚNu1r Y%uES=BXr)Y7U9jyO;~c^,rzE@|)SB\AЊ Ik$4o} ý/јՇlt1@?m0d3Z ',n.=klGOx伍fWawڇcÁd0:r:)Jg\vz>u#{+"U:=aVN lE'rͰ`dyla9 ǤeL풸^w]rO!!}aVSjǵZ7jMlK"a&.&hyT'C"}P,̞{[nj́<]iG~qQloH4x"^W|=T`QުgBJx㽧Gu06!8S,&ogwRu~҂ X]OinANͩekV[^56p tX橿 ~t]¥zvM`@dhKMR]D wj@_]Uwou{)FYA:DiVTfR̉仸פ1?,,U_.Zlw~3Cu$=1R2q-PʹhB\xDl4f J&|{s[Ѷl`RDR HLnG 7k)F@el7*aV}_ᐠhqҖLT"< ) OY/}Wahim<7ZFE]xTt6qrk% ^U|.Α u)t?b{8-$oX!k-FnKD!o濳S).mwF׼%Қ潍K =Rm%zj0`Fo'hYfK #,)KA.Ue"l]BJDDx[.Jz%7^ߩF<$+ޡ5U4O˵pۏ"Z wAɟUB'dF>5OmzɜmCT< lU Y fﳡx6Ƈ]>t藯)"] =T}vөVA+Ӱ# M9'(u-"X|tf锴]BRn*n8p=L4)oj$fbHf;͏ڷK$1L\mOz%frV-F3tO0w$2?Κ "| endstream endobj 973 0 obj << /Length1 1453 /Length2 6490 /Length3 0 /Length 7485 /Filter /FlateDecode >> stream xڍuTm.R"4HC7 % : 0 ! HKItH *!c9kykǽvfc~%=c I(" .?0!!A 1*C.!4vD  O'F(Bvl' Pr"a`B;B]1;A.cE+#&-(%rE |/`EAPgÀ W&0oa! C(LE0tnPog|?gO??࿂A`0 0(@_]W \@v_J?H%lQg)!*WW("Y* cGd/ߟ= Msjq@`P4@ (%..*`GM|ܠB?aL~n7= hy@P ߆ :dPk0o=!/ ?+hjnjؔ?~ _JL $$); ?Zp{@wcb?#nsG`X pCrkf Rpqee? W i=!029h[ 2 E0: 1)7)5j@~-( l}1 _&(F>W F@~LXLB"A>@ 1dƄ0=HŸcb^1 1ҁa7 ssIT 膄a'ZH$F(i  g`pW-5J^kCb-zuofk+?T0y96Oϳs:mݑRYQ/?;p2Bt#1+d <µ|b>u!)Q^|Նڰ p2ӣھbY0-[X=LLG`'-g>+VYdtUWFRRj3 R=nDaq(HKf}ɱ隵jzL}|ިΌHo63 y59\e;r>ՀH&/Wr(joXON`wsQ$Z5\eĜ_O#|8PB2錊vAZ;>2G^Eʺ27>,1]"_瘰q֝Bԉ{/\TŽK+:Rz7[9 Z7y;Ĩ㉞ \g5)}-6` lLcyhGZYp\'`읻wml["C3O +f2m+3Òuݰr1Ut X7->92,5G޶z7շܤY6LJFyc ;e)S`8i-l!$dUB8gmԈp{6w #>1{A 7#G p456AXٷy'7-C}GNdDWCl7]+I8gbN8ru'8k`o}VnM"ilˌ[>o9(VS쟩+͊im Yh{ 7=r^⬬$-*uՐF ѽH;'&agմA+z=5v{me"A"89o>>{1ʑw6EL(ݶ[@-!}s@ ɞt4~\=Ua1b:D鼈ax2 +Av''Vs !ި`eyJMӧoF!LU{o\8^U7 .Z}.rGc `X|rޙ}UjyYː`(_yk+9ixgjCo:ZO/PeĮ=&ou»S;øo[y).w8Zu6bl? ?&Jg5r1 ,k0fh)<ߟʆUpw&ZEE"Y~'~X~=zkW^ӀCإ!vߍZXU2眗FX/]ԣFdV!eҨNl|;*hUAk/ hSd:)[VtBċTpkutmt}fAuH-Z߫DjόIrba~>c5Iqs;P 1!MߛJb{_u2""cj&9~]DVCJӴ n`BSn33u%Zdq.ߚx=  i*2Reyr+5P]Yw7= O,9)rX]_V1}U<!gud-{)a9ƁxP/$:6$Oܟ"1 Mj6,s-~KiDIt,S=GneE8"cqCTzUtY#*jQysկ7D+tVM+v Ir7fBVcDS>NPyYbr^:3Ȁ{,؊X4'ez)S;“Rah @/)$&QZeC4(qtaDv/S27$[ t-G+mɵyITן|/^kd[C4UTƳkL[j)?>s8uT`\[ci5m[e$6-4] *u4ReDlY:bm_jA9ș4l!Wf6^{m7mY*W8 U} cg\ʧ" `nNh {y:& Kܶ/OVEܾӇ- 6JnF{)F_v5+ͬ70pVQ L"1$.PgӍ~awIxyYd[vwBN9>s, `!Ď\7%k!k$C!|ۀf`.MU+|)z{zM& P>FbdcG:Uzv -{O=<_)P'LqUz;^jc99f 7)c<f @{,\>xhcUk%ߟųd{GuMMCM՚S'9u 䪪zd?dcj^9*Y9n1Jgk#R՘vt2yԒ-AYg" J&S;ն>S^2c$I%#;&VYT' iXaWL}L$a27P}u;9菦a"Ճđх+jZ} &1w;9W5;F,n{< mwkIԭgVcikkѫka) ЎDmEСȮfAbD}JBzAXn8ٔ5\5hͫ--@x<8|M8V) m;(Wm2Q>mhZ0Erޒ8zOM 5>'ʕ21W+5m=4[(ju Je# 01M&-8tcV}ij(d`]MA)v>|BO/>uq`NdۚL9!FOutߌ>K³鶇"G@M˥I|YQNꦠMf{ˀsv1!x(aƸ_ju9Up°霆NANAiCv#w"5 k@ i# mge!ؖ淕GvՇQh w_RO@ʫ12۟Uw 61&{0lZpkPW,瀿@vu2;9;o5J$~vHC hD_+: NN(OMī]73NB$ ۮ|0LI\F=NTK@ ;}c)%()NLh܅\Ţ폵s|iAV9v2 -TtRgPxUlˆR9i_HF OTRLBh-*F ],E _j[MzMYKtć_R`cά/Zߤ;S| Ey )z>b6]xnݴSj9h(ʺW[ȆS<բ!CfIUHyד%w%)qeu)`\4b&SBaǼ&0nlhBف!EgX TZkc0J3l 5RZSRi?{o+ YۉaJP{t&>e~<&w嚴Y%'DeaHowghW[M=}FL6%(>,T~rh?j񥁘UgrK->qmN8N4F|v!PUZl.Hɟ.&=|~.P%2tq凞,l?a$p1M]ꀋAk%:{2{9ܐhJ rnoyM|BFë9Z/V]{jpj??0P+EzM雷8VwZO/'n6f7ZQ㿯]Ԕ0=jxӪ%Cx1Igik'K={n2P\8[VU-%VJ)%?zꑧ ]7cF (y8K &MueX}s/\E+&8 zL>7V3PUyj _0ɧX~Q'_n':ę)_Y0[ |o4z4|&F{wdHW0uwnH께NXku=*@$C'~cLٝ]KC]tb0A@? }o8%%:,XVTb4 {Z*NLV#Hee} j}a;٨ƔOFG7 hrj׸M=ݥ; >&'f@;6wT*Jlګ#H`ﳼrG+ MQE{|yJq9ɉ'g|7¥MrH ;M@T#Q# M1_//0eujw{ru8{u448NgTN69$uZJXuL]pђGx[V턈AXek\[_Q8:˘0x*rJ:x6x ys3{CK]ޤY1yۆf,DqLd{$xs=%ju6G }Q|20_ݹZ13K‘ endstream endobj 975 0 obj << /Length1 1613 /Length2 10155 /Length3 0 /Length 11207 /Filter /FlateDecode >> stream xڍP[.{q/-ݵ@  HZx)Zݡ[bZ܊^>9|̽,_};FSC,spsrdt\\\\wM:vUӜU`{AXK鉹`ne.,#[ßz ?z#o'@ikjkuV7*AO yb47'_rL ҄-mb_r?k: 0O^\\{Z2K[D?UrPK'?_ruya=p?mO2P' G?++J! jFO$ ZQ+?SR?a )? (Jv|ԂǟfhtI y{`K9'KJc<|O1 (wd~[y{ސl7bm+&LՍgG؜jeCzԨ\jd{6cOA''X;tr!!^6錶WU3JS_/~ VŽH{Y5~F!w;S]sQTHc[?mِ`L.04T ]ʁLB6MCjĐ"Ƚ~BS2 RC_UXGagĘYJ}I~{&Q][~ATs?ֈHYQ+^ai^<Ģ1s?jխ_m_tlTэ[sZN-ݤoyrV` uso7K 4ZYKXE{( )f )Y a1(rL[=Q|>?[t4]~ d̰fUm n~/ČҚx?սj?d+vzl Tj70gr@6a-:nLD8jgmP /5MmھC}dqW UnRmEqyݳI{0iښM]TIǺX}AAr[diQpUė639K~j~ݧ)yi &@Ig0b&/~^gVŹFEǬ |cHö"6ϵl>)_ieYOJZ3{yd5}hgjI{O& YssEb%u]x.C1 bL ~ ^;Xz7%z;2C X: E }qF$jvIn+Lcr㌙~" s =D2KdSV[FݼIf̷+%^ Œ%K7R0l9%? ̝e~N~Ձ^ͥa%m9ۗT-P\?HKG"S^DnJF7iqR@E] aN98ͷ=Жʋ1TE6aڢ4E\1@[BvR͏1_ZJW;}m{\弌g|Z"n*<٠23o;.NjDRA5ϼM)ȅT ҷdVÝD!I4Y-qNIEluLB<ܩ3az /<3k , C+BvY ԎG$u.rZ2Fa)* 3eu&_ĵyIn$hb["ƒV5+lvH*~P0+-S)]8gJ_ٲ6z P+{~S}ײBFyjg w$=M_4]C$ns';u%)F9UQ8k=G UŲ҇+:sqJ2r,j90p2pqsi.pI|\-.MkO\Vg5qPȁ f%U=ïn<__x`GQ4fMS1 t5IBHnFt/4,+d9,[D &_C|˕ß n.p/cF5Y*,ģG}-O~P)~(C3HȲ'sHuba_$=eښ@+(6 Xo ŌՀ(' T]Tr79 ᐇLtC̶Pzn}NSmS,ɺGҥƣ%>c<{ `DDbCgn>w1; 4yY5anw$s+wڦ]%L83Yf3xƣWAػQ8lͫ[ٵ«#Yk5qw ZXț"w'z)_ %ڗjbPmk.߿1CxUߕO{N_<)L`7zdVwџ{ZC!kNOrKp5nPqQ,0x4sgCF'S>S8S.ckN&L|7{S'oȓ03l@I!A&]51)ɠ6{M!k& ge#m]`nJ313Q TuԟwˋD !}Fglިi!5],Pfef$.IuĢZI!])?UZ? 9Aڔ#L< ]?zT /7, &%ȅ07Uonyˊ ֑oqhC7߳ ML4~ݑ=hzV3KT>}t xرz *_Yrl!,Fh71;݆lf{+@ݮqj#im7||k>Xk>(]\R~s&}AK!a-XKKUhy !t"/>aZ筿4F>x4%A\Fϴ鬱m/~T)C~v;Yx+JݥArQDzۚww;ɅA!۶7]GL_O徸)([=~%8{O]x{ƿſ({Hf6U(†^b?HUN"d95"UOӸ7KA2y cE A=R˿otV8$穞^Ib⡻ˆEv{0-k7GǕhFt. =2V|so&륨 \ey9oD;,(OxDz~Jx㿏%s#cn %Mj3YȾ+1)o+Q@Ö2H{Š;4n%[axCab`gr hT8j(;CH4KLH-o%QNv ooHA½ duIN\F=lSUqڹǛ6u06?6S{$aba1c0Rm+[i;ruhu eT¦c q̟zqNl +׀O-5n~.ʠ"v Ԏe:'I/` k=2qZWyRxazॽŦf6%Қd15GaHkfѤIAޕDNMDdz1ÁV[&L(ԏ5ha"qj>`W62ۄEO\vD)k({ ,vWA3 6I浒wK35âdeBZ>~^:CpkXDb|D.ޞN]uRJhZ˓^~\JaYXCIG+\c8iH>4=f5e7hdj?!֣@A}H Ap{7bYSFAu&EdHQ1WKnGq[YoUf[ $p-{˒!A`Ǜ!HƄBII*jS ?d]nfmr{Aan.7 Jʔxl'OO|.xna*kxGqdPpU/m&g{"U9nȱ??٤stu$v8ߝ_v;qZo0_9hj;Sin^,eɱQ!~F\'C ;[8/cLG(#JK G,\ħ0D9oI- d*P*8Jqqd6c$N|BX{y$"B5 9!7Wjx>$gH~-X滆_*0_,o=*2v^fK3d\+Z$9G*mcJQpQ̿ x!MHGdp, =dedhdW|¨6]ѷFIxtesP&B{>V[G Mh[On E`rU|/B|c2u7;/18ؤs^G\I{s.~T8VdAP!5bl[&OmTnItO^{,Rr!:7U;~ ī R[0bڴvnbF4ֵo+Cu) 'V;N;6{;=iP^Sz4۟iuVڼc>v Hx8ij(`&۶uFY%Y>2 _KڮUԴQI)HX^E[c.2w ,y_\ᜉ㹑ՎaFMx ֠ƫc>#CD?vxTл |,|=OԁȬYy5".yߛ.z, FBM@IK(i!3Ble%2QRR| A ΁اՑ*߸?|<֚3g(z8;l,;qFڈPOuB@ʰ*U>I *3Cޏ TSR5UuJ!aZL?ps䳤nX d:RWZϤU}7[sl&ѝdOtyP2j:pQb+璔|jđ*et!5PIeOk۝Jf'˵zZB 7{m<02takj(ȃZ_ -:۸'nH3MiY2.*u~7)3,;Fz%Dq"kD9l?HLS~>nLt-'= w#Biku|zT_KѴOVdNEϫ};c0͇ Б*k=ՖO$ x JG6s,J:1[UojsvBplh/OIҲCv`Ŭt \od9R|1v}ǜkHp=,'C掲I(˙(<IHNY,)/T2:/h̾~ށgv0Q_&@*qѱhhb)̕mJ-5C IAȄЍγuzҊDwz) ƴvGnq7Zk)%ǕOpw2iTھqa6E^iI̻ZII+)~s7^h~f֓;  ,\*g_"Y6+UK)f|Aw:_#`j%~`;B8Ԭ3@R|']vhF?8P>ى@poF_?3l޽FHV^Z__V:tv6{I~=Wiۣ"b&gu}Rт}'wHM&~2W[omcǷۿpq?/ZJD6[SYjCz.V&P$Y'j(#0Z 4XT}zհT{"λZ+p=&ybL /XqWV&}U^@o`a`xEW?5% uB>Sm x)8}XO&ڪ1U(Dk u0%>%(XaR*X7)Mn |iQyXLbU@Q^N{ms`j١b vF.{d;M=u>JWv@7. +Sh2U r]! PLLӺvZtKW>Z!y' 5&K8۹BwłWTZA`2^ߋ#z(~F`P@@ycھlJ w'{d#K.D2kIۘg幛Vn(0pu,3=?~B$r2aJyay$48akֲ.e8UcPxpÅf2{}K",lwvw<@wR+񑘒UY>= A*gty/lzJ~\ d<)p cz|lr=#ӬSʼn)']U-ֲo0kko5E[lys繓dhj5ʨjij{dBEФDAݳ)SSe^wJЏ3b&^ \{L`٘owc&cg(]sqg2^8!RC^EWV3>l@V"#W?S}ApYjAe^K,e6xҬ|0͘d^gʋ9# 3{+gҖ93)vǾ;(&ψbP'`w־8^fsu bl^Z&",Gdt8詖Z!1K-@!>AS֏;,-ـb}ݚav݆?'S|6M<":J ~7d$EP D`0vm {fJ]oɔu|ZQc8vxN `t~u$)OҊMFl]uuL>e&F&y34pH6m@lU-eUuFMx>wW:[°̄蝋Wlǵ-@tFܐ 7ёRPc}\ '[W^Rثi+˛v.oInфτQ~ENQ[wYKɉv> stream xڌTm W&WdvWmsdcmvmN~s?[}Wk86PN(bcH@K Vg`33*9Z#!U;XsB!w0H8Y lFzzs͌Ҵ k G|PR88بv[ @EC3AmhIGBo@kcobh P:흁FJ[]- )@_ EcG}{ C`ifvpq6>ť@Kˀh{ܿ:og}CC+[}k73k% +"EH з6P_YR" ]_5uGm֎0'df4ݿ/?dlfmdWFNtfvN@q|`LzzzvFt54+-o%_&3Z,?.S_f$di_?z}+3K[|LH|5UkuFfNVW+ &MLK/+H_S/_fif q0 EOtKfhqͿU߸ֆ6F-# +@^ cYX [it{t6.60],+ /ѿ@'t@'8t%6zbЉ!FbЉ!f.>K2##.>+#E}_ G}1scH^1v#g__G}qqtfHEf~D5~tO5]0u5x#?-XAJqT>lm?^S3fKcI~܁?Q? ?i;X;tQ?0#YGnY^C'B~~l& 4Y^1 4 l]ŏB7 ?p~ +| /xÔ5rXhAo3%c `s Aʉq_SCIaojo>Hl"* ;;X`;z{@5\85YF.޻mŘة"~k nOdwPVr3(DD(UP8eҸv~3 $xŮ؄lN⯊ǢXob.cJzy286{f~ l@%䴾^ŵ3\J[s T}3n6>OːFa PZ4#z_®ubP,,ܫdhhV3H&<`x@ѢX\.#sBԮ +5%mr?RZ>Ov( `mjwM {'U}km~B OrMީ5z!H8,82Qsky~eJo,225KD_B9^lvK[aamP\.8$FiIH.,UŘg:b^l#v_A+% ؑagXob ka|6CiHˆ͋~ g e|5@ϩCpm MjTWHZt9W&6̭Py,C/O~EdhHFAL?XP@xr +2r`sm>nBvgĢZQPc9Ƶ :Z;,IjX1 8)!s(MͤDSrnz/=U-61FM}^a:Bzs荍g/ZL~C; iJBIRz3g l ̵6V1 N~S+=ؠm_\H,N̈́ =D5LXh0b~̬s,fq_N?`IT8bG.0&QK|c ;>*VBCsNn;qVBɘ*96)Wsf|"y"z4iKIJ)'dCMჀovZEuw)8&wj[ S׋&6 .ޱfEmj̺0'}ձ({dV5*w5<hpl at2TN[$J̐lsZQ6(S4c? =-}G_. %q:pW{|lK2q740 b5Qn=z(P%jc[֢V!]9mCyW8c] :]EAzMAmٻϗL9R8^{d>*Y7[â(⾤j0'x&29/u.ػI%9EddK:MY3O'uB "dsc׮xTSCq3 RiK\AzA;+ha҅Ȏ]-Uf |`5CaQE֜_T,&ʼn{"̉WzڜDDehP,$'|Q8Zz劉60H fx^*R@8`2e[$">\ eS~W&Hu6L6{;{}3iQfE(ɗ;16N:M #ыy g ֑mZ!QX7ʒ IAݺֺўԖmǚW;gPns<ui`/1O5Ek_jCnJ쇓-. d/>4h=،$v+h o]<m]>?,վЈሯU/YZL+ 1tN|˓ ~ X鿽) ;7}pL˜M fuc׀yQi=( ϩ eB 4}|TBcg]M cb}`21S~gvaLC@2vj=(S5~A7p$-O%#yi쏑ӜR7;#;5$vfDI&ws\Sc Y[a^p5#:DE3g#Oӎ*L?OI~|O (lQ?ZMN1xy OJubԱ맆8+}Y8$"R(sf'HTA܊X-VϝkY[) yx nÁhɰsH[&y CƠ |thgxsP^+QHAK?IfgϥV }Z3smEH!k]+3=j4eEڥ&A @,m na|+K"Es%])[a} { &Apjx7륯L; @)y&Ȃpa~ϯsq9Ӡu`1  V: 8^f<z/R$fiWG|:4c`ܾVBN dk46$H?(nsbMew;ÖN1 ƙ,)1,9k~MݨG簯ƕ NwdGMjH ,asPx]v uœdO0 #w*j| zٹEd؎/1& oֱ4WqǔTV+~?)|,7*ɾIge*C^:ADCK)wKX:rhwЬ9>ٶ&q-] C2Ao3.Uk 4Oz"{Zʳbnr u[ -O&ZY"Voa!3@)!3Jٲm5 -{V3qrN4ZM _I\ Θ@ l#ϮҜ W{ׅ*QWd/B}N5h u;78 I%/7Rf/a폲jgfbGO4!-oȿ})+n(|{L0Eȃ `_?/6^NC>rED_# g@[ ;0hv^@DɍG3u8Zl p脽wh!+0d#[f#>Q;94v5%q\䪹R4S rtIci=SBA>m"?R KZ}0IT6tZ! sT3;uFJ6MoGKEknZ&]1 a;Nq9Lcjk|=:UJփ[*{!*xД-G XD|\BŎ7Lln>x .cCM}WDm9~gߺsSN6բ]?BѼk+=h-56gk.OIonD EÌ[q2=Ī'JR"ך<#)(cҀz&v{ vIIGYtRt|= ׹lzc] =/ɫv3Bs/!DSjp3aM~eO{1ϙM-fP [ .0w70n?Ύ$ם^>v/QoF]@\L[<ȀnI). P&aHbܿku=+^/U5jFȮETgB9zu=+jKScC菧%fj+)!r2uб9f= ok3P<-TmHkN#~ʨˏyጦFb$ks2:beCD`fm`lVh] /AHӭsCM?KFY=9W0٘2 o8 ໣t!#d5paem2 3qvkwT0# vLxL"Vj19ioQ1YU-=>Q IT=m" ( >'b_l']/Kq)mf/%?i+LG XEU[3Ga-tYa¶t}Rr– N䉔Ui9\\%uaoTzw)7Ob=gPcnBR;GS$n8 $6ţ}W#kGg̊G_ۖFC-C@GdF: ٺIC(߿[ OᒐN@qQݣ6lpeǹOvjbWNM Z$ E PyUݻ·~;'xb^|7q)&+10LE!XWS%Whz65n"0~,gz='<: ̊Q_8}̳oG~#B %YYB-X[ r5vQoT ׯ.s [2Kiѧ_ip5!TC`9BsτFo#14}xn*w6U)P؍9Fm<R lP0w+OX$rHCgphsp ᧻ZC無K|is˨ pm4pbswY0ц/AKZ~kSś^ |ۣP? yd( ?2 Դ܈-:x><tPeR!WV ("L?|rduᓾqGzÀ=2ux+)_?[k_Aw; Uαu@?Mg-ژrOK2s I[ P@;AO|lW@gpF< S#Z|ox&`%Da̤EyO!oeQGl}ϡ9xػ":ZEH=N{MQkxݥPXJFdN Si  nEg"ZcQ,wu {evcgiZ0sKHW2!%Aq69* 1ҋjZ(( @)$M/"/DKD<0)CQg\"yINSdD?R8|pt8lhE7〼<h4Nqs[.QM}{90#5ҙx\-t2HsL*i3s4=>J"x {tƸaX*fUלA/aM_0jX Ѭ7*uU&61iuA\(\rf :3M YÔڽlM TYF )^G"ٶkBևp1 %7B:vQ<(:BL V:z{ŭ5o97o sгlġ:Fbݒ Ȱ&D;%ZTak'RoAex'^T!_*W7q;}Υb^*g#YI<YrhbWnQ$QC Em7U)p>LWhPr=Vs-+o'2Q7.Fn~t%$bU(58O Ii^;PJYUƘ&)dt*>" j顡IQkX"NuSM6nw-hVGB$k2t=+:foQ-9 ۃf~I`Q‰ARǍ`zr~L#{q9] H&S[~*{ ]RvTU}^/ L 37J DZ[` ?BQ&;iq[^eϒԩyN[օWi%Lf S9o<C};Td,E0>WD]m`09f9܅<Ⱥtװ8&#c.cΰp($xZmJ8KZŌ\uED(Kz"~o[uWB:wٷKx9dN%r5Ij\'r՛GbձE"|70`yuW֮$PW OF:@,~O#};BaKH!Z_jxqt_ru~)(|tLhG/' Wv'swEWKh.k$ӱӬ[kvv.^cu(tCh!L7xrثx. [CY_v{>:^,dZ <:Sz05(BbAW&sJە ?_> C 3-]lO SNWEgd2%Ȳi@sjx>a_#an"*6A92۔\؊$)_*o_ cW`xy6V$Wg$xi5xBALld=4֩M""v/=uH+BLSx%̾VXRPΜ2VA #pj2v%|TĂ>6] *DL7a]& ynļw5}weӬZWlڐ%09kSss^ynƷ_v*|^@,&첹ئ]ְkm^ oM ]H/|ׂe&YmK8g'p/צg41w5׼ex^/z6UC1dXB$b5qwYgM+܅O*zfMA1חdd^&5* Lpd$}9HUVj() gܜ# 0xn 0{G,BJ#<m\uJVkΞ^*DyeۤƢIb4r*QZӣK*'"E@@i4 bf Jۉ$v )>J}0C5-Wb Y$>hO&bG%h5aN B$*cWGx:ӇXg%[|΢"ȭCMl !u3^͜ %g] 8E(65G^vJ{2gbjOTz>k0k7J|jhEm109<7۷q[k99E'&ʞ%Ud>i.yLk$5Mk_i3P7ʆՆH9D|]8j,l݃prb{OD| :%H2r2r 4beiw J]t#I̳[҂WufaIn2:vGjT5mIU2gBOe<\ [y0i{F4R0X%?i8NO,P,IE3Lw] YJ~zFLT*=Ӝ6 B`=@Zͻd%_q9WnX7+pȨPs>**lGy9|)[ܵ4;81Xp C7meL\"e^<=tqGdNc Xv *uOr/,DDZg+c>Z:J+k?B@sw{ftˏMdg1c_ߐGʪ!7َPFf%8Y}?Fſ{Ђunb4U2iJtIһCk⋿C)8_0Jf[1gB'!luxaNwI&'ѽFXԼ|UD~mJH ŴTpBz[`d.{/;i[&yl#wnK=%B o'J"2K@޴u4N7{*8~ɼ'[#u[>,I5#/2vͰ~'u ]B9javcg^aȠpyo,Ҥnǯ.0kЗ*=}E!eq҆|p N᭎Y1QYʣ]~_ _E@rOSYozdg 9EǯVsR&S_1F[eI$D>'qԼs f ݕX4(AFT-S},:_ w8n%\X\T\3$K" AYc1| ϴ1m^a@L%o~Teo6 iCQf~PwNRßuzI-r-}9dpޝz+lg'#\ZY dKߺpzo`.t?ʷCN!]&c>gbɋDtACW4䴢r/bO& Wai| Ts7¦hf=H 8M:ڶ5 ='W@8ʗM-,J1 blReY0!ɨ/Q eE+;iEcqe* )/ 2jNo=q*ˆNyIq?uP$=R t. CnP`T ×AQ7cI듒QsîH&Ι^΄eKΠj<[ _nTߪ#`BOxȸV.)ÇL>Wb ͙~ J18 *;Ϟ [[Y6R|w€&]lEm۵ VQ#g居jE.wj%mgYͷ%KZV ]IfcUWL,Zӧa"/X7*^Ǭcvt}rdZ!U֘(Pvu2)Da 85eQYV55$|5áPRJ[\FvELHon_d5ژwF^,ғ_X=yNA}Kt}øU  g:g1&0PEy&̶JÆW*\߻_ xALYq3 ?kapjƏ>)fh,Ae;Yiʐ2w& _+?8 BZnƖC"Ƭ˵|n8l1Cҋ:d4`|[HiuN>j2Hz5vhPew LZn`|=|ol33gC4tN@+ F4h,q<76 l[:UZE䵶xTUu=H w)5, )QreZ.A8Y"y3Tm-~|&zY V,ӹt~Kjmr1%b9ڥ|#8h?\I~c!L+~YJgs-h%[i%΢P @ ѝzgXߤ֐Z#_$4Hݦ|f4݉E&(p&Rtwʘ(,ýz;mPTNsT}JIU}0ZK(3svU&W }[c^;^ *o7r3hvD:M7H.P/`t({6.Nlys=m~pt=-ٹqRo )8j_?p$ $@'~+9+*ϴ̾(XouW`zc6z>v>7N>-6EhlBz{jnי@K~ӏ a;"O}c;Âu.QY{0_f 5*g #< L4HD%.+Ų 0Ay9c v}L[DNj&R< F%,IՓ]i3hC*C#BWkѲжmą$Yme6#NEa;sDQsii^oaF سXryѪA=;]jqkV#t_ucw+NXeToe蟛^ћjaax^G^p$z&ؒN6lܱIǬQ'fٰo\ŋօDܴ-+]4-p) ?T 6Tf@ķ6!H314-^p][߂:a0å"$[;! բO00r$dDL)L]ZsV*mljHc/q2'xL}=5jťs*Gۻk S>{zq]z8+w[ %Cny3o/rTܢ7mp4H{ \6ߢBc42rfJeBC\bxqrT42ɍoիY.*{zH8O]#\6a.U$`&3X"Y9ղx@[u C(3+x+H;=duqZOp'A4 嚃WOe%% m l#ipk^8݇|Xz7bui85pӋ$`a)a0CHkWDk1j,"`5୚{pkjZnD=;ІYUe`yEjZh5ج $3@? ߾ UNtf?(C4ufA)] dkІ,QX%hĉhA)["p}v]\+8 V7u(XYxIȴ'9P`͞ #66 -&tQ ͈^կ(+Pٯ{s;.ܜM4qah,RaE$@~L}hoԫ\ƫzz>,D2c%بK6|NpwOH-r,P $loeë!*Bv(edTsn^>PGC&7_E[5yEЀl" hrvPqz-OP _p!I*2r#*8o5ݭ ~Z-ƺ,o`n(q |U ?U ME eYc6OykG|瘉^W {!KwUG3fX $a'wG: $x;LOضEx{TQ˶?NJr1rW I .EGZa˂rf#y"nT6d$J0? 2WfVg9-'ߒ`#'C/h""+wh[8IrZZpM{"N_v>oD:Llx6)9ACqAG %@z!]C{M -!bd{D4.wy{܂;ҟ+j[݉u=J%(PTK4):dI4jck6cf+7]@5r:'ؖ`L/\{Xβiqx'/h0y nx7nn:}ԍ:L=L⡵ks.8jVཹ, ;?Ƀ]Z+|Ɉ_;yXOGde0`O:Y z]`1bƉO)Th\o2ЗT :Q9e=IcI/ Ǿdl^5m|Gz'o*$4'ZJ  K(k"Om e]fiO6="-o ]K=WxoADğ=PNJT,w٣hWjQa}zadauNMPmRrیL|A1nİ %!rs;uuknDoh&m|?ɠ8t u}FyoiclVzD1?zؑ>7x*v7,칸&Lj?=@jzŸE%|<`@P/x* ;ݤP2j9730߳sQ8wozLFNsO+G1 FdG\Ox ۣz: 2;YcΩ\zYܙ6V;ÌNgvV\brHUp@Iy)khx1Wl w|,z*9s,c{)Xv?LgR4A_۬\~^%[҄^1G%z:.8*)!8)Y(?3f::!>_j%kKO2%9sَKkЄru RHd"uQbf>\ &V5*L&ۗ<p:`> stream xڍPi.-Xwwww ].]!pٝ{ssޯ: PW`e3!RRj6)?Av|wd7CE{; `ca@dPd)<@<~ИXyyt@vEc%- @{WK0؁͍֙B[Ԁ@'W%c[_Ԙ) )nN@d s~sq3:޲e@+ˀWsL @v::y,  @YJ fۙahlloj 16y3tc*_M@`g&gfI;3q{[[$@N@ӷ{0uvnv^F ;3?h80kځ]ټYNv6t7d#O%7>^7@9;}o 0&@ @wXƏ?OoffogGqUɏQRL`dcgpsx>EW3ط.`׿&w,%hO,,o_ߌ:[͟zƶ ,o`Z-" bZY.Y g);L6s6%cl@v@{g7 toejv{8*wFI;S{? `d6Il/ַm49f&;{  #0!0qFf 7߈7b0x2x&#^1fПg[!oXr-9a` g7vmߪm ߡ8\Ju7gh7ֿNEmNo7?LѮ?}7t1v[ȿ#3<\>ʷhIMInC(o=z'7uqz[m|@S{S`QB7ƽ Y=4ZFeGTdM[^]I^'- amOφզp' OD5D;z n쒣stAU)wv([ ]SݯGz.aьP1 4aޢWIQb -hׇ3>=YL<e^1 z>d=u#:E|zs77)~0p'z3E8x%eI==&ua>؊pD<ޅV^}V.hX߸)?bq|Bm3@C7L"xq?#Fa4Y$iDjeL 5=!;paL\DnլJdMr\1ǰ, ܂DP$fN7;د)U(R;RSyeW@n0?l PrU^,R/-J4|Kkp%,J\ᑫj^a tzxYW7e;zcD}5ic=K#Cua$>AdRa6u 9V:7._89ƋɶmGpKT;b8XL!e9.\NQt:R2rS3#)+_-#M_薉(ERTBC2#.KicK5D=cI4R{+28~+<<` a@Nj0CL9\:oaMKN4L}2PNaNξ-r 7 Ejvw֝\%Rsx6i+W@{uP1gU'N<܂)a+r!I~XVdGs_ҵĽˢsS.'%2 Wi|([:0Grtpmr{߇N_l~6LPOnQ.+ur|e(*״TZDvP$?zV}/ aF*Tkjv{+>ߩe1K~-zM/]`;HB_1>tċ؀t2S˜F~h<-X: o\,mYgƴ DMr,5m#1<5, P.F䖊W:4ק-aƋy\j?)\&G˴>;ܫ'@e*Mb EZr3&nvRڝF4f *%M?^ Kaࣩz, z54dd"6?`t'\a..KH7QiauX[o#4+JOa5)NiOA]2X;s${qy˭ϑ 9f;6&& )2LĚCm%%;~+XxGq IP=OGsfPLgt)ۭh#]@lFD:{4yS^隆w9>ޘWY%3+4O+]KV> <?, +uRzJnI+.Eh؈>ux8]U%'JcqMxxZ&,ŗp:Yc >Mװ/|j}ujs},]v"edO9X{꾓eKX!ܭ}2$n.m"nЍULZ 3͑w9U; >1ݗ9.WoBܚG~}!욕Ц'X36_K_?!)󾮠qpcDyHLlnvGUz+&yWy4w>lM$CtOZV{n@ȚzAD} qdh ͮ8 SbWu,-nTYq6}<̎\5ȮVYh$!Ā]ɉEUɜ[eL7I'X?mlcIp)[-#ջzqȦ%m)2cȻS`.Ox {ຑ*&S 1fAJƻVcu3k=#ʛ~E>Hd }{5L))* kL+ DwL4|=;?˳R) ʮp* <V M2w쫚B6*2k'S;<Mb N(^*(R>t7AtQe`3LqH%OomOҾ~K9@1[Y9&d䮾'uޤhCY>=o"NMA9DpH&\fIZw9$p?NA{}2Ь6'#W=pBkʣONvJr/QW*+a:R;Wm7kO9*L-OKճ_&wr4ᴛ7+0bÐ0@Ij*sӦ\j$ ߘ9u'.OE 9enpvZƬ?[KpCa<'Pe,M9oh4bN1F-9py8}נ]KӋĤ묩;%D޾qO](AQje _IKN Ow$xzӿ?+"ier.3dmt6[K:1׊ 隲}[ =Z6<4ŧDBSuJ跎nںdNp=(H2i4b̷f`G~,h8'ȃk P;"#-jQ@6S?jz/ޕLQ` $hqNJWIl g:ȇ $&s8= v<8CeUK΅RvCF ~lJf+n'L5fDRT &2q-YoJ` &i~{F O "۔kyj ]>*7K9 , ڴ`Nwo˯Q>d?V (&w@ё |g蜽M2F b ҀK,G:1 O(' f،jx}b@'[SCeIJu x={QhHnEYxH2V2tjXL!x0,KI:pĮ:nK7 ^W?dMFgKVV+gxW'Ԇ5o໊/GjlPowCw%W\t4,`Vj}p55&s'q2,r^. Il ^%?wMk>xЩ/ё(-ř`Y bCuCl5f>Sw1)@EP)hr*LE=ɘ/}xʩ`Jf*Iw ֻ۩%_ؕ7 r izjw#89-GD$ la&nԕMY;!@o\{^΄D@2 ϴ$;KD^%0qBo` tylX !iP""ins=gOm[-%u"4#ZWlʬb. e~\5͖_rdc?E?\;ExҺ;ko]0١429?x<&=us䦿=uČN eG:9w𹄤"ZF!uLG( oD4a vWJ+<Xa5d(IypWD`uq]2+E{I텟I(f@y[MP;l[\c<,XxHX>U'*d!;c j4EϹ^a.Iڬ]m(+Aw,N2鮞AK8cRV8$Ƚt!|ů "nXfȜ23'ū{g0Rp*$m(b`2 'ɞCgaSA&,b7ҵa@җTIiٙ*L鸤@7Vf Z(Zsۯc *ɭ1 <{^j<gEϫH۵ևjN]22Wb6r+F;9bcASvnZ3ؾmނ=g>Z#0E9c)>ソO =MFSP//I%Jq{2eūXZBE#6uW8)~. k=˹i![鮎MrΊ,B˵Cq5a>T*4}[^yY?UbJ{s,&~FIaHm45Г/PAq(3ߐ kZrl=Ghv?m a7HY+3|N5s5}&8w>7m0-U֘ʮw"meŭS@㟿1w%3 )qUXLF.H]ӅO(8QsGpbn:̗k+8Є=M53. X"?r^E`OE՟T=60bKT@BY\Pr7>w~r8MoW0D鶯(Xځ~ةbWAO]ôGv̭Ѧ_\Y$\o{V{ N;,cPWEVEσ|Xt9I`hȶ3B{76Pt^ˢD ,xTPm:|Yn˜|?"@QU4cHxba_yRgF_\<lw>s 8& _O7NmW ƅ'c;,,ʗN6+^V< 5vf0ELvA*辇%bQ4GEN e_lZB$Pf4kG5DQ%$zX劸H!ݢԎ?tL|~ wp+ ճ6%Cݽ5G6 @ Gˠ1_ //S\Q'.ypB1ZquDs貐+h&R UWGgJ4k- N.5k <ٿYR.Tݣii)"u3+ (jQ[Rv' Nj&,a)Iba Z d3R<"p,*頋WН!e"i5ğ:u+yBLa_BB gTB"$/.3tuPL\YvtnaRZg ?柊T4C{CBzx}|  4wO/B>== ߱i>ݹ~kEcoFh:iQzJsuqc,Ȳdco,jA0t-^jpI~`JII:3_ƈҼD6L+D.\bTAU_k%VفJLX!QͩL-&R{fO cL [MY,i%NF5EL5耐-.U~EAkk\JrFB+ahuw:ÆQjkAD3IW%r޽ڲ]L쇮٢fQi[~;mw"׫VhL}Q*Rh3V?>j[<vIhm(0la臊Z_ .WV[CΊGLJ9*n57'W"EQ[_hd#K{]RٰY%9.]&1P;G:-+`{K4 Ҷ;SK<ܯnXK`;p̸f,'gneT}B)x-1`H,Gm|C~fg9KBlm[/XMz4)鮋)ѵʽ y+}vnV{Lw尒ڳNdxZDZ:lOAVJjmi|v뷼뜑|eZub%*l%LI̧I _)SW1 ;=X  [N}fjHRʘ.3ͥ!kɄesӠz[~Ͷ>l&iR Gĝx|!TΕTppۄʓ6Ԭs'U}ꪟzMp!.󧙹xC48VFpsz)SN6#waPokkO;\2ߤW0o)1v&I.t1 ~ Cz+%;ÒA'|Mtp t6,? ANȋnH{ldW6HWۓXxVD{rǵY-c^ l:OfRq֚ơò⣊$[͙ӕV%S)S+iB.9dm\YƢtѓ IE*MA^ю]ЕkR@fىW5aN j ȍv(Y_RH`|ipƽ K9*筥k<^ߖ3E 4;8Nɵ8_SAR0[},.i& 06%cʮ/a_;"G5 U Wlf`򺩵4!0⑭[+%?̷#@W zSD%x?18'_X(t꨿5P& T4c I ȿg8v0  ZYd B'nGKdwVf[{Z>vhVYհ^\|谹q΢}N5mz%a/%_۹Rb2dhz Ϙ¡BF([}vٹwEUb\8Y(Dtph?f1{\Cd2ОDIC9Kyue}?PBȩ(dy`lv5s>6ZXFFB2kL|S7#1ؓ򼎉u€sVe\|Q'7,놌EgLcF>w'[Lh8Ht6tGm"D1;rHxmD=[BY,aYG"~r˭XG&KuV>֭wsGݵ=F({c>/߮ѾU 7jj*.pǰ<'Ћ|Pv )/Ռ!~Ϲr)) Eߢ}X2c$("A]rG7$:H iU5M۟l5,Y"6<ܵ̕Wߛlæ!}oUW: 2?֓/}Fy K]dm{~%%됞Gn4jFY"2}um, B RLa;/Ɂ'$ q^Wg32FE5Wֈxkc7u |QV?"giߡźN2 dh4+Ir ?kV?k ;D|SӼ:`qx҄ul1iW22+r kg3R{6JJp+#';#C~iPndD5mxpxu j!=e!g=ϽqjGF*Z٭z$@=l\(g.EIQ(-|sz(2Ws>kIKU MxA~aT[Ё=Xҝ^e67{]VYZ,ۈDO z@u! }J(tz5'g"/ z,@v߽e enS.9DO5!cWL&gqŝ/jFXW'|El2띹UmB Y0d5U: 6tm87Kꉲ܅~WOٺ}9?/V&~cP nmyky]cqa!xj% kvshfcjD9#JYVT)D\+]fy/g/wEִF5GӖg9PS7sx8YY)B:3*2G q#sF5omDׇlH^#}GMֹkǮ\B1!yZ8Q=ZsM0ZP媋g` b,3U|p&:mCNItszԕCL@TNF9ڑz;.`ZMq$k(Ԧ$W%OvCD2HF@)(nIO lbdKNM^lZ<뛈.[F,}li{=<*`?W'O\ x4o, nIؒVӖ[OsTkFeR\VȈEFk2{}[- Kڎ h~B!ڲ~߇JfB S ,jYYwݴ3"SsZw2H(&Ubj (= HUqӓo^`7ahn(l;P"y@eI]h p"q9/ â8=nJD U%tUB~ z&3+3idվN%w LTjP1-Fŗ- 5q4c wgؙv$^WӍK8`ar_DXAMV։(Tr%(u^n9`e{H՗g~ .6xR%1@2ѰɎ%7p? :N㱵6.h%4 8.b}Sˀ,TN'jj-s!NF*k̾O..+#Lxr4tA|[JR@~ǗdzouH=',.\fVc%!mQsCPA\+5G)eR- >|2/b=T@PXygB}‘÷ 'B%X٤R'GUzaO*hY-"})C2?~md2@RRnz̒6G@a!qAz?[$fʆ?HRP,LWx&׊_˚Ǭl. ̹kHؼ'Ƅw>%X]faG}y6Bʁ0ĚtvPр32Vo4DƑb#UߠՅ zBQS^zsvh~;,m1gvS}QNxCe6=͙R`jQ2uY#hDAhH endstream endobj 981 0 obj << /Length1 2517 /Length2 10976 /Length3 0 /Length 12430 /Filter /FlateDecode >> stream xڍT RJ %  5 ) " ݒR H;x߷ֽy|{-%$qrqU9l\h`=r4Z]+(􇅴  ɘCa*G='/!E c! W4Zi   #SP/w 4wCm@@s{AA ubg`3wpeX1g`G:frrpp`+l%R`ge߇`b$.^^'4Z ,6jݣ #bb+補_/Ba7'o#SgR#&Ozg7Qytbfv#o5cDF66^N6 ?,`2k։G|7G 3xL ; a,~_DPݬGҰ?8a"ӣ 9g)_Փƃ'_˨rKi4mK%fRœzhHS,=lĵ]'O(qnldWDL~}^q[Tf75&q DqX $:IyE & . cOCP>B@9&xD~q,p qCxbTTkCfe ^ד a묣ݍg n׹D洪?rt'z:xť˿խڏ s+l[".==\?So;:be>mor&ZD~l ݗ#i~ۦ_ot3bQɦ?尌,[/~D;,fzduWkj[{?y$&;c((c.:|e3vQX@]?c3v es'2R#N35 aL }mBi:3RR-=U_ ^kg1:;12IPs¾~n:GN2/a7𘒉cF pd ߅l&(FIӭ/zdңx{rS=?.ŵɍ&![.QЫfөӥ3>X?s(8FrmV*R.:Ban8?=TDP^"1X9M7wJƃI_-Ӻf L)qW2~Ko4,BSAKʶ(ϩ1#3?sط '$ l7J%+uEнՕ >`6 eW:Cs-oϚeQ]<i 7tA&wj\4'N;}Lڄ*ly~1&+{؋o0jL(+P]oH K _a_9Ѹ-LV2;eje?A: g=>cW_{8!d)ǒ *||1)Π-*Ǒ/`.MdI==TNK+EY3h"m !x] lS SkH퐚8 +*MY72MrjʐLk?nDfgLMQTi-R0#pY,1|E!;|1YRd#d):1j;Dh`>ohLNS}lfKjB9`hM [ŽQۑo/Ҹ]fH;_A5]_GվWlZF#Lyd6d1 6?:82')% NWw| R a4얉_jP'R>.z&q""J﫭6TmU6i@W+EC!.[9 wV:(y&S9..5/Xe-zd lK975|T[hY˨0O ^5$b?^+k=f(S p9aMs5?n]Kjq`b|Okن@O"sCH.5|JKpO#l#4(=DM;LjXc}QT- ,_!@\ڱ[ ܰ Vx.Ip^I"= ua[L#$( j }篛G%|kO'A&j}]<Վ2kԓ IgM{̢ax/y=V-fl]='"U4v(O@_eyvP7_ u_w彷\hpA`&O5D8q}D!-cFU EEDNWœv]Ca鱀M v4g*F_Co?x_c"vS/s,_k %T>"Խ魎N[> U1Wnշ"oZ8A#a-$f7LmFpk  ľ, Z)5ѹX\ wP c%,TW FdVcֿ?Щr*N2F5KU _j{TƚȲ< Kb VT~OM>n0YҴ%_ս!5,?l㼊wu 2ĉv} Ez6B دiΰ] ;* %ۢch,I× jD/@eY6'D͚'naפq4+Xbm~o=|5hœ0lM֠Cg(ڰu7'^V*l9tcF';`㽀Pt:YxKЎqjuݭi7j)Jp&siN-g1g+ZH+k]:YeqT{YUM ׮EJJg15dF!5_sן$g~°Y$8yFF\Bx,CvI&+"Iݛ2(]=]T7ٹ(Df'f[yï/Msa| #sG3U:~?9*&h@>$q S_fDžD!nCMv_LxDZL40VoZkHT޷?|gYMgӰq|՛~\7%E\*FEBZrܥbLv]oCOgEvacgIۙ87(0-t_s.:Kx>#s5D^j=aJd-ɲ4yjR7*CRSl@'yIz6])ap%Pwx'Nc#?q|9lnɲ ԰j*ѱv?fLM|?*~^cQby[Ť*8I)Z^˦QosèWhFwjh~ JL. z*_vQoJP|YsTËx)e}^%{zU𯟧89:"[FV5NzuQVYn:1Js ݹ4)^~:czMmY 8ׅL=:<#&,ERrwcN@ |Y&O)m@*=JI;[qa+x ]F%ĢgntݺBVd-@]jEDk#X4+}Go#ʻMUe] ^?X4gs`]5iSǟ Z:HzPO㻱X/aM2rhqmqrE^44dU"RklBcXT6NQg>oCV,i^*+)(vte&:vt0 cŶc.]pt!/ E a=+X&:@CTLsӻ/W OgGa2O%Ea`E/Ï]v2" ٢`Q/ajIR2aYu6M0YsƱXJ=sXeX뺰0EqaGP*M-( QwYhuo"h\=\f,:av1Q-1S$|M:_kr~F=5j!޸Uf 8^s:*>߀t4]%qa]D&31F"DO^)i&dEQȇAY9sz#f!693>rHMV(էNΏڕkxLYu:TǸk)K;|31*AH }Вqp3$ȯBm3x=E>oSpǚe|ՠE$r=9.,~S-k9"@0,܈BI"!|3\Y6Ͽ3Q hmѻehr_F; 9g|0GD@e~0_ )a5bI'+jC PnSny6ט _Pg$"*N 󖝭j9{ȷ:%ssl6:3HU/_(G8oRlJ4H.{g3kHLf-)w?'h#˝]6@19&۹[Mx=KGIә#T"M?a^,_/RU͆S&QQ|<q9$6#cL[J F#,-ݳY3Z荦e?i 4 ڽ4% kxnҶt~hQRfP%fFKrW|"{/Bʓf:1O64й <{gNO>Aِru;~f4t1` )֞DK^`Z7)rBmWaEO)AԷ4u%*Mu]C/P|n7w{*,}we h{҃_䝰}3ޏjpLd%|JY&YKT⪅Ym Т:)H2]mw\8UHW%{~sjAW1lP{3c ^2WP<D%4}&}vC N.'WSѻ=^kWj kTuoJ)`C)2+Ѓ u4{HHſPD;2/Mzyr5b/lju+ a?⺕!zhP A -7֊h Nx[3lp EgtT' fX_#(IU@orE8ʸB% j[Li (S:;5!q\DqgNo2>mjYe]O)Xca#c2ˣz/^v"N+6w}6am.]zG//e̒]=իp@J;oEkL U-u:l4М/*CC;I&'ܹQwNp+Ɇo5z!U 5$ۺ1WWF!HLϬ:90^E~q*5:2f ݀ smKuȂV^qy[b=ŇBvRŴt̆j[u%z?o]bΆpX 4 N$tVm4r*MRЗV/XvLEÐWT\vR7&?*=!àBkJsSN}=yVy{ QBޮK]>~Gk]/bTd3`_d wHpq-JVgt6.u$<@4&b4͎H$@٢݉WB^X%/=Ʀ"놐RPP Qu eB6x|V8>G3ykb+[T"J]ک2-M8x~z/%I ?WEa_r˸̢'0)B*`SF(IbT7~O+*LpȽӌh4)ZF}LVo]@cijxSF!uY]"9=\^7}30kWYGdPoCSfп!9Chꀫ?a|րyR׿wSnqN Jk72Axm7a3UF9dzʏQ)o"[ [≫s\ɠ"udlk7a[%QnT$;!o;"@9UX4nvYT ѴifVeh nҢ//-7s`_n yhԪL*8],\pppL΢~BgInSW .'tH߱!t-WqCTh9+{%)dU%- 8cV6dWQ׷\\\/jФJ η0q,jE*& L+/qb-?/.VXt.}ҜسH/>Y k~:}h^HX0U֠ϖ5LtCIBoRc>)!Y=` Ӵ 3v=Ѡg!ͧAzqw:o SzWw9 ZqR;/F3t(|e?ۻ8{jUNxj}8,C`ftfjEhnr*Gٓu/M**kP83V݄)89?78DKf7UTP6d'Y[#d˛ͮ7X\;7-Y'/Cy<~6C70VY^̉[@sHHᗬȎ8W=Hk4(rj'\a O]MčaQ/׵N-K}9ô #[.6; :i-y>q~ua2@pB&>zv/v|uSܧ7m8'ZN||5)̙1/ߗ#K o{W=&q~ROYVWhp*%_8K}\]t{^v2l3>'5ם6 smj]^3Zm`01E]e9t\F*'eZ[1t<}j#H 7ݫi!l 2DMg z$L R4g~>N:y!b!ﰄO"IVd% ~fo"s3K:XR{,az~ AlkYŶNQ٩hݵ[P/td&ORz" \ԽY~Hf2m>7)"1 A7?vp endstream endobj 983 0 obj << /Length1 1378 /Length2 1294 /Length3 0 /Length 2176 /Filter /FlateDecode >> stream xڍT XLE'TE4fj~.JŶY,ͬ5ͬ]wAv"Mjd#RdE.Jʥf ;<9<Ϛ}}|NBqd dE &$%ȨbHQsC 1T& +q`:@`Bg .q6 ,1DJ14Z@m>39`RMHP.6Lّ AE"~L 3'AtBAER.v6J@ "E$rT XlF`*|BK@:(d !@6A`F#*=1VB1u2"1ţX4B,qOṘ0*Hzrx-0zRRVʐ7)ߪ*- a3u?°ƏHl-4,2ѷQ_&eE #*F%51F!* Q RTM &FC~7$!؎^dX")I#&yHNpLvIK( tˤrmPj{L#L"!f9g[jCz똲0]뽛&-"4ZQzxY/Tm]vh[>p ôPŜL&>R-|ˍ&u,;hJmd#3?i|ᓑg2HtCN[b&Te4l-g9\N++Q2&G'qS#)2L ^K40EH,|c U^+cuoewԬKs,Nb]uv˫3黣ROy/ k=nr%X VwV$jOjq/4atN@46LzkCO˩kbQcV=D1ot&f9C?]5AZInr:xy-ڞFwJsQ/~U_8ק]!⬓4rMN LֹnUԟR^Χt؈mݑ(ɴnhʼX|ŴR-xZ.JP)?Yb&Vł;wϒ'w鍟1>fo I}Vlچ:-IKWzӖHd6S/2h.\;bj`Fe>7úwfMF 04˨ȉ;* eo(\uScȾ S݉{=˴,e**EoυZ[Lf |6E7kK1˱,;m.;|1$+E&OSnnq9{nTygCC&E`_V9+]>4<J=^AK]1!ʍ+Ѫ^-ظ4wk{nMs +㣿/2oGO흷p!KЖ*R_齰Fcs`Bή9$|%>7p[+1v6>Qg®-7%a.SW<67 t'&ߔT endstream endobj 985 0 obj << /Length1 1244 /Length2 1256 /Length3 0 /Length 2046 /Filter /FlateDecode >> stream xڕU TZ%->*.R L B$**@@""**.*q DW`JTT AĕX% my=睞e߽=1T%#ӎ<$sÂy 'fV,<0 !"Qf|>0 ps<C< A %1A0LQ% & 5,ӐX<77WStMHHJC1@FAJd<*=XaDQ1M'$ CPbRT %s:q( 8\_$B(`\1 S`_Ve& &1,e=k ))L3rMe2T8M1ּ`0le2\L_,f2 p4GM0^L!7mC CY@4${Z}m/Sh*J0Þ00SPF 5ICh(Aay/Y\B^2Lw>ʈ_=rC|%}W'#$)$ kX یydjm.'h&ĩ$M,|W%lrzn(Dq Fzl6C("1"1HOO37N|wf~B \P_$)ޠEX}mt˕>nprr .|Ӻ6Ww1C 况ck{GOwYQmJ}dvY>:sm8wg3ifk~٪^W5`ʹafSeף5JۗK0sk@PtZWkG<'"M#FRXR(_+Xrxqݬ ՞O+ܻ,U٢Kؖ145mdgQєaww-V&,=#rZ<0^睲0LW0 lب{hط3auc,No'Eg|4%^t;}y*GG׿U`6flz{3?>ۢ@"A.;P( #F 9:[o_:rSO &Hjma"5Q DWprhٲלKl2)"_RfJK#vpc Œ5>w`ߺVVgt֬)c0#m@ՠ*Yr}dDb1;m7}?wӛ[\[>_QnJj2*.<)|R;M"sgjt,݁+NС궔b5cCuTy9,G[Z;NL8 2nPgu>WE!:';8u=WUlӱGyJ,^='^c>,5l^KC"#n-U5HbsF[goݕՁe̟U(8cjMMȅʨ"UN endstream endobj 987 0 obj << /Length1 1309 /Length2 6209 /Length3 0 /Length 7110 /Filter /FlateDecode >> stream xڍvT6!!!5tCH!J33034 H* HK )sk}ߚfk?kx9 EHzz $ $HyyMg8)= G"A0WDݝqqY MDj8h#04)*w(ˊv(Pp[8\n8p!0W11OOO  DEp<`P؟Ҁ8/1 AW3@_#0v]]+ Y/Os@+bktq {0b0"qF#!3;u@C \U>- Ap_5 sfuTC`ФS`W}3\'**f\Aa4HR\^Nsl~]` m _t]~H}r'RqqnGav揂{,@W~}du0(oi3'UT^_9̕\%%W3!'pCdMgGCX+ֹ%Hd{%VoE_ui;; E_ uwg fU.r{ր{p_*îO_u-kK$e M 4Wj0*H̕ CHD vsW0?BۺPW {zW}0̖tfi{cճJeOѵ[ckfE}gP-DIOQGI=Wr\n7I7[5N3t V~F*jrg^~#n6oa.ݱgW5zyhieP8o͋ &.B(W/ã1ڬK8aR(|_% R t;39!mf6oQ|״Wڱ|5 o&գlijOMD8,/z"rov).j@]Xwz;c4=jLF;bY3]uOQZɸyæU7U<@c>q ڙ,gm^@۲OSH5w!\VSFBF9 Z00yI>RF$.WR/VYI;x[,-Il+Й< =U`dד 4YǁIĿ3>'{Rnp#kޞOr}Ps;7nt\ڕ97Bf9oP&ɬMBK>Ҩ8EP Q)0/cw>|64;ZP5b!ӱA>xZki e5QP)R :;SSXMֆneElk󓕾`b"hLTC&SڂrB.cKQcoiVZ PR7a|HN0!qT<榲&埼n滴?L &Ug~jE.US׼L^){!ި.k,+,r$N07m2 &wq OWj^*ٛIA#e(JxCF\H=+Ըb'GFF1H-GNq OA]aަΥE\50}Kh5 1 k!Op }af vsC]Iwٱﭻ>>!ձ|3c#x  g@q5I`=]|VS\Rǹ%%MT'VjH9q=I7{ 3Ɓ8 og,*Oۨ,͈߱.aqS}}̡FbT_`sj7淚)cau,*m^X 7R#QR8{V懦F" @Ch\i$]{mcAbw{e>k jɾ4 ;Z mGPƤSqRkEV>tgto⮑_ЁQ*xǖr&4zxD)gSn?.'ܑ|?pMvI:"cb@0?^e5^HB3Ƃ4Gj,C~etN~]<>%.81z](sv`͛ =Ϸ+޷:V^qM7݋)X/}(*t~& +J7u?=jh eO,`6h-Fs^a.ׇv UL`r",B˗/&̧+iD6JqiY;i'#eΠ^QRsr`bXoGk+xE) ]agmYL}|f&._|4s<7n%%.U)1gCJq7&_)qk}j̻_}qE|n}쨇>ŷr_+B.K vWqP[ 4Pcmv7:HsBDZ|k9즽E=SkO[N) X4d^y#Pd4sOIZmC[t|M-A0)2w[!=&PHUikTIS&'IՀA5Vz-c.t 05eO4O |LDo^n{xe(+^찭-O}e5nLs Τ:ٍ֮-:E)݆,P2 JKPPt42~꒿XK&1LA3Ό͋YI!s,ûN&KfzĽzby;1#sL{fqg'{zfp1LĂBhsKakS[ag~Z2;n*c=ph T [s}"LcT%kЌ\ym+֚mpc]@XD63 + ބ1B*j^΃oǏh݅`!)h *]Ui {ם ?ok=-)ҍ=ܐJ_6ؿ>ƨ64P?d^s- [ v@2 <Vc5nƸdit/6+Ϟ $DJo**hC(Yʱ8N)~t}=yEة'+tjCTkba;25ˊY+sn1` MtS/%هށϒ񍳨~gkW9;FHJEYIZ*Sk] 'Ox^NbQr1)?J$.6ے}knƆ'cn>;e y"%oCC@ VǁKЇ_" +}7>5!"|fk X{'3msJ2 6`/`Ϧhxڣu{Rʑ~WKVmQ `ip(lSgÐl{SÙ!SgSs[mQB?U,C/ &GLJOBu,O2;2$NUTS"#>t?9!Z:f0)&8ͤuB)m,x?J'x1 рcwUy!"sB'>>~Rk+'z#9nB6d5WHGկkrbvn G0('dQq)w%n n Dmwg/k.f67_dϺ@5뛠\%Bǁz<{s)N,HL, ꣺س!MnMVu_XO]TDLܼ,)+Kēؖ6Xvr|r]c\/<խL&Uxh'*i:%&r#?rk=WjJQ$VIU 96 okpqWi} `qHBuЛ%s#!r{lCLeю@jgjN>d6KѺ!7jTjRj˾,gxVA[&+ӃB'wvV; z<F^IC]S'oNXպ m?byNn̩[qg0:CP@3Rd:kE.wkp{d,0L3$5Ax{(+RV^IZȘ7gd_,vRpiXveG'NRFm//rN326U'3֣oeϚ*AI{GMdf;WeFn! >^/pぽ؞B2u?B_4%p> ߏ32L}ȯcS|HNY=@AuJcf>]56&2+=QrV3e?=*x hsJǯ2eq^=Q1[Fv2z9&폆Yjq= =1p4;"i$ٰpg碎z1wۻf#Y6m>`sakG+oCU@WttesOO]OEN5˳Iig ~U2崓tCLl{۵3"R땋RԎSw%p2ۈ ;[Lrt'R/!B)-R/zX/m1- ě}mGB. )5ȮN4M"mnO✆"qͻ<[\L^!c7J&|yN矶_1Ma9U{B/2sޫҪso㳾~cp@O w~>.(ێg X ߣi+Zx};v*cJD$͏vgX6.|0ߘ$ ѮC耤2B9:>kr1Yv"AFmsY~EZax뇘hfVXƌ{G1Z#gɕXyM> >rayĮ YtZyGedT lQI҃{2:L;SY眐!E墨i/9KTByFDtA~8z6益;*dx>ak6CqNS93]p~Ȳe|3쫁J}9[S/ݒ!{/uQ_p4D(p멲Zz Q[.Ѩ`?Rg;7-MhxsXzp6*r'/s.no0"7c=u?ip,%OpL62?g7B*&Tf[[Ao/Lck~%p#4p_ý|KFFe#R.(? .p)I-}7"2)ޖHP~`rC-b@O2`E1 Nm'I][L_[pJ-K,皣Zߞ]d|> ]NS଄l[zMy5 Ny%I$oDͱH*CZO"/AbqˎN rʟ$ +?Ś?gJF2u#2w\a+`*o q endstream endobj 956 0 obj << /Type /ObjStm /N 100 /First 916 /Length 3149 /Filter /FlateDecode >> stream xڭZr۸}WޚJa'Ǚx$S@۴͉,y(9qnd,ũ(9#5a&Q WLAFx&8D#g "Ca2۰&#xFlΈ4(@ODQyN4[iD ꥂe,#h :7p.c3.9AL@4-Kϙf+ 68r.9@ h.@{KQ\FL)1%9Dkr@ DBx`U@$Wp`@8xQq7%=5y&Ao08 7Hfq47ozs|7* \8S+$%E}Z:ۻXlNjB>kBɓr],@+bXv;Z z=yx(?+}cXt:*tFg}*ononA'p3X=Ǟ76ĿK3zI zE[,nhIAZT-ſm~+r } KHۙ;m+z=_DNgD.K:>ONU0s`.Ҙ&xӔ^r=V[= GzDVw=@'~bYI `~k9km@`QOnG30: -7UYj `G?bd=jbcJcժja1Q`rv.w10&īfKB_T\Jdz]fTԂt >l XmPh/9XuZ+uǗӭO} G/f:.4ΕԢ[F׼*mKU+dny!r6Ǚ\_oR7]OSIXO]_uUQӇj ]i4G?踼+fdpcȠԓIAM]T=QQ`jɇ }ոqOh_bQíVj#鱮0䕺%dz}{y_P4g@;m &qaOƏ?]6mz[CXlu{{)E@<,px'yǃk}=vy!iSp3i]tqg"^` wanelmp6Z -{ȭjPZte,Kz],N{2/u =o=XYƂUp!׺F찮y޳`!]եĨA]"ЕxN ҕ Cg(a)iv& AY4yRtD שpAA逆,E4h6K4$Ru)A àT08]˹$Ȑ L*=R ުS ـ*0JxD H-obP:(;"UvCxPv~q5WE l,I˲~’%E)c{MgAɮo~YN5PO}!߅& rvuPMD=6@6h۫9譩;8hꛪICHzo@b0Yy&R62>jsn%3ꛁ%4j.)o|zg&蜩f6C5݌NuP@*Ѿ-4Nrwz endstream endobj 1044 0 obj << /Author()/Title()/Subject()/Creator(LaTeX with hyperref package)/Producer(pdfTeX-1.40.15)/Keywords() /CreationDate (D:20151212141640+01'00') /ModDate (D:20151212141640+01'00') /Trapped /False /PTEX.Fullbanner (This is pdfTeX, Version 3.14159265-2.6-1.40.15 (TeX Live 2015/dev/Debian) kpathsea version 6.2.1dev) >> endobj 991 0 obj << /Type /ObjStm /N 61 /First 568 /Length 2556 /Filter /FlateDecode >> stream xڭZm~$p);(|Xmt褫sy\runQy˭4cR0pLt3LJθayS2Ǵ3e f^24Vwq2 8ƭʓNE1!ȫA&r7Ԓz~,ZNseƖc gh9Sk|pɔ+;E F-MD~cB3ř 4|Ɍ@O5Vc<)ƠeeV,45:uj';*'=Q|H#V1gej0F4#]l9Ag^I |SZhhVȇo^GѽOg2WPKO3F SxS/z6<pQxbd1%'e5,n)29ƙ59=0ZrH.d:e]Wz8oJm|%޴L+JՕX8&/u| Îӄi)4t0ȿ=eǾ`0xT\X9Mj.||d`RfLiBBWdê !V3Gn~?<<ڀTP|ewzx:Gnx Ů(A& qˮM3냣))O|F>~K" rRw+JZ%JF'|<3iRmʰM]?|&#L(Oy<_HZXIZ]-Ǥv!)-*:E ZfCrTtTaو.,-,Ar$@e|@MO ~C iwi= &`w+kPF]l TpV)"׈Q"|wѐcsuJmxHl3 7ëz]* q@c+թ_pS%'ɑ:UrP%: R#s\_!E\_r\lͶn$5Wlaq_N}:/nǠEU.uH"):c O™\K2Whܷ"Of\"qv"DKAVu KFNÅ%Ht܈e" ]>Lj㲛K6u.iBR,A^LνVau <`xHg(rNӏ lrK6.T6g\㻢2dQ(fe+TөS=S>&ׂ?[?U6cI3E#P:g^my. RNv)%~,Gޥ%2K4Em\A)1E5cFtW0My2JK:DBTDJ xRDZtFBuPFr1ӧ)چBNS/\]E|ɮq!G,P\(pI䪆'C5o(@HSi7NވM -joisP8s϶!N6ke+tQPMB([\#$!YBl-K*qkJsӮɴ섓_ʪה겦 25%oŶ4~r(֯Yc8vMN kJkJ*?GY̩4UqV,JiCkZ5JE%y~Ӷ~8U@=+І״}[kCܚU$=Zl\'>oZ?C\5&dLgl[Zތwd#5>]jf^_7'%|35xjԐ_ֳot>ۙҩaRæK _!Olxbfw7I,xb +qg!p\hj%"1Kem?2񒉗Ld%/y5ٿtO)6ecNd?x^~۟a/]T>̟Κ>ow˿ԛ.,6ҟ endstream endobj 1045 0 obj << /Type /XRef /Index [0 1046] /Size 1046 /W [1 3 1] /Root 1043 0 R /Info 1044 0 R /ID [ ] /Length 2525 /Filter /FlateDecode >> stream x%Kl\Iinc'v.vގr'8;ĉ<$FQjv4;]JH,FBVfxH * iX@H@dgw[n7)'W$)0…pDY)2; 7It$=$89 %=#~`OD'L$8 &=#GX9I'Yi$Dp!vb~i@ХX:V@0%Mp}>U{Ѯi;x!ងMx:!4eE֌">^њI+[]d$S V+g5+m2IZ y'tD@$B(K? ';!D*H;<N *!DJK6bM̈́D6Ӵ>ҔB P`Pe+?}*;-{Ls #/%n%݌TS~Zӳ**}6@a ފ.=EE};R ~/@ZoZzv 7osv[qߺ,<ۛr@.nd)<YGxCMEmeDۜHM0;+Mppv0 {}`n%=L]uz2`t W@f"fljD *x0⼈Cco eDZ7F<`DGt`5o43>@"-֪0㼨r#ފx07|FT,2rFv7q7?*84rT^T *1mQ|@Gڂ_b.rc`_m$~x:.f}O_\G b:>7sݲ GiYeԠ4 K4:.`-z`;UA3o6MTW1-TLk=5K;9 vGw۬ p8"2:ǁIp8ʑi1`8&9C)`\WkZ[ǀZ!0ij5OZ&Hy9 =G0"v.>EHW)1@ * #6@MP;ւ6FT]r= .  H**i/ZqV0¡JB:CU~[3#BOKF%BUyOO | ' |}Fir 1 pʶu_i}+ 0Y%Z_@!R@*ᴕ=z * @{fJ:^;N9x5wmh-j )H*h" u5̷wZb 5h=TzzjI/sy#ǚa' -ZRլ|.ƵN/59 n#Prp,ȩx')8e.r#d4c 5p t{Z-VIǜ@aXy'E:tԑ,"y(msz389Nʱ\GHPE84q1+5=}w Z9nypwrϳz9\xm}+6r($y @ 떭zn_zг/~IϞzɳM-Zjn|_߀kPXYcߠQ{5]!Y';5~ΰe.#29"/3GFgb?ݙ2!Z&dR5e$TFRIe$T?mk Ie$KFCjLLgb:әo&!HGDr&3TX3J(1Č2r/yꟼj9o3 endstream endobj startxref 579918 %%EOF pycorrfit-1.1.7/doc/Images/0000775000372000037200000000000013554643106016364 5ustar travistravis00000000000000pycorrfit-1.1.7/doc/Images/PyCorrFit_Screenshot_Main.png0000664000372000037200000026040613554642611024124 0ustar travistravis00000000000000PNG  IHDR)`PbKGD pHYs  tIME,kӱf IDATxwxln6=${"(KQ4A?ED˵kCE"U H"5޷ !l(!y};s̼yj?!B!Bk>$ B!B!4^$B!B!IR!B!Z$B!B!@qxzyKB!H/yMdNri\_LeޜZ  .gՁ^!B!MZ>gc/>4v]?Jf,)Rw/O'ʌd]`Y۽سks_*#i+]lM޹B!Bqr}Ɏo߿sZ& "nihr!o?_46+vj .DV{ڴv^s*[!zw3+soXt Q ܕk QY,:]a:sj&-co9F"b_[&BM?g]S* !B! ES*Pȅ;xhH}yd9W[ʧW̟;fxkk9Y?Cè鏛=dW(L <[[:-GoƷ0T-ǩM?0ovNek`GyAđ® Xi?I:!-c:[zD\||K{vrڄLEnj]- 7ժ]T$C:V/`ɯ{IKPS: MMcX',;ܜC-Eso*>swPQ Kt#.>J Ia'>04?5 J|`, baߡ4,ܭA({w4|-|2ז-Z7NQB!BqS').UlؕJ(?:IӇP{X;m[kmsC[|-u1 -KY4ON \#=/cfKdϪ,JOGH +uk6/g?;hy([qdda3T*WG⾍lJ0`P8ʜז?zWqXj+B!(Q_$&8x2 XB"`[x?巃8Zxjn$88!PsRB 3P| oVIOr-^(@tv#̧Id/L{]oεq$!APs(bc2}eT @úO5+x^kpC6 HC[oK1x\tZ֞ fc$>}=/^ۦ3(*b$hW𠒯-΃^pL&N$I!B!7 )fbVf|6[>AA7A~ wp,\#l`9+X5rn66Zy)GIt:ɘ3s.,Osq NnpuH%E4}GHqPHZnًkR[r,O7+%tbc*zDO~'~!Q6'SH>ko ّOFQv݉P"e=VGP^XPy:p[=?)$dѾC82.zb&~ Ht"(V sXv;M|prH>yԮ };kǭ!pꏥHAQ VƣϦ՛i?gڗ[#禓؂Wg ivLkd_ssXfaZwWk z(M%=-!8 B!3teIaX+֊Ut=-o-3>[ ;:vy Ї[/6̱`xnOŋCv 43[TP5$;Wo`aڜABû!υ,=רz -o Ǣ+b= h =FdD:6~և:+ViB稀uu*nD{Yj&,gPz.J݊',uWj`A!hնi/]s74q i t/qH;'H r w"hA$y4Y~j2L8F$)B!UBoУ p6k֜6Zܭ7wWz5R(^zUCDbvs}4L ㈬>Y1EikaB!:1$I!WU2y! !BQtFEg$B\U LB!BIotj><@.QDEE1 :B\KEeI/ !BQ\$#Mdx(z1lKUK6y>8I}DJA'GN;Nª1 *fQ 7AݢG^gh'$**c}<ؚd/G~q1ET<|dr}G.8C2hidO<@M\쐭XU2;*.x0f.U(qQ&(dL!BqtFŠ7\NFgxav6=ƽSYtyѓo?BY;s>$A%Wα%3ٮz-̥6S6τ`T6﹅qN}&N2/io*"p cxvxsH>e3X t6{4d ˍe!=MFsoZ`s0=7>x]yzʜ$DFɠȧLTn.=('B!(^pYsRawG4G6ǂF3k,ßdcq?ǛۤV[_γw2{Mxn+˙-ҷDc<[ 7mDǰja ꡵gcD۽8G:4oohۑڸ% :v:W$[*اDe1|oɱAʢǸcQSa}1:Ln#G2T MƇkr``ӄx o>u4jb-Z![!Eh('&g{^;Q_P6%ݛyag,жǾWs>/1p ?חb:?|N,:`O2hijd Y}/ÍJӋ/ Cf#C%aX}?CLZp$* NZD#!BQz+L3M`)*gWֱso KFPrRPTR6f]yWKvbKYЙ!JfvWd „*_L)V߈2pݻI6S] N4ӉS9ߒJGa8/-=^u1Ae4A/XSoixο>@ђOVˏ^]ٻ*kC &7*g6gRC"+E Xk̈́&x(oD`=hFCi) )QUӫ@0a]9f CzS!B IKIH O7Ey Wt(Spf՜x:C/q_zWMƑ\3KuksF@ncțrݙM]-H<(,-<{IGx<k{>QvL.Eoّ ~Rqz ty .Hh&9>%tk`Kd6 UpCm ̀{aqT"8/e-KW^4ս)}WJ^VUixk|tv=&+fdD!!L+B!C¨S h(b4͍</d3pnwgu݀d'^ 玁9y7CQ;aK6{}%ODp7)=;FlƾχCj7-CL[MxgxBLZ4lҤĜ*Ji8]}:j`uB:yG˼1C/ aCu~=saWGOO%FLkڊ^,~NJj:?O1ݙ֒75 ty%%,6'9?>Θ5tV^_ʖ-ۂ ($QZEU!B!⢶^yKZ3TP#Rhԯ_u/?>+*||6²RU~]_YU>^>4xɵ}x ֺi`#T&nZ%mڶm۶m]“MfhҰJKPTƫ:/1˼OLӥx쥞;;ُ~~>.EhΠ۬dGK=+"kT(OgPW'vx>a"u{c&sF {0gF4]!љ _z:w{i﫻{0:懟V'mݎ(nD&'._ڲW$%B!Dy={3Fdׯ 8&ٓ vko[zԷwG[PdEq4]MYG1}ۻxeOn<9}RrW YdJg?YԭŞLGɗΛ&ek|#5F58Ӊ;SaxoK{g<+-&ɣ Jswÿv85܊r1v 䡑~wb;jGd@1QC'Ny:Kv4+C%;.#8} '.>>x- 2xfxwoxvHJT\JcJ&]qzAHo !BQ/wN uG7O_dL?*WSزO=A%@pt?OА{ _7|kv C ܀Wű*iTԽ['?-|[ Ztt /310[ ORF9{~5E ?dm0ޜNɅ}ʕw?j8l\hxީi$[' XCh >7܂*GU#e15p+ ]>s[Ԭߒ{I)+ˊ;M ֲ8AKq'*Vc_B!UEף|ڽ㢍w+9_9lܰG'uss`6[&#^ >*(I+ȕC;Z [-Z'B!D^zef U=.eȇ B!zt Ut 7d6XNv6vlA8Uq=L0ie'+B!(P9)L&7B0L,_!iVjaO]8_E4W.eU!BqczK;+Ng Vm#$zBTsJB\5&4`hDW8_$*B!nnzCe h)DžvT8D&B!ѕ!9)Q#M*3=OO4iQTgDaH ~i%Ad !BJ{`Wwv) d;qS{O|,ޗ,+D x~YO. l',ڟSJF{ ?8άYY p sUB!7J$)Tr3Pa .2ſ$oBkа9x#u|9ocZphO#6I2I~j#&B!n&MP%'%LX6y׹6NC̎ rvo\5 }Q{"ۖ|Ǐ[f> vXMIa iÆ9R~k5 fbI3-z8H5t4`~A)>z@`iG,l#nl,lU[8e;Og0ӸYk=-YVw˕u ͜VnTF? ~:_!jv2;>x@,R5ډCyP  rDi2C!BptCB~f7C9fpC9l*_0(,eӰ?9&в9TfoѴ ѓvVKRhy^}KPC5HC7֯8Hׇ(XB"`P+oq(=1]K+,A i #ξ5is[kzlC8D_ѵFi$88|o -lgSB 3P[⺥p6<") v$Si@%z{ܽL{3;+ 2]ɡHB!&NR=buGƓ&wbn(]ˢ[H+otJ:& [VAإP֩o⧬7ft:u: ap<9æ߲:.ޠGZ4Ǥ/kMe^LǢi֭]˷fO0Om"5wM&';dv;^'Z0݆d'3_P><qd|v3LNxg2 GXN{M#(^A_3`yp~Ъ+d/B!U)gN#8`Pɶl@ ҫݚA;SǬfl[v>N_ fL^0G2b0Eg_j7$!i '@̩ot>/.Hl51@~aͨ>h䥀FMk\jrX^slr26n6Fi;|NlydE? 3 mm5 ==#&u S?ȏyɣw}Mk O?Rt{sEQ I !BWI ?4 lR6rl`4 zHh.:lȡU=f57kv_3؏[CKYTncU@hQ5hLWSyGI/plJ[|m0槐DXtf͐v`м^Vf"}!aOH㰆XpGz֭6xR9[qLXFbEj#M`CNFW3څWP|M.6P_)E7oPǗTjt8 y||6f==ŞM.un6D/\Rn0BB!U 0[,kqkvr`Z1ҳWg{ɧyK;ᦂz}X ?0gs7D*RDbOBokTY i=cӶ_nbѯ-h<4⢞:C6VfMɷ.Qѿ^О<88yq&?/ozeUp:/Z JۙKX6o0xpn*\S3Ns;=xM~33L8F gi1_DELB!P̛箋6Xܭ)^pM8"5O/&^ܴI愨y"GB!43y%W\vM/⊪,cb&5!B$)B\ ~7yrUB!(gNvTՉ:+w9QЊ`4 œ[qyN`,*'T!B!7<a哙X22ɑXoOO/zhF@Lft:";uU=D!B\:EUNԔdvnJFMנEX RS}FsЦ] ˥p:GDSQEGGWd,M*Bq}s9Ia9-[!֪7;n}N.Ͱ㋇"%9_?? BT,M*Bq2FFz:~J{vUjy^Oxd]ϿF/NN`BʈLLL4&C>B!' nQj==l$7TU%?=S'̴rЙ̸s,>58w'"jSB@zT!B\_TsR+$Ei=|RƜBN1Y=˯nĶl\xnX ?*7Bq}Ds&;C!$EU[>'77Yf1z' -%:A~pt@a.hw PP 4Ny#ə5EB$B!e=rrs:mL6ܢm%]N9(tMUAutw`eIΤUWnSH\(B!z]R`~a,C`' 4rsa{~O&aqOI7@8Sd;1wqrtv/O==%1vV#ףsqoډY/8^O|ųzoqO3w\㋞:u*Wᒮ\4f8lゎYgYοvFȇˮؙoc$d!5@B\bb&(',ܐ^B!')\ނx0i+rSK5AtkJ?WP!!!L4PVjUphaGvG]X sWb4alхEk7sninu7é=I5 `?օsLj8VIS!WQop~P. B!Aٻ:~=Aa K9iܼ1 }m;/۲3a@"&cCk3F[J' 4gw2>՗];'~_m+јA0SEq Ps{ܽ6clc,^0b`NMT+uk>CJS%B&R,B!~l3*d'=2KKkjtxCף&~3v׶ksF|xw{g&;!|td_n&?A2Bۍ>ԷwG[0ˑq޾y<O"ܣmTpE@˹n%*irZWG"J(*_U;v ƨQ#(年o V$dd^Ti:j+(wd;j^zKسD4h,gc?ŏu1owB!B*sKӟ}xçୃ<;yYA |7g:I9Ɲ#c,hԗؒ'>| 1,f"5pm|9':zcz_Ɖmٸձݰ~UnB(::\B&B! JŒ̚5ѣG>i) $m#v sEkN蠱wʻI/zZ_Y'::̙;!M(s("BqB\seڴiL6q](υ5[:gr w`eIΤU޷jȋV;}¬ئ_Ϟ̒9Hݳi/?ʈ;}L]LQQDuǃ1sٕ~Y1[QQ,ؗWl'aտJd)4:u*O.zL:` M;(-ɡ84-?QGt>Tt:pzvlBQ#.G6yf4oք&0PIGʻD!B!D̞6bg= 1U 8΢̋|B ?$w'LYNny #+yr9׉40^?‚ ~gf/9o}SX +l|)s ^6 M>h'd^4 ^l~ ooa~bI $$I& ڪ{Îܻ-`۱춂&-wxTU6:)@"BGDHQP,(‚ uCEQQA@ײqU쀄&5P$S$dBx?3d9wf΋ IDATܹy,_iE+7d|B:$B!rd+d.$tLXݷHK;1ibtF}yoI[6DSFL~_wߣaʝ`N_z|f3OyeB!BgPn9)GVCb(Ϩ`II irpBm}cb:16#D|dX-&i7<CmÎ^D [2 8{9 Bi߬krrlrhd@l Rަh,ZCŋE5=T d-[рۺ`7?iV8L~ Bl]69KPľx~2USʼn7>S0_1, -}e_b4Ɲo.D/}]UGQ9Ol !B N9)Y`!z:֡È \u_FF|Ʀ-[YOō{mp>VV53Hy0GYOqk^LY[8Kֺ/ibGic1q}`Ҵx~o_gVr%}%;B!ץVJƇO3vaz=3Ւ(ow@=̏OaǸoԍ'Ȥg㞾U ֢G|rP)O wl,MĀD|ɫKw aPzBv ؋w返i붊zItlc×}|q򓟸2߲M8{4h0>~w d3.Q׍ocQ)ǪZW%q7m-aR#RT!O (u܂ToJ!Y-&Q3" Jٻlg3VdDћ09vpX"ZDa(owc'p,|w}7&@wr5o-/?c?`᾿̬;x 6gqT|k׮=A ۍnB4{E)++D۸u|RWEdiF<ϩNpKچvqɱMEIR%!Bq)HalK?!NIQVmܔE0~^I}4-`/;)= y$/VCHd#V^0+|ܝ ?|^ M::TL-Sy?B GFׯݞf惪/\.4\=wa=\ݡeF+)*_"IBjq6 *]DnSB+>i/(@(B!. ~I N1`̯)Mzf(2a&+uį#|1w9 qSd{v  %ܔ~7e^%>ܝ׷#73Bgd9̞߾bpݼ2<B7h5&}ٶ5֖6`h;3JUٸ1V`66l(qW[  spv-/O?OP&P3Fc|.8s0Zk۽po~l !B??H&_bzs6o2_8|hu/M@#rRK̃Zd^ c864:/2D!y_ƿ(W]-l~!&j$">kF~0m^W'c|߼2JFvNK=}|?x&yv(-dB!þo=1u4sR1꡿SQ 9$+Z&gǸ%}}p9-p=ř,`NSb&a,6VM[q.MgQ@g&Q#L[jB!ĥo7ATM qzO`J,~OK 'RoP)9 8N~J6[+zMEJ4KI㽊Z k6H_A$B!ąwa댺+K+v$ EJ!OƐ }HzlW't>`=<~hE)Y!A+̦B!ğw5Pшw yy4σFcDzk\!2:B!w=X4E"!-iq:rx7мe"bXk}MӰGʠG!.#SH!BsL LtL,UvH!fCUUvQPbbdB\fRRR2EA!\r@tLi<ڴj!W>"ErS!Bg:e[Pф^_EEt Ɗ6B!B!ĕ㑰i8e7=|%%҃5uHhh-Z`X ٵvF0K&:N:S!.3ӇB!9=v&mzHiJ֠ܜl~{n{3оյCvbymB?_JJ @tB!ՋN];ܶ=qMja a,@6N]ySG`P0+۷m&';L! l !By@ ?P5/luW[zb㚰oo:!aUTUtj(N`4ʲ!B!I}N/pVޯQ+ڵ)צMSlV2]cVB)ŸI4Ek"|!E P+J )2B!׺5WП P9 j~wMc[ݯ:9V$$^9NڶJays4p y:dײ/>?~FB?B!AؿyȈޱ=, i==C1*A MVS)HTٱOu5y~49OK)-B! k_JeO1zazMywH7z9)Bg1zKV *8= ʲ*ks=,c/y&CDlw_zĠb |Fꛥ|޴Z6A]5F0C/fif0׍hŋ|ys ̓!?86Ctl(>l;X +rB!BKIgRhm{&u5alKm?dPz߲[61zX^%Hv{|Ԛgz𬍉~qcaΜFk9Z[%#{N>x^!?:߃XHڇsxY;<= ~xC06${淸yHl?yz<㮱5@qqM"R!Bm&;/ '!naΜN31CdeLVL눟_CŻSy)z }lX>kJa` SN)Tl*Nf J\Ƨ?̤P O'4[t~Bl5=*Ӈtg:p)??rF*C=v9Iw3mXja)!v!B!)-HQzu{5\y<6r!o/blRX",'h2%>k&ÇF]G42k)[%K $??!Ye9HuA Df-ؒvޢ0[8q޿"2U*Kd#|.O\\FO?9k̚_ "J )uKM!Ei(J-Bq[¿3ϯY[cs}~$tlDE*MC8FJQ5=[~*ΠXp!w Fxx* R ۵ ŝ;xG-XϜoQmX.xcr̹ȱ&Mau^r=B\`)))'!B!?HQ d^ӀƨO+5TUEu\m)t!={L☷X8SS>˩)d6B!D.h¿d~3JGÙ{Lw䛫B!B+i̬.茺+KOw[v&&̦B!8A EQ0\N5/ﮡy0M;~N$B!BˑA -ْh4Iբ7Ц}G4MbFUdefijU!d6B!i&X >Y)3*jc0t5l];|x H h.7y;623p]pj^Q]44 fHFv!7|_KX4 }+àI C,{]~MjM@6QDFFbQBA!S()C!!FQ)@1w\̝ѣTTTTv NҷYM3зBFv!mu7]mlS<<-1zm9tD~tKEqEB!)z{әteDwƸC`2ৌ<GD\nE|·>}^fj6ygɆxwwl>R>>Ə|+zRk)|x-_nVrEYq9s{53KShbpܮ*)4Cҵ|շmLdCczqcA7M{qƳ?'~X.vq ķ{¤(u@T[S^%j <׵'Ma>;=%r nދ[:l9PA2_K,:@wxW)L^B!B?Yeg҂bz~GmY;lih~{ 0H/xL1T>_Hm<7#2!&kbWlVl›Ā,];k 8Ь򀼨U@ 7n\Zz[4\`0lޅm۹u׀Q^hغ|G-DFEߍZa}}\~yV6G3`Њ4~5w[Dz52b0pvIN>%ںEXRjCX| #q[O.57ˆ P!.C!8W%[Y$s8h$3dGZ߉I3S-tOˏLڲ,W'57e:FMUo i-;]1\<:@:<8*tZ^z-3C$i/[wfz/qVSWS˗{(7gOVwArO-[1}v~,[hDn3‰ד ivKTo7Fk=\vb^~Z&*L4#lZހkzw_`7D-Φ IDAT&&,%\*!Bg4vMc[ݯ:9U$$^9Nڶ^WП 9 =f[g÷‚<y<-3o[I92Ƅ +ߟ=1?WK{O]CuNH۲MomzI۲; (ln<҂h"`| '{Po%< 쨀kYSs?䇌d#!ĥF@QNmK*BqeP, i=}Fdl,ETwؿyȈ޷zn7rgy_Nv^߹2檡/2`&T ǓLKAóy~4ogƏ4gRٴu(++cӖ3/6m{mp>VA'w$ Td&=TL$1Es듯a7|JS0_1 pRRRN'R*fPl !B\)tJXgהdA-9Ή26zOYB{v&Vp +twu ËTIy5=ڟ_5Pj;7.rl\8HNJc8;~Eup>p1&F[sٲ1t+>DO7X;bP?9f)藙;)n1~͋) ]V} g)Ze?|)M! B RxLoJ!YZY6Y`Z){Mb\<&U̮P& @qb NJNY1B{Bj+h؎ m)v>_gy84?A ۍnBޡC*6nseנ9Jj%'{DlZް](-amː>]3? [gK62K/u|YǶ=¶ W&[ rfDǵg?9wT@u#\Y8dB!DuZalK?!NIQVmܔrE0~^I}(ѴHp祳'OGdЊ<=?8|j{ۓ% g~)nV7Aʗ{h.h{;sЎ _Nr/u~qwY:sK`1=L.`]KD3:7$2\'`ہ#Vf Z>24y ⳯nIn=39֝g.6@q*7Ŕ)'B!T9)Kb8xw*/Eg~Mi# 7CNf} 3Y1#~wዹ+Ix"sس3qtfBcb 37MYyqw'y͌NUv*! v M 3KjAW3ky~g9^UU6nLU(+;=Wl63lP7>=86σhYDZ9B^GDLHyPK 'q GLty֚"Y y}I0c_;9_,XPNC|)!zQ+ΦPa@ϳ>[(me'D]B!W:n`^͛uKa99㠖YWc#+?!o/bls3`F~g<-Qlޗ>Ձ~wplG ʷj@q|*o~݀{eAz2(HNnMDD8,!00| Ldd$j%),4w? %V*9Xc6ɝcZ{]Ed>Niu0F]l\\FLGwa/(R&W!B!!n^ۨj%GWGBF=g_dze Vvܙ%..ݓ׉Z;KSAp Ă k0ë(j RH'Q&:P[foZ9&R'$zj2a ?7Rz}|0i keIE)D N]uE/g] qN=Y!B+B>}z2Ccdm@UOcCǧV?.'DzL☷X8Ԕr^5ƴrvlc~'ڼ^q'B!ez!n2K?x6~p&ɫ=0Dk`2ş?ої-0ů9v26ggpjߟΞ}/<8ϩB!+~QwEwpIqq v;F U =;9R!-oaěTJd{zMxd֊#|x>Ɵ^I>OC|!JqBQ F#.Sz yy4σFcDzk\!J!OƐ }HzlW't>\wu|@)E!Bqq.zY؂TQtHHdKF^ӣ--t%l޸6;i*K;lL4MYm !ĥIž_().B!8w=Xqd2``'+p9eFEk4kPUݻvx.8$8E! HB!uiEEQ D*9iṾMQUMfP!B!x8(CU2(&n.**N`0VB!N%B!TŋpQXt 򱗔H/`4F|X,Vz{vh"f#NBQ,B!DK{ZvMچ4k@f-Aaa9fE}ǫkmÑÇ6jR!B!rqtkv۶'6\-A4lҩK72`}fr  BQA|!R4U"FA~>!s^"}fFׄ}{ R*.UUkCQt:=Q!B!Iv ߿"w'3)NX8* UuSTXȦ8z4³d2KN]diB3B!)."oJ5S/mXGpH({bz5?gϮm%oBԋȒ!BO*O̟?ÇW?ˡ9t[!;uЮC=1|ˮlSP#ZvnK.^-Lֆ4k׃[UFB!B!JQdty ;vΝ s=z\zbbYrb?ڕ#;m(8 ՂPݔC~7-dLxo( !E4eJ*B!.yuΦ[ݻӽY(8߿8CN}6?lUmyȃÇ3gG͙3Zxͣ4 GonӃ(͉UDmY9Ǖ(O K>BQhyI.>F1J#Fh }H~, =W!m[>J{pIbaѼu;~5?bƗio<$T]o07N7’?AzGHj!B!uWѥTG^1xL|n3Y`!zas!zX^4Ջ] Eځ~37/'3}B@&H"%8[IMBL~B!DQ1B`gXjߧ,xy--`Ҹ#!Gz|jםC6 $Jr\k@xE! !$/~B!OѴJL!<1n?+=|MΦ["RHV>,bFY1?){IfsY #K2/cWl/x!!/7⾬,X #226~~m3mu-./w&i*oYWT1DfR!^ZX_?4}z2Ccd9SU==!<Bqy"HEB"[6ڞ&ZyڴX,ZۨL4MYm !B!>8Ha2`025t5l];m> EX;=ʌc>?R~[Z9&\9J9wZ:EN!S 5%/B!.u*)v;gr&*G<~N%йOAW9AcS#]Xʻx+΁s^\˒3ۋLT!EIAbB!\Hengܹ2g\JʷzbbP) l~=.s͚'gE)ͦ&[R3'$4HB Bg9 B!E4Iq{:#̗{h;|/+E)49¢E̬{Μ9}hFx+fq]#T>AsQ5ܷIԠxk)N ᦩSg!o}# 50<+X[oDVKlgM+b)?x{>(wGB!BzA 3iA1G?ã,O44x[W 4`ܸq'+.HB+eS {{b`X8ce2j@QL0Hzpw7̀ &]jn%l*|oCH!>|/WN[c8G7L~ !Ĺ2%EIa !B EV- \&m/I oᑖfnwU'άN hi,[}A/ 1ZDQw+m 8h"::ɿ]vb9O$59Xi,&Sk0 `./(mz($'BdB!pMc[ݯnKBOoHۖez/Կ*/|ʆԝZ,m'ǝzf\Ϣz:_e8E޼5'cNW}$B!n9)Y`!z09ؐYA*^);S4>:6}gd+?&4 _:̐8SF ` ɼR/HFqq1G!;;2'"",$9d!B\d6E){u19 _mqM˗{<Ɩ5f\^>CT<|-]ߛ/?'O}lbc;?#9P唹"nvɲeغu+ٌ^prZ\s5zDFF(gڣul,C%B26tN,+~G >3| ^I>q9C!^)U>=.&Q3^[}s=,?A 0Be LY27w3Vc7Xͣ`rRu|cCqgb _%-z3yU7 6.lĵoѼYMeԁ꼋4=Sdeeѽ{wH AQTU}_Op뭷rwB~yw/}f/gyhxڅ狹|q*Ƴ3F!)Bq9ե1-RAiW ~ݪ!转S˫)noZSLQ|hvS<1sg@4'<M^y\M'OOWv5&@ս)Gy~vQJU@ ^}|!.}a޼y1l6Kxx8:ubĉ̚5ƍ|0W&[ rfDǵg?9wx_y4˝:~0Zhy⒕BjjtB!.7uI_CŻSy)z }lX>kJa`v2{胬LɊi񫭾wq qcZEYlfذU|U7BQ>q0Goѡ `qTSD*!ϐ?6 3};gn~\})(^zU弯i)(йs#-Hb9y>%mKX8a=:moPZ:81&r_yV!B\N8WD/19^7/>4:&p9)u%rrVE-aڵUɭg%ϐ hilIRGP-Vɸn9%'`ȑ4oګj~4 ti` EJJ,}gãg[z i$BM@ŕ IDATP]QPEX*vAĊ`W@h}wg?{ $p>ϳe[^~`~y>zi0QDGǠ(Q[!%)7FO[ՌKiTz~ۛ!0{F w*JR \]ܭGbTsg!RT&M[oue+fl;iu_3w ͭ~&B!u+IQc[8q3#[:JU-Qckk[Q9IUUQsr$⚕Mfff{{J9xdp!Qkӱ:9ץledھ<~䦱,Zs!D]"S=BQ(j¡D~AE'W L&S;~h2W\>L&SСC+P%[N![˶s4jU*Piy!*Pl  ي(wdʇB!Z3glUUff&FI."uI&z<=='=b'MʦgqЧ#̟Č=xaxWR^7{x_~+l{|gݹ?&i.Q!,BQϓ`0XjHJJ]Czh4q\<^^޲ѺuKw}%gf[oqd' t"Prۈݛ9sжm[Z9qg殻@ !B!I"U-J-X{Z̉U إ9;_ƶWkiI`,BQ60ٝ{DFRW-ţw-zۈ 4۷3fZjEXXfǎ4iҤšB_O|!&VU}Z^(5> AKhȫt-92宸G#2%w~KU!<Λ3ypɒsUxc4< E(Tu(jmrNoa37?9!yo}Wz933]3:C2 pD+s1 >fg 5-OcX'dz^٥ov.6^(ό耇^`瓙(gRN(lk;~olh zq 5U\ZhQ9DLL Vb͚5]F-Rl !B!ĵz#)N/|]sT,:3{$ =:û?\'nɛqtafų}fvwexM{~C8[ٝx7AzPSؿh`S3Bkl[ɶ[낏I))b7> zu:zmtf9~Ӟ1mc|oo (yj 7m#==n wv,>3EM}$ ;ñˊcnҗO6 1d~S'_|{h!xۈ'* m݆lh4!B!]™ijBZe}_iN0W'X=.%ڴM[xy󶓪VLaT4"{;] X5-T{jgV/kwho_ cy$lXwQd'FpJ/AA5ˎ2}4kӞ:4M> ZGҨQ$][}s8#ZҮu ZwC⑾ '2QS2oĸa=hۢ9mE Pźtۈj~(t`cc# !(bbb$B!p+/W;QXAUQM!ۗUVph4Լ4UUQLoȒ{9{ e+KKz7麝$S[9ӀM gԠ--juڔbo#7ar{kVȤ?ycXH8C8.dbU~wxLf_;@SgcUU2ӻN iٙm}s(8F4*mS>6BQ_KCTTD!WJ5 ;jޒuU jD6Wy-F0k,s/mPDSF۱Ք*)ئ!34*^H;HNkw] ?ܕ/zјDn\uۣAd!Dvm`:?u|V ;:> 4)Vwcn ꢖ=vE=y JG;2 7NrlYe):thX*Ղ=v㤪hս^F!D};BB!zpf];beZaOh{Y1;3Kn5MgŲ$K]|,{a]ZPƜޱwlqf;Itҩ\v"n|ަ%¥(yh*.oKM+pfXcs0㧬I9z쌐EU˞STV;b9MDKW;&%2ӊлZb] 'E^h*J^1km$(.")BQjrUC v;57C#e*fqw4_$ 7xJ&t&ߟ"w#SԆ,8͏4ǑVXF.|h`bAL\-^V[ljc9J-\1VMM6هj3bBM=Ŗ4d\0&7}&+ cyRУg8OXBvdt/{VܤV$F0sNNng`Z/> 3|źlWX& -WLy9;wsr4((R^Na4XOܧoi|j'Ny0tۦ?GhxDρprv~>LT8s?dʇB!ץ!5%WX~{Pd*A\ws^VeETL\8v429+]]( -+YД .t:6W&%k٫%$7񸿁. Rm#LB!RC)Zn/]Fx}Kpit3SM7?J~GWQ]RE(=RrG}hZfrfsL+kދO &̦:F!B!D&Eu򆃖_Cޛɐ-S1odhmQ0ځBFh2FR鶹tδy5 Y>gpoElG5k -?vs:CC̍b` Į^ȯ!>|!i,ϲm9q huB!B!Pj&)TRc'-ym( \ulKhAwTIcýB8 VQo: ڐ{ V|˒𗹻 L?B |;!nb>q 6w7QBsD/fg.>UYɾ́c,R+#h,څ[-<Βɖ3M0Ѭ}~K,y|~kvƓb _[ B!5zI E[wvchhq#?<;DFb$ []CmhlٿsLЧviDF6@y{*8M6þU4xUd64捴 ̉c9_&m/+7~XuF%ͥl:MƶEr,_['p_obbY"޷gxO_ @0/̧g͊}|%ye#i޸pH&%RwVXȀ'?6bf)ǩzӬIn'-l?c&LM$;6 4 }}ĠT]ދhdSL;7"Х2;Y\rwAQ4B!p"-5rr|v3=r^BHN_e( Me O0fFp48L~CIT=IQ]לiV]}KQỶaܜrа-ax3'ZD aXiQa=Beᴙ*׿⺙m˯O% HT1Ewn2ѣGWZ:O_bqtr޾~~[cbYo[W|K~˾1xfEsE!B+HpUSqw‘ IX,/`F>7Q0o!1.S[5 :T섈 ZEG [Zͭ Lcih\U*[OQ:]^l7}Z~AG_bFŋf]\]cl?c$ 7"icn\lWL'2ŇX(BwJM-BQ{&NϿ`6`MÆi~- j fIk%Gj` BE3rP)w{YPnpo~#Rn`xap[^ah~#\;mEN斮M>Ƭ﨩z+6xG`X[.&#]ʇs0LjER5{zG!BV(7RYyKNZ8b9jS_B?u27ELL %B!D+7IANv&~p{Sc=NƶbɇuK7'%.2;Wʧne+;ehc@g-$FY/ZxM+XԍHqnNOZ`պ[M@+M'1(粋tܜqK'f~t wǘ@|/;c/۷ ӗ,V:L&ӻ%m|rk7>Z5sY̊>۩n֧5(;.ޖl~nؘ8p&lN FrR">X-8*`0ooᏌ?ϱrqz,2:MTB!I [;;2.Srܭjqz IDAT[tobڙH.^!ؕYbr C7DA9/~%wXOq!+K L_>ʽX{vXn @Ȁ+/ur]ãN?||FmUI ; zy|R'ύO1#ŧY[y9KQYtק̘5(;l.jI^O@c@ xr#N2LX,FSJR#iOGOR*+o1n >F/<ŧTw\":Z B,7[ӯȼR-gXln,/ DB\} '+͒VHl9 ھz[fr£|:_a豬2ϊPmV/$dm !\KSS|~&zBk[cYSǶ'8ӏ;#̏f{8gBy]|b7'z>}eB!5}I/!B\;x@tT}f8lT2{)9ZU+87Oȓ5l21 1_B!BԴ1CQWT)\E}ILL ѲB!ףUWtU+NBQC4M!BHRd#77bIa8̵Q#|ya^[{AVӿXk'Kh[B!BQMIPBmp9k.fS+c w=n5ح87bָe,WuZЕf=ǿKױt~B!BTO{(6nnSjk_CZ[o0|ˬVˌ8yっABWv( >'_ފ{٣Zu-SP#ʔy}:[:B!AQ$B!2"{\i&K}5 .8 7ƜNXi1$p+S!B!FZz,GMb}9/U{?a*#7O4 Y/q4~)9c`{>("Z0~:x/™],NR29Xc/Gי@CLea 7Ȼ{C 291[ۄצ>A+|88{mhzQS`™2iN.&OyQXٵxWlj4 s8ƽJ!`Mb_dzAn~b w6Hc/>N$d49o@oId3r+qЖ~#V'~v,<&oa,g1qτ1OFUzh9kcs OٜR!B!f"'SgV^~eLL9-͖2YMyVy޳8;O;2}X߽3OƦ>ϻ!;jb<>x߲ܰؤ҃ʡ]1rry(3n!TN4B4ގƅ]ǏGeshBa7 sb2^!-C%ٴq6vE=o} fO f8hA., sdǼp?L` -I({Ȣ?צyǡЯ_o:5tVFH֒^u|n6пsW; !#[Lv_ ʖsȷ?mdߙDtsaAb9iq} rGwrLT0I+N-"Y}?UOR \YEq="&&`!HʮI7 hp9oOxDENOvM8sSi{=}- k*ӣihaD+ظ9Z52Ohw-i;_!aV -{70m-ۓ:}>7*u ]D4#kYy{ez>3iV4SAsHbEAWG X-꥝?& "^FS!My۠><3yo*Θ{Uy3܉'ە*7BVZEFF);@@O*O[ZW>[9U(8vYnlwԑ[%1 a ғVX<$v:!^$oE,ըѻ1 I]wϱ+6C/GH3M~xn}hчEg,z4tB?Z8sC/:>ͽ1eL|!䄦E(QD!BT}yA<3l/_:ɰg-|%ODᑇl~ ֈ.x0]eڙ|Nu7V&aO+6 pnψ1wlRC ʭ#Fr;˾@t ) E;͋xl\iШ #^Lf;y$y]MCn)4{0gê`6>tz)|+9BiDvuYF<}PP)-xۙ#&e}@[Ox;m)wcT[8,ϐ9W!9W<+O8\C*B!j plO^w 3#3iJxvJ0so&v׳֫t|ɅLoMZ:r|*O"DDGHB!2$KgT{$fN'))GFVv*Y_ѓe_޾/QE7a_wGt'κ@IP!DoLC|!OЎ|̈́Fuε9/E:)egXi4ћQ"z폫įbtO !B!ĵ=QS!D1111DGGvB!\Rt+|&K!B!N$B!jE!BTAQ$I!BELLB!"I !B!B kMYfj0 6fTՊJL+hBύkff Ot: c1YY}9t #dBL!J$B!D%$idHMM%+3SZ7pusaX8N cÇIII&3#CX^\F<< }3 V$ADD4itRSSHL^ybZHLѤ)M$U릿6Ч_E]ΕSdffKRBQ5*d4B!*Ryk2W.϶]3#ҪM;Cäu+J@ ]ع?wRfl6s`_mf:vV=]\ptr&vN.^ݽ޽w!(Ott EtB!vB:Ŧd_;8`l kA;箖U)d[ɔ7v{i){HV{vJIN^B\Yz=a;zwze$C!աUo_8Z>ؚ8oVǬiشd>s$vdݎZkUIsNNu$%削E!S>BQ')L͛@żƐp¤DH\?1kI(8g#2  ~z?K 5:I >f't!Dş|H,BQϲϲ uav0vc3x鹇f3] wrg+q\=232rGYyأjֲMBFM!BTI Kq_Vr!۵E^ dNE.#Z##3iӧʴ,x裾H/B!Bjv](J2݂p# y&Oݗݻӽ|w G2Z6qrSt{}>S ~|۽;o#sٙ\X^MOo==ӽ{OxMƦV\w/a+,c9φֻ;ݻDWM#..iӦqǴiJBk#oʇB!Dy;msϐ]ߠGv_-݆wȻ+#"_/B!z(J֤лoݜ9;>ޛ?b3貺 ƛ19e/W0l63417^FtLhbssq4]ESpXȶ zI!:FbT~|aڶ/2/fpx~=cѴē΅Tlxs%.c~`@ xڻ ousx =!+.';zB3ħв/~ΥNW$$B!BT?V EAٚ @ڶiQnnZVgo3`wq=n:s$ӁdCXG"l8s*p}rWi 'Oj^՚tCI:|eK/J*7ޡx?in:#X7||cC~y7W;Il۶e˗] ÇKhHHs2Bq)B!.jfc$[Z4BH?ɿG5,9ȢGz1˹Ny*)__$EV-f̰C-6DB!B뙲plO^w 3#^̌t6}oUZ͟mœSYYrEb&b]Bx]HJL,x.>,a:ށ{vҺm{2ү_8B#.s0UI2 qI;CTTC!NKSS|~ EM<373P===qlmm=/wR!B!>IB8Qbw0L!wB!3IR\jkJΨM&$W!^.Ȕ!B\cI Ea0XҲUGAKT`4JL/!^hZՓFsiCMa&Neh֏G>L?W/~ғO_[]e:B!5ۧWkvSoԘ]۷a6H W"#=#q$2G=It4iLbz qm MSqrr@ hLdVί}hL x/j/ MFHa2B!H Ɇ@&b u脻'ܘc0Xrˉ"q5A~ipd~4[9ku>A`L_B!B˼ߔlƪZQe )шPi`clFUD`4s㚙^1:Xp ȢFW D'NeXe=/c@%#n?UJAQdʇBqΛ׿ vdf׫7i99٤r1RSS̔V.`͝a8:9ח}OdcӴ4N;>:kv7R !4) V@EĊb=T%r*ٰ`/EEx_\cbIOoOxxvE @ '00vē۷n!cj9_QHD~)DDDh޾o6l%_}_Q~b^vaɢt:+FHH֚v}ݳ?SnvKСii{+|rss 808u>.gnLQQ0MSli9sQdEScѰyw.}N[r:Yz={CjZMr""kN5kѵkWRRRIRR,Z>}uCxD$˖ŞݻU0E$+KqiꝤ7G Y סϿNv>vD\FBFg{,"/o1)> K-ZoGttUZb'iIMukm HHEDDZz%)|GMuu=f.eƛ({>73Sq$ܭ=5naaP544YZςcǎ%33Si,z4X>u_niDžc!IC<;;?^\NaG-y챝 Y9W.%Ճ) ?hHRN|adJT6,Z?r!dUN;F3fN'W"vpnKCo4lﭨ&OJ7$33c*"""-I WBbcoXZ_?hz;+nLETZe)'[/mN«¢"&LȾ<&LHaQQ<ߛ|zPBDDega&'B )9*a2w M9<{GǥC߿?q*.[f̘k4gHW6?8KNOgpŽު˻ػ3&>p3}*]3NsTοf,˧Q JW ϰl„ غukm„ +*DDD4>IalGkYSc; bdžUqeNg&~(_a*Komٷ3&w[3_kIр_1=NJY݌yA>ȝW?d`՝tz!iЎIJhogG&sLv~ώr45"Y1zARRwuWEDneBDD!,>ߟ{?g=eě[8P'(lQ{[8)2ǘwUtٿ'a0f|ѱ,\̻ﯥ`=l ~])k"eWpAj1uGi'AW߱߷rBdБkAPO]rhw91O)DDjz IDHss#^W_*zxo|33vklf?>ֱn:mpq.`|&ctVIT̳{$[CŹcK?O`z F6 mf?e;eA]&o/4רkZæsR dE JFz._|经u=o""BDDW!=$n{Uⴣ]aÉQAn1 RL]˗UaORXæo%ZO˪) 0,ޭ]8.yC/y-.Hd}[G}ҿxmlYo7 `ʃ+&Yq[8I50 Ue1Qdus~䜁l63LY/^žg1MμEDDDDk1?حѽMm`nn}~#nhC$ylWш*L$(STiMRǬc3g~+4_})L㻧yF8|!4MoJRGM>DDDh> 7N{:NwEm@BoEk)\'V#G_Wtauc{kh>ʛ(1ЏNlۜwW'z.|ۏpLe*I!"?TDD1 GE˘柄uHBE_ҟnďfJ{<*%؂{ڝ/NWvqFF>z{aAudKO;Qw|4 DZo^mg˒cvqQauŠd/O>%ΘG:Ia̟/AAA\q夵os$\>Ia吽Kxgn ,[luI\#{X~HGyn|UL{/ c\nn*4|&w?{'G[{r?gP YSP#j1p9)vADoC]1;|gϣiӦ5(rssIHHg"""""Ғ3m >j3BB(*,hR/gC&2*Zﮟ>ggii ls0`]/zg-6}gu&9{O۾}oW^q m=&44KWc),8[ֱA}DDF/SPI~ +k, H;/ۗ[of~;W~Z 4֭h:[+iyH3ca(I! .Lz*P}t@!"r0@SDDYr(-ۡjR` h}Nԥ@Y%) ÆrSN^bb[t Z57.GLL " ;Z>DD੦DR4nf13,ջ[‚:wmka##˖-Tǯd22:bY&uس{e:A>DDDDD1 hVA$kG@` _~~D``III|wqӧ7ɪz\Ll+RRTE!"RYYc1L5i.S m Ri&(bj;HNN] ,*oѫϱ~f--""RL<ǎՐ"""M]8Ӳ,nn]5bADDjd4y""" a>V>e;K?dBMWUP)Qt-^ &J"""M}RX}z4O LS7 z`xg0Ik:0BKJ")>x56DDDj M!""TF= @umB݅l]ʏc72+H1Q@24mB/\oG:n{'ɨrճ]{Yk՟l)>yu3ƷwW_߰ymX}Axc99S8?qϩ/3mr6)~~}mΧrn?âpy7p]6K8 /C"Ĵ{SXK_G~ sݯuWW=WLH⩦4HHRTa ŁYO =ĸ/+ӻ3aJ>+J/↌b1 ͋Wqn:!%[ỼR%uogo fؙH+% ͋WbApJ|u:oLbW12%?9u0W90]>y%9Y}`+xqVf~~?8Nf@@ֽx44$ĕϞMKd9Chs+o3I'dG%Rn Xȹ]6(&vBOtAl<3s7rt K1G{+ uA҉sXQ' :@G~~eKv꜌ДKm;8@q~)jl]6:pSm;P̩S_Gq/1z/^;&"""5QaY0v"""MAAB e"3Lw/&?͌m2Od2^M Ӏt$|,\u68T}-?sh7hq&6nLvYᎉHc䄈Hg]5'_6&_t ~w>zG !4F_<mZ?6=;$:WPg"4d"F^?zn oM0϶o>v=7|$R1mj3BB(*,PDZ[ֱA}DDF/SRg."Iy_KJRK}5N?-欬*)DDD1,PBDDDDDDDzIr:qnL[ѓ&$4LA)DDDeQZZBA~>[6o ?"EPN](**EPU+qLö-),, !!Iod7̷p?%__ǓA$AFP=72w~ig\Ʉ-o`d".T$*TM!""Ar:ٰn-ݎEJjb'wW5{,z5o"##YrssVЛ"6~!v`'G0)|Z'y@1,ͽVWDDDD!qeYagBm Dm D6ޱCo,@){cлu!g<\EŘQD)"M[M>DDDpBȞPVTRVA'gib +|EDFQZZ7QfnP}ćC|XB* 搨E-i\| U'9M8VHXT4(?3-_j4M\_Gkx⚮F-0L@}S4c$4IrzsjSI iU:YjB[X+"Bvo{ w>vɅ}>;sy4+YYBDDDI z')|+$[ECŴ Oay߿JW s$r갞Vy|V9 'gۏ[{ q;ְzjVDN*nDu)""rq=l>",&LG/>>/KVf-ReU* y׆ỼQq_=2;Fy&b ឱW`RetœcVj~XL)NWNebNyc~>mTixi(ibyÛ(ۖ4aHz\/^R`|O\ވȸOFOhHSg4aG|<}//`l2O91.\e;[|`O8xg>/ۙ\>?벘$]V6GATiͪyzMEHOTde)A!"":=–2sx}Jœnc48ևb"ンɯrk0Ybvȃ:M<&~i^Ȃӿu.8~vS39!R5W%K2eJ9r$IIIW֊DD;V"""GPCiu(&ny5R#F0B:anN_3YPҭ X능O߄a;l+yq']nJ_&#\DDD>=^,cXaw`]^ƀAKT1Ӭ|tx;gXePP]z ^LII ^z X&VE=5 ,*OٕmKDDZ659ޑ|æ&bz`R/{?o;哝[m~=I {݁hg+`:i_~K%!L6giOei+USg^BB=^B+5AZܣ,YQêիw56mԘwW^w["""੦qHjO{n+lJ[^`\Lx)'swquÈؑy,9Sy (s5Hi,6$& "qaU>FpTdOGɖc4?g"""ehŇHSIRlִØӰ*92 IDAT9ZЁ&߼D`&L''ߠ7%1ʕ2ʓe9CaP2<""Rq&5:ȡgX&I0-{hMP~ŭ( |yeT5֛VCMI i,=,, x;,*%% *:$,*ʌ_*)CDDJTBJRH8Ӫ(oD}"5kegAaaxj(<TJpBDDPBDDQBOs +I"a3{ky; wY>(#gp~,{+Q!""rHIa6.Kqia0 i6 ͎nw`m6wY O֢bv >]J2C|(Q!""rhwd颅8NEN+#((` !8JHBppH!!{yv~e;(""c"""CL@` LrXɌmKI))""""""GI O)Ĥ$KQr*""hUSXVE5EY9㫁, ۅۭ4gNݮ@H Vkw*:"-T^n.aYEDVS fH"-iŴLc[R'""uHTd9Pe#:6RV'"-HHh(mKI """$f'44(jEZ EII!"RO OPBDD?Z88YADDDe BiԓC!iX#xFUU09| 4ܰ |Ƚb]kl[11mz7ʳ&EDD:xᩤ(_Ia2vf2 b۴Mlvf|U~=7ݨƨ)?w9"*F,CDDd# ֑R!mB1r(N9gpp?Ql]DDD ߑ?CDD:mCZzªH?o_#;ygsgF d"lsӭ{xk#ѩUEVG\= )Hz {\I |__`S9b>}Q6s2Hv؁[hF'!)YA9DE}%*DDDSIg.L$66PlmJj>) s ܘذ~8/ nɤ_x?۹:vҞ+NQa "RO#DTگm$& RLgQ=@Ia)& YzǤ/]wj~hN}Vƕi~k7+HH#J^V+&h0]pQ8q0{ ] 4d""$E c[aYPZRtk%2Y)'L"7mz[d7kOaFYnckolҫ^u嬓[ܗnqzEJU!}m!-ّpqg2{}WמcraB$N+""ҜUJTH S{!!Ī#9zp Qt>;>j޶e0W[@Lr+ lTl|&{=᜞6G˾zoLc:9cg^S^m}X,>uv“{pՃI^+""PBDDZPbԷgSmFHhXkcO< y?S`x)~v'ȨejXiY'kdEVX%+DDɟ˭q73?tCd9p+}=DDDDy(KNBDD0^7o![(;U;@"""ҼTNVBDDOq9M7}sndt*%"9uԃ pihTBB:׬(4~'),ˢ|lDA~>ӱH=dtBQQ ۪-i7UWHswv/7˖СQt|!V,㘾ݻm[6SXX@BB.""Mohe """MsɆukvt/RR=9;iCeY֋}FGɒCTT.""MWq?+˓PBDD>0{<,|0Rړ0YzV^+IqNrϨjaaax;,; """ej@+˪\e0OurOV2>~>mϊlܫ\2M'Ga+?*+KaxQM\mK&[X+"Bvo|!<.Z>HLV_ H0I>$.x^䛻fԲ+SaYO<ec䒡έdbcpd},`+O1_\h#*T.w\KW_;x.5{sL#rLދcY/}MFdz{4,~χ6V| O-rMN;&,*URXTNR2B+g6Rܷ {2 %[|u-jDD_R*b?KﺔQNa<4<Ǔ]SZUoaxmw5_LdݷS4Unp(td(:E緵K "]wW :_^{w?MRd?n'];n[y+r')ӆ+{^I!}Z&{01>7 a8׾̿Ekd‰vo"m Z{x_d SLu=#G$))|r%M-Sd yHMW͙%(eGyquZTZ`"8bx6|Zq 8ܱyJsx*PCiu(&ny]^ _yuf cD`` _UZ.N;Syѫf~K_[_GCHnjAb/V6]H`7 f:iŹժ4r%3]^(ӧX;A_&WAXiZ4+,$!!G^^#*ycy]^aY8SDDDDD0AK; g掯dܟ'pEI}<t guXD^ܖ8* ڞ٩kisٍ:|&mlLOsؓqN<#n`hD >'V$60/ l%&ѣ>i݆adO\ZǿBք83oMq iy &*2ʪ"~6 ^zھ=1U00 00,,w[""""""r uϭ|q峔tƅ>̈́o2>yWw m< /=CH:'3}>y/9b=Ř7`"} Ϙ1mkTFQaAE: tQ.εp5pS[F8{腔70}<ʛ0 ݎͰ|i( ~W]sc-3S0`rŬzoZ†]w.P<]>} 3 l?];1YF,o_nӿ:D)?mmi:[~6[_JP4FeztX}S]eټ?¢җԻe`3MICdo!AiW!M4Aś@1/oxk[;3d7z0IO]HC& ;[IQS1ߓaab>|9oC=DAiwt5j8noM`l+ԓ~?[D-5~ݙ1[XE߈ų> 9;a il^@r+C{c7kk?XHؐq\;@#s>Z~ U,7[~lߏjlCs rg޿h/Z櫎夻_⃙?2{lf9o=uBi^2=Ior¢oJ[已ˍDF>TI!r|4g$qiD^syb.|v2 wUoq(OWQ1f`ARU7U{Lz.dlFK}?6\XsPz?9mЦ=F4eza3޶&ky; wY>("gt9x.ukGt6Z r_R,.:<ο&>kHPC^$c# `Gkx⚮sƃ/QŒXtRߏ O>%\Y$9б,0liYh!ǝ؟pAAwiUN2Muxz*_,-!-.r0U[g|IVYw2ab@TBaiMJ@::fc|׵srdmw0vɅ~lx}̟3YyӅL6^{rq- G   ۙB+41Cv)?4[ B1Kr9t#8лpiGcʾx9Je o mOD'Hq.˘3n.c) K}?}Seh;ya|QEKx\8O%uP6Ĥ$Kў&TAi$̂mV@ɦV1ְ0TcOo?u9YzîCINC{˪IʉCwg7kEӟgAl!\?:${laCƲxHͰ-}HA0ʱ<yrk$(I!"""""uYeD` IDATxS>fcZWSfgRVo+D#iHSvxV);}c>w]=;؝窼峾dGtn˕f`{gj{V {6ckNiGWDDDDDD9C_رz%$Gőԡ魃o#35#)yB =Lٷr+vEҡg7bmuϿk qYs7+!JDט0b5c `gO OY0+""""""MB=.rVgc~ǟH芇In_vMy2b7` !62\ەYuKX~N [`4)}'#"E?H kuj" {vn~L386ճgI%9ZUvGxRAZ\'`}|GaC@DMjL@*և &CB3rBj sdT4hSE\U)tD$ޭ;}}i߿3J1{E=O9ULEٶb7@lIrR|xmmL*""""""M;IÆ%z] bSܻ#A]kXOEW.?יԓNQKJ 9Zz,zv8jNƬй" orV͛y|υ"^J;stzw;W/c؛V\W@b/Gb+*j{d:"*${Rb,BZ*eX1o1{W\Hc'9%1'9;0"0/;)DZ. \{_H3ny~x2*]o'AS|SĒ N4b=VN'i XBy/RB!#HXU(;Rbm~|;B8ٷz5ۈy4pTP}Z`MvS?,ڸ,:F↍ڠ~F[3Q)(QT%=b01{-#ɘtx!5Z:>>\yOx& 6aΌl1.VXP o i?f[̽91P*17 BR(fb/Yö,=ذxp O#+س}0J.OØU)rk_++sbIrS'4x;wg,[+־mDmcoDaTQm'1a/Ϲ+بfSP҆xuR~f8ғڵu@Dz+pIEj̨j[XZfaA@!K@7 :3j$M~35f bԘtq9c)Uc9{D1!\eBѽA @&}\nGT̡Ħkj6}5L6"BMu-Ta[$3$0oduٮ!;Bpdag۷&a1fjcߊ!$ՇAABx*x29l۱K5vUqp3=x<#,پo=kvꠘ'&Od3Tlyh} M`hZf;V!(]/^c#GڲOa.J2C-[b÷,bEc3ƐRl0`TQ&~iN9?Ic5d&MNUBn RhRInyGƥh%n0cOahngvǓ¾B)4n) h_+1chM1'3ҷ#qYZf3SRi~[:&#`'cB!z#'>%9%1̊wl}gRTCkJ]o<QǶL93/o1nRXb{gs@BtޛR B!`3;9Ѥ[1*>JwvwSL~ﯮ}p=-:,,XdD`R7Ix{էb\>Opr/css~BNgOiD-JWԐaR Bq}[\ƹ朸^;n=;oxKosˌ\2Έy*5e>՗-IhfvWB!ߴPK>)%mUG6p&>1BcQm[ ?|Q7 Alݾr7jߟ} 7)P;6[fx2Ok4µRvZAy G]!AoLBѣ>RhWcN̠oBdXSĎ3c{&VagXQHk&-?L~Ǣ{J9k+; 'ʥb] !B!F=)t|>po@Hj`RG{8u.FuUnpk39;xi|=N|6m?@[5& !B! Em\ q# UP> ͎¬{ٶջ!!s$P +@Ft/ S̞غN wȍ>MI(tUn 5āYgT w )+`m17Ea*EY֠tXŨSUMW9 F XF3愄Ԝ@;fga1ULfcUPwۖ ȹhr|ƞɰ~5oL 8&6_v͹2`$cbxl P!B! j%8+vm#ؘt!vu1!T :ʎV=6:~M= DbuvR6 ESRTioQw0ONѮ%6]Aw@5̓^;d$B|J2:≫3fy{ + PGa'lDc@,1^HxE|1<o>O%LFn"U&B!"b쪼uW1NB\_7FTTkz;4 `@ b!GVb>{WbIw4Ўgɲ~C8 xn!>2Vn?@ M1@ۈwh{ڮsєj Â*o˥0D'=0Ñ.=)B!B *Cu@EŠ-qܳ[\$ϠWP25>ʼU+Cx̊bDiMgZvVم'qojo{kX8tsύ&Qsػk?1&B!&elQj\u#B@sQ\FGax38q2YHb =Āj\U(!QWk!H!:" Gxvq>AӿڢUR\FGb1tѹh5 M'kiNPg>y !B!h]PqKs 6Y$ma+aq;c'[DgYu]4soՎ.8>KRCYs9kLRblb\~q]پKRf ?*yyFUTIhkfTeV灭쬰<$\Pxk1 b*)vzh$5!B!pN 01dr ٶc%+k?j(}#: |[X +'ioiC[$NOgH&rwo`. P1",ѩe0";6j_MFD.AlWofàZʈ !횋<n_ͮj5CɈ2uݹh챑G8c[jXRyYTǙ۷0dX׭nJWEPm{.;dƜF1AB!BхAUVd'FBdu !B!gSzgB+cתp[hj!B!S>`~M5B||CWPUф(A!B!=W_~[ZvWxz匴jU5`X1 -TUc}݆K%!B!hmn R|^hE#h{X^G*R!B!DϤ3'V–Q5t$xp: bʉ|)hZ,X8t0WEVMӤ"B!BHA7jf3ux1r '*: gM!B! ժ:H54nV>C!B!=W=)ro>]v o!B!=ūrkx=!B!=]LRYϒe{inLk&dIMwxCÈe(f̘& !B!=])԰ FOm1XA½JQT\ˣb"$"H,ڙ3*+/g ?|ό3p;!B!SPYAb넥D|*d*>W ٯXIykWۙ4q"Ұf|`;!B!a= bjE(p 'қ,qW'2 2=l&&ۑKoټm?eZ*׾SAqQm !X}wg hP>bJTl!hD{.8]b;bcj9?4Bߑa > bkyO/PEQz1~sOzQQPH=J=J=8s{X8&?a1t۶lfW^{ f1ț8yz\3i )飘z8%lq#ӯ+/ XO!BQCуYԋ՞LZt09سkpq"n\YsD1͜c"'E<LO'o_RRRBPQQQ!{wpm$:^(336C5;P #tѾߏh$>>իWW_ѷo_?|,K*(m$ vb,tLH !B! 8ްzk)9 6vC1uW/ǃ'x͛7pA~aJJJXh͓ B!p̳h91X rr$R sa[nh42jH2CGff&}1GJLL L80]8rJƎ(v۝bˈD;V쥪1_fVdEB!8 btAAdfe9hB QLj=d;U0)q`,Xŋeƍ@^^|p*˛璷{?? ٹp3|_ A8ٱ=;ؾc7Uz@ۋ"aB!߾KGA bePU.fϞͦM+IMM`Ŋ;9=c&J|Ve$*,{22,[ټc0n=Z;;PEO"BԛȶB!)EQ>`~S6|mT*dǕ CUU9(+;B\|UAok)x:m^`9ƒJ|n7`2W㩗~*uWB!roYǿ ғ⸢kL ]*D\ϟ=z{ SB!3+|i)EEUU9kRgy!6;J=wp_|Q*LtsfۧI!B R vx# IDATGeDDF(2# :Xe躎#,Lzgʔ)R B!B`tKl`20Mi6XCjv!Dk[)y,f>4ޒ}'׹o-COeɥzЌ?7\@v!B Rpu|>/>&!`RU7۵k>(UUU8N`vUQHY m_Ct'93{ }db!B!z^B!͝;:.^uJKK7o1U:c<:~)h`~M!P*]M7rmŻFE39q+s;SN޺a~>qvY_#mgvi`gx;[šO`UwAhHξZQEpWd_$?Gw#W\o{]G˛u+<4'Hʽ ќ{u3Ў+7^+wq̜9clG-kgӹ,qwQx0Z~h+ 7):3Hxێ)uJB!G6W_~9ȞGV-g0LңFg?eihΊ3ҨfyjF.Ό@7-Hx 8;*|>8Տ 9ל(Khn^"ǹšZq!W$^n :(]ṟ:QݏbZ"IIIC39#k?I3Nf`daBɲvt6y-((`Ν1B*HmM!B荓vyqL6 aI5:`>O9# bx~GkR{t(I'2EbWsSl K2V aa[Y"Dx<ذaT9 璐B!D bw5B ]~]~ 0-4{Ϧa.'MhDAWP IBc`00yd.]Ly,\zq%C`ZW;qݽio_8#BIxYc©׎쥿)>ij[~-[dLf<j{g2}k]T1b3A-t]gTUU?n4Tu,r\,Pgx һuEו?B!5HS+VUIC?Id}.ia6lPn)k'2:i$獱MŜphwőEg1럳yt {|t()7M)d#_CK"J#F3!]N=]s=ؽ{7SNmwZE庍d۝NN| hVzOo?|pƏugPۉx IB!8>B?ºp^C8GI.s!ΊKb+_e5\|?oLĤţ* (V^ YOIؒGrk9/\{ [ϮcCep) Jpp50x` i}zDQhϹ`Bѽ R(R& / ~]wDΕ'N1h F[[ p;Yi0ڻk|LǢ9ٹI^3{gN\[鍖=B78#:+] ΚGXF Xoj+xVږZo@3llEIKTm=Ly=`JO#I'9)x=| 8uyƏr,Bz!2_z%~_s5`ZXfMb * sgYj[ټc0n=}MxR zLy>rU:1]3[!J-_yh)g4FIm-Q_z=ԗw]5Uh\޺\5@ʩ7x{khR{\k>{suZemM:o)y48;<^{5:W^y%NEQ}}֓UoŃU`|Yf`7מn-MG >s Vu5ӝc]=s?Vj0m+kwg13'zjm&{=u ]ѓ^Wgc4e>՗m '祬7 dj +@[73j̸.˿p*HHH &M65 RQ]]}COqQ2;+g j{c;,cܞwuב:d>ם^kᱚD~_pÞ];2li8ת5 u>RRPh-mDD$aaal\#GHcoΆ 9r$Fm۶gq'޺:ݹ2YdwL( ӓ#uԞ ZUn\Bq|8H:Ng9QRkӴi-=Om_|1 |',[ 8n&&NxB8Nڳ|g[@gkq,`JkekL B!D/ Rѽ=);D=[ f8q»0qDN=TIuf3v)Ә X=y;Xr\^|]8^ypkC!1E8 H(X4Pj'jzp{;qqUWqw_l3?uԄ5d\b9Ge"i45!I.nŻ5fA' 8t\: B! vz#/3|S*!_!?-\i\9>C{O_܆FT+ 50oKt)SЧO;*4.^xlO0VFP\:@m {kR m=)4,}|2/`qvVacW x $!Bnwbi\yF9wǚ p s0_(Я P~o}Lip7r]#CO'Achv7Ii`_F$zt^da|%Yo#cHlbk=f*?yoq8^tEyS'rB!^N+'0o)V9&Lѥ4m> &L󼽻" u?qwW'#Mc>|իWOpB;01˹}.yWɍ? l g1o競7tJ6locS2` L3wjEH]!B3Qve|5g!aZ7]pϴ)MQku*q4}2ܣ;=L6x<?Lvv6ׯ[n9=c&J|Ve$*,{22,[ټc0n=mJs \S#Ɲӫ6ʏ :{hDOYE9B!859KDK%233'd˖-<RjC=Ӛ{J(mĩc{_Gx<v @K;|ׄ$* O@1bsSZR!,QW\/^Vv|&k"R-Cx„}x& 琌pv~y5^jc0Mʦ|ϼHbq`M*Kl(̵sEKyJw iM GeiKL%|h~Y`]^s>|>rs_Abbuemjbd,XCenvp8RAlx B!w :x?~oFᜡ b%: Ŭvgrqضf)f8)t/)y#ݧcm>VSR4y\=?}S &`I8k'o|ŁDLc`h5+BUj39fݞ* 45ְńSR kn$ObNt]C>9x饗ٷ/8@ZZrj">UU9 Rtٳgi&JRSSXbcǎ jG$@qYA!8Rt%eq}s^*=X^4Oξ*-Mv6*A@׼hXtlS! M` (vrYf==Z@UHMpijmf>3xd`Y7WV(*|_S8{s:t NLxY3wD?K~;^~~UvZqn2 E)p_JII+^PUW]ձ.k<v3{Wo]24 ~>}mN ߻ ^yOB!Bmve!RHۗ?i$ȓ.dzW|QBȶPVa?Sa""֬-ia3jhF ©H%XTzW]$_tBP,Pyąp]>qs9ipe7!QY&=$AᶃT5ӀiGXKₑP TV.ZA44MG|hG48s)//wW_ln6&^.gvMy[_x1֭k#ݵ5Msw!28 t0=86A!R:kN\V.%Կ1SݔX^0霗uGչCp0pnsKf mEs^+U_i\•C>+&0r=諐 kUEYmj osYRk&TB heTkHL "|:EUޗHEGϏ'';wn׃ 7un`$u֜оF>,r!z>18+;Ո.AZH ˨?Ÿ#HۗȈFL((~fEA5U}c?f…\tE[ {w5jTE6~ow;vp:pb`wtB!B:'Ht; ų}<}WpqΩ |M-CkRlZAQD/ygx1YevcP#99c; ^E. ɀJ/*=)dn#Tjq5Ugs'5slX“NE g=^}a{ Dg [C3B IDAT ,{W^zɊ4bNm7sx音z35uFq=ʃ?)7d6VAꡣizm5C57Pj=kIUQo1c'tR Evv6 (utd\bIzDB!mA 5ZKcJOqV\ܶܰp 7pFKKά+3~gcr: oy\uk&Lz*~0MJ1‡Z(weSiuuwUxX}/ͼŨ+f+Z>Rsxs qiўC>t ]Wuk+֮c3M~V.ZWzJ~.i_3$DEGCEAݦ>]mO =j}Y:7|Ò%KXr%=P36&2|\r7礖ƟY6΅O;tLZAvz$''c1bĈl * sgYȰhoe@҃k|jC=q/eSo2#B!B)}@?)MR骨UOKǤ3~IXXԜ8ۜiTWWq^놄4 P]u QT~-BEQd13;# mƳ>bv:fUOxJR!ĺ,/+ml1V1x^9`ZkNYKXckVڷT1f4)**`0( ˉ@?[B!ʦ_gmo$U~ش~-'$ӿcaXkI<'l%x,_#w5Kw: 0m4fs7WA=g N!B0-$&'c2OG84M}1ӥɪUطo_~%111My̙3G*I!B^. j$1)RsR i5{Xx|:i^% +O⣯sp[ӻWcjbiLxkhd=!B! <|^|>;!D6mZ䫻v栉ץb}EN% Oԁ-Or4cOe ʉB!ZJW%.c}L&e!UQHY o@6;x'GCYY!BۼAk>ICsp.W IR!Bq.}˟\G"I\2y.9 HiW/hmDD$MRZzH!j /V.b",';5>))@IAeD+RUW626l@TFb8'ː !B R,}Jݝߏ蚆R5AW}=r48 +X|p*˛璷{?? ٹp3|_ A8ٱ=;ؾc7U:Tmݹo钕lز>d~vhRRRBPQQQzLbMjk`G|-V?D?:uY+6Iy|u$^PZ}ߨ EEQ~ rxczQD cQeQCQ#mf~4i мGy3!Mߙd>~?S4"#T* @J8:U3l'oW0I2gm^.<27M-a3%ԕojLp9OlҢmIjsP%ABx6%k]ݻO5Hee(l?_9Y.Yt=ކ=FAq0Of_S +溧@US i._lIQ!N[-`0$~?)Ea|ٶ+g͹oFܬLY[=i_{(f)RjFRXUFQ>W_d ׬%W|5MTBÆJLÒC󓓵3i:7T`޷dɐᛧQ=j{Ga* g3Ϲ)I{R\w?;L{_tcmAk$41kЖ ~;4;}KO]O%K\D@Sꚹzyzm۾OƺT"ֲ˂ -5SB߶n1L׶AThc Y5z gg{ ڲhfV{3ӡ=D˥+a0nwUCPb{ r.5q]:KH Ƙ*eGWǩ,Sn[yWaf8~p_5g]9d("pL-FQ@-%t .ܤ7ǴQ;{qZ ޫuャYVœ- rjvPk-hzb|fMJR:f֡OQ1/ZM|{XYx_˫a6E}닅(l>wV7nPHa5pׂBta$yt1]#1y>AOnP$E WGA~Xp雴`Vվ&Y"tsĩsa[N%F G)!JH͐[ "b~b9r\hcZhެ$Y8`E om==³3T(,)(cYo"g+4U-uiH onjq2 ˔e4#Ul:fhZPl]JQn t۴麣i=2-CY_ӜfuAA~(IVN}3Zp=eEsDrt' g8t~9r\X9{0Z5pHrmT~^;dz E '"ELKZ\R*lAiY,G$:Yk:ZQgBͯDnl2,Sl<,˔ekޘq%VC13۳/Q9-#7+ZЎ!瘠"9Zٻӏݜ$=T57O#G=62'3ޝCRT|Dx Q99.W,nj5=mHlu}zcb/L/?GRX>#)<NOGRa*F]֟ӂ)K8u.j鬶(S0 K6tgzh|U[teJ1԰QyyʳZ_k5jÛ+m{u}Mun\}ewkpIAZuHE92D~ ?.NsiYgmG^Vΐ4&Dhr-Z."$$TyEZi')PEg* vgˈP+=Q!Ũ}$]۹le&ڣԯ.m]*NU *0 2d۵mj֢%xpB)ڵ^_=+6'Cng) {X .޳U?X㻨Ml)fo?\g;Ւ9cu=4 䘻O=;VnG ȑ#:rl1 cr<}F:ȱDcT7lJ鉭uV̞wvSlUzAvqslZj~Ekd\3T7RMQrrJ]_2E ߵݧg2OYr\LSn=.{IQJcMHh5 EF\ Ðan+$4T|z^s-1"`Ef&o)4RW&1åCZp~^[GUzԵv)'ɌxvdIh%vܩ͝5JJ m%43gFr,X$ǒ9ǵ:v(]/ ߻ս~(rf]exmuP+}{#(U~ܠ'xRc)UR/_~˗}7$WN,˔6%0" {PdQ8Ð%K6ȟadO>\{_GpHq"_FLL\hx/ + "O>Sj,ƙů1LaabC2,ɔge`di,gd،^S?,&ԲLcG(<,5_Ep6}A:u.r̂ l6(s"n1E@ nkոisBԘd]]Y)QuIP*3+o6eeekQ-PcU Q /{vkOIՠQ&슫P}]ө6m۫Mt̽Zz4M9Q;EK^H*ʝV|x^I衹գ6#$ ഹQ5y+tqSf:%@!IFDLsBT<,l>tPH'1rǃB?Yʔ )<1۰l4~nSͺjSZScܨƯdtmȒ,q_N]YZcX?ͥvh ttC=YO= u袡ɺ͹K/+QӔGS\Z&:Ed*G;UdSL(?1t\gnP΋۳/Ajt:+!`O lY' ajq@\ȼ:|\CڪٱuhS{~TOI,o:BHI Q9w~ 5m[F ׂBta$HB#ucG\ƒVku&IY YPUn*gBAVoU9[6[,K.H F0<#ye%pK萑Na#A'5 ]Qᷝ ;Y##^Dx v98eW)):3V\]5Ŀ*nTӇӊ.T /|^@b_iOѾT?&\y['Yw} I!{Eelm#̞8L7}~oC/=)T DrmW wņakC[)ڧr +e~wYegV NnښgĐ=&3]E(5>j &N:]rE Dl{ͦTR+]6x_~]Nioi΃:kyyRj=iw^]~ج=t<=[Jx?aj~C~\%sfjɞMEQ|xZ{(=jK5gJmuR觵׬ u"asFݿY3Uκn%齁.wLG/Q^lk4H]~[0{iHu6u|U<fDٚXnYW-ۣ'vsp_3AO߷LS%QUhIqאpZE ߵ݇$TXB o|=0z=Y}kExD;G̭"$~[٤^SJ]|T{?/ӧ~G՚;}jKk_jr%yk~̮($Oϳz84=٫/_~MR%9ح6դEh+ {}U[XuO^lRPn){f4aD8 LY]{4"")@@H=)@`HE E (R@T; PP")@IE (R@t(R@ Г P"")@ ` )@@HE IE (R@zRᓥKI1ޮi)$o>$4{@ P 0P")@ '8O)R3Y֥.w 82ON`6S<2{0Tx1݃j5Ń*k=@+\yx1(1#:&D@T'E>}~<$琤˖ω UIDATVCy;/IENDB`pycorrfit-1.1.7/doc/Images/PyCorrFit_logo_dark.png0000664000372000037200000001047413554642611023002 0ustar travistravis00000000000000PNG  IHDR<r>sBIT|d pHYstEXtSoftwarewww.inkscape.org<IDATxyTzwٔMfiE@a28рK4Dq\A#q' 1J9x4$qd5.aId# Bw]ukW]@9}h}w+QUQZtq|nyL Q#.|ͩ:xN˴1x+nM8NiWJp 0Xey8>94" UձILlc{/Ң4"@D:~ |[L?K&y$;EYe ?Cn׀9TJ`lq |t֥AZyV`-^&q׈fwI#ȭLc*ZKU2۾UO5Q7-K"a4c-.˒]h6swͣ+s^dp|_Kd㛸FDð)f63R2:,mYb!0Ļ /w@҅k>d K1(g"B-頪mk&h39W!` X椛L*s?*YލkX1H:c[Xļ҈HGLBPB(Աmc)ihR=.'fc3c)%XB+D42B4`yAUS#"C0Y1Z5,u.N mbeY\2d;ų=돥sҜMg."rw,jJUHcU|)TљsβfRCgDz5n|E0M UՄj+8۠ s,6)"jA:U8Lqdy5","?Kr<&Ui,f$ nrX%pUJU \D*ˁכM,Kk2PTb+A<;0{Hbf&~)!V9=`Q0F`k5Ͱ 36QEs͍[!)vx C'Q@tJBa'jEt$:!cZle-}ZW7rh'!UMb6oHl[O!;KqqqRM["t!U= <ƴp>%kpKi:ґ@\0b+9jFg.=,f7W2S8|AgHY1s 2MlaLJ"̫YqvsE`c*+"ضf <˯("wOuVɘq0V.33F4s 1|АZw0foPxB5mc=B6r.A=4u tzpDd40 G3)" L%kTDaX͘#u8f{@UCDPѨ#0Cƃ"E[чzкZjpOKC|‘[`!SN`:;Ե|%ϯT㜏9c7aN1"|LJ#?|ЭǤLJ3|ta?~?q_8drIt&|K6/G8dPJ5Y%XhPo:uK }R:?jΈYPyWE4z;}7 Yh@aua>la-qqڭ3~HmaoXV*ɴ)W^ U| J=4.9G#˕y!s )oPpX4cgdpwNvظ>r5PwT*K|mR_(WK=91Ei&AaL=;8J% ]Vujশ#DTgk6|Q a xڊ!:aL88Zd݇ERi^zqyɠ/~ gsE4# `Y`pB/L'6مp"9ɰ@E2RyUƣ}QiLPџԩLL.?PՔGx؄U%dJW8\#T7UzִNGM7mGٙF!.B(n,<33q!R塺zjAVsC>\ pQU䗒!nBGr$H (ee/Y Ԩ?tɭXʋ^sf0 e&'bDvMu/|J04ðГb<<AG!SZa:K%Ӏjnb ~B:=ߙؖx'+2Uy$Ko #N8`4V(ߒ慭x -"#?XH{[{?_Uו@D`N,3*4ؚ>XceIHJ$%H`6N8[Y*﫪[j<<’292H&m , +պJda}=sG[r92Ѻ%/3`1aXpeyj[/b*m4&Sq*caE0V ~KG*w>tIENDB`pycorrfit-1.1.7/doc/Images/PyCorrFit_logo.svg0000664000372000037200000001250213554642611022006 0ustar travistravis00000000000000 image/svg+xml PyCorrFit pycorrfit-1.1.7/doc/Images/PyCorrFit_icon.png0000664000372000037200000003055313554642611021771 0ustar travistravis00000000000000PNG  IHDR\rfsBIT|d pHYs N tEXtSoftwarewww.inkscape.org< IDATxwTǿw]ҫ16&Kb1Ɛ5FX"i***Җ]6e2s|3s< sy("HH$ d1R$,F DHH)I#@"bH$Y$ d1R$,F DHH)I#@"b\V d+W9hT+LAHG*'%oߤVj\.@ZVe\H| &O˥H$s*b=4H$&x?P+f#@"1c\{4+HHLB*wL4 ɥ@jLc@ Db&FA`hHHL@*'\V̴GC DbF?X!@"I1W)~bpy@ Dznr =VX/@"I!WdpMXHm"Uذ-vuAj=Bx-l`i]WyゟK ڜJ؃@V;;;Eܤ;stٛ/BhЭ&`%% zW ~!w`.0GP"0+ T+)G EtUrm{-C~Ĉ}7: Ġ/PZ4RNb>ūEl]Fr@ ^* >VW&񳓊k]"s"\z# ?h?uۈ>/qNS`O5kmN?zū 0˞ m(W~+$j/w~i8j:gIQPn&0Rǃ'!:٧"F7Xb]p\.Hטm(!7}VW  /T3mJ)8޹w\Z":eo%1E>(H\w~79;ڵ}#)w&EwWŊ0L[!PB?83zF:`)roTooTj=4%aJjtip-ВAL>m3z 3jVK~]Tr]r5KQ.:;AˀU֙oߺP( ]~;T:ws>ntpH#f)BD 9;pl39fiH/uB *J,7e-EzA-;?E$H$K6 @b/AVBT؀|whslbĵ,;⑭?B?&z7؄BOa#; @TҐ`1'}¯#"lG܎Q{p3Sz'>-"GH] FטiJ46ld!xi{&`!D-fkueY[:>8M&2Bb<].:&VT5#`$I"X I,A9{/lsbRS0Ͳ#Ezڡ9-U9{ Ui)1 j# }cNoc+'j`ցNqӧfOt4lCF!@9ӊftrࣉܙ2|;c*/ILAZgyEoqz]k,@Um?[yQc+j6%B& @?w27-in0^_ԔG]+L9ێ>L4'a2Y~huЫc(i.n+oc*[Mc{z̾6rf&MJL|Uz*pFb <I&ݏAfRW ֨TɎ3l?81L} "׆]u[<1AW=2BF6};U(zܩ8L|}GO<(!<3U7{i~\J3kq'FЙ$_827 O|}ObѺCq4;\ r]b0" `/"tI.^k?㙾lz׽}?y7p:?r| oqqpoD/e~+_x?:n{qݎ <AxĠBI"0(S@A?k>Ͳú>Fmr\9[ͽAI8uԫq^ tCDeyL>?If{WS庩ymkeO|zcFm@KYtט;&шR^<18Dx#d"9_]$9}{uCD6g ?~ ^?E-*.ڴ1 @S/+e8=s]{ =;~g|Q^P$7-^vo&t!u} @zNy玺]w9\*NSlj\r,zj}5`h 0dXtcT;R厔vix v8`**Iw7puEQ|?[f9Î?憟=k5EAVH=3nҗ;=Fи~do.C ښB: @֠$9sF-ڲOĸ_O~=oۼ0Smꃾ{e:* h  #IO4e}XUUQ#KCmD=~31BԔt'2/8ųNa_UUUuSLKUЋZ6 HWо0'\0cX콍7ov%}*r=TQ\G6$IO,[y;7>ѨMЀ tB4"Ė;Yc|ArӜ-ؼ`|Q́A5hX'KA EՃ:`o/7iwESzl&DzDv+vS-LE|Y%5lvݹ+`g5((œ};ࡉ9v01lYgpJZS«ޔ!Tv|0hte]r,J#~7w߸dmY;to^: %{5~ǘ?dҫ2űf.A fi^3H78 Q]%2w!z$ -K`٤xT>b?}A9X{oQCn]3kLFM[O`bd0s6̙l C]s݈WVb6DHO./Zd.k%ʃ㬶pPQ?>j;l@`P4Dsْ:rd i-bI"~05>~+i+)qhdGݎZmC2PU5ǻ;j;,F @ "kkk9ln boi'RD~@dRH͵c#hkyc3ҍz_}+gRb9|0MlTDYpp_foV -m]K"ZOdRSRo ĩ83NAP;}9fG;~!וkJZj) RҐqzNP~Ls7e7]`_9 *0ӡdXmG{8=o]KVaZ Iߦh@M* @(QPLOӀAkŤ\t):X4j`ig~1k-m%ߝ?j;lBNzo6餋 Fdt҂ߺƭ5[/KE_4좻 <ȁLEk E9Q't:`ܳ&~A}=OFD>h*NLXOQTsնKz??yj;l&A-DASȝ cIU˙Ҕkժ&XSAmo%%˾ÆhSZzQh ?yK MҠusUz Sgǃ\ٽmʍOyiyλ93r|sٜES^2 T0>|~!VM*$)6qםۀZs;vo 4hw*΃;/V>l'7$6gt.~oބNQ߿Cߺ/tw蓇i4Nӡϳ+[Q) ޏ2 ĩoK74o/떌QW_6#蝧޹53*]xׇPBGezˉ.f_e6՞Tc@QI=N~|dz4bpR.b H7KP5815w'_U}usP &T)?gX{8}ע<_V7VMNZ6ٗ~N 6# >DF3HP x-:( 2E!ثQUCjr\9[song=٣o?-EJY{lc>z#s+ݱڽjluCPC~]-pYs=|$ˮ,b+4"-@*7q/SFv99<IDATY=d{Hei @PBSqꅯ>/hOzbl_M@ū^]\b?"%Ы׋/_k);VR_kۭ1Ǟ-@h8ܚk]`r=<ԶZso9J,mWϺ8Vxk7渱9nol(QSC/|gS?7[!ԂNF87 ܐ|+wӘce-4l$6z#RAwwbbhM5p}}r2`U]h^q݊ 5% ?ЏX4-F!N{:];Cj(`9{Qľ?_K(^h9R NuQ.a` %-I_[ ZcF]0s?QJ^IMc$H9%Z+|e9-D痫08 /苺)J?Gtw<Գϗ)T> kjj<:[>L. ;ݤQT-)uD7:G(&?>rԻ{yW~.MW?X3w"I7ۊ6C77Ė8["ՒgTE{s ӕ5mi@؞g@/"$ެIJmB {L4Eb _7^ =6^e;q(ƶ!F"RFTǗY >UΦ^vqr;ݲ_vQh; ho+ؕi5Df\gK$ a `u0>ϤWI d Ǣ֡XbE `P+`Qk4́ZC.9L4Eb==+ r0%('0oG{zK$ N:-i!Un>b>ԧY/y9tKQ̀0sƋ~3yKg'i ֣B@ڑ3 90hRV1j7wbpX@p[S\8trD!-:}JK ^~a Uz? ?zinbo,B=)^e4""桀?p:1ԛ(*<7KAW@I:pp\u3~^l6 @xn6bH47B{nU T.Nr:۴>!bi ۾a$¯_)j?=ei@Z(^HJDז_Ty 1q#\Ns:(Utr#sQاUU*߅;~UϙG|g<~ ߇^iM\PH~Dc{Yp߉'U~a.کkgZ!Dk8ŷ-ū q8P+U3/M7lN83c¯C>~"N<A۷>f/jJb)^E.34#4N?caeFDF?{>687ꦱADALdQ/Y(^%ax1V=8mә$iϯ (~DYcZ'ChZ6&ٶQJG*ls(w{;Fߡ}9h͇{"[}~d]OND@lj7U=b]`I p"{nuҦW~EږcĂqXqbߍvM"F .Ky¯5Q! cӳ2BDT_dzk=eR b) | #6'.ZN[3`u6C @bJUCr"B&%C%Q'0oV# !E5v r)ɡ #&D'nu^ !~` rtG]" RѵX!{>)É($y"z ! A\t5b Qވ9tw!rY26pz&GDDA^{!Dh(JPiD;~b_}H1-ȣe*lãGK wV!v\,KOO'OoĴ rځ|Z Aq^·lCt#8Ћ#í}JDm@ utCA@Ј 6)N(X܋B-7;'mՕv!zJB07!yZ1MBx  !elFtfZz?;Y ؇z3vt1ž] ]~8a#>؆6,M&R쇂X'Mju~`|s-bPQО-+ϥD?ME>H}N4?ǎH7n Ȅ[tA"hbη|GYwx^-;{,;BߚF )BE(DdB̩Br"DGj.&DP6$# =q"ѽӅnwt%|ͅ. ,PU;6+@tmN[:Rx(V?ZG|nsH3q+3!V;"3~]4Żlh%RB^f1ksw&^i$I"D"ISH$Y$ d1R$,F DHH)I#@"bH$Y$ d1R$,F DHH)I#@"bH$Y$ d1R$,F DHH)I#@"bH$Y$ d1PUIENDB`pycorrfit-1.1.7/doc/Images/PyCorrFit_icon_dark.svg0000664000372000037200000001135713554642611023006 0ustar travistravis00000000000000 image/svg+xml pycorrfit-1.1.7/doc/Images/PyCorrFit_Screenshot_CSFCS.png0000664000372000037200000035431213554642611024101 0ustar travistravis00000000000000PNG  IHDR ǪbKGD pHYs  tIME+/h7 IDATxwtIH!{P+`oW*E Ҭ4iR ^Oy%B MV3{̞ǽg;șGm șK @3"""""""""g(eJ (a IU1,UĶ_2gS>!"""""""r: @V*럸J:&/bXSƦòۍJ-^|W2pES~ ҙq.L5C@'mLmp- foNou:I7hKgMūٞ^ݮ{peY97 NKF5b$c6-/Ϥ\LG8՚uwN2aɨ}v(L}sLYVdG~M9KU*X]qkx ޽\"'N?ys7.NB,L~~INݹf ,R7m 402IlE~MI1ݳmfe/u<85FPql*\In֍ݣv-iIUYf%ǚEarKeX'F8A| xX&mK;k$~9{U~xj֪MoLs;ۇ/S E=w[CpFS 1%>CS]gW=Bg,f{h\u;7)NxY6}"S&FHbs.ӛ)AgK~P[!_Sof6+l`g^9foYl:_v'pxZ{_lrЮo.:8%+UîxiէC04EUڼt_}lMrVMg².4h8xRN"YE\K;:HvvJ~|Y5odW7pQif-Rߊ"""""""gcC^Ë3,6 ӑnFDEƚbXHyAB&&:-a~G_-'@nF^I Gh.s'M#X=s&@i(BvKHNr8*b3_-BBп/,/rlџ DH4Y/%7' 04MM3ٌЦQYHFGte).0 9Xe8lj5Irmx!($NjvfM"HHےaDAC3~ˡN{Ufom[jAeI,~{ԖkIs׃Z>˘mm@cB Gx\qdSε_J"""""""rvVVf\<e$6B+V?xc雿7.57<I37k盀и4#({?Ll@5sq^lӝƚ>T}qL5whg>6˿?^L#bk;ߘ[grzq:]J80kO8R߳7gJڢxXhlڹu@XxIU7 R[ylQ\BuޡWDcSDDDDDDDiG9WJn`4 P46EDDDDDDtb~`Y>ӇA;q@ټZNi6N'vC:R<< r,Xsg @˲paPXP rq8DFESvB2a'o\NaaeMTtDNU!"""""""g3 &rf  {:EDDDDDDrvC @36"""""""""g$ih%4}uSG,0K)aryN\?~S!t},wNNt59|* Siӹk':6Sgp/3#߿7]ݭ8'`nڳVDDDDDDDq, @7>c֧xaC͎%}L=/,}*EcЬߣ ֭(6Ʈ<63fU|u)؊ef/fm72l":|FڐiZa10~+M ¢I>2 zqo<[O/ ƣ,ܾ=S酀؆]{m@9oy'es.Hr9Upy^{eO{^q"hhs\m?Vl%ߪ,TEu *MMZyKn2]c~e+}, #lGT#<w62h/Cu}pmqz}ފ湯)_V[jq{osk .=z9N p펣>gRVҸS-OIX kҕ&`/Z{d~a?SsӀ1}F ^K3~ Xxg0||kÆ(T)Foբ9}ylm"6FVf Z)u+Ma`ǝS*I9:2jx~ՙkHZ9?r'q}%lUF{4 ٿtI_l(?gu6 l?a8wwMRF@CaQj6^bٳ|j]#Օn#F[АѡCfʨvRֱ<޿N]q~gKlк M`,gUO D?/Ihu +MQEToNf~ߡy6rq|1e#.z'l'֏:& ^xil{s+Ҥ]-<|[϶BHnS`eDDDDDDDDN H9|gڧ\P?j֢sp.w.e7Vm%kgRR`7c~\q$ aԻ}Wkk/c[Vo|)/C@q~Ys =Ѵl_E٢ҫi0! ӫCeaODDDDDDDDN ݁XhJ89>gU]yJY w\\kA|fxWQeY>6^Rw$**KԷ$Pq+}??Ғ_.-n 'r]꯿cQ7tݿnWnh;dȗKs(5 -6nF'a'& 9ZP;P/.&~C-6Ƅ1N XQ]gQm`"pۥhTy UOޛ}]>7=_G%,Awj~9o]O LVMOBi~ݼy58YPQ݊bSHPT΋?9HܓZW{W,끍PZG{MjeǩX0\AὤC{?DqDZ|GL5~N"SұSϧ1u8BA1wt~ 'w &;kHH}'\rUxJݵs5@eG0Y1MFLb{os{c&a©qe|{ZE+՞ayaذa :TS[NvV2v pl۔J~iS U9`ZMmzDNY>~qt{e޼,o%yԼVDH &7' "QDDDDDDDN<41nyե 2gk4OB$}a<޺ǥGLߛF`p0Al6EK,`&Y|>i/_;z СC5(DDDDDDR?~ڷ˜MQiC,LՄ$8䟔lOLLlv pQ-"y*"""""""gr7, ǍVDDDDDDDDDN3azL٬q 3/%_{ EDDDDDDDD95t3v4sټ|99_owJV-DDDDDDDD"JɃc+%aǴyO=Q<ۙ6j4WZ%&1gv4k)^i"""""""""40 MEHl[yj?Nc#@o6>n0bլ|3g% (1/cf˳E|ɷ 6=lD[MZ\ԧ7Sngdxƞ zsοr:ݟghn nMݎW2D3~wK63 QCP[/2mzӵ vg.^W n >CS]g,ze]C@vg,f{h\u;7{EDDDDDDDTK#c'q`fɷSʾO\7V`/2v,”o.Kqǚ/G0~Q/։v2%V!'Å\NFzL9!YNRl$3'M&нsɖZ IDAT1yK9;au:rM݈X_L)O0Y(ǖ?Wӝjjb%b^n&;'7_XMt&mm+V|Ǘ?fj!Z߱NpKhlFT\U5Duݍ"ȫ䟈!/ǃ-8zݺQ}4_ߙ:I; !Yg|a[0׎ų%.5#$gp`w-N<cۂU?t_-:Vųfom[jU4i4lX 7eZ.1APb#%MI !u>B9_ NlHɔ7S/(> %Q{jVUZے@5 Ga]ZUWt5銈#!!,ʜ .{3'FuQ39z*DDDDDDDD &ssqq$\8Zѳ ޟj̚E')fPݷT_8MjQCj#fo! y2gy7#a<ߏ3b_WM {|=s1dQdW+[AH {1)w̎Z+ `[ϱuG:w>ɡ؁];w 9vB V0DDDDDDDi{1-*c3S-W y&M bfdp' zUN@ JvF@d(Yx~1?ckߛn}4hv <5ǵeJLCiw~ƽ:/=SN==%g\2Ll60<78#.=fS@j`9 t;Sx|֭݇7x 0}ِ#&pDע}>JS#^N!֘]&2w$.7X7uWM!E{֓FuhOpT|W Z9(ߍɡ$VX[NFq ;ӷA;Z5f',c1ޅRPDDDDDDDNse&#c,p`w3Y<N.|Zt+>a'e,cAPH#zviًsaSV.bڈK:Ucy ,ĞC}1s9(>:8ٕO)5KX;U}l&3fZЧ>!xcj/5BbT2棭WNk͘o_%)_~23tlvBrHr89].^/NK @Әi1E=.=`PpgPo*= ǐQ^K?vfz=0 _Yp6lCU DDDDDDȘ9~6N3""r\ 6LA98-4}B{= },]z Q^2db۶^fF:;wl51MSwv*lMJڊ\#"IV,\@9&"2аpV\F}DEG+""""""""r"*:FQتbNJl޴* sfaaw80*9c8+8$x@XX8oC4)G7(\ޜrasܼa!( [W.z'ʋXC<=;Wx{O4,a3c)?npz4U'̝OC˭r СC9!l c/(`oÈ&౒?3-|.y0L4,Ӈeao؞Sq_-.f]{1yt%>1!ϭ6'x$^{Q=|ً<SRSS~4NU M~ų]yݑ4M4Ȫ`)~zNNTrۥn6}i*ӷv]|c)^X `eg|=>8/mbwU 9X|oP;hH 1M9'Ƣh/O>Y&_M`6 \Uh=q"""""""r1c~ cy\xSIfo+XߘFAv{<$~> uFWqdח>yw?Y@P+PdK3O=x[@ ]e?yKfELk'Xr훝{qrp6;I?f'$4ćiYΪt kǑk\f(\+>YNd0ӎVėϨgd&i'0vm"X&g^eP"$^|y.nEU \E%}zN1 r= mJ)+ˍ\6oJMB6LX׋{T|; lN{RN Y>ǖ/auiQ5o>VA#jܳ;twmgc1\d6v|;7GY dvf~6mH#{2IRZf`#4.صk_pEp5I?apUWOgl}R`ta7lǶ fNyR%а4\T rUbWkJ&pQ*DλM:}U+⭟-s>j?njYb^/{– \Ǽ}BMW3fm})ٽ94Tūa.ҕ*]~ cwgbM#qmίpxس|9!hsߟ.3 r-C4ӯT?o=sp!i{r:nUs>/K`qg |._@WJ^>|>ou:q"۽BąBQv&Pe.2vKgZi,P8G@Ka>3~6f EDDDDDDTvL3}ٻ&3 Re󼻱.7>6V% gӵs 97€ ^#|ukXu|:=inByk[XY9s~:T۪LVJ3}4[XDQQ._qp+V.$;c=UlvCZ'|- !7rg7BbCS9ܵ ́\v80)2|8z= =k|CW2 y6wSaM+MvSaƩ UƉOp; \_Rb-)\tEG6i򀙟"Hv9nDF=d=GH<)n/Ay.xIic‹o(͟+VL5oҘj^ŋe4>w*Mg1=`?ې!n/V< bEѰ;wg%z\vڇmbТ>wQ8gNEDDDDDDDN=4Vprؕ}(fcW'qr[lw.OÃLsyD_V;}neցpyAq$=Ъr-?rToח\|/;R%xU;X>눶Lnߗ1~AQQqʬj\,_ܕ߀*O9[keA2Rױ,A{͹?ЫGs'è?7УRdk8ޛhrQ(w_bX-r77qްyDo  Bh]#b!Q/solL*kȹ:H|Xi#v AoŬ{ue/y?{UzXO˚uhߦE~ -^6\cNpD0, d7ANN~EA{Uoy5 FU).ľY׀@Tԏ> =;փjXr fn䷯qp҆LDDDDDDDtlY&S\IHa{]7fMWL[+*Ɍ'vzƧ~Mzq;bP%m2wi:'uX9u&ҿU,\qu&| ^#.dxHl_fE^J}E;ߟOQ[8]zsi,YM;8c SFYlS]yć+]N6V.q1fa6fYNg9*#Ce}`Spя;i}c쪡M5(}m%^~%L+]fy o>.ɷCDDDDDDDN i8mE^;3HsoGz(BXհ*(?6YxpO~s~q~ȫN֖>O>55gxዼ9!n7߃^ۯYލQq4x O-'Pt9%7oFժq|DDDE>}aԿ~})7o+qd[]NY&$<d' "B]e&ae74NKxo%/ٹPr0Y1MFLb{os({;cz ۬-#_窄C_f2&>L ͸9MʱtxG)Kdsv0V#?)=_fb0h8U܎?/hU|qqq\ @\\,-J\Ɯ3m'hوJ@@?2yd0Pz;u?f>{Ifeg[N>~qt{e޼#'V\SzЦݟ|{&_}7O?D"xW^|opmuaÆ1tP 5S `BRc2Y" wUo#00o%ξPBBOxx|28'9JV#qÛO$pn>@71&̓+]>-pͧa @^lqtɵtoH7YSc"8""""""rzȓz =L`gq\G|vw,MuRmأ2.u+)sLm(_^,3-sg٘N)!""""""0c~`lNYC7w]AAN E .o9V0I%ĆB\|Ossa/arQ]7DDDDDDD1~' tzJ J,tg.bc\ ,"+#ᶖ zJEѼ$$=XDDDDDDHhШ1˗.tjOˊl"ZneUiؕeYŗ~@-$0#^8/\^̗gj\޺=5fovCg ~']p86u2^fV\vϣJl,injEEP=f#-?bѩ{W< ߿Ϥt='͙aX.4}JP`Ӆ^ l6:"r^Of7B  0߱U)[q)>BxI?r1C"ׂt)nmT6C61;dYnw9Ylڰ, tS `ڵs:]}WQϭi7!PC! EQ]q"`[]t QW׮ ]C\)bwDWA"z -䖙 !7<c-t#5%ͮgt<8y3Mcn._1&\s\ƇOc[@ﻈ<*^/SLn<;`iܾ+&Sv?<}zQ+o_xˊ0p5&\|y?5tcX~v/}򭗩 IDATHޟ?R"+XnNᐗyo,:~>7 :ۮ}lQf6&}~<-hS͜|A{[YUpIH+&a];v,cƌQGqeN"kV2aD 0q"^oyYŭ2v{zzh1)+Hqn4<ã受n6/?I&X?w_c ˨3KjA/Ϸrag]_R^_68sQHvV);Nշ\8Z{^4f@fi8MȿH*b_.BYع3fY{„ yiFuЪulgӳ|C1uڥ=q /asa][2~fo)N`Wo15_&I^|vb/+]7ia۸xEd3~n< `/fo2w`1nbwBwwŬܚO]+16xl9fUq#oPF,j2˦Z \ t!0I"""""""rJU*\?{<4,k'L~s<Ќv'a(VDsOf0u@qꎯ{аaCFYy'4x8g}z-s!&awVh+e_^GRcbn +l%+yR+rѢKDN3{֮%$Sl_AnAsea{S2ri9Kf֬%nm,sI7ܷn ;KH]9;3g)'"""""""Z- Ow1`FXt"W.N *f'2zH±hLcֺ}hΕTu^QRgb+Rܰzc6*jgo<ѝz5lU5 RD GB` ^׈,\$ab~ݐCEϰf>aɯ,U|͖tE]cQDҹ]{Zc6׷ è?`s8g.]mҌ,_M֪;WdpIx]ʶO_fݜ3bW6u¯(#h4ߋ nYĊ|n&z+R^_>4/n~9W)'7т"""""""r*;#K2LژMo&%Crt.wQ43  Vs|i,Ȳgٓ]f$WX>M9wn؏dJ Bc_1D&Falq3`֬] .;O?Y/{l͉8Մ+~?̛˄G_iNVSY*a0|.|oVV4͐7!v1me`qnر(^3g]nqu;gb"}_, ߳ŊM;w y6}8FN#%lc6~ȩM<;;o^X-G]~ ]~_uըfTss6aS\aWDVq?g#8U߰'ln?kyk!"""""""uF%aEZ oVn?-b[DDDDDDD5Oݼc٦y0 /));nCPU4ML_]XrMo0}ql'+zOZpܬ9Ǝ˘1c"""""rLL4~`1= ò\.W+W HM8~ 9mu`_e^/N EDDDDDDD6B$j 77l^k6=HLL;v:ADDDDD4rahݶ+/ t{(.*bŲt 2xcAvefbYIɕ("""""""" 0BrӤi !W&`uN'=DLd]ڐ4%U"2-!""""""UAITR eYsu:kHuLRiN;9YjWi a8.n.,̯fp[ei=9B2YWJA~6l ??oqzu:O uk?Za^nڜ͛6pHi~LӬv`08N %9i Zi,+1 !C RXPOd] E)tCõEN*""""""LJa'QMF%)~@l\yrYv`l1M۷ C t?<6m؀aQ| rJ,?a7hyICEDDDDD,<Ҽ^/'N`ĉ >!U [ Ah#onGD 0`fh&3 h)"""""""R' @כa=3,EcŽ7 O昧m!,;v0avYM0L*4X:Qiպ5mZ-⻆GDDDDDDD. s2bd&M@ˣcÔQ<8-~-F=a4r~t8аaCFY Oh2 d 66pРX^†Q3jgM2͊'!hbz"t 4f)OC`kr qӄ]^T`BE,""""""""ZyCmP5".](ӶiY:ӯ5tf#hԇDLEBDDDDDDN/_a]!9i wQ43Ӱ"˪ɺg`x2[T?9]`<f}bq7sA1`wAٔ[^ *~-[(1"Hp?z=-8DDDDDDaS{7/!3"L3X%%%pp[U;4|53ьN lXuYò=K|m"g>}ɞ`(ǃYٰ-U-Xa3{K,ڞ87bێu,:{q%)+""""""O}vFwpqQQ{4^˅"Nbx,gǿKĵ% `Rlh x(iw? 6yDraDw̫SbDg3;&E aVcFs$MܜUxN={v"aG40;y!Q=ͼyG ~b{""""""uԁQS @۶cewAN hNqQ+-SnX]m +3˲HLJ4t.&MSp8h2\OBb"i.cmHbi? 4iJJs!,|oT4-eHf <+4 Pn/jl6gy9s;1cƨ#DDDDDDVB2YWJA~6l ??oqzu:O ukbcV~s/[p3߼?<^u0/#S_Tidѿu 7_:[*)K?ۃg6|xc]vC0< 3B- {c ,o^VQw^Ye^յO`/|q~ 4& e_nKlWv VrkYع3fY{„ y3+Le`z£' h98_VאaGi9@/ hwɍ>lCzC-@ˤ7ɫX\>N["-lw?oVSL`QMr-(`1nbwBwwŬܚO]+16x:nر3iGDDDDDDNm2bd&M@ˣ,2{hϓ<=o z1.M*[控 oexRF0-+WHt%ɆMkGs}0o=VLa(VDsOf0u@َ޾6lȑ#+f4-8[`5۱7e_WvӅcof+Vܠ"GNLK+طro[iph^6xXZhѥ n "[t=kR:zyI Wexw?ҝ,p3{/'UU6=WVg1%! {d&o{m_Vn}EDDDDDDQ?ohV< 9G9p%Т}B߁0:*;0s(ӦCZ;4  wsuH5߿kg/㆔E_gVmJe}W1_=C:E ~`6Go^%glTve 6bnɏw7ڵ+3\}B\E (\!ig7-E ++5lU ͂EQ]GB5iD~.:&q/Yķ|7/Ѹڟ$^Or~tZ8X֬GU#M~)"""""""gZAyCmPMS0s7X8%c߿;X"C^D"15\Du "uceR!.w-_hw/guAzE=mf嫳 iP~U|r`*=6,lXXrpeb~Z k 0sxŬ̡Ζ]䬗B,]N_> G^L}EDDDDDD aQЎu4ysA EL |Ƃʹi<8]6ۓHwOqc\.ﱗQyk+26ivւkRViY}_e-E|/nv#((?>xc1л0)ڱ;wfGtgW<])?٪Qyc?bɎwgȠT>y o2$ne_Rno3Me˖)---v:t͚* 2>H.MwdQi٤}1K򱇗oؗGfq:NwԥwuL=r#..ʝMTWxly8NB^lHz;?KkE钧x嵇Fnh3?4IOHI|}bbbcA$''cV[YCɏSy[mU>Fi4"-"773(q_x.x'#hۭ1g 9,YI ymTyHz< (/UU}D;^~5w 5a\7t(III`DdIi8G_F|b}X@Ls>u){>ge{@DDDDDDDNc'4лy qk(4`LJJo#,,XUEdԯٟnڍx#p SYL;`1=r-ò\.W|(""""""""R3'|`vFwpqQQC zq\((""""""""rt0 N'_V9'$bYeP6=HLLr(HeƎ[٨/"""""r 90lnێ˗q}p:]jbR:uex<կkAvefbYIɕ("""""""".&MSp8h2\OBb"i.cmHbi?ôd4M%YYU>_T4-eigر3F!"""""rx *@0p:]e\X_m͆,#"R)'""""" 9dY>_)ylڰ<:$>>֭xȨ"!1(O.W6M)ryB>T )'"""""R dg|ZnK WV ,f0jdsv^/)RˇeZWDDDDDaFs~2֮!sRRSF≎aْޣ'eU[/^,Qh֬^AvVqqL9f5h"?/ظxZ $&U+0vRRy} E0 13аٰ;59-) ODDDDD̤&NC<84MҼ, >G +^xEx^NPDDDDDDDD D5 VŠ9祸"=GocE/g2YSB=9%ccYDDDDD$aZbd[\'eVv ]cڈ?c)uK d,s= >-Ϫ^W ̂L1`y۹+?afvX\q<ޢODDDDDDIdߊ^&LH~A&N-/δL +0M`,,3eaos.s}Ƭ:*:^tAYLA/0q#w~sc؎@b~p}W"r}_MhެNf0QK9QSФ(c&#f=/OacIӿg/вBUyhX2Ox3Ndeye:Oѿ.~c_aF;,IfY3T<;Θi/+ȕyp2v•@ ;WȟڵoϢhDjַ&Jɿ_%t}t qYrCazi/U[)pG?4"Q|J̲!FT,-.]vqe>~+\s݀eCѤgIG6HceQ`E(ל=((WH J9?~y#]z""""""rJC^g'A:`s8g?z.߽qgd앞Y,xc̺̽?/Ua܃ÖK3m̛^7:7 !.%ʨ~EV #N,lXXrsg8;e|*8Lr=Wgb)&8OHv} """"""Ǧ?S [ܗeߙ1Ma H1i>Ay7`mz\ͿBbCK=x/J!+%wxcfg篥tPz?[2ٱ+M:yM3 U`=[qӐkyg۷[T|K?NT$O,FD3:5ђtkCYò=KG֯ >^e{U6 XO7~0[]ϦtXŅ`aeÆ-Lc'<3 %\c0) k6gr<4^U~y<"r_S5TDDDDDqӉ 3de٬%lM|`v ;mgq[~@!ØE<ٍڶnUk_ø]x1}yz8lTL)-!ikLӤpl5̜#Ze;f#fL>~Eilr3~yf!6kVϪt=/"9,#$moWVo㏘%7ˣ^8 _g>ǦOmh: ~I++ 3ư#0ù+("""""Rsb2S K&5bA"ߜ܀=kwA4?c-?%eն=4,ϖ>nwv9}Xv<=&7̓s&%%pv*싌"2J ȩd A0gȳb{&6هg\*3*PDDNeFq_i\\TTPx^.kap: !>!* EbbRCEt*1,DDDDDwe5hݶ+/BP\TĊeKiզeDWEFF˲HLJP`Q@ODDDDDj%!.&MSp8h2\OBb"i.cmHbi?a`;h4fgBdYVRӴ*]XDS7j갈E5^+4 PC0p:]e\X_m͆,#"r:QMMDDDDDN.9dY>_)ylڰ<*8NHk'j9.Draԙ"rIY""""""S`0@Nv˗.e붴hٚp` oqI1 .έ^nN6;wl,|ȱ9X]HImFtL=5n':eK{!S/(O4kV ;+ظ8u.{,c+"""""rj0ќE~^qHLϫV`ױ6g 'Rf&4]%0 l6;SCEDDDDDDD@Q9DDFϕJO4~i),(?{. چIvqkf(XSv#йDu62e'-ny1}땕SEYft5""""""r4 5 }>~\qOtt|ǬXK.^L F;";p6ꇟb6>|jz?EjKw<*^/SLn<;`iܾ+&Sv?<}zQ+o_xˊwbѪE\=bDWgcINnpX»^~r:C^找q>dt*4ܟoѶʏk‰'!(wSX[ODDDDDN"kV2aD 0q"^oyYŭ2v{zz>n R[f##U |7' $`fy9x*+acgya^&-Gx\DSQ}&""""".2k9`03_ X?09+Y9^O&+ x h 0ց/QR ޘq@`/|q~ 4& e_nK,S|OއecNf̘Afff &CՉjB֭2w"A>w=5lD˶j*[L1~w IoWd=}؋mčEZ6~#>z75<%ٕ t[HQ,c/|?`'Յ`׋Y5>W cUm(-"h=9.hرtv47#珘3jŴi?ٻꃏsWBƅL @6]p>jqR6"֭ 8Z+PQZG@{s?!Ku^3~w=7o9|=]ٟ;{y>/8R["&gpQ|Ype !!{UȈ;ី-,x5&Li#ih|+v1~*3vvV>8"܄२ RJ4.acze}9\~xt4gCNݟT0.=V;Y<&AWWP( XzvzlŃFWi/[ IDATV5v`DŽcǎ^{_huQθt m`Q=[(ztiCh TK-DDDDDDDo-<=3|~|9w-<5<1 n'`,~ͭ&yh![>^mqe[]D7!p{߿. 5ui>vxkvҋۓK,^)r)';k.Bd0SDDDDDnHXNǎ24%ڞLGV}pP_ތ_hBL?2$dӕrA-{k{aH|"/c=W

bb:E۷n!&6@[4#&ňc_Uwn|-n }oÙ}[x$F?+?Yd:Oam0Y*ўϾH}Ej![ fffJ9Ia C[BoGsdWJ mdY>$Ͻswϧ0Qqs(-a3wk+B1<;n 9\ti/ /McO(κ'^EGW>}Nmxޙ;֭[QHHH0;EUsQl\<={-С|pZG}+|PRO_폤PWY9 REDDDDZZ!!fN6煑6!J%N%Vu@H'L~1'| y z̚=kǎ%>>RUv6m8s9A' ā6.rٸRf顲St/F2T rERo^®4`G[rm%44# L)C9ׄO37N?""""""""ML'1m&q8:n,"͗H]3,˰؜]E6M3ॸ˅r@ ^K۞H a8N|>j-kQmkc9.M\\|'"[CV""""""_ FZQK[XId%<<cYq ,"* hBS+.WI)8}>ZXN'<ظ8LdCrJZHєLuEDDDD2l;HJN%iYUocV3T[U@N}"""""A z`+ A0p:]e\PW16 Y~4 THcpdYO)yl&//"`utK݉tc;ذn]9.b∈ f2ENӘ>;9y17}#$Nb/=k LW_÷Q~~&?ջ ln:q76uMDDDDD 8}<@֏K=]ӪU+`59x .00;s;S\\LJԀgZ[$o=ä|<B%$%w moý;BR/+'<%2GBxhD93ׁezYv } %jmh!Hwk.C4o6QDDYj9 *:Z)r2|e_$7J(o`hu{c:8Š:_:Jh\^CmODDDDe E^n.Q1 ŵ%7FvN޽è|LSZJIq1E'\J𔖪HVfv94(#/b}xk |%ָCt>#uoB3MԳixDdS)84M?,={vS2\.I) <k <1ubWVgKqYו̉,kN:#b벒ȷhQ6g . Jl_[Eԅ;KOOorf^Ff&E@` &4h2xaD5ӏذn- Uފ赿Q`{=(ƻSͧkHgd# _ u k'l8M m %(-cE*.UۃvS\\Tև:^}C3-^nu} HD/KRrx?g Kѥgqyݿxf>{%cODDDDDDN}|}0 ʏ9մJ/av;7/m[رӧseǷF`@X>J<8\{c VfNL+]"7~ݣms>r7M^?o\# xs]lܰmZ%ے͆+EO07Cݓ͆ ذq<·;jkwxpV!#x{ⶰט0)1[Y1u[6~+t? &¨k׎WYD1v&^pg?4~C^H{p$x*fi, UON_OԂ3%{\c`RgdQմ?-ݏ,M`!_7i_TdfzoՎkgLא 6Y@c233IopPɾpDDDDT5/CSW'Ң̙+g0qLoZa+<bKGH ̃s洗Hև RV5핟bhT;6{6=N$))c7>~o ~zqˌ*L).Bi V >v)/G$M69, x8q.ņzLt*Z)Li^Lڌ@VwOC94ZGdڹt1 kA=iEf`wVX/dOgT~JBJP0[54br0(J74j7xbn߸p^XCj'"""""""Rj5?o7yD>#!$D@Ɛ+e˂?.Dj. nȲ̀">幬*}s׮ttn^n V1kb-gGk5(""""Rlq1ue}Nmxޙ;֭[QHHH4vy*t:]tڝY?QRRg>:`,o!s:Uϭ\|PRO_mIOOW &"@Ӧ*bY֥8z MfN6煑6!g_tyM}/>>G^ͬٳvX+Puhw8h&3)y}g=k<0r;/>@f~0OϬHODDDD9Ks>Lh+XnVZDui)x!gfktuoq.NFf&Bhnm}WPu^zQ&""""| SE\g2F{JGB""""]E.j)..rPDb t^l"""""eFa8N|>j.kM\\|'"""Sx)""""-Ai=Yף @Qa!˗H=,HwKxx8Dz,XD 9`$Hc$ .WI)8}>ZXN'<ظ8LdCrJZH26XMaRi\ nwJJTs, , 1iU93HP@18=)4 `N jge]FBBΧ H4栈H@syDEǨւז+LFaIIMDVf&>4k%0 l6;S]Ixٽ{7Æ cȑk׎P 4Mټy39?]v\s*Nи~"""""aYfgac%H7^Rph~ Y{ ?e\.S8xkKp2dBCCުU+4h999_^TleXW-sܽL]kEDDDDZ DXUFO?@Tt Æ ş~Ćuk7`>O'A;祺agJZPvRODDDDPؐ*̜9믿yUisv._rg?`Aɢc궞YI[֭*C5NT􊋋1cg;8.n5#nUco'ѡlEff7D`׉S&//L2\$MNa`cpMDDDDi è}|}0 ʏ9f {{1)m\;v`ܹ|>}z8utF` ĤѻW/zJS B֫l]T}R)--|)**bլ\R#u瓶T⸆PDDDDZ=s*dpO<&Eִt:~g>kP5>k׎ǟa}"3<*4M^/FRqjj5W223A-x"""""uv`JH̕387f1o#L&QLd' UMn{%ALRݾ6{6Ok{?vbccIHHP圤2^Aݷk,rzҒwK1YDDDDUݓŪPz;V "ΥYHgNǼcmE3Ц`ҫҒtԉ}""""""Rwj9?o7yD>#!$D@<|Uqj* nȲ̀"@Ғljڔ4)~5 YDDDD󵪠ᘦbUn;ЦP9sЧOU4:$!""""R*GM>.w;5WT'ZJ3fODDGwUiPS f~nJL+Dij:3Ul#O/Yoѯg4:./ n^,I@l xOصk%%%|>~?^"6nȬY~ŋUi-'u&""""֋1RGy:imeSnFv ҟ6O{&"9ï-Ld,>\!!!;ԎU0 ZiÁ mߺظjBzKBNoKvq8eYу{w4UZ#ޤÓc{zz:dfBzzV}T,"""""rܳe qyd /4=t8&xK"Fs:mܹnݚ\FEBBi[Utܵ;+~8g>*EPFٳg6mb˖-vIMM%55( C#I/4UAjJ!d蔉H } y~&h'Hԁc7 fg_U^S~$W3kl;JTڴ̡u|^ᙅEȍ}oߞCBI]ֳF=Ueff2<XPiFYx%9[OPMy^||,[BCC+/&3MQeJUZZʕ+߿?a0uJorԘBHj3ZPTDDDDDj^ͩkr[Wi݅L .dٲe 0O?$BCCغu+qqq\}ժ,9SM%+""""Rsa~֢+Յ}U)..rPZ?#GVZ7a:$磏>b…f+Oc^ t:}oY<3^}'z H*l*Mp:gϫZ ¡C9eZM\\g۶m$''fa֭۷o$??I[;8g1a.u~SY_zr{K 1S U{ =1K)A]EZW_}?bڢA7_}1jzR2T1Um"""""''..WI)8}>ZXN'<ظ8LdCrJZIի֭!!_z55cm&kN7'zx=j>}Rpbھ4fL\ q IDATŒL8Eq4x~xDDDDD 4 ARr*)OSeY孙1iU93HK_ٳgӿ1 uVf͚ů~?GȷhQ6g . J\ﵨuXľ]|EDDDD'xJ1M@2 Ӆ^Vy5cp8Lj4W]uYYYyӇN:Ͳe޽q%+U|O0PDDDDZS&˲xJeSv6yyi\j+$&&.ݻnwaݺs:]fSeJrO>>cp뭷rr<-p7b "6{-PHp9xе{vUVjs}5\| a0`Й5w( ;wl&Ci)\.^z)?xN3@HD/,Jɠ5,Fqu vxi FcHSdXfcz^֭]C~HIګuwH&ݚKgۺMn֬ZVeJeَ$`Dه8K7Ybnx^r;pwR^}[PB)ą||KNTN ʺX ȩ@syDEǨւז+cnډ͛4M|^/i8KalvAičxLv,q"K?&; Pl*w?>6s^5/M[]!<6P+""""$iz^>Vb -tx* ?eY?gn OX")9ڥye;OIg⮫<91E{-I盞"}Dvklnz~GW1^aaCYpɾ4uj(""""2F\` &bz<DE0l"YGlX~yc%Y9=)\c,I{'oDudCdH4uu&qCOm;e*|Eb\Sk="""""ǑaڰWPR\TīR:J˱'4ٹ};C9/`g);j&>,oe7rVĶkGZ3BHHHmtuzN"cHlCCs4e)<|a4, :3} >cE*.Uۃ"n)U\21".:!(i|jط8SV$5񿿁Q?L wM'ze$۶,vɜ9sصkWӧ;h``vٿ{ ®eߗsby 6?1t /{-?%|{>d} 48r{eR ^NaE̿Ap.A ]It3嘱;oߘ7' ؉vvoY5_H[\v w\уH`ӿ|| \ aw>"Rgj-3F|HcP HuD+]a\)l,^ wt+[> 8x>>Co-ۯoT3pJg|'ЛNEWb|˲8{z ֬ڏ[{N #"r@W`ݘ[զRPDDDDA-'bU~(=vxVȴsb#k&~isSuՆ$10-ϖL %sҼbL %\ѣ?lCHsDgV?o7yD>f%!rwԴi j(m"2^2n{'~WqcC724l؇4y!m\v0Jⲇ^Ϙ;lJ,0DZ~Rw@c.""""-fn@iXtN"AJ g8#\uRGq^%5tHscTNw|i/!?*E;ٜFڠ@ۏ̔Ͼ SH#f\;v,?:;iřC 3 y`v^N?w]%ͼ`*1EJ6nXW:wU4C b4#=gj+"MZ(޼]1sm4‹~KhhhvibzZtt ^=:sQHQ'""""R{,<$}?}Tp,.+YEH[4.j!"""""Dz 8m- ]]WR\\B94/H㑞`T٨ò}Hp:gϫZ ¡C9eZv{wߢ9UL DD:i 4 z"k)gs.NK5XB/aY&1M?w²,P1:oqQ*Z5UOzƤ'hJ223L8Rl|_DDDDpr`%`t2̳4M֯[qQ1$4)v,'~  4HHf= Օ]) 5$" DDDD  )9},L,`@ǘ"ⱡđQ1UMΪJۂ ޾ .[Wjain|P#,""p9xе{vUVjs}5\| a0`Й5w( ;wl݇ED$eT#:}$H-EDe 8z[>I5Ww6L5K|!C,Z""͚U9xQѪL7-1A嘙YmXXV1c]8dzC9P0*"]YZ B\\[V\Ӄn'%7e[,X~+͆0 f11!ar5Q_VM=HI+J3MԳixDdS)84MϟJ:a6W(a}9.Ze,{Ii|U=T׊P! &"M |*HiLC`C>0Vb)䴡 XX!aV6)6qj=d3'sӫ x\mgs]g`o.lbϲx<)L)y`>,Slow]@OrOOO/gd>DDZ*XbϘA^~>g̠|[Xe<%X~/ibfb&~, {3yol p8vB֪5yp!F W$\u˃\9,abu1svHXĚ幯ϲOAsub/{ϋÎAVTG}CVc<UD\ύfIFzzq4y.,Zɜr³,歿{ʪʳO2F=2哟 b e< o≕[)a{W,{Ŗ18֏Ys+F<{7W wb_,lDDݒoؽ{7{.?\|Y+V¯9kpu/OxvI{@nz/>ӝHRR#uT `#m2IetA _^tMmke1\n0[r޿%V_HT"IIPD\s G9`cU]!^Fff8QD'}7}L!``c_UDZ+=Yhu&2\_ ؃ؿ$ǼcmE3D`B~7٭pWFC|ҟY7.N{Twń8{C0ǽ a#kJ,a#xoYb%׌]sлw-|m;瑕ζf~_ !.K}l8.qݷ>^k_:b6.~7]W}7##عDDUM$cĒ?GK)ϰ,~r'W= h3!-j^Ʒc!X>'^DV [#-3ڵU񛕵4нKGJKK-\E##m-^Jⲇ^d/pL{?ǫ;a,,Z1INuU8 $*]TH9',Jk2;4$T}QiZ?B lx|ϕ$1n\~3ߟGv cꏷE ?$)pp~A#M xlw {1RN.[<ӫ'Y=߇ˬߍCZ޽XCW'sBI{17?4O>".$ln׾=˗vĆp愒OCF"r#‘:\f>ZH<e;;A$"Ԫ=-nٝ/_g`w!^!AW[x"{&ҹg_zp#3|ngh;,l/1^JV n=emܻ+qxeU3Ƿ}=іɯnj9PZZ @8.|J}<%XNW1'xm#W_Ogyb9ݖKnKdg!)%P>Vm+VQ`UFgx6iq8#יpg;y|BRȰDnj|mكY 7DKS溮=k\+͠5GDPЙЏe7)܋P, Vz3_>nܼ ~~+_nJɔo %:CM?]U=*l|`Yvl ʂw4?SXE`a|e݆O>o0{ L)M\9$h#߽)+Hqo7.~g=!1xxgmtӍ {ao?݅!]#p*2X9}"1=/榻oD_v3w9_3g3&&΅׏$3 6yE}ѲNQ473f>= 'e %ioRwp>$>;2v,ygf=DtiHߛ69hޏ# }D)Աł_ fEt 4i]/{JÒ0:G`Rj=o$#μUw ^aFP >_I96t}(ass #)qgj_Uׯ/))|8>1iDڶm[kpC`tl,=N~}Awqfo6i߹;]/ȘBpa;%r\qJa@X~>2;E>+Gٺ\iSfl Tǩ21pfKHb Jd@wB@#RC2'MR+?^C:Dzۉʣ\#] W$?ۛHN?7r.%''3aޝ7S\o}(g1ޗ^OPӑBhhv!]9hU2\`^kZ6} W.Ol9{}c i^gERލ@ uGME 4$!L< pɮ?90 }ҀVTɉLZk}n?F싌]Z:;;uKK'4L!ch$Fxϡ") Rbz)!/qu]3|M@G {jGa7EUvnM{v]YJE"Oj1U{L£`{;!GHHH&{ 5 o5gxǘ9ȒǞdAKH$PW)N2xHLA䖈 v" EtF]PN())耂̶'2h6/BPQȑBJFum*;`o}qY':0Lvz u7w3̫LAA1Σ`0٬b5!!1 Mh^oC$%%7:XmVD'ZJ>tJ'9Jz6*( 6kAQ {j||:UMAP2AiN[fE pҞbH?PG^Y_\,EE[YhJtt#22cG42X!hvZ'/a{.̌C3wZ ';8bdr~uX6o6aZOL dTnle A! CBBI똎``aJ$C ~"IIm[^\|;K =I߉9f8/K g杽(לI|*{?/Ŝ{oP,F W 0&-&)P֝jv)J%jNV`E)!<1XC'Ă_8-*^o cg;u9jZv;boH¥R|T䔈n>4X0`{xxktFvke}@A8B YY`n4M@JK ߙ; `tjN ODQM -RoAZ7+&iXBc_-f}(%%Kh3OH &&VF܉3_~ؒ<RRS=BГ}% PB_Wzm[˨ʶ_H܋KV>РCt:@NN6mIA zCs+Qb|UO؜5d  . yDӴCQZ*)60JڐڶM8ٴ~-yyƶo\J v5̮Y#PA y(*(:WpaҪ6iL@jH8 j;wM]zt?HA ӵ;@5Z*9ºuAh[Ԙ:-!K`Jhhh퇩@hRu^㇦lcW9.cb)// ڱ!9u$Y[ǽ͖:} D 4N-L".hD܏K5GS@o44P[ղKM۠(-[k*o],)_.Ah-fDةufW~ꄻJ vִ2A|8 񩐵ANTUV:9*ֲ}Z3Ujo -AJs!3"7`o7 `*#;eA>HD  zI OH:?8'xngN+izp`Mw@C_phiR 8ɳ~rTfZǽTӺp~TҚQfמ[_ъ9qH}hI ׭u1`Ru^@p/" .nda2DYUA*\ܰdO:ܱx`EKzSUY#7meQK^Z.w]+ow$KwcyTܒ_4 t +Jfʗ`h9BF W'Bsq9 Φ ljg(gYE۱vxn#AFcv,*e<E7ンKbx4z d p9$zPq -j>^e*76 Ѽ\A;}NԂߣ)5E:Z]/Ҿ=Kh3,:J+pA8>ϼwp 0}c/?fV*ΤU'] SH7X=fn:_^arՕz͏jƲ)?'[cuLȬ#8uGWygj<^yQ(Ui͝κc :#99w6.R ~7<}/4w ʁ$!Tekފ9_(Dwƅ]υ=+MS/~<$uř7dB琦 N-CYZ\0]x7hŃ63D@l2!nu$#f0:\  LH X0Bn6[0+&yS%n%P9崁AGUs3;Gf%a?)}UB^Sbyy`'O>ɔΥA_ 9阬5kc6oИj;F׉D~fw8RB "6}O¸dhB>?|~ٷ1X,sPOj>[[͡v3Lbb6uWypt^}{r:N ^oe@d޽g t#dƣ_MVN֦lAh=:Ϧ/ӓ3yW\` XyAhE1GD:c䵞 XԪM7A~lQ'CfDȘ,f7ei}4/N8+5Φ9>L4̓O볩TXWӰ.DkOE*whn$~z* k*jJ>6knaIt'^'(_h\*;ѱci cEրסi?398m9;.c}Կa$aٞU|AI'3Ooʠ ywwB  ^-psXt_#)35 =<>GWM oDk Z%rTV?wQ;I;bPUԐNuF[ju~*nH{gEQk9VbrFO;#ۦRo;0W4x^vN)HQ3x0_9mrϼ W~/\ 0a"o|ݔqh }~2PUTã3'2C`++ۤO1م6n`5>V֑4͉lA{?ЫIx,͙V07+TG|7--z'/i0&D ﭸVm}ihjPQ[(c9̩|X\_m(UahU5UDTߙ15 䥌)dնWQ*]+2O@8g{'CbbF8Co{H\Tͪ6d8F]ٛ(4Jqpl/ٗ({Z,x$.AZh1uV)0UNЪeSs}c{VWƆ/҅R;qO2{%a,9|Z7" ho注Ykk35wՎh5lhLJ"jRbODSoUi(q9HϾo'rB{C~dS]q Y}HZX‹@vdsT}!Poo۽YņՈ2jZUgZ^Pe͒ wDxNA'X0'V)^0T .Vkiit|+^9KgZ7Bd|:eD#=](JAU4tr1}eaբ \Hn9@݆q/aTU '3K*@M مp} MsO-gӄV4-gsx Qd =zE13-xILOAAAD_6ukݥ[Ad5<}7JW"[BD P'"+`MT@(BS*|,#~UWR;c*Eb]LU MUcMA!T=Jd;:Ąb3}4:R*ד4)TWo`wMXQLsX}  ڎGFrm 0ϼϼn{ùv4Kg53ꚪ*o q(j㬐挰{.z҈7Ok:l5UN-W:D-%uE$A@W.v|9l>_M=R9w(Mͣ;k:+q&Ǣka^5xV˞UG9CMi˾+ٸe+V~˼׿!hgJ Y}:rR'wFzYl5X+RfC Hl*ͭТlR)QfhUzs*j;!0_}μN!76P*7Pѡ)J4UQfuCxV=dT.ާ3t uW6L\fpގ.tgkM.`CD Bc6|'[ĉѬu\j L8)yC&z֙jә[y ^w*C ɺj_LwZUY>]6Rc0[yEy1[4nIpsܒivzt'yRpڒ(F{jAOh~l7gV}E/K`XhhСիu'uŮTFQ&Gn#x UvRхMXhM SZ'Y 5]y>5 zeI,~ A3(rm а֮ZNpCNCQz:N^GסPt5]6R:~J{tTxUz֮YMN]llWq w8;\̟ϕ/)ȔcA[D \AC/?u&2231BhMTLJ7ӚVΊߖc-/sR6~P6BIqT&}S"YC6ԢmKk}hd-zjfX$HпЂ~f f$c6w-Q0\F#g]|+Jł^ c:#O%e'e#N }":Ma:|EiAtQluF QNZ߰Xj TAl fDhgȖYEDD=zG^b=CF*1/B=3ЛuUPlhX3JA@A'"#Ap((Bp`Zb@%赝)+sd{ (k {$, ǎBRGTtt]p|ɿuPJ}WS/, F>^pFҒbPUU$AdCT'`Z(>Nc^0W[.dɦ|d A`xM1QXo7X"u?%-6fEgG>y,cǎcڿ_姃Oz%{aر=2;mN>Hx Q~2EBe! gXAZoݱX,#Eiwm̱ oPSRHG`Ǣ7,٬m/wOb7g=5_~Wwr 'cn }> :Gyk,?Yur_a?G BDjN0:Ghe,Yž" w=ӆ'Hx.4U}d@r1?o8_z-Q{^B~^SBڹ9̻k?( ]ξ&Nj/d1g֢Į8B<(n6&Y,3H*bXhlaAػ(l\:rtbKY݇ U2Z¡mO=+NDh81̐.䊱mn2K%m+?ʖel~g9Cy%WGBX\o[ݕ :2ӓw!jl6ooO",O_{/WD Y+gye~LHl@+cyG0_Mk7s$a4OBZN=nm.=c {g Њ6x1Ͼ)GJ$[(q2ݣ3{T~+OAVdɅH9hE-ΡpRg9#]7E& ,h@ȐAtŨ+oaln|gKh?poNMv<1Xmsl{LᎵd Bb9>޿i2 rhۮ=VP"'\V6\H#%˯t i'"#Z@UP$D߅MJnc'ٶ,"yӢ'GDEL Z)h l(H!D%F{7:8o)ٟc]:OsK`IDkHTJϴGV3Q$]dj ,̊kl{4 e='Nge+ Y],vG+ޠGjjVw SܰVNV󦀂hF)9CMmU5 5WGy;NWe[ʨ&zo~g1-CDcǰZ@;S|JdTlfg%Q} d`U{SuƨcCki" aYrew`TD*~CI(w~k f/Lv=4*t:Rur* 7lM!)g\Ծ%%5O?mgݕ3[.BN 7*)zPV\JtGXha.藳eN} ap&w5ħ[mSjؐЅlv [zsclYmg jiMQ w[ %9d&ʠO?K -.'W>E!!)#Z1ď5r~|ҋuu,7=e 3#rQ^}29dG Mׅs䓘+8}Cٽd63 b3ٔ7Ǯ׹@ka, BAGJD֩;bcGόgZ'j&>M Eۗ' AZt{0|X mؘ(Cykشy K{GbzF"@Fln]:lsӺlY;+v/lڶ-[a^32c߾_Άm;غy;k,DgqְX[_Άmٺw~u7%u2Dg1zX,ʜ%a66U gEoضm]}&2rLf}9[SYpCmi:˞[vb5 Fl8rs ~|'zNvyq&g 4W4'Ň8)+-> 6۝G8]Av|~_0gFg(bw@%$+#/nUӊ >o$OKuvUb;a|ooC,=Ig`}粶Їס;:P t:FG}ŸRvptJ8=.UџhG H ~kXJ8Q xc!ߍqt:F ϿJ!mc!;ʛ4hu#V曟$j߇ .?Ob fa4Pab3>^`+Gc8ḙ<3VDnMDP fw8 Ds)^ɠFNgN朚y晚{ftD-b#ׅЅ1y(SzjF C'&.QMY*g_IﳝjξcuUj:z䭉YjFfӹҊH'x`=&2Zl$ъlXSkHt4X BK3d޽ ( pٍ3=miLQA@zc_ekӁ_AK+|-X" B@-p]T$BD@wO )Ƃ τg685}}6=|C>jnt MVN \p3z"'-/M//~[0zS! >I'*Ip@8J'  G XWg0eLz)|[6~'rW߼;JY`Q:/`uUchr6 M(9wF.? ~؟ȺRVw/ej ~j-Et)>5' J+b8V˜ƅoV{Rټ{[+W0㿛)V| u8_~/ā޹oJ -OuEn2X,ی# EJtyţ9 !3J܁S8ڶM%%>lR<;u+^iUS^e}}D_z\=V ;#5Apkl^ Q5D!/wnה =d ؞sw_N?WQpiۙ>i(-6 :BiQ`.F( BPٖdwց[f6ȝi6G~֧Q\#8f]/3ʫ8/#`_[_!`={߬Pa\x4tDqt]y,gW! n9-u/q7?|g/R-s]vO&`eχwz 3kJT=2=mps[ٜmO8V$u\?i :`a9x%{ 5 13ntrPB\?[UGء' Y-."}ͣujz[k>}wb1DȘWrA6l3/ϟccS1➙!a˼7-+7} >?Qfp|g`WD3Ab#nO,ϒblJ>WRn-fr!]Phe|o8;0FN-f߆mtu!~͚Cn_'m%G!<1b6}g%qU28 ^OEgod@hu '݌i`ZrsNjQ>Zً؏A}GTj7Iן[}zR|Zλs(1-?}S|.Ϋ*q-}\ț=bDc'hI=O"+(_2 mюnd8nݗd5\}bi_}#1IiR #)?3]_/fgy08|'_?W%Hf+tF6sp޹+j۳. `uD[B=`ei=:ddt$EAήÔ5ceLA{CW\WAzJ'V6  c /DCѹ )'sÿƒ<_. *{䍥~2Iٳw/{HY>bY~Vm08,|>6XtENܗ,6[ֳqoAwZ{Χ8ix6(Ö͛ٸ~5} > ?1.Dt#Y/{7רmrѩ ^gVnb/dC?$=?߬b|ՏsT#0dW VūOFOOS<4? e [7L:+>{g6~FJ-) He6̕ASXD@lo荬j>7Y=3ql#LlYCϏɸ?N\R0+j0gRwNhO3q{;-tY;9V@L`θh0b$Mu6IbtqBW~.-~#) 靵! ώ/g7*7PkD5|BaLPudmZ5v-iLzn.?!GwxNx.7!1ݪ?.ż p7:W/Č9N=鏟zȠ34;d/MvfMGucl(! 'XCLNyt&jZ'd`lމ33qu[~̝(}< p;&4|j #c1t6$^TAV 5 Yשf.e6lȸY!&%PZD2]gSh.eX,_-ظ[ĴAJ֎buͽ3Ӂ E: xrw@DQ4z;o OAGxYDCMA{O ;/sj]j o>c=#}3K* L_8 Mv4-Xv?\./1n>ɺpaRi(Y7μZ1w!ݸb\R ųrO2xBc|AvTd6[PGY\S/dƦ@k*q=fbrkEhA)AqAb[Tw-K=Q &"kW" P.  Bt;~M{ EPAd$ vqyt{ 'rm #A@AAm8SpsD@wmhX37CAp@)JZZQ!%ܔ30 Wа0V+ZMUh AkIIt: cwJKŘ B--lTE6ZK] (!1LV  >Oy !,iew. (-)JP KnDEǠ뛴iA~I~~%bj4H=A7c63CHLJ"*:0t: Ah dc?A[,j bq:ГAUD,|[ ݽqS%4^PgӒ= #[]]kًYKA7@AA>9XN=,tz}6rfdE l> IDATbS'/8s̹( !~/7'QRRBzΨ*_v-AhX,L&S70LbĽ]E,'pшV_;o ;wm6БX֭^'4(ZVm,6m]W'9Ķ#*:ב}qbLAHpFlG)  ۏz}4FWެ9+YϓpaRBPZQR\kZXZ\ t΂ ړ" .͆AВ\wl48-rwsRt ́#;a|:S>tt) (.)a/_P_\CJ_4(:mAx7fK7G]j<^h3f,#b"xYv5\[vžƸQLvqved#9,6+sy@lƒ~fϞ́ٳg׏&_Aj{ $*9'䅥 xf$ cH6)sмmwy4_|~[s{? 6uuӮ];nvzo',<\!HA\}ꊀ\ANeP4Eq~ @%;g p|]op.9g{*eyiٴd @A!\͝Ӛ; `?̳#A Di ߲|jV?/YYƂ0Fv!u*ݕ#٘MK=|*u'ÕCp#j>?c;v*?,o~zg㧇'3vX^^( oӛ4w,$nj{z >V _\Ů"D6p Mm̯'Z:J_0 Xl:}֚ʑ4w?}ox_KeGÁ;{ ~?u'UlD~+N h7c˯ç[&qGge;u=? 9qkKMBjd6ق0[7[:w ~_Ao]X_GCOʎqbRc)҇zAlp6UUC@7oŻXsH! ŸL$}./wz,y~n!C.5cNӚD BgG^Aͽhl;R6WU$Fٮ&1 +<|^/6 8N*^t-Ĩ>((6l+/^r5zBDTUUV^WQVv|PLΝ:`Ib93iٴ3{/R Avq䑵AAM&Ku!TBzSA$.7y>6V^zx'Sz+gtD xw}D .So 5_ubZ{ d/WAȐTp;%`(&cZQ}$''2kINN4U . 6E_ퟶo*>yiSpafP_sRW8y]D :~%RNzj_)Pw bj 6 zv:N0bkiD| 9>zG-.!!! p';1Omǚg2}C,,=;,S\RxV6!;s̙wΙ9kF#m;& @%IJAA"U,E=IVkPXŽ`J/B7j pm}P 9!f@Ć@Ͽh\]D({DOBjJ%(47B隁ck@ `BHp`pynvU5}>"ۊD"e`tڋ"~ !Ovl !M P{2!t%L XFBBHCQ ;@$܃oBvzv /h6G —!ĕh ,&BDWz?qb+?,G b5LH%M,^lUBf$H ZjjfleZEvAٵ1* XI#(K22X& 1% !`2l2,3 T*x@Z٦f3`6aXh@vtHNNNZAUej ".Nι`I!Jq%*IJ``g#hЍ~u (Lt'/!V L--8TY4$'t"fZq7$V&  33j4ZZZԄ*B$Ɔz٬EBBdY1Iw)yGR8 BEp5:т];%/ٌjgd"))ZmT*?.>:)HMKG2U=f34t:'>>$׏NCvvLCp !(@yc"C@ T ab7??+~Ŷ n[g_`1اt:]tzx= L&X>IK,$q 0^ !P؟7vO@B W:;s1za?1xIW ̇ewذq90dX>ɀI|6Fa}a.^ *5h4ZmZB(aX@@B!h~p`4BlK0"M(MW(/;Rr[ l[>hAk>}1 79wX ѷUΖ>?7?6j0tq3rsrwe`>&.yFbBHx;"į^=$Vl $2vQ.Zk_ ԹQ9:FǘC+_7LR9<&d Ebyd1_`1[:l!BHX akK|/6!{7j<#cٳR>,4&ԢQ3V,)Fa[*9¬뱵>ANJMbwlƪիb2a?l1$BH DzgҫKؑSP2.:Xlq<Њݿ?3hNqaF}^Y,$%҇z4 ZPs3vBF|F T_PR[ aÆkۼz?n4uBH`=? XZ p!]=O,M0Jfc(VTqЫ\$I$7#{ -;P 3?ڦ(Sh@FcO#{ AEY>!0iD̘>3iD:DfBq?Q4 AYI04&$l%}J" wtfi`'aڌ5#{ C+QuBd"15y*qؕ:-}Gۀ3ٽ`6TPnxK][5Z`jiCS$B:, -G6QLDvtBY=9vkkBCaƖ~AT؋[6=E#Sj/[P_[֡K4ɪ>Y&6SMMN[z9-ը#m=HMrтږN-5ڦ~?|,B!~E@_h$V.6mSIvQbWDO`1Ga`~vo†5?c͚qWk5rmX;Be1CZåJhՀl=}GfXNp *tl\p(P$B7 @n$sH{d!ϬȺ{*] ; GO;x2x&!=V%ߤ1qpZ{;d,vrqb+"1O}B)vSF PBH3ȿ` >CD%0LgmuľB+̲C Z=}G=41lB5۲r\%B!KW {KϯzxC'RI+b^Whkn{Zj-ⵝ:ҫB?p> +շh;萚t ҫCm`Baǐ>Gq:0!XG քD;&ݨNJbPt,˰ u_^ta-VC?P }c TW_[BN)F^ sWX{ͦ҇zW74!P^~=Ϸ}4L!nΫJi+ B:-C@p'8lt%@mGA(G,4%,'A =h:eehgg h%҇ztTTG hL!C `0eXe@{4zK]=Ƹ#"11HLLĉ !B!'Rɴ`|`55u%eŕjmK&h")7]Ҥ"@a6#hdv҄& ߅ؽz '@%cZ BH% qOiiiL fFciD-6 ^lBoed`]tT }z=(T*X,!B'%@= y "†2K^YW{;OUz}2aBH:m0Ǝ5ma{vT,j63 0!?N*8N8zұ=UDI² b?J(vs,KhlR~ff jKB!al!k 7(a!c-JF 1%J * ,f+l6!>>CA$ZZZ];Tj"99BބBH&@=KBPՈ`M bu*rl * i8\UIJՇ#+Vڦ )(//Mb6QQQt!C{tqhl` $b~m?;!kE6F!jg}V1(Z&'T류VV{)XyAR!g!a1T@`B >^YQqp#3BHCdqq:p0#z[*Y <ęyZc>2+/ed[=다zPZZz"%Buف?XBdRl]WZtq_" M]VV(AF BHA;O&%KKC_p FzVnNWxi0`4vd{O"[NQ !\sϘ3NÅ w&{oo>3f`){oU5B"oObAuݮ ׺]@Ga@+acʲ<8- Vnv'$)+^~K(;[v $`xu/F\0<~kƒױş^aŚ%Xxg)w~|BH Z"‘1ĂTHB',+#Y cqAX~Qފk |)$\?[8hƁͿaݞQ` K]یns XxոhӮ,4;pi0rFO\wnwFB`F E*cNa~.l"g/"]vɴi/@(jq9}.{3'P{i#xum bv|g%@h^s ƝX[EqEзnSFlXM>fqH~BHTCB2x* $6r[터>HA(87v;n_x.?m^Ǣ۞:ܢ-7ރ#.AH!7BH@fC#uN?8xKo1?Qo |}BHUSGxMTԩx(]1{綋$|Wm3B=\]!\g{+ߣ'bࠁH 1͘g\"0~"^7m s.)#:|q_ nؾz?kM$$_l7y[`wU D ڿS IDAT^\zq15P#}Lo†ݵr0r\y`$޷ @\&N;W,jރ/ķcĿ{JtIw<^:#NMؕ˱y;>|u`ؘ!$DS^X$J!9 $7RHa߆tHjCجs{7X lZAbzrm?[ LɞBDKVA4c˛wPt5XR>p7*'=-}݇+wGnD-8\s͸sqo<k:ϙoο 3GBݚwu᩸n-Xpi(*Ǟ܀9%qXx-rCnÝ⚙x~Mlu"w",*?L XL8oRj{ov%$Q%@Ԉz% 2:M^0?Ѽ} ^k9 wS#hGcD`cD 6gpfZ( %މ@#6FͶ ҕbP7V#b'&-Qx娐aB߄NHԭŻDެp1|xL|NݏO]Z.=(*쉬xJ(1#cpeǣGcX0l`[S`'*\Vt4hjPkjd'-Mэ8 ?IyH\N?db;>yk۲;vFvpW(:Y{{SF{ nB|( B T?%‚}pT\ Z~AV(l|m [,%*C.QlGZWյ Z0_SURgӿ%Hp@M6=/Vǀ3opB~8f`) .%GÏ[k`񩇠#׍~J>CDZ؁DA`PK^ )"{K/ݧԻ_Tω6F )R|F@v8:i=@B:e+oֽ4: uDHHq곖QdMP<.R(5tYl@BL-h޲=hGss{t$,0MMkZ-2z@JJ*M!{:6Q1]]G;9P-r,`@ך`uRHm}NӀ] FmOv^ϗ2) Zl0_Vk9N(N)]z蟽xj5幜;l9jbjnބA#aa럛1vQ!*-CccrsipB!5@6Dl^PFmpW0M}k+[>JM4;(]@ p[@ 6X$N%8{eY V86!Miiq&,L}$>ۛ}z\R\l3fgbb6c׎:b{Ges$^.Ҧ#%%֯EMM5RShtB!bmwQh %zZMvG)RtZS˃Y cl[m]!؄хAvی;[&Wv ۳1~ڻ͓xӔ➒tYK0 G {9kF Bi|#]s'In۷;iU*5z释hpB!_= CP"v 1HWHC  0E7RzMpث-`aG,S/ꩯo~Ni… 1,mO;u*?Lx}yhFck J]5 $ {Mk}%z?)0ZhpB!D$p%F9.(uѱ$C:;P@2R#NL~[/gMg0Xs~DZ* zr-Bo4{`NC<=֐'|<== sBaq|l7&))u(@eH<۽[E<)}mDEGP,48!B\ :zuSKv/)P; <'}OCՎl_׏ f\R?<5e$ZzWaCp|U.}XgPii#҅0/P1q`ʋJןr*Rt7}reJFZ .#(s:58V:D (cCo>;@M:?"+羬EB} @8zЎ:󺀎l{B!]E48P~ƒm: {+Y{2Z8zҀb;Q?ϟl <_)b_ZSjs}A9b $*rQ.ocEhg$TH!Hq`ˠϗhr<|xh0mŕ'(QvUа?cJL3 Ft􊍴xoT y硬O8 h)AKI4iX+Ev&w :p.:nш\)B +VtKwfϐPx99{…I)ꗷ{G FzBest^)[ /X* Ive(3Ө$4Ȳwa˟BvHC=5QWy(F+t+[px&"^K!:W0 GcsX*8-ċ*aY3%%q hd7JPR2 /6MudGQ{O(CE!m&; ^J/) I x( (p1HER܏H #%(tU7Խ~{ȵ'pKwἾWߛlizlu#*{;zu-~S7=u XUfLq\Գ < OĤpqq g|²C=N!#'゛.AF>-WBv>d`= =zF##>>3fLG^^nc%ɶ-N&BHl`T8UR_"EPpGq?I1pM]J^! d %`ȅw>k2u|vKƌ˅־Q`޾&j3! T,TL*M#|؆fTV4qpLWVU}dYn}9s&t:)CSS3v܅bs̱mXֶcVSIB&},o]0 ֽ 'H$pkw꟡5 TE pڙҽQ5^`ضQN!KI ֶK1+8|^ŝӃ|1$B@ȭB, N3ϘݻBql@8:x.B!Hd\7-ОoO[ E5"*!wBbkf18.zkhlއk3yqBU׫z}݌aDV=!ːeYv[\2spC> " = !ȿ< 0`pWע Ѫ !$iNo5c:*z |zn-~qX.> تcpY(&G_ۏ7ML# 5o`6%XtGnokǼZE!B(""ܭ(ER_B #44fչ8i昻as–nüym}qуOಡR2~ ]0>| ] O{7qL#d6mW/ u.Ԕ']w~B!h!Hߡ?=PZpC!$Zt* 33]et*炄~B!`\Q(mZ@k6/@qP۴龶v1Qئyr 0!BvE O9/ߕSrPيtm}e/BC$ lA@D`'d':$<!'A< !BHG<.J(JqS#KP$6O tI%A%畤O*dZ?5$ZT^ !K؊"]ScB @IRAbj$rU*m]?T*T*5j j $]RݚVMl?ZƺkPԫ N!$XtW1èCi IՖ! JB>u8hZZ :Xӣ-Gގ =_W-&^̦ӗ'}B!$B!1BR,WPm\X=IXezHQXT؄B!N**" 2jC22zb6(B!BHt>HZFZZL`B + brPwwVQCO^yqt?!EkB޸U*5 FIh-B V--~^-EuXxРCg"B!5x "l(R]E@ ӥK1doV|BHT/?zB!]#$q'֖kw\1:[7VczS"! !B$I(BbD2IJ+a19VB!B!]D@.w(}{@r&l.opLnH4B!B>B!$r(N)`ţwλ 0:d '#j0Ͽ/"o~зIsa% .bEJQyf|n4 =Xs0fAcu@%B" nBHl"|rлO_$JN-侇zu¶Í{ݸjC;/Ww^{QQo !!o8'Nr-(U<`YcZӥYkw 3gaڠg\5ニ`iE#1iLLaײ+<(=o(k6NՃD(A)[O>^EuXRl9s'0a]q-F0!IQ07P߇X"lՕeffllݹ}:D! 26PJ%M^r1.>**HЧnoCn-;Q{.8:All=Fm`ތ],_` g)~û۪-@т-Ƴg\S󁽫aٳK`vfr܎ܓpЛ*EZe{s IDATWx>Ox H?|\=$  )QYSoX<|ܬA0lƽb!0 %O!B!t ;^_mVy OE 0.5yyY•vbBh12#`m@qzB @>kψ.CwF^qKDlll߱Yp;oپ.WQUIwR` CaS P$r/[0tX׵,ܚz_&&a@z_Pei9^_܀)8F,{^{,&N< m78r hͩ%B!B†5*+! OL L,*`8P A@ *DRI@;^65`͗_}o]N,B~XuI@#B!B![PR!/' f ɷ%5@K 2>]vQ!+}aX<꟰u!XmSra9?C<-uG9H(+dCOA1asB!B!BVL!SK9gw?\EQ2 l[:B?^wN Vc?>Ni!8x/ tZrތ5?g9c;'X^ hKQVv_KzW [} $!B!B>>-M,`ĬY8bM*cz0̹EBĒ.{~O5Cq⥳p|q4 kNF\z_OoKz>wȵ{~IqPe%ǂʟ^-{Q+giy˓%mQwwVQCO^yq4/!Qݾ&$f۷?~3~/oPב3pĘ u_$$ݷ^N<ӾĨ[R)p%^{ fk}=VVlNrJ*@m ᥗ ϓhE\dwa `道&$z7ՄlnZǍ@7nF/@bFWsC By]mOQ$2/?a*Yi P5ϿBeQ8'!D.L[ `$X|-ʛc0kᴡP'!$۷_'<& @K?fc}]MH̶v3.{^s* zHQ`hEnFŎ5r^T7ˀ*yŜ=A!$XB|u`AdZP[S?7oBo %$,ls3Ǝ;*d>\ehll@nn> N W15sA4_K!Zr8AH̴HQYo:gZz )3 jm߄oߢy'>r裏glƮ1t(f m !F* IR$kڴt`FjjN q9Q }7ฒNӅ$}zNdNQyf|n4 =Xs0fq/!Qܾ !ݾEN,g֞;9 )>㟧iVl6a0h0tzVF¢6Q |$}:t; (۽=2i g ر*v+ {ÚdU}Zsn&ڽ ?CljLpBHTd6׿E]]% 'ǡ~ 7ՄhN؏;9]u2zZQUUSWGF6eһo&)VISuHY?N{mb^k:Ot`0d=$YA1?-Y6:mMĔE/a0=`귞˟Ʈ:ub6>g_6cI-b;cVaoO%_yq4.!Q޾&$6܇S8gbYָWR$Qńc'BmKⷋ:~4H> +Fу@B!B!$7,gI׾hS$B!BH )`0dܹƅ0g_ϟ f%OYǰdc07ɡ '^y!\QNDBqn8=Bw=c 윇)B!B%ԜwVc/E?erŗx]p邅 Z™ Cb™3Y="rV'Ac[@x'ўS !B!{hA;@ԲۚkoqHD]h?+_NMy J8]_WaLiW.L7JPR2 /6Muާ 3M@s1V_syb }mO[Y,eop&z_?g͜L܆7Զr_< %%%2r,v]O): .inFR()G!E9'=m.:!BHWX>30G9|33nė9BHl`}E=>W`^W`{q䊯:&$?u7T7C hƧMo_y ld x5yW~ji"6׈i}8v h^ XU\{(ɮ7>· ^ԩ"t=Azlu#*{_;o=axGmJjƦg oX00wh*̇;k $$&(rt.s ;FE'ͮ6aҥn둟8gasB!D=uWyJ'^ (EG'&{)\cv+c]!I L&n:%cϽCol&6m&\^غd@]ϿE[-xU8H9/v}.³ m㣕XzjOxa!^ .*owgՊ[OE^^.!RHS !H)y5IKB!݂@U >/ixϱo`^~ï-%hj o-[20q\n@ixqZ&C_ er&v[@SzXPutw}qMa ,ԣފ}k9u|>i*E!DG{[fΜz{!33Cqq19IZPT} !ҕ%XjM:O:W_~zǷY\oqSs 0o容xnx2<䩸gc-W'h,exkQur8s<*݈ԡ[Ȕi?~|u1._& ̚8mhr'E]^y)i:şro oUyA_h )qS<z;;`o׹`⩻¯=p%pf{ߣx#XBMTwZ*BrK9ЀPU~.w HZ$fމJ- y Ye+dY\2spC甮5ZGބB0!B< 'K.?X+Wk.ľ9s νxd"1^+D⡘v։ȭن31}P4h<6ëZzLZ>8rUc#>Y[ݩ4;nƍA=cPZw5, y3O< 94B!݆Q %~SebW`j1efm ABr]-Zd )m#s[E8_/e=qmG!U E G\znl?*\xr&ܮXսqҌHV'!+ XV*ıʼnNmOLR^}: l jCq .kwȗYDCd;{i80)Bn kdvo>`I0j(4u$Hh$HB \B!]A͆sQzG +B*Y0qQE k@ =DV<5z$@jM~GYs96]SzXhl38cNb=!۽D_Y$tX6}}}ԔOmM$@T B~O*La =׈Fc,±s\T!kG׺8/.{{\3/II]8)m~C98f"LꢓqK'V>Z1dm!m @c)(Sw$H@h !لEQ !ҥ+6c/.:H8c'9`fd5ذZ6o@\U [lj;|ǕCVъNBјi(3 }$n)|cFгBh BnWغP(.˭pHߺƠmz1@B!tbʰb]%,hkG[*IpdQ~vAm]3HdLӒwb}J,{n 6y8R0Xg*N- e v!. TTPzQ Xp^@ʼn 22.t$MI߷/^9ys33Fɽa@u|;Ǖg-( !_H@-0 %wE}/w n.&Cł5wt- B!ʓ݌>Ày~Ԩ݊xK^D9zaQx1Z^GIA@_Z Hi yyו[8U컼DZ&U7Fm ?+zPgxwx{fZwTw[}*^ޤwV% A:ԓG!!EY*^֣ge||HwF ~'wDZ*GVfV~`ŒCE IS< B: t:t:OO/~ K*^!BV ;`&BߝЪlӋ7;y::)%)4hؘ鎻1 R{TT;`6*\)o;~d̢AFRB!zw>yZB!(^խӓLY,֮ZmGDrIe !B!Jq N'N=GH͉2e2B!B!w$٘RqB!B!BT2 B!B!UR+t6nM$"UYR8)?_BFeB!B!EmВwp) KYfr1I/Xv#Wbٟ55Y_EC+ QB!B!.&I;goNcӹZޗgB6]C$X|i*=˳ W9x:@NMJgXE_!B!B[=fph<6 )ӯq&qLF3݆2|@[jYRv6,!~.JS7G+ 8Oi; =}+^[OIs٨XF}n8y=f^cWMf m|NbOΥoݶ$}Zȉ'XRB~)zEU/MMm9/g1j[%t~Š-{/>=(k:{gbƩLw W採ׯ9?V}_׵,Lc-Tvv4ҸB!B!|c2:, IDATY xмC<@M_+6h\5yqfqL2Fhc?LI;U۶1K0(T V<5#+Mp Wɚ2AGHןѽR-<;׭`ŻK=j/WG0O3 .~{E^f׺e7Ago1`xj"k? Y6jz6Z7A]Kdi,iVZ8^.e3MdFAc^3j}e%5%Y*A!N;BVmHaB!*/͑I4BXL=Q|"1"Ƒ~ej>|:f\ .rtWm+0;n*^[.o{)YY'_kk|GsжuCU!Bi8*?kBQyi; :&ƉmZT#\ݓ b_;㯻łs@|Wh_-[kf0ҟ{qu:r{x(; L:7CpT+ymC0M67;UkEw 0,0 HS߭`Pzfd.OcmF7c/]cȸ̹zt"WG2~'i9i~5*m>J L=UTB!BQlM55زu?u"d,\[sBToNK}w o i= -JaDq Ukk6%~KvԠÓøxV hEh7Q'1iS-}?I^w#uU~Z_l=_bOx 3xqct1yӸ{WB[SBO{VR>},a7s0Οz3lx֔B!BZn^TB8mgh5=>3^oxr.)[`c-$3r.[B!BRBD]0(:gƵq8}}OOx"+Kׯ֊3Zx#x&?HZ2B!BۤB8@M?ŞD:DG`4mB9Ԏ";#g^r<B!B'@!pURUh@Qv% s)ۣe12B!B{U+gu5MǙxs|$'B!Bjq6}g{Ac;IQ+$n>N ,gS7T-j&`tVWBMӷ~J:W aNrTG)[N߷_%>!zazKO!B!gYI_=ecԫL~U r큦{e$C?ƠǟgRTu}4?dfLƩLz(]O̙|'[ :ZWBS0wEf{v_I` L_)̒0yʣ4B!B!}m1^X=9zOw楸,79x31\>}c^ԍ$u)&]Q?BiiFƯH{Eŷ|o:G^T;rv aWMf mX g&yޓͥo z~?&VŬ~F= H@l7dڪ?jE^-??s*U }8m" x&'a86fݝqS B$oQՒaKl.?el1#Ò7$?U8z8)=W(gzwgWʫ:vE^ru\Uy\eu u,Umu\pUjdpL#'~˅оz) ԈfJGɍb@Or,=6I3WCegxw GZ_rMg=<7ĒtUΟ?޴ C;a;ZhF,B!p\FxxtF]Ԕr~3޳. YY'_kk|Gsԋ!eC<"]_;N7XϾgp M(s[l$Ցic=1^a9$ 6tqgxm.a_W^a7-S/9edZ_loǢX1mOb̞I?#\WI×6-ZՠJFr Fft/bgzUʫ:vE^ru\Uy\eu u,Umu,װc0tgk! PbY0MWǿvu0&bT[BÑ qY2] y aŌ )7A64]9 J^u K̡$2u>`ʶ3s }l:p+x:u,3Ԉ$q-^z'z'+ފ؝otj/-B!B!ʁTNG9/ڧjf1y%:Ov:!A&Mb{n oóFchft ޙ.t#jp-W;Y;^,4spzO{ ek_y_#e\~!\C@!B!:weliJ=u쿜KLؗ^.鳦nk<ա͖fgME6A; h 47>}1ZIu>Dѻ_Z7$A#V/X =>L3jhw={pH:MepӱReބInfbUDYVЏ}|iq}R7Y<ǺlB!B!q[ @ů 0,0 HS߭`Pz̗a6?uC[)mPb3-2$fLژ 6պo >\,oi]=[['RJrfo$uu"*H쏫Y}O৸XX+{` ᑵ6]P-_i']3.}[ꃹ|b,"Rٱx GC{0"0rb4Kqb7=Xn$r*YgcR3|8ҽIB!BnY&j$y3-|궥' ă;m\Ioǐzw; >]k25^gOyoC6`/4N|;: /a֜q<曳J :<9ߏgݠÌ&~J3?"قgP4ݟGn JF`Ǻ,M2z#3xT"=bU]39iƱ1SO2&^&xsw/Lߟ S_bt)q-/B!Bآ]vѳ؂j>dߨ{g<ʢt^5ʙΰz+lo&6Dl"J(r3_V\) {Sl,#B!g(5%|@՜U≿H<_S|h]KB ~8I0L!B!*f{48YB!B!Tl Ōlvq 3U֤SӡLBT jQ!B!\\"Cb4fq#-sgp#- 964jJFzcWo 00j|BIE QJ/i6cEadi,~_8/otxb䃴 7pOΞ?$_B!3R.Ofs6)N8@f4n ojrDl8z8vnshk?w,EDm2IE k̙ŕ[9uSҹ i\NAp0r]|5G@ř/gې8B!BܚXTv6;OGSp]'h*r35jO?~X,DQ!edll2صkDDD9xyy9UNŧ>a&I Cłq{x| \QP6wGB!B@AuP'اX GJZZ*5kp3"7֬bJĉ_G 6 o&o={hoԩ 0Pyiٲ%ʕ+9ɻf]fsXzEc`D`i?ݙ#^zGsnl 0kfr|B_})1.`!B!nY|i*=ʍdEPT6mb…?@nXt<Μ9CӦMؾ};M6eڴi:tI&9ă㙜4ysؘftwMD/ p9FUKz{95R?%#M7Mj^-v4DzB!)R(*cΰz+lo&6V'uМxj' ロٳdee9߮]ر#Xʖ:bLbzr 6hNoV{iEz ;cyeؤB!"ˋTxLY322XlC Zj%'D%#0k,O۶m9r7ҷo_MyDEEOLL bԨQ@NϯJ_y}FU.U!Bq+sIxt1'CJ ˂zOciPs~?dȑł+*Svm֯_ώ; atYScƌa̙M03E[15'D%n:V^M~|rѣGkF^yG)> 3ο7`NJ`%.Ӓv~^ǙKd 0(*'7Ù}pQ;{k֚B!BT~ pAc-WTԺɚ5kSN-[dѢEVٳg5ҷ5>˧-+"p4#}#'VOcμw!l/UGbɛYٗGZyqyr})-sIJ!B!l<ʺU]PQ<^,@{&a2]qnݨʼntRuRIKKAl6[]Lxu<0on3Ԍθ)h`!a7ǨjIooy|٫P;-6~߿sgl(8!B!8T3I6SqDOܡo?XfWn8X,2 c߾}tڵyuV"##5瓙ٳgIKK+vi0JZM˱+ؠ9J5c(݇'B!^7T=6kkapsta4nF7ͅ{P>%(n?8g… l۶~]vkiɓdff/` IO!Bᨼ8JU j$>e`d'8բbzBT&wy'\/^Lݺuy뭷ՔܹsiӦ O=d$''h"]‘[kL]GVzE!BAsp/MFoԮ|jkU[F,p=|iYYYyyy1hУ4_zBT& 66֭[c4QUOOOTUhnGeرzBCCcȑO* W*{2ׅGB!y*Ԩz\NGoĘ1xc/ܙi׺u+=85kҠA}j dQ(Ee/9ͭFqƍe^'111םS~>374b3liL7Y+ݻ/~˭w~Xkhk5i&B!82 HռfsrlJ>`%B_R,Ee]}/P^=˨'6ė;"C4ހ9)ݗ|h"{x\5fܛq''kw[$e3N^m]JsW庬%B!(үQU K1CB1YK\D/z(xFV 8rg@OKޕ%_E U B!}*dpF3o/?qO׿ GA]i椦8` Y6d2Je2sN-ym. JP^t.t򵖶h~ho{eV֒-`@2[ǡhNkϙ`I3$[ {ۑsL"S!Ba{R٢|iLMI.-6՗OE%%l=L[ohɥTb`!*X{~wRSSpLb7b˗9q؇W:ʳBn+Cy_E;v@B!A*4s`u 6྽tn W 9b&yTJpp&ٶmm۶%44ԩ<&NX=ٿT\0WRB!A==['[7)-5S/G9^@@ 4lYXT"_5 wqKK.,\ѣGr]?~ͯf[tҖ G-'zPGk7[.B!DPN]t:=auQ7Gx|x_<α΋z)QpG@V0΢;NQwԕL"BQ>fI;IA,.K^yh\s&x>OJV ˋH:t@hhheM&v"00z=ϟ'11VZ:bLb~1KoZ]m/W h)SfEvhivuҬc-Z5w֏B2"]e6onB!*/ Dos@Ewb_'xO uTzʹ)D®] "<<3gpZn͟ŋ8q"ڵ_}?Æ ˟Hd2߲gϞ*SW&(63ZU,gŪǡGWoָ ۮu:Vr?B!D=j{w8fÖD O麟j [ubξXwNXÇz͸x0IY _tۖO94'ǪY!iL3^a{75!;rCW漎bs^'}?QےHVޞO>E(<>u)&]Q?BiiFO'C=c=gΕm4d,]  i&.\Xha@nXti+3jzXҲZ9eqqr 0Q3Ǡ<[˱#ݤklC[]om B!lw:wgSl糿a!cfY}Y7|']3A#+Mp Wɚ2AƱr!/i%5"KdP8v jA T/k2V#iGޟ14;aّ:t~tc#IڹEk沲,i%gni&>'z駟fȑՋuY`0pb݄Ϟ=KVVT#5kMk/PY]t&8Hٵ[%e57/9oBűlٗC՝f>:Z]'꛳m#u/k@p.ڴlI(aK{=;ӑ^o |v/)>|#ZӮUsZĶeMK0ÉGp۠kEL z˙%Cl洺CzCz;mbpAtN!­ *ÇslW#<¬Y;w.?+˖-cܸqfʏa~曦S,{?R.mqE[я݊]5n+f^jU kmk֑ɯ`YQQoy&ҖUUiZWޫgBTv4gC2*2W%۶r(aRgLi[OJZ~35|!h9eSG݋ 0FH:%V`L&ŨT#)X[\~9s&"&&}i&={G]6ׯgǎ0|p:wKĆr_dv0'%MZk/G]"쿽TcPG[WǹtdBk_і.e-%훳̝ٙΎimGqi+ρZڕ-sLWώB[,Nn55`Me)lNHT/>T3f<*ؓ^s^O v<k/`3zh'gF{f OQ:w]w݅јxҷ5>˧-+"p4#}#'VOcμw!J{ȪAUxx+cஉC;W>Z]ڼ]-I5UQ‘kٞ羭[I[ 69s p:{ DڻKXVGgr]u\`wYLTj7WSYᅱ:-JVٞ{DIC~wPKp3gΎc}-ko^L_!|áWn\ !Mʁ15Ӛt} )El}n=>L3J+=!*1NG.]ܹ3&SNZFӡ u6lXݛz'KApLNüqlS3; "gBt p9FUK.\+am\]2d%p,RhJ;)JyO]Khł݀츚l\Z•v LuvogZmj? ں\=JƬw%Moo&dCI+oѲjm5w+[fZ>z Y_&>rJ9rP 4gwkDWIўÀ[ ލ=nC2zh/Ο?޴ ӻ`yޝ:ʿ\T]\)Ym~xc;G? RWPEC\ph9N]ҾJy;Z_%w%gS붊SYW?N_"ז}.T M|8l Bj<zYr]>xxho/{ٖxZpn4AT-N㘗^IP}GSڠGW+?R%bק`Ze.uW,U)v(sܑcUz.=VkɿؽUys5Nzֶ[./֎E˒wۻZnaZYN\%omVyO" vUڭGb˪;輚ďSፁzEFϾ_\Й=\NyoX غt ~;ʕLA@[ {',z7n[1I4aWMf mxs,7&ngp&50na]tjLLluX>黦L#k(`: r2?ٸ hN;rȑ# mܸzѺuk6nh3={VVm~ޯZյ/%_Je9nX~iݮ+~ՒpZ3U.JsZksɑkVkg/ڽѝQI-iq8zqGrd,DQG[4ٻtUćROlmCa>J{te7ޗ՝փZLֽΡ`nˑ{s~8zi)tuf{ַ^( a%_AD}SSK|']tf-_VSf2҄*ZЙ/[St[yP,mƑUAw2 kk2h=.%MhP˹HCk#K{&[A*׵ue|S:09u]7>ϥ 3ci#XGK:;6Ѡ;ŝ玫:t;Dt6`XfPi ںXT7rǭզvnob..?!D^kUU1Nw9z=޽-[P~}郗Tz) \xk -Ҵl#}Qδ,l88Rw%6:v1Bmvuk5g$QLOH02G~r׺뵴3e+{Y'qZrN:u?(EYl%NiSvKsuy*й} .d5Ͽh^QqX,[Əs= صk~JE">E~\`vYkG>>ӒVKܙclę r}iFy͖烽붢*gSU p!?B8ܬ&dl1c15' hdMJ%B֭[իׯK.-lkngΜiӦl߾M2m4:ĤI3f >Tk ]F-tebӸՃSdTt|foJ|C4.&ceBsŃ#c!S_KZчhᎇDgNjhU~̵lzW轢_aԍBX9*Fc78w 7̐eIT4 j֪c Yf qqqtԩPe˖[k.:v숢(c4jA^x7f˝!oK>4ihr=}v/E@EPzt = 8+[-*7K)M 4չmmb?T2:PmB!D9m2q Z%"Ԝ(/K3飯?+yܧ[ikߟ!9:5QXXk׮֭[Ԕǘ1c9s&dȐ!DDDsN:vXyu<0on3Ԍθ)r5~0 ǛcT\M>w{X䗣?pBiZ_*+/T) cnAH~rv;hz]}CxB!mJe?vUڭGb˪~#OMR~߉bɿxz7ت6/܂ NMI#4j*~N+~'fϞ̓>ʕ+3f ?v^3dgg?hDQ CR>| wt<)WxxE+:SGZ^:w=k^}T/frJ鴎XRҮ[~:G[;]Ge1bI۶u=hҦ+Ryd)BaQE\[6m<)*U |>u4  h̒ /gwy'\/^Lݺuy뭷Քb端"%% _SNM;;"NQюiEޞ;f_yvSB!HPT %P^U j HV~JW42+66֭[c4QUOOOt:]1([ ,̫ \EWuj"0 <=Z4x^ EEi=x3ZJ wU>#RSS $**;ҤI򲛇+fV3/srfc v(~?1qL&-9 MX6r4?;y# oԔm&X>|N_rws"st҃[٭)_EGB!W l^t #]^Lؾ|{dAxX9cC_MPsry- >p|.źt,oYYY\rcǎo駟r5ኙ`}>a&I C~lP|l< j ͸yg I.|a;&U|E&B!*=:Z.ߧ!Y~)K2KF-z~gvEdfWk8fSU`|kS9݉OW 1 xzzb0ԨQC?8'>>m۶1e֬YСC/>.}[sc\>(gOp4FFNwYCz;u>D֯OP $2ӭjˋ\^r?tXu/g~tx;χu xTvXxe2C=g|>jpsc_レe`B𬋯jn_̘q9 Mۇ^`ҳw폕s9txS+2{}:͛Ȑ|:8=}vO>&skz<>z_QOPf)-At_=~rEZPN_- ~ .]p]wѨQ#ݲy={6ӧOwz&at %-lМ^|ƺZ敠̈́m݅I%+B!(Up<c/dv|}=8&BH$@hw)"w4+ֳE^P *MQQPPvibLP(=ja×? ą1m`v"HIУ{rFVҍN1e|5[?:Z]pmN}?>tnrZ ZGj&_ߜ@,>vLwU 53H:=<Ӯ֟`/xG[ɚCޫ!϶ؼ(YDfgpN3>$Du5|n2Ω'Ylᯙ`WdM˥MLy}ʸ?0i2r*c/w,C$,5OӦ ZVـMrv ںu+:u֭h=b8ܾ#{ul6w ð pIdd$;v$%%-[|rVX+Xxq߿?seذa8B7nݮ@KC޿T Mf84|3gb*tʠvd5՟kF FuJD5#c8*@.6/Ae@?%glHvE˹ ~grE %a4IpPgb&f෿ILv%OfErVφ9:ߕq{6ˌu²,ʟ{6Ӵ2d/ ;w"**xz<3z !jx>Sd۴i6l`ժUl߾tԩCڵWXz5;wdٲeDEE;i$(vRUI:Uw^bD2Zl݆vbV<%+h;3mXʺ$ڝaw`eydhxQ"`YDyL${*bt]3)"q.ݖHxEw1s}d>yԶvÏ񥗡Jx5lmt9RX3^?fn=y{Y\v0^~e:\z0|V.-y衇cǎ 6x¼W^'\c?<ȕOIr)\1w-'Vyew_Fy6=aq ϼf?g"[>țNd̛۞/0}x3dDuw; 3 lyALF5 W#x9~rq؞ԶYm8v^+fκGYHVv›t&y# h8;O? ڇ`#)kIK>6p(̋kC}]tQ/ܴ4r)ewLG9>`;IoP $Ғ𥝝e_w, ~WC fuۭ:g?IJ-MR̜9;lذ.GH;:-͓b [jmŒ˂ucJBJ^>DeՀhF|7.S:%DDDDDI&m旕!N`'<"{rh{/ۆ3S Δ=l?Ҙ nC N m1k!VizYcUfՂu%(>l{r9!<ʃL  v}@V0HNgHٷ̠={ Ap ;dMkM=3%:/ZN)xmzpy5m88ؿf1!qs {V>dZH5/1;se'؂i}P/y_Jϓź%  ժ7Lť-NlZ9vwcYH8VøƱA͇q߃634!Մ 0΃<3k"c>p mВgE)<,fکgp'ͮ|8¨׼ j9p6,㾍`ڎʋ5'{;ӻq;6Hz}􇾇hؖsKIZiy[g;*0w|w ZK[3dEo1B-˿\@FzzDf1jXUo0 N'>.az0Y`Si"[熻'caw[lt:SIA1uvIٮ"""""""wj2͜nٳgT60_g9- 4ݲui9|@\2U>ˈHOTE Æ ++KqJgko{6 ͎nw`m6gY 0;8ng_iب.[dZ"vdΝܹL +[z!ʝ$1Mfm|Fi'}/fܪcy>C1g{u @fиI3~_gE@@'t "gO , 33+?~OfF:4U} ~o6cL&K~AZVu0 m Ͳm3^\iB= ,2]aHL\N'+-QK@Tip 9W[Jk̟27ŠGYl; 4}6CҖwaoY2xm ^H4"= ֊.^ &>q *rR232"DOb,Ms٪?c@qKk2,o/>0Gou'uӎwR/{P>u}_eN1:Ye.|=wrzbɡ.5B |J&9BA,+`S߫kR0\T ƶfZ$%!")""""""""ŲiШ P|袒ZRDjyyj;hԤ)x""""""""R++|e',plۓ,x E4 0;Nyadd+""""""""R"4=',˓bʻ^*,;ח'#,4_Y~6,ZMI6r/qAmSD*Ιɬ_8 ~dWmpqvSX߽"պg`ՂY,|9e Q\w%tW8*tϮHni[N?X/n$jX|LU]s.?{GM[gڤ9>6!D|W4>ƎScRg\{#vgU/c |g{Nv.?wK#χ+1}& c>1^$c/߽5Yְ'B{r[MX16E=|p-!sĎG?1CB,R忏+Rp>}[8`#׎ߓ'vU0|jN8eJ#k+OϪ?a8m)X`wJV8{~y1/J1ּ:rӘ9%<#wx{B걕5{hڵ!AUZѩN 'XD*ga:#S @˲ /I:" o/o)Q[gDׯO\t H;dz)oBO#ux% 7>:noo5G7M%RYr &.4_PC؁cQD"Riu:y7P .?y<-*_O)_{!qP!<ǐ<=R30rʘ=;)}>DDԖwvam2g\o1bllTHuǮ֊HD>D[i[<&\:Oީj`ބSޖ/fϲǪZҲu y|e/ g]ob[mC3Upᷔ9Yekou0f.u8&OnjM͹_[ QSY및`{*?4ejxԖ?lM,+i"'&;7. dȩ e$>ObB=MF ptf2G-!uh\qA Hvc'1uh9l՟1_ABb+R]+R}박_N05J,WNe 0;s~Fp)_r޿rea)3IadC1#f>v:ːywǗ1ڦ8g'>t" E3a`3l6>Y™gR-*+,<'gk u6<*q:37!i&QsNH?Fib;gȓ0 ӝ2lU,K?) ܽe-_$`.]w#qV|4;x I5pV0;~N00,,CDDDDDDDD{׼VΤy[1=,hLpNZΜ6^ݛrcNt74 iUnJ|!iyCn]JWR0ܣya`۱6N>h}W """""""1д.v@ngIB?fFXcO,ᎿfR4{_OY(ݿ$s L?~<2;ݪ2'WyȎ)ŵw\- vUff\;3ِVrg[\DD 1<ҺG9I.%VΌRÓ˓`U @)W|8Bn`OnDn8Qo.caw&6oDż?ytHSk $ۯ.O"dM R Xcd، +jY&ip!<XF8HS00(M\< = wIkiKHP,jYW6j 2عt>Z%Qs`Mi\_Ă_т]՗}!6oS~~r$mς7rL /qgMfq8vmm܃Y+W;ٿ` o͎"\˜C3[Sl {R!4' ڄjYx\>~}Gb] RN++tpv9DZHe/uj\}t՛ZhcͫcyqS7n~Fd3sDN{޵Z_c$aΖ3KҦb#iOӱL q{:EAnܔC1""R nsv/ԔE$h0zQ DDDDNy4r99,ӏFi߲뺖4> 즁@]rڍ<!@hԇz0heCjAlJ} o)]aq#v]cIDAT/ٜӺ‰ֵ#5*Gdf@bE:݈pBpp0-ZE֊Ie}^yC~z$w⎠38I4h;v3 +N1"y  vD[Óȳ\;#=t2ή=lBRclЇ2epVti]zpY R@ Hd%qpxLz1AdmOR#pLZ {gmY&w,}#+7K9 l̰ 9}jVy牗nS""Rm>9_27%)CvADEҶu?J ͺ9dYΌ҃6lYfYqvِcb`tQiS Ciعng'M߰|[.6֊Mr4%*Q3]YX==;C;ɳf~o^YѰS7Noq0i#W I~ůMc *!m?n!fVl0c"^9ḱ{Ǖ2kӨ:NH5jFFdee)R"Rzq H%>Qc´ Fz30^sF?ĞxdE4gUWyڋJh v*38tYWưਅ=M;aVS.ƍ\F۱o3 ıxff35}oJHf,?7``GvИ"RԔ P`DDDp?2xDձAelfƍ >[F岙ҷDD8T˗-=\?eYEffHcqOHS:&D8%Sp]kH [&_0};I|<> V('""Չڲ1+}_2uoO]9bW>uIH52m?gPl 2cJ1(")EDDDDDD"""""""""L @?SPDDDDDDDDď)("""""""" M"""""""""ߔcJ1%EDDDDDDDD1EDDDDDDDD"""""""""~L @?"""""""""L @?SPDDDDDDDDi @SPDDDDDDDDď)(""""""""4?SPDDDDDDDDď)(""""""""ǔcJ/M"""""""""ϔcJ1%EDDDDDDDDgJ1%EDDDDDDDD"""""""""Kc3%EDDDDDDDD"""""""""~L @1EDDDDDDDD#/K.UDDDDDDDDDD/I@ !?."""""""""Kc3%EDDDDDDDDXf22y'KqSE~"V)"-vۼ\*9e=Jb_0<+_dk?~wۏW7R;DDDDDDʁO͜ŭք/PDݗzxoD,s娸x|N$29o9h_t"'RG7ѓK m@-58я}'\EΙ[m%ŤsqԤq|#2iѲe9]. ߿1~ƾLNgݎdapnڊ0)=nY58c-M{ N!~|s=I@M˯_‹_y)S95P+ HqVpA\v|y7߽:j7~.u5OW;B:3ڡfC<`/ٛwz[/ì[<~a1 Gx3 rDegkcwa9*'`&~K4|9J-͒ϟ-:(}5SUDDDDD g3rP+jYh<;/Bgg%4g.G;GbѺ5K-@Y4wƎA`'yh ]"̙^NkZֽQ#Znb5ϝC 8n13LffKXhԠqǑuVָ?MS8ނ&Y̱?^#w>BBI˅+ v4lEoLaMip wI &>y}s6rᮆA]f.̼/0E`A0c ؘ}=qO2qJVJ/GbrGY}C8#xղ㶎m0=/388 'hiޓ< vno?3ǟf98\nԄwr饗ūNb, k׻$%-jb7g~2au[%|%n,J"""""Rg75'5x&Cw-qZ/};3д` S4o\qS_֊Aְ$ZxKϨ4~Uw狭itj98}|v[Cfa~>[̮9_) šUߐL^0RflE5L-lk~V&g |2fi0IEĘ(aTF^35<hlxw>\6Is}bٷ킀f};",;c߱cc1΄<ɥ\E\ۘK)GIGYРA'q<Mes 2Z6f/st ' <0]ҷ壅kto79 ѹCUt"kvf>{sG޲^<[kh%zŖDeg,t/уqEp,2 ot/e}|Tn}qEm?jH-R8*t C[;O毞0\7 /5R8(|9gC%̨ɾ0{0i`2 yOp5ѻW@9g[~j"1K-[һozۮ{%wpu *-&3?'|~^ڐl $.Ȫj ^(d$aAp¦Tg=Q :O6q=CL>kդ[˨b˔vfդkDY@]ڵ g?IFP!f<R\^%ܛ޿Ytɻ7('v/+̙t[d"DI2_=^?ռȉ)/<ذzs6`SK>^S?#7%'W¢]G>PA`0 `C1q4|ݿA>-w$ud&\IZ68TZټ"-AժCtǗ6&Κş=?5Ѹi3B+i~ÁAli=fI6脈UCyD  ˲Nl;{ *Qc- ˎSf.8D)u{͗tO,}R뫈,o*DF1pCppo[2*h=Apײ+ *# %*2thO|ąB#cfw|El,!fPכ䟷1)txd !:oOzIOٱ)W~t Gl oۧsGSJ?9X+R@wa܆Y1GDT^)W@%j>J~a`OM.__R~U_EDDDDB[LAe&aqQb# qV|e#:HR.Љ zbyv}[G9w3Bٜn3}ADF/ŋ5.0N@hOhiARHZS;m16R~|~|~-&ٳ^~ҥ[]_-N&aX{W cy8jbϤyŮoeZaFQ.Z5vUak֗İh_:8SdOZc {p- eȎ'EY:W$O-$z!vG}/MYx/+>kՙ!]yaK鼄q{W.bH607h])u{Y5|>P#(j"okbtʏ1p& < WJbèI㬗2_Ia hGkټ< a4q)!>w]}b :4 q?"0A4ߗ_._4<!%`aU':J8e=?g9뎜_8`©8BjԖ6prrfKἃ-(kޟʇI&ΨV.o쾆vת݋4>ׇRA׬/ׅDe@\Y&/ۜCܭd _c^E^f:^8vLޔś5rO,U߂t5D^R챇n,ϛiDf`L2 vpK%ׄx]KNfWO}rS\ ϵ K4p(R2w| ^ NED˪{Of%+;wOߪnZraMNb&*"][=Eb;,a_doz6(=]{=3EB)"""""~n3 HYG>oJlsa$z71DDDDDy.Tqt- UDXx"*""""""""Ry t4p GI?Xl"!""""""""/4ΛIIENDB`pycorrfit-1.1.7/doc/Images/PyCorrFit_logo_dark.pdf0000664000372000037200000002265613554642611022774 0ustar travistravis00000000000000%PDF-1.5 % 3 0 obj << /Length 4 0 R /Filter /FlateDecode >> stream xUMk1 WDo(9,B{ 9)P!kk&M)eO'o't w@=}w> 9ʃN R %F!^J2k#6BjfFWRuܣWpSیޓw*Jw]8N^ZO~3[U<Ы9Lo`N!N JrIS/0 5`ai 9j;\rP9gD=w/%+ %DSY3A^ c`!hm9-aofi/Hwm?Yߋ];.f-]-J)=)$C’6&1 %I-PfІ-XteWnOQ;!(f(&*^څX5/T3@%{weP~Ej1 mP<'"p u4te,~; ߱]tWpB?GWG> /a1 << /CA 0.445104 /ca 0.445104 >> /a2 << /CA 1 /ca 1 >> /a3 << /CA 0.804734 /ca 0.804734 >> >> /Font << /f-0-0 5 0 R >> >> endobj 6 0 obj << /Type /Page /Parent 1 0 R /MediaBox [ 0 0 629.979187 184.27446 ] /Contents 3 0 R /Group << /Type /Group /S /Transparency /CS /DeviceRGB >> /Resources 2 0 R >> endobj 7 0 obj << /Length 8 0 R /Filter /FlateDecode /Length1 9652 >> stream x: xՕ;yfɼ< 3!!dL$"AB&@paM D, >W |k"2IMl-Z݅Xjn g@:w9{sϽ7 >`[ @Cn-|>%Tvx~nk{n?eѶ#+iIgcys;7n=Xh򓼼mռ 'mlK뺭#N| L8[4 *U#$0r0*y1Ҩ8%N;_珇o2~oC$<0oಏU% ( 7 Ep!\[m+Ѡ\0"}5nv}E ԅG]{0BecFMbQ((Q;̼<5+h2r!sAE \ШՓ $U\D<&sw8FHh5i?k1(nڕQCnTgF5D:C$ +5B11^8|kaj iJMLR[U)Lb*b0yi*E?I$wwsL~9M3%L2Zi&p-vGb*]%o^}'dPJ^F0;A^A*L Q%˟:2/I:M[n<`y 0jHL\j6ZBԣz)5Ox x<; )%SȔbJLZZ4T(pY9d/{nZ.Kia.n,(( r@ĀU& f R0= *ZrJQVN  3] q7)Ǎ KqVqAT8 ?8Rpnt1X$(,#dءTm{L+ZsWg+G謈ZD*y4Ǭu٬&{#`ǣfhFl27LaLPQV2jC ώ' ?#ݿȡc t1%/8}g8g DwefO(gQexly!z1:B_jG5qnʝ4)/7eZ3tZZ X3ܼ**_PeUUnAuAEU"r b'D ?$-Sw-WC?ob1ۏ6)m-L9[6a7uYӦ#5\o;(3:-LX(u=.S-pY*j>,+4N_WoZ`ldTT,ʮpȾo}I#upЊwBM-N&[_pw?A& - 8KQ"j&wtzc+34 E Ö|<++^#䱗 -I+l4SVbu,ƃQG3u*ku%VXӋ̓!}.z.Ʌtetk u=1 +'Dv/j}O{Nؒy?|]wZkR J F&d0V3cy&jT檪eߩ>wPH_lc/+h"{~2\s|ts%cm;.Vsiugx0>{/t@K$Gkϲ+*K 9B~!ۻrsק[FݏsrMv e>doŜ?\ku DP#r.% 1HJ&v|(͢>ņSΤ=XqIc%iH}`z\K5p 6+# F4C_RK80:$nxPFAa>yvl×O>vx {,X94UJ?c ixH@}ҀW4Uڈa$Y Ω o *8fX߁'a~ oBL"}=rJǓǥvV 6`<+y%n;a7S}>$j7̂Ў|3DML%m+ka؋:}qx>D2$A$w!y70 l6 wt |~*@=ވ\g=%P jQkeO%C7F0'T#c[ O;;`'ƀ'1}5oؿ(Y^X a:ZɋvJhѿw)tZ[q(ggZh49rzEyYԒ)E`Iybzr.Î d4dJ%D~gr>oPE_E@>iKsyƋO_'Da!wU-X-+B[FzC&m[lf1B;} Se Z%hR]<.'oY uկhwLSݑo\,6WOqɄRj]:637M稛ɵ+ BsFBïRv5#*5z!1>7cZol4"]dM43UpB>m\_;#PkH}(#>~Ţ#ґwag Ifʦ|~ŭEF-1[鋳5ҩQA5D#|StJzW'a0#@x'ec_xP}G&o&]J]Cx*݆N F(s/tZZA2S&Ù}2ҔEj1B8{^-g/Cs9Gs9Ga#@ؿr8agn >ܼlUnH.60T5hL"#<` El1l!'&VyH`ӛ 2F;6< orҹXtkta8PsC%qngdN+^,IXZL5[D(D(jV5Pv( k TG s̓7U # (A"4!Oۅp^xDޠx V4y y =GB裇fCl1V^!!hF ݓRMKpjncQMiSSKR"Pt8TF*X d),wAB`DDڋP|M/; 94 LBI=-x}n麀9Wg[8" ,0$6<0Dfj `%݅܅z=M\5nC 6i2L0010nLaڅi'PhVZ[CG+T/6LqjfXi6]5F|Ȭ=6GelVϭbWWoZoX/^!Q{PaP'_O +~rP_c'rOe<[ƥ28,҃r3^' y󏨑lKnN*|[_ 8)=RZY}uoQs"ˁˁ:98Iyd9YA,YfDue22,%FFu_ux*s6;fy`VˆNGZr:ȏ䴭c;bop[ʫu~IhGмz.6<2`a_鬍wVǚsTsXXXssdGT^ie:LuZtm3vU\w>"E=*)UxU&HTGȏUFd__ߞtolZճS۳i37@&45hcN9FئO=o:ی=kzpB $LmzVb7'= endstream endobj 8 0 obj 6610 endobj 9 0 obj << /Length 10 0 R /Filter /FlateDecode >> stream x]j >$n!Cд 1}PA|83[{+: gx0 Z)1:Z,k18I7/k3U8kb޽Oc+c\m!s*IXތ,jW+%{yTg9k.DD剨LT)3) х{+)TV6{b>vfZݚ\igs` endstream endobj 10 0 obj 264 endobj 11 0 obj << /Type /FontDescriptor /FontName /MEWPKO+CourierNewPSMT /FontFamily (Courier New) /Flags 4 /FontBBox [ -21 -679 637 1020 ] /ItalicAngle 0 /Ascent 832 /Descent -300 /CapHeight 1020 /StemV 80 /StemH 80 /FontFile2 7 0 R >> endobj 12 0 obj << /Type /Font /Subtype /CIDFontType2 /BaseFont /MEWPKO+CourierNewPSMT /CIDSystemInfo << /Registry (Adobe) /Ordering (Identity) /Supplement 0 >> /FontDescriptor 11 0 R /W [0 [ 600 600 600 600 600 600 600 600 600 ]] >> endobj 5 0 obj << /Type /Font /Subtype /Type0 /BaseFont /MEWPKO+CourierNewPSMT /Encoding /Identity-H /DescendantFonts [ 12 0 R] /ToUnicode 9 0 R >> endobj 1 0 obj << /Type /Pages /Kids [ 6 0 R ] /Count 1 >> endobj 13 0 obj << /Creator (cairo 1.10.2 (http://cairographics.org)) /Producer (cairo 1.10.2 (http://cairographics.org)) >> endobj 14 0 obj << /Type /Catalog /Pages 1 0 R >> endobj xref 0 15 0000000000 65535 f 0000009014 00000 n 0000000773 00000 n 0000000015 00000 n 0000000751 00000 n 0000008851 00000 n 0000001008 00000 n 0000001221 00000 n 0000007925 00000 n 0000007948 00000 n 0000008290 00000 n 0000008313 00000 n 0000008584 00000 n 0000009079 00000 n 0000009207 00000 n trailer << /Size 15 /Root 14 0 R /Info 13 0 R >> startxref 9260 %%EOF pycorrfit-1.1.7/doc/Images/PyCorrFit_logo_dark.svg0000664000372000037200000001256413554642611023017 0ustar travistravis00000000000000 image/svg+xml PyCorrFit pycorrfit-1.1.7/doc/Images/PyCorrFit_icon.svg0000664000372000037200000001131713554642611022001 0ustar travistravis00000000000000 image/svg+xml pycorrfit-1.1.7/doc/PyCorrFit_doc.tex0000775000372000037200000001254313554642611020417 0ustar travistravis00000000000000\documentclass[a4paper,12pt]{scrartcl} % apt-get install texlive-science %Wir arbeiten mit PDF-Latex. %Bei Texmaker unter Werkzeuge > PDFLaTeX (F6) \usepackage[utf8x]{inputenc} %\usepackage{tipa} % apt-get install tipa %Für deutsche Schriften: %\usepackage[ngerman]{babel} %\usepackage{sistyle} %\SIstyle{German} %\usepackage{icomma} % Komma als Dezimaltrenner (im Mathe Modus) % Standardmäßig läßt LaTeX im Mathe Modus immer etwas % Platz nach einem Komma, für 3,45 etc. ist das falsch. % Mit icomma gilt: % Wenn auf das Komma ein Leerzeichen folgt, soll auch % eins kommen, wenn nicht, schreibe es als Operator: % z.B. $f(x, y) = 3,45$ %Für englische Schriften: \usepackage[english]{babel} \usepackage{siunitx} \usepackage[top = 2cm, left = 2.5cm, right = 2cm, bottom = 2.5cm]{geometry} \usepackage{amsmath} \usepackage{amssymb} \usepackage{array} \usepackage{cite} %Für Zitate und Quellen \usepackage{url} \urlstyle{tt} %\usepackage{longtable} % mehrseitige Tabellen %\usepackage{multirow} %Zusammenfassen von Spalten/Zeilen \usepackage{subfig} % Vereinen von Bildern in gesamte Figure %Schönere Unterschriften für Bilder und Tabellen %Setze captions mit Kommas und Beschriftungen \DeclareCaptionLabelFormat{mycaption}{#1 #2} \DeclareCaptionLabelSeparator{comma}{, } \captionsetup{font=small,labelfont=bf,labelformat=mycaption,labelsep=comma} \setcapindent{0pt} % Zeileneinzug ab zweiter Zeile \newcommand{\mycaption}[2]{\caption[~#1]{\textbf{#1:} #2}} \usepackage{tabularx} \usepackage{textcomp} % Sonderzeichen \usepackage{wrapfig} \usepackage[svgnames]{xcolor} %Farben wie DarkBlue \usepackage{fancyvrb} %% %% %% Definitionen für schöne Links innerhalb des Dokuments %%% graphicx: support for graphics \usepackage[pdftex]{graphicx} \pdfcompresslevel=9 %%% hyperref (hyperlinks in PDF): for more options or more detailed %%% explanations, see the documentation of the hyperref-package \usepackage[% %%% general options pdftex=true, %% sets up hyperref for use with the pdftex program %plainpages=false, %% set it to false, if pdflatex complains: ``destination with same identifier already exists'' % pdfstartview={XYZ 0 0 1.0} , %% Startet das PDF mit 100% Zoom, also Originalgroesse %%% extension options backref, %% adds a backlink text to the end of each item in the bibliography pagebackref=false, %% if true, creates backward references as a list of page numbers in the bibliography colorlinks=true, %% turn on colored links (true is better for on-screen reading, false is better for printout versions) linkcolor=DarkBlue, %% Aendern der Linkfarbe urlcolor=DarkBlue, %% Aendern der Url-Linkfarbe und andere serioese Farben anchorcolor = black, citecolor = DarkGreen, filecolor = black, urlcolor = DarkBlue, breaklinks=false, % %%% PDF-specific display options bookmarks=true, %% if true, generate PDF bookmarks (requires two passes of pdflatex) bookmarksopen=true, %% if true, show all PDF bookmarks expanded bookmarksnumbered=false, %% if true, add the section numbers to the bookmarks %pdfstartpage={1}, %% determines, on which page the PDF file is opened %pdfpagemode=None %% None, UseOutlines (=show bookmarks), UseThumbs (show thumbnails), FullScreen ]{hyperref} %%% provide all graphics (also) in this format, so you don't have %%% to add the file extensions to the \includegraphics-command %%% and/or you don't have to distinguish between generating %%% dvi/ps (through latex) and pdf (through pdflatex) % \DeclareGraphicsExtensions{.pdf} %% %% %\newcommand{\kommentar}[1]{\marginpar{\textcolor{red}{#1}}} % Kommentarkommando %\newcommand{\fehler}[3]{\SI{(#1}{}\SI{\pm #2)}{#3}} % Fehlerkommando %Ort für mögliche Bilddateien (Unterordner) \graphicspath{{bilder/}{messwerte/}{auswertung/}} %Neue Befehle \newcommand{\hyref}[2]{\hyperref[#2]{#1~\ref{#2}}} %Schönerer link statt "link zu Bild \ref{im:bild}" -> "\hyref{link zu Bild}{im:bild}" \newcommand{\mytilde}{\raisebox{-0.9ex}{\~{ }}} \setcounter{page}{1} % Tell latex how to break the program names \hyphenation{Py-Corr-Fit Py-Scan-FCS} % For non-italic greek letters \usepackage{upgreek} \usepackage{doi} \begin{document} \noindent \begin{tabularx}{\linewidth}{Xr} \textbf{PyCorrFit \newline FCS data evaluation} \newline \textit{Software Guide} & \raisebox{-2em}{\includegraphics[angle=0,width=40mm]{Images/PyCorrFit_logo_dark.pdf}} \\ \\ Thomas Weidemann & \\ Max Planck Institute of Biochemistry, Martinsried, Germany & \\ \\ Paul Müller & \\ Biotechnology Center of the TU Dresden, Germany & \\ \\ \today & \\ \end{tabularx} \vspace{2em} \tableofcontents \newpage \graphicspath{{Images/}} \include{PyCorrFit_doc_content} \section*{Acknowledgements} \addcontentsline{toc}{section}{Acknowledgements} I thank André Scholich (TU Dresden, Germany) for initial proof reading of the manuscript. %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% % Literaturverzeichnis \pagestyle{plain} % nur Nummerierung in der Fuzeile \bibliographystyle{plainurl} % Zitierstil: alphadin = [Nam88] apt-get install bibtex-extras \bibliography{Bibliography} % BibTeX-Datei name.bib ohne .bib hier einfgen %\nocite{*} % Listet alle Eintrge der Datei auf, wenn aktiv \end{document} pycorrfit-1.1.7/doc/Bibliography.bib0000775000372000037200000036017313554642611020265 0ustar travistravis00000000000000% This file was created with JabRef 2.10b2. % Encoding: UTF-8 @Article{Aragon1976, Title = {Fluorescence correlation spectroscopy as a probe of molecular dynamics}, Author = {S. R. Aragon and R. Pecora}, Journal = {The Journal of Chemical Physics}, Year = {1976}, Number = {4}, Pages = {1791-1803}, Volume = {64}, Doi = {10.1063/1.432357}, Owner = {paul}, Publisher = {AIP}, Timestamp = {2012.11.02} } @Article{Ashkin1970, Title = {Acceleration and Trapping of Particles by Radiation Pressure}, Author = {Ashkin, A.}, Journal = {Physical Review Letters}, Year = {1970}, Month = {Jan}, Pages = {156--159}, Volume = {24}, Doi = {10.1103/PhysRevLett.24.156}, Issue = {4}, Owner = {paul}, Publisher = {American Physical Society}, Timestamp = {2012.11.13} } @Article{Axelrod1984, Title = {Total Internal Reflection Fluorescence}, Author = {Axelrod, D and Burghardt, T P and Thompson, N L}, Journal = {Annual Review of Biophysics and Biomolecular Structure}, Year = {1984}, Month = jun, Number = {1}, Pages = {247--268}, Volume = {13}, Booktitle = {Annual Review of Biophysics and Bioengineering}, Comment = {doi: 10.1146/annurev.bb.13.060184.001335}, Doi = {10.1146/annurev.bb.13.060184.001335}, ISSN = {0084-6589}, Owner = {paul}, Publisher = {Annual Reviews}, Timestamp = {2012.02.14} } @Article{Bacia2006, Title = {Fluorescence cross-correlation spectroscopy in living cells}, Author = {Bacia, K. and Kim, S. A. and Schwille, P.}, Journal = {Nat Methods}, Year = {2006}, Number = {2}, Pages = {83--9}, Volume = {3}, Abstract = {Cell biologists strive to characterize molecular interactions directly in the intracellular environment. The intrinsic resolution of optical microscopy, however, allows visualization of only coarse subcellular localization. By extracting information from molecular dynamics, fluorescence cross-correlation spectroscopy (FCCS) grants access to processes on a molecular scale, such as diffusion, binding, enzymatic reactions and codiffusion, and has become a valuable tool for studies in living cells. Here we review basic principles of FCCS and focus on seminal applications, including examples of intracellular signaling and trafficking. We consider FCCS in the context of fluorescence resonance energy transfer and multicolor imaging techniques and discuss application strategies and recent technical advances.}, Doi = {10.1038/nmeth822}, Keywords = {Algorithms Animals Biological Transport Diffusion Endocytosis/physiology Enzymes/metabolism Fluorescence Resonance Energy Transfer/methods Humans Laser Scanning Cytometry/instrumentation/*methods Oligonucleotides/metabolism Protein Binding Protein Transport Proteins/metabolism Signal Transduction/physiology Spectrometry, Fluorescence/instrumentation/methods}, Owner = {paul}, Timestamp = {2014.01.15} } @Article{Bacia2012, Title = {Correcting for spectral cross-talk in dual-color fluorescence cross-correlation spectroscopy}, Author = {Bacia, K. and Petr\'{a}\v{s}ek, Zden\v{e}k and Schwille, P.}, Journal = {Chemphyschem}, Year = {2012}, Number = {5}, Pages = {1221--31}, Volume = {13}, Abstract = {Dual-color fluorescence cross-correlation spectroscopy (dcFCCS) allows one to quantitatively assess the interactions of mobile molecules labeled with distinct fluorophores. The technique is widely applied to both reconstituted and live-cell biological systems. A major drawback of dcFCCS is the risk of an artifactual false-positive or overestimated cross-correlation amplitude arising from spectral cross-talk. Cross-talk can be reduced or prevented by fast alternating excitation, but the technology is not easily implemented in standard commercial setups. An experimental strategy is devised that does not require specialized hardware and software for recognizing and correcting for cross-talk in standard dcFCCS. The dependence of the cross-talk on particle concentrations and brightnesses is quantitatively confirmed. Moreover, it is straightforward to quantitatively correct for cross-talk using quickly accessible parameters, that is, the measured (apparent) fluorescence count rates and correlation amplitudes. Only the bleed-through ratio needs to be determined in a calibration measurement. Finally, the limitations of cross-talk correction and its influence on experimental error are explored.}, Doi = {10.1002/cphc.201100801}, Owner = {paul}, Timestamp = {2014.01.15} } @Article{Bag2012, Title = {Calibration and Limits of Camera-Based Fluorescence Correlation Spectroscopy: A Supported Lipid Bilayer Study}, Author = {Bag, Nirmalya and Sankaran, Jagadish and Paul, Alexandra and Kraut, Rachel S. and Wohland, Thorsten}, Journal = {ChemPhysChem}, Year = {2012}, Number = {11}, Pages = {2784--2794}, Volume = {13}, Doi = {10.1002/cphc.201200032}, ISSN = {1439-7641}, Keywords = {fluorescence spectroscopy, membrane, multiplexing, point spread function, total internal reflection}, Owner = {paul}, Publisher = {WILEY-VCH Verlag}, Timestamp = {2012.09.20} } @Article{Bestvater2010, Title = {EMCCD-based spectrally resolved fluorescence correlation spectroscopy}, Author = {Felix Bestvater and Zahir Seghiri and Moon Sik Kang and Nadine Gr\"{o}ner and Ji Young Lee and Kang-Bin Im and Malte Wachsmuth}, Journal = {Optics Express}, Year = {2010}, Month = {Nov}, Number = {23}, Pages = {23818--23828}, Volume = {18}, Abstract = {We present an implementation of fluorescence correlation spectroscopy with spectrally resolved detection based on a combined commercial confocal laser scanning/fluorescence correlation spectroscopy microscope. We have replaced the conventional detection scheme by a prism-based spectrometer and an electron-multiplying charge-coupled device camera used to record the photons. This allows us to read out more than 80,000 full spectra per second with a signal-to-noise ratio and a quantum efficiency high enough to allow single photon counting. We can identify up to four spectrally different quantum dots in vitro and demonstrate that spectrally resolved detection can be used to characterize photophysical properties of fluorophores by measuring the spectral dependence of quantum dot fluorescence emission intermittence. Moreover, we can confirm intracellular cross-correlation results as acquired with a conventional setup and show that spectral flexibility can help to optimize the choice of the detection windows.}, Doi = {10.1364/OE.18.023818}, Keywords = {CCD, charge-coupled device; Confocal microscopy; Spectroscopy, fluorescence and luminescence}, Owner = {paul}, Publisher = {OSA}, Timestamp = {2012.11.07} } @Article{Blom2009, Title = {Triplet-State Investigations of Fluorescent Dyes at Dielectric Interfaces Using Total Internal Reflection Fluorescence Correlation Spectroscopy}, Author = {Blom, Hans and Chmyrov, Andriy and Hassler, Kai and Davis, Lloyd M. and Widengren, Jerker}, Journal = {The Journal of Physical Chemistry A}, Year = {2009}, Number = {19}, Pages = {5554-5566}, Volume = {113}, Doi = {10.1021/jp8110088}, Owner = {paul}, Timestamp = {2012.11.02} } @Article{Blom2002, Title = {Parallel Fluorescence Detection of Single Biomolecules in Microarrays by a Diffractive-Optical-Designed 2 x 2 Fan-Out Element}, Author = {Hans Blom and Mathias Johansson and Anna-Sara Hedman and Liselotte Lundberg and Anders Hanning and Sverker H{\aa}rd and Rudolf Rigler}, Journal = {Applied Optics}, Year = {2002}, Month = {Jun}, Number = {16}, Pages = {3336--3342}, Volume = {41}, Abstract = {We have developed a multifocal diffractive-optical fluorescence correlation spectroscopy system for parallel excitation and detection of single tetramethylrhodamine biomolecules in microarrays. Multifocal excitation was made possible through the use of a 2 {\texttimes} 2 fan-out diffractive-optical element with uniform intensity in all foci. Characterization of the 2 {\texttimes} 2 fan-out diffractive-optical element shows formation of almost perfect Gaussian foci of submicrometer lateral diameter, as analyzed by thermal motion of tetramethylrhodamine dye molecules in solution. Results of parallel excitation and detection in a high-density microarray of circular wells show single-biomolecule sensitivity in all four foci simultaneously.}, Doi = {10.1364/AO.41.003336}, Keywords = {Diffractive optics; Confocal microscopy; Fluorescence microscopy; Fluorescence, laser-induced}, Owner = {paul}, Publisher = {OSA}, Timestamp = {2012.11.07} } @Article{Brinkmeier1999, Title = {Two-beam cross-correlation:  a method to characterize transport phenomena in micrometer-sized structures.}, Author = {M. Brinkmeier and K. Dörre and J. Stephan and M. Eigen}, Journal = {Analytical Chemistry}, Year = {1999}, Month = {Feb}, Number = {3}, Pages = {609--616}, Volume = {71}, Abstract = {To determine flow properties, namely, the velocity and angle of the flow in microstructured channels, an experimental realization based on fluorescence correlation spectroscopy is described. For this purpose, two micrometer-sized spatially separated volume elements have been created. The cross-correlation signal from these has been recorded and evaluated mathematically. In addition to previous results, two-beam cross-correlation allows for fast and easy determination of even small (down to 200 μm/s) flow velocities, as well as simultaneous measurement of diffusion properties of single dye molecules within a rather short detection time of 5-100 s and an error rate of less than 20\%. The spatial flow resolution is around 1-2 μm, limited by the diameter of the volume element. Furthermore, vectorial flow data can be obtained and evaluated. A discussion of the theoretical background and an experimental verification of the theoretical results is performed. The feasibility of fast and easy data processing is shown if the flow time is the only desired information. Possible applications of this precise and simple method are the determination of transportation effects within artificial microstructures for CE and HPLC, fast chemical kinetics, and high-throughput screening.}, Doi = {10.1021/ac980820i}, Institution = {Max-Planck-Institut für biophysikalische Chemie, Am Fassberg, D-37077 Göttingen, Germany.}, Language = {eng}, Medline-pst = {ppublish}, Owner = {paul}, Pmid = {21662718}, Timestamp = {2012.11.07} } @Article{Brutzer2012, Title = {Scanning Evanescent Fields Using a pointlike Light Source and a Nanomechanical DNA Gear}, Author = {Brutzer, Hergen and Schwarz, Friedrich W. and Seidel, Ralf}, Journal = {Nano Letters}, Year = {2012}, Number = {1}, Pages = {473-478}, Volume = {12}, Doi = {10.1021/nl203876w}, Owner = {paul}, Timestamp = {2012.08.09} } @Article{Buchholz2012, Title = {FPGA implementation of a 32x32 autocorrelator array for analysis of fast image series}, Author = {Jan Buchholz and Jan Wolfgang Krieger and G\'{a}bor Mocs\'{a}r and Bal\'{a}zs Kreith and Edoardo Charbon and Gy\"{o}rgy V\'{a}mosi and Udo Kebschull and J\"{o}rg Langowski}, Journal = {Optics Express}, Year = {2012}, Month = {Jul}, Number = {16}, Pages = {17767--17782}, Volume = {20}, Abstract = {With the evolving technology in CMOS integration, new classes of 2D-imaging detectors have recently become available. In particular, single photon avalanche diode (SPAD) arrays allow detection of single photons at high acquisition rates (\&\#x02265; 100kfps), which is about two orders of magnitude higher than with currently available cameras. Here we demonstrate the use of a SPAD array for imaging fluorescence correlation spectroscopy (imFCS), a tool to create 2D maps of the dynamics of fluorescent molecules inside living cells. Time-dependent fluorescence fluctuations, due to fluorophores entering and leaving the observed pixels, are evaluated by means of autocorrelation analysis. The multi-\&\#x003C4; correlation algorithm is an appropriate choice, as it does not rely on the full data set to be held in memory. Thus, this algorithm can be efficiently implemented in custom logic. We describe a new implementation for massively parallel multi-\&\#x003C4; correlation hardware. Our current implementation can calculate 1024 correlation functions at a resolution of 10\&\#x003BC;s in real-time and therefore correlate real-time image streams from high speed single photon cameras with thousands of pixels.}, Doi = {10.1364/OE.20.017767}, Keywords = {Detectors; Arrays; Cameras; Correlators ; Fluorescence microscopy; Three-dimensional microscopy; Spectroscopy, fluorescence and luminescence; Avalanche photodiodes (APDs)}, Owner = {paul}, Publisher = {OSA}, Timestamp = {2012.10.24} } @PhdThesis{Burkhardt2010, Title = {Electron multiplying CCD – based detection in Fluorescence Correlation Spectroscopy and measurements in living zebrafish embryos}, Author = {Burkhardt, Markus}, School = {Biophysics, BIOTEC, Technische Universität Dresden, Tatzberg 47–51, 01307 Dresden, Germany}, Year = {2010}, Note = {\url{http://nbn-resolving.de/urn:nbn:de:bsz:14-qucosa-61021}}, Owner = {paul}, Timestamp = {2012.10.24} } @Article{Burkhardt:06, Title = {Electron multiplying CCD based detection for spatially resolved fluorescence correlation spectroscopy}, Author = {Markus Burkhardt and Petra Schwille}, Journal = {Optics Express}, Year = {2006}, Month = {Jun}, Number = {12}, Pages = {5013--5020}, Volume = {14}, Abstract = {Fluorescence correlation spectroscopy (FCS) is carried out with an electron multiplying CCD (EMCCD). This new strategy is compared to standard detection by an avalanche photo diode showing good agreement with respect to the resulting autocorrelation curves. Applying different readout modes, a time resolution of 20 {\textmu}s can be achieved, which is sufficient to resolve the diffusion of free dye in solution. The advantages of implementing EMCCD cameras in wide-field ultra low light imaging, as well as in multi-spot confocal laser scanning microscopy, can consequently also be exploited for spatially resolved FCS. First proof-of-principle FCS measurements with two excitation volumes demonstrate the advantage of the flexible CCD area detection.}, Doi = {10.1364/OE.14.005013}, Keywords = {CCD, charge-coupled device; Medical optics and biotechnology; Fluorescence, laser-induced}, Publisher = {OSA} } @Article{Chiantia2006, Title = {Combined AFM and Two-Focus SFCS Study of Raft-Exhibiting Model Membranes}, Author = {Chiantia , Salvatore and Ries , Jonas and Kahya, Nicoletta and Schwille, Petra}, Journal = {ChemPhysChem}, Year = {2006}, Number = {11}, Pages = {2409--2418}, Volume = {7}, Doi = {10.1002/cphc.200600464}, ISSN = {1439-7641}, Keywords = {fluorescent probes, force measurements, membranes, sphingolipids}, Owner = {paul}, Publisher = {WILEY-VCH Verlag}, Timestamp = {2012.10.24} } @Article{Dertinger2007, Title = {Two-Focus Fluorescence Correlation Spectroscopy: A New Tool for Accurate and Absolute Diffusion Measurements}, Author = {Dertinger, Thomas and Pacheco, Victor and von der Hocht, Iris and Hartmann, Rudolf and Gregor, Ingo and Enderlein, Jörg}, Journal = {ChemPhysChem}, Year = {2007}, Number = {3}, Pages = {433--443}, Volume = {8}, Doi = {10.1002/cphc.200600638}, ISSN = {1439-7641}, Keywords = {diffusion coefficients, fluorescence spectroscopy, fluorescent dyes, time-resolved spectroscopy}, Owner = {paul}, Publisher = {WILEY-VCH Verlag}, Timestamp = {2012.02.14} } @Article{Einstein1905, Title = {Über die von der molekularkinetischen Theorie der Wärme geforderte Bewegung von in ruhenden Flüssigkeiten suspendierten Teilchen}, Author = {Einstein, A.}, Journal = {Annalen der Physik}, Year = {1905}, Number = {8}, Pages = {549--560}, Volume = {322}, Doi = {10.1002/andp.19053220806}, ISSN = {1521-3889}, Owner = {paul}, Publisher = {WILEY-VCH Verlag}, Timestamp = {2012.11.02} } @Article{Elson1974, Title = {Fluorescence correlation spectroscopy. I. Conceptual basis and theory}, Author = {Elson, Elliot L. and Magde, Douglas}, Journal = {Biopolymers}, Year = {1974}, Number = {1}, Pages = {1--27}, Volume = {13}, Doi = {10.1002/bip.1974.360130102}, ISSN = {1097-0282}, Owner = {paul}, Publisher = {Wiley Subscription Services, Inc., A Wiley Company}, Timestamp = {2012.09.24} } @Article{Enderlein1999, Title = {Highly Efficient Optical Detection of Surface-Generated Fluorescence}, Author = {J\"{o}rg Enderlein and Thomas Ruckstuhl and Stefan Seeger}, Journal = {Applied Optics}, Year = {1999}, Month = {Feb}, Number = {4}, Pages = {724--732}, Volume = {38}, Abstract = {We present a theoretical study of a new highly efficient system for optical light collection, designed for ultrasensitive fluorescence detection of surface-bound molecules. The main core of the system is a paraboloid glass segment acting as a mirror for collecting the fluorescence. A special feature of the system is its ability to sample not only fluorescence that is emitted below the angle of total internal reflection (the critical angle) but also particularly the light above the critical angle. As shown, this is especially advantageous for collecting the fluorescence of surface-bound molecules. A comparison is made with conventional high-aperture microscope objectives. Furthermore, it is shown that the system allows not only for highly efficient light collection but also for confocal imaging of the detection region, which is of great importance for rejecting scattered light in potential applications such as the detection of only a few molecules.}, Doi = {10.1364/AO.38.000724}, Keywords = {Geometric optical design; Microscopy; Detection; Fluorescence microscopy}, Owner = {paul}, Publisher = {OSA}, Timestamp = {2012.11.02} } @Article{Foo2012, Title = {Factors Affecting the Quantification of Biomolecular Interactions by Fluorescence Cross-Correlation Spectroscopy}, Author = {Foo, Y. H. and Naredi-Rainer, N. and Lamb, D. C. and Ahmed, S. and Wohland, T.}, Journal = {Biophys J}, Year = {2012}, Number = {5}, Pages = {1174--83}, Volume = {102}, Abstract = {Fluorescence cross-correlation spectroscopy (FCCS) is used to determine interactions and dissociation constants (K(d)s) of biomolecules. The determination of a K(d) depends on the accurate measurement of the auto- and cross-correlation function (ACF and CCF) amplitudes. In the case of complete binding, the ratio of the CCF/ACF amplitudes is expected to be 1. However, measurements performed on tandem fluorescent proteins (FPs), in which two different FPs are linked, yield CCF/ACF amplitude ratios of approximately 0.5 or less for different FCCS schemes. We use single wavelength FCCS and pulsed interleaved excitation FCCS to measure various tandem FPs constituted of different red and green FPs and determine the causes for this suboptimal ratio. The main causes for the reduced CCF/ACF amplitude ratio are differences in observation volumes for the different labels, the existence of dark FPs due to maturation problems, photobleaching, and to a lesser extent Forster (or fluorescence) resonance energy transfer between the labels. We deduce the fraction of nonfluorescent proteins for EGFP, mRFP, and mCherry as well as the differences in observation volumes. We use this information to correct FCCS measurements of the interaction of Cdc42, a small Rho-GTPase, with its effector IQGAP1 in live cell measurements to obtain a label-independent value for the K(d).}, Doi = {10.1016/j.bpj.2012.01.040}, Owner = {paul}, Timestamp = {2014.01.15} } @Article{Hansen1998, Title = {Measuring Reversible Adsorption Kinetics of Small Molecules at Solid/Liquid Interfaces by Total Internal Reflection Fluorescence Correlation Spectroscopy}, Author = {Hansen, Richard L and Harris, Joel M}, Journal = {Analytical Chemistry}, Year = {1998}, Number = {20}, Pages = {4247--4256}, Volume = {70}, Doi = {10.1021/ac980925l}, Owner = {paul}, Timestamp = {2012.02.14} } @Article{Hashmi2007, Title = {Spatially extended FCS for visualizing and quantifying high-speed multiphase flows in microchannels}, Author = {Sara M. Hashmi and Michael Loewenberg and Eric R. Dufresne}, Journal = {Optics Express}, Year = {2007}, Month = {May}, Number = {10}, Pages = {6528--6533}, Volume = {15}, Abstract = {We report the development of spatially extended fluorescence correlation spectroscopy for visualizing and quantifying multiphase flows in microchannels. We employ simultaneous detection with a high-speed camera across the width of the channel, enabling investigation of the dynamics of the flow at short time scales. We take advantage of the flow to scan the sample past the fixed illumination, capturing frames up to 100 KHz. At these rates, we can resolve the motion of sub-micron particles at velocities up to the order of 1 cm/s. We visualize flows with kymographs and quantify velocity profiles by cross-correlations within the focal volume. We demonstrate the efficacy of our approach by measuring the depth-resolved velocity profile of suspensions of sub-micron diameter silica particles flowing up to 1.5 mm/s.}, Doi = {10.1364/OE.15.006528}, Keywords = {Velocimetry; Flow diagnostics; Fluorescence, laser-induced}, Owner = {paul}, Publisher = {OSA}, Timestamp = {2012.11.07} } @Article{Hassler2005, Title = {High Count Rates with Total Internal Reflection Fluorescence Correlation Spectroscopy}, Author = {Hassler, Kai and Anhut, Tiemo and Rigler, Rudolf and G\"{o}sch, Michael and Lasser, Theo}, Journal = {Biophysical Journal}, Year = {2005}, Month = jan, Number = {1}, Pages = {L01--L03}, Volume = {88}, Doi = {10.1529/biophysj.104.053884}, ISSN = {0006-3495}, Owner = {paul}, Publisher = {Cell Press}, Refid = {S0006-3495(05)73079-4 DOI - 10.1529/biophysj.104.053884}, Timestamp = {2012.05.02} } @Article{Hassler2005a, Title = {Total internal reflection fluorescence correlation spectroscopy (TIR-FCS) with low background and high count-rate per molecule}, Author = {Kai Hassler and Marcel Leutenegger and Per Rigler and Ramachandra Rao and Rudolf Rigler and Michael G\"{o}sch and Theo Lasser}, Journal = {Optics Express}, Year = {2005}, Month = {Sep}, Number = {19}, Pages = {7415--7423}, Volume = {13}, Abstract = {We designed a fluorescence correlation spectroscopy (FCS) system for measurements on surfaces. The system consists of an objective-type total internal reflection fluorescence (TIRF) microscopy setup, adapted to measure FCS. Here, the fluorescence exciting evanescent wave is generated by epi-illumination through the periphery of a high NA oil-immersion objective. The main advantages with respect to conventional FCS systems are an improvement in terms of counts per molecule (cpm) and a high signal to background ratio. This is demonstrated by investigating diffusion as well as binding and release of single molecules on a glass surface. Furthermore, the size and shape of the molecule detection efficiency (MDE) function was calculated, using a wave-vectorial approach and taking into account the influence of the dielectric interface on the emission properties of fluorophores.}, Doi = {10.1364/OPEX.13.007415}, Keywords = {Spectroscopy, fluorescence and luminescence; Spectroscopy, surface; Fluorescence, laser-induced}, Owner = {paul}, Publisher = {OSA}, Timestamp = {2012.09.21} } @Article{Haupts1998, Title = {Dynamics of fluorescence fluctuations in green fluorescent protein observed by fluorescence correlation spectroscopy}, Author = {Haupts, Ulrich and Maiti, Sudipta and Schwille, Petra and Webb, Watt W.}, Journal = {Proceedings of the National Academy of Sciences}, Year = {1998}, Number = {23}, Pages = {13573-13578}, Volume = {95}, Abstract = {We have investigated the pH dependence of the dynamics of conformational fluctuations of green fluorescent protein mutants EGFP (F64L/S65T) and GFP-S65T in small ensembles of molecules in solution by using fluorescence correlation spectroscopy (FCS). FCS utilizes time-resolved measurements of fluctuations in the molecular fluorescence emission for determination of the intrinsic dynamics and thermodynamics of all processes that affect the fluorescence. Fluorescence excitation of a bulk solution of EGFP decreases to zero at low pH (pKa = 5.8) paralleled by a decrease of the absorption at 488 nm and an increase at 400 nm. Protonation of the hydroxyl group of Tyr-66, which is part of the chromophore, induces these changes. When FCS is used the fluctuations in the protonation state of the chromophore are time resolved. The autocorrelation function of fluorescence emission shows contributions from two chemical relaxation processes as well as diffusional concentration fluctuations. The time constant of the fast, pH-dependent chemical process decreases with pH from 300 μs at pH 7 to 45 μs at pH 5, while the time-average fraction of molecules in a nonfluorescent state increases to 80% in the same range. A second, pH-independent, process with a time constant of 340 μs and an associated fraction of 13% nonfluorescent molecules is observed between pH 8 and 11, possibly representing an internal proton transfer process and associated conformational rearrangements. The FCS data provide direct measures of the dynamics and the equilibrium properties of the protonation processes. Thus FCS is a convenient, intrinsically calibrated method for pH measurements in subfemtoliter volumes with nanomolar concentrations of EGFP.}, Doi = {10.1073/pnas.95.23.13573}, Owner = {paul}, Timestamp = {2012.11.01} } @Article{Haustein2007, Title = {Fluorescence Correlation Spectroscopy: Novel Variations of an Established Technique}, Author = {Haustein, Elke and Schwille, Petra}, Journal = {Annual Review of Biophysics and Biomolecular Structure}, Year = {2007}, Number = {1}, Pages = {151-169}, Volume = {36}, Doi = {10.1146/annurev.biophys.36.040306.132612}, Owner = {paul}, Timestamp = {2012.02.14} } @Article{Helmers2003, Title = {CMOS vs. CCD sensors in speckle interferometry}, Author = {Heinz Helmers and Markus Schellenberg}, Journal = {Optics \& Laser Technology}, Year = {2003}, Number = {8}, Pages = {587 - 595}, Volume = {35}, Doi = {10.1016/S0030-3992(03)00078-1}, ISSN = {0030-3992}, Keywords = {CCD sensors}, Owner = {paul}, Timestamp = {2012.10.06} } @Article{Holekamp2008, Title = {Fast Three-Dimensional Fluorescence Imaging of Activity in Neural Populations by Objective-Coupled Planar Illumination Microscopy}, Author = {Terrence F. Holekamp and Diwakar Turaga and Timothy E. Holy}, Journal = {Neuron}, Year = {2008}, Number = {5}, Pages = {661 - 672}, Volume = {57}, Doi = {10.1016/j.neuron.2008.01.011}, ISSN = {0896-6273}, Keywords = {SYSBIO}, Owner = {paul}, Timestamp = {2012.11.13} } @Article{Humpolickova2006, Title = {Probing Diffusion Laws within Cellular Membranes by Z-Scan Fluorescence Correlation Spectroscopy}, Author = {Jana Humpol\'{i}\v{c}kov\'{a} and Ellen Gielen and Ale\v{s} Benda and Veronika Fagulova and Jo Vercammen and Martin vandeVen and Martin Hof and Marcel Ameloot and Yves Engelborghs}, Journal = {Biophysical Journal}, Year = {2006}, Number = {3}, Pages = {L23 - L25}, Volume = {91}, Doi = {10.1529/biophysj.106.089474}, ISSN = {0006-3495}, Owner = {paul}, Timestamp = {2012.10.25} } @Book{Nocedal2006, Title = {Numerical Optimization}, Author = {Nocedal J. and Wright S J.}, Publisher = {Springer Berlin Heidelberg}, Year = {2006}, Doi = {10.1007/978-3-540-35447-5}, Owner = {paul}, Timestamp = {2014.03.31} } @Article{Jin2004, Title = {Near-surface velocimetry using evanescent wave illumination}, Author = {Jin, S. and Huang, P. and Park, J. and Yoo, J. Y. and Breuer, K. S.}, Journal = {Experiments in Fluids}, Year = {2004}, Pages = {825-833}, Volume = {37}, Affiliation = {School of Mechanical and Aerospace Engineering Seoul National University Seoul 151-742 Korea}, Doi = {10.1007/s00348-004-0870-7}, ISSN = {0723-4864}, Issue = {6}, Keyword = {Technik}, Owner = {paul}, Publisher = {Springer Berlin / Heidelberg}, Timestamp = {2012.02.14} } @Article{Kannan2006, Title = {Electron Multiplying Charge-Coupled Device Camera Based Fluorescence Correlation Spectroscopy}, Author = {Kannan, Balakrishnan and Har, Jia Yi and Liu, Ping and Maruyama, Ichiro and Ding, Jeak Ling and Wohland, Thorsten}, Journal = {Analytical Chemistry}, Year = {2006}, Number = {10}, Pages = {3444-3451}, Volume = {78}, Doi = {10.1021/ac0600959}, Owner = {paul}, Timestamp = {2012.11.07} } @Article{Kim2007, Title = {Fluorescence correlation spectroscopy in living cells}, Author = {Kim, S. A. and Heinze, K. G. and Schwille, P.}, Journal = {Nat Methods}, Year = {2007}, Number = {11}, Pages = {963--73}, Volume = {4}, Abstract = {Fluorescence correlation spectroscopy (FCS) is an ideal analytical tool for studying concentrations, propagation, interactions and internal dynamics of molecules at nanomolar concentrations in living cells. FCS analyzes minute fluorescence-intensity fluctuations about the equilibrium of a small ensemble (<10(3)) of molecules. These fluctuations act like a 'fingerprint' of a molecular species detected when entering and leaving a femtoliter-sized optically defined observation volume created by a focused laser beam. In FCS the fluorescence fluctuations are recorded as a function of time and then statistically analyzed by autocorrelation analysis. The resulting autocorrelation curve yields a measure of self-similarity of the system after a certain time delay, and its amplitude describes the normalized variance of the fluorescence fluctuations. By fitting the curves to an appropriate physical model, this method provides precise information about a multitude of measurement parameters, including diffusion coefficients, local concentration, states of aggregation and molecular interactions. FCS operates in real time with diffraction-limited spatial and sub-microsecond temporal resolution. Assessing diverse molecular dynamics within the living cell is a challenge well met by FCS because of its single-molecule sensitivity and high dynamic resolution. For these same reasons, however, intracellular FCS measurements also harbor the large risk of collecting artifacts and thus producing erroneous data. Here we provide a step-by-step guide to the application of FCS to cellular systems, including methods for minimizing artifacts, optimizing measurement conditions and obtaining parameter values in the face of diverse and complex conditions of the living cell. A discussion of advantages and disadvantages of one-photon versus two-photon excitation for FCS is available in Supplementary Methods online.}, Doi = {10.1038/nmeth1104}, Keywords = {Animals Biological Transport Cytophotometry/instrumentation/*methods Eukaryotic Cells/*metabolism Fluorescent Dyes/chemistry Humans Kinetics Models, Biological Proteins/chemistry/metabolism Spectrometry, Fluorescence/instrumentation/methods Staining and Labeling}, Owner = {paul}, Timestamp = {2014.01.15} } @InCollection{Kohl2005, Title = {Fluorescence Correlation Spectroscopy with Autofluorescent Proteins}, Author = {Kohl, Tobias and Schwille, Petra}, Booktitle = {Microscopy Techniques}, Publisher = {Springer Berlin / Heidelberg}, Year = {2005}, Editor = {Rietdorf, Jens}, Pages = {1316-1317}, Series = {Advances in Biochemical Engineering/Biotechnology}, Volume = {95}, Affiliation = {Pastor-Sander-Bogen 92 37083 Göttingen Germany}, Doi = {10.1007/b102212}, ISBN = {978-3-540-23698-6}, Keyword = {Chemistry and Materials Science}, Owner = {paul}, Timestamp = {2012.02.14} } @Article{Koppel1974, Title = {Statistical accuracy in fluorescence correlation spectroscopy}, Author = {Koppel, D.}, Journal = {Phys Rev A}, Year = {1974}, Pages = {1938--1945}, Volume = {10}, Doi = {10.1103/physreva.10.1938}, Keywords = {FCS}, Owner = {paul}, Timestamp = {2014.01.15} } @Article{Korlach1999, Title = {Characterization of lipid bilayer phases by confocal microscopy and fluorescence correlation spectroscopy}, Author = {Korlach, J. and Schwille, P. and Webb, W. W. and Feigenson, G. W.}, Journal = {Proc Natl Acad Sci U S A}, Year = {1999}, Number = {15}, Pages = {8461--6}, Volume = {96}, Abstract = {We report the application of confocal imaging and fluorescence correlation spectroscopy (FCS) to characterize chemically well-defined lipid bilayer models for biomembranes. Giant unilamellar vesicles of dilauroyl phosphatidylcholine/dipalmitoyl phosphatidylcholine (DLPC/DPPC)/cholesterol were imaged by confocal fluorescence microscopy with two fluorescent probes, 1, 1'-dieicosanyl-3,3,3',3'-tetramethylindocarbocyanine perchlorate (DiI-C(20)) and 2-(4,4-difluoro-5,7-dimethyl-4-bora-3a, 4a-diaza-s-indacene-3-pentanoyl)-1-hexadecanoyl-sn-glycero-3 -phosphoc holine (Bodipy-PC). Phase separation was visualized by differential probe partition into the coexisting phases. Three-dimensional image reconstructions of confocal z-scans through giant unilamellar vesicles reveal the anisotropic morphology of coexisting phase domains on the surface of these vesicles with full two-dimensional resolution. This method demonstrates by direct visualization the exact superposition of like phase domains in apposing monolayers, thus answering a long-standing open question. Cholesterol was found to induce a marked change in the phase boundary shapes of the coexisting phase domains. To further characterize the phases, the translational diffusion coefficient, D(T), of the DiI-C(20) was measured by FCS. D(T) values at approximately 25 degrees C ranged from approximately 3 x 10(-8) cm(2)/s in the fluid phase, to approximately 2 x 10(-9) cm(2)/s in high-cholesterol-content phases, to approximately 2 x 10(-10) cm(2)/s in the spatially ordered phases that coexist with fluid phases. In favorable cases, FCS could distinguish two different values of D(T) in a region of two-phase coexistence on a single vesicle.}, Doi = {10.1073/pnas.96.15.8461}, Keywords = {Carbocyanines Cholesterol/chemistry Diffusion Fluorescent Dyes Lipid Bilayers/*chemistry Liposomes/chemistry Microscopy, Confocal Phospholipids/chemistry Spectrometry, Fluorescence}, Owner = {paul}, Timestamp = {2014.01.15} } @Article{Korson1969, Title = {Viscosity of water at various temperatures}, Author = {Korson, Lawrence and Drost-Hansen, Walter and Millero, Frank J.}, Journal = {The Journal of Physical Chemistry}, Year = {1969}, Number = {1}, Pages = {34-39}, Volume = {73}, Doi = {10.1021/j100721a006}, Owner = {paul}, Timestamp = {2012.10.29} } @Book{LandauLifshitsStatPhys, Title = {{Statistical Physics, Third Edition, Part 1: Volume 5 (Course of Theoretical Physics, Volume 5)}}, Author = {Landau, L. D. and Lifshitz, E. M.}, Publisher = {Butterworth-Heinemann}, Year = {1980}, Edition = {3}, Month = jan, Abstract = {{A lucid presentation of statistical physics and thermodynamics which develops from the general principles to give a large number of applications of the theory.}}, Citeulike-article-id = {1284487}, Citeulike-linkout-0 = {http://www.amazon.ca/exec/obidos/redirect?tag=citeulike09-20\&path=ASIN/0750633727}, Citeulike-linkout-1 = {http://www.amazon.de/exec/obidos/redirect?tag=citeulike01-21\&path=ASIN/0750633727}, Citeulike-linkout-2 = {http://www.amazon.fr/exec/obidos/redirect?tag=citeulike06-21\&path=ASIN/0750633727}, Citeulike-linkout-3 = {http://www.amazon.jp/exec/obidos/ASIN/0750633727}, Citeulike-linkout-4 = {http://www.amazon.co.uk/exec/obidos/ASIN/0750633727/citeulike00-21}, Citeulike-linkout-5 = {http://www.amazon.com/exec/obidos/redirect?tag=citeulike07-20\&path=ASIN/0750633727}, Citeulike-linkout-6 = {http://www.worldcat.org/isbn/0750633727}, Citeulike-linkout-7 = {http://books.google.com/books?vid=ISBN0750633727}, Citeulike-linkout-8 = {http://www.amazon.com/gp/search?keywords=0750633727\&index=books\&linkCode=qs}, Citeulike-linkout-9 = {http://www.librarything.com/isbn/0750633727}, Day = {15}, HowPublished = {Paperback}, ISBN = {0750633727}, Keywords = {fermi\_statistics, statistical\_physics}, Owner = {paul}, Posted-at = {2011-03-03 11:38:41}, Priority = {2}, Timestamp = {2012.02.03} } @Article{Leutenegger2012, Title = {Fluorescence correlation spectroscopy with a total internal reflection fluorescence STED microscope (TIRF-STED-FCS)}, Author = {Marcel Leutenegger and Christian Ringemann and Theo Lasser and Stefan W. Hell and Christian Eggeling}, Journal = {Optics Express}, Year = {2012}, Month = {Feb}, Number = {5}, Pages = {5243--5263}, Volume = {20}, Abstract = {We characterize a novel fluorescence microscope which combines the high spatial discrimination of a total internal reflection epi-fluorescence (epi-TIRF) microscope with that of stimulated emission depletion (STED) nanoscopy. This combination of high axial confinement and dynamic-active lateral spatial discrimination of the detected fluorescence emission promises imaging and spectroscopy of the structure and function of cell membranes at the macro-molecular scale. Following a full theoretical description of the sampling volume and the recording of images of fluorescent beads, we exemplify the performance and limitations of the TIRF-STED nanoscope with particular attention to the polarization state of the laser excitation light. We demonstrate fluorescence correlation spectroscopy (FCS) with the TIRF-STED nanoscope by observing the diffusion of dye molecules in aqueous solutions and of fluorescent lipid analogs in supported lipid bilayers in the presence of background signal. The nanoscope reduced the out-of-focus background signal. A lateral resolution down to 40--50 nm was attained which was ultimately limited by the low lateral signal-to-background ratio inherent to the confocal epi-TIRF scheme. Together with the estimated axial confinement of about 55 nm, our TIRF-STED nanoscope achieved an almost isotropic and less than 1 attoliter small all-optically induced measurement volume.}, Doi = {10.1364/OE.20.005243}, Keywords = {Diffraction; Fluorescence microscopy; Fluorescence}, Owner = {paul}, Publisher = {OSA}, Timestamp = {2012.09.21} } @Article{Levenberg1944, Title = {A method for the solution of certain non-linear problems in least squares}, Author = {Levenberg, Kenneth}, Journal = {Quarterly Journal of Applied Mathmatics}, Year = {1944}, Number = {2}, Pages = {164--168}, Volume = {II}, __markedentry = {[paul:6]}, Citeulike-article-id = {10796881}, Keywords = {indefinite, nonconvex, optimization}, Owner = {paul}, Posted-at = {2012-06-17 09:00:21}, Priority = {2}, Timestamp = {2014.03.31} } @Article{Lieto2003a, Title = {Ligand-Receptor Kinetics Measured by Total Internal Reflection with Fluorescence Correlation Spectroscopy}, Author = {Lieto, Alena M. and Cush, Randall C. and Thompson, Nancy L.}, Journal = {Biophysical Journal}, Year = {2003}, Month = nov, Number = {5}, Pages = {3294--3302}, Volume = {85}, Doi = {10.1016/S0006-3495(03)74748-1}, ISSN = {0006-3495}, Owner = {paul}, Publisher = {Cell Press}, Refid = {S0006-3495(03)74748-1 DOI - 10.1016/S0006-3495(03)74748-1}, Timestamp = {2012.09.21} } @Article{Lieto2003, Title = {Lateral Diffusion from Ligand Dissociation and Rebinding at Surfaces†}, Author = {Lieto, Alena M. and Lagerholm, B. Christoffer and Thompson, Nancy L.}, Journal = {Langmuir}, Year = {2003}, Number = {5}, Pages = {1782-1787}, Volume = {19}, Doi = {10.1021/la0261601}, Owner = {paul}, Timestamp = {2012.02.14} } @Article{Lieto2004, Title = {Total Internal Reflection with Fluorescence Correlation Spectroscopy: Nonfluorescent Competitors}, Author = {Alena M. Lieto and Nancy L. Thompson}, Journal = {Biophysical Journal}, Year = {2004}, Number = {2}, Pages = {1268 - 1278}, Volume = {87}, Doi = {10.1529/biophysj.103.035030}, ISSN = {0006-3495}, Owner = {paul}, Timestamp = {2012.02.14} } @Article{Muller2014, Title = {Scanning fluorescence correlation spectroscopy ({SFCS}) with a scan path perpendicular to the membrane plane}, Author = {M\"{u}ller, P. and Schwille, P. and Weidemann, T.}, Journal = {Methods Mol Biol}, Year = {2014}, Pages = {635--51}, Volume = {1076}, Abstract = {Scanning fluorescence correlation spectroscopy (SFCS) with a scan path perpendicular to the membrane plane was introduced to measure diffusion and interactions of fluorescent components in free-standing biomembranes. Using a confocal laser scanning microscope (CLSM), the open detection volume is repeatedly scanned through the membrane at a kHz frequency. The fluorescence photons emitted from the detection volume are continuously recorded and stored in a file. While the accessory hardware requirements for a conventional CLSM are minimal, data evaluation can pose a bottleneck. The photon events must be assigned to each scan, in which the maximum signal intensities have to be detected, binned, and aligned between the scans, in order to derive the membrane-related intensity fluctuations of one spot. Finally, this time-dependent signal must be correlated and evaluated by well-known FCS model functions. Here we provide two platform-independent, open source software tools (PyScanFCS and PyCorrFit) that allow to perform all of these steps and to establish perpendicular SFCS in its one- or two-focus as well as its single- or dual-color modality.}, Doi = {10.1007/978-1-62703-649-8_29}, Owner = {paul}, Timestamp = {2014.01.25} } @Article{Magde1972, Title = {Thermodynamic Fluctuations in a Reacting System - Measurement by Fluorescence Correlation Spectroscopy}, Author = {Magde, Douglas and Elson, Elliot and Webb, W. W.}, Journal = {Physical Review Letters}, Year = {1972}, Month = {Sep}, Pages = {705--708}, Volume = {29}, Doi = {10.1103/PhysRevLett.29.705}, Issue = {11}, Owner = {paul}, Publisher = {American Physical Society}, Timestamp = {2012.11.01} } @Article{Magde1974, Title = {Fluorescence correlation spectroscopy. II. An experimental realization}, Author = {Magde, Douglas and Elson, Elliot L. and Webb, Watt W.}, Journal = {Biopolymers}, Year = {1974}, Number = {1}, Pages = {29--61}, Volume = {13}, Doi = {10.1002/bip.1974.360130103}, ISSN = {1097-0282}, Owner = {paul}, Publisher = {Wiley Subscription Services, Inc., A Wiley Company}, Timestamp = {2012.09.21} } @Article{Magde1978, Title = {Fluorescence correlation spectroscopy. III. Uniform translation and laminar flow}, Author = {Magde, D. and Webb, W. W. and Elson, E. L.}, Journal = {Biopolymers}, Year = {1978}, Pages = {361--376}, Volume = {17}, Doi = {10.1002/bip.1978.360170208}, Keywords = {FCS; laminar flow; capillary electrophoresis}, Owner = {paul}, Timestamp = {2014.01.15} } @Article{Meseth1999, Title = {Resolution of fluorescence correlation measurements}, Author = {Meseth, U. and Wohland, T. and Rigler, R. and Vogel, H.}, Journal = {Biophys J}, Year = {1999}, Pages = {1619--1631}, Volume = {76}, Abstract = {The resolution limit of fluorescence correlation spectroscopy for two-component solutions is investigated theoretically and experimentally. The autocorrelation function for two different particles in solution were computed, statistical noise was added, and the resulting curve was fitted with a least squares fit. These simulations show that the ability to distinguish between two different molecular species in solution depends strongly on the number of photons detected from each particle, their difference in size, and the concentration of each component in solution. To distinguish two components, their diffusion times must differ by at least a factor of 1.6 for comparable quantum yields and a high fluorescence signal. Experiments were conducted with Rhodamine 6G and Rhodamine-labeled bovine serum albumin. The experimental results support the simulations. In addition, they show that even with a high fluorescence signal but significantly different quantum yields, the diffusion times must differ by a factor much bigger than 1.6 to distinguish the two components. Depending on the quantum yields and the difference in size, there exists a concentration threshold for the less abundant component below which it is not possible to determine with statistical means alone that two particles are in solution.}, Doi = {10.1016/S0006-3495(99)77321-2}, Keywords = {Diffusion, Multiple components}, Owner = {TW}, Timestamp = {2014.01.27} } @Article{Nelder1965, Title = {A simplex method for function minimization}, Author = {Nelder, John A and Mead, Roger}, Journal = {Computer journal}, Year = {1965}, Number = {4}, Pages = {308--313}, Volume = {7}, Doi = {10.1093/comjnl/7.4.308}, Owner = {paul}, Timestamp = {2014.03.31} } @Article{Nitsche2004, Title = {A Transient Diffusion Model Yields Unitary Gap Junctional Permeabilities from Images of Cell-to-Cell Fluorescent Dye Transfer Between Xenopus Oocytes}, Author = {Johannes M. Nitsche and Hou-Chien Chang and Paul A. Weber and Bruce J. Nicholson}, Journal = {Biophysical Journal}, Year = {2004}, Number = {4}, Pages = {2058 - 2077}, Volume = {86}, Doi = {10.1016/S0006-3495(04)74267-8}, ISSN = {0006-3495}, Owner = {paul}, Timestamp = {2012.11.08} } @Article{Ohsugi2009, Title = {Multipoint fluorescence correlation spectroscopy with total internal reflection fluorescence microscope}, Author = {Ohsugi, Yu and Kinjo, Masataka}, Journal = {Journal of Biomedical Optics}, Year = {2009}, Number = {1}, Pages = {014030-014030-4}, Volume = {14}, Doi = {10.1117/1.3080723}, Owner = {paul}, Timestamp = {2012.11.12} } @Article{Ohsugi2006, Title = {Lateral mobility of membrane-binding proteins in living cells measured by total internal reflection fluorescence correlation spectroscopy.}, Author = {Ohsugi, Yu and Saito, Kenta and Tamura, Mamoru and Kinjo, Masataka}, Journal = {Biophysical Journal}, Year = {2006}, Number = {9}, Pages = {3456--3464}, Volume = {91}, Doi = {10.1529/biophysj.105.074625}, Owner = {paul}, Publisher = {Biophysical Society}, Timestamp = {2012.02.14} } @Article{Palmer1987, Title = {Theory of sample translation in fluorescence correlation spectroscopy.}, Author = {A. G. Palmer and N. L. Thompson}, Journal = {Biophysical Journal}, Year = {1987}, Month = {Feb}, Number = {2}, Pages = {339--343}, Volume = {51}, Abstract = {New applications of the technique of fluorescence correlation spectroscopy (FCS) require lateral translation of the sample through a focused laser beam (Peterson, N.O., D.C. Johnson, and M.J. Schlesinger, 1986, Biophys. J., 49:817-820). Here, the effect of sample translation on the shape of the FCS autocorrelation function is examined in general. It is found that if the lateral diffusion coefficients of the fluorescent species obey certain conditions, then the FCS autocorrelation function is a simple product of one function that depends only on transport coefficients and another function that depends only on the rate constants of chemical reactions that occur in the sample. This simple form should allow manageable data analyses in new FCS experiments that involve sample translation.}, Doi = {10.1016/S0006-3495(87)83340-4}, Keywords = {Kinetics; Lasers; Mathematics; Models, Theoretical; Spectrometry, Fluorescence, methods}, Language = {eng}, Medline-pst = {ppublish}, Owner = {paul}, Pii = {S0006-3495(87)83340-4}, Pmid = {3828464}, Timestamp = {2012.11.02} } @Article{Pero2006-06, Title = {Size dependence of protein diffusion very close to membrane surfaces: measurement by total internal reflection with fluorescence correlation spectroscopy.}, Author = {Pero, JK and Haas, EM and Thompson, NL}, Journal = {The Journal of Physical Chemistry. B}, Year = {2006}, Number = {5}, Pages = {10910-8}, Volume = {110}, Doi = {10.1021/jp056990y}, ISSN = {1520-6106}, Owner = {paul}, Timestamp = {2012.09.21} } @Article{Petrasek2010, Title = {Scanning {FCS} for the characterization of protein dynamics in live cells}, Author = {Petr\'{a}\v{s}ek, Zden\v{e}k and Ries, J. and Schwille, P.}, Journal = {Methods Enzymol}, Year = {2010}, Pages = {317--43}, Volume = {472}, Abstract = {Scanning fluorescence correlation spectroscopy (sFCS) is the generic term for a group of fluorescence correlation techniques where the measurement volume is moved across the sample in a defined way. The introduction of scanning is motivated by its ability to alleviate or remove several distinct problems often encountered in standard FCS, and thus, to extend the range of applicability of fluorescence correlation methods in biological systems. These problems include poor statistical accuracy in measurements with slowly moving molecules, photobleaching, optical distortions affecting the calibration of the measurement volume, membrane instabilities, etc. Here, we present an overview of sFCS methods, explaining their benefits, implementation details, requirements, and limitations, as well as relations to each other. Further, we give examples of different sFCS implementations as applied to cellular systems, namely large-circle sFCS to measure protein dynamics in embryo cortex and line sFCS to measure protein diffusion and interactions in unstable membranes.}, Doi = {10.1016/s0076-6879(10)72005-x}, Owner = {paul}, Timestamp = {2014.01.15} } @Article{Petrasek2008, Title = {Precise Measurement of Diffusion Coefficients using Scanning Fluorescence Correlation Spectroscopy}, Author = {Petr\'{a}\v{s}ek, Zden\v{e}k and Schwille, Petra}, Journal = {Biophysical Journal}, Year = {2008}, Month = feb, Number = {4}, Pages = {1437--1448}, Volume = {94}, Doi = {10.1529/biophysj.107.108811}, ISSN = {0006-3495}, Owner = {paul}, Publisher = {Cell Press}, Refid = {S0006-3495(08)70660-X DOI - 10.1529/biophysj.107.108811}, Timestamp = {2012.05.20} } @InCollection{Petrov:2008, Title = {State of the Art and Novel Trends in Fluorescence Correlation Spectroscopy}, Author = {Petrov, E. P. and Schwille, P.}, Booktitle = {Standardization and Quality Assurance in Fluorescence Measurements II}, Publisher = {Springer Berlin Heidelberg}, Year = {2008}, Editor = {Resch-Genger, Ute}, Pages = {145-197}, Series = {Springer Series on Fluorescence}, Volume = {6}, Affiliation = {Biophysics, BIOTEC, Technische Universität Dresden, Tatzberg 47–51, 01307 Dresden, Germany}, Doi = {10.1007/4243_2008_032}, ISBN = {978-3-540-70571-0}, Keyword = {Chemistry} } @Article{Powell1964, Title = {An efficient method for finding the minimum of a function of several variables without calculating derivatives}, Author = {Powell, M. J. D.}, Journal = {The Computer Journal}, Year = {1964}, Month = {Feb}, Number = {2}, Pages = {155–162}, Volume = {7}, Doi = {10.1093/comjnl/7.2.155}, ISSN = {1460-2067}, Owner = {paul}, Publisher = {Oxford University Press (OUP)}, Timestamp = {2014.03.31} } @Article{Press, Title = {Numerical recipes}, Author = {Press, William and Flannery, Brian P and Teukolsky, SAUL and Vetterling, WT}, Journal = {Cambridge University Press}, Year = {2006}, Pages = {989}, Volume = {1}, __markedentry = {[paul:]}, Owner = {paul}, Publisher = {Cambridge Univ Press}, Timestamp = {2014.03.31} } @Article{Qian1991, Title = {Analysis of confocal laser-microscope optics for 3-D fluorescence correlation spectroscopy}, Author = {Hong Qian and Elliot L. Elson}, Journal = {Applied Optics}, Year = {1991}, Month = {Apr}, Number = {10}, Pages = {1185--1195}, Volume = {30}, Abstract = {Quantitative fluorescence correlation spectroscopy (FCS) and fluorescence photobleaching recovery (FPR) measurements in bulk solution require a well characterized confocal laser microscope optical system. The introduction of a characteristic function, the collection efficiency function (CEF), provides a quantitative theoretical analysis of this system, which yields an interpretation of the FCS and FPR measurements in three dimensions. We demonstrate that when the proper field diaphragm is introduced, the 3-D FCS measurements can be mimicked by a 2-D theory with only minor error. The FPR characteristic recovery time for diffusion is expected to be slightly longer than the corresponding time measured by FCS in the same conditions. This is because the profile of the laser beam used for photobleaching is not affected by the field diaphragm. The CEF is also important for quantitative analysis of standard scanning confocal microscopy when it is carried out using a finite detection pinhole.}, Doi = {10.1364/AO.30.001185}, Owner = {paul}, Publisher = {OSA}, Timestamp = {2012.11.02} } @Article{Richter2006, Title = {Formation of Solid-Supported Lipid Bilayers:  An Integrated View}, Author = {Richter, Ralf P. and Bérat, Rémi and Brisson, Alain R.}, Journal = {Langmuir}, Year = {2006}, Number = {8}, Pages = {3497-3505}, Volume = {22}, Doi = {10.1021/la052687c}, Owner = {paul}, Timestamp = {2012.11.12} } @PhdThesis{Ries:08, Title = {Advanced Fluorescence Correlation Techniques to Study Membrane Dynamics}, Author = {Ries, E.}, School = {Biophysics, BIOTEC, Technische Universität Dresden, Tatzberg 47–51, 01307 Dresden, Germany}, Year = {2008}, Note = {\url{http://nbn-resolving.de/urn:nbn:de:bsz:14-ds-1219846317196-73420}} } @Article{Ries2009, Title = {Accurate Determination of Membrane Dynamics with Line-Scan FCS}, Author = {Jonas Ries and Salvatore Chiantia and Petra Schwille}, Journal = {Biophysical Journal}, Year = {2009}, Number = {5}, Pages = {1999 - 2008}, Volume = {96}, Doi = {10.1016/j.bpj.2008.12.3888}, ISSN = {0006-3495}, Owner = {paul}, Timestamp = {2012.11.08} } @Article{Ries2010, Title = {A comprehensive framework for fluorescence cross-correlation spectroscopy}, Author = {Ries, J. and Petr\'{a}\v{s}ek, Zden\v{e}k and Garcia-Saez, A. J. and Schwille, P.}, Journal = {New Journal of Physics}, Year = {2010}, Number = {11}, Pages = {113009}, Volume = {12}, Abstract = {Dual-colour fluorescence cross-correlation spectroscopy is a powerful method of studying binding between labelled biomolecules in vitro as well as in vivo. However, numerous artefacts and experimental complexities complicate quantitative measurements. Here, we show that a combination of dual-colour fluorescence correlation spectroscopy (FCS) with dual-focus FCS avoids artefacts due to chromatic aberrations or saturation and circumvents the calibration of the detection volumes. In addition, we present a comprehensive mathematical framework that allows us to accurately analyse correlation curves even in the presence of spectral cross-talk, incomplete or stochastic labelling, multiple binding sites, a fluorescent background and depletion due to photobleaching. We demonstrate the merits of this approach using dual-colour dual-focus scanning FCS, which allows binding measurements on membranes not affected by membrane movements.}, Doi = {10.1088/1367-2630/12/11/113009}, Keywords = {living cells membrane dynamics diffusion molecules accurate fret fcs}, Owner = {paul}, Timestamp = {2014.01.15} } @Article{Ries2008390, Title = {Total Internal Reflection Fluorescence Correlation Spectroscopy: Effects of Lateral Diffusion and Surface-Generated Fluorescence}, Author = {Jonas Ries and Eugene P. Petrov and Petra Schwille}, Journal = {Biophysical Journal}, Year = {2008}, Number = {1}, Pages = {390 - 399}, Volume = {95}, Doi = {10.1529/biophysj.107.126193}, ISSN = {0006-3495} } @Article{Ries2008, Title = {New concepts for fluorescence correlation spectroscopy on membranes}, Author = {Ries, Jonas and Schwille, Petra}, Journal = {Physical Chemistry Chemical Physics}, Year = {2008}, Number = {24}, Pages = {--}, Volume = {10}, Abstract = {Fluorescence correlation spectroscopy (FCS) is a powerful tool to measure useful physical quantities such as concentrations, diffusion coefficients, diffusion modes or binding parameters, both in model and cell membranes. However, it can suffer from severe artifacts, especially in non-ideal systems. Here we assess the potential and limitations of standard confocal FCS on lipid membranes and present recent developments which facilitate accurate and quantitative measurements on such systems. In particular, we discuss calibration-free diffusion and concentration measurements using z-scan FCS and two focus FCS and present several approaches using scanning FCS to accurately measure slow dynamics. We also show how surface confined FCS enables the study of membrane dynamics even in presence of a strong cytosolic background and how FCS with a variable detection area can reveal submicroscopic heterogeneities in cell membranes.}, Doi = {10.1039/b718132a}, ISSN = {1463-9076}, Owner = {paul}, Publisher = {The Royal Society of Chemistry}, Timestamp = {2012.02.14} } @Article{Ries2006, Title = {Studying slow membrane dynamics with continuous wave scanning fluorescence correlation spectroscopy}, Author = {Ries, J. and Schwille, P.}, Journal = {Biophys J}, Year = {2006}, Number = {5}, Pages = {1915--24}, Volume = {91}, Abstract = {Here we discuss the application of scanning fluorescence correlation spectroscopy (SFCS) using continuous wave excitation to analyze membrane dynamics. The high count rate per molecule enables the study of very slow diffusion in model and cell membranes, as well as the application of two-foci fluorescence cross-correlation spectroscopy for parameter-free determination of diffusion constants. The combination with dual-color fluorescence cross-correlation spectroscopy with continuous or pulsed interleaved excitation allows binding studies on membranes. Reduction of photobleaching, higher reproducibility, and stability compared to traditional FCS on membranes, and the simple implementation in a commercial microscopy setup make SFCS a valuable addition to the pool of fluorescence fluctuation techniques.}, Doi = {10.1529/biophysj.106.082297}, Keywords = {Biological Transport, Active/physiology Cell Membrane/*chemistry/*metabolism Diffusion Membrane Proteins/analysis/*chemistry/*metabolism Spectrometry, Fluorescence/*methods Time Factors}, Owner = {paul}, Timestamp = {2014.01.25} } @Article{Rigler1993, Title = {Fluorescence correlation spectroscopy with high count rate and low background: analysis of translational diffusion}, Author = {Rigler, R. and Mets, {\"U}. and Widengren, J. and Kask, P.}, Journal = {European Biophysics Journal}, Year = {1993}, Pages = {169-175}, Volume = {22}, Doi = {10.1007/BF00185777}, ISSN = {0175-7571}, Issue = {3}, Keywords = {Fluorescence correlation spectroscopy; Fluorescence intensity fluctuations; Translational diffusion; Epifluorescence microscope; Silicon photon counter}, Language = {English}, Owner = {paul}, Publisher = {Springer-Verlag}, Timestamp = {2012.11.02} } @Article{Rippe2000, Title = {Simultaneous binding of two {DNA} duplexes to the NtrC-enhancer complex studied by two-color fluorescence cross-correlation spectroscopy}, Author = {Rippe, K.}, Journal = {Biochemistry}, Year = {2000}, Number = {9}, Pages = {2131--9}, Volume = {39}, Abstract = {The transcription activator protein NtrC (nitrogen regulatory protein C, also termed NR(I)) can catalyze the transition of Escherichia coli RNA polymerase complexed with the sigma(54) factor (RNAP x sigma(54)) from the closed complex (RNAP x sigma(54) bound at the promoter) to the open complex (melting of the promoter DNA). This process involves phosphorylation of NtrC (NtrC-P), assembly of an octameric NtrC-P complex at the enhancer DNA sequence, interaction of this complex with promoter-bound RNAP x sigma(54) via DNA looping, and hydrolysis of ATP. Here it is demonstrated by two-color fluorescence cross-correlation spectroscopy measurements of 6-carboxyfluorescein and 6-carboxy-X-rhodamine-labeled DNA oligonucleotide duplexes that the NtrC-P complex can bind two DNA duplexes simultaneously. This suggests a model for the conformation of the looped intermediate that is formed between NtrC-P and RNAP. sigma(54) at the glnAp2 promoter during the activation process.}, Doi = {10.1021/bi9922190}, Keywords = {Bacterial Proteins/chemistry/*metabolism Base Sequence DNA, Bacterial/chemistry/*metabolism DNA-Binding Proteins/chemistry/*metabolism Diffusion *Enhancer Elements (Genetics) Escherichia coli/chemistry/genetics Escherichia coli Proteins Models, Chemical Models, Molecular Molecular Sequence Data Nucleic Acid Heteroduplexes/chemistry/*metabolism PII Nitrogen Regulatory Proteins Phosphorylation Protein Binding Reproducibility of Results Spectrometry, Fluorescence/methods *Trans-Activators *Transcription Factors}, Owner = {paul}, Timestamp = {2014.01.15} } @Article{Ruan2004, Title = {Spatial-Temporal Studies of Membrane Dynamics: Scanning Fluorescence Correlation Spectroscopy (SFCS)}, Author = {Ruan, Qiaoqiao and Cheng, Melanie A. and Levi, Moshe and Gratton, Enrico and Mantulin, William W.}, Journal = {Biophysical Journal}, Year = {2004}, Month = aug, Number = {2}, Pages = {1260--1267}, Volume = {87}, Doi = {10.1529/biophysj.103.036483}, ISSN = {0006-3495}, Owner = {paul}, Publisher = {Cell Press}, Refid = {S0006-3495(04)73605-X DOI - 10.1529/biophysj.103.036483}, Timestamp = {2012.02.14} } @Article{Sankaran2009, Title = {Diffusion, Transport, and Cell Membrane Organization Investigated by Imaging Fluorescence Cross-Correlation Spectroscopy}, Author = {Sankaran, Jagadish and Manna, Manoj and Guo, Lin and Kraut, Rachel and Wohland, Thorsten}, Journal = {Biophysical Journal}, Year = {2009}, Month = nov, Number = {9}, Pages = {2630--2639}, Volume = {97}, Doi = {10.1016/j.bpj.2009.08.025}, ISSN = {0006-3495}, Owner = {paul}, Publisher = {Cell Press}, Refid = {S0006-3495(09)01387-3 DOI - 10.1016/j.bpj.2009.08.025}, Timestamp = {2012.09.21} } @Article{Sankaran2010, Title = {ImFCS: a software for imaging FCS data analysis and visualization.}, Author = {Jagadish Sankaran and Xianke Shi and Liang Yoong Ho and Ernst H K Stelzer and Thorsten Wohland}, Journal = {Optics Express}, Year = {2010}, Month = {Dec}, Number = {25}, Pages = {25468--25481}, Volume = {18}, Abstract = {The multiplexing of fluorescence correlation spectroscopy (FCS), especially in imaging FCS using fast, sensitive array detectors, requires the handling of large amounts of data. One can easily collect in excess of 100,000 FCS curves a day, too many to be treated manually. Therefore, ImFCS, an open-source software which relies on standard image files was developed and provides a wide range of options for the calculation of spatial and temporal auto- and cross-correlations, as well as differences in Cross-Correlation Functions (ΔCCF). ImFCS permits fitting of standard models to correlation functions and provides optimized histograms of fitted parameters. Applications include the measurement of diffusion and flow with Imaging Total Internal Reflection FCS (ITIR-FCS) and Single Plane Illumination Microscopy FCS (SPIM-FCS) in biologically relevant samples. As a compromise between ITIR-FCS and SPIM-FCS, we extend the applications to Imaging Variable Angle-FCS (IVA-FCS) where sub-critical oblique illumination provides sample sectioning close to the cover slide.}, Doi = {10.1364/OE.18.025468}, Institution = {Singapore-MIT Alliance, National University of Singapore, E4-04-10, 4 Engineering Drive 3, 117576 Singapore.}, Keywords = {Algorithms; Pattern Recognition, Automated, methods; Software; Spectrometry, Fluorescence, methods}, Language = {eng}, Medline-pst = {ppublish}, Owner = {paul}, Pii = {208325}, Pmid = {21164894}, Timestamp = {2012.10.24} } @Article{SbalzariniSPT, Title = {Feature Point Tracking and Trajectory Analysis for Video Imaging in Cell Biology}, Author = {I. F. Sbalzarini and P. Koumoutsakos}, Journal = {Journal of Structural Biology}, Year = {2005}, Pages = {182-195}, Volume = {151(2)}, Doi = {10.1016/j.jsb.2005.06.002}, Owner = {paul}, Timestamp = {2012.10.16} } @Article{Schatzel1990, Title = {Noise on photon correlation data. I. Autocorrelation functions}, Author = {K. Sch{\"a}tzel}, Journal = {Quantum Optics: Journal of the European Optical Society Part B}, Year = {1990}, Number = {4}, Pages = {287}, Volume = {2}, Abstract = {An adequate analysis of photon correlation data requires knowledge about the statistical accuracy of the measured data. For the model of gamma-distributed intensities, that is including the effect of a finite intercept, the full covariance matrix is calculated for all the channels of the photon autocorrelation functions. A thorough discussion of multiple sample time correlation illuminates the importance of temporal averaging effects at large lag times. A practical estimation scheme is given for the noise in photon correlation data from a multiple sample time measurement.}, Doi = {10.1088/0954-8998/2/4/002}, Owner = {paul}, Timestamp = {2012.11.02} } @Article{Schwille1999, Title = {Molecular dynamics in living cells observed by fluorescence correlation spectroscopy with one- and two-photon excitation}, Author = {Schwille, P. and Haupts, U. and Maiti, S. and Webb, W. W.}, Journal = {Biophys J}, Year = {1999}, Number = {4}, Pages = {2251--65}, Volume = {77}, Abstract = {Multiphoton excitation (MPE) of fluorescent probes has become an attractive alternative in biological applications of laser scanning microscopy because many problems encountered in spectroscopic measurements of living tissue such as light scattering, autofluorescence, and photodamage can be reduced. The present study investigates the characteristics of two-photon excitation (2PE) in comparison with confocal one-photon excitation (1PE) for intracellular applications of fluorescence correlation spectroscopy (FCS). FCS is an attractive method of measuring molecular concentrations, mobility parameters, chemical kinetics, and fluorescence photophysics. Several FCS applications in mammalian and plant cells are outlined, to illustrate the capabilities of both 1PE and 2PE. Photophysical properties of fluorophores required for quantitative FCS in tissues are analyzed. Measurements in live cells and on cell membranes are feasible with reasonable signal-to-noise ratios, even with fluorophore concentrations as low as the single-molecule level in the sampling volume. Molecular mobilities can be measured over a wide range of characteristic time constants from approximately 10(-3) to 10(3) ms. While both excitation alternatives work well for intracellular FCS in thin preparations, 2PE can substantially improve signal quality in turbid preparations like plant cells and deep cell layers in tissue. At comparable signal levels, 2PE minimizes photobleaching in spatially restrictive cellular compartments, thereby preserving long-term signal acquisition.}, Doi = {10.1016/s0006-3495(99)77065-7}, Keywords = {Animals Calibration Cell Line Cell Membrane/*metabolism/radiation effects Cell Wall/*metabolism/radiation effects Cytoplasm/*metabolism/radiation effects Diffusion Fluorescence Fluorescent Dyes/*metabolism Green Fluorescent Proteins Humans Kinetics Luminescent Proteins/metabolism Photochemistry *Photons Plant Leaves/cytology/radiation effects Plants, Toxic Rhodamines/metabolism Spectrometry, Fluorescence/instrumentation/*methods Tobacco}, Owner = {paul}, Timestamp = {2014.01.15} } @Article{Schwille2000, Title = {Fluorescence correlation spectroscopy reveals fast optical excitation-driven intramolecular dynamics of yellow fluorescent proteins}, Author = {Schwille, Petra and Kummer, Susanne and Heikal, Ahmed A. and Moerner, W. E. and Webb, Watt W.}, Journal = {Proceedings of the National Academy of Sciences}, Year = {2000}, Number = {1}, Pages = {151-156}, Volume = {97}, Abstract = {Fast excitation-driven fluctuations in the fluorescence emission of yellow-shifted green fluorescent protein mutants T203Y and T203F, with S65G/S72A, are discovered in the 10−6–10−3-s time range, by using fluorescence correlation spectroscopy at 10−8 M. This intensity-dependent flickering is conspicuous at high pH, with rate constants independent of pH and viscosity with a minor temperature effect. The mean flicker rate increases linearly with excitation intensity for at least three decades, but the mean dark fraction of the molecules undergoing these dynamics is independent of illumination intensity over ≈6 × 102 to 5 × 106 W/cm2. These results suggest that optical excitation establishes an equilibration between two molecular states of different spectroscopic properties that are coupled only via the excited state as a gateway. This reversible excitation-driven transition has a quantum efficiency of ≈10−3. Dynamics of external protonation, reversibly quenching the fluorescence, are also observed at low pH in the 10- to 100-μs time range. The independence of these two bright–dark flicker processes implies the existence of at least two separate dark states of these green fluorescent protein mutants. Time-resolved fluorescence measurements reveal a single exponential decay of the excited state population with 3.8-ns lifetime, after 500-nm excitation, that is pH independent. Our fluorescence correlation spectroscopy results are discussed in terms of recent theoretical studies that invoke isomerization of the chromophore as a nonradiative channel of the excited state relaxation.}, Doi = {10.1073/pnas.97.1.151}, Owner = {paul}, Timestamp = {2012.09.24} } @Article{Schwille1997, Title = {Dual-color fluorescence cross-correlation spectroscopy for multicomponent diffusional analysis in solution}, Author = {Schwille, P. and Meyer-Almes, F.J. and Rigler, R.}, Journal = {Biophysical Journal}, Year = {1997}, Month = apr, Number = {4}, Pages = {1878--1886}, Volume = {72}, Doi = {10.1016/s0006-3495(97)78833-7}, ISSN = {0006-3495}, Owner = {paul}, Publisher = {Cell Press}, Refid = {S0006-3495(97)78833-7 DOI - 10.1016/S0006-3495(97)78833-7}, Timestamp = {2012.02.14} } @Article{Scomparin2009, Title = {Diffusion in supported lipid bilayers: Influence of substrate and preparation technique on the internal dynamics}, Author = {Scomparin, C. and Lecuyer, S. and Ferreira, M. and Charitat, T. and Tinland, B.}, Journal = {The European Physical Journal E: Soft Matter and Biological Physics}, Year = {2009}, Pages = {211-220}, Volume = {28}, Affiliation = {CNRS UPR 3118 CINAM 13288 Marseille Cedex 09 France}, Doi = {10.1140/epje/i2008-10407-3}, ISSN = {1292-8941}, Issue = {2}, Keyword = {Physik und Astronomie}, Owner = {paul}, Publisher = {Springer Berlin / Heidelberg}, Timestamp = {2012.10.22} } @Article{Seu2007, Title = {Effect of Surface Treatment on Diffusion and Domain Formation in Supported Lipid Bilayers}, Author = {Seu, Kalani J. and Pandey, Anjan P. and Haque, Farzin and Proctor, Elizabeth A. and Ribbe, Alexander E. and Hovis, Jennifer S.}, Journal = {Biophysical Journal}, Year = {2007}, Month = apr, Number = {7}, Pages = {2445--2450}, Volume = {92}, Doi = {10.1529/biophysj.106.099721}, ISSN = {0006-3495}, Owner = {paul}, Publisher = {Cell Press}, Refid = {S0006-3495(07)71049-4 DOI - 10.1529/biophysj.106.099721}, Timestamp = {2012.10.22} } @Article{Shannon1984, Title = {Communication in the presence of noise}, Author = {Shannon, C.E.}, Journal = {Proceedings of the IEEE}, Year = {1984}, Month = {sept.}, Number = {9}, Pages = { 1192 - 1201}, Volume = {72}, Doi = {10.1109/PROC.1984.12998}, ISSN = {0018-9219}, Owner = {paul}, Timestamp = {2012.11.12} } @Article{Skinner2005, Title = {Position-sensitive scanning fluorescence correlation spectroscopy.}, Author = {Joseph P Skinner and Yan Chen and Joachim D Müller}, Journal = {Biophysical Journal}, Year = {2005}, Month = {Aug}, Number = {2}, Pages = {1288--1301}, Volume = {89}, Abstract = {Fluorescence correlation spectroscopy (FCS) uses a stationary laser beam to illuminate a small sample volume and analyze the temporal behavior of the fluorescence fluctuations within the stationary observation volume. In contrast, scanning FCS (SFCS) collects the fluorescence signal from a moving observation volume by scanning the laser beam. The fluctuations now contain both temporal and spatial information about the sample. To access the spatial information we synchronize scanning and data acquisition. Synchronization allows us to evaluate correlations for every position along the scanned trajectory. We use a circular scan trajectory in this study. Because the scan radius is constant, the phase angle is sufficient to characterize the position of the beam. We introduce position-sensitive SFCS (PSFCS), where correlations are calculated as a function of lag time and phase. We present the theory of PSFCS and derive expressions for diffusion, diffusion in the presence of flow, and for immobilization. To test PSFCS we compare experimental data with theory. We determine the direction and speed of a flowing dye solution and the position of an immobilized particle. To demonstrate the feasibility of the technique for applications in living cells we present data of enhanced green fluorescent protein measured in the nucleus of COS cells.}, Doi = {10.1529/biophysj.105.060749}, Institution = {School of Physics and Astronomy, University of Minnesota, Minneapolis, 55455, USA. josephs@physics.umn.edu}, Keywords = {Algorithms; Image Enhancement, methods; Image Interpretation, Computer-Assisted, methods; Information Storage and Retrieval, methods; Microscopy, Confocal, methods; Reproducibility of Results; Sensitivity and Specificity; Spectrometry, Fluorescence, methods}, Language = {eng}, Medline-pst = {ppublish}, Owner = {paul}, Pii = {S0006-3495(05)72776-4}, Pmid = {15894645}, Timestamp = {2012.10.28} } @Article{Starr2001, Title = {Total Internal Reflection with Fluorescence Correlation Spectroscopy: Combined Surface Reaction and Solution Diffusion}, Author = {Tammy E. Starr and Nancy L. Thompson}, Journal = {Biophysical Journal}, Year = {2001}, Number = {3}, Pages = {1575 - 1584}, Volume = {80}, Doi = {10.1016/S0006-3495(01)76130-9}, ISSN = {0006-3495} } @Article{Sutherland1905, Title = {A dynamical theory of diffusion for non-electrolytes and the molecular mass of albumin}, Author = {Sutherland, William}, Journal = {Philosophical Magazine Series 6}, Year = {1905}, Number = {54}, Pages = {781-785}, Volume = {9}, __markedentry = {[paul]}, Doi = {10.1080/14786440509463331}, Owner = {paul}, Timestamp = {2012.11.14} } @Article{Tamm1985, Title = {Supported phospholipid bilayers}, Author = {Tamm, L.K. and McConnell, H.M.}, Journal = {Biophysical Journal}, Year = {1985}, Month = jan, Number = {1}, Pages = {105--113}, Volume = {47}, Doi = {10.1016/S0006-3495(85)83882-0}, ISSN = {0006-3495}, Owner = {paul}, Publisher = {Cell Press}, Refid = {S0006-3495(85)83882-0 DOI - 10.1016/S0006-3495(85)83882-0}, Timestamp = {2012.10.29} } @InCollection{Thomps:bookFCS2002, Title = {Fluorescence Correlation Spectroscopy}, Author = {Thompson, Nancy}, Booktitle = {Topics in Fluorescence Spectroscopy}, Publisher = {Springer US}, Year = {2002}, Editor = {Lakowicz, Joseph and Geddes, Chris D. and Lakowicz, Joseph R.}, Pages = {337-378}, Series = {Topics in Fluorescence Spectroscopy}, Volume = {1}, Affiliation = {University of North Carolina at Chapel Hill Department of Chemistry Chapel Hill North Carolina 27599-3290 USA}, Doi = {10.1007/0-306-47057-8_6}, ISBN = {978-0-306-47057-8}, Keyword = {Biomedical and Life Sciences}, Owner = {paul}, Timestamp = {2012.01.10} } @InBook{Thompson1991, Title = {Fluorescence Correlation Spectroscopy}, Author = {Thompson, N.L.}, Editor = {Lankowicz, J.R.}, Pages = {337--378}, Publisher = {Plenum Press}, Year = {1991}, Address = {New York}, Edition = {Techniques}, Series = {Topics in Fluorescence Spectroscopy}, Volume = {1}, Booktitle = {Topics in Fluorescence Spectroscopy}, Owner = {paul}, Timestamp = {2014.01.15} } @Article{Thompson1983, Title = {Immunoglobulin surface-binding kinetics studied by total internal reflection with fluorescence correlation spectroscopy}, Author = {N.L. Thompson and D. Axelrod}, Journal = {Biophysical Journal}, Year = {1983}, Number = {1}, Pages = {103 - 114}, Volume = {43}, Doi = {10.1016/S0006-3495(83)84328-8}, ISSN = {0006-3495}, Owner = {paul}, Timestamp = {2012.02.14} } @Article{Thompson1981, Title = {Measuring surface dynamics of biomolecules by total internal reflection fluorescence with photobleaching recovery or correlation spectroscopy}, Author = {N.L. Thompson and T.P. Burghardt and D. Axelrod}, Journal = {Biophysical Journal}, Year = {1981}, Number = {3}, Pages = {435 - 454}, Volume = {33}, Doi = {10.1016/S0006-3495(81)84905-3}, ISSN = {0006-3495}, Owner = {paul}, Timestamp = {2012.02.14} } @Article{Thompson1997, Title = {Equilibrium, Kinetics, Diffusion and Self-Association of Proteins at Membrane Surfaces: Measurement by Total Internal Reflection Fluorescence Microscopy}, Author = {Thompson, Nancy L. and Drake, Andrew W. and Chen, Lixin and Broek, Willem Vanden}, Journal = {Photochemistry and Photobiology}, Year = {1997}, Number = {1}, Pages = {39--46}, Volume = {65}, Doi = {10.1111/j.1751-1097.1997.tb01875.x}, ISSN = {1751-1097}, Owner = {paul}, Publisher = {Blackwell Publishing Ltd}, Timestamp = {2012.02.14} } @Article{Thompson1997a, Title = {Total internal reflection fluorescence: applications in cellular biophysics}, Author = {Nancy L Thompson and B Christoffer Lagerholm}, Journal = {Current Opinion in Biotechnology}, Year = {1997}, Number = {1}, Pages = {58 - 64}, Volume = {8}, Doi = {10.1016/S0958-1669(97)80158-9}, ISSN = {0958-1669}, Owner = {paul}, Timestamp = {2012.02.14} } @Article{Thompson2007, Title = {Total internal reflection with fluorescence correlation spectroscopy}, Author = {Thompson, N. L. and Steele, B. L.}, Journal = {Nat Protoc}, Year = {2007}, Number = {4}, Pages = {878--90}, Volume = {2}, Abstract = {Total internal reflection-fluorescence correlation spectroscopy (TIR-FCS) is an emerging technique that is used to measure events at or near an interface, including local fluorophore concentrations, local translational mobilities and the kinetic rate constants that describe the association and dissociation of fluorophores at the interface. TIR-FCS is also an extremely promising method for studying dynamics at or near the basal membranes of living cells. This protocol gives a general overview of the steps necessary to construct and test a TIR-FCS system using either through-prism or through-objective internal reflection geometry adapted for FCS. The expected forms of the autocorrelation function are discussed for the cases in which fluorescent molecules in solution diffuse through the depth of the evanescent field, but do not bind to the surface of interest, and in which reversible binding to the surface also occurs.}, Doi = {10.1038/nprot.2007.110}, Keywords = {Fluorescent Dyes/analysis Kinetics Ligands Spectrometry, Fluorescence/instrumentation/*methods}, Owner = {paul}, Timestamp = {2014.01.25} } @Article{Toomre2001, Title = {Lighting up the cell surface with evanescent wave microscopy}, Author = {Derek Toomre and Dietmar J. Manstein}, Journal = {Trends in Cell Biology}, Year = {2001}, Number = {7}, Pages = {298 - 303}, Volume = {11}, Doi = {10.1016/S0962-8924(01)02027-X}, ISSN = {0962-8924}, Keywords = {green-fluorescent protein (GFP)}, Owner = {paul}, Timestamp = {2012.02.14} } @Article{Unruh2008, Title = {Analysis of Molecular Concentration and Brightness from Fluorescence Fluctuation Data with an Electron Multiplied CCD Camera}, Author = {Unruh, Jay R. and Gratton, Enrico}, Journal = {Biophysical Journal}, Year = {2008}, Month = dec, Number = {11}, Pages = {5385--5398}, Volume = {95}, Doi = {10.1529/biophysj.108.130310}, ISSN = {0006-3495}, Owner = {paul}, Publisher = {Cell Press}, Refid = {S0006-3495(08)78962-8 DOI - 10.1529/biophysj.108.130310}, Timestamp = {2012.09.21} } @Article{Vacha2009, Title = {Effects of Alkali Cations and Halide Anions on the DOPC Lipid Membrane}, Author = {V\'{a}cha, Robert and Siu, Shirley W. I. and Petrov, Michal and Böckmann, Rainer A. and Barucha-Kraszewska, Justyna and Jurkiewicz, Piotr and Hof, Martin and Berkowitz, Max L. and Jungwirth, Pavel}, Journal = {The Journal of Physical Chemistry A}, Year = {2009}, Number = {26}, Pages = {7235-7243}, Volume = {113}, Doi = {10.1021/jp809974e}, Owner = {paul}, Timestamp = {2012.10.24} } @Article{Wachsmuth2000, Title = {Anomalous diffusion of fluorescent probes inside living cell nuclei investigated by spatially-resolved fluorescence correlation spectroscopy}, Author = {Wachsmuth, M. and Waldeck, W. and Langowski, J.}, Journal = {J Mol Biol}, Year = {2000}, Number = {4}, Pages = {677--89}, Volume = {298}, Abstract = {We have investigated spatial variations of the diffusion behavior of the green fluorescent protein mutant EGFP (F64L/S65T) and of the EGFP-beta-galactosidase fusion protein in living cells with fluorescence correlation spectroscopy. Our fluorescence correlation spectroscopy device, in connection with a precision x-y translation stage, provides submicron spatial resolution and a detection volume smaller than a femtoliter. The fluorescence fluctuations in cell lines expressing EGFP are caused by molecular diffusion as well as a possible internal and a pH-dependent external protonation process of the EGFP chromophore. The latter processes result in two apparent nonfluorescent states that have to be taken into account when evaluating the fluorescence correlation spectroscopy data. The diffusional contribution deviates from ideal behavior and depends on the position in the cell. The fluorescence correlation spectroscopy data can either be evaluated as a two component model with one fraction of the molecules undergoing free Brownian motion with a diffusion coefficient approximately five times smaller than in aqueous solution, and another fraction diffusing one or two orders of magnitude slower. This latter component is especially noticeable in the nuclei. Alternatively, we can fit the data to an anomalous diffusion model where the time dependence of the diffusion serves as a measure for the degree of obstruction, which is large especially in nuclei. Possible mechanisms for this long tail behavior include corralling, immobile obstacles, and binding with a broad distribution of binding affinities. The results are consistent with recent numerical models of the chromosome territory structure in the cell nucleus.}, Doi = {10.1006/jmbi.2000.3692}, Keywords = {Animals COS Cells Cell Line Cell Nucleus/chemistry/*metabolism Cell Survival Cytoplasm/chemistry/metabolism Diffusion Fluorescence Fluorescent Dyes/*metabolism Genetic Vectors/genetics Green Fluorescent Proteins Hydrogen-Ion Concentration Kinetics Luminescent Proteins/chemistry/genetics/metabolism Models, Biological Protein Conformation Protons Recombinant Fusion Proteins/chemistry/genetics/metabolism Solutions Spectrometry, Fluorescence Statistics Transfection}, Owner = {paul}, Timestamp = {2014.01.15} } @Article{Weidemann2013, Title = {Dual-color fluorescence cross-correlation spectroscopy with continuous laser excitation in a confocal setup}, Author = {Weidemann, T. and Schwille, P.}, Journal = {Methods Enzymol}, Year = {2013}, Pages = {43--70}, Volume = {518}, Abstract = {Fluorescence correlation spectroscopy evaluates local signal fluctuations arising from stochastic movements of fluorescent particles in solution. The measured fluctuating signal is correlated in time and analyzed with appropriate model functions containing the parameters that describe the underlying molecular behavior. The dual-color extension, fluorescence cross-correlation spectroscopy (FCCS) allows for a comparison between spectrally well-separated channels to extract codiffusion events that reflect interactions between differently labeled molecules. In addition to solution measurements, FCCS can be applied with subcellular resolution and is therefore a very promising approach for a quantitative biochemical assessment of molecular networks in living cells. To derive thermodynamic and kinetic reaction parameters, the influence of a number of other factors like background noise, illumination intensity profiles, photophysical processes, and cross talk between the channels have to be treated. Here, we provide a roadmap to derive binding reaction data with dual-color FCCS using continuous wave laser excitation, as it is now accessible with many state-of-the-art confocal microscopes.}, Doi = {10.1016/B978-0-12-388422-0.00003-0}, Owner = {paul}, Timestamp = {2014.01.15} } @Book{Weidemann2009, Title = {Fluorescence Correlation Spectroscopy in Living Cells}, Author = {Weidemann, T. and Schwille, P.}, Publisher = {Springer}, Year = {2009}, Address = {Heidelberg}, Series = {Handbook of Single-Molecule Biophysics}, Owner = {paul}, Pages = {648}, Timestamp = {2014.01.15} } @Article{Weidemann2002, Title = {Analysis of Ligand Binding by Two-Colour Fluorescence Cross-Correlation Spectroscopy}, Author = {Weidemann, T. and Wachsmuth, M. and Tewes, M and Rippe, K. and Langowski, J.}, Journal = {Single Mol}, Year = {2002}, Number = {1}, Pages = {49--61}, Volume = {3}, Abstract = {Fluorescence correlation spectroscopy (FCS) is a well-established method for the analysis of freely diffusing fluorescent particles in solution. In a two-colour setup, simultaneous detection of two different dyes allows the acquisition of both the autocorrelation of the signal of each channel and the cross-correlation of the two channels (fluorescence cross correlation spectroscopy, FCCS). The cross-correlation function is related to the amount of diffusing particles carrying both dyes and can be used for monitoring a binding reaction. Here we develop a formalism for a quantitative analysis of ligand binding from a combination of the auto- and the cross-correlation amplitudes. Technical constraints, like the focal geometry, background signal and cross-talk between the detection channels as well as photophysical and biochemical effects which modulate the brightness of the particles are included in the analysis. Based on this framework a comprehensive treatment for the determination of two-component binding equilibria by FCS/FCCS is presented.}, Doi = {10.1002/1438-5171(200204)3:1<49::aid-simo49>3.0.co;2-t}, Keywords = {FCS FCCS receptor-ligand binding protein-DNA interactions}, Owner = {paul}, Timestamp = {2014.01.15} } @Article{Weiss2003, Title = {Anomalous protein diffusion in living cells as seen by fluorescence correlation spectroscopy}, Author = {Weiss, M. and Hashimoto, H. and Nilsson, T.}, Journal = {Biophys J}, Year = {2003}, Number = {6}, Pages = {4043--4052}, Volume = {84}, Abstract = {We investigate the challenges and limitations that are encountered when studying membrane protein dynamics in vivo by means of fluorescence correlation spectroscopy (FCS). Based on theoretical arguments and computer simulations, we show that, in general, the fluctuating fluorescence has a fractal dimension D(0) >or= 1.5, which is determined by the anomality alpha of the diffusional motion of the labeled particles, i.e., by the growth of their mean square displacement as (Deltax)(2) approximately t(alpha). The fractality enforces an initial power-law behavior of the autocorrelation function and related quantities for small times. Using this information, we show by FCS that Golgi resident membrane proteins move subdiffusively in the endoplasmic reticulum and the Golgi apparatus in vivo. Based on Monte Carlo simulations for FCS on curved surfaces, we can rule out that the observed anomalous diffusion is a result of the complex topology of the membrane. The apparent mobility of particles as determined by FCS, however, is shown to depend crucially on the shape of the membrane and its motion in time. Due to this fact, the hydrodynamic radius of the tracked particles can be easily overestimated by an order of magnitude}, Doi = {10.1016/s0006-3495(03)75130-3}, Keywords = {Algorithms ARE Biology Biophysics Cell Membrane Cells Comparative Study COMPLEXES Computer Simulation CORRELATION SPECTROSCOPY Diffusion DYNAMICS Endoplasmic Reticulum Fluorescence FLUORESCENCE CORRELATION SPECTROSCOPY Fractals Germany Golgi Apparatus Hela Cells Humans IN-VIVO LIVING CELLS Membrane Proteins metabolism methods Microscopy,Confocal Models,Biological Molecular Biology Motion N-Acetylgalactosaminyltransferases physiology Protein Protein Transport Proteins Recombinant Fusion Proteins Research Support,Non-U.S.Gov't Spectrometry,Fluorescence spectroscopy Statistics SURFACE Tissue Distribution ultrastructure}, Owner = {paul}, Timestamp = {2014.01.15} } @Article{Widengren1995, Title = {Fluorescence correlation spectroscopy of triplet states in solution: a theoretical and experimental study}, Author = {Widengren, Jerker and Mets, {\"U}lo and Rigler, Rudolf}, Journal = {The Journal of Physical Chemistry}, Year = {1995}, Number = {36}, Pages = {13368-13379}, Volume = {99}, Doi = {10.1021/j100036a009}, Owner = {paul}, Timestamp = {2012.02.20} } @Article{Widengren1998, Title = {Fluorescence correlation spectroscopy as a tool to investigate chemical reactions in solutions and on cell surfaces}, Author = {Widengren, J. and Rigler, R.}, Journal = {Cell Mol Biol (Noisy-le-grand)}, Year = {1998}, Note = {Retrieved from \url{http://europepmc.org/abstract/MED/9764752}}, Number = {5}, Pages = {857--79}, Volume = {44}, Abstract = {Taking advantage of the present day possibilities for ultrasensitive detection by fluorescence, fluorescence correlation spectroscopy (FCS) has over the last ten years emerged as a potentially very powerful technique. In this article we present some results to illustrate the use of FCS for monitoring chemical kinetics on a molecular level and show how, for a wide range of chemical processes, the theoretical treatment can be strongly simplified. The experimental examples given include measurements of ion concentrations and buffer properties, electron transfer reactions, ligand-receptor interactions and diffusion of ligand-receptor complexes in cell membranes. For each of these examples the properties of the FCS technique is discussed in relation to other established techniques used for that particular application. From these examples it is found that FCS can offer important complementary information and, due to the extreme sensitivity of the technique, new information not yet explored by other methods.}, Owner = {paul}, Timestamp = {2014.01.15} } @Article{Widengren1994, Title = {Triplet-state monitoring by fluorescence correlation spectroscopy}, Author = {Widengren, Jerker and Rigler, Rudolf and Mets, {\"U}lo}, Journal = {Journal of Fluorescence}, Year = {1994}, Pages = {255-258}, Volume = {4}, Affiliation = {Department of Medical Biochemistry and Biophysics Karolinska Institute S-171 77 Stockholm Sweden}, Doi = {10.1007/BF01878460}, ISSN = {1053-0509}, Issue = {3}, Keyword = {Biomedizin & Life Sciences}, Owner = {paul}, Publisher = {Springer Netherlands}, Timestamp = {2012.09.24} } @Article{Wohland2001, Title = {The Standard Deviation in Fluorescence Correlation Spectroscopy}, Author = {Wohland, Thorsten and Rigler, Rudolf and Vogel, Horst}, Journal = {Biophysical Journal}, Year = {2001}, Month = jun, Number = {6}, Pages = {2987--2999}, Volume = {80}, Doi = {10.1016/S0006-3495(01)76264-9}, ISSN = {0006-3495}, Owner = {paul}, Timestamp = {2012.09.08} } @Article{Wohland2010, Title = {Single Plane Illumination Fluorescence Correlation Spectroscopy (SPIM-FCS) probes inhomogeneous three-dimensional environments}, Author = {Thorsten Wohland and Xianke Shi and Jagadish Sankaran and Ernst H.K. Stelzer}, Journal = {Optics Express}, Year = {2010}, Month = {May}, Number = {10}, Pages = {10627--10641}, Volume = {18}, Abstract = {The life sciences require new highly sensitive imaging tools, which allow the quantitative measurement of molecular parameters within a physiological three-dimensional (3D) environment. Therefore, we combined single plane illumination microscopy (SPIM) with camera based fluorescence correlation spectroscopy (FCS). SPIM-FCS provides contiguous particle number and diffusion coefficient images with a high spatial resolution in homo- and heterogeneous 3D specimens and live zebrafish embryos. Our SPIM-FCS recorded up to 4096 spectra within 56 seconds at a laser power of 60 \&\#x03BC;W without damaging the embryo. This new FCS modality provides more measurements per time and more, less photo-toxic measurements per sample than confocal based methods. In essence, SPIM-FCS offers new opportunities to observe biomolecular interactions quantitatively and functions in a highly multiplexed manner within a physiologically relevant 3D environment.}, Doi = {10.1364/OE.18.010627}, Keywords = {Fluorescence microscopy; Three-dimensional microscopy; Spectroscopy, fluorescence and luminescence}, Owner = {paul}, Publisher = {OSA}, Timestamp = {2012.11.07} } @Conference{Wright1996, Title = {Direct Search Methods: Once Scorned, Now Respectable}, Author = {Wright, M.H.}, Booktitle = {Numerical Analysis}, Year = {1996}, Editor = {D.F. Griffiths and G.A. Watson}, Pages = {191-208}, Publisher = {Addison Wesley Longman, Harlow, UK}, Owner = {paul}, Timestamp = {2014.03.31} } @Article{Yordanov2009, Title = {Direct studies of liquid flows near solid surfaces by total internal reflection fluorescence cross-correlation spectroscopy}, Author = {Stoyan Yordanov and Andreas Best and Hans-J\"{u}rgen Butt and Kaloian Koynov}, Journal = {Optics Express}, Year = {2009}, Month = {Nov}, Number = {23}, Pages = {21149--21158}, Volume = {17}, Abstract = {We present a new method to study flow of liquids near solid surface: Total internal reflection fluorescence cross-correlation spectroscopy (TIR-FCCS). Fluorescent tracers flowing with the liquid are excited by evanescent light, produced by epi-illumination through the periphery of a high numerical aperture oil-immersion objective. The time-resolved fluorescence intensity signals from two laterally shifted observation volumes, created by two confocal pinholes are independently measured. The cross-correlation of these signals provides information of the tracers' velocities. By changing the evanescent wave penetration depth, flow profiling at distances less than 200 nm from the interface can be performed. Due to the high sensitivity of the method fluorescent species with different size, down to single dye molecules can be used as tracers. We applied this method to study the flow of aqueous electrolyte solutions near a smooth hydrophilic surface and explored the effect of several important parameters, e.g. tracer size, ionic strength, and distance between the observation volumes.}, Doi = {10.1364/OE.17.021149}, Keywords = {Velocimetry; Fluorescence, laser-induced; Spectroscopy, surface}, Owner = {paul}, Publisher = {OSA}, Timestamp = {2012.09.21} } @Article{Yordanov2011, Title = {Note: An easy way to enable total internal reflection-fluorescence correlation spectroscopy ({TIR-FCS}) by combining commercial devices for {FCS} and {TIR} microscopy}, Author = {Stoyan Yordanov and Andreas Best and Klaus Weisshart and Kaloian Koynov}, Journal = {Review of Scientific Instruments}, Year = {2011}, Number = {3}, Pages = {036105}, Volume = {82}, Doi = {10.1063/1.3557412}, Eid = {036105}, Keywords = {fluorescence spectroscopy; optical microscopy}, Numpages = {3}, Owner = {paul}, Publisher = {AIP}, Timestamp = {2012.05.02} } @Article{Zhang2007, Title = {Gaussian approximations of fluorescence microscope point-spread function models}, Author = {Bo Zhang and Josiane Zerubia and Jean-Christophe Olivo-Marin}, Journal = {Applied Optics}, Year = {2007}, Month = {Apr}, Number = {10}, Pages = {1819--1829}, Volume = {46}, Abstract = {We comprehensively study the least-squares Gaussian approximations of the diffraction-limited 2D-3D paraxial-nonparaxial point-spread functions (PSFs)of the wide field fluorescence microscope (WFFM), the laser scanning confocal microscope(LSCM), and the disk scanning confocal microscope (DSCM). The PSFs are expressed using the Debye integral. Under anL$\infty$ constraint imposing peak matching, optimal and near-optimal Gaussian parameters are derived for the PSFs. With anL1 constraint imposing energy conservation, an optimal Gaussian parameter is derived for the 2D paraxial WFFM PSF. We found that (1) the 2D approximations are all very accurate; (2) no accurate Gaussian approximation exists for 3D WFFM PSFs; and (3) with typical pinhole sizes, the 3D approximations are accurate for the DSCM and nearly perfect for the LSCM. All the Gaussian parameters derived in this study are in explicit analytical form, allowing their direct use in practical applications.}, Doi = {10.1364/AO.46.001819}, Keywords = {Numerical approximation and analysis; Microscopy; Confocal microscopy; Fluorescence microscopy; Three-dimensional microscopy}, Owner = {paul}, Publisher = {OSA}, Timestamp = {2012.09.20} } @Book{Rigler:FCSbook, Title = {Fluorescence Correlation Spectroscopy, Theory and Applications}, Editor = {R. Rigler and E.S. Elson}, Publisher = {Springer Berlin Heidelberg}, Year = {2001}, Edition = {1}, HowPublished = {Paperback}, ISBN = {978-3540674337}, Owner = {paul}, Timestamp = {2012.11.02} } pycorrfit-1.1.7/doc/PyCorrFit_doc_models.tex0000664000372000037200000004204213554642611021754 0ustar travistravis00000000000000\section{Implemented model functions} \label{sec:imple} This is an overview of all the model functions that are implemented in \textit{PyCorrFit}. To each model a unique model ID is assigned. Most of the following information is also accessible from within \textit{PyCorrFit} using the \textit{Page info} tool. \subsection{Confocal FCS} \label{sec:imple.confo} % 2D diffusion %\noindent \begin{tabular}{lp{.7\textwidth}} %Name & \textbf{2D (Gauß)} \\ %ID & \textbf{6001} \\ %Descr. & Two-dimensional diffusion with a Gaussian laser profile\cite{Aragon1976, Qian1991, Rigler1993}. \\ %\end{tabular} %\begin{align} %G(\tau) = A_0 + \frac{1}{N} \frac{1}{(1+\tau/\tau_\mathrm{diff})} %\end{align} %\begin{center} %\begin{tabular}{ll} %$A_0$ & Offset \\ %$N$ & Effective number of particles in confocal area \\ %$\tau_\mathrm{diff}$ & Characteristic residence time in confocal area \\ %\end{tabular} \\ %\end{center} %\vspace{2em} % 3D diffusion %\noindent \begin{tabular}{lp{.7\textwidth}} %Name & \textbf{3D (Gauß)} \\ %ID & \textbf{6012} \\ %Descr. & Three-dimensional free diffusion with a Gaussian laser profile (eliptical)\cite{Aragon1976, Qian1991, Rigler1993}. \\ %\end{tabular} %\begin{align} %G(\tau) = A_0 + \frac{1}{N} \frac{1}{(1+\tau/\tau_\mathrm{diff})} \frac{1}{\sqrt{1+\tau/(\mathit{SP}^2 \tau_\mathrm{diff})}} %\end{align} %\begin{center} %\begin{tabular}{ll} %$A_0$ & Offset \\ %$N$ & Effective number of particles in confocal volume \\ %$\tau_\mathrm{diff}$ & Characteristic residence time in confocal volume \\ %$\mathit{SP}$ & Structural parameter, describes elongation of the confocal volume \\ %\end{tabular} %\end{center} %\vspace{2em} % 3D diffusion + triplet \noindent \begin{tabular}{lp{.7\textwidth}} Name & \textbf{Confocal (Gaussian) T+3D} \\ ID & \textbf{6011} \\ Descr. & Three-dimensional free diffusion with a Gaussian laser profile (eliptical), including a triplet component\cite{Widengren1994, Widengren1995, Haupts1998}. \\ \end{tabular} \begin{align} G(\tau) = A_0 + \frac{1}{n} \frac{1}{(1+\tau/\tau_\mathrm{diff})} \frac{1}{\sqrt{1+\tau/(\mathit{SP}^2 \tau_\mathrm{ diff})}} \left(1 + \frac{T e^{-\tau/\tau_\mathrm{trip}}}{1-T} \right) \end{align} \begin{center} \begin{tabular}{ll} $A_0$ & Offset \\ $n$ & Effective number of particles in confocal volume \\ $\tau_\mathrm{diff}$ & Characteristic residence time in confocal volume \\ $\mathit{SP}$ & Structural parameter, describes elongation of the confocal volume \\ $T$ & Fraction of particles in triplet (non-fluorescent) state\\ $\tau_\mathrm{trip}$ & Characteristic residence time in triplet \\ \end{tabular} \end{center} \vspace{2em} % 3D+3D diffusion + triplett \noindent \begin{tabular}{lp{.7\textwidth}} Name & \textbf{Confocal (Gaussian) T+3D+3D} \\ ID & \textbf{6030} \\ Descr. & Two-component three-dimensional free diffusion with a Gaussian laser profile, including a triplet component\cite{Elson1974, Aragon1976, Palmer1987}. \\ \end{tabular} \begin{align} G(\tau) &= A_0 + \frac{1}{n (F + \alpha (1-F))²} \left(1 + \frac{T e^{-\tau/\tau_\mathrm{trip}}}{1-T} \right) \times \\ \notag &\times \left[ \frac{F}{(1+\tau/\tau_1)} \frac{1}{\sqrt{1+\tau/(\mathit{SP}^2 \tau_1)}} + \alpha^2 \frac{1-F}{ (1+\tau/\tau_2) } \frac{1}{\sqrt{1+\tau/(\mathit{SP}^2 \tau_2)}} \right] \end{align} \begin{center} \begin{tabular}{ll} $A_0$ & Offset \\ $n$ & Effective number of particles in confocal volume ($n = n_1+n_2$) \\ $\tau_1$ & Diffusion time of particle species 1 \\ $\tau_2$ & Diffusion time of particle species 2 \\ $F$ & Fraction of molecules of species 1 ($n_1 = F n$) \\ $\alpha$ & Relative molecular brightness of particles 1 and 2 ($ \alpha = q_2/q_1$) \\ $\mathit{SP}$ & Structural parameter, describes elongation of the confocal volume \\ $T$ & Fraction of particles in triplet (non-fluorescent) state\\ $\tau_\mathrm{trip}$ & Characteristic residence time in triplet state \\ \end{tabular} \end{center} \vspace{2em} % 2D diffusion + triplett \noindent \begin{tabular}{lp{.7\textwidth}} Name & \textbf{Confocal (Gaussian) T+2D} \\ ID & \textbf{6002} \\ Descr. & Two-dimensional diffusion with a Gaussian laser profile, including a triplet component\cite{Aragon1976, Qian1991, Rigler1993,Widengren1994, Widengren1995, Haupts1998}. \\ \end{tabular} \begin{align} G(\tau) = A_0 + \frac{1}{n} \frac{1}{(1+\tau/\tau_\mathrm{diff})} \left(1 + \frac{T e^{-\tau/\tau_\mathrm{trip}}}{1-T} \right) \end{align} \begin{center} \begin{tabular}{ll} $A_0$ & Offset \\ $n$ & Effective number of particles in confocal area \\ $\tau_\mathrm{diff}$ & Characteristic residence time in confocal area \\ $T$ & Fraction of particles in triplet (non-fluorescent) state\\ $\tau_\mathrm{trip}$ & Characteristic residence time in triplet state \\ \end{tabular} \end{center} \vspace{2em} % 2D+2D diffusion + triplett \noindent \begin{tabular}{lp{.7\textwidth}} Name & \textbf{Confocal (Gaussian) T+2D+2D} \\ ID & \textbf{6031} \\ Descr. & Two-component, two-dimensional diffusion with a Gaussian laser profile, including a triplet component\cite{Elson1974, Aragon1976, Palmer1987}. \\ \end{tabular} \begin{align} G(\tau) = A_0 + \frac{1}{n (F + \alpha (1-F))²} \left[ \frac{F}{1+\tau/\tau_1} + \alpha^2 \frac{1-F}{ 1+\tau/\tau_2 } \right] \left(1 + \frac{T e^{-\tau/\tau_\mathrm{trip}}}{1-T} \right) \end{align} \begin{center} \begin{tabular}{ll} $A_0$ & Offset \\ $n$ & Effective number of particles in confocal area ($n = n_1+n_2$) \\ $\tau_1$ & Diffusion time of particle species 1 \\ $\tau_2$ & Diffusion time of particle species 2 \\ $F$ & Fraction of molecules of species 1 ($n_1 = F n$) \\ $\alpha$ & Relative molecular brightness of particles 1 and 2 ($ \alpha = q_2/q_1$) \\ $T$ & Fraction of particles in triplet (non-fluorescent) state\\ $\tau_\mathrm{trip}$ & Characteristic residence time in triplet state \\ \end{tabular} \end{center} \vspace{2em} % 3D+2D diffusion + triplett \noindent \begin{tabular}{lp{.7\textwidth}} Name & \textbf{Confocal (Gaussian) T+3D+2D} \\ ID & \textbf{6032} \\ Descr. & Two-component, two- and three-dimensional diffusion with a Gaussian laser profile, including a triplet component\cite{Elson1974, Aragon1976, Palmer1987}. \\ \end{tabular} \begin{align} G(\tau) = A_0 + \frac{1}{n (1 - F + \alpha F)²} \left[ \frac{1-F}{1+\tau/\tau_\mathrm{2D}} + \frac{ \alpha^2 F}{ (1+\tau/\tau_\mathrm{3D}) } \frac{1}{\sqrt{1+\tau/(\mathit{SP}^2 \tau_\mathrm{3D})}} \right] \left(1 + \frac{T e^{-\tau/\tau_\mathrm{trip}}}{1-T} \right) \end{align} \begin{center} \begin{tabular}{ll} $A_0$ & Offset \\ $n$ & Effective number of particles in confocal volume ($n = n_\mathrm{2D}+n_\mathrm{3D}$) \\ $\tau_\mathrm{2D}$ & Diffusion time of surface bound particles \\ $\tau_\mathrm{3D}$ & Diffusion time of freely diffusing particles \\ $F$ & Fraction of molecules of the freely diffusing species ($n_\mathrm{3D} = F n$) \\ $\alpha$ & Relative molecular brightness of particle species ($ \alpha = q_\mathrm{3D}/q_\mathrm{2D}$) \\ $\mathit{SP}$ & Structural parameter, describes elongation of the confocal volume \\ $T$ & Fraction of particles in triplet (non-fluorescent) state\\ $\tau_\mathrm{trip}$ & Characteristic residence time in triplet state \\ \end{tabular} \end{center} \vspace{2em} % 3D+3D diffusion + T+T \noindent \begin{tabular}{lp{.7\textwidth}} Name & \textbf{Confocal (Gaussian) T+T+3D+3D} \\ ID & \textbf{6043} \\ Descr. & Two-component three-dimensional free diffusion with a Gaussian laser profile, including two triplet components. The correlation function is a superposition of three-dimensional model functions of the type \textbf{T+3D} (6011). \end{tabular} \vspace{2em} \subsection{TIR-FCS} \label{sec:imple.tirfc} The model functions make use of the Faddeeva function (complex error function)\footnote{In user-defined model functions (\hyref{Section}{sec:hacke.extmod}), the Faddeeva function is accessible through \texttt{wofz()}. For convenience, the function \texttt{wixi()} can be used which only takes $\xi$ as an argument and the imaginary $i$ can be omitted.}: \begin{align} w\!(i\xi) &= e^{\xi^2} \mathrm{erfc}(\xi) \\ \notag &= e^{\xi^2} \cdot \frac{2}{\sqrt{\pi}} \int_\xi^\infty \mathrm{e}^{-\alpha^2} \mathrm{d\alpha} \label{eq:faddeeva} \end{align} The lateral detection area has the same shape as in confocal FCS. Thus, correlation functions for two-dimensional diffusion of the confocal case apply and are not mentioned here. \subsubsection{TIR-FCS with Gaussian-shaped lateral detection volume} % 3D diffusion (Gauß/exp) \noindent \begin{tabular}{lp{.7\textwidth}} Name & \textbf{TIR (Gaussian/Exp.) T+3D} \\ ID & \textbf{6014} \\ Descr. & Three-dimensional free diffusion with a Gaussian lateral detection profile and an exponentially decaying profile in axial direction, including a triplet component\cite{Starr2001, Hassler2005, Ohsugi2006}. \\ \end{tabular} \begin{align} G(\tau) = \frac{1}{C} \frac{ \kappa^2}{ \pi (R_0^2 +4D\tau)} \left(1 + \frac{T e^{-\tau/\tau_\mathrm{trip}}}{1-T} \right) \left( \sqrt{\frac{D \tau}{\pi}} + \frac{1 - 2 D \tau \kappa^2}{2 \kappa} w\!\left(i \sqrt{D \tau} \kappa\right) \right) \end{align} \begin{center} \begin{tabular}{ll} $C$ & Particle concentration in confocal volume \\ $\kappa$ & Evanescent decay constant ($\kappa = 1/d_\mathrm{eva}$)\\ $R_0$ & Lateral extent of the detection volume \\ $D$ & Diffusion coefficient \\ $T$ & Fraction of particles in triplet (non-fluorescent) state\\ $\tau_\mathrm{trip}$ & Characteristic residence time in triplet state \\ \end{tabular} \end{center} \vspace{2em} % 3D+3D+T diffusion (Gauß/exp) \noindent \begin{tabular}{lp{.7\textwidth}} Name & \textbf{TIR (Gaussian/Exp.) T+3D+3D} \\ ID & \textbf{6034} \\ Descr. & Two-component three-dimensional diffusion with a Gaussian lateral detection profile and an exponentially decaying profile in axial direction, including a triplet component\cite{Starr2001, Hassler2005, Ohsugi2006}. \\ \end{tabular} \begin{align} G(\tau) = &A_0 + \frac{1}{n (1-F + \alpha F)^2} \left(1 + \frac{T e^{-\tau/\tau_\mathrm{trip}}}{1-T} \right) \times \\ \notag \times \Bigg[ \,\, & \frac{F \kappa}{1+ 4 D_1 \tau/R_0^2} \left( \sqrt{\frac{D_1 \tau}{\pi}} + \frac{1 - 2 D_1 \tau \kappa^2}{2 \kappa} w\!\left(i \sqrt{D_1 \tau} \kappa\right) \right) + \\ \notag + & \frac{(1-F) \alpha^2 \kappa}{1+ 4 D_2 \tau/R_0^2} \left( \sqrt{\frac{D_2 \tau}{\pi}} + \frac{1 - 2 D_2 \tau \kappa^2}{2 \kappa} w\!\left(i \sqrt{D_2 \tau} \kappa\right) \right) \,\, \Bigg] \end{align} \begin{center} \begin{tabular}{ll} $A_0$ & Offset \\ $n$ & Effective number of particles in confocal volume ($n = n_1+n_2$) \\ $D_1$ & Diffusion coefficient of species 1 \\ $D_2$ & Diffusion coefficient of species 2 \\ $F$ & Fraction of molecules of species 1 ($n_1 = F n$) \\ $\alpha$ & Relative molecular brightness of particle species ($ \alpha = q_2/q_1$) \\ $R_0$ & Lateral extent of the detection volume \\ $\kappa$ & Evanescent decay constant ($\kappa = 1/d_\mathrm{eva}$)\\ $T$ & Fraction of particles in triplet (non-fluorescent) state\\ $\tau_\mathrm{trip}$ & Characteristic residence time in triplet state \\ \end{tabular} \end{center} \vspace{2em} % 2D+3D+T diffusion (Gauß/exp) \noindent \begin{tabular}{lp{.7\textwidth}} Name & \textbf{TIR (Gaussian/Exp.) T+3D+2D} \\ ID & \textbf{6033} \\ Descr. & Two-component, two- and three-dimensional diffusion with a Gaussian lateral detection profile and an exponentially decaying profile in axial direction, including a triplet component\cite{Starr2001, Hassler2005, Ohsugi2006}. \\ \end{tabular} \begin{align} G(\tau) &= A_0 + \frac{1}{n (1-F + \alpha F)^2} \left(1 + \frac{T e^{-\tau/\tau_\mathrm{trip}}}{1-T} \right) \times \\ & \notag \times \left[ \frac{1-F}{1+ 4 D_\mathrm{2D} \tau/R_0^2} + \frac{\alpha^2 F \kappa}{1+ 4 D_\mathrm{3D} \tau/R_0^2} \left( \sqrt{\frac{D_\mathrm{3D} \tau}{\pi}} + \frac{1 - 2 D_\mathrm{3D} \tau \kappa^2}{2 \kappa} w\!\left(i \sqrt{D_\mathrm{3D} \tau} \kappa\right) \right) \right] \end{align} \begin{center} \begin{tabular}{ll} $A_0$ & Offset \\ $n$ & Effective number of particles in confocal volume ($n = n_\mathrm{2D}+n_\mathrm{3D}$) \\ $D_\mathrm{2D}$ & Diffusion coefficient of surface bound particles \\ $D_\mathrm{3D}$ & Diffusion coefficient of freely diffusing particles \\ $F$ & Fraction of molecules of the freely diffusing species ($n_\mathrm{3D} = F n$) \\ $\alpha$ & Relative molecular brightness of particle species ($ \alpha = q_\mathrm{3D}/q_\mathrm{2D}$) \\ $R_0$ & Lateral extent of the detection volume \\ $\kappa$ & Evanescent decay constant ($\kappa = 1/d_\mathrm{eva}$)\\ $T$ & Fraction of particles in triplet (non-fluorescent) state\\ $\tau_\mathrm{trip}$ & Characteristic residence time in triplet state \\ \end{tabular} \end{center} \vspace{2em} \subsubsection{TIR-FCS with a square-shaped lateral detection volume} % 3D TIRF diffusion (□xσ) \noindent \begin{tabular}{lp{.7\textwidth}} Name & \textbf{TIR (□x$\upsigma$/Exp.) 3D} \\ ID & \textbf{6010} \\ Descr. & Three-dimensional diffusion with a square-shaped lateral detection area taking into account the size of the point spread function; and an exponential decaying profile in axial direction\cite{Ries2008, Yordanov2011}. \\ \end{tabular} \begin{align} G(\tau) = \frac{\kappa^2}{C} & \left( \sqrt{\frac{D \tau}{\pi}} + \frac{1 - 2 D \tau \kappa^2)}{2 \kappa} w\!\left(i \sqrt{D \tau} \kappa\right) \right) \times \\ \notag \times \Bigg[ & \frac{2 \sqrt{\sigma^2+D \tau}}{\sqrt{\pi} a^2} \left( \exp\left(-\frac{a^2}{4(\sigma^2+D \tau)}\right) - 1 \right) + \frac{1}{a} \, \mathrm{erf}\left(\frac{a}{2 \sqrt{\sigma^2+D \tau}}\right) \Bigg]^2 \end{align} \begin{center} \begin{tabular}{ll} $C$ & Particle concentration in detection volume \\ $\sigma$ & Lateral size of the point spread function \\ $a$ & Side size of the square-shaped detection area \\ $\kappa$ & Evanescent decay constant ($\kappa = 1/d_\mathrm{eva}$)\\ $D$ & Diffusion coefficient \\ \end{tabular} \\ \end{center} \vspace{2em} % 3D+3D TIRF diffusion (□xσ) \noindent \begin{tabular}{lp{.7\textwidth}} Name & \textbf{TIR (□x$\upsigma$/Exp.) 3D+3D} \\ ID & \textbf{6023} \\ Descr. & Two-component three-dimensional free diffusion with a square-shaped lateral detection area taking into account the size of the point spread function; and an exponential decaying profile in axial direction. \newline The correlation function is a superposition of three-dimensional model functions of the type \textbf{3D (□x$\upsigma$)} (6010)\cite{Ries2008, Yordanov2011}. \\ \end{tabular} \vspace{2em} % 2D TIRF diffusion (□xσ) \noindent \begin{tabular}{lp{.7\textwidth}} Name & \textbf{TIR (□x$\upsigma$) 2D} \\ ID & \textbf{6000} \\ Descr. & Two-dimensional diffusion with a square-shaped lateral detection area taking into account the size of the point spread function\cite{Ries2008, Yordanov2011}\footnote{The reader is made aware, that reference \cite{Ries2008} contains several unfortunate misprints.}. \\ \end{tabular} \begin{align} G(\tau) = \frac{1}{C} \left[ \frac{2 \sqrt{\sigma^2+D \tau}}{\sqrt{\pi} a^2} \left( \exp\left(-\frac{a^2}{4(\sigma^2+D \tau)}\right) - 1 \right) + \frac{1}{a} \, \mathrm{erf}\left(\frac{a}{2 \sqrt{\sigma^2+D \tau}}\right) \right]^2 \end{align} \begin{center} \begin{tabular}{ll} $C$ & Particle concentration in detection area \\ $\sigma$ & Lateral size of the point spread function \\ $a$ & Side size of the square-shaped detection area \\ $D$ & Diffusion coefficient \\ \end{tabular} \\ \end{center} \vspace{2em} % 2D+2D TIRF diffusion (□xσ) \noindent \begin{tabular}{lp{.7\textwidth}} Name & \textbf{TIR (□x$\upsigma$) 2D+2D} \\ ID & \textbf{6022} \\ Descr. & Two-component two-dimensional diffusion with a square-shaped lateral detection area taking into account the size of the point spread function. \newline The correlation function is a superposition of two-dimensional model functions of the type \textbf{2D (□x$\upsigma$)} (6000)\cite{Ries2008, Yordanov2011}. \\ \end{tabular} \vspace{2em} % 3D+2D TIRF diffusion (□xσ) \noindent \begin{tabular}{lp{.7\textwidth}} Name & \textbf{TIR (□x$\upsigma$/Exp.) 3D+2D} \\ ID & \textbf{6020} \\ Descr. & Two-component two- and three-dimensional diffusion with a square-shaped lateral detection area taking into account the size of the point spread function; and an exponential decaying profile in axial direction. \newline The correlation function is a superposition of the two-dimensional model function \textbf{2D (□x$\upsigma$)} (6000) and the three-dimensional model function \textbf{3D (□x$\upsigma$)} (6010)\cite{Ries2008, Yordanov2011}. \end{tabular} \vspace{2em} % 3D+2D+kin TIRF diffusion (□xσ) \noindent \begin{tabular}{lp{.7\textwidth}} Name & \textbf{TIR (□x$\upsigma$/Exp.) 3D+2D+kin} \\ ID & \textbf{6021} \\ Descr. & Two-component two- and three-dimensional diffusion with a square-shaped lateral detection area taking into account the size of the point spread function; and an exponential decaying profile in axial direction. This model covers binding and unbinding kintetics. \newline The correlation function for this model was introduced in \cite{Ries2008}. Because approximations are made in the derivation, please verify if this model is applicable to your problem before using it. \end{tabular} pycorrfit-1.1.7/doc/PyCorrFit_doc_content.tex0000775000372000037200000022536313554642611022157 0ustar travistravis00000000000000\section{Introduction} \label{sec:intro} \subsection{Preface} \label{sec:intro.prefa} \textit{PyCorrFit} emerged from my work in the Schwille Lab\footnote{\url{http://www.biochem.mpg.de/en/rd/schwille/}} at the Biotechnology Center of the TU Dresden in 2011/2012. Since then, the program has been further developed based on numerous input from FCS users, in particular Franziska Thomas, Grzesiek Chwastek, Janine Tittel, and Thomas Weidemann. The program source code is available at GitHub\footnote{\url{https://github.com/FCS-analysis/PyCorrFit}}. Please do not hesitate to sign up and add a feature request. If you you find a bug, please let us know via GitHub.\\ \noindent \textit{PyCorrFit} was written to simplify the work with experimentally obtained correlation curves. These can be processed independently (operating system, location, time). PyCorrFit supports commonly used file formats and enables users to allocate and organize their data in a simple way.\\ \noindent \textit{PyCorrFit} is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 2 of the License, or (at your option) any later version\footnote{\url{http://www.gnu.org/licenses/gpl.html}}. \subsubsection*{What \textit{PyCorrFit} can do} \begin{itemize} \item Load correlation curves from numerous correlators \item Process these curves \item Fit a model function (many included) to an experimental curve \item Import user defined models for fitting \item Many batch processing features \item Save/load entire \textit{PyCorrFit} sessions \item \textbf{\LaTeX} support for data export \end{itemize} \subsubsection*{What \textit{PyCorrFit} is not} \begin{itemize} \item A multiple-$\tau$ correlator \item A software to operate hardware correlators \end{itemize} \subsection{System prerequisites} \label{sec:intro.prere} \subsubsection{Hardware} \label{sec:intro.prere.hardw} This documentation addresses the processing of correlation curves with \textit{PyCorrFit} and was successfully used with the following setups: \begin{itemize} \item[1.] APD: Photon Counting Device from PerkinElmer Optoelectronics, Model: \texttt{SPCM-CD3017}\\ Correlator: Flex02-01D/C from correlator.com with the shipped software \texttt{flex02-1dc.exe}. \item[2.] APD: Photon Counting Device from PerkinElmer Optoelectronics\\ Correlator: ALV-6000 \item[3.] LSM Confocor2 or Confocor3 setups from Zeiss, Germany. \end{itemize} \subsubsection{Software} \label{sec:intro.prere.softw} The latest version of \textit{PyCorrFit} can be obtained from the internet at \url{http://pycorrfit.craban.de}. \begin{itemize} \item \textbf{MacOS X.} Binary files for MacOS X 10.9.5 and later are available from the download page. \item \textbf{Windows.} For Windows XP and later, stand-alone binary executables are available from the download page. \item \textbf{Ubuntu/Debian.} PyCorrFit is available from the Debian repositories and can be installed via the operating systems packaging tool (e.g. \texttt{apt-get install pycorrfit}). \item\textbf{PyPI.} To run \textit{PyCorrFit} on any other operating system, the installation of Python v.2.7 is required. \textit{PyCorrFit} is included in the Python package index (PyPI, \url{http://pypi.python.org/pypi/pip}) and can be installed via\footnote{See also the wiki article at \url{https://github.com/FCS-analysis/PyCorrFit/wiki/Installation_pip}} \texttt{pip~install~pycorrfit$\!$[GUI]}. \item \textbf{Sources.} You can also directly download the source code at any developmental stage\footnote{See also the wiki article at \url{https://github.com/FCS-analysis/PyCorrFit/wiki/Running-from-source}}. \end{itemize} \vspace{1em} \noindent \textbf{\LaTeX .} \textit{PyCorrFit} can save correlation curves as images using matplotlib. It is also possible to utilize \LaTeX to generate these plots. On Windows, installing MiKTeX with ``automatic package download'' will enable this feature. On MacOS X, the MacTeX distribution can be used. On other systems, a latex distribution, Ghostscript, \texttt{dvipng} and the latex packages \texttt{texlive-latex-base} and \texttt{texlive-math-extra} need to be installed. \subsection{Workflow} \label{sec:intro.workf} The following chapter introduces the general idea of how to start and accomplish a fitting project. FCS experiments produce different sets of experimental correlation functions which must be interpreted with appropriate physical models (\hyref{Chapter}{sec:theor}). Each correlation function refers to a single contiguous signal trace or ``run''. In \textit{PyCorrFit}, the user must assign a mathematical model function to each correlation function during the loading procedure. The assignment is irreversible in the sense that within an existing \textit{PyCorrFit} session it cannot be changed. This feature assures the stability of the batch processing routine for automated fitting of large data sets. Nevertheless, the fit of different models to the same data can be explored by loading the data twice or simply by creating two different sessions. Let's briefly discuss a typical example: To determine the diffusion coefficient of a fluorescently labeled protein in free solution, one has to deal with two sets of autocorrelation data: measurements of a diffusion standard (e.g. free dye for which a diffusion coefficient has been published) to calibrate the detection volume and measurements of the protein sample. The protein sample may contain small amounts of slowly diffusing aggregates. While the calibration measurements can be fitted with a one-component diffusion model (T+3D), the protein sample displays two mobility states, monomers and aggregates, which are taken into account by a two-component diffusion model (T+3D+3D). With \textit{PyCorrFit} such a situation can be treated in three ways, having different pros and cons: \begin{enumerate} \item Create separate sessions for each type of sample and assign different model functions. \item Assign a one-component model to the dye measurements and a two-component model to the protein measurements when loading consecutively into the same session. \item Assign a two-component model for all data and, when appropriate, manually inactivate one component by fixing its contribution to 0\%. \end{enumerate} The first approach is straightforward, however, it requires homogeneous diffusion behavior for each data set. The second strategy has the advantage that the dye and the protein curves, as well as the obtained parameters, can be visually compared during the fitting analysis within the same session. In this case, batch fitting is still possible because it discriminates data sets assigned to different models. In the third case, simultaneous batch fitting is also possible. However, for each dye measurement one has to eliminate the second, slow diffusion species manually, which might be laborious. Inactivating components by fixing parameters is nevertheless a common way to evaluate heterogeneous data sets, for example, a protein sample for which only a subgroup of curves requires a second diffusion component due to occasional appearance of aggregates. Such situations are frequently encountered in intracellular measurements. In conclusion, all three strategies or combinations thereof may be suitable. In any case, the user must decide on model functions beforehand, therefore it is advisable to group the data accordingly. The fitting itself is usually explored with a representative data set. Here, the user has to decide on starting parameters, the range in which they should be varied, corrections like background, and other fitting options. Once the fit looks good, the chosen settings can be transferred at once to all other pages assigned to the same model using the \textit{Batch control} tool (\hyref{Section}{sec:menub.tools.batch}). After flipping through the data for visual inspection one may check the parameters across all pages in the \textit{Statistics view} tool and re-visit outliers (\hyref{Section}{sec:menub.tools.stati}). From there, the numerical fit values and example correlation functions can be exported. \subsection{Graphical user interface (GUI)} \label{sec:intro.graph} \begin{figure}[h] \centering \includegraphics[width=\linewidth]{PyCorrFit_Screenshot_Main.png} \mycaption{user interface of PyCorrFit}{Confocal measurement of nanomolar Alexa488 in aqueous solution. To avoid after-pulsing, the autocorrelation curve was measured by cross-correlating signals from two detection channels using a 50 \% beamsplitter. Fitting reveals the average number of observed particles ($n \approx 6$) and their residence time in the detection volume ($\tau_{\mathrm{diff}} = \SI{28}{\mu s})$. \label{fig:mainwin} } \end{figure} Together with a system's terminal of the platform on which \textit{PyCorrFit} was installed (Windows, Linux, MacOS X), the \textit{main window} opens when starting the program. The window title bar contains the version of \textit{PyCorrFit} and, if a session was re-opened or saved, the name of the fitting session. A menu bar provides access to many supporting tools and additional information as thoroughly described in \hyref{Chapter}{sec:menub}. There are three gateways for experimental data into a pre-existing or a new \textit{PyCorrFit} session (\textit{File/Load data}, \textit{File/Open session}, and \textit{Current page/Import data}). When a session has been opened or correlation data have been loaded, each correlation curve is displayed on a separate page of a notebook. For quick identification of the active data set, a tab specifies the page number, the correlated channels (AC/CC), and the run number in cases where multiple correlation runs are combined in a single file. When clicking a little triangle to the far-right, one can use a drop-down list of all page titles to directly access a particular data set. Alternatively, the pages can be toggled by tapping the arrow keys (left/right). There can be only one activated page for which the tab appears highlighted. The active page displaying a correlation function is divided in two panels (\hyref{Figure}{fig:mainwin}). At the left hand side the page shows a pile of boxes containing values or fitting options associated with the current model and data set: \begin{itemize} \item \textit{Data set} specifies the assigned model abbreviation in parentheses and shows a unique identifier for the correlation curve containing the file name, the number of the ``run'', and the data channel. This string is automatically assembled during the loading procedure (\hyref{Section}{sec:menub.filem.loadd}). However, during the session it can be manually edited, thereby allowing to re-name or flag certain data during the fitting analysis. \item \textit{Model parameters} displays the values which determine the current shape of the assigned model function (\hyref{Chapter}{sec:theor}). Initially, starting values are loaded as they were defined in the model description (\hyref{Section}{sec:menub.filem.impor}). Little buttons allow a stepwise increase or decrease in units of 1/10\textsuperscript{th}. It is also possible to directly enter some numbers. A checkbox is used to set the parameter status to ``varied'' (checked) or ``fixed'' (unchecked) during the fitting. At the end, when saving the session, the current set of values together with their indicated names are stored in the *.yaml file (\hyref{Section}{sec:menub.filem.saves}). \item \textit{Amplitude corrections} applies additional rescaling to amplitude related parameters like the number of particles $n$ or amplitude fractions associated with different correlation times ($n_1$, $n_2$, etc.). Experimental values of non-correlated background intensity can be manually entered for each channel. In addition, the correlation curves can be normalized, to facilitate a visual comparison of the decay. \item \textit{Fitting options} offers weighted fitting (a) and a choice for the fit algorithm (b). \begin{itemize} \item[\textbf{a)}] The underlying idea is that data points with higher accuracy should also have a higher impact on model parameters. To derive weights, \textit{PyCorrFit} calculates the variance of the difference between the actual data and a smooth, empiric representation of the curve for a certain neighbourhood. The number of neighbouring data points at each side ($j > 0$) can be set. For such a smooth representation a spline function or the model function with the current parameter set can be used. The default number of knots for the spline function is 5. This number can be manually edited in the dropdown-selector. To view the spline function after each fit, \textit{Preferences/Verbose mode} has to be checked. If the current page is an average, then the standard deviation from the curves that were used to create the average can be used as weights. \item[\textbf{b)}] Several fitting algorithms can be chosen. We recommend to use the Levenberg-Marquardt algorithm (b). For more information, see \hyref{Section}{sec:theor.alg}. \end{itemize} \end{itemize} At the right hand side are two graphics windows. The dimensionless correlation functions $G(\tau)$ are plotted against the lag time ($\tau$) on a logarithmic scale. Below, a second window shows the residuals, the actual numerical difference between the correlation data and the model function. Fitting with appropriate models will scatter the residuals symmetrically around zero ($x$-axis). When weighted fitting was performed, the weighted residuals are shown. A good fit will not leave residuals systematically above or below the $x$-axis at any time scale. The main window can be rescaled as a whole to improve data representation. In addition, to zoom in, one can drag a rectangle within the plot area; a double click then restores the initial scale. Experimental data points are linked by grey lines, the state of the model function is shown in blue. When a weighted fit was applied, the variance of the fit is calculated for each data point and displayed in cyan. \section{The menu bar} \label{sec:menub} The menu bar organizes data management (File), data analysis (Tools), display of correlation functions (Current Page), numerical examples (Model), software settings (Preferences), and software metadata (Help). \subsection{File menu} \label{sec:menub.filem} The File menu organizes the import of theoretical models, experimental correlation data, and opening and saving of entire \textit{PyCorrFit} fitting sessions. However, the numerical fit results are exported from the \textit{Statistics view} panel which can be found under \textit{Tools} (\hyref{Section}{sec:menub.tools.stati}). \subsubsection{File / Import model} \label{sec:menub.filem.impor} Correlation data must be fitted to models describing the underlying physical processes which give rise to a particular time dependence and magnitude of the recorded signal fluctuations. Models are mathematical expressions containing parameters with physical meaning, like the molecular brightness or the dwell time through an illuminated volume. While the most commonly used standard functions are built-in, the user can define new expressions. Some examples can be found at GitHub in the \textit{PyCorrFit} repository, e.g. circular scanning FCS \cite{Petrasek2008} (\hyref{Figure}{fig:csfcs}) or a combination of diffusion and directed flow \cite{Brinkmeier1999}. External model functions are discussed in detail in \hyref{Section}{sec:hacke.extmod}. \subsubsection{File / Load data} \label{sec:menub.filem.loadd} \textit{Load data }is the first way to import multiple correlation data sets into a \textit{PyCorrFit} session. The supported file formats can be found in a drop-down list of supported file endings in the pop-up dialog \textit{Open data files}: \begin{tabular}{l l} \rule{0pt}{3ex} (1) All supported files & default \\ \rule{0pt}{3ex} (2) ALV (*.ASC) & ALV Laser GmbH, Langen, Germany \\ \rule{0pt}{3ex} (3) Correlator.com (*.SIN) & www.correlator.com, USA \\ \rule{0pt}{3ex} (4) PicoQuant (*.pt3) & PicoQuant \\ \rule{0pt}{3ex} (5) Zeiss ConfoCor3 (*.fcs) & AIM 4.2, ZEN 2010, Zeiss, Germany \\ \rule{0pt}{3ex} (6) Matlab ‘Ries (*.mat) & EMBL Heidelberg, Germany \\ \rule{0pt}{3ex} (7) PyCorrFit (*.csv) & Paul Müller, TU Dresden, Germany \\ \rule{0pt}{3ex} (8) PyCorrFit session (*.pcfs) & Paul Müller, TU Dresden, Germany \\ \rule{0pt}{3ex} (9) Zip file (*.zip) & Paul Müller, TU Dresden, Germany \\ \end{tabular} \vspace{3ex} \newline While (2)-(5) are file formats associated with commercial hardware, (6) refers to a MATLAB based FCS evaluation software developed by Jonas Ries in the Schwille lab at TU Dresden, (7) is a text file containing comma-separated values (csv) generated by PyCorrFit via the command \textit{Current Page / Save data}. Zip-files are automatically decompressed and can be imported when matching one of the above mentioned formats. In particular loading of *.pcfs files (which are actually zip files) is a possibility to re-import correlation data from entire \textit{PyCorrFit} sessions. However, these data are treated as raw, which means that all fitting parameters and model assignments are lost. During loading, the user is prompted to assign fit models in the \textit{Choose Models} dialogue window. There, curves are sorted according to channel (for example AC1, AC2, CC12, and CC21, as a typical outcome of a dual-color cross-correlation experiment). For each channel a fit model must be selected from the list (see \hyref{Section}{sec:menub.model}): If a file format is not yet listed, the correlation data could be converted into a compatible text-file (*.csv) or bundles of *.csv files within a compressed archive *.zip. For reformatting correlation data, have a look at \hyref{Section}{sec:hacke.csv}. \subsubsection{File / Open session} \label{sec:menub.filem.opens} This command is the second way to import data into PyCorrFit. In contrast to \textit{Load data}, it opens an entire fitting project, which was previously saved with \textit{PyCorrFit}. Session files are *.zip files named *.pcfs. These files contain all information to restore a session, including comments, model assigned correlation data, and the current state of parameters for each data set (\hyref{Section}{sec:menub.filem.saves}). \subsubsection{File / Comment session} \label{sec:menub.filem.comme} This command opens a window to place text messages that can be used to annotate a fitting session. \subsubsection{File / Clear session} \label{sec:menub.filem.clear} This command closes all pages. The user is prompted to save the current session. \subsubsection{File / Save session} \label{sec:menub.filem.saves} In addition to display and fit individual curves, a strong feature of \textit{PyCorrFit} is to save an entire fitting project as a single session. Sessions allow the user to revisit and explore different models, fitting strategies, and data sets. Importantly the work can be saved at any stage. The number of files bundled in a session varies depending on the number of data sets (pages), the number of used models, and what was done during the fitting. A detailed description can be found in the Readme.txt file attached to each session. For example, the numerical correlation and intensity data are saved separately as *.csv text files. However, in contrast to the \textit{Save data (*.csv)} command of the \textit{Current Page} menu, there are no metadata in the header, just tables containing the numerical values. In sessions, the fitting parameters are stored separately in the human-readable data serialization format *.yaml. \subsubsection{File / Exit} \label{sec:menub.filem.exit} This command closes down \textit{PyCorrFit}. The user is prompted to save the session under the same or a different name. \subsection{Tools menu} \label{sec:menub.tools} The \textit{Tools} menu provides access to a series of accessory panels which extent the capability of the main window. These accessory panels can stay open during the entire analysis. Open panels appear checked in the menu. Most operations can be executed across the entire data set with a single mouse click. \subsubsection{Tools / Data range} \label{sec:menub.tools.datar} This panel limits the range of lag times which are displayed in the main window panel. At the same time it defines the range of points which are used for fitting. For example, this feature can be applied to remove dominant after-pulsing of the avalanche photo diodes (APDs) which may interfere with Triplet blinking at short lag times. The user has the options to \textit{Apply} the channel settings only to the current page or he can \textit{Apply to all pages}. In contrast to \textit{Batch control}, this operation ignores whether the data are assigned to different models. Power user, who frequently load and remove data sets, may take advantage of a checkbox to fix the channel selection for all newly loaded data sets. \subsubsection{Tools / Overlay curves} \label{sec:menub.tools.overl} This window displays the correlation data (not the fit curves) of all pages in a single plot. The curves can be discriminated by color. If only one curve is selected, it appears in red. Curves with ambiguous shape can easily be identified, selected, and removed by clicking \textit{Apply}. A warning dialogue lists the pages which will be kept. Data representation is synchronized with the page display in the \textit{Main window}. For example, narrowing the range of lag times by \textit{Data range }is immediately updated in the \textit{Overlay curves }tool. Likewise, their normalization of the amplitudes to unity. The other way round, some tools directly respond to the selections made in the \textit{Overlay curves} tool: \textit{Global fitting}, \textit{Average curves}, and \textit{Statistics view} allow to perform operations on an arbitrary selection of pages which can be specified by page number. Instead of manually typing their numbers, the curves may be selected within the \textit{Overlay curves} tool. The respective input fields are immediately updated. The tool is closed by the button \textit{Cancel}. All the listed data sets will be kept. However, the selections transferred to the \textit{Global fitting}, \textit{Average curves}, and \textit{Statistics view} tools are kept as well. \subsubsection{Tools / Batch control} \label{sec:menub.tools.batch} By default, the current page is taken as a reference to perform automated fitting. A batch is defined as the ensemble of correlation data sets (pages) assigned to the same model function within a session. A session can therefore have several batches, even for the same data. For fitting, it is crucial to carefully define the starting parameters, whether parameters should be fixed or varied, the range of values which make physically sense, and other options offered within the \textit{Main window}. By executing \textit{Apply to applicable pages}, these settings are transferred to all other pages assigned to the same fit model. Note that this includes the range of lag times (lag time channels) which may have been changed with the \textit{Data range} tool for individual pages. It is possible to prevent the application of fitting parameters to individual pages by checking the \textit{prevent batch modification} checkbox in the \textit{Model parameters} box of a page. The button \textit{Fit applicable pages} then performs fitting on all pages of the same batch. Alternatively, the user can define an external source of parameters as a reference, i.e. the first page of some \textit{Other session} (*.pcfs). However, this assumes a consistent assignment of model functions. \subsubsection{Tools / Global fitting} \label{sec:menub.tools.globa} Global fitting is useful when experimental curves share the same values for certain physical parameters. For example, due to physical constraints in two-focus FCS both autocorrelation curves and cross-correlation curves should adopt the same values for the diffusion time $\tau_\mathrm{diff}$ and the number of particles $n$. A global fit can be applied such that $n$ and $\tau_\mathrm{diff}$ are identical for all data sets. All curves are added to a single array. In contrast to fixing the shared parameters across a batch, in \textit{Global fitting} $\chi^2$ is minimized for all data sets simultaneously (see \hyref{Section}{sec:theor.nonle}). To perform \textit{Global fitting}, a subset of curves has to be selected by typing the numbers into the input field or by highlighting the pages via the \textit{Overlay curves} tool. \subsubsection{Tools / Average data} \label{sec:menub.tools.avera} Often in FCS, the measurement time at a particular spot is divided in several runs. This approach is taken when occasional, global intensity changes are superimposed on the molecular fluctuations of interest. Then the user has to sort out the bad runs. After fitting, one may want to re-combine the data to export a cleaned average correlation function. This can be done with the tool \textit{Average data}, for which a subset of curves has to be selected by typing the numbers into the input field or by highlighting the pages via the \textit{Overlay curves} tool. For averaging, there are constraints: \begin{enumerate} \item Since the correlation curves are averaged point by point this requires the same number of lag time channels. Runs of different length cannot be averaged. \item The tool can only average data sets which are exclusively autocorrelation or cross-correlation. \item The user can check a box to enforce the program to ask for data sets with the same model as the current page. This may help to avoid mistakes when selecting pages. \end{enumerate} The averaged curve is shown on a separate page. The new \textit{Filename/title} receives the entry \textit{Average [numbers of pages]}. The assigned model is by default the same as for the individual pages. However, while averaging, the user can choose a different model from a drop-down list. \subsubsection{Tools / Trace view} \label{sec:menub.tools.trace} FCS theory makes assumptions about the thermodynamic state of the system. Signal fluctuations can only be analyzed when the system is at equilibrium or at a sufficiently stable steady state. Global instabilities on the time scale of the measurement itself, e.g. photo-bleaching, have dramatic effect on the shape of the measured correlation curve. Therefore, it is common practice to check the correlated intensity trace for each curve. Trace view simply displays the signal trace for each correlation function. The window stays open during the session and can be used to revisit and flag ambiguous data sets. \subsubsection{Tools / Statistics view} \label{sec:menub.tools.stati} The goal of a correlation analysis is to determine experimental parameter values with sufficient statistical significance. However, especially for large data sets, it can get quite laborious to check all of the individual values on each page. We designed the \textit{Statistics view} panel to review the state of parameters across the experimental batch (pages assigned to the same model) in a single plot, thereby facilitating to the identification of outliers. The current page is taken as a reference for the type of model parameters which can be displayed. The user can choose different \textit{Plot parameters} from a drop-down list. A subset of pages within the batch can be explicitly defined by typing the page numbers into the input field or by highlighting in the \textit{Overlay curves} tool. Note that page numbers which refer to different models than the current page are ignored. The \textit{Statistics view} panel contains a separate \textit{Export} box, where parameters can be selected (checked) and saved as a comma separated text file (*.csv). Only selected page numbers are included. \subsubsection{Tools / Page info} \label{sec:menub.tools.pagei} Page info is a most verbose summary of a data set. The panel \textit{Page info} is synchronized with the current page. The following fields are listed: \begin{enumerate} \item Version of \textit{PyCorrFit} \item Field values from the main window (filename/title, model specifications, page number, type of correlation, normalizations) \item Actual parameter values (as contained in the model function) \item Supplementary parameters (intensity, counts per particle, duration, background correction, etc.) \item Fitting related information (Chi-square, channel selection, varied fit parameters) . \item Model doc string (\hyref{Section}{sec:menub.model}) \end{enumerate} The content of Page info is saved as a header when exporting correlation functions via the command \textit{Current page / Save data (*.csv)} (\hyref{Section}{sec:menub.curre.saved}). \subsubsection{Tools / Slider simulation} \label{sec:menub.tools.slide} This tool visualizes the impact of model parameters on the shape of the model function of a current page. Such insight may be useful to choose proper starting values for fitting or to develop new model functions. For example, in the case of two parameters that trade during the fitting one may explore to which extent a change in both values produces similar trends. Two variables (A and B) have to be assigned from a drop-down list of parameters associated with the current model function. For each of these, the \textit{Slider simulation} panel shows initially the starting value (x) as a middle position of a certain range (from 0.1*x to 1.9*x). The accessible range can be manually edited and the actual value of the slider position is displayed at the right hand side of the panel. Dragging the slider to lower (left) or higher (right) values changes the entry in the box \textit{Model parameters} of the \textit{Main window} and accordingly the shape of the model function in the plot. By default the checkbox \textit{Vary A and B}\textit{ }is active meaning that both variables during \textit{Slider simulation} can be varied independently. In addition, the variables A and B can be linked by a mathematical relation. For this a mathematical operator can be selected from a small list and the option \textit{Fix relation} must be checked. Then, the variable B appears inactivated (greyed out) and the new variable combining values for A and B can be explored by dragging. \subsection{Current Page} \label{sec:menub.curre} This menu compiles import and export operations referring exclusively to the active page in the main window. \subsubsection{Current Page / Import Data} \label{sec:menub.curre.impor} This command is the third way to import data into a pre-existing session. Single files containing correlation data can be imported as long as they have the right format (\hyref{Section}{sec:menub.filem.loadd}). In contrast to \textit{Load data} from the \textit{File} menu, the model assignment and the state of the parameters remains. The purpose of this command is to compare different data sets to the very same model function for a given set of parameters. After successful import, the previous correlation data of this page are lost. To avoid this loss, one could first generate a new page via the menu \textit{Models} (\hyref{Section}{sec:menub.model}), select a model function and import data there. This is also a possibility to assign the very same data to different models within the same session. \subsubsection{Current Page / Save data (*.csv)} \label{sec:menub.curre.saved} For the documentation with graphics software of choice, correlation curves can be exported as a comma-separated table. A saved \textit{PyCorrFit} text-file (*.csv) will contain a hashed header with metadata from the \textit{Page info} tool (\hyref{Section}{sec:menub.tools.pagei}), followed by the correlation and fitting values in tab-separated columns: \textit{Channel (tau [s])}, \textit{Experimental correlation}, \textit{Fitted correlation}, \textit{Residuals}, and \textit{Weights (fit)}. Below the columns, there are again 5 rows of hashed comments followed by the intensity data in two columns: \textit{Time [s]} and \textit{Intensity trace [kHz]}. Note that there are no assemblies of ``multiple runs'', since \textit{PyCorrFit} treats these as individual correlation functions. A *.csv file therefore contains only a single fitted correlation curve and one intensity trace for autocorrelation or two intensity traces for cross-correlation. \subsubsection{Current Page / Save correlation as image} \label{sec:menub.curre.savec} The correlation curve can be exported as bitmap (e.g. *.png for quick documentation) or as a scalable vector graphic (e.g. *.pdf for post-processing in \textit{Adobe Illustrator} or \textit{Inkscape}). The plot contains a legend and fitting parameters. Note that the variable $\tau$ (= tau) cannot be displayed using Unicode with Windows. A \LaTeX formatted image can be exported when the option \textit{Use Latex} is checked in the \textit{Preferences} menu (\hyref{Section}{sec:menub.prefe}). Furthermore, if \textit{Preferences/Verbose mode} is checked, the plot can be edited before saving\footnote{The plots are generated with matplotlib, \url{http://matplotlib.org/}}. \subsubsection{Current Page / Save trace view as image} \label{sec:menub.curre.savet} An image of the trace can be exported in the same way as for the correlation curve (see above). \subsubsection{Current Page / Close page} \label{sec:menub.curre.close} Closes the page; the data set is removed from the session. The page numbers of all other pages remain the same. The command is equivalent with the closer (x) in the tab. \subsection{Models} \label{sec:menub.model} When choosing a model from the \textit{Models} menu, a new page opens and the model function is plotted according to the set of starting values for parameters as they were defined in the model description. The lists contains all of the implemented model functions, which can be selected during \textit{File / Load data}. The parameters can be manipulated to explore different shapes; the tool \textit{Slider simulation} can also be used. Via \textit{Current page / Import data}, the model may then be fitted to an experimental data set. Standard model functions for a confocal setup are (\hyref{Section}{sec:imple.confo}): \begin{tabular}{l l} %Confocal (Gaussian): 3D \ \ \ \ \ \ [Free diffusion in three dimensions] \rule{0pt}{3ex} - Confocal (Gaussian): T+3D & Triplet blinking and 3D diffusion \\ \rule{0pt}{3ex} - Confocal (Gaussian): T+3D+3D & Triplet with two diffusive components \\ %Confocal (Gaussian): T+3D+3D+3D & [Triplet with three diffusive components] %Confocal (Gaussian): 2D & 2D diffusion, e.g. in membranes \\ \rule{0pt}{3ex} - Confocal (Gaussian): T+2D & Triplet blinking and 2D diffusion \\ \rule{0pt}{3ex} - Confocal (Gaussian): T+2D+2D & Triplet with two diffusive components \\ \rule{0pt}{3ex} - Confocal (Gaussian): T+3D+2D & Triplet with mixed 3D and 2D diffusion \\ \rule{0pt}{3ex} \end{tabular} \noindent There is also a collection of models for FCS setups with TIR excitation (\hyref{Section}{sec:imple.tirfc}): \begin{tabular}{l l} \rule{0pt}{3ex} - TIR (Gaussian/Exp.): 3D & 3D diffusion \\ \rule{0pt}{3ex} - TIR (Gaussian/Exp.): T+3D+3D & Triplet with two diffusive components \\ \rule{0pt}{3ex} - TIR (Gaussian/Exp.): T+3D+2D & Triplet with mixed 3D and 2D diffusion \\ \rule{0pt}{3ex} \end{tabular} \noindent In addition, there are may be user defined model functions which have been imported previously via \textit{File / Import model} (\hyref{Section}{sec:menub.filem.impor}). \subsection{Preferences} \label{sec:menub.prefe} \paragraph*{Use Latex} If the user has a Tex distribution installed (e.g. MikTex for Windows), checking this option will generate \LaTeX formatted plots via the \textit{Current page / Save […] as image} commands. \paragraph*{Verbose mode} If checked, this will cause the \textit{PyCorrFit} to display graphs that would be hidden otherwise. In weighted fitting with a spline, the spline function used for calculating the weights for each data points is displayed\footnote{For obvious reasons, such a plot is not generated when using the iteratively improved \textit{Model function} or the actual \textit{Average} correlation curve for weighted fitting.}. When saving the correlation curve as an image (\hyref{Section}{sec:menub.curre.savec}), the plot will be displayed instead of saved. If ``Use Latex'' is checked, these plots will also be Latex-formatted. The advantage in displaying plots is the ability to zoom or rescale the plot from within \textit{PyCorrFit}. \paragraph*{Show weights} Checking this option will visualize the weights for each data point of the correlation function in the plot, as well as in the exported image. Note that the weights are always exported when using the \textit{Save data (*.csv)} command from the \textit{Current page} menu. \subsection{Help} \label{sec:menub.help} \paragraph*{Documentation.} This entry displays this documentation using the systems default PDF viewer. \paragraph*{Wiki.} This entry displays the wiki of \textit{PyCorrFit} on \textit{GitHub}. Everyone who registers with \textit{GitHub} will be able to make additions and modifications. The wiki is intended for end-users of \textit{PyCorrFit} to share protocols or to add other useful information. \paragraph*{Update} establishes a link to the \textit{GitHub} website to check for a new release; it also provides a few web links associated with \textit{PyCorrFit} \paragraph*{Shell.} This gives Shell-access to the functions of \textit{PyCorrFit}. It is particularly useful for trouble-shooting. \paragraph*{Software.} This lists the exact version of \textit{Python} and the corresponding modules with which \textit{PyCorrFit} is currently running. \paragraph*{About.} Information of the participating developers, the license, and documentation writers. \section{Hacker's corner} \label{sec:hacke} \subsection{External model functions} \label{sec:hacke.extmod} \textit{PyCorrFit} supports the import of your own model functions. If your model function is not implemented, i.e. not available in the \textit{Models} menu, writing a short text file containing a model description is the easiest way to make \textit{PyCorrFit} work. Some examples can be found at GitHub in the \textit{PyCorrFit} repository, e.g. circular scanning FCS \cite{Petrasek2008} (\hyref{Figure}{fig:csfcs}) or a combination of diffusion and directed flow \cite{Brinkmeier1999}. Model functions are imported as text files (*.txt) that must follow a certain format and syntax: \begin{itemize} \item \textbf{Encoding}: \textit{PyCorrFit} can interpret the standard Unicode character set. The model files have to be encoded in \textit{UTF-8}. \item \textbf{Comments}: Lines starting with a hash (\texttt{\#}), empty lines, or lines containing only white space characters are ignored. The only exception is the first line starting with a hash followed by a white space and a short name of the model. This line is evaluated to complement the list of models in the dialogue\textit{ Choose }\textit{model}, when loading the data. Imported models are available from the menu \textit{Models/User}. \item \textbf{Units}: \textit{PyCorrFit} works with internal units for: \begin{itemize} \item Time: \SI{1}{ms} \item Distance: \SI{100}{nm} \item Diffusion coefficient: \SI{10}{\mu m^2s^{-1}} \item Inverse time: \SI{1000}{s^{-1}} \item Inverse area: \SI{100}{\mu m^{-2}} \item Inverse volume: \SI{1000}{\mu m^{-3}} \end{itemize} \item \textbf{Parameters:} To define a new model function, new parameters can be introduced. Parameters are defined by a sequence of strings separated by white spaces containing name, the dimension in angular brackets, the equal sign, and a starting value which appears in the main window for fitting. For example: \texttt{D [\SI{10}{\mu m^ 2 s^{-1}}] = 5.0}. User defined dimensions are only for display; thus mathematical expressions must correctly account for their conversion from internal units of \textit{PyCorrFit}. The parameter names contain only alphabetical (not numerical) characters. \texttt{G}, variables starting with \texttt{g}, as well as the numbers \texttt{e} and \texttt{pi} are already mapped and cannot be used freely. \item \textbf{Placeholder:} When defining composite mathematical expressions for correlation functions, one can use place-holders. Place-holders start with a lower-case ‘g’. For example, the standard, Gaussian 3D diffusion in free solution may be written as \begin{itemize} \item \texttt{gTrp = 1+ T/(1-T)*exp(-tau/tautrip)} \item \texttt{gTwoD = 1/(1+tau/taudiff)} \item \texttt{gThrD = 1/sqrt(1+tau/(taudiff*S**2))} \end{itemize} \end{itemize} The individual parts are then combined in the last line of the *.txt file, where the correlation function is defined starting with upper-case ’G’: \begin{equation} \texttt{G = 1/n * gTrp * gTwoD * gThrD} \notag \end{equation} For reference of mathematical operators, check for example \href{http://www.tutorialspoint.com/python/python_basic_operators.htm}{www.tutorialspoint.com / python / python\_basic\_operators.htm}. To illustrate a more complex example, a model function for circular scanning FCS is shown in \hyref{Figure}{fig:csfcs}. External models will be imported with internal model function IDs starting at $7000$. Models are checked upon import by the Python module sympy. If the import fails, there is most likely syntax error in the model file. \begin{figure} % for case sensitiver Verbatim, we need the package fancyvrb \begin{Verbatim}[frame = single] # CS-FCS T+2D+2D+S (Confocal) # Circular Scanning FCS model function for two 2D-diffusing species # including triplet component. ## Definition of parameters: # First, the parameters and their starting values for the model function # need to be defined. If the parameter has a unit of measurement, then it # may be added separated by a white space before the "=" sign. The starting # value should be a floating point number. Floating point abbreviations # like "1e-3" instead of "0.001" may be used. # Diffusion coefficient of first component D1 [10µm²/s] = 200.0 # Diffusion coefficient of second component D2 [10µm²/s] = 20.0 # Fraction of species one F1 = 1.0 # Half waist of the lateral detection area (w0 = 2*a) a [100nm] = 1.0 # Particle number n = 5.0 # Scan radius R [100nm] = 3.850 # Frequency f [kHz] = .2 # Triplet fraction T = 0.1 # Triplet time tautrip [ms] = 0.001 offset = 0.00001 # The user may wish to substitute certain parts of the correlation function # with other values to keep the formula simple. This can be done by using # the prefix "g". All common mathematical functions, such as "sqrt()" or # "exp()" may be used. For convenience, "pi" and "e" are available as well. gTriplet = 1. + T/(1-T)*exp(-tau/tautrip) gScan1 = exp(-(R*sin(pi*f*tau))**2/(a**2+D1*tau)) gScan2 = exp(-(R*sin(pi*f*tau))**2/(a**2+D2*tau)) gTwoD1 = F1/(1.+D1*tau/a**2) gTwoD2 = (1-F1)/(1.+D2*tau/a**2) # The final line with the correlation function should start with a "G" # before the "=" sign. G = offset + 1./n * (gTwoD1 * gScan1 + gTwoD2 * gScan2) * gTriplet \end{Verbatim} \mycaption{user defined model function for PyCorrFit}{The working example shows a model function for circular scanning FCS (see also its appearance in the \textit{Main window} \hyref{Figure}{fig:csfcsplot} \label{fig:csfcs}} \end{figure} \subsection{Internal model functions} Alternatively, new models can be implemented by programming of the models module of \textit{PyCorrFit}. First, edit the code for \texttt{\_\_init\_\_.py} and then add the script containing the model function. There is no Tutorial yet, but the implemented model files are self-explanatory. If you need help creating a new internal model function and/or want to publish it with \textit{PyCorrFit}, do not hesitate to contact an active developer by creating a new issue on GitHub. \subsection{Correlation curve file format} \label{sec:hacke.csv} PyCorrFit can read correlation data from many file formats. If a file format is not yet listed, the correlation data could be converted into a compatible text-file (*.csv) or bundles of *.csv files within a compressed *.zip archive. For reformatting the following requirements must be fulfilled: \begin{itemize} \item \textbf{Encoding}: \textit{PyCorrFit} uses the standard Unicode character set (UTF-8). However, since no special characters are needed to save experimental data, other encodings may also work. New line characters are \texttt{{\textbackslash}r{\textbackslash}n} (Windows). \item \textbf{Comments}: Lines starting with a hash (\texttt{\#}), empty lines, or lines containing only white space characters are ignored. Exceptions are the keywords listed below. \item \textbf{Units}: PyCorrFit works with units/values for: \begin{itemize} \item Time: \SI{1}{ms} \item Intensity: \SI{1}{kHz} \item Amplitude offset: $G(0) = 0$ (not 1) \end{itemize} \item \textbf{Keywords:}\footnote{Keywords are case-insensitive.} \textit{PyCorrFit} reads the first two columns containing numerical values. The first table (non-hashed) is recognized as the correlation data containing the lag times in the first and the correlation data in the second column. (In case the *.csv file has been generated with \textit{PyCorrFit} up to three additional columns containing the fit function are ignored). The table ends, when the keyword \texttt{\# BEGIN TRACE} appears. Below this line the time and the signal values should be contained in the first two columns. If cross-correlation data have to be imported a second trace can be entered after the keyword \texttt{\# BEGIN SECOND TRACE}. \item \textbf{Tags:}\footnote{Tags are case-insensitive.} Channel information can be entered using defined syntax in a header. The keyword \begin{center} \vspace{-1em} \texttt{\# Type AC/CC Autocorrelation} \vspace{-1em} \end{center} assigns the tag \texttt{AC} and the keyword \begin{center} \vspace{-1em} {\texttt{\# Type AC/CC Crosscorrelation}} \vspace{-1em} \end{center} assigns the tag \texttt{CC} to the correlation curve. These strings are consistently displayed in the user interface of the respective data page in \textit{PyCorrFit}. If no data type is specified, autocorrelation is assumed. Tags may be specified with additional information like channel numbers, e.g. \begin{center} \vspace{-1em} \texttt{\# Type AC/CC Autocorrelation \_01}. \vspace{-1em} \end{center} In this case the tag \texttt{AC\_01} is generated. This feature is useful to keep track of the type of curve during the fitting and when post-processing the numerical fit results. \end{itemize} \subsection{New file format} Alternatively, new file formats can be implemented by programming of the readfiles module of \textit{PyCorrFit}. First, edit the code for \texttt{\_\_init\_\_.py} and then add the script \texttt{read\_FileFormat.py}. There is no Tutorial yet, but the implemented scripts are self-explanatory. If you need help implementing a new file format and/or want to publish it with \textit{PyCorrFit}, do not hesitate to contact an active developer by creating a new issue on GitHub. \section{Theoretical background} \label{sec:theor} In the first place, \textit{PyCorrFit} was designed to evaluate FCS data, therefore we focus on fluorescence. However, the correlation theory could be applied to any other stochastic signal. \subsection{How FCS works} \label{sec:theor.howfc} FCS is a method to determine molecular properties of stochastically moving fluorescent molecules in solutions \cite{Elson1974,Magde1974,Magde1978}. The solution may be a liquid volume (3D) or a lipid membrane (2D) \cite{Widengren1998,Korlach1999,Schwille1999}. The diffusion may be free or anomalous due to barriers or obstacles \cite{Wachsmuth2000,Weiss2003}. The size of the diffusing particles range from synthetic dyes (\SI{800}{Da}) to large complexes or aggregates of labelled macromolecules (several MDa). Particles which differ in size or emission behaviour can be discriminated. Therefore FCS is a powerful tool to study molecular recognition \cite{Bacia2006,Kim2007}. The measurement principle is to illuminate a small open volume within solutions of fluorescent molecules and to detect their molecular transits with a sub-microsecond time resolution by sensitive optics. The stochastic movements and other processes affecting fluorescence emission generate a fluctuating signal. The typical time pattern of these fluctuations can be revealed by a correlation analysis. The shape and time range of the decaying correlation function is defined by the exact geometry of the FCS detection volume in conjunction with the properties of the molecular system at hand. To derive hard numbers, the system must be parametrized as a theoretical model. Once the geometry of the detection volume is characterized, parameters can be determined by comparing the model function with experimental data (mathematical fitting, \hyref{Figure}{fig:mainwin}). \subsection{Framework for a single type of fluorescent dye} \label{sec:theor.frame} The following equations are described in many papers in different ways. Here we follow the notation of one of us \cite{Weidemann2009}. Fluorescence signals are a result of absorption and emission of photons by a fluorophore. Under experimental conditions, the signal depends on the time dependent distribution of fluorescent particles in the sample volume $c(\vec{r},t)$ and the normalized instrumental detection efficiency $W(\vec{r})$. The total intensity, signal $S(t)$, is the sum of all photons emitted from fluorophores at different positions within the detection volume: \begin{equation} \label{eq1} S(t) = q \int W(\vec{r}) c(\vec{r},t) \,dV \end{equation} The factor $q$ combines all the photo-physical quantities associated with fluorescence emission like absorption cross section, quantum yield, and the peak intensity of excitation (laser power). In the following, time averages of observables are indicated by angular brackets. We apply the ergodic theorem (see below). \begin{equation} \label{eq2} \langle S(t) \rangle = \lim_{t\to\ \infty} \int S(t) \,dt = q \int W(\vec{r}) c \,dV = qn \end{equation} \hyref{Equation}{eq2} reveals that $q$ is the instrument dependent molecular brightness (kHz/particle), i.e. the average signal divided by the average number of particles $n$ observed within the effective detection volume $V_{\mathrm{eff}} = \int W(\vec{r}) \,dV$. During FCS measurements the detected signal is correlated by computing a normalized autocorrelation function: \begin{equation} \label{eq3} G(\tau) = \frac{\langle S(t) \cdot S(t+\tau)\rangle}{\langle S(t) \rangle^2}-1 = \frac{\langle \delta S(t) \cdot \delta S(t+\tau)\rangle}{\langle S(t) \rangle^2} = \frac{g(\tau)}{\langle S(t) \rangle^2} \end{equation} Here, $\tau$ denotes the lag time used for correlation and $\delta S(t) = S(t)-\langle S \rangle$ the amplitude of the signal fluctuation for a given time point. \hyref{Equation}{eq3} defines a function with a finite intercept decaying to zero, whereas $g(\tau)$, the non-normalized correlation function, decays to a finite value $\langle S \rangle^{-2}$. To visualize correlation times, the functions $G(\tau)$ are typically plotted against $\log(\tau)$ (\hyref{Figure}{fig:mainwin}). A general way to derive theoretical model functions is to evaluate \hyref{Equation}{eq3} with explicit expressions describing the instrumental detection efficiency $W(\vec{r})$ and the molecular dynamics governing the local fluorophore concentrations $c(\vec{r},t)$. For example, free diffusion of the molecules can be described by the so-called diffusion propagator. \begin{equation} \label{eq4} P_\mathrm{d} \left( \vec{r} \,' | \vec{r},\tau \right) = \frac{1}{\left( 4 \pi D_t \tau \right) ^{3/2}} \exp \left[ - \frac{\left| \vec{r} \,' -\vec{r} \, \right|} {4D_t \tau} \right] \end{equation} The propagator $P_\mathrm{d} \left( \vec{r} \,' | \vec{r},\tau \right)$ is the conditional probability of a particle with diffusion coefficient $D_t$ to move from $\vec{r}$ to $\vec{r \,'}$ within a time period $\tau$. The probability to find a particle inside the volume $d^3r$ is simply the ratio of volumes $d^3r/V$. Such a diffusion propagator leads to a Gaussian shaped probability distribution spreading in time when the molecules successively roam the sample volume $V$. Assuming that the time average and the ensemble average are equivalent (ergodic theorem), the average signal of a single particle is its molecular brightness $q$ normalized to its contribution to the entire volume, because the integral over the detection efficiency function is exactly the effective measurement volume $V_\mathrm{eff}$. \begin{equation} \label{eq5} \langle s(t) \rangle = q \frac{\int W(\vec{r}) \,dV}{V} = \frac{V_\mathrm{eff}}{V} q \end{equation} Accordingly, we can express the average product of two signals separated by $\tau$ as \begin{equation} \label{eq6} \langle s(t) \cdot s(t + \tau) \rangle = \frac{q^2}{V}\iint W(\vec{r}) P_\mathrm{d} \left( \vec{r} \,' | \vec{r},\tau \right) W(\vec{r} \,') dVdV' \end{equation} With the definition of the signal $S(t)$ and its average $\langle S(t) \rangle$ \begin{align} S(t) &= \Sigma_{k=1}^n s_k(t) \\ \langle S(t) \rangle &= \Sigma_{k=1}^n \langle s_k(t) \rangle := N \langle s(t) \rangle \end{align} - where $N$ is the total number of particles in the sample volume $V$ - we can reduce $G(\tau)$ to the sum of individual molecular contributions \begin{align} G(\tau) = \frac{\langle S(t) \cdot S(t+\tau)\rangle}{\langle S(t) \rangle^2}-1 =& \frac{\overbrace{N \langle s(t) \cdot s(t+\tau)\rangle}^{\mbox{\small same particles}} +\overbrace{N(N-1) \langle s(t) \rangle^2}^{\mbox{\small different particles}}}{N^2 \langle s(t) \rangle^2}-1 \notag \\ \approx & \frac{\langle s(t) \cdot s(t+\tau)\rangle}{N \langle s(t) \rangle^2} \label{eq7} \end{align} Inserting \hyref{Equation}{eq5} and \hyref{Equation}{eq6} yields \begin{equation} \label{eq8} G(\tau) = \frac{V}{N} \frac{\iint W(\vec{r}) P_\mathrm{d} \left( \vec{r} \,' | \vec{r},\tau \right) W(\vec{r} \,') dVdV'}{\left( \int W(\vec{r}) \,dV \right)^2} \end{equation} Note that the molecular brightness $q$ cancels; when considering multiple species with different $q$, this is no longer the case. Solving these integrals for the confocal detection scheme yields a relatively simple equation containing the diffusion coefficient $D_t$ (molecular property) and the $1/e^2$ decay lengths $w_0$ and $z_0$ capturing the dimension of the Gaussian detection volume transversal and parallel to the optical axis, respectively (instrumental properties). \begin{equation} \label{eq9} G(\tau) = \frac{1}{n} \left(1+\frac{4 D_t \tau}{w_0^2} \right) ^{-1} \left(1+\frac{4D_t \tau}{z_0^2} \right)^{-1/2} \end{equation} The inverse intercept $(G(0))^{-1}$ is proportional to the total concentration of oberved particles $C = N/V = n/V_{\mathrm{eff}} = n/ (\pi^{3/2}w_0^2z_0)$. It is common to define the diffusion time $\tau_{\mathrm{diff}} = {w_0}^2/4D_t$ and the structural parameter $\textit{SP}=z_0^2/w_0^2$ as a measure of the elongated detection volume. Replacement finally yields the well known autocorrelation function for 3D diffusion in a confocal setup (Model ID 6012) \begin{equation} \label{eq10} G(\tau) \stackrel{\mathrm{def}}{=} G^{\mathrm{D}}(\tau) = \frac{1}{n} \overbrace{ \left(1+\frac{\tau}{\tau_{\mathrm{diff}}} \right) ^{-1}}^{\mathrm{2D}} \overbrace{ \left(1+\frac{\tau}{\textit{SP}^2 \, \tau_{\mathrm{diff}}} \right)^{-1/2}}^{\mathrm{3D}} \end{equation} For confocal FCS, both the detection volume $W(\vec{r})$ and the propagator for free diffusion $P_\mathrm{d}$ are described by exponentials (Gaussian functions). Therefore, spatial relationships can be factorized for each dimension $xyz$. As a result, \hyref{Equation}{eq10} can be written as a combination of transversal (2D) and longitudinal (3D) diffusion. \subsection{Autocorrelation of multiple species} \label{sec:theor.autoc} Very often in FCS, one observes more than one dynamic property. Besides diffusion driven number fluctuations, a fluorophore usually shows some kind of inherent blinking, due to triplet state transitions (organic dyes) or protonation dependent quenching (GFPs) \cite{Widengren1995}. \begin{equation} \label{eq11} G(\tau) \stackrel{\mathrm{def}}{=} G^{\mathrm{T}}(\tau) G^{\mathrm{D}}(\tau) = \left( 1+ \frac{T}{1-T} \exp\left[-\frac{\tau}{\tau_{\mathrm{trp}}} \right] \right)G^{\mathrm{D}}(\tau) \end{equation} Blinking increases the correlation amplitude $G(0)$ by the triplet fraction $1/(1-T)$. Accordingly, the average number of observed particles is decreased $n = (1-T)/G(0)$. In case of GFP blinking, two different blinking times have been described and the rate equations can get quite complicated. Besides photo-physics, the solution may contain mixtures of fluorescent particles with different dynamic properties, e.g. different mobility states or potential for transient binding. Such mixtures show several correlation times in the correlation curve. \hyref{Equation}{eq11} can be derived by considering the correlation functions of an ensemble, which can be built up by the contribution of $n$ single molecules in the sample volume: \begin{equation} \label{eq12} G(\tau) = \frac{g(\tau)}{\langle S(t) \rangle^2} = \frac{\sum_{i=1}^n \sum_{j=1}^n g_{ij}(\tau)}{\langle S(t) \rangle^2} \end{equation} with $g_{ij}(\tau)$ as the pairwise correlation function of identical ($i = j$) or distinguishable ($i \not= j$) particles \begin{equation} \label{eq13} g_{ij}(\tau) = \langle s(t) \cdot s(t + \tau) \rangle = \frac{q_iq_j}{V} \int \int W(\vec{r}) P_{\mathrm{d},ij} \left( \vec{r} \,' | \vec{r},\tau \right) W(\vec{r}\,') dVdV' \end{equation} Note that the diffusion propagator $P_{\mathrm{d},ij}$ is now indexed, since the movement of some particle pairs may depend on each other and therefore show correlations. If particle $i$ and particle $j$ move independently, the mixed terms cancel $g_{ij}(\tau) = 0$. Due to the sums in \hyref{Equation}{eq12}, adding up individual contributions of sub-ensembles is allowed. A frequently used expression to cover free diffusion of similarly labelled, differently sized particles is simply the sum of correlation functions, weighted with their relative fractions $F_k = n_k/n$ to the overall amplitude $G(0) = 1/n$: \begin{equation} \label{eq14} G^{\mathrm{D}}(\tau) = \sum_{k=1}^m F_k G^{\mathrm{D}}(\tau) = \frac{1}{n} \sum_{k=1}^m F_k \left(1+\frac{\tau}{\tau_{{\mathrm{diff}},k}} \right) ^{-1} \left(1+\frac{\tau}{\textit{SP}^2 \, \tau_{{\mathrm{diff}},k}} \right) \end{equation} Up to three diffusion times can usually be discriminated ($m = 3$) \cite{Meseth1999}. Note that this assumes homogenous molecular brightness of the different diffusion species. One of the molecular brightness values $q_k$ is usually taken as a reference ($\alpha_k = q_k/q_1$). Brighter particles are over-represented \cite{Thompson1991} \begin{equation} \label{eq15} G^{\mathrm{D}}(\tau) = \frac{1}{n \left( \sum_k F_k \alpha_k \right)^2} \sum_k F_k \alpha_k^2 G_k^D(\tau) \end{equation} Inhomogeneity in molecular brightness affects both the total concentration of observed particles as well as the real molar fractions $F_k^{\mathrm{cor}}$ \cite{Thompson1991} \begin{equation} \label{eq16} n = \frac{1}{G^{\mathrm{D}}(0)} \frac{\sum_k F_k^{\mathrm{cor}} \alpha_k^2}{\left( \sum_k F_k^{\mathrm{cor}} \alpha_k \right)^2} \quad\mbox {with} \quad F_k^{\mathrm{cor}} = \frac{F_k/\alpha_k^2}{\sum_k F_k/\alpha_k} \end{equation} \subsection{Correcting non-correlated background signal} \label{sec:theor.correc} In FCS, the total signal is composed of the fluorescence and the non-correlated background: $S = F + B$. Non-correlated background signal like shot noise of the detectors or stray light decreases the relative fluctuation amplitude and must be corrected to derive true particle concentrations \cite{Koppel1974,Thompson1991}. In \textit{PyCorrFit}, the background value [kHz] can be manually set for each channel (B1, B2) (\hyref{Figure}{fig:mainwin}). For autocorrelation measurements ($B1 = B2 = B$) the average number of observed particles is then \begin{equation} \label{eq17} n = \frac{1}{G^{\mathrm{D}}(0)} \left( \frac{S-B}{S} \right)^2 = \frac{1}{(1-T)G(0)} \left( \frac{S-B}{S} \right)^2. \end{equation} For dual-channel applications with cross-correlation (next section) the amplitudes must be corrected by contributions from each channel \cite{Weidemann2013} \begin{equation} \label{eq18} G_{\times,\mathrm{cor}}(0) = G_{\times, \mathrm{meas}}(0) \left( \frac{S_1}{S_1-B_1} \right) \left( \frac{S_2}{S_2-B_2} \right) \end{equation} \subsection{Cross-correlation} \label{sec:theor.cross} Cross-correlation is an elegant way to measure molecular interactions. The principle is to implement a dual-channel setup (e.g. channels 1 and 2), where two, interacting populations of molecules can be discriminated \cite{Foo2012,Ries2010,Schwille1997,Weidemann2002}. In a dual-channel setup, complexes containing particles with both properties will evoke simultaneous signals in both channels. Such coincidence events can be extracted by cross-correlation between the two channels. A prominent implementation is dual-colour fluorescence cross-correlation spectroscopy (dcFCCS), where the binding partners are discriminated by spectrally distinct (differently coloured) labels. The formalism is similar to autocorrelation, just the origin of the signals must now be traced \cite{Rippe2000,Weidemann2002,Schwille1997}. \begin{equation} \label{eq19} G_\times (\tau) \stackrel{\mathrm{def}}{=} G_{12} (\tau) = \frac{\langle \delta S_1(t) \delta S_2(t+\tau)\rangle}{\langle S_1(t) \rangle \langle S_2(t) \rangle} \approx \frac{\langle \delta S_2(t) \delta S_1(t+\tau)\rangle}{\langle S_1(t) \rangle \langle S_2(t) \rangle} = G_{21} (\tau) \end{equation} A finite cross-correlation amplitude $G_{12}(0)$ indicates co-diffusion of complexes containing both types of interaction partners. The increase of the cross-correlation amplitude is linear for heterotypic binding but non-linear for homotypic interactions or higher order oligomers. The absolute magnitude of the cross-correlation amplitude must be calibrated because the chromatic mismatch of the detection volumes (different wavelength, different size) and their spatial displacement ($d_\mathrm{x}$, $d_\mathrm{y}$, $d_\mathrm{z}$) constitute instrumental limits, even for 100\% double labelled particles \cite{Weidemann2002}. \hyref{Equation}{eq9} can be extended \begin{equation} \label{eq20} G(\tau) = \frac{1}{n} \left(1+\frac{4 D_t \tau}{w_0^2} \right) ^{-1} \left(1+\frac{4D_t \tau}{z_0^2} \right)^{-1/2} \exp \left(- \frac{d_\mathrm{x}^2 + d_\mathrm{y}^2}{4 D_t \tau + w_{0,\mathrm{eff}}} + \frac{d_\mathrm{z}^2}{4 D_t \tau + z_{0,\mathrm{eff}}} \right) \end{equation} The ratio between cross- and autocorrelation amplitude is used as a readout which can be linked to the degree of binding. Let us consider a heterodimerization, where channel $1$ is sensitive for green labelled particles ($g$) and channel $2$ is sensitive for red labelled particles ($r$), then the ratio of cross- and autocorrelation amplitudes is proportional to the fraction of ligand bound \cite{Weidemann2002} \begin{eqnarray} \label{eq21} CC_1 \stackrel{\mathrm{def}}{=} \frac{G_\times(0)}{G_1(0)} & \propto & \frac{c_{gr}}{c_r} \nonumber \\ CC_2 \stackrel{\mathrm{def}}{=} \frac{G_\times(0)}{G_2(0)} &\propto & \frac{c_{gr}}{c_g} \end{eqnarray} Recently, a correction for bleed-through of the signals between the two channels has been worked out \cite{Bacia2012}. The effect on binding curves measured with cross-correlation can be quite dramatic \cite{Weidemann2013}. To treat spectral cross-talk, the experimenter has to determine with single coloured probes how much of the signal (ratio in \%) is detected by the orthogonal, 'wrong' channel ($BT_{12}, BT_{12}$). Usually the bleed-through from the red into the green channel can be neglected ($BT_{21} = 0$) leaving only a contribution of bleed through from the green into the red channel ($BT_{12}$) \begin{eqnarray} \label{eq22} \langle F_1 \rangle & = & \langle \hat{F}_1 \rangle + \langle \hat{F}_2 \rangle BT_{21} \cong \langle \hat{F}_1 \rangle \nonumber \\ \langle F_2 \rangle & = & \langle \hat{F}_2 \rangle + \langle \hat{F}_1 \rangle BT_{12} \end{eqnarray} Here, the dashed fluorescence signals are the true contributions from single labelled species. Thus, each set of simultaneously recorded auto and cross-correlation curves suffers from a specific fraction of wrong signal in the vulnerable red channel, $X_2 = BT_{12} \langle \hat{F}_1 \rangle/\hat{F}_2 \rangle$, which can be used to back-correct the cross-correlation amplitudes \cite{Bacia2012,Weidemann2013} \begin{subequations} \label{eq23} \begin{align} \frac{c_{gr}}{c_r} & \propto \frac{CC_1-X_2}{\left( 1-X_2 \right)} \label{eq23a} \\ \frac{c_{gr}}{c_g} & \propto \frac{CC_2-X_2 \left( 1-X_2 \right) \frac{G_1^{\mathrm{D}}(0)}{G_2^{\mathrm{D}}(0)}}{1+ X_2 \frac{G_1^{\mathrm{D}}(0)}{G_2^{\mathrm{D}}(0)} - 2 X_2 CC_2} \label{eq23b} \end{align} \end{subequations} As apparent from \hyref{Equations}{eq23}, it is much simpler to use the autocorrelation amplitude measured in the green channel for normalization (\ref{eq23a}) and not the cross-talk affected red channel (\ref{eq23b}). Finally, the proportionality between the fraction ligand bound and the measured cross-correlation ratio depend solely on the effective detection volumes of all three channels (two auto- and the cross-correlation channels) and must be determined with appropriate positive controls (single labelled and double labelled calibration dyes). \subsection{Extensions of the method} \label{sec:theor.exten} Using as confocal alignment for FCS measurements is very successful and widely applied. However, mainly in the context of membrane research, other excitation schemes have been explored. Once the excitation and detection geometry is changed one has to account for it in the model functions. This is usually done by modifying the expressions of the instrumental detection efficiency $W(\vec{r})$ in \hyref{Equation}{eq8}. However, in many cases analytical solutions to the above integrals are not straightforward and approximations have to be made. The following section introduces model functions for different detection symmetries and particle dynamics. \subsubsection{Perpendicular scanning FCS (pSFCS)} \label{sec:theor.exten.perpe} Scanning FCS with a scan path perpendicular to the membrane plane was introduced to measure the lateral diffusion of fluorescent components of a fluid lipid membrane \cite{Ries2006}. Using the linear scan option of a confocal laser scanning microscope (CLSM), the focal spot is moved repeatedly on a straight line through a free standing membrane (typically the equatorial face of a giant vesicle). The fluorescence photons emitted from the detection volume are continuously recorded and stored. The signal originating from the transit through the membrane must be extracted post-measurement, correlated, and evaluated by well-known FCS model functions. Scanning can also be performed with two parallel scan paths perpendicular through the membrane (continuous imaging with two lines). Like in two-focus FCS \cite{Dertinger2007}, the distance between these scan paths can been used to calibrate the diffusion coefficients without further use of diffusion standards. We recently introduced a free, open source software tool (\textit{PyScanFCS}) to perform these steps \cite{Muller2014}. \subsubsection{Circular scanning FCS (cSFCS)} \label{sec:theor.exten.circu} The principle of circular scanning FCS is similar. Here, the laser focus is moved in small circles, several µm in diameter. While pSFCS requires relatively large free standing membranes, cSFCS can be performed in µm sized homogeneous fluorescent regions of supported bilayers, cell membranes, as well as the apical poles of giant vesicles. Once the radius of the circular scan path has been accurately determined (e.g. with a fluorescent grid), the diffusion coefficients can be determined from cross-correlation curves without the need to calibrate the detection volume by diffusion standards \cite{Petrasek2010,Petrasek2008} (See \hyref{Figure}{fig:csfcsplot}). \begin{figure}[h] \centering \includegraphics[width=\linewidth]{PyCorrFit_Screenshot_CSFCS.png} \mycaption{user interface with external model function}{cSFCS curve of DiO diffusing in a reconstituted lipid bilayer on glass support. Fitting yields a diffusion coefficient of \SI{0.28}{\mu m^2s^{-1}} ($F1=1$, so only one component is fitted). The source code of the external model function for this fit is shown in \hyref{Figure}{fig:csfcs}.\label{fig:csfcsplot}} \end{figure} \vspace{1em} \subsubsection{Total internal reflection FCS (TIR-FCS)} \label{sec:theor.exten.total} TIR-FCS was developed to measure transient ligand binding events to receptors in the membrane \cite{Thompson2007}. In contrast to scanning FCS, the detection volume is fixed. However, the geometry is confined to the surface showing an exponential decay of the evanescent field along $z$, whereas the lateral boundaries imposed in $xy$ by a pinhole in the detection path. The situation is notoriously difficult to model and different solutions have been proposed. \paragraph{TIR-FCS with Gaussian-shaped lateral detection volume.} The detection volume is axially confined by an evanescent field and has an effective size of \begin{align} V = \pi R_0^2 d_\mathrm{eva} \end{align} where $R_0$ is the lateral extent of the detection volume and $d_\mathrm{eva}$ is the evanescent field depth\footnote{Where the field has decayed to $1/e$}. From the concentration $C$, the effective number of particles is $n = CV$. The decay constant $\kappa$ is the inverse of the depth $d_\mathrm{eva}$ : \begin{align} d_\mathrm{eva} = \frac{1}{\kappa} \end{align} \paragraph{TIR-FCS with a square-shaped lateral detection volume.} The detection volume is axially confined by an evanescent field of depth $d_\mathrm{eva} = 1 / \kappa$. The lateral detection area is a convolution of the point spread function of the microscope of size $\sigma$, \begin{align} \sigma = \sigma_0 \frac{\lambda}{\mathit{NA}}, \end{align} with a square of side length $a$. \subsection{Fitting} \label{sec:theor.nonle} One can define a distance $d(G,H)$ between two discrete functions $G$ and $H$ with the discrete domain of definition $\tau_1 \dots \tau_n$ as the sum of squares: \begin{equation} d(G,H) = \sum_{i=1}^n \left[ G(\tau_i) - H(\tau_i) \right]^2 \end{equation} The least-squares method minimizes this distance between the model function $G$ and the experimental values $H$ by modifying $k$ additional fitting parameters $\alpha_1, \dots, \alpha_k$: \begin{equation} \chi^2 = \min_{\alpha_1, \dots, \alpha_k} \sum_{i=1}^n \left[ G(\tau_i,\alpha_1, \dots, \alpha_k) - H(\tau_i) \right]^2 \end{equation} The minimum distance $\chi^2$ is used to characterize the success of a fit. Note, that if the number of fitting parameters $k$ becomes too large, multiple values for $\chi^2$ can be found, depending on the starting values of the $k$ parameters. \subsubsection{Weighted fitting} \label{sec:theor.weigh} In certain cases, it is useful to perform weighted fitting with a known variance $\sigma_i^2$ at the data points $\tau_i$. In \textit{PyCorrFit}, weighted fitting is implemented as follows: \begin{equation} \chi^2_\mathrm{weighted} = \min_{\alpha_1, \dots, \alpha_k} \sum_{i=1}^n \frac{\left[ G(\tau_i,\alpha_1, \dots, \alpha_k) - H(\tau_i) \right]^2}{\sigma_i^2} \end{equation} Besides importing the variance alongside experimental data, \textit{PyCorrFit} is able to estimate the variance from the experimental data via several different approaches. A recommended approach is averaging over several curves. Other approaches such as estimation of the variance from spline fits or from the model function (see \hyref{Section}{sec:intro.graph}) cannot be considered unbiased. Note that when performing global fits (see \hyref{Section}{sec:menub.tools.globa}), different types of weights for different correlation curves can strongly influence the result of the fit. Especially mixing curves with and without weights will most likely result in unphysical fits. \subsubsection{Displayed $\chi^2$ values} The displayed value of $\chi^2$ is defined by the type of the performed fit. This value is commonly normalized by the degrees of freedom $\nu = N - n - 1$, where $N$ is the number of observations (data points) and $n$ is the number of fitting parameters. \begin{itemize} \item \textbf{reduced expected sum of squares}: This value is used when there is no variance available for plot normalization. \begin{equation} \chi^2_\mathrm{red,exp} = \frac{1}{\nu}\sum_{i=1}^n \frac{\left[ G(\tau_i,\alpha_\mathrm{min}) - H(\tau_i) \right]^2}{G(\tau_i,\alpha_\mathrm{min})} \end{equation} \item \textbf{reduced weighted sum of squares}: This value is used when the fit was performed with variances $\sigma^2$. \begin{equation} \chi^2_\mathrm{red,weight} = \frac{1}{\nu}\sum_{i=1}^n \frac{\left[ G(\tau_i,\alpha_\mathrm{min}) - H(\tau_i) \right]^2}{\sigma^2} \end{equation} \item \textbf{reduced global sum of squares}: This value is used for global fits. The weights are computed identically to the situation with reduced weights, except that the variance $\sigma_\textrm{glob}^2$ may result in non-physical weighting (hence the emphasis on global). \begin{equation} \chi^2_\mathrm{red,weight} = \frac{1}{\nu}\sum_{i=1}^n \frac{\left[ G(\tau_i,\alpha_\mathrm{min}) - H(\tau_i) \right]^2}{\sigma_\textrm{glob}^2} \end{equation} \end{itemize} \subsubsection{Algorithms} \label{sec:theor.alg} \textit{PyCorrFit} uses the non-linear least-squares fitting capabilities from \texttt{scipy.optimize}. This package contains several algorithms to minimize the sum of the squares. PyCorrFit can utilize several algorithms to perform this minimization. The descriptions of the algorithms listed here are partly copied from the scipy documentation at \url{http://docs.scipy.org/doc/scipy/reference/optimize.html}. \begin{itemize} \item The \textbf{BFGS} method uses the quasi-Newton method of Broyden, Fletcher, Goldfarb, and Shanno (BFGS) \cite{Nocedal2006} (pp. 136). It uses the first derivatives only. BFGS has proven good performance even for non-smooth optimizations. \item The \textbf{Levenberg-Marquardt} algorithm \cite{Levenberg1944} uses the first derivatives and combines the Gauss–Newton algorithm with a trust region approach. It is very robust compared to other algorithms and it is very popular in curve-fitting. \textit{PyCorrFit} uses this algorithm by default. If this algorithm is used, \textit{PyCorrFit} can estimate an error of the fit parameters using the covariance matrix. \item The \textbf{Nelder-Mead} method uses the Simplex algorithm \cite{Nelder1965,Wright1996}. This algorithm has been successful in many applications but other algorithms using the first and/or second derivatives information might be preferred for their better performances and robustness in general. \item The method \textbf{Powell} is a modification of Powell's method \cite{Powell1964, Press} which is a conjugate direction method. It performs sequential one-dimensional minimizations along each vector of the directions set, which is updated at each iteration of the main minimization loop. The function need not be differentiable, and no derivatives are taken. \item \textbf{Sequential Linear Squares Programming} inherently accepts boundaries and thus might behave better than other algorithms for problems with bounded parameters. \end{itemize} \input{PyCorrFit_doc_models} \section{Troubleshooting} If you are having problems with PyCorrFit, you might find the solution in the frequently asked questions\footnote{\url{https://github.com/FCS-analysis/PyCorrFit/wiki/Frequently-Asked-Questions-\%28FAQ\%29}} or on other pages in the \textit{PyCorrFit} wiki\footnote{\url{https://github.com/FCS-analysis/PyCorrFit/wiki}}. There you will also find instructions on how to contact us to file a bug or to request a feature. pycorrfit-1.1.7/setup.cfg0000664000372000037200000000014213554643106016230 0ustar travistravis00000000000000[aliases] test = pytest [metadata] license_file = LICENSE [egg_info] tag_build = tag_date = 0 pycorrfit-1.1.7/CHANGELOG0000664000372000037200000003663413554642611015640 0ustar travistravis000000000000001.1.7 - docs: add sponsor links to contribute section - docs: add gallery with all images in docs/gallery 1.1.6 - fix: improve support for Confocor FCS file format (see discussion in #37) - ref: make pathlib.Path a standard in readfiles - code cleanup - drop support for Python<3.6 1.1.5 - docs: fix build with recent version of latex (#191) 1.1.4 - maintenance release 1.1.3 - maintenance release 1.1.2 - ci: Automated release to PyPI with appveyor and travis-ci - fix: support lmfit >= 0.9.11 1.1.1 - Fix plotting error with LaTeX (thanks @toubol) (#179) - Update documentation 1.1.0 - BREAKING CHANGE: Move to Python3 (thanks @toubol) (#173) - GUI: - Add option in Preferences to automatically close tools after usage - Add keyboard shortcuts (thanks @toubol) - Fix entry point for GUI (#172) - Refactor several asserts into raises 1.0.1 - Improved support for ALV ".ASC" file format (#169) - NumPy 0.13 support for ".ptu" file reader - Code cleanup: - Fetch latest available version from GitHub releases - New dependency for "simplejson" Python package - Move appveyor recipe to separate folder 1.0.0 - New confocal fitting models T+T+2D and T+T+3D - Fix regression: .sin files could not be opened (#167) 0.9.9 - Remove admin-requirement during install (Windows) - Support newer correlator.com .sin file format (experimental, #135) - Add smart progress dialog for fitting (#155) - Statistics: check "filename/title" by default (#151) - Documentation: fix bad LaTeX commands (#163) 0.9.8 - Bugfixes: - Indexing error when saving sessions (#154) - Page number truncated in csv export (#159) - Export of csv files used incorrect normalization (#153) - Normalization parameter was not displayed in the 'Info' tool 0.9.7 - Second triplet time is now larger than first triplet time by default - Remove a hack that causes a run through all pages e.g. when an average is created - Bugfixes: - Opening sessions with user-defined models - Saving sessions with comments containing non-ASCII characters - Windows build: Graphical plot export was misconfigured (added matplotlibrc patch in .spec file) 0.9.6 - Bugfixes: - Fixed minor wx sizer problems for the tools - Fixed 'AttributeError' in page.py if no weights are present - New confocal fitting models (#111): - 3D+3D, 2D+2D, 3D+2D; no triplet - T+T+2D+2D, T+T+3D+2D; double triplet - T+3D+3D+3D, T+3D+3D+2D (#40, #59) - Under the hood: - Separation of core and GUI modules - Include tests in distributions for PyPI - Improve automated testing on Windows and Mac OS - More constraint options for fitting 0.9.5 - Bugfixes - Closing the batch control window causes segfault bug (#142) - Closing page causes error when batch control is active (#143) - Plot normalization causes "Save Session" to fail (#144) - Plot normalization not loaded from session (#145) 0.9.4 - Batch control allows to select individual parameters (#108) - Allow to exclude pages from batch fitting (#107) - Bugfixes: - Fix 'ValueError' in parameter display - Possibly fixed error with 'yaml.safe_dump' on Mac OSx 10.8.5 - Make sure background is lower than signal (#137) 0.9.3 - Fitting: migrate to lmfit - This introduces a new dependency for building PyCorrFit. (e.g. in Debian, the package "python-lmfit" is required) - Improved fitting behavior at parameter boundaries - Removed "Polak-Ribiere" fitting algorithm - Added "Sequential Linear Squares Programming" algorithm - Heuristic fit (#109): - Detect parameters that are stuck during fitting - Fit each curve five times or less and check whether the fit converges. - If two diffusion time parameter exist in a model, always make sure that one parameter is the larger one. This feature can currently not be disabled (#110). - Allow infinity ("inf" and "-inf") parameters for models and boundaries. - New model: confocal T+T+3D+3D - Bugfixes: - Sessions saved with 64bit Windows were not opened (#136) - Old sessions and "KeyError: 'chi2'" - Old session file extension was not recognized (#106) 0.9.2 - Bugfixes: - "Slider Simulation"/"Parm Range" broken (#133) - Computation of average intensity did not work correctly for unequally spaced traces - Update .pt3 reader to version 8399ff7401 - Import traces of .pt3 files (experimental, #118) Warning: Absolute values for intensity might be wrong 0.9.1 - Tool 'Overlay curves': improve UI (#117) - Tool 'Statistics view': improve UI (#113) - Tool 'Trace view': display countrate (#121) - Bugfixes: - Unicode errors in statistics tool (#131) - Load session errors with empty pages 0.9.0 - Improve parameter display (#52, #114) - Display Chi2 on each page (#115) - The displayed Chi2-value for non-weighted fits is now normalized to the expected values of the fit. The documentation has been updated accordingly. - Add "All files" option in save dialogs (#97) - Improved plot export dialog (#99) 0.8.9 - Improved support for "ALV-7004" files (#104) - Increase resolution for image export - Load weights from PyCorrFit csv files - Tool 'Overlay Curves': show cropped correlation curves - Tool 'Trace view': increase size of window (#93) - Tool 'Global fitting': remove forced, joint weights - Session comment dialog: more intuitive behavior (#116) - Improve plot export (#95) - Bugfixes: - Weighted fits at borders of fit interval were computed incorrectly due to integer division - Fitting algorithms did not work (#94) - Creating averages did not work (#123) - ASCII errors in statistics tool (#112) - Under the hood: - Introduce new classes: Correlation, Fit, Trace - Code cleanup and rewrite to support planned features - In some cases support older versions of NumPy 0.8.8 - Improved support for "ALV-7004" files - If you install the GUI with pip, you now need to include the 'GUI' requirement: 'pip install pycorrfit[GUI]'. The GUI depends on matplotlib and wxPython which is not required for scripting with the pycorrfit module. - Bugfix: missing version string on SuSe linux (#101) - Under the hood: - Python entry point script replaces "bin/" script - Windows build system hosted by appveyor.com - MacOS X build system hosted by travis-ci.org - New builds use wxPython3 (#85) - Unicode support without 'reload(sys)' - Error messages are more verbose 0.8.7 - Removed unused fitting parameter d_eva from model 6022 and secured backwards compatibility. - Improved support for ALV700X (#92) - Bugfix: Corrected false display of Unicode characters on Windows - Under the hood: - Code cleanup with pyflakes - Repo cleanup (#98) 0.8.6 - Bugfix: Opening .fcs files with only one AC curve works now - Zip files with measurements may now contain subfolders - Improved pt3-file support from https://github.com/dwaithe/FCS_point_correlator (#89) 0.8.5 - Fixed bug that made it impossible to load data (#88) - Exceptions are now handled by wxPython - Under the hood: - Pythonic repository structure - Relative imports - Windows build machine is now Windows 7 - Removed strict dependency on matplotlib 0.8.4 - Support for PicoQuant data file format Many thanks to Dominic Waithe (@dwaithe) - Improved compatibility with Zeiss .fcs file format - PyCorrFit is now dependent on Cython - The module 'openfile' is now available from within Python - Installer for Windows 0.8.3 - New .pcfs (PyCorrFit Session) file format (#60) - Additional fitting algorithms: Nelder-Mead, BFGS, Powell, Polak-Ribiere (#71) - Improvements - Massive speed-up when working with large data sets (#77) - Plot export: legend position and displayed parameters (#54) - Average tool: traces may now start at time points != 0 - Statistics tool: display on smaller screens - ALV data files: updated parser to identify curve types and segment traces - Zeiss ConfoCor3 data files: some files could not be opened due to dummy AC curves - Models: default parameters were changed to prevent unstable fits - Software: notification dialogs for missing modules or other software - Bugfixes - User could accidently clear a session (#65) - wxPython plotting problem on MacOSx (#64) - Statistics view: some parameters were duplicated (#76) - Caught zero-division warnings (models with triplet component) - Corrected x-axis scaling of statistics view and trace view 0.8.2 - The documentation has been thoroughly reworked - The user is now warned if he does not have a TeX distribution installed - Improvements: - Complete support for installing PyCorrFit with virtualenv and pip (This is documented in the wiki) - Statistics tool now displays average and SD (#43) - Bugfix: TeX did not work on Ubuntu due to missing imports 0.8.1 - Thanks to Alex Mestiashvili for providing initial setup.py files and for debianizing PyCorrFit (@mestia) - Thanks to Thomas Weidemann for his contributions to the documentation (@weidemann) - Bugfixes - Some ConfoCor files were not imported - The cpp was not calculated correctly in case of background correction (#45) - Enabled averaging of single pages (#58) - Background correction for cross-correlation data is now computed (#46) - Improvements of the user interface - The menus have been reordered (#47, #50) - The fitting panel has been optimized (#49) - the slider simulation got a reset button (#51) - The Help menu contains documentation and wiki (#56) - Model functions that are somehow redundant have been removed from the menu, but are still supported - The model doc strings were fully converted to Unicode - Several text messages were modified for better coherence - The background correction tool is more intuitive - Statistics panel improvements (#43) - Run information is included in the Data set title - The page counter starts at "1" instead of "0" (#44) - New handling of background correction (#46, #53) 0.8.0 - Filename/title of each tab now shows up in the notebook (#39) - Statistics tool can plot parameters and page selection with the Overlay tool is possible (#31) 0.7.9 - Support for Mac OSx - Enhancements: - Export file format (.csv) layout improved - Model function info text in UTF-8 - Improved waring message when opening sessions from future versions - New feature lets user set the range for the fitting parameters - Bugfixes: - Cleaned minor tracebacks and exceptions created by the frontend - Mac version now works as expected, but .app bundling failed - Latex plotting features now support more characters such as "[]{}^" 0.7.8 - Enhancements: - Averages can now be calculated from user-selected pages - Pages selected in the Overlay tool are now automatically set for computation of average and for global fitting - Source pages are now displayed in average title - Graph normalization with particle numbers is now possible - Bugfixes: - Errors during fitting with weights equal to zero - Overlay tool displayed last curve after all pages have been removed - Global fit did not work with weights - Session saving now uses 20 digits accuracy - CSV export is now using tab-delimited data for easier Excel-import - Added version checking for session management 0.7.7 - Fixed: Tools windows could not be closed (or moved on MS Windows) - Fixed: .csv export failed in some cases where no weights were used - Enhancement: The user is now asked before a page is closed - Enhancement: Tool "Page Info" and in exported .csv files, variables and values are now separated by a tab stop instead of a "=" - Fixed: Opening a session with an empty page failed in some cases - Fixed: Tool "Statistics" missed to output the column "filename/title" if that key value is empty - replaced empty strings with "NoName" - Enhancement: Tool "Overlay" now asks the user to check kept curves instead of showing the curves to be removed - Enhancement: Tool "Overlay" now has a "Cancel" button 0.7.6 - Improved handling - Tools are now sorted according to a standard work-flow - Renamed "Curve selection" to "Overlay tool" - this is more intuitive - Tools will now stay open or may be opened when there are no open pages (#25) - Filenames and runs are now displayed on each page (also added filename/title tag) (#23) - Notebook: moved close button to each tab to prevent accidental closing of tabs - Improved tool "Statistics" (#21) - Fixed the case where "useless" data was produced - instead we write "NaN" data, removed warning message accordingly - Row-wise ordering according to page numbers (#22) - Column-wise ordering is now more intuitive (Fitted parameters with errors first) - Some columns are now checked by default - PyCorrFit remembers checked parameters for a page (not saved in session) - Improved tool "Overlay" (#23) - New feature: Overlay shows run number of each file (upon import), the run (or index) of an experimental file is unique to PyCorrFit - Upon import, filenames and runs are displayed - In a session, the filename/title is displayed - Web address of PyCorrFit changed from "fcstools.dyndns.org/pycorrfit" to "pycorrfit.craban.de" - Minor bugfixes: Batch control, Global fitting, import dialog 0.7.5 - Added model functions to documentation. - Weights from fitting are now exported in .csv files. - Rework of info panel for fitting - Cleared unintuitive behavior of session saving: The fitting parameters were read from the frontend. This could have led to saving false fit meta data. - During fitting, units are now displayed as "human readable" (#17). - Slider simulation now also uses human readable units (#17). - Secured support for Ubuntu 12.10 and 13.04 - Fixed: new line (\n) characters for LaTeX plotting on Windows 0.7.4 - New tool: Colorful curve selection - Import data: Curve selection possible - Average: Crop average according to current page. - Fixed: Page now displays Chi-squared of global fit. - Fixed: Chi-squared and parameters of global fitting are now stored in sessions. 0.7.3 - Issue closed. External weights from averages saved in session (#11). - Solved minor bugs - Added estimation of errors of fit (Issue #12/#14) - Fixed: Some .fcs files containing averages were not imported. 0.7.2 - Bugfix: Issue #10; we now have a goodness of the fit, if weighted fitting is performed - Bugfix: Weights for fitting not properly calculated (sqrt(std)). - Bugfix: Batch control IndexError with Info window opened - Tool Statistics: Sort values according to page numbers - Tool global: Added weighted fitting - Residuals: According to weighted fitting, weighted residuals are plotted - Average: Variances from averaging can be used for weighted fitting 0.7.1 - Feature: Added Python shell - Bugfix: Saving image was not performed using WXAgg - Bugfix: Notebook pages were drag'n'dropable - Update function now works in its own thread - Code cleanup: documentation of model functions - Added program icon 0.7.0 - File import dialog was enhanced (#4, #5 - subsequently #7, #8): - Now there is only one "load data" dialog in the file menu - The model function is chosen for each type of data that is to be imported (AC, CC, etc.) - Bugfix: Channel selection window causes crash on file import (#1). - Bugfix: Hidden feature changes fixed parameters during fitting (#2). - Feature: Convert TIR model function parameters lambda and NA to sigma (#3). - Code cleanup: Opening data files is now handled internally differently. 0.6.9 - Initital GitHub commit pycorrfit-1.1.7/LICENSE0000664000372000037200000003660713554642611015433 0ustar travistravis00000000000000Copyright (C) 2011-2012 Paul Müller PyCorrFit is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. GNU GENERAL PUBLIC LICENSE Version 2, June 1991 Copyright (C) 1989, 1991 Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA Everyone is permitted to copy and distribute verbatim copies of this license document, but changing it is not allowed. Preamble The licenses for most software are designed to take away your freedom to share and change it. By contrast, the GNU General Public License is intended to guarantee your freedom to share and change free software--to make sure the software is free for all its users. This General Public License applies to most of the Free Software Foundation's software and to any other program whose authors commit to using it. (Some other Free Software Foundation software is covered by the GNU Lesser General Public License instead.) You can apply it to your programs, too. When we speak of free software, we are referring to freedom, not price. Our General Public Licenses are designed to make sure that you have the freedom to distribute copies of free software (and charge for this service if you wish), that you receive source code or can get it if you want it, that you can change the software or use pieces of it in new free programs; and that you know you can do these things. To protect your rights, we need to make restrictions that forbid anyone to deny you these rights or to ask you to surrender the rights. These restrictions translate to certain responsibilities for you if you distribute copies of the software, or if you modify it. For example, if you distribute copies of such a program, whether gratis or for a fee, you must give the recipients all the rights that you have. You must make sure that they, too, receive or can get the source code. And you must show them these terms so they know their rights. We protect your rights with two steps: (1) copyright the software, and (2) offer you this license which gives you legal permission to copy, distribute and/or modify the software. Also, for each author's protection and ours, we want to make certain that everyone understands that there is no warranty for this free software. If the software is modified by someone else and passed on, we want its recipients to know that what they have is not the original, so that any problems introduced by others will not reflect on the original authors' reputations. Finally, any free program is threatened constantly by software patents. We wish to avoid the danger that redistributors of a free program will individually obtain patent licenses, in effect making the program proprietary. To prevent this, we have made it clear that any patent must be licensed for everyone's free use or not licensed at all. The precise terms and conditions for copying, distribution and modification follow. GNU GENERAL PUBLIC LICENSE TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION 0. This License applies to any program or other work which contains a notice placed by the copyright holder saying it may be distributed under the terms of this General Public License. The "Program", below, refers to any such program or work, and a "work based on the Program" means either the Program or any derivative work under copyright law: that is to say, a work containing the Program or a portion of it, either verbatim or with modifications and/or translated into another language. (Hereinafter, translation is included without limitation in the term "modification".) Each licensee is addressed as "you". Activities other than copying, distribution and modification are not covered by this License; they are outside its scope. The act of running the Program is not restricted, and the output from the Program is covered only if its contents constitute a work based on the Program (independent of having been made by running the Program). Whether that is true depends on what the Program does. 1. You may copy and distribute verbatim copies of the Program's source code as you receive it, in any medium, provided that you conspicuously and appropriately publish on each copy an appropriate copyright notice and disclaimer of warranty; keep intact all the notices that refer to this License and to the absence of any warranty; and give any other recipients of the Program a copy of this License along with the Program. You may charge a fee for the physical act of transferring a copy, and you may at your option offer warranty protection in exchange for a fee. 2. You may modify your copy or copies of the Program or any portion of it, thus forming a work based on the Program, and copy and distribute such modifications or work under the terms of Section 1 above, provided that you also meet all of these conditions: a) You must cause the modified files to carry prominent notices stating that you changed the files and the date of any change. b) You must cause any work that you distribute or publish, that in whole or in part contains or is derived from the Program or any part thereof, to be licensed as a whole at no charge to all third parties under the terms of this License. c) If the modified program normally reads commands interactively when run, you must cause it, when started running for such interactive use in the most ordinary way, to print or display an announcement including an appropriate copyright notice and a notice that there is no warranty (or else, saying that you provide a warranty) and that users may redistribute the program under these conditions, and telling the user how to view a copy of this License. (Exception: if the Program itself is interactive but does not normally print such an announcement, your work based on the Program is not required to print an announcement.) These requirements apply to the modified work as a whole. If identifiable sections of that work are not derived from the Program, and can be reasonably considered independent and separate works in themselves, then this License, and its terms, do not apply to those sections when you distribute them as separate works. But when you distribute the same sections as part of a whole which is a work based on the Program, the distribution of the whole must be on the terms of this License, whose permissions for other licensees extend to the entire whole, and thus to each and every part regardless of who wrote it. Thus, it is not the intent of this section to claim rights or contest your rights to work written entirely by you; rather, the intent is to exercise the right to control the distribution of derivative or collective works based on the Program. In addition, mere aggregation of another work not based on the Program with the Program (or with a work based on the Program) on a volume of a storage or distribution medium does not bring the other work under the scope of this License. 3. You may copy and distribute the Program (or a work based on it, under Section 2) in object code or executable form under the terms of Sections 1 and 2 above provided that you also do one of the following: a) Accompany it with the complete corresponding machine-readable source code, which must be distributed under the terms of Sections 1 and 2 above on a medium customarily used for software interchange; or, b) Accompany it with a written offer, valid for at least three years, to give any third party, for a charge no more than your cost of physically performing source distribution, a complete machine-readable copy of the corresponding source code, to be distributed under the terms of Sections 1 and 2 above on a medium customarily used for software interchange; or, c) Accompany it with the information you received as to the offer to distribute corresponding source code. (This alternative is allowed only for noncommercial distribution and only if you received the program in object code or executable form with such an offer, in accord with Subsection b above.) The source code for a work means the preferred form of the work for making modifications to it. For an executable work, complete source code means all the source code for all modules it contains, plus any associated interface definition files, plus the scripts used to control compilation and installation of the executable. However, as a special exception, the source code distributed need not include anything that is normally distributed (in either source or binary form) with the major components (compiler, kernel, and so on) of the operating system on which the executable runs, unless that component itself accompanies the executable. If distribution of executable or object code is made by offering access to copy from a designated place, then offering equivalent access to copy the source code from the same place counts as distribution of the source code, even though third parties are not compelled to copy the source along with the object code. 4. You may not copy, modify, sublicense, or distribute the Program except as expressly provided under this License. Any attempt otherwise to copy, modify, sublicense or distribute the Program is void, and will automatically terminate your rights under this License. However, parties who have received copies, or rights, from you under this License will not have their licenses terminated so long as such parties remain in full compliance. 5. You are not required to accept this License, since you have not signed it. However, nothing else grants you permission to modify or distribute the Program or its derivative works. These actions are prohibited by law if you do not accept this License. Therefore, by modifying or distributing the Program (or any work based on the Program), you indicate your acceptance of this License to do so, and all its terms and conditions for copying, distributing or modifying the Program or works based on it. 6. Each time you redistribute the Program (or any work based on the Program), the recipient automatically receives a license from the original licensor to copy, distribute or modify the Program subject to these terms and conditions. You may not impose any further restrictions on the recipients' exercise of the rights granted herein. You are not responsible for enforcing compliance by third parties to this License. 7. If, as a consequence of a court judgment or allegation of patent infringement or for any other reason (not limited to patent issues), conditions are imposed on you (whether by court order, agreement or otherwise) that contradict the conditions of this License, they do not excuse you from the conditions of this License. If you cannot distribute so as to satisfy simultaneously your obligations under this License and any other pertinent obligations, then as a consequence you may not distribute the Program at all. For example, if a patent license would not permit royalty-free redistribution of the Program by all those who receive copies directly or indirectly through you, then the only way you could satisfy both it and this License would be to refrain entirely from distribution of the Program. If any portion of this section is held invalid or unenforceable under any particular circumstance, the balance of the section is intended to apply and the section as a whole is intended to apply in other circumstances. It is not the purpose of this section to induce you to infringe any patents or other property right claims or to contest validity of any such claims; this section has the sole purpose of protecting the integrity of the free software distribution system, which is implemented by public license practices. Many people have made generous contributions to the wide range of software distributed through that system in reliance on consistent application of that system; it is up to the author/donor to decide if he or she is willing to distribute software through any other system and a licensee cannot impose that choice. This section is intended to make thoroughly clear what is believed to be a consequence of the rest of this License. 8. If the distribution and/or use of the Program is restricted in certain countries either by patents or by copyrighted interfaces, the original copyright holder who places the Program under this License may add an explicit geographical distribution limitation excluding those countries, so that distribution is permitted only in or among countries not thus excluded. In such case, this License incorporates the limitation as if written in the body of this License. 9. The Free Software Foundation may publish revised and/or new versions of the General Public License from time to time. Such new versions will be similar in spirit to the present version, but may differ in detail to address new problems or concerns. Each version is given a distinguishing version number. If the Program specifies a version number of this License which applies to it and "any later version", you have the option of following the terms and conditions either of that version or of any later version published by the Free Software Foundation. If the Program does not specify a version number of this License, you may choose any version ever published by the Free Software Foundation. 10. If you wish to incorporate parts of the Program into other free programs whose distribution conditions are different, write to the author to ask for permission. For software which is copyrighted by the Free Software Foundation, write to the Free Software Foundation; we sometimes make exceptions for this. Our decision will be guided by the two goals of preserving the free status of all derivatives of our free software and of promoting the sharing and reuse of software generally. NO WARRANTY 11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING, REPAIR OR CORRECTION. 12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGES. END OF TERMS AND CONDITIONS pycorrfit-1.1.7/README.rst0000664000372000037200000000244713554642611016110 0ustar travistravis00000000000000|PyCorrFit| =========== |PyPI Version| |Build Status Win| |Build Status Travis| |Coverage Status| |Docs Status| Documentation ------------- The documentation of PyCorrFit is available at https://pycorrfit.readthedocs.org. Problems -------- If you find a bug or need help with a specific topic, do not hesitate to ask a question at the `issues page `__. .. |PyCorrFit| image:: https://raw.github.com/FCS-analysis/PyCorrFit/master/doc/Images/PyCorrFit_logo_dark.png .. |PyPI Version| image:: https://img.shields.io/pypi/v/PyCorrFit.svg :target: https://pypi.python.org/pypi/pycorrfit .. |Build Status Win| image:: https://img.shields.io/appveyor/ci/paulmueller/PyCorrFit/master.svg?label=win :target: https://ci.appveyor.com/project/paulmueller/pycorrfit .. |Build Status Travis| image:: https://img.shields.io/travis/FCS-analysis/PyCorrFit/master.svg?label=linux_osx :target: https://travis-ci.org/FCS-analysis/PyCorrFit .. |Coverage Status| image:: https://img.shields.io/codecov/c/github/FCS-analysis/PyCorrFit/master.svg :target: https://codecov.io/gh/FCS-analysis/PyCorrFit .. |Docs Status| image:: https://readthedocs.org/projects/pycorrfit/badge/?version=latest :target: https://readthedocs.org/projects/pycorrfit/builds/ pycorrfit-1.1.7/setup.py0000664000372000037200000000620513554642611016127 0ustar travistravis00000000000000from os.path import join, dirname, realpath, exists from setuptools import setup, Extension, find_packages import sys # The next three lines are necessary for setup.py install to include # ChangeLog and Documentation of PyCorrFit from distutils.command.install import INSTALL_SCHEMES for scheme in INSTALL_SCHEMES.values(): scheme['data'] = scheme['purelib'] # We don't need to cythonize if a .whl package is available. try: import numpy as np except ImportError: print("NumPy not available. Building extensions "+ "with this setup script will not work:", sys.exc_info()) extensions = [] else: extensions = [Extension("pycorrfit.readfiles.read_pt3_scripts.fib4", sources=["pycorrfit/readfiles/read_pt3_scripts/fib4.pyx"], include_dirs=[np.get_include()] )] try: import urllib.request except ImportError: pass else: # Download documentation if it was not compiled with latex pdfdoc = join(dirname(realpath(__file__)), "doc/PyCorrFit_doc.pdf") webdoc = "https://github.com/FCS-analysis/PyCorrFit/wiki/PyCorrFit_doc.pdf" if not exists(pdfdoc): print("Downloading {} from {}".format(pdfdoc, webdoc)) try: urllib.request.urlretrieve(webdoc, pdfdoc) except: print("Failed to download documentation.") # Parameters author = u"Paul Müller" authors = [author] description = 'Scientific tool for fitting correlation curves on a logarithmic plot.' name = 'pycorrfit' year = "2014" sys.path.insert(0, realpath(dirname(__file__))+"/"+name) try: from _version import version except: version = "unknown" setup( author=author, author_email='dev@craban.de', data_files=[('pycorrfit_doc', ['CHANGELOG', 'doc/PyCorrFit_doc.pdf'])], description=description, long_description=open('README.rst').read() if exists('README.rst') else '', include_package_data=True, license="GPL v2", name=name, platforms=['ALL'], url='https://github.com/FCS-analysis/PyCorrFit', version=version, # data files packages=find_packages(include=(name+"*",)), package_dir={name: name}, # cython ext_modules = extensions, # requirements install_requires=[ "lmfit >= 0.9.2", "numpy >= 1.14.2", "pyyaml >= 3.12", "scipy >= 1.0.1", ], extras_require = { # Graphical User Interface (pip install pycorrfit[GUI]) 'GUI': ["matplotlib >= 2.2.2", "sympy >= 1.1.1", "simplejson", # for updates "wxPython >= 4.0.1", ], }, setup_requires=["cython", 'numpy', 'pytest-runner'], tests_require=["pytest", "urllib3", "simplejson"], python_requires='>=3.6, <4', # scripts entry_points={ "gui_scripts": ["pycorrfit=pycorrfit.gui.main:Main"] }, keywords=["fluorescence correlation spectroscopy", ], classifiers= [ 'Operating System :: OS Independent', 'Programming Language :: Python :: 3', 'Topic :: Scientific/Engineering :: Visualization', 'Intended Audience :: Science/Research' ], ) pycorrfit-1.1.7/tests/0000775000372000037200000000000013554643106015554 5ustar travistravis00000000000000pycorrfit-1.1.7/tests/test_fit_models.py0000664000372000037200000001104513554642611021313 0ustar travistravis00000000000000""" Go through each model, vary one parameter and fit it back to the default value of that model. """ import numpy as np import pycorrfit from pycorrfit.correlation import Correlation from pycorrfit.fit import Fit import pytest # GLOBAL PARAMETERS FOR THIS TEST: TAUMIN = 1e-3 TAUMAX = 1e6 TAULEN = 100 FITALG = "Lev-Mar" def fit_single_parameter(modelid, fullparms, parmid, parmval, noise=False): """ Use the full parameter set `fullparms` and leave a single parameter `parmid` variable during the fit. Returns the fitted value of the parameter with index `parmid` """ corr = Correlation(fit_model=modelid, fit_algorithm=FITALG, verbose=0) tau = np.exp(np.linspace(np.log(TAUMIN), np.log(TAUMAX), TAULEN)) # Create artificial data by using the current fit_model data = corr.fit_model(fullparms, tau) if noise: if noise is True: deltanoise = (np.max(data)-np.min(data))/20 else: deltanoise = (np.max(data)-np.min(data))*noise anoise = (np.random.random(data.shape[0])-.5)*deltanoise data += anoise # Add artificial data to data set corr.correlation = np.dstack((tau, data))[0] # Set variable parameters fit_bool = np.zeros(fullparms.shape[0]) fit_bool[parmid] = True corr.fit_parameters_variable = fit_bool fullparms_edit = fullparms.copy() fullparms_edit[parmid] = parmval corr.fit_parameters = fullparms_edit Fit(corr) return corr.fit_parameters[parmid] def deviate_parameter(model, parmid): """ Returns a deviated version of the parameter with id `parmid`. Performs model checks to ensure the new value is physical. """ val = model.default_values[parmid] if val == 0: val += .1 else: val *= .9 return val @pytest.mark.filterwarnings('ignore::pycorrfit.fit.StuckParameterWarning') def test_fit_single_parameter(): """ Deviate a single parameter and fit it back. """ allow_fail = [ [6082, "SP"], ] faillist = list() for model in pycorrfit.models.models: fullparms = model.default_values for ii, val in enumerate(fullparms): newval = deviate_parameter(model, ii) fitval = fit_single_parameter(model.id, fullparms, ii, newval, noise=False) # print(val-fitval) if not np.allclose([val], [fitval]): if not [model.id, model.parameters[0][ii]] in allow_fail: faillist.append([model.id, model.parameters[0][ii], val, fitval]) if faillist: raise ValueError("Model tests failed for:\n", faillist) def fit_single_parameter_with_noise(noise=0.005): """ Deviate a single parameter and fit it back. """ faillist = list() succlist = list() for model in pycorrfit.models.models: fullparms = model.default_values for ii, val in enumerate(fullparms): newval = deviate_parameter(model, ii) fitval = fit_single_parameter(model.id, fullparms, ii, newval, noise=noise) if not np.allclose([val], [fitval], atol=.1, rtol=.1): faillist.append([model.id, model.parameters[0][ii], val, fitval]) else: succlist.append([model.id, model.parameters[0][ii], val, fitval]) return succlist, faillist @pytest.mark.filterwarnings('ignore::pycorrfit.fit.StuckParameterWarning') def test_fit_single_parameter_with_noise_one_permille(): succlist, faillist = fit_single_parameter_with_noise(noise=0.001) if len(faillist)/len(succlist) > .01: raise ValueError("Model tests failed for:\n", faillist) @pytest.mark.filterwarnings('ignore::pycorrfit.fit.StuckParameterWarning') def test_fit_single_parameter_with_noise_two_percent(): succlist, faillist = fit_single_parameter_with_noise(noise=0.02) if len(faillist)/len(succlist) > .05: raise ValueError("Model tests failed for:\n", faillist) @pytest.mark.filterwarnings('ignore::pycorrfit.fit.StuckParameterWarning') def test_fit_single_parameter_with_noise_five_percent(): succlist, faillist = fit_single_parameter_with_noise(noise=0.05) if len(faillist)/len(succlist) > .10: raise ValueError("Model tests failed for:\n", faillist) if __name__ == "__main__": # Run all tests loc = locals() for key in list(loc.keys()): if key.startswith("test_") and hasattr(loc[key], "__call__"): loc[key]() pycorrfit-1.1.7/tests/test_fit_model_gaussian.py0000664000372000037200000003055013554642611023024 0ustar travistravis00000000000000"""Check known model parameters and cross-check across models""" from pycorrfit import models as mdls # GLOBAL PARAMETERS FOR THIS TEST: TAU = 1.468e-6 def test_6001(): # 2D model = mdls.modeldict[6001] parms = [4.874, 0.2476, 0.015] assert abs(model(parms, tau=TAU) - 0.22016907491127263) < 1e-14 def test_6002(): # T+2D model = mdls.modeldict[6002] # n τ_diff τ_trip T offset parms = [4.891, 0.853, 0.00141, 0.0121, 0.034] assert abs(model(parms, tau=TAU) - 0.24095843709396209) < 1e-14 model2 = mdls.modeldict[6001] parms2 = [4.891, 0.853, 0.034] parms1 = [4.891, 0.853, 0.0, 0.0, 0.034] assert abs(model(parms1, tau=TAU) - model2(parms2, tau=TAU)) < 1e-14 def test_6031(): # T+2D+2D model = mdls.modeldict[6031] parms = [2.487, # n 3.4325, # taud1 2534, # taud2 0.153, # F 0.879, # alpha 0.00123, # tautrip 0.0314, # T 0.00021] # offset assert abs(model(parms, tau=TAU) - 0.41629799102222742) < 1e-14 model2 = mdls.modeldict[6002] parms2 = [4.891, 0.853, 0.0012, 0.108, 0.034] parms1 = [4.891, # n 0.853, # taud1 1.0, # taud2 1.0, # F 1.0, # alpha 0.0012, # tautrip 0.108, # T 0.034] # offset assert abs(model(parms1, tau=TAU) - model2(parms2, tau=TAU)) < 1e-14 def test_6011(): # T+3D model = mdls.modeldict[6011] # n T τ_trip τ_diff SP offset parms = [2.168, 0.1682, 0.00028, 0.54, 5.864, 0.0053] assert abs(model(parms, tau=TAU) - 0.55933660640533278) < 1e-14 model2 = mdls.modeldict[6012] parms2 = [2.168, 0.54, 5.864, 0.0053] parms1 = [2.168, 0, 1.0, 0.54, 5.864, 0.0053] assert abs(model(parms1, tau=TAU) - model2(parms2, tau=TAU)) < 1e-14 def test_6012(): # 3D model = mdls.modeldict[6012] parms = [2.168, 0.54, 5.864, 0.0053] assert abs(model(parms, tau=TAU) - 0.46655334038750634) < 1e-14 def test_6030(): # T+3D+3D model = mdls.modeldict[6030] parms = [2.153, # n 5.54, # taud1 1532, # taud2 0.4321, # F 4.4387, # SP 0.9234, # alpha 0.002648, # tautrip 0.1151, # T 0.008] # offset assert abs(model(parms, tau=TAU) - 0.53367456244118261) < 1e-14 model2 = mdls.modeldict[6011] # n T τ_trip τ_diff SP offset parms2 = [2.168, 0.1682, 0.00028, 0.54, 5.864, 0.0053] parms1 = [2.168, # n 0.54, # taud1 1.0, # taud2 1.0, # F 5.864, # SP 0.9234, # alpha 0.00028, # tautrip 0.1682, # T 0.0053] # offset assert abs(model(parms1, tau=TAU) - model2(parms2, tau=TAU)) < 1e-14 def test_6032(): # T+3D+2D model = mdls.modeldict[6032] parms = [1.58, # n 3548, # taud2D 0.351, # taud3D 0.345, # F3D 4.984, # SP 0.879, # alpha 0.0014, # tautrip 0.108, # T 0.008] # offset assert abs(model(parms, tau=TAU) - 0.72001694812574801) < 1e-14 # ->T+3D model2 = mdls.modeldict[6011] # n T τ_trip τ_diff SP offset parms2 = [2.168, 0.1682, 0.0028, 0.54, 5.864, 0.0053] parms1a = [2.168, # n 1.0, # taud2D 0.54, # taud3D 1.0, # F3D 5.864, # SP 0.879, # alpha 0.0028, # tautrip 0.1682, # T 0.0053] # offset assert abs(model(parms1a, tau=TAU) - model2(parms2, tau=TAU)) < 1e-14 # ->T+2D model3 = mdls.modeldict[6002] # n τ_diff τ_trip T offset parms3 = [4.891, 0.853, 0.00141, 0.0121, 0.034] parms1b = [4.891, # n 0.853, # taud2D 1.0, # taud3D 0.0, # F3D 1.0, # SP 0.879, # alpha 0.00141, # tautrip 0.0121, # T 0.034] # offset assert abs(model(parms1b, tau=TAU) - model3(parms3, tau=TAU)) < 1e-14 def test_6043(): # TT+3D+3D model = mdls.modeldict[6043] parms = [1.452, # n 4.48, # taud1 8438, # taud2 0.425, # F 5.43, # SP 0.876, # alpha 0.0012, # tautrip1 0.0101, # T1 0.0021, # tautrip2 0.0102, # T2 0.00004] # offset assert abs(model(parms, tau=TAU) - 0.70599013426715551) < 1e-14 # ->T+3D+3D model2 = mdls.modeldict[6030] parms2 = [2.153, # n 5.54, # taud1 1532, # taud2 0.4321, # F 4.4387, # SP 0.9234, # alpha 0.002648, # tautrip 0.1151, # T 0.008] # offset parms1 = [2.153, # n 5.54, # taud1 1532, # taud2 0.4321, # F 4.4387, # SP 0.9234, # alpha 0.002648, # tautrip1 0.1151, # T1 0.0021, # tautrip2 0.0, # T2 0.008] # offset assert abs(model(parms1, tau=TAU) - model2(parms2, tau=TAU)) < 1e-14 def test_6044(): # TT+2D+2D model = mdls.modeldict[6044] parms = [1.452, # n 4.48, # taud1 8438, # taud2 0.425, # F 0.876, # alpha 0.0012, # tautrip1 0.0101, # T1 0.0021, # tautrip2 0.0102, # T2 0.00004] # offset assert abs(model(parms, tau=TAU) - 0.70599013619282502) < 1e-14 # ->T+2D+2D model2 = mdls.modeldict[6031] parms2 = [2.153, # n 5.54, # taud1 1532, # taud2 0.4321, # F 0.9234, # alpha 0.002648, # tautrip 0.1151, # T 0.008] # offset parms1 = [2.153, # n 5.54, # taud1 1532, # taud2 0.4321, # F 0.9234, # alpha 0.002648, # tautrip1 0.1151, # T1 0.0021, # tautrip2 0.0, # T2 0.008] # offset assert abs(model(parms1, tau=TAU) - model2(parms2, tau=TAU)) < 1e-14 def test_6045(): # TT+3D+2D model = mdls.modeldict[6045] parms = [25.123, # n 240.123, # taud2D 0.1125, # taud3D 0.3512, # F3D 5.312, # SP 0.87671, # alpha 0.0021987, # tautrip1 0.032341, # T1 0.0013243, # tautrip2 0.014341, # T2 0.12310] # offset assert abs(model(parms, tau=TAU) - 0.16498917764250026) < 1e-14 # ->T+3D+2D model2 = mdls.modeldict[6032] parms2 = [25.123, # n 240.123, # taud2D 0.1125, # taud3D 0.3512, # F3D 5.312, # SP 0.87671, # alpha 0.0021987, # tautrip1 0.032341, # T1 0.12310] # offset parms1a = [25.123, # n 240.123, # taud2D 0.1125, # taud3D 0.3512, # F3D 5.312, # SP 0.87671, # alpha 0.0021987, # tautrip1 0.032341, # T1 0.1, # tautrip2 0.0, # T2 0.12310] # offset parms1b = [25.123, # n 240.123, # taud2D 0.1125, # taud3D 0.3512, # F3D 5.312, # SP 0.87671, # alpha 0.1, # tautrip1 0.0, # T1 0.0021987, # tautrip2 0.032341, # T2 0.12310] # offset assert abs(model(parms1a, tau=TAU) - model2(parms2, tau=TAU)) < 1e-14 assert abs(model(parms1b, tau=TAU) - model2(parms2, tau=TAU)) < 1e-14 def test_6081(): # T+3D+3D+3D model = mdls.modeldict[6081] parms = [1.412, # n 4.498, # taud1 245, # taud2 2910, # taud3 0.123, # F1 0.321, # F3 5.12, # SP 0.876, # alpha21 0.378, # alpha31 0.0021, # tautrip 0.021, # T -0.0004] # offset assert abs(model(parms, tau=TAU) - 0.85970140411643392) < 1e-14 # ->T+3D+3D model2 = mdls.modeldict[6030] parms2 = [2.153, # n 1.120, # taud1 30.120, # taud2 0.4321, # F 4.4387, # SP 0.4321, # alpha 0.002, # tautrip 0.1151, # T 1.2008] # offset parmsa = [2.153, # n 1.120, # taud1 30.120, # taud2 100.00, # taud3 0.4321, # F1 1-0.4321, # F2 4.4387, # SP 0.4321, # alpha21 1, # alpha31 0.002, # tautrip 0.1151, # T 1.2008] # offset parmsb = [2.153, # n 1.120, # taud1 10.000, # taud2 30.120, # taud3 0.4321, # F1 0, # F2 4.4387, # SP 1, # alpha21 .4321, # alpha31 0.002, # tautrip 0.1151, # T 1.2008] # offset assert abs(model(parmsa, tau=TAU) - model2(parms2, tau=TAU)) < 1e-14 assert abs(model(parmsb, tau=TAU) - model2(parms2, tau=TAU)) < 1e-14 def test_6082(): # T+3D+3D+2D model = mdls.modeldict[6082] parms = [1.412, # n 4.498, # taud1 245, # taud2 2910, # taud3 0.123, # F1 0.321, # F3 5.12, # SP 0.876, # alpha21 0.378, # alpha31 0.0021, # tautrip 0.021, # T -0.0004] # offset assert abs(model(parms, tau=TAU) - 0.85970140411789908) < 1e-14 # ->T+3D+2D model2 = mdls.modeldict[6032] parms2 = [2.153, # n 30.120, # taud1 1.234, # taud2 0.4321, # F 4.4387, # SP 1.341, # alpha 0.002, # tautrip 0.1151, # T 1.2008] # offset parmsa = [2.153, # n 1.234, # taud1 1, # taud2 30.120, # taud3 0.4321, # F1 0, # F2 4.4387, # SP 1., # alpha21 1/1.341, # alpha31 0.002, # tautrip 0.1151, # T 1.2008] # offset parmsb = [2.153, # n 1, # taud1 1.234, # taud2 30.120, # taud3 0, # F1 0.4321, # F2 4.4387, # SP 1, # alpha21 1/1.341, # alpha31 0.002, # tautrip 0.1151, # T 1.2008] # offset assert abs(model(parmsa, tau=TAU) - model2(parms2, tau=TAU)) < 1e-14 assert abs(model(parmsb, tau=TAU) - model2(parms2, tau=TAU)) < 1e-14 if __name__ == "__main__": # Run all tests loc = locals() for key in list(loc.keys()): if key.startswith("test_") and hasattr(loc[key], "__call__"): loc[key]() pycorrfit-1.1.7/tests/README.md0000664000372000037200000000030713554642611017033 0ustar travistravis00000000000000### Test Scripts Execute all tests using `setup.py` in the parent directory: python setup.py test ### Running single tests Directly execute the scripts, e.g. python test_simple.py pycorrfit-1.1.7/tests/test_global_fit.py0000664000372000037200000000264513554642611021276 0ustar travistravis00000000000000import numpy as np from pycorrfit.correlation import Correlation from pycorrfit.fit import Fit def create_corr(): n = 2.4 taud = 10 SP = 3.3 tau = np.exp(np.linspace(np.log(1e-3), np.log(1e6), 10)) corr1 = Correlation(fit_model=6002) corr1.lag_time = tau # 0: n # 1: τ_diff [ms] p1a = corr1.fit_parameters.copy() p1b = p1a.copy() p1b[0] = n p1b[1] = taud # write values and return to original corr1.fit_parameters = p1b corr1.correlation = corr1.modeled_fit.copy() corr1.fit_parameters = p1a corr1.fit_parameters_variable = [True, True, False, False, False] corr2 = Correlation(fit_model=6011) corr2.lag_time = tau # 0: n # 3: τ_diff [ms] # 4: SP p2a = corr2.fit_parameters.copy() p2b = p2a.copy() p2b[0] = n p2b[3] = taud p2b[4] = SP # write values and return to original corr2.fit_parameters = p2b corr2.correlation = corr2.modeled_fit.copy() corr2.fit_parameters = p2a corr2.fit_parameters_variable = [True, False, False, True, True, False] corrs = [corr1, corr2] initparms = np.array([n, taud, SP]) return corrs, initparms def test_globalfit(): corrs, initparms = create_corr() # commence global fit globalfit = Fit(correlations=corrs, global_fit=True) assert np.allclose(globalfit.fit_parm, initparms), "Global fit failed" if __name__ == "__main__": test_globalfit() pycorrfit-1.1.7/tests/test_simple.py0000664000372000037200000000243613554642611020463 0ustar travistravis00000000000000import numpy as np from pycorrfit.correlation import Correlation from pycorrfit.fit import Fit def create_corr(): corr = Correlation() tau = np.exp(np.linspace(np.log(1e-3), np.log(1e6), 10)) data = corr.fit_model(corr.fit_parameters, tau) noise = (np.random.random(data.shape[0])-.5)*.0005 data += noise corr.correlation = np.dstack((tau, data))[0] return corr def test_simple_corr(): corr = create_corr() oldparms = corr.fit_parameters.copy() temp = corr.fit_parameters temp[0] *= 2 temp[-1] *= .1 Fit(corr) res = oldparms - corr.fit_parameters assert np.allclose(res, np.zeros_like(res), atol=0.010) if __name__ == "__main__": import matplotlib.pylab as plt corr = create_corr() fig, (ax1, ax2) = plt.subplots(2, 1) ax1.set_xscale("log") ax2.set_xscale("log") print(corr.fit_parameters) temp = corr.fit_parameters temp[0] *= 2 temp[-1] *= .1 ax1.plot(corr.correlation_fit[:, 0], corr.correlation_fit[:, 1]) ax1.plot(corr.modeled_fit[:, 0], corr.modeled_fit[:, 1]) print(corr.fit_parameters) Fit(corr) print(corr.fit_parameters) ax2.plot(corr.correlation_fit[:, 0], corr.correlation_fit[:, 1]) ax2.plot(corr.modeled_fit[:, 0], corr.modeled_fit[:, 1]) plt.show() pycorrfit-1.1.7/tests/test_session.py0000664000372000037200000000642713554642611020661 0ustar travistravis00000000000000"""Session files""" import os import pathlib import tempfile import shutil import numpy as np import pytest import data_file_dl import pycorrfit as pcf NOAPITOKEN = "GITHUB_API_TOKEN" not in os.environ examplefile = "Zeiss_Confocor3_LSM780_FCCS_HeLa_2015/019_cp_KIND+BFA.fcs" @pytest.mark.xfail(NOAPITOKEN, reason="Restrictions to GitHub API") @pytest.mark.filterwarnings('ignore::pycorrfit.fit.StuckParameterWarning') def test_basic(): """This is a very rudimentary test for the session handling""" dfile = data_file_dl.get_data_file(examplefile) data = pcf.readfiles.open_any(dfile) corr = pcf.Correlation(correlation=data["Correlation"][0], traces=data["Trace"][0], corr_type=data["Type"][0], filename=os.path.basename(dfile), title="test correlation", fit_model=6035 # confocal 3D+3D) ) corr.fit_parameters_variable = [True, True, True, True, False, False, False] # crop triplet data corr.fit_ival[0] = 8 pcf.Fit(corr) tmpdir = tempfile.mkdtemp(prefix="pycorrfit_tests_") path = pathlib.Path(tmpdir) / "session.pcfs" fiterr = [] for ii, fitpid in enumerate(corr.fit_results["fit parameters"]): fiterr.append([int(fitpid), float(corr.fit_results["fit error estimation"][ii])]) Infodict = { "Correlations": { 1: [corr.lag_time, corr.correlation]}, "Parameters": { 1: ["#1:", corr.fit_model.id, corr.fit_parameters, corr.fit_parameters_variable, corr.fit_ival, [3, 3, 5, corr.fit_algorithm], [None, None], True, None, [[0.0, np.inf], [0.0, np.inf], [0.0, np.inf], [0.0, 0.9999999999999], [-np.inf, np.inf]] ]}, "Supplements": { 1: {"FitErr": fiterr, "Chi sq": float(corr.fit_results["chi2"]), "Global Share": [], }}, "External Functions": {}, "Traces": {}, "Comments": {"Session": "No comment."}, "Backgrounds": {}, "External Weights": {}, "Preferences": {}, } pcf.openfile.SaveSessionData(sessionfile=str(path), Infodict=Infodict) ldt = pcf.openfile.LoadSessionData(str(path)) # lag time only, shape (N,) assert np.allclose(data["Correlation"][0][:, 0], ldt["Correlations"][1][0]) # lag time and correlation, shape (N, 2) assert np.allclose(corr.correlation, ldt["Correlations"][1][1]) # parameters assert corr.fit_model.id == ldt["Parameters"][0][1] assert np.allclose(corr.fit_parameters, ldt["Parameters"][0][2]) assert np.allclose(corr.fit_parameters_variable, ldt["Parameters"][0][3]) assert np.allclose(corr.fit_ival, ldt["Parameters"][0][4]) shutil.rmtree(tmpdir, ignore_errors=True) if __name__ == "__main__": # Run all tests loc = locals() for key in list(loc.keys()): if key.startswith("test_") and hasattr(loc[key], "__call__"): loc[key]() pycorrfit-1.1.7/tests/data_file_dl.py0000664000372000037200000001311613554642611020517 0ustar travistravis00000000000000""" Download experimental data files from https://github.com/FCS-analysis/FCSdata This module establishes """ import os from os.path import abspath, dirname, join, exists import simplejson as json import urllib3 import warnings # Download path root raw_origin = "https://github.com/FCS-analysis/FCSdata/raw/master/" # GitHub API root api_origin = "https://api.github.com/repos/FCS-analysis/FCSdata/git/" # Download directory dldir = join(dirname(abspath(__file__)), "data") # Pool Manager handles all requests pool_manager = urllib3.PoolManager() _fcs_data_tree = None def dl_file(url, dest, chunk_size=6553, http=pool_manager): """ Download `url` to `dest`. Parameters ---------- url : str Full download URL dest : str Full download path. Directory will be created if non-existent. chunk_size : int Chunk size of download (download buffer size). http : instance of `urllib3.PoolManager` Manages all connections. Must implement the `request` method. """ if not exists(dirname(dest)): os.makedirs(dirname(dest)) r = http.request('GET', url, preload_content=False) with open(dest, 'wb') as out: while True: data = r.read(chunk_size) if data is None or len(data) == 0: break out.write(data) def get_data_file(filename, dldir=dldir, pool_manager=pool_manager, api_origin=api_origin, raw_origin=raw_origin): """ Return first occurrence of `filename` in the data file tree. """ _f, ext = os.path.splitext(filename) assert ext != "", "filename has no extension!" extp = ext.strip(".").lower() fbase = filename fdir = os.path.join(dldir, extp) fpath = os.path.join(fdir, fbase) if not os.path.exists(fpath): # download file if it does not exist url = raw_origin+extp+"/"+fbase dl_file(url, fpath) return fpath def get_data_files_ext(extension, dldir=dldir, pool_manager=pool_manager, api_origin=api_origin, raw_origin=raw_origin): """ Get all files in the repository `origin` that are in the folder `extension` and have a file-ending that matches `extension` (case-insensitive). The files are downloaded and local paths in the `dldir` directory are returned. If no internet connection is available, previously downloaded files will be used. Parameters ---------- extension : str A file extension such as `fcs` or `sin`. dldir : str Path to download directory. http : instance of `urllib3.PoolManager` Manages all connections. Must implement the `request` method. raw_origin : str Web root for downloads, e.g. "https://raw.github.com/FCS-analysis/FCSdata" api_origin : str GitHub api URL, e.g. "https://api.github.com/repos/FCS-analysis/FCSdata/git/" Notes ----- The files in the remote location must be sorted according to file extionsion. E.g. all `*.sin` files must be located in a folder in the root directory named `sin`. """ ext = extension.lower() if not ext.startswith("."): ext = "."+ext try: # Get file list and download files = get_data_tree_remote( pool_manager=pool_manager, api_origin=api_origin) extfiles = [f for f in files if f.lower().startswith( ext[1:]+"/") and f.lower().endswith(ext)] extfiles.sort() dl_files = [] for f in extfiles: dest = join(dldir, f) if not exists(dest): dl_file(join(raw_origin, f), dest) dl_files.append(dest) except (urllib3.exceptions.MaxRetryError, KeyError): # e.g. no internet connection warnings.warn( "No connection, using previuously downloaded files only.") files = get_data_tree_local(dldir=dldir) dl_files = [f for f in files if f.lower().endswith(ext)] return dl_files def get_data_tree_local(dldir=dldir): """ Returns FCSdata repository tree of local files. """ loc_files = [] for root, _dirs, files in os.walk(dldir): # sorting convention: the folder names the extension relpath = os.path.relpath(root, dldir) ext = os.path.basename(relpath[::-1])[::-1] for f in files: if f.lower().endswith(ext): loc_files.append(os.path.join(root, f)) return loc_files def get_data_tree_remote(pool_manager=pool_manager, api_origin=api_origin): """ Returns FCSdata repository tree. The tree is saved in the global variable `_fcs_data_tree` to reduce number of GitHub API requests. """ global _fcs_data_tree if _fcs_data_tree is None: url = api_origin+"trees/master?recursive=1" # headers headers = {'User-Agent': __file__} # GitHub API token to prevent rate-limits # Key is generated with # # gem install travis # travis encrypt GITHUB_API_TOKEN=secret-token # # Add the result to env in travis.yml. if "GITHUB_API_TOKEN" in os.environ: headers["Authorization"] = "token {}".format( os.environ["GITHUB_API_TOKEN"]) r = pool_manager.request("GET", url, headers=headers, retries=10) jd = json.loads(r.data) tree = jd["tree"] else: r = pool_manager.request("GET", url, headers=headers, retries=10) jd = json.loads(r.data) tree = jd["tree"] fcs_data_tree = [t["path"] for t in tree] return fcs_data_tree pycorrfit-1.1.7/tests/test_constraints.py0000664000372000037200000000730313554642611021537 0ustar travistravis00000000000000"""Constraints of model functions""" import os import numpy as np import pytest import data_file_dl import pycorrfit as pcf NOAPITOKEN = "GITHUB_API_TOKEN" not in os.environ examplefile = "Zeiss_Confocor3_LSM780_FCCS_HeLa_2015/019_cp_KIND+BFA.fcs" @pytest.mark.xfail(NOAPITOKEN, reason="Restrictions to GitHub API") @pytest.mark.filterwarnings('ignore::pycorrfit.fit.StuckParameterWarning') def test_fit_constraint_simple_inequality(): """ Check "smaller than" relation during fitting. """ dfile = data_file_dl.get_data_file(examplefile) data = pcf.readfiles.open_any(dfile) corr = pcf.Correlation(correlation=data["Correlation"][0], traces=data["Trace"][0], corr_type=data["Type"][0], filename=os.path.basename(dfile), title="test correlation", fit_model=6035 # confocal 3D+3D) ) corr.fit_parameters_variable = [True, True, True, True, False, False, False] # crop triplet data corr.fit_ival[0] = 8 pcf.Fit(corr) assert corr.fit_parameters[1] <= corr.fit_parameters[2] # -> deliberately reverse everything and try again corr.fit_parameters[1], corr.fit_parameters[2] = (corr.fit_parameters[2], corr.fit_parameters[1]) corr.fit_parameters[3] = 1-corr.fit_parameters[3] pcf.Fit(corr) # This tests also for equality assert corr.fit_parameters[1] <= corr.fit_parameters[2] if corr.fit_parameters[1] == corr.fit_parameters[2]: print("found identity of fit parameters - \ multiplying by two to see if relation holds") corr.fit_parameters[2] *= 2 pcf.Fit(corr) assert corr.fit_parameters[1] < corr.fit_parameters[2] @pytest.mark.xfail(NOAPITOKEN, reason="Restrictions to GitHub API") @pytest.mark.filterwarnings('ignore::pycorrfit.fit.StuckParameterWarning') def test_fit_constraint_sum_smaller_one(): """ Check "a+b deliberately reverse everything and try again corr.fit_parameters[4], corr.fit_parameters[5] = (corr.fit_parameters[5], corr.fit_parameters[4]) pcf.Fit(corr) # This tests also for equality assert corr.fit_parameters[4] + corr.fit_parameters[5] < 1 if __name__ == "__main__": # Run all tests loc = locals() for key in list(loc.keys()): if key.startswith("test_") and hasattr(loc[key], "__call__"): loc[key]() pycorrfit-1.1.7/tests/test_file_formats.py0000664000372000037200000001521413554642611021642 0ustar travistravis00000000000000"""Test support for FCS file formats""" import os from os.path import split import warnings import numpy as np import pytest import data_file_dl import pycorrfit # Files that are known to not work exclude = [] NOAPITOKEN = "GITHUB_API_TOKEN" not in os.environ @pytest.mark.xfail(NOAPITOKEN, reason="Restrictions to GitHub API") def test_asc_all_open(): # get list of supported file extensions ext = "asc" files = data_file_dl.get_data_files_ext(ext) for f in files: if [ex for ex in exclude if f.endswith(ex)]: continue dn, fn = split(f) data = pycorrfit.readfiles.open_any(dn, fn) assert data @pytest.mark.xfail(NOAPITOKEN, reason="Restrictions to GitHub API") def test_asc_alv7004usb(): """Test alv7004/USB format""" f1 = data_file_dl.get_data_file("ALV-7004USB_ac01_cc01_10.ASC") data = pycorrfit.readfiles.open_any(f1) assert data["Type"] == ["AC1", "AC2", "CC12", "CC21"] assert np.allclose(data["Correlation"][0][10], np.array([0.000275, 0.11208])) assert np.allclose(data["Correlation"][1][12], np.array([0.000325, 0.0900233])) assert np.allclose(data["Correlation"][2][18], np.array([0.00055, 0.0582773])) assert np.allclose(data["Correlation"][3][120], np.array([3.6864, 0.0224212])) assert len(data["Trace"][0]) == 253 assert len(data["Trace"][1]) == 253 assert len(data["Trace"][2]) == 2 assert len(data["Trace"][3]) == 2 assert np.all(data["Trace"][0] == data["Trace"][2][0]) assert np.all(data["Trace"][1] == data["Trace"][2][1]) assert np.all(data["Trace"][0] == data["Trace"][3][0]) assert np.all(data["Trace"][1] == data["Trace"][3][1]) assert np.allclose(data["Trace"][0][10], np.array([1289.06, 140.20404])) assert np.allclose(data["Trace"][1][100], np.array([11835.94, 94.68225])) f2 = data_file_dl.get_data_file("ALV-7004USB_dia10_cen10_0001.ASC") data2 = pycorrfit.readfiles.open_any(f2) # There are empty AC2 and CC12/CC21 curves in this file that should # be removed by pycorrfit. assert data2["Type"] == ["AC1"] assert np.allclose(data2["Correlation"][0][56], np.array([0.0144, 0.0513857])) assert len(data2["Trace"][0]) == 254 assert np.allclose(data2["Trace"][0][210], np.array([49453.13, 165.41434])) f3 = data_file_dl.get_data_file("ALV-7004.ASC") data3 = pycorrfit.readfiles.open_any(f3) assert len(data3["Type"]) == 1 assert len(data3["Trace"][0]) == 66 assert data3["Type"][0] == "AC" assert np.allclose(data3["Correlation"][0][56], np.array([0.0144, 0.38757])) assert np.allclose(data3["Trace"][0][60], np.array([1.21523440e5, 5.11968700e1])) f4 = data_file_dl.get_data_file("ALV-7004USB_ac3.ASC") data4 = pycorrfit.readfiles.open_any(f4) assert len(data4["Type"]) == 1 assert data4["Type"][0] == "AC" assert len(data4["Trace"][0]) == 254 @pytest.mark.xfail(NOAPITOKEN, reason="Restrictions to GitHub API") def test_csv_all_open(): # get list of supported file extensions ext = "csv" files = data_file_dl.get_data_files_ext(ext) for f in files: if [ex for ex in exclude if f.endswith(ex)]: continue dn, fn = split(f) data = pycorrfit.readfiles.open_any(dn, fn) assert data @pytest.mark.xfail(NOAPITOKEN, reason="Restrictions to GitHub API") def test_fcs_all_open(): # get list of supported file extensions ext = "fcs" files = data_file_dl.get_data_files_ext(ext) for f in files: if [ex for ex in exclude if f.endswith(ex)]: continue dn, fn = split(f) data = pycorrfit.readfiles.open_any(dn, fn) assert data @pytest.mark.xfail(NOAPITOKEN, reason="Restrictions to GitHub API") def test_fcs_channel_names(): """Test arbitrary channel names (instead of just "1" and "2" The file '001_A488_channel_names.fcs' was created manually. """ fref = data_file_dl.get_data_file("Zeiss_Confocor3_A488+GFP/001_A488.fcs") fed = data_file_dl.get_data_file("001_A488_channel_names.fcs") dataref = pycorrfit.readfiles.open_any(fref) dataed = pycorrfit.readfiles.open_any(fed) assert np.all(dataref["Correlation"][0] == dataed["Correlation"][0]) assert np.all(dataref["Correlation"][1] == dataed["Correlation"][1]) assert np.all(dataref["Correlation"][2] == dataed["Correlation"][2]) assert np.all(dataref["Correlation"][3] == dataed["Correlation"][3]) assert np.all(dataref["Trace"][0] == dataed["Trace"][0]) assert np.all(dataref["Trace"][1] == dataed["Trace"][1]) assert np.all(dataref["Trace"][2][0] == dataed["Trace"][2][0]) assert np.all(dataref["Trace"][2][1] == dataed["Trace"][2][1]) assert np.all(dataref["Trace"][3][0] == dataed["Trace"][3][0]) assert np.all(dataref["Trace"][3][1] == dataed["Trace"][3][1]) assert dataref["Type"] == dataed["Type"] assert dataref["Filename"] != dataed["Filename"] @pytest.mark.xfail(NOAPITOKEN, reason="Restrictions to GitHub API") def test_pt3_all_open(): # get list of supported file extensions ext = "pt3" files = data_file_dl.get_data_files_ext(ext) for f in files: if [ex for ex in exclude if f.endswith(ex)]: continue dn, fn = split(f) data = pycorrfit.readfiles.open_any(dn, fn) assert data @pytest.mark.xfail(NOAPITOKEN, reason="Restrictions to GitHub API") def test_pt3_basic(): f1 = data_file_dl.get_data_file( "PicoQuant_SymphoTime32_A42F-4jul2014/Point_1.pt3") data = pycorrfit.readfiles.open_any(f1) trace = data["Trace"][0][0] assert trace.shape == (600, 2) try: assert np.allclose(trace[40], np.array([2037, 6.48])) except AssertionError: warnings.warn("Unknown pt3 problem after migration to Python3!") corr = data["Correlation"][0] assert corr.shape == (150, 2) assert np.allclose(corr[40], np.array([0.000698, 0.58007174877053136])) assert np.allclose(corr[100], np.array([0.72089, 0.019201608388821567])) @pytest.mark.xfail(NOAPITOKEN, reason="Restrictions to GitHub API") def test_sin_all_open(): # get list of supported file extensions ext = "sin" files = data_file_dl.get_data_files_ext(ext) for f in files: if [ex for ex in exclude if f.endswith(ex)]: continue dn, fn = split(f) data = pycorrfit.readfiles.open_any(dn, fn) assert data if __name__ == "__main__": # Run all tests loc = locals() for key in sorted(list(loc.keys())): if key.startswith("test_") and hasattr(loc[key], "__call__"): loc[key]() pycorrfit-1.1.7/pycorrfit.egg-info/0000775000372000037200000000000013554643106020125 5ustar travistravis00000000000000pycorrfit-1.1.7/pycorrfit.egg-info/PKG-INFO0000664000372000037200000000412213554643106021221 0ustar travistravis00000000000000Metadata-Version: 2.1 Name: pycorrfit Version: 1.1.7 Summary: Scientific tool for fitting correlation curves on a logarithmic plot. Home-page: https://github.com/FCS-analysis/PyCorrFit Author: Paul Müller Author-email: dev@craban.de License: GPL v2 Description: |PyCorrFit| =========== |PyPI Version| |Build Status Win| |Build Status Travis| |Coverage Status| |Docs Status| Documentation ------------- The documentation of PyCorrFit is available at https://pycorrfit.readthedocs.org. Problems -------- If you find a bug or need help with a specific topic, do not hesitate to ask a question at the `issues page `__. .. |PyCorrFit| image:: https://raw.github.com/FCS-analysis/PyCorrFit/master/doc/Images/PyCorrFit_logo_dark.png .. |PyPI Version| image:: https://img.shields.io/pypi/v/PyCorrFit.svg :target: https://pypi.python.org/pypi/pycorrfit .. |Build Status Win| image:: https://img.shields.io/appveyor/ci/paulmueller/PyCorrFit/master.svg?label=win :target: https://ci.appveyor.com/project/paulmueller/pycorrfit .. |Build Status Travis| image:: https://img.shields.io/travis/FCS-analysis/PyCorrFit/master.svg?label=linux_osx :target: https://travis-ci.org/FCS-analysis/PyCorrFit .. |Coverage Status| image:: https://img.shields.io/codecov/c/github/FCS-analysis/PyCorrFit/master.svg :target: https://codecov.io/gh/FCS-analysis/PyCorrFit .. |Docs Status| image:: https://readthedocs.org/projects/pycorrfit/badge/?version=latest :target: https://readthedocs.org/projects/pycorrfit/builds/ Keywords: fluorescence correlation spectroscopy Platform: ALL Classifier: Operating System :: OS Independent Classifier: Programming Language :: Python :: 3 Classifier: Topic :: Scientific/Engineering :: Visualization Classifier: Intended Audience :: Science/Research Requires-Python: >=3.6, <4 Provides-Extra: GUI pycorrfit-1.1.7/pycorrfit.egg-info/dependency_links.txt0000664000372000037200000000000113554643106024173 0ustar travistravis00000000000000 pycorrfit-1.1.7/pycorrfit.egg-info/top_level.txt0000664000372000037200000000001213554643106022650 0ustar travistravis00000000000000pycorrfit pycorrfit-1.1.7/pycorrfit.egg-info/SOURCES.txt0000664000372000037200000001220613554643106022012 0ustar travistravis00000000000000CHANGELOG LICENSE MANIFEST.in README.rst setup.cfg setup.py doc/Bibliography.bib doc/PyCorrFit_doc.pdf doc/PyCorrFit_doc.tex doc/PyCorrFit_doc_content.tex doc/PyCorrFit_doc_models.tex doc/README.md doc/Images/PyCorrFit_Screenshot_CSFCS.png doc/Images/PyCorrFit_Screenshot_Main.png doc/Images/PyCorrFit_icon.png doc/Images/PyCorrFit_icon.svg doc/Images/PyCorrFit_icon_dark.svg doc/Images/PyCorrFit_logo.svg doc/Images/PyCorrFit_logo_dark.pdf doc/Images/PyCorrFit_logo_dark.png doc/Images/PyCorrFit_logo_dark.svg docs/README.md docs/conf.py docs/index.rst docs/requirements.txt docs/sec_about.rst docs/sec_changelog.rst docs/sec_contribute.rst docs/sec_gallery.rst docs/sec_getting_started.rst docs/_static/PyCorrFit_logo_dark.png docs/extensions/github_changelog.py docs/extensions/simple_gallery.py docs/gallery/Screenshot_Desktop.png docs/gallery/Screenshot_Desktop_Mac.png docs/gallery/Screenshot_Desktop_Raspbian_Jessie.png docs/gallery/Screenshot_Desktop_Win.png docs/gallery/Screenshot_Graphics_output.png docs/gallery/Screenshot_Main.png docs/gallery/Screenshot_Select_curves.png docs/gallery/Screenshot_Trace_view.png examples/external_model_functions/ExampleFunc_CS_2D+2D+S+T.txt examples/external_model_functions/ExampleFunc_CS_3D+S+T.txt examples/external_model_functions/ExampleFunc_Exp_correlated_noise.txt examples/external_model_functions/ExampleFunc_SFCS_1C_2D_Autocorrelation.txt examples/external_model_functions/ExampleFunc_SFCS_1C_2D_Cross-correlation.txt examples/external_model_functions/ExampleFunc_TIRF_zOnly.txt examples/external_model_functions/Model_AC_3D+T_confocal.txt examples/external_model_functions/Model_Flow_AC_3D_confocal.txt examples/external_model_functions/Model_Flow_CC_Backward_3D_confocal.txt examples/external_model_functions/Model_Flow_CC_Forward_3D_confocal.txt pycorrfit/PyCorrFit.py pycorrfit/__init__.py pycorrfit/__main__.py pycorrfit/_version.py pycorrfit/_version_save.py pycorrfit/correlation.py pycorrfit/fit.py pycorrfit/meta.py pycorrfit/openfile.py pycorrfit/trace.py pycorrfit.egg-info/PKG-INFO pycorrfit.egg-info/SOURCES.txt pycorrfit.egg-info/dependency_links.txt pycorrfit.egg-info/entry_points.txt pycorrfit.egg-info/requires.txt pycorrfit.egg-info/top_level.txt pycorrfit/gui/__init__.py pycorrfit/gui/contribute.py pycorrfit/gui/doc.py pycorrfit/gui/edclasses.py pycorrfit/gui/frontend.py pycorrfit/gui/icon.py pycorrfit/gui/main.py pycorrfit/gui/misc.py pycorrfit/gui/page.py pycorrfit/gui/plotting.py pycorrfit/gui/threaded_progress.py pycorrfit/gui/update.py pycorrfit/gui/usermodel.py pycorrfit/gui/wxutils.py pycorrfit/gui/tools/__init__.py pycorrfit/gui/tools/average.py pycorrfit/gui/tools/background.py pycorrfit/gui/tools/batchcontrol.py pycorrfit/gui/tools/chooseimport.py pycorrfit/gui/tools/comment.py pycorrfit/gui/tools/datarange.py pycorrfit/gui/tools/example.py pycorrfit/gui/tools/globalfit.py pycorrfit/gui/tools/info.py pycorrfit/gui/tools/overlaycurves.py pycorrfit/gui/tools/parmrange.py pycorrfit/gui/tools/plotexport.py pycorrfit/gui/tools/simulation.py pycorrfit/gui/tools/statistics.py pycorrfit/gui/tools/trace.py pycorrfit/models/MODEL_TIRF_1C.py pycorrfit/models/MODEL_TIRF_2D2D.py pycorrfit/models/MODEL_TIRF_3D2D.py pycorrfit/models/MODEL_TIRF_3D2Dkin_Ries.py pycorrfit/models/MODEL_TIRF_3D3D.py pycorrfit/models/MODEL_TIRF_gaussian_1C.py pycorrfit/models/MODEL_TIRF_gaussian_3D2D.py pycorrfit/models/MODEL_TIRF_gaussian_3D3D.py pycorrfit/models/__init__.py pycorrfit/models/classes.py pycorrfit/models/control.py pycorrfit/models/cp_confocal.py pycorrfit/models/cp_mix.py pycorrfit/models/cp_triplet.py pycorrfit/models/model_confocal_2d.py pycorrfit/models/model_confocal_2d_2d.py pycorrfit/models/model_confocal_3d.py pycorrfit/models/model_confocal_3d_2d.py pycorrfit/models/model_confocal_3d_3d.py pycorrfit/models/model_confocal_t_2d.py pycorrfit/models/model_confocal_t_2d_2d.py pycorrfit/models/model_confocal_t_3d.py pycorrfit/models/model_confocal_t_3d_2d.py pycorrfit/models/model_confocal_t_3d_3d.py pycorrfit/models/model_confocal_t_3d_3d_2d.py pycorrfit/models/model_confocal_t_3d_3d_3d.py pycorrfit/models/model_confocal_tt_2d.py pycorrfit/models/model_confocal_tt_2d_2d.py pycorrfit/models/model_confocal_tt_3d.py pycorrfit/models/model_confocal_tt_3d_2d.py pycorrfit/models/model_confocal_tt_3d_3d.py pycorrfit/readfiles/__init__.py pycorrfit/readfiles/read_ASC_ALV.py pycorrfit/readfiles/read_CSV_PyCorrFit.py pycorrfit/readfiles/read_FCS_Confocor3.py pycorrfit/readfiles/read_SIN_correlator_com.py pycorrfit/readfiles/read_mat_ries.py pycorrfit/readfiles/read_pt3_PicoQuant.py pycorrfit/readfiles/util.py pycorrfit/readfiles/read_pt3_scripts/README pycorrfit/readfiles/read_pt3_scripts/__init__.py pycorrfit/readfiles/read_pt3_scripts/__version__.py pycorrfit/readfiles/read_pt3_scripts/correlation_methods.py pycorrfit/readfiles/read_pt3_scripts/correlation_objects.py pycorrfit/readfiles/read_pt3_scripts/fib4.pyx pycorrfit/readfiles/read_pt3_scripts/fitting_methods.py pycorrfit/readfiles/read_pt3_scripts/import_methods.py tests/README.md tests/data_file_dl.py tests/test_constraints.py tests/test_file_formats.py tests/test_fit_model_gaussian.py tests/test_fit_models.py tests/test_global_fit.py tests/test_session.py tests/test_simple.pypycorrfit-1.1.7/pycorrfit.egg-info/requires.txt0000664000372000037200000000016613554643106022530 0ustar travistravis00000000000000lmfit>=0.9.2 numpy>=1.14.2 pyyaml>=3.12 scipy>=1.0.1 [GUI] matplotlib>=2.2.2 sympy>=1.1.1 simplejson wxPython>=4.0.1 pycorrfit-1.1.7/pycorrfit.egg-info/entry_points.txt0000664000372000037200000000006313554643106023422 0ustar travistravis00000000000000[gui_scripts] pycorrfit = pycorrfit.gui.main:Main